SHA256
Сервер: удалить manual import и AddUser слой
This commit is contained in:
@@ -10,9 +10,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SolanaUsersDAO — локальная таблица пользователей из Solana.
|
||||
* SolanaUsersDAO — совместимый runtime-доступ к текущему срезу пользователей.
|
||||
*
|
||||
* Таблица: solana_users
|
||||
* Источник:
|
||||
* - solana_user_pda_current
|
||||
*
|
||||
* Колонки:
|
||||
* - login TEXT PRIMARY KEY
|
||||
@@ -41,40 +42,6 @@ public final class SolanaUsersDAO {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// -------------------- INSERT --------------------
|
||||
|
||||
/** Вставка с внешним соединением. Соединение НЕ закрывает. */
|
||||
public void insert(Connection c, SolanaUserEntry user) throws SQLException {
|
||||
String sql = """
|
||||
INSERT INTO solana_users_manual (
|
||||
login, blockchain_name, solana_key, blockchain_key, client_key, updated_at_ms
|
||||
) VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
blockchain_name = EXCLUDED.blockchain_name,
|
||||
solana_key = EXCLUDED.solana_key,
|
||||
blockchain_key = EXCLUDED.blockchain_key,
|
||||
client_key = EXCLUDED.client_key,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, user.getLogin());
|
||||
ps.setString(2, user.getBlockchainName());
|
||||
ps.setString(3, user.getSolanaKey());
|
||||
ps.setString(4, user.getBlockchainKey());
|
||||
ps.setString(5, user.getClientKey());
|
||||
ps.setLong(6, System.currentTimeMillis());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/** Вставка без внешнего соединения. Сам открывает/закрывает. */
|
||||
public void insert(SolanaUserEntry user) throws SQLException {
|
||||
try (Connection c = db.getConnection()) {
|
||||
insert(c, user);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- EXISTS --------------------
|
||||
|
||||
/** Проверка существования по login (case-insensitive) с внешним соединением. Соединение НЕ закрывает. */
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
package shine.db.dao;
|
||||
|
||||
import shine.db.DbController;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
/**
|
||||
* UserCreateDAO — атомарное добавление пользователя:
|
||||
* - solana_users (login, blockchain_name, solana_key, blockchain_key, client_key)
|
||||
* - blockchain_state (blockchain_name, login, blockchain_key, size_limit, ... last_block_number=-1 ...)
|
||||
*
|
||||
* ВАЖНО:
|
||||
* - только INSERT (без перезаписи существующих записей)
|
||||
* - если login или blockchainName заняты — возвращаем false (пользователь уже есть/занято)
|
||||
*/
|
||||
public final class UserCreateDAO {
|
||||
|
||||
private static volatile UserCreateDAO instance;
|
||||
private final DbController db = DbController.getInstance();
|
||||
private final SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
|
||||
|
||||
private UserCreateDAO() {}
|
||||
|
||||
public static UserCreateDAO getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (UserCreateDAO.class) {
|
||||
if (instance == null) instance = new UserCreateDAO();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true если добавили; false если занято (login уже есть или blockchainName уже существует).
|
||||
*/
|
||||
public boolean insertUserWithBlockchain(
|
||||
String login,
|
||||
String blockchainName,
|
||||
String solanaKey,
|
||||
String blockchainKey,
|
||||
String clientKey,
|
||||
long sizeLimit,
|
||||
long nowMs
|
||||
) throws SQLException {
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean oldAuto = c.getAutoCommit();
|
||||
c.setAutoCommit(false);
|
||||
|
||||
try {
|
||||
// 1) solana_users
|
||||
SolanaUserEntry u = new SolanaUserEntry();
|
||||
u.setLogin(login);
|
||||
u.setBlockchainName(blockchainName);
|
||||
u.setSolanaKey(solanaKey);
|
||||
u.setBlockchainKey(blockchainKey);
|
||||
u.setClientKey(clientKey);
|
||||
|
||||
usersDao.insert(c, u); // если login занят (NOCASE) или blockchainName (unique) -> constraint
|
||||
|
||||
// 2) blockchain_state — строго INSERT, без UPSERT (иначе можно перезаписать существующую цепочку)
|
||||
insertBlockchainStateStrict(
|
||||
c,
|
||||
blockchainName,
|
||||
login,
|
||||
blockchainKey,
|
||||
sizeLimit,
|
||||
nowMs
|
||||
);
|
||||
|
||||
c.commit();
|
||||
return true;
|
||||
|
||||
} catch (SQLException e) {
|
||||
c.rollback();
|
||||
|
||||
String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
|
||||
if (msg.contains("constraint")) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
|
||||
} finally {
|
||||
c.setAutoCommit(oldAuto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void insertBlockchainStateStrict(
|
||||
Connection c,
|
||||
String blockchainName,
|
||||
String login,
|
||||
String blockchainKey,
|
||||
long sizeLimit,
|
||||
long nowMs
|
||||
) throws SQLException {
|
||||
|
||||
String sql = """
|
||||
INSERT INTO blockchain_state (
|
||||
blockchain_name,
|
||||
login,
|
||||
blockchain_key,
|
||||
size_limit,
|
||||
file_size_bytes,
|
||||
last_block_number,
|
||||
last_block_hash,
|
||||
updated_at_ms
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
int i = 1;
|
||||
ps.setString(i++, blockchainName);
|
||||
ps.setString(i++, login);
|
||||
ps.setString(i++, blockchainKey);
|
||||
|
||||
ps.setLong(i++, sizeLimit);
|
||||
ps.setLong(i++, 0L);
|
||||
|
||||
ps.setInt(i++, -1);
|
||||
ps.setNull(i++, Types.BINARY); // старт: блоков ещё нет
|
||||
ps.setLong(i++, nowMs);
|
||||
|
||||
ps.executeUpdate(); // если blockchainName занят -> constraint (PK)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,21 +17,6 @@ public final class SolanaUsersSql {
|
||||
current_users.blockchain_key AS blockchain_key,
|
||||
current_users.client_key AS client_key
|
||||
FROM solana_user_pda_current current_users
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
manual.login AS login,
|
||||
manual.blockchain_name AS blockchain_name,
|
||||
manual.solana_key AS solana_key,
|
||||
manual.blockchain_key AS blockchain_key,
|
||||
manual.client_key AS client_key
|
||||
FROM solana_users_manual manual
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM solana_user_pda_current current_users
|
||||
WHERE LOWER(current_users.login) = LOWER(manual.login)
|
||||
)
|
||||
) %s
|
||||
""".formatted(alias);
|
||||
}
|
||||
|
||||
@@ -111,40 +111,6 @@ CREATE TABLE IF NOT EXISTS solana_user_pda_current (
|
||||
CREATE INDEX IF NOT EXISTS idx_user_pda_current_slot
|
||||
ON solana_user_pda_current(slot);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS solana_users_manual (
|
||||
login TEXT PRIMARY KEY,
|
||||
blockchain_name TEXT NOT NULL UNIQUE,
|
||||
solana_key TEXT NOT NULL,
|
||||
blockchain_key TEXT NOT NULL,
|
||||
client_key TEXT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_solana_users_manual_login
|
||||
ON solana_users_manual(login);
|
||||
|
||||
CREATE OR REPLACE VIEW solana_users AS
|
||||
SELECT
|
||||
current_users.login AS login,
|
||||
current_users.blockchain_name AS blockchain_name,
|
||||
current_users.client_key AS solana_key,
|
||||
current_users.blockchain_key AS blockchain_key,
|
||||
current_users.client_key AS client_key
|
||||
FROM solana_user_pda_current current_users
|
||||
UNION ALL
|
||||
SELECT
|
||||
manual.login,
|
||||
manual.blockchain_name,
|
||||
manual.solana_key,
|
||||
manual.blockchain_key,
|
||||
manual.client_key
|
||||
FROM solana_users_manual manual
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM solana_user_pda_current current_users
|
||||
WHERE LOWER(current_users.login) = LOWER(manual.login)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS solana_user_pda_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tx_signature TEXT NOT NULL,
|
||||
|
||||
-5
@@ -43,9 +43,6 @@ import server.logic.ws_protocol.JSON.handlers.blockchain.Net_GetBlockchainBlock_
|
||||
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_GetBlockchainBlock_Request;
|
||||
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_AddUser_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Request;
|
||||
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_GetUser_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_TestGetFreeAvatarQuota_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.Net_TestUploadFreeAvatar_Handler;
|
||||
@@ -142,7 +139,6 @@ public final class JsonHandlerRegistry {
|
||||
}
|
||||
|
||||
private static final Map<String, JsonMessageHandler> HANDLERS = Map.ofEntries(
|
||||
Map.entry("AddUser", new Net_AddUser_Handler()),
|
||||
Map.entry("GetUser", new Net_GetUser_Handler()),
|
||||
Map.entry("SearchUsers", new Net_SearchUsers_Handler()),
|
||||
Map.entry("TestGetFreeAvatarQuota", new Net_TestGetFreeAvatarQuota_Handler()),
|
||||
@@ -223,7 +219,6 @@ public final class JsonHandlerRegistry {
|
||||
);
|
||||
|
||||
private static final Map<String, Class<? extends Net_Request>> REQUEST_TYPES = Map.ofEntries(
|
||||
Map.entry("AddUser", Net_AddUser_Request.class),
|
||||
Map.entry("GetUser", Net_GetUser_Request.class),
|
||||
Map.entry("SearchUsers", Net_SearchUsers_Request.class),
|
||||
Map.entry("TestGetFreeAvatarQuota", Net_TestGetFreeAvatarQuota_Request.class),
|
||||
|
||||
+2
-18
@@ -26,7 +26,7 @@ import java.security.SecureRandom;
|
||||
*
|
||||
* Что делает:
|
||||
* 1) Проверяет login.
|
||||
* 2) Находит пользователя (solana_users).
|
||||
* 2) Находит пользователя в текущем PDA snapshot.
|
||||
* 3) Пишет solanaUser в ctx, ставит AUTH_STATUS_AUTH_IN_PROGRESS.
|
||||
* 4) Генерирует authNonce (base64url(32)) и сохраняет в ctx.authNonce.
|
||||
*/
|
||||
@@ -61,25 +61,9 @@ public class Net_AuthChallenge_Handler implements JsonMessageHandler {
|
||||
}
|
||||
|
||||
SolanaUserEntry solanaUserEntry = SolanaUsersDAO.getInstance().getByLogin(login);
|
||||
if (solanaUserEntry == null) {
|
||||
try {
|
||||
solanaUserEntry = SolanaUserPdaImportService.findOrImportByLogin(login);
|
||||
if (solanaUserEntry != null) {
|
||||
log.info("AuthChallenge: пользователь {} импортирован из Solana PDA", solanaUserEntry.getLogin());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("AuthChallenge: ошибка lazy-import пользователя {} из Solana", login, e);
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
"SOLANA_IMPORT_FAILED",
|
||||
"Ошибка проверки пользователя в Solana"
|
||||
);
|
||||
}
|
||||
}
|
||||
if (solanaUserEntry == null) {
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
req,
|
||||
WireCodes.Status.UNVERIFIED,
|
||||
"UNKNOWN_USER",
|
||||
"Пользователь с таким логином не найден"
|
||||
|
||||
+1
-33
@@ -4,9 +4,6 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.dao.UserCreateDAO;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
import utils.config.SolanaProgramsConfig;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -22,8 +19,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lazy-import пользователя из Solana PDA в локальную БД сервера.
|
||||
* Используется при входе, если в solana_users нет записи по login.
|
||||
* Чтение пользовательских и серверных PDA из Solana.
|
||||
*/
|
||||
public final class SolanaUserPdaImportService {
|
||||
|
||||
@@ -34,34 +30,6 @@ public final class SolanaUserPdaImportService {
|
||||
|
||||
private SolanaUserPdaImportService() {}
|
||||
|
||||
public static SolanaUserEntry findOrImportByLogin(String loginRaw) throws Exception {
|
||||
String login = normalizeLogin(loginRaw);
|
||||
if (login == null) return null;
|
||||
|
||||
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
|
||||
SolanaUserEntry existing = usersDao.getByLogin(login);
|
||||
if (existing != null) return existing;
|
||||
|
||||
ParsedSolanaUser parsed = fetchFromSolana(login);
|
||||
if (parsed == null) return null;
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long sizeLimit = parsed.paidLimitBytes > 0 ? parsed.paidLimitBytes : 100_000L;
|
||||
boolean inserted = UserCreateDAO.getInstance().insertUserWithBlockchain(
|
||||
parsed.login,
|
||||
parsed.blockchainName,
|
||||
parsed.clientKeyB64, // в текущей модели solanaKey = clientKey
|
||||
parsed.blockchainKeyB64,
|
||||
parsed.clientKeyB64,
|
||||
sizeLimit,
|
||||
now
|
||||
);
|
||||
if (!inserted) {
|
||||
return usersDao.getByLogin(login);
|
||||
}
|
||||
return usersDao.getByLogin(login);
|
||||
}
|
||||
|
||||
public static SessionTypeCheckResult checkSessionTypeAgainstPda(String loginRaw, String sessionKeyApi, int requestedSessionType) throws Exception {
|
||||
String login = normalizeLogin(loginRaw);
|
||||
if (login == null) return SessionTypeCheckResult.noRecord();
|
||||
|
||||
+4
-40
@@ -33,46 +33,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
1. Добавление пользователя (AddUser)
|
||||
1. Пользователь уже существует в актуальном PDA snapshot
|
||||
|
||||
Назначение: создать локальную запись пользователя с двумя ключами — solanaKey и clientKey.
|
||||
|
||||
📤 Запрос клиента
|
||||
{
|
||||
"op": "AddUser",
|
||||
"requestId": "req-1",
|
||||
"payload": {
|
||||
"login": "anya4",
|
||||
"loginId": 100212,
|
||||
"bchId": 4222,
|
||||
"solanaKey": "BASE64_LOGIN_KEY",
|
||||
"clientKey": "BASE64_DEVICE_KEY",
|
||||
"bchLimit": 1000000
|
||||
}
|
||||
}
|
||||
|
||||
🖥 Действия сервера
|
||||
|
||||
Проверяет корректность данных.
|
||||
|
||||
Вставляет запись в таблицу:
|
||||
|
||||
CREATE TABLE solana_users (
|
||||
login TEXT NOT NULL,
|
||||
loginId INTEGER PRIMARY KEY,
|
||||
bchId INTEGER NOT NULL,
|
||||
solanaKey TEXT,
|
||||
clientKey TEXT,
|
||||
bchLimit INTEGER
|
||||
);
|
||||
|
||||
📥 Ответ
|
||||
{
|
||||
"op": "AddUser",
|
||||
"requestId": "req-1",
|
||||
"status": 200,
|
||||
"payload": { "ok": true }
|
||||
}
|
||||
Назначение: перед началом auth-flow сервер уже должен видеть пользователя
|
||||
в актуальном `solana_user_pda_current`.
|
||||
|
||||
2. Шаг 1 — запрос временного пароля сессии
|
||||
AuthChallenge
|
||||
@@ -173,4 +137,4 @@ pushAuthKey TEXT
|
||||
}
|
||||
|
||||
📘 Итоговая схема создания сессии
|
||||
AddUser → AuthChallenge → CreateAuthSession → Session Created
|
||||
PDA snapshot ready → AuthChallenge → CreateAuthSession → Session Created
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.tempToTest;
|
||||
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Request;
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
|
||||
/**
|
||||
* AddUser отключен: регистрация работает через Solana.
|
||||
*/
|
||||
public class Net_AddUser_Handler implements JsonMessageHandler {
|
||||
|
||||
@Override
|
||||
public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) {
|
||||
Net_AddUser_Request req = (Net_AddUser_Request) baseRequest;
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
410,
|
||||
"ADD_USER_DISABLED",
|
||||
"Серверная регистрация AddUser отключена. Используйте регистрацию через Solana."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
-4
@@ -5,7 +5,6 @@ import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.auth.SolanaUserPdaImportService;
|
||||
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_GetUser_Request;
|
||||
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_GetUser_Response;
|
||||
@@ -43,9 +42,6 @@ public class Net_GetUser_Handler implements JsonMessageHandler {
|
||||
|
||||
try {
|
||||
SolanaUserEntry u = usersDAO.getByLogin(req.getLogin());
|
||||
if (u == null) {
|
||||
u = SolanaUserPdaImportService.findOrImportByLogin(req.getLogin());
|
||||
}
|
||||
|
||||
Net_GetUser_Response resp = new Net_GetUser_Response();
|
||||
resp.setOp(req.getOp());
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Request;
|
||||
|
||||
/**
|
||||
* Запрос AddUser — временная/тестовая регистрация локального пользователя.
|
||||
*
|
||||
* Клиент отправляет:
|
||||
*
|
||||
* {
|
||||
* "op": "AddUser",
|
||||
* "requestId": "test-add-1",
|
||||
* "payload": {
|
||||
* "login": "anya",
|
||||
* "blockchainName": "anya-001",
|
||||
* "solanaKey": "base64-ed25519-public-key-login",
|
||||
* "blockchainKey": "base64-ed25519-public-key-blockchain",
|
||||
* "clientKey": "base64-ed25519-public-key-device",
|
||||
* "bchLimit": 1000000
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Все поля лежат внутри payload.
|
||||
*/
|
||||
public class Net_AddUser_Request extends Net_Request {
|
||||
|
||||
private String login;
|
||||
private String blockchainName;
|
||||
|
||||
/** Ключ пользователя Solana (публичный ключ логина) */
|
||||
private String solanaKey;
|
||||
|
||||
/** Ключ блокчейна (публичный ключ блокчейна) */
|
||||
private String blockchainKey;
|
||||
|
||||
/** Ключ устройства (публичный ключ устройства) */
|
||||
private String clientKey;
|
||||
|
||||
private Integer bchLimit;
|
||||
|
||||
public String getLogin() { return login; }
|
||||
public void setLogin(String login) { this.login = login; }
|
||||
|
||||
public String getBlockchainName() { return blockchainName; }
|
||||
public void setBlockchainName(String blockchainName) { this.blockchainName = blockchainName; }
|
||||
|
||||
public String getSolanaKey() { return solanaKey; }
|
||||
public void setSolanaKey(String solanaKey) { this.solanaKey = solanaKey; }
|
||||
|
||||
public String getBlockchainKey() { return blockchainKey; }
|
||||
public void setBlockchainKey(String blockchainKey) { this.blockchainKey = blockchainKey; }
|
||||
|
||||
public String getClientKey() { return clientKey; }
|
||||
public void setClientKey(String clientKey) { this.clientKey = clientKey; }
|
||||
|
||||
public Integer getBchLimit() { return bchLimit; }
|
||||
public void setBchLimit(Integer bchLimit) { this.bchLimit = bchLimit; }
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
// file: server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_AddUser_Response.java
|
||||
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
|
||||
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
|
||||
/**
|
||||
* Успешный ответ на AddUser.
|
||||
*
|
||||
* Сейчас дополнительных полей нет — достаточно status=200.
|
||||
*
|
||||
* Пример:
|
||||
* {
|
||||
* "op": "AddUser",
|
||||
* "requestId": "test-add-1",
|
||||
* "status": 200,
|
||||
* "payload": { }
|
||||
* }
|
||||
*/
|
||||
public class Net_AddUser_Response extends Net_Response {
|
||||
// При необходимости сюда можно добавить, например, флаг created/updated и т.п.
|
||||
}
|
||||
+1
-5
@@ -1,6 +1,5 @@
|
||||
package server.logic.ws_protocol.JSON.messages;
|
||||
|
||||
import server.logic.ws_protocol.JSON.handlers.auth.SolanaUserPdaImportService;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.SignedMessageV2Entry;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
@@ -62,10 +61,7 @@ final class SignedMessagesCore {
|
||||
}
|
||||
|
||||
private static SolanaUserEntry resolveUser(String login) throws Exception {
|
||||
SolanaUsersDAO usersDao = SolanaUsersDAO.getInstance();
|
||||
SolanaUserEntry user = usersDao.getByLogin(login);
|
||||
if (user != null) return user;
|
||||
return SolanaUserPdaImportService.findOrImportByLogin(login);
|
||||
return SolanaUsersDAO.getInstance().getByLogin(login);
|
||||
}
|
||||
|
||||
static void validatePair(SignedMessageBlock incoming, SignedMessageBlock outgoing) {
|
||||
|
||||
@@ -4,15 +4,12 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Exception_Response;
|
||||
import server.logic.ws_protocol.JSON.entyties.Net_Response;
|
||||
import server.logic.ws_protocol.JSON.handlers.auth.SolanaUserPdaImportService;
|
||||
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler;
|
||||
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils.BlockchainLocks;
|
||||
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
|
||||
import shine.db.dao.BlockchainResyncCleanupDAO;
|
||||
import shine.db.dao.BlockchainStateDAO;
|
||||
import shine.db.dao.SyncServersDAO;
|
||||
import shine.db.dao.UserCreateDAO;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.dao.SolanaUserPdaCurrentDAO;
|
||||
import shine.db.entities.BlockchainStateEntry;
|
||||
import shine.db.entities.SolanaUserPdaCurrentEntry;
|
||||
@@ -20,7 +17,6 @@ import shine.db.entities.SyncServerEntry;
|
||||
import server.sync.BlockchainResyncGuard;
|
||||
import utils.files.FileStoreUtil;
|
||||
import utils.blockchain.BlockchainNameUtil;
|
||||
import utils.config.AppConfig;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.List;
|
||||
@@ -55,12 +51,9 @@ public final class PeriodicBlockchainSyncService {
|
||||
private static final Net_AddBlock_Handler ADD_BLOCK_HANDLER = new Net_AddBlock_Handler();
|
||||
private static final BlockchainStateDAO STATE_DAO = BlockchainStateDAO.getInstance();
|
||||
private static final SyncServersDAO SYNC_SERVERS_DAO = SyncServersDAO.getInstance();
|
||||
private static final UserCreateDAO USER_CREATE_DAO = UserCreateDAO.getInstance();
|
||||
private static final BlockchainResyncCleanupDAO RESYNC_CLEANUP_DAO = BlockchainResyncCleanupDAO.getInstance();
|
||||
private static final FileStoreUtil FILE_STORE = FileStoreUtil.getInstance();
|
||||
private static final SolanaUsersDAO SOLANA_USERS_DAO = SolanaUsersDAO.getInstance();
|
||||
private static final SolanaUserPdaCurrentDAO SOLANA_USER_PDA_CURRENT_DAO = SolanaUserPdaCurrentDAO.getInstance();
|
||||
private static final String CONFIG_IMPORT_PROFILE_FROM_PARTNER = "sync.importUserProfileFromPartner.enabled";
|
||||
|
||||
private PeriodicBlockchainSyncService() {}
|
||||
|
||||
@@ -370,11 +363,7 @@ public final class PeriodicBlockchainSyncService {
|
||||
if (login == null || login.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
if (AppConfig.getInstance().getBoolean(CONFIG_IMPORT_PROFILE_FROM_PARTNER, false)) {
|
||||
return importUserProfileFromPartner(partner, login);
|
||||
}
|
||||
SolanaUserPdaImportService.findOrImportByLogin(login);
|
||||
return STATE_DAO.getByBlockchainName(blockchainName) != null;
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.warn("Periodic blockchain sync: failed to ensure local chain exists for blockchainName={} reason={}",
|
||||
blockchainName, String.valueOf(e));
|
||||
@@ -416,67 +405,6 @@ public final class PeriodicBlockchainSyncService {
|
||||
return keyBytes != null && keyBytes.length == 32;
|
||||
}
|
||||
|
||||
private static boolean importUserProfileFromPartner(SyncServerEntry partner, String login) throws Exception {
|
||||
if (partner == null || partner.getServerAddress() == null || partner.getServerAddress().isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoteBlockchainSyncClient.RemoteSyncUserProfile profile =
|
||||
REMOTE.getSyncUserProfile(partner.getServerAddress(), login);
|
||||
if (profile == null) {
|
||||
log.warn("Periodic blockchain sync: partner has no sync profile for login={} partner={}",
|
||||
login, normalize(partner.getLogin()));
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long sizeLimit = profile.blockchainSizeLimitBytes() > 0 ? profile.blockchainSizeLimitBytes() : 100_000L;
|
||||
BlockchainStateEntry state = buildStateFromProfile(profile, sizeLimit, now);
|
||||
|
||||
if (SOLANA_USERS_DAO.existsByLogin(profile.login())) {
|
||||
STATE_DAO.insertIfMissing(state);
|
||||
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
|
||||
}
|
||||
|
||||
boolean inserted = USER_CREATE_DAO.insertUserWithBlockchain(
|
||||
profile.login(),
|
||||
profile.blockchainName(),
|
||||
profile.solanaKey(),
|
||||
profile.blockchainKey(),
|
||||
profile.clientKey(),
|
||||
sizeLimit,
|
||||
now
|
||||
);
|
||||
|
||||
if (inserted) {
|
||||
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
|
||||
}
|
||||
|
||||
// Если пользователь уже успел существовать локально, но chain_state отсутствует,
|
||||
// добиваем только state и не пытаемся пересоздать identity.
|
||||
if (SOLANA_USERS_DAO.existsByLogin(profile.login())) {
|
||||
STATE_DAO.insertIfMissing(state);
|
||||
return STATE_DAO.getByBlockchainName(profile.blockchainName()) != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static BlockchainStateEntry buildStateFromProfile(RemoteBlockchainSyncClient.RemoteSyncUserProfile profile,
|
||||
long sizeLimit,
|
||||
long nowMs) {
|
||||
BlockchainStateEntry state = new BlockchainStateEntry();
|
||||
state.setBlockchainName(profile.blockchainName());
|
||||
state.setLogin(profile.login());
|
||||
state.setBlockchainKey(profile.blockchainKey());
|
||||
state.setSizeLimit(sizeLimit);
|
||||
state.setFileSizeBytes(0L);
|
||||
state.setLastBlockNumber(-1);
|
||||
state.setLastBlockHash(null);
|
||||
state.setUpdatedAtMs(nowMs);
|
||||
return state;
|
||||
}
|
||||
|
||||
private static String normalize(String value) {
|
||||
if (value == null) return null;
|
||||
String s = value.trim().toLowerCase(Locale.ROOT);
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
package test.it.cases;
|
||||
|
||||
import test.it.utils.TestConfig;
|
||||
import test.it.utils.TestIds;
|
||||
import test.it.utils.json.JsonBuilders;
|
||||
import test.it.utils.json.JsonParsers;
|
||||
import test.it.utils.log.TestResult;
|
||||
import test.it.utils.ws.WsSession;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* IT_01_AddUser
|
||||
* Создаёт 3 пользователей: TestUser1/2/3 (200 OK или 409 USER_ALREADY_EXISTS).
|
||||
*
|
||||
* Обновление:
|
||||
* - теперь AddUser может вернуть 409 не только USER_ALREADY_EXISTS,
|
||||
* но и BLOCKCHAIN_ALREADY_EXISTS / BLOCKCHAIN_STATE_ALREADY_EXISTS.
|
||||
* - дополнительно проверяем GetUser (status=200 всегда).
|
||||
* - добавлен SearchUsers: поиск по префиксу (первые 3 символа).
|
||||
*/
|
||||
public class IT_01_AddUser {
|
||||
|
||||
public static void main(String[] args) {
|
||||
String summary = run();
|
||||
System.out.println(summary);
|
||||
}
|
||||
|
||||
public static String run() {
|
||||
TestResult r = new TestResult("IT_01_AddUser");
|
||||
|
||||
Duration t = Duration.ofSeconds(5);
|
||||
|
||||
try (WsSession ws = WsSession.open()) {
|
||||
|
||||
checkPingAndServerInfo(r, ws, t);
|
||||
|
||||
r.ok("AddUser USER1: " + TestConfig.LOGIN());
|
||||
String resp1 = ws.call("AddUser#USER1", JsonBuilders.addUser(TestConfig.LOGIN()), t);
|
||||
checkAddUser200or409(r, resp1);
|
||||
checkGetUserMustExist(r, ws, TestConfig.LOGIN(), t);
|
||||
|
||||
r.ok("AddUser USER2: " + TestConfig.LOGIN2());
|
||||
String resp2 = ws.call("AddUser#USER2", JsonBuilders.addUser(TestConfig.LOGIN2()), t);
|
||||
checkAddUser200or409(r, resp2);
|
||||
checkGetUserMustExist(r, ws, TestConfig.LOGIN2(), t);
|
||||
|
||||
r.ok("AddUser USER3: " + TestConfig.LOGIN3());
|
||||
String resp3 = ws.call("AddUser#USER3", JsonBuilders.addUser(TestConfig.LOGIN3()), t);
|
||||
checkAddUser200or409(r, resp3);
|
||||
checkGetUserMustExist(r, ws, TestConfig.LOGIN3(), t);
|
||||
|
||||
// Доп: проверяем case-insensitive поиск в GetUser
|
||||
String mixed = mixCase(TestConfig.LOGIN());
|
||||
r.ok("GetUser case-insensitive: запрос=" + mixed + " (должен найти " + TestConfig.LOGIN() + ")");
|
||||
checkGetUserMustExist(r, ws, mixed, t);
|
||||
|
||||
// Доп: проверяем "не существует" (но status=200)
|
||||
String missing = "NoSuchUser_987654321";
|
||||
r.ok("GetUser missing: " + missing);
|
||||
checkGetUserMustNotExist(r, ws, missing, t);
|
||||
|
||||
// SearchUsers: один раз ищем по первым трём символам логина USER1
|
||||
String prefix3 = first3(TestConfig.LOGIN());
|
||||
String prefix3Mixed = mixCase(prefix3);
|
||||
r.ok("SearchUsers: prefix(3)='" + prefix3Mixed + "' (должен вернуть список и содержать " + TestConfig.LOGIN() + ")");
|
||||
checkSearchUsersMustContain(r, ws, prefix3Mixed, TestConfig.LOGIN(), t);
|
||||
|
||||
checkNegativeRequests(r, ws, t);
|
||||
|
||||
} catch (Throwable e) {
|
||||
r.fail("IT_01_AddUser упал: " + e.getMessage());
|
||||
}
|
||||
|
||||
return r.summaryLine();
|
||||
}
|
||||
|
||||
private static void checkPingAndServerInfo(TestResult r, WsSession ws, Duration t) {
|
||||
String pingResp = ws.call("Ping", JsonBuilders.ping(System.currentTimeMillis()), t);
|
||||
if (JsonParsers.status(pingResp) != 200 || !Boolean.TRUE.equals(JsonParsers.ok(pingResp))) {
|
||||
r.fail("Ping: ожидали status=200 и ok=true, resp=" + pingResp);
|
||||
fail("Ping unexpected response");
|
||||
}
|
||||
|
||||
Long serverTs = JsonParsers.pingTs(pingResp);
|
||||
if (serverTs == null || serverTs <= 0) {
|
||||
r.fail("Ping: сервер не вернул ts, resp=" + pingResp);
|
||||
fail("Ping missing ts");
|
||||
}
|
||||
r.ok("Ping: ok, serverTs=" + serverTs);
|
||||
|
||||
String infoResp = ws.call("GetServerInfo", JsonBuilders.getServerInfo(), t);
|
||||
if (JsonParsers.status(infoResp) != 200 || !Boolean.TRUE.equals(JsonParsers.ok(infoResp))) {
|
||||
r.fail("GetServerInfo: ожидали status=200 и ok=true, resp=" + infoResp);
|
||||
fail("GetServerInfo unexpected response");
|
||||
}
|
||||
if (!JsonParsers.payloadIsObject(infoResp)) {
|
||||
r.fail("GetServerInfo: payload должен быть объектом, resp=" + infoResp);
|
||||
fail("GetServerInfo payload is not object");
|
||||
}
|
||||
|
||||
r.ok("GetServerInfo: ok, url='" + safe(JsonParsers.payloadText(infoResp, "url"))
|
||||
+ "', version='" + safe(JsonParsers.payloadText(infoResp, "version"))
|
||||
+ "', physicalRegion='" + safe(JsonParsers.payloadText(infoResp, "physicalRegion"))
|
||||
+ "', description='" + safe(JsonParsers.payloadText(infoResp, "description"))
|
||||
+ "', origin='" + safe(JsonParsers.payloadText(infoResp, "origin"))
|
||||
+ "', extraInfo='" + safe(JsonParsers.payloadText(infoResp, "extraInfo")) + "'");
|
||||
}
|
||||
|
||||
private static void checkAddUser200or409(TestResult r, String resp) {
|
||||
int st = JsonParsers.status(resp);
|
||||
if (st == 200) {
|
||||
r.ok("AddUser: status=200 (создан)");
|
||||
return;
|
||||
}
|
||||
if (st == 409) {
|
||||
String code = JsonParsers.errorCode(resp);
|
||||
|
||||
// раньше был только USER_ALREADY_EXISTS, теперь добавились ещё варианты
|
||||
if ("USER_ALREADY_EXISTS".equals(code)) {
|
||||
r.ok("AddUser: status=409 USER_ALREADY_EXISTS (уже был)");
|
||||
return;
|
||||
}
|
||||
if ("BLOCKCHAIN_ALREADY_EXISTS".equals(code)) {
|
||||
r.ok("AddUser: status=409 BLOCKCHAIN_ALREADY_EXISTS (blockchainName уже занят)");
|
||||
return;
|
||||
}
|
||||
if ("BLOCKCHAIN_STATE_ALREADY_EXISTS".equals(code)) {
|
||||
r.ok("AddUser: status=409 BLOCKCHAIN_STATE_ALREADY_EXISTS (blockchain_state уже есть)");
|
||||
return;
|
||||
}
|
||||
|
||||
r.fail("AddUser: status=409 но code=" + code + ", resp=" + resp);
|
||||
fail("AddUser unexpected 409 code=" + code);
|
||||
}
|
||||
r.fail("AddUser: неожиданный status=" + st + ", resp=" + resp);
|
||||
fail("AddUser unexpected status=" + st);
|
||||
}
|
||||
|
||||
private static void checkGetUserMustExist(TestResult r, WsSession ws, String loginQuery, Duration t) {
|
||||
String resp = ws.call("GetUser#" + loginQuery, JsonBuilders.getUser(loginQuery), t);
|
||||
|
||||
int st = JsonParsers.status(resp);
|
||||
if (st != 200) {
|
||||
r.fail("GetUser: ожидали status=200, получили " + st + ", resp=" + resp);
|
||||
fail("GetUser unexpected status=" + st);
|
||||
}
|
||||
|
||||
Boolean exists = JsonParsers.exists(resp);
|
||||
if (exists == null || !exists) {
|
||||
r.fail("GetUser: ожидали exists=true, resp=" + resp);
|
||||
fail("GetUser expected exists=true");
|
||||
}
|
||||
|
||||
// Проверяем, что сервер возвращает данные
|
||||
String login = JsonParsers.userLogin(resp);
|
||||
String blockchainName = JsonParsers.userBlockchainName(resp);
|
||||
String solanaKey = JsonParsers.userSolanaKey(resp);
|
||||
String blockchainKey = JsonParsers.userBlockchainKey(resp);
|
||||
String clientKey = JsonParsers.userClientKey(resp);
|
||||
|
||||
if (isBlank(login) || isBlank(blockchainName) || isBlank(solanaKey) || isBlank(blockchainKey) || isBlank(clientKey)) {
|
||||
r.fail("GetUser: exists=true, но поля пустые/неполные, resp=" + resp);
|
||||
fail("GetUser returned incomplete user data");
|
||||
}
|
||||
|
||||
// ВАЖНО:
|
||||
// Поиск делается без учета регистра, но login/blockchainName должны вернуться как в БД.
|
||||
// Для тех логинов, которые мы создаем в тесте, это ровно TestConfig.LOGIN*().
|
||||
// Поэтому если запрос был смешанный регистр — сравниваем не с loginQuery, а с "каноничным" логином из конфига.
|
||||
String canonical = canonicalLogin(loginQuery);
|
||||
if (canonical != null) {
|
||||
if (!login.equals(canonical)) {
|
||||
r.fail("GetUser: login должен вернуться как в БД. expected=" + canonical + ", got=" + login + ", resp=" + resp);
|
||||
fail("GetUser wrong login case");
|
||||
}
|
||||
|
||||
String expectedBch = TestConfig.getBlockchainName(canonical);
|
||||
if (!blockchainName.equals(expectedBch)) {
|
||||
r.fail("GetUser: blockchainName должен вернуться как в БД. expected=" + expectedBch + ", got=" + blockchainName + ", resp=" + resp);
|
||||
fail("GetUser wrong blockchainName");
|
||||
}
|
||||
|
||||
// ключи должны совпадать с теми, что AddUser использует при регистрации
|
||||
String expSol = TestConfig.solanaPublicKeyB64(canonical);
|
||||
String expBchKey = TestConfig.blockchainPublicKeyB64(canonical);
|
||||
String expDev = TestConfig.clientPublicKeyB64(canonical);
|
||||
|
||||
if (!solanaKey.equals(expSol)) {
|
||||
r.fail("GetUser: solanaKey mismatch, resp=" + resp);
|
||||
fail("GetUser solanaKey mismatch");
|
||||
}
|
||||
if (!blockchainKey.equals(expBchKey)) {
|
||||
r.fail("GetUser: blockchainKey mismatch, resp=" + resp);
|
||||
fail("GetUser blockchainKey mismatch");
|
||||
}
|
||||
if (!clientKey.equals(expDev)) {
|
||||
r.fail("GetUser: clientKey mismatch, resp=" + resp);
|
||||
fail("GetUser clientKey mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
r.ok("GetUser: exists=true, login=" + login + ", blockchainName=" + blockchainName);
|
||||
}
|
||||
|
||||
private static void checkGetUserMustNotExist(TestResult r, WsSession ws, String loginQuery, Duration t) {
|
||||
String resp = ws.call("GetUser#" + loginQuery, JsonBuilders.getUser(loginQuery), t);
|
||||
|
||||
int st = JsonParsers.status(resp);
|
||||
if (st != 200) {
|
||||
r.fail("GetUser(not exist): ожидали status=200, получили " + st + ", resp=" + resp);
|
||||
fail("GetUser(not exist) unexpected status=" + st);
|
||||
}
|
||||
|
||||
Boolean exists = JsonParsers.exists(resp);
|
||||
if (exists == null) {
|
||||
r.fail("GetUser(not exist): payload.exists отсутствует, resp=" + resp);
|
||||
fail("GetUser(not exist) missing exists");
|
||||
}
|
||||
if (exists) {
|
||||
r.fail("GetUser(not exist): ожидали exists=false, resp=" + resp);
|
||||
fail("GetUser(not exist) expected exists=false");
|
||||
}
|
||||
|
||||
r.ok("GetUser: exists=false (ok)");
|
||||
}
|
||||
|
||||
private static void checkSearchUsersMustContain(TestResult r, WsSession ws, String prefix, String expectedLogin, Duration t) {
|
||||
String resp = ws.call("SearchUsers#" + prefix, JsonBuilders.searchUsers(prefix), t);
|
||||
|
||||
int st = JsonParsers.status(resp);
|
||||
if (st != 200) {
|
||||
r.fail("SearchUsers: ожидали status=200, получили " + st + ", resp=" + resp);
|
||||
fail("SearchUsers unexpected status=" + st);
|
||||
}
|
||||
|
||||
List<String> logins = JsonParsers.searchLogins(resp);
|
||||
if (logins == null || logins.isEmpty()) {
|
||||
r.fail("SearchUsers: ожидали непустой список, resp=" + resp);
|
||||
fail("SearchUsers expected non-empty list");
|
||||
}
|
||||
|
||||
// ВАЖНО: ожидаемый логин должен быть в ответе в регистре БД (каноничный expectedLogin)
|
||||
boolean found = false;
|
||||
for (String s : logins) {
|
||||
if (expectedLogin.equals(s)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
r.fail("SearchUsers: ожидаемый логин не найден. expected=" + expectedLogin + ", got=" + logins + ", resp=" + resp);
|
||||
fail("SearchUsers expected login not found");
|
||||
}
|
||||
|
||||
r.ok("SearchUsers: ok, prefix=" + prefix + ", results=" + logins.size() + ", contains=" + expectedLogin);
|
||||
}
|
||||
|
||||
private static void checkNegativeRequests(TestResult r, WsSession ws, Duration t) {
|
||||
String badAddUserReqId = TestIds.next("bad-adduser");
|
||||
String badAddUser = """
|
||||
{
|
||||
"op": "AddUser",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"login": "",
|
||||
"blockchainName": "%s",
|
||||
"solanaKey": "%s",
|
||||
"blockchainKey": "%s",
|
||||
"clientKey": "%s",
|
||||
"bchLimit": %d
|
||||
}
|
||||
}
|
||||
""".formatted(
|
||||
badAddUserReqId,
|
||||
TestConfig.BCH_NAME(),
|
||||
TestConfig.SOLANA_PUBKEY_B64(),
|
||||
TestConfig.BLOCKCHAIN_PUBKEY_B64(),
|
||||
TestConfig.DEVICE_PUBKEY_B64(),
|
||||
TestConfig.TEST_BCH_LIMIT
|
||||
);
|
||||
String badAddUserResp = ws.call("AddUser#NEGATIVE", badAddUser, t);
|
||||
assertErrorFormat(badAddUserResp, "AddUser", badAddUserReqId, "BAD_FIELDS");
|
||||
r.ok("Negative AddUser: error format OK");
|
||||
|
||||
String badGetUserReqId = TestIds.next("bad-getuser");
|
||||
String badGetUser = """
|
||||
{
|
||||
"op": "GetUser",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"login": ""
|
||||
}
|
||||
}
|
||||
""".formatted(badGetUserReqId);
|
||||
String badGetUserResp = ws.call("GetUser#NEGATIVE", badGetUser, t);
|
||||
assertErrorFormat(badGetUserResp, "GetUser", badGetUserReqId, "BAD_FIELDS");
|
||||
r.ok("Negative GetUser: error format OK");
|
||||
|
||||
String badSearchReqId = TestIds.next("bad-searchusers");
|
||||
String badSearch = """
|
||||
{
|
||||
"op": "SearchUsers",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"prefix": ""
|
||||
}
|
||||
}
|
||||
""".formatted(badSearchReqId);
|
||||
String badSearchResp = ws.call("SearchUsers#NEGATIVE", badSearch, t);
|
||||
assertErrorFormat(badSearchResp, "SearchUsers", badSearchReqId, "BAD_FIELDS");
|
||||
r.ok("Negative SearchUsers: error format OK");
|
||||
}
|
||||
|
||||
private static void assertErrorFormat(String resp, String op, String requestId, String code) {
|
||||
int status = JsonParsers.status(resp);
|
||||
if (status >= 200 && status < 300) fail("Expected non-2xx status: " + resp);
|
||||
if (!Boolean.FALSE.equals(JsonParsers.ok(resp))) fail("Expected ok=false: " + resp);
|
||||
if (!op.equals(JsonParsers.op(resp))) fail("Unexpected op: " + resp);
|
||||
if (!requestId.equals(JsonParsers.requestId(resp))) fail("Unexpected requestId: " + resp);
|
||||
if (!code.equals(JsonParsers.errorCode(resp))) fail("Unexpected error code: " + resp);
|
||||
if (!JsonParsers.payloadIsObject(resp)) fail("payload must be object: " + resp);
|
||||
if (JsonParsers.payloadSize(resp) != 0) fail("error payload must be empty object: " + resp);
|
||||
if (isBlank(JsonParsers.message(resp))) fail("error message must be present: " + resp);
|
||||
}
|
||||
|
||||
private static String canonicalLogin(String anyCaseLogin) {
|
||||
if (anyCaseLogin == null) return null;
|
||||
String x = anyCaseLogin.trim();
|
||||
if (x.isEmpty()) return null;
|
||||
|
||||
// Привязка только к нашим тестовым логинам, чтобы не гадать.
|
||||
if (x.equalsIgnoreCase(TestConfig.LOGIN())) return TestConfig.LOGIN();
|
||||
if (x.equalsIgnoreCase(TestConfig.LOGIN2())) return TestConfig.LOGIN2();
|
||||
if (x.equalsIgnoreCase(TestConfig.LOGIN3())) return TestConfig.LOGIN3();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String mixCase(String s) {
|
||||
if (s == null) return null;
|
||||
String x = s.trim();
|
||||
if (x.length() < 2) return x;
|
||||
// простой "микс" без рандома, чтобы тест был детерминированный
|
||||
return Character.toUpperCase(x.charAt(0)) + x.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
private static String first3(String s) {
|
||||
if (s == null) return "";
|
||||
String x = s.trim();
|
||||
if (x.length() <= 3) return x;
|
||||
return x.substring(0, 3);
|
||||
}
|
||||
|
||||
private static boolean isBlank(String s) {
|
||||
return s == null || s.trim().isEmpty();
|
||||
}
|
||||
|
||||
private static String safe(String s) {
|
||||
return s == null ? "" : s;
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,7 @@ public class IT_02_Sessions {
|
||||
private static final String LOGIN = TestConfig.LOGIN();
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> сначала запускаю IT_01_AddUser");
|
||||
System.out.println(IT_01_AddUser.run());
|
||||
TestLog.info("Standalone: этот тест требует заранее существующего пользователя в актуальном PDA snapshot");
|
||||
String summary = run();
|
||||
System.out.println(summary);
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
public class IT_03_AddBlock_NoAuth {
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> запускаю IT_01_AddUser");
|
||||
System.out.println(IT_01_AddUser.run());
|
||||
TestLog.info("Standalone: этот тест требует заранее существующих пользователей в актуальном PDA snapshot");
|
||||
String summary = run();
|
||||
System.out.println(summary);
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ public class IT_04_UserParams_NoAuth {
|
||||
private static final ObjectMapper M = new ObjectMapper();
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestLog.info("Standalone: этот тест требует заранее созданных пользователей -> сначала запускаю IT_01_AddUser");
|
||||
System.out.println(IT_01_AddUser.run());
|
||||
TestLog.info("Standalone: этот тест требует заранее существующего пользователя в актуальном PDA snapshot");
|
||||
String summary = run();
|
||||
System.out.println(summary);
|
||||
}
|
||||
@@ -283,4 +282,4 @@ public class IT_04_UserParams_NoAuth {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import test.it.utils.log.TestResult;
|
||||
import test.it.utils.ws.WsSession;
|
||||
import utils.crypto.Ed25519Util;
|
||||
import shine.db.dao.SolanaUsersDAO;
|
||||
import shine.db.entities.SolanaUserEntry;
|
||||
import utils.crypto.HashSHA256Util;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -22,7 +21,7 @@ public class IT_07_EspPairing {
|
||||
private static final String LOGIN = TestConfig.LOGIN();
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestLog.info("Standalone: при необходимости локально создаю тестового пользователя напрямую в БД");
|
||||
TestLog.info("Standalone: этот тест требует заранее существующего пользователя в актуальном PDA snapshot");
|
||||
String summary = run();
|
||||
System.out.println(summary);
|
||||
}
|
||||
@@ -211,13 +210,7 @@ public class IT_07_EspPairing {
|
||||
if (SolanaUsersDAO.getInstance().existsByLogin(LOGIN)) {
|
||||
return;
|
||||
}
|
||||
SolanaUserEntry entry = new SolanaUserEntry();
|
||||
entry.setLogin(LOGIN);
|
||||
entry.setBlockchainName(TestConfig.getBlockchainName(LOGIN));
|
||||
entry.setSolanaKey(TestConfig.solanaPublicKeyB64(LOGIN));
|
||||
entry.setBlockchainKey(TestConfig.blockchainPublicKeyB64(LOGIN));
|
||||
entry.setClientKey(TestConfig.clientPublicKeyB64(LOGIN));
|
||||
SolanaUsersDAO.getInstance().insert(entry);
|
||||
throw new IllegalStateException("Тестовый пользователь отсутствует в актуальном PDA snapshot: " + LOGIN);
|
||||
}
|
||||
|
||||
private static String derivePairingHash(String login, String password) {
|
||||
|
||||
@@ -7,8 +7,6 @@ import blockchain.body.TextBody;
|
||||
import blockchain.body.UserParamBody;
|
||||
import test.it.blockchain.AddBlockSender;
|
||||
import test.it.blockchain.ChainState;
|
||||
import test.it.utils.TestIds;
|
||||
import test.it.utils.json.JsonParsers;
|
||||
import test.it.utils.log.TestResult;
|
||||
import test.it.utils.ws.WsSession;
|
||||
import utils.crypto.Ed25519Util;
|
||||
@@ -25,7 +23,8 @@ import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Вспомогательные функции для массового заполнения тестовой соц-сети через API.
|
||||
* Вся запись состояния делается только через AddUser / AddBlock.
|
||||
* Пользователи должны уже существовать в актуальном PDA snapshot сервера.
|
||||
* Вся запись блокчейн-состояния делается через AddBlock.
|
||||
*/
|
||||
public final class SeedDataPopulationHelper {
|
||||
|
||||
@@ -52,7 +51,6 @@ public final class SeedDataPopulationHelper {
|
||||
}
|
||||
|
||||
public void createUsersAndHeaders(List<UserSpec> users) {
|
||||
for (UserSpec user : users) createUserViaApi(user.login);
|
||||
for (UserSpec user : users) initHeader(user.login);
|
||||
}
|
||||
|
||||
@@ -159,48 +157,6 @@ public final class SeedDataPopulationHelper {
|
||||
), timeout);
|
||||
}
|
||||
|
||||
private void createUserViaApi(String login) {
|
||||
String requestId = TestIds.next("seed_adduser");
|
||||
String req = """
|
||||
{
|
||||
"op": "AddUser",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"login": "%s",
|
||||
"blockchainName": "%s",
|
||||
"solanaKey": "%s",
|
||||
"blockchainKey": "%s",
|
||||
"clientKey": "%s",
|
||||
"bchLimit": 50000000
|
||||
}
|
||||
}
|
||||
""".formatted(
|
||||
requestId,
|
||||
login,
|
||||
bch(login),
|
||||
keys.solanaPublicB64,
|
||||
keys.blockchainPublicB64,
|
||||
keys.clientPublicB64
|
||||
);
|
||||
|
||||
String resp = ws.call("AddUser#" + login, req, timeout);
|
||||
int status = JsonParsers.status(resp);
|
||||
if (status == 200) {
|
||||
result.ok("AddUser " + login + ": created");
|
||||
return;
|
||||
}
|
||||
if (status == 409) {
|
||||
String code = JsonParsers.errorCode(resp);
|
||||
if ("USER_ALREADY_EXISTS".equals(code)
|
||||
|| "BLOCKCHAIN_ALREADY_EXISTS".equals(code)
|
||||
|| "BLOCKCHAIN_STATE_ALREADY_EXISTS".equals(code)) {
|
||||
result.ok("AddUser " + login + ": already exists (" + code + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("AddUser failed for " + login + ": status=" + status + ", resp=" + resp);
|
||||
}
|
||||
|
||||
private void initHeader(String login) {
|
||||
ChainState state = new ChainState();
|
||||
AddBlockSender sender = new AddBlockSender(ws, state, login, bch(login), keys.blockchainPrivate32);
|
||||
|
||||
@@ -12,7 +12,8 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
* Seed_TestDataPopulation
|
||||
*
|
||||
* Заполняет тестовую соц-сеть через API:
|
||||
* - пользователи + HEADER через AddUser/AddBlock;
|
||||
* - пользователи должны уже существовать в актуальном PDA snapshot;
|
||||
* - HEADER и остальные блоки пишутся через AddBlock;
|
||||
* - параметры профиля (имя, фамилия, пол, адрес, web, phone, official, shine) через AddBlock USER_PARAM;
|
||||
* - связи (close friend/contact/follow/parent/child/sibling) через AddBlock CONNECTION;
|
||||
* - посты через AddBlock TEXT_POST.
|
||||
@@ -34,7 +35,7 @@ public class Seed_TestDataPopulation {
|
||||
SeedDataPopulationHelper seed = new SeedDataPopulationHelper(ws, result, timeout, PASSWORD);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// 1) Пользователи и профили
|
||||
// 1) Заголовки и профили
|
||||
// -----------------------------------------------------------------
|
||||
List<SeedDataPopulationHelper.UserSpec> users = buildUsers();
|
||||
seed.createUsersAndHeaders(users);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package test.it.runner;
|
||||
|
||||
import test.it.cases.IT_01_AddUser;
|
||||
import test.it.cases.IT_00_TechnicalRequests;
|
||||
import test.it.cases.IT_02_Sessions;
|
||||
import test.it.cases.IT_03_AddBlock_NoAuth;
|
||||
@@ -44,9 +43,6 @@ public class IT_RunAllMain {
|
||||
String s0 = IT_00_TechnicalRequests.run(); summaries.add(s0);
|
||||
if (s0.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
|
||||
|
||||
String s1 = IT_01_AddUser.run(); summaries.add(s1);
|
||||
if (s1.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
|
||||
|
||||
String s2 = IT_02_Sessions.run(); summaries.add(s2);
|
||||
if (s2.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); }
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package test.it.suite;
|
||||
|
||||
import org.junit.platform.suite.api.SelectClasses;
|
||||
import org.junit.platform.suite.api.Suite;
|
||||
import test.it.cases.IT_01_AddUser;
|
||||
import test.it.cases.IT_02_Sessions;
|
||||
import test.it.cases.IT_03_AddBlock_NoAuth;
|
||||
import test.it.cases.Seed_TestDataPopulation;
|
||||
@@ -15,7 +14,6 @@ import test.it.cases.Seed_TestDataPopulation;
|
||||
*/
|
||||
@Suite
|
||||
@SelectClasses({
|
||||
IT_01_AddUser.class,
|
||||
IT_02_Sessions.class,
|
||||
IT_03_AddBlock_NoAuth.class,
|
||||
Seed_TestDataPopulation.class
|
||||
|
||||
@@ -126,12 +126,12 @@ public final class TestConfig {
|
||||
public static String BCH_NAME2() { return getBlockchainName(LOGIN2()); }
|
||||
public static String BCH_NAME3() { return getBlockchainName(LOGIN3()); }
|
||||
|
||||
/** solanaKey для AddUser: публичный ключ Solana-пользователя */
|
||||
/** публичный ключ Solana-пользователя */
|
||||
public static String SOLANA_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN()); }
|
||||
public static String SOLANA2_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN2()); }
|
||||
public static String SOLANA3_PUBKEY_B64() { return solanaPublicKeyB64(LOGIN3()); }
|
||||
|
||||
/** blockchainKey для AddUser: публичный ключ блокчейна */
|
||||
/** публичный ключ блокчейна */
|
||||
public static String BLOCKCHAIN_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN()); }
|
||||
public static String BLOCKCHAIN2_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN2()); }
|
||||
public static String BLOCKCHAIN3_PUBKEY_B64() { return blockchainPublicKeyB64(LOGIN3()); }
|
||||
|
||||
@@ -11,40 +11,6 @@ import java.util.Base64;
|
||||
public final class JsonBuilders {
|
||||
private JsonBuilders() {}
|
||||
|
||||
// ---------------- AddUser ----------------
|
||||
|
||||
public static String addUser(String login) {
|
||||
String requestId = TestIds.next("adduser");
|
||||
String blockchainName = TestConfig.getBlockchainName(login);
|
||||
|
||||
String solanaKeyB64 = TestConfig.solanaPublicKeyB64(login);
|
||||
String blockchainKeyB64 = TestConfig.blockchainPublicKeyB64(login);
|
||||
String clientKeyB64 = TestConfig.clientPublicKeyB64(login);
|
||||
|
||||
return """
|
||||
{
|
||||
"op": "AddUser",
|
||||
"requestId": "%s",
|
||||
"payload": {
|
||||
"login": "%s",
|
||||
"blockchainName": "%s",
|
||||
"solanaKey": "%s",
|
||||
"blockchainKey": "%s",
|
||||
"clientKey": "%s",
|
||||
"bchLimit": %d
|
||||
}
|
||||
}
|
||||
""".formatted(
|
||||
requestId,
|
||||
login,
|
||||
blockchainName,
|
||||
solanaKeyB64,
|
||||
blockchainKeyB64,
|
||||
clientKeyB64,
|
||||
TestConfig.TEST_BCH_LIMIT
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- GetUser ----------------
|
||||
|
||||
public static String getUser(String login) {
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
client.version=1.2.354
|
||||
server.version=1.2.333
|
||||
server.version=1.2.334
|
||||
|
||||
Reference in New Issue
Block a user