ОГО доделал и тесты (теперь два пользователя добавляется и меж ними есть связи) вроде всё работает
This commit is contained in:
AidarKC 2026-01-02 19:09:17 +03:00
parent c3d20ba338
commit 432b574592
6 changed files with 389 additions and 224 deletions

View File

@ -1,32 +1,49 @@
package test.it; package test.it;
import blockchain.body.ConnectionBody;
import blockchain.body.HeaderBody; import blockchain.body.HeaderBody;
import blockchain.body.ReactionBody; import blockchain.body.ReactionBody;
import blockchain.body.TextBody; import blockchain.body.TextBody;
import blockchain.body.UserParamBody;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import test.it.addBlockUtils.AddBlockSender; import test.it.addBlockUtils.AddBlockSender;
import test.it.addBlockUtils.ChainState; import test.it.addBlockUtils.ChainState;
import test.it.utils.ItRunContext; import test.it.utils.*;
import test.it.utils.TestConfig; import utils.crypto.Ed25519Util;
import test.it.utils.TestLog;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.Base64;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
/** /**
* IT_03_AddBlock_NoAuth * IT_03_AddBlock_NoAuth
* *
* Теперь тест максимально "линейный": * ОБЪЕДИНЕНО: прежний IT_03 + прежний IT_04 в одном тесте,
* - создаём только Body * чтобы "четвёртый" сценарий гарантированно запускался сразу после "третьего".
* - sender.send(body) делает всё остальное (номера, prev-hash, подпись, отправка, проверка, state)
* *
* ДОБАВЛЕНО: * Сценарий:
* - 2 reply на старые сообщения (включая reply на reply) * 1) AddUser(USER1) 200 или 409 USER_ALREADY_EXISTS
* - ещё 1 реакция (вторая) * 2) AddUser(USER2) 200 или 409 USER_ALREADY_EXISTS
*
* 3) USER1: HEADER + 3 NEW + 2 REPLY + 2 REACT (как было)
* 4) USER2: HEADER + UserParams(name+address) + Connection(FRIEND -> USER1)
* 5) USER1: UserParams(name+surname) + Connection(FRIEND -> USER2) + Connection(FOLLOW -> USER2)
*
* Важно:
* - у каждого пользователя СВОЙ ChainState
* - AddBlockSender создаём с новой сигнатурой:
* new AddBlockSender(state, login, blockchainName, loginPrivKey)
* - USER2 ключи делаем детерминированно из login (как в ItRunContext), но локально.
*/ */
public class IT_03_AddBlock_NoAuth { public class IT_03_AddBlock_NoAuth {
// ===== USER2 (константы прямо тут, чтобы не ломать твой TestConfig) =====
private static final String USER2_LOGIN = "Anya2";
private static final String BCH_SUFFIX_3 = "001";
private static final String USER2_BCH = USER2_LOGIN + BCH_SUFFIX_3;
public static void main(String[] args) { public static void main(String[] args) {
int failed = run(); int failed = run();
// System.exit(failed); // System.exit(failed);
@ -39,7 +56,7 @@ public class IT_03_AddBlock_NoAuth {
@BeforeAll @BeforeAll
static void ensureUserExists() { static void ensureUserExists() {
ItRunContext.initIfNeeded(); ItRunContext.initIfNeeded();
// как и было: предусловие можно включить потом // можно оставить пустым, как у тебя
} }
private static void testBody() { private static void testBody() {
@ -48,51 +65,79 @@ public class IT_03_AddBlock_NoAuth {
Duration t = Duration.ofSeconds(1); Duration t = Duration.ofSeconds(1);
// =========================================================
// 1) AddUser(USER1)
// =========================================================
addUserOr409AlreadyExists(
"USER1",
TestConfig.LOGIN(),
TestConfig.BCH_NAME(),
TestConfig.LOGIN_PUBKEY_B64(),
TestConfig.DEVICE_PUBKEY_B64()
);
// =========================================================
// 2) AddUser(USER2)
// =========================================================
// Генерим ключи детерминированно из login (как твой ItRunContext)
byte[] user2LoginPriv = Ed25519Util.generatePrivateKeyFromString(USER2_LOGIN);
byte[] user2LoginPub = Ed25519Util.derivePublicKey(user2LoginPriv);
byte[] user2DevPriv = Ed25519Util.generatePrivateKeyFromString(USER2_LOGIN + "#device");
byte[] user2DevPub = Ed25519Util.derivePublicKey(user2DevPriv);
String user2LoginPubB64 = Base64.getEncoder().encodeToString(user2LoginPub);
String user2DevPubB64 = Base64.getEncoder().encodeToString(user2DevPub);
addUserOr409AlreadyExists(
"USER2",
USER2_LOGIN,
USER2_BCH,
user2LoginPubB64,
user2DevPubB64
);
// =========================================================
// 3) USER1 блоки (как было раньше в IT_03)
// =========================================================
if (TestConfig.DEBUG()) { if (TestConfig.DEBUG()) {
TestLog.titleBlock(""" TestLog.titleBlock("""
IT_03_AddBlock_NoAuth: AddBlock без отдельной авторизации (Body-only в тесте) IT_03_AddBlock_NoAuth (combined): USER1 + USER2 сценарии
login = %s USER1 login = %s
blockchainName = %s USER1 blockchainName= %s
""".formatted(TestConfig.LOGIN(), TestConfig.BCH_NAME())); USER2 login = %s
USER2 blockchainName= %s
""".formatted(TestConfig.LOGIN(), TestConfig.BCH_NAME(), USER2_LOGIN, USER2_BCH));
} }
ChainState state = new ChainState(); ChainState st1 = new ChainState();
AddBlockSender sender = new AddBlockSender(state); AddBlockSender sender1 = new AddBlockSender(
st1,
TestConfig.LOGIN(),
TestConfig.BCH_NAME(),
TestConfig.LOGIN_PRIV_KEY()
);
// ========================================================= if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: HEADER");
// 0) HEADER sender1.send(new HeaderBody(TestConfig.LOGIN()), t);
// ========================================================= assertTrue(st1.hasHeader());
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 0: HEADER");
sender.send(new HeaderBody(TestConfig.LOGIN()), t);
assertTrue(state.hasHeader());
// ========================================================= if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#1 (NEW)");
// 1..3) TEXT NEW sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #1 (NEW) from IT_03 test"), t);
// =========================================================
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 1: TEXT#1 (NEW)");
sender.send(new TextBody(TextBody.SUB_NEW, "Hello #1 (NEW) from IT_03 test"), t);
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 2: TEXT#2 (NEW)"); if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#2 (NEW)");
sender.send(new TextBody(TextBody.SUB_NEW, "Hello #2 (NEW) from IT_03 test"), t); sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #2 (NEW) from IT_03 test"), t);
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 3: TEXT#3 (NEW)"); if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#3 (NEW)");
sender.send(new TextBody(TextBody.SUB_NEW, "Hello #3 (NEW) from IT_03 test"), t); sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #3 (NEW) from IT_03 test"), t);
// Теперь у нас есть: byte[] text1Hash = st1.getGlobalHash32(1);
// global=1 -> TEXT#1 byte[] text2Hash = st1.getGlobalHash32(2);
// global=2 -> TEXT#2
// global=3 -> TEXT#3
byte[] text1Hash = state.getGlobalHash32(1);
byte[] text2Hash = state.getGlobalHash32(2);
assertNotNull(text1Hash); assertNotNull(text1Hash);
assertNotNull(text2Hash); assertNotNull(text2Hash);
// ========================================================= if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#4 (REPLY -> TEXT#1)");
// 4) REPLY на TEXT#1 sender1.send(new TextBody(
// =========================================================
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 4: TEXT#4 (REPLY -> TEXT#1)");
sender.send(new TextBody(
TextBody.SUB_REPLY, TextBody.SUB_REPLY,
"Reply to TEXT#1", "Reply to TEXT#1",
TestConfig.BCH_NAME(), TestConfig.BCH_NAME(),
@ -100,15 +145,11 @@ public class IT_03_AddBlock_NoAuth {
text1Hash text1Hash
), t); ), t);
// global=4 -> REPLY на global=1 byte[] reply1Hash = st1.getGlobalHash32(4);
byte[] reply1Hash = state.getGlobalHash32(4);
assertNotNull(reply1Hash); assertNotNull(reply1Hash);
// ========================================================= if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#5 (REPLY -> TEXT#4)");
// 5) REPLY на REPLY (ответ на ответ) sender1.send(new TextBody(
// =========================================================
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 5: TEXT#5 (REPLY -> TEXT#4)");
sender.send(new TextBody(
TextBody.SUB_REPLY, TextBody.SUB_REPLY,
"Reply to REPLY (TEXT#4)", "Reply to REPLY (TEXT#4)",
TestConfig.BCH_NAME(), TestConfig.BCH_NAME(),
@ -116,39 +157,140 @@ public class IT_03_AddBlock_NoAuth {
reply1Hash reply1Hash
), t); ), t);
// ========================================================= if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#1 (LIKE -> TEXT#1)");
// 6) REACTION#1 -> LIKE на TEXT#1 sender1.send(new ReactionBody(
// =========================================================
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 6: REACT#1 (LIKE -> TEXT#1)");
sender.send(new ReactionBody(
ReactionBody.SUB_LIKE, ReactionBody.SUB_LIKE,
TestConfig.BCH_NAME(), TestConfig.BCH_NAME(),
1, 1,
text1Hash text1Hash
), t); ), t);
// ========================================================= if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#2 (LIKE -> TEXT#4)");
// 7) REACTION#2 -> LIKE на REPLY (TEXT#4) sender1.send(new ReactionBody(
// =========================================================
if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 7: REACT#2 (LIKE -> TEXT#4)");
sender.send(new ReactionBody(
ReactionBody.SUB_LIKE, ReactionBody.SUB_LIKE,
TestConfig.BCH_NAME(), TestConfig.BCH_NAME(),
4, 4,
reply1Hash reply1Hash
), t); ), t);
// ========================================================= assertEquals(7, st1.globalLastNumber(), "USER1: должно быть 8 блоков: globalLastNumber=7");
// Итоги: 1 header + 3 new + 2 reply + 2 react = 8 блоков assertEquals(5, st1.lineLastNumber((short) 1), "USER1: line=1 должно быть 5 TEXT блоков (3 new + 2 reply)");
// globalLastNumber должен быть 7 assertEquals(2, st1.lineLastNumber((short) 2), "USER1: line=2 должно быть 2 REACTION блока");
// =========================================================
assertEquals(7, state.globalLastNumber(), "Должно быть 8 блоков: globalLastNumber=7");
assertEquals(5, state.lineLastNumber((short) 1), "В line=1 должно быть 5 TEXT блоков (3 new + 2 reply)");
assertEquals(2, state.lineLastNumber((short) 2), "В line=2 должно быть 2 REACTION блока");
assertNotNull(state.globalLastHashHex()); // =========================================================
assertEquals(64, state.globalLastHashHex().length()); // 4) USER2: HEADER + PARAMS + FRIEND->USER1
// =========================================================
ChainState st2 = new ChainState();
AddBlockSender sender2 = new AddBlockSender(
st2,
USER2_LOGIN,
USER2_BCH,
user2LoginPriv
);
TestLog.pass("IT_03_AddBlock_NoAuth: OK"); if (TestConfig.DEBUG()) TestLog.stepTitle("USER2: HEADER");
sender2.send(new HeaderBody(USER2_LOGIN), t);
assertTrue(st2.hasHeader());
if (TestConfig.DEBUG()) TestLog.stepTitle("USER2: UserParams (name + address)");
sender2.send(new UserParamBody(
"Anya",
"Amsterdam, Example street 10"
), t);
if (TestConfig.DEBUG()) TestLog.stepTitle("USER2: Connection (FRIEND -> USER1)");
sender2.send(new ConnectionBody(
ConnectionBody.SUB_FRIEND,
TestConfig.LOGIN(), // to_login (USER1)
TestConfig.BCH_NAME(), // toBch (USER1 chain)
0,
new byte[32]
), t);
// =========================================================
// 5) USER1: params + взаимность + подписка (без нового HEADER!)
// =========================================================
// ВАЖНО: мы НЕ создаём новый ChainState для USER1, и НЕ шлём header заново.
// Мы продолжаем тем же sender1 и st1, иначе будет пытаться начать цепочку заново.
if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: UserParams (name + surname)");
sender1.send(new UserParamBody(
"Anna",
"Gareeva"
), t);
if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: Connection (FRIEND -> USER2)");
sender1.send(new ConnectionBody(
ConnectionBody.SUB_FRIEND,
USER2_LOGIN,
USER2_BCH,
0,
new byte[32]
), t);
if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: Connection (FOLLOW -> USER2)");
sender1.send(new ConnectionBody(
ConnectionBody.SUB_FOLLOW,
USER2_LOGIN,
USER2_BCH,
0,
new byte[32]
), t);
TestLog.pass("IT_03_AddBlock_NoAuth (combined): OK");
}
// ======================================================================
// helpers
// ======================================================================
private static void addUserOr409AlreadyExists(String label,
String login,
String blockchainName,
String loginPubKeyB64,
String devicePubKeyB64) {
TestLog.title(label + ": AddUser (200 OK) или 409 USER_ALREADY_EXISTS");
TestLog.info(" login = " + login);
TestLog.info(" blockchainName = " + blockchainName);
String reqId = "it-adduser-" + label.toLowerCase();
String reqJson = """
{
"op": "AddUser",
"requestId": "%s",
"payload": {
"login": "%s",
"blockchainName": "%s",
"loginKey": "%s",
"deviceKey": "%s",
"bchLimit": %d
}
}
""".formatted(
reqId,
login,
blockchainName,
loginPubKeyB64,
devicePubKeyB64,
TestConfig.TEST_BCH_LIMIT
);
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
TestLog.send("AddUser(" + label + ")", reqJson);
String resp = client.request(reqId, reqJson, Duration.ofSeconds(5));
TestLog.recv("AddUser(" + label + ")", resp);
int st = JsonParsers.status(resp);
if (st == 200) {
TestLog.ok(label + ": создан/добавлен (status=200)");
} else if (st == 409) {
String code = JsonParsers.errorCode(resp);
assertEquals("USER_ALREADY_EXISTS", code, label + ": expected USER_ALREADY_EXISTS, resp=" + resp);
TestLog.ok(label + ": уже есть (status=409, USER_ALREADY_EXISTS)");
} else {
fail(label + ": неожиданный status=" + st + ", resp=" + resp);
}
}
} }
} }

View File

@ -5,19 +5,6 @@ import test.it.utils.TestLog;
/** /**
* Ручной запуск всех IT тестов БЕЗ JUnit / Suite. * Ручной запуск всех IT тестов БЕЗ JUnit / Suite.
*
* Делает:
* 1) запускает тесты по очереди
* 2) печатает итоговый короткий отчёт
*
* Запуск из IDE:
* Run 'main' этого класса
*
* Запуск из консоли:
* ./gradlew testClasses
* java -cp ... test.it.IT_RunAllMain
*
* (Classpath зависит от твоего Gradle, но в IDE проще всего)
*/ */
public class IT_RunAllMain { public class IT_RunAllMain {
@ -25,15 +12,9 @@ public class IT_RunAllMain {
ItRunContext.initIfNeeded(); ItRunContext.initIfNeeded();
int failed = runAll(); int failed = runAll();
// Удобно для CI: код выхода = число упавших тестов
System.exit(failed); System.exit(failed);
} }
/**
* Основной метод, который возвращает число не пройденных тестов (0 если всё хорошо).
* Его можно вызывать из других раннеров (например, из варианта с очисткой data/).
*/
public static int runAll() { public static int runAll() {
final int total = 3; final int total = 3;
@ -42,23 +23,18 @@ public class IT_RunAllMain {
TestLog.title("IT RUN: запуск всех тестов подряд (без очистки data/)"); TestLog.title("IT RUN: запуск всех тестов подряд (без очистки data/)");
// 1) IT_01_AddUser
TestLog.stepTitle("RUN: IT_01_AddUser"); TestLog.stepTitle("RUN: IT_01_AddUser");
int f1 = IT_01_AddUser.run(); int f1 = IT_01_AddUser.run();
failed += f1; passed += (f1 == 0 ? 1 : 0); failed += f1; passed += (f1 == 0 ? 1 : 0);
// 2) IT_02_Sessions
TestLog.stepTitle("RUN: IT_02_Sessions"); TestLog.stepTitle("RUN: IT_02_Sessions");
int f2 = IT_02_Sessions.run(); int f2 = IT_02_Sessions.run();
failed += f2; passed += (f2 == 0 ? 1 : 0); failed += f2; passed += (f2 == 0 ? 1 : 0);
// 3) IT_03_AddBlock_NoAuth (оставлен как есть, поэтому запускаем через его main) TestLog.stepTitle("RUN: IT_03_AddBlock_NoAuth (combined 3+4)");
// Если он упадёт он кинет исключение. Мы перехватим и посчитаем как fail=1.
TestLog.stepTitle("RUN: IT_03_AddBlock_NoAuth (main)");
int f3 = IT_03_AddBlock_NoAuth.run(); int f3 = IT_03_AddBlock_NoAuth.run();
failed += f3; passed += (f3 == 0 ? 1 : 0); failed += f3; passed += (f3 == 0 ? 1 : 0);
// Итоговый короткий отчёт
TestLog.titleBlock(""" TestLog.titleBlock("""
IT RUN RESULT IT RUN RESULT
---------------------------- ----------------------------

View File

@ -3,9 +3,7 @@ package test.it.addBlockUtils;
import blockchain.BchBlockEntry; import blockchain.BchBlockEntry;
import blockchain.BchCryptoVerifier; import blockchain.BchCryptoVerifier;
import blockchain.body.BodyRecord; import blockchain.body.BodyRecord;
import test.it.utils.TestConfig;
import test.it.utils.TestLog; import test.it.utils.TestLog;
import utils.crypto.Ed25519Util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@ -17,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
/** /**
* AddBlockSender "одна кнопка": * AddBlockSender "одна кнопка":
* - принимает ГОТОВЫЙ Body (HeaderBody/TextBody/ReactionBody) * - принимает ГОТОВЫЙ Body (HeaderBody/TextBody/ReactionBody/ConnectionBody/UserParamsBody и т.п.)
* - сам берёт номера/prev-hash из ChainState * - сам берёт номера/prev-hash из ChainState
* - строит raw/hash/signature * - строит raw/hash/signature
* - собирает BchBlockEntry (старый, без изменений) * - собирает BchBlockEntry (старый, без изменений)
@ -25,8 +23,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
* - проверяет serverLastGlobalHash == localHash * - проверяет serverLastGlobalHash == localHash
* - обновляет ChainState * - обновляет ChainState
* *
* В тестах: * ИЗМЕНЕНО:
* sender.send(body, timeout); * - sender больше НЕ завязан на TestConfig.LOGIN()/BCH_NAME()/ключи
* - теперь он работает от параметров конкретного пользователя:
* login, blockchainName, loginPrivKey
*/ */
public final class AddBlockSender { public final class AddBlockSender {
@ -35,17 +35,22 @@ public final class AddBlockSender {
private final ChainState state; private final ChainState state;
public AddBlockSender(ChainState state) { private final String login;
private final String blockchainName;
private final byte[] loginPrivKey;
public AddBlockSender(ChainState state, String login, String blockchainName, byte[] loginPrivKey) {
this.state = state; this.state = state;
this.login = login;
this.blockchainName = blockchainName;
this.loginPrivKey = (loginPrivKey == null ? null : loginPrivKey.clone());
if (this.loginPrivKey == null) throw new IllegalArgumentException("loginPrivKey == null");
} }
public ChainState state() { public ChainState state() { return state; }
return state;
}
/** /**
* Отправить следующий блок по body.expectedLineIndex(). * Отправить следующий блок по body.expectedLineIndex().
* Ничего не возвращает состояние хранится в ChainState.
*/ */
public void send(BodyRecord body, Duration timeout) { public void send(BodyRecord body, Duration timeout) {
if (body == null) throw new IllegalArgumentException("body == null"); if (body == null) throw new IllegalArgumentException("body == null");
@ -70,10 +75,9 @@ public final class AddBlockSender {
byte[] prevLineHash32 = (lineIndex == 0) ? ZERO32 : state.prevLineHash32ForNext(lineIndex); byte[] prevLineHash32 = (lineIndex == 0) ? ZERO32 : state.prevLineHash32ForNext(lineIndex);
long ts = System.currentTimeMillis() / 1000L; long ts = System.currentTimeMillis() / 1000L;
byte[] bodyBytes = body.toBytes(); byte[] bodyBytes = body.toBytes();
// RAW bytes (ровно то, что подписываем/хэшируем) // RAW bytes
int recordSize = BchBlockEntry.RAW_HEADER_SIZE + bodyBytes.length; int recordSize = BchBlockEntry.RAW_HEADER_SIZE + bodyBytes.length;
byte[] rawBytes = ByteBuffer.allocate(recordSize) byte[] rawBytes = ByteBuffer.allocate(recordSize)
@ -88,13 +92,13 @@ public final class AddBlockSender {
// preimage -> sha256 -> signature // preimage -> sha256 -> signature
byte[] preimage = BchCryptoVerifier.buildPreimage( byte[] preimage = BchCryptoVerifier.buildPreimage(
TestConfig.LOGIN(), login,
prevGlobalHash32, prevGlobalHash32,
prevLineHash32, prevLineHash32,
rawBytes rawBytes
); );
byte[] hash32 = BchCryptoVerifier.sha256(preimage); byte[] hash32 = BchCryptoVerifier.sha256(preimage);
byte[] signature64 = Ed25519Util.sign(hash32, TestConfig.LOGIN_PRIV_KEY()); byte[] signature64 = utils.crypto.Ed25519Util.sign(hash32, loginPrivKey);
// Собираем полный блок (BchBlockEntry не меняем) // Собираем полный блок (BchBlockEntry не меняем)
BchBlockEntry entry = new BchBlockEntry( BchBlockEntry entry = new BchBlockEntry(
@ -107,28 +111,30 @@ public final class AddBlockSender {
hash32 hash32
); );
// отправляем JSON // JSON AddBlock
String prevGlobalHashHex = (globalNumber == 0) ? ZERO64 : state.globalLastHashHex(); String prevGlobalHashHex = (globalNumber == 0) ? ZERO64 : state.globalLastHashHex();
String req = buildAddBlockJson( String req = buildAddBlockJson(
TestConfig.BCH_NAME(), blockchainName,
globalNumber, globalNumber,
prevGlobalHashHex, prevGlobalHashHex,
base64(entry.toBytes()) base64(entry.toBytes())
); );
String op = "AddBlock (global=" + globalNumber + ", line=" + lineIndex + ", lineNum=" + lineNumber + ")"; String op = "AddBlock (user=" + login + ", bch=" + blockchainName +
", global=" + globalNumber + ", line=" + lineIndex + ", lineNum=" + lineNumber + ")";
String resp = WsJsonOneShot.request(op, req, timeout); String resp = WsJsonOneShot.request(op, req, timeout);
assert200(op, resp); assert200(op, resp);
String serverLastGlobalHash = extractPayloadString(resp, "serverLastGlobalHash"); String serverLastGlobalHash = JsonMini.extractPayloadString(resp, "serverLastGlobalHash");
assertNotNull(serverLastGlobalHash, op + ": payload.serverLastGlobalHash must not be null"); assertNotNull(serverLastGlobalHash, op + ": payload.serverLastGlobalHash must not be null");
assertEquals(64, serverLastGlobalHash.trim().length(), op + ": serverLastGlobalHash must be 64 hex chars"); assertEquals(64, serverLastGlobalHash.trim().length(), op + ": serverLastGlobalHash must be 64 hex chars");
String localHashHex = bytesToHex64(hash32); String localHashHex = bytesToHex64(hash32);
if (TestConfig.DEBUG()) { if (test.it.utils.TestConfig.DEBUG()) {
TestLog.ok(op + ": localHash=" + localHashHex); TestLog.ok(op + ": localHash=" + localHashHex);
TestLog.ok(op + ": serverLastGlobalHash=" + serverLastGlobalHash); TestLog.ok(op + ": serverLastGlobalHash=" + serverLastGlobalHash);
} }
@ -138,7 +144,7 @@ public final class AddBlockSender {
// обновляем ChainState // обновляем ChainState
state.applyAppendedBlock(globalNumber, lineIndex, lineNumber, hash32); state.applyAppendedBlock(globalNumber, lineIndex, lineNumber, hash32);
if (TestConfig.DEBUG()) { if (test.it.utils.TestConfig.DEBUG()) {
TestLog.ok(op + ": state updated"); TestLog.ok(op + ": state updated");
} }
} }
@ -166,17 +172,7 @@ public final class AddBlockSender {
private static void assert200(String op, String resp) { private static void assert200(String op, String resp) {
int st = test.it.utils.JsonParsers.status(resp); int st = test.it.utils.JsonParsers.status(resp);
assertEquals(200, st, op + ": expected status=200, but got=" + st + ", resp=" + resp); assertEquals(200, st, op + ": expected status=200, but got=" + st + ", resp=" + resp);
if (TestConfig.DEBUG()) TestLog.ok(op + ": status=200"); if (test.it.utils.TestConfig.DEBUG()) TestLog.ok(op + ": status=200");
}
private static String extractPayloadString(String json, String field) {
try {
com.fasterxml.jackson.databind.JsonNode root =
new com.fasterxml.jackson.databind.ObjectMapper().readTree(json);
com.fasterxml.jackson.databind.JsonNode payload = root.get("payload");
if (payload != null && payload.has(field)) return payload.get(field).asText();
} catch (Exception ignore) {}
return null;
} }
private static String base64(byte[] bytes) { private static String base64(byte[] bytes) {

View File

@ -5,91 +5,104 @@ import utils.crypto.Ed25519Util;
/** /**
* Глобальный контекст IT прогона (одна JVM). * Глобальный контекст IT прогона (одна JVM).
* *
* ТЕПЕРЬ: * БЫЛО:
* - login берётся из TestConfig.LOGIN() * - один пользователь (login/device)
* - blockchainName = TestConfig.BCH_NAME()
* - ключи генерятся ДЕТЕРМИНИРОВАННО из login (как ты хотела)
* *
* ПЛЮС: * СТАЛО:
* - тесты можно запускать по одному initIfNeeded() вызовется автоматически. * - два пользователя (login1/device1 и login2/device2)
* - ключи детерминированы из логинов
*/ */
public final class ItRunContext { public final class ItRunContext {
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
private static volatile boolean inited = false; private static volatile boolean inited = false;
private static String login; private static String login1;
private static String blockchainName; private static String bchName1;
private static byte[] loginPrivKey; private static String login2;
private static byte[] loginPubKey; private static String bchName2;
private static byte[] devicePrivKey; private static byte[] login1PrivKey;
private static byte[] devicePubKey; private static byte[] login1PubKey;
private static byte[] device1PrivKey;
private static byte[] device1PubKey;
private static byte[] login2PrivKey;
private static byte[] login2PubKey;
private static byte[] device2PrivKey;
private static byte[] device2PubKey;
private ItRunContext() {} private ItRunContext() {}
/** Инициализировать, если ещё не инициализировано. */
public static void initIfNeeded() { public static void initIfNeeded() {
if (inited) return; if (inited) return;
synchronized (LOCK) { synchronized (LOCK) {
if (inited) return; if (inited) return;
login = TestConfig.LOGIN(); // USER1
blockchainName = TestConfig.BCH_NAME(); login1 = TestConfig.LOGIN();
bchName1 = TestConfig.BCH_NAME();
// 1) Генерация ключей ИЗ login login1PrivKey = Ed25519Util.generatePrivateKeyFromString(login1);
// loginKey: приватный ключ = SHA-256(login) login1PubKey = Ed25519Util.derivePublicKey(login1PrivKey);
loginPrivKey = Ed25519Util.generatePrivateKeyFromString(login);
loginPubKey = Ed25519Util.derivePublicKey(loginPrivKey);
// deviceKey: приватный ключ = SHA-256(login + "#device") String deviceSeed1 = login1 + "#device";
String deviceSeedStr = login + "#device"; device1PrivKey = Ed25519Util.generatePrivateKeyFromString(deviceSeed1);
devicePrivKey = Ed25519Util.generatePrivateKeyFromString(deviceSeedStr); device1PubKey = Ed25519Util.derivePublicKey(device1PrivKey);
devicePubKey = Ed25519Util.derivePublicKey(devicePrivKey);
// USER2
login2 = TestConfig.LOGIN2();
bchName2 = TestConfig.BCH_NAME2();
login2PrivKey = Ed25519Util.generatePrivateKeyFromString(login2);
login2PubKey = Ed25519Util.derivePublicKey(login2PrivKey);
String deviceSeed2 = login2 + "#device";
device2PrivKey = Ed25519Util.generatePrivateKeyFromString(deviceSeed2);
device2PubKey = Ed25519Util.derivePublicKey(device2PrivKey);
inited = true; inited = true;
System.out.println(TestColors.C + "\n============================================================" + TestColors.R); System.out.println(TestColors.C + "\n============================================================" + TestColors.R);
System.out.println(TestColors.C + "IT CONTEXT INIT: фиксированные данные из TestConfig" + TestColors.R); System.out.println(TestColors.C + "IT CONTEXT INIT: 2 users" + TestColors.R);
System.out.println(TestColors.C + "============================================================" + TestColors.R); System.out.println(TestColors.C + "============================================================" + TestColors.R);
System.out.println("login = " + login);
System.out.println("blockchainName = " + blockchainName); System.out.println("USER1 login = " + login1);
System.out.println("loginPubKey = " + bytesToHexShort(loginPubKey)); System.out.println("USER1 blockchainName = " + bchName1);
System.out.println("devicePubKey = " + bytesToHexShort(devicePubKey)); System.out.println("USER1 loginPubKey = " + bytesToHexShort(login1PubKey));
System.out.println("USER1 devicePubKey = " + bytesToHexShort(device1PubKey));
System.out.println(TestColors.C + "------------------------------------------------------------" + TestColors.R);
System.out.println("USER2 login = " + login2);
System.out.println("USER2 blockchainName = " + bchName2);
System.out.println("USER2 loginPubKey = " + bytesToHexShort(login2PubKey));
System.out.println("USER2 devicePubKey = " + bytesToHexShort(device2PubKey));
System.out.println(TestColors.C + "------------------------------------------------------------\n" + TestColors.R); System.out.println(TestColors.C + "------------------------------------------------------------\n" + TestColors.R);
} }
} }
public static String login() { // =========================
initIfNeeded(); // USER1 getters
return login; // =========================
} public static String login1() { initIfNeeded(); return login1; }
public static String bchName1(){ initIfNeeded(); return bchName1; }
public static String blockchainName() { public static byte[] login1PrivKey() { initIfNeeded(); return login1PrivKey.clone(); }
initIfNeeded(); public static byte[] login1PubKey() { initIfNeeded(); return login1PubKey.clone(); }
return blockchainName; public static byte[] device1PrivKey(){ initIfNeeded(); return device1PrivKey.clone(); }
} public static byte[] device1PubKey() { initIfNeeded(); return device1PubKey.clone(); }
public static byte[] loginPrivKey() { // =========================
initIfNeeded(); // USER2 getters
return loginPrivKey.clone(); // =========================
} public static String login2() { initIfNeeded(); return login2; }
public static String bchName2(){ initIfNeeded(); return bchName2; }
public static byte[] loginPubKey() { public static byte[] login2PrivKey() { initIfNeeded(); return login2PrivKey.clone(); }
initIfNeeded(); public static byte[] login2PubKey() { initIfNeeded(); return login2PubKey.clone(); }
return loginPubKey.clone(); public static byte[] device2PrivKey(){ initIfNeeded(); return device2PrivKey.clone(); }
} public static byte[] device2PubKey() { initIfNeeded(); return device2PubKey.clone(); }
public static byte[] devicePrivKey() {
initIfNeeded();
return devicePrivKey.clone();
}
public static byte[] devicePubKey() {
initIfNeeded();
return devicePubKey.clone();
}
private static String bytesToHexShort(byte[] b) { private static String bytesToHexShort(byte[] b) {
if (b == null) return "null"; if (b == null) return "null";

View File

@ -8,7 +8,37 @@ import java.util.Base64;
public final class JsonBuilders { public final class JsonBuilders {
private JsonBuilders(){} private JsonBuilders(){}
// =========================
// AddUser USER1 (как было)
// =========================
public static String addUser(String requestId) { public static String addUser(String requestId) {
return addUserAny(
requestId,
TestConfig.LOGIN(),
TestConfig.BCH_NAME(),
TestConfig.LOGIN_PUBKEY_B64(),
TestConfig.DEVICE_PUBKEY_B64()
);
}
// =========================
// AddUser USER2 (новое)
// =========================
public static String addUser2(String requestId) {
return addUserAny(
requestId,
TestConfig.LOGIN2(),
TestConfig.BCH_NAME2(),
TestConfig.LOGIN2_PUBKEY_B64(),
TestConfig.DEVICE2_PUBKEY_B64()
);
}
private static String addUserAny(String requestId,
String login,
String blockchainName,
String loginKeyB64,
String deviceKeyB64) {
return """ return """
{ {
"op": "AddUser", "op": "AddUser",
@ -23,10 +53,10 @@ public final class JsonBuilders {
} }
""".formatted( """.formatted(
requestId, requestId,
TestConfig.LOGIN(), login,
TestConfig.BCH_NAME(), blockchainName,
TestConfig.LOGIN_PUBKEY_B64(), loginKeyB64,
TestConfig.DEVICE_PUBKEY_B64(), deviceKeyB64,
TestConfig.TEST_BCH_LIMIT TestConfig.TEST_BCH_LIMIT
); );
} }
@ -43,7 +73,7 @@ public final class JsonBuilders {
public static String createAuthSession(String requestId, String authNonce, String storagePwd) { public static String createAuthSession(String requestId, String authNonce, String storagePwd) {
long timeMs = System.currentTimeMillis(); long timeMs = System.currentTimeMillis();
String sigB64 = signAuthorificated(authNonce, timeMs); String sigB64 = signAuthorificated(authNonce, timeMs, TestConfig.DEVICE_PRIV_KEY());
return """ return """
{ {
@ -105,12 +135,17 @@ public final class JsonBuilders {
/** /**
* Подпись для режима AUTH_IN_PROGRESS: * Подпись для режима AUTH_IN_PROGRESS:
* preimage = "AUTHORIFICATED:" + timeMs + authNonce * preimage = "AUTHORIFICATED:" + timeMs + authNonce
* подписываем devicePrivKey (как в твоём протоколе). * подписываем devicePrivKey.
*/ */
public static String signAuthorificated(String authNonce, long timeMs) { public static String signAuthorificated(String authNonce, long timeMs, byte[] devicePrivKey) {
String preimageStr = "AUTHORIFICATED:" + timeMs + authNonce; String preimageStr = "AUTHORIFICATED:" + timeMs + authNonce;
byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8); byte[] preimage = preimageStr.getBytes(StandardCharsets.UTF_8);
byte[] sig = Ed25519Util.sign(preimage, TestConfig.DEVICE_PRIV_KEY()); byte[] sig = Ed25519Util.sign(preimage, devicePrivKey);
return Base64.getEncoder().encodeToString(sig); return Base64.getEncoder().encodeToString(sig);
} }
// старый метод оставим для совместимости
public static String signAuthorificated(String authNonce, long timeMs) {
return signAuthorificated(authNonce, timeMs, TestConfig.DEVICE_PRIV_KEY());
}
} }

View File

@ -5,19 +5,11 @@ import java.util.Base64;
/** /**
* Конфиг для IT тестов. * Конфиг для IT тестов.
* *
* ЛОГИКА: * ДОБАВЛЕНО:
* - login по умолчанию берём из DEFAULT_LOGIN * - Второй пользователь (LOGIN2) + его blockchainName и ключи.
* - можно переопределить запуском:
* -Dit.login=anya24
* -Dit.bchSuffix=001
* *
* ВАЖНО: * Важно:
* - ключи/имя блокчейна вычисляются из login (через ItRunContext). * - Имена/ключи вычисляются детерминированно из логина (см. ItRunContext).
* - тесты можно запускать по отдельности, ItRunContext сам инициализируется при первом обращении.
*
* ЛОГИ:
* - детальный вывод включается флагом:
* -Dit.debug=true
*/ */
public final class TestConfig { public final class TestConfig {
@ -26,9 +18,12 @@ public final class TestConfig {
// Твой WS URI // Твой WS URI
public static final String WS_URI = "ws://localhost:7070/ws"; public static final String WS_URI = "ws://localhost:7070/ws";
// ======= По умолчанию (можно поменять под свою среду) ======= // ======= Пользователь #1 (по умолчанию) =======
public static final String DEFAULT_LOGIN = "Anya"; public static final String DEFAULT_LOGIN = "Anya";
// ======= Пользователь #2 (новый) =======
public static final String DEFAULT_LOGIN2 = "Anya2";
// Суффикс блокчейна по твоему правилу: login + 3 цифры // Суффикс блокчейна по твоему правилу: login + 3 цифры
public static final String DEFAULT_BCH_SUFFIX_3 = "001"; public static final String DEFAULT_BCH_SUFFIX_3 = "001";
@ -38,51 +33,59 @@ public final class TestConfig {
// Любая строка клиента (для логов) // Любая строка клиента (для логов)
public static final String TEST_CLIENT_INFO = "it-tests"; public static final String TEST_CLIENT_INFO = "it-tests";
/** DEBUG-режим: подробные логи отправки/получения/ожиданий (по умолчанию false). */ /** DEBUG-режим: подробные логи (по умолчанию true, как у тебя). */
public static boolean DEBUG() { public static boolean DEBUG() {
return Boolean.parseBoolean(System.getProperty("it.debug", "true")); return Boolean.parseBoolean(System.getProperty("it.debug", "true"));
} }
/** login для прогона (по умолчанию DEFAULT_LOGIN, можно переопределить -Dit.login=...). */ // =========================
// USER #1
// =========================
/** login для прогона (user1). */
public static String LOGIN() { public static String LOGIN() {
return System.getProperty("it.login", DEFAULT_LOGIN); return System.getProperty("it.login", DEFAULT_LOGIN);
} }
/** Суффикс для имени блокчейна (по умолчанию DEFAULT_BCH_SUFFIX_3, можно переопределить -Dit.bchSuffix=...). */ /** Суффикс для имени блокчейна (user1). */
public static String BCH_SUFFIX_3() { public static String BCH_SUFFIX_3() {
return System.getProperty("it.bchSuffix", DEFAULT_BCH_SUFFIX_3); return System.getProperty("it.bchSuffix", DEFAULT_BCH_SUFFIX_3);
} }
/** blockchainName по правилу: login + суффикс. */ /** blockchainName по правилу: login + суффикс (user1). */
public static String BCH_NAME() { public static String BCH_NAME() {
return LOGIN() + BCH_SUFFIX_3(); return LOGIN() + BCH_SUFFIX_3();
} }
// ======= Ключи (берём из ItRunContext) ======= public static byte[] LOGIN_PRIV_KEY() { return ItRunContext.login1PrivKey(); }
public static byte[] LOGIN_PUB_KEY() { return ItRunContext.login1PubKey(); }
public static byte[] DEVICE_PRIV_KEY(){ return ItRunContext.device1PrivKey(); }
public static byte[] DEVICE_PUB_KEY() { return ItRunContext.device1PubKey(); }
public static byte[] LOGIN_PRIV_KEY() { public static String LOGIN_PUBKEY_B64() { return Base64.getEncoder().encodeToString(LOGIN_PUB_KEY()); }
return ItRunContext.loginPrivKey(); public static String DEVICE_PUBKEY_B64() { return Base64.getEncoder().encodeToString(DEVICE_PUB_KEY()); }
// =========================
// USER #2
// =========================
/** login второго пользователя. Можно переопределить -Dit.login2=... */
public static String LOGIN2() {
return System.getProperty("it.login2", DEFAULT_LOGIN2);
} }
public static byte[] LOGIN_PUB_KEY() { /** blockchainName второго: login2 + тот же суффикс. */
return ItRunContext.loginPubKey(); public static String BCH_NAME2() {
return LOGIN2() + BCH_SUFFIX_3();
} }
public static byte[] DEVICE_PRIV_KEY() { public static byte[] LOGIN2_PRIV_KEY() { return ItRunContext.login2PrivKey(); }
return ItRunContext.devicePrivKey(); public static byte[] LOGIN2_PUB_KEY() { return ItRunContext.login2PubKey(); }
} public static byte[] DEVICE2_PRIV_KEY() { return ItRunContext.device2PrivKey(); }
public static byte[] DEVICE2_PUB_KEY() { return ItRunContext.device2PubKey(); }
public static byte[] DEVICE_PUB_KEY() { public static String LOGIN2_PUBKEY_B64() { return Base64.getEncoder().encodeToString(LOGIN2_PUB_KEY()); }
return ItRunContext.devicePubKey(); public static String DEVICE2_PUBKEY_B64() { return Base64.getEncoder().encodeToString(DEVICE2_PUB_KEY()); }
}
public static String LOGIN_PUBKEY_B64() {
return Base64.getEncoder().encodeToString(LOGIN_PUB_KEY());
}
public static String DEVICE_PUBKEY_B64() {
return Base64.getEncoder().encodeToString(DEVICE_PUB_KEY());
}
/** Псевдо-пароль хранилища — достаточно для тестов. */ /** Псевдо-пароль хранилища — достаточно для тестов. */
public static String fakeStoragePwd() { public static String fakeStoragePwd() {