diff --git a/src/test/java/test/it/IT_03_AddBlock_NoAuth.java b/src/test/java/test/it/IT_03_AddBlock_NoAuth.java index 96cbe3e..40b71e6 100644 --- a/src/test/java/test/it/IT_03_AddBlock_NoAuth.java +++ b/src/test/java/test/it/IT_03_AddBlock_NoAuth.java @@ -1,32 +1,49 @@ package test.it; +import blockchain.body.ConnectionBody; import blockchain.body.HeaderBody; import blockchain.body.ReactionBody; import blockchain.body.TextBody; +import blockchain.body.UserParamBody; import org.junit.jupiter.api.BeforeAll; import test.it.addBlockUtils.AddBlockSender; import test.it.addBlockUtils.ChainState; -import test.it.utils.ItRunContext; -import test.it.utils.TestConfig; -import test.it.utils.TestLog; +import test.it.utils.*; +import utils.crypto.Ed25519Util; +import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.Base64; import static org.junit.jupiter.api.Assertions.*; /** * IT_03_AddBlock_NoAuth * - * Теперь тест максимально "линейный": - * - создаём только Body - * - sender.send(body) делает всё остальное (номера, prev-hash, подпись, отправка, проверка, state) + * ОБЪЕДИНЕНО: прежний IT_03 + прежний IT_04 в одном тесте, + * чтобы "четвёртый" сценарий гарантированно запускался сразу после "третьего". * - * ДОБАВЛЕНО: - * - 2 reply на старые сообщения (включая reply на reply) - * - ещё 1 реакция (вторая) + * Сценарий: + * 1) AddUser(USER1) 200 или 409 USER_ALREADY_EXISTS + * 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 { + // ===== 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) { int failed = run(); // System.exit(failed); @@ -39,7 +56,7 @@ public class IT_03_AddBlock_NoAuth { @BeforeAll static void ensureUserExists() { ItRunContext.initIfNeeded(); - // как и было: предусловие можно включить потом + // можно оставить пустым, как у тебя } private static void testBody() { @@ -48,51 +65,79 @@ public class IT_03_AddBlock_NoAuth { 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()) { TestLog.titleBlock(""" - IT_03_AddBlock_NoAuth: AddBlock без отдельной авторизации (Body-only в тесте) - login = %s - blockchainName = %s - """.formatted(TestConfig.LOGIN(), TestConfig.BCH_NAME())); + IT_03_AddBlock_NoAuth (combined): USER1 + USER2 сценарии + USER1 login = %s + USER1 blockchainName= %s + USER2 login = %s + USER2 blockchainName= %s + """.formatted(TestConfig.LOGIN(), TestConfig.BCH_NAME(), USER2_LOGIN, USER2_BCH)); } - ChainState state = new ChainState(); - AddBlockSender sender = new AddBlockSender(state); + ChainState st1 = new ChainState(); + AddBlockSender sender1 = new AddBlockSender( + st1, + TestConfig.LOGIN(), + TestConfig.BCH_NAME(), + TestConfig.LOGIN_PRIV_KEY() + ); - // ========================================================= - // 0) HEADER - // ========================================================= - if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 0: HEADER"); - sender.send(new HeaderBody(TestConfig.LOGIN()), t); - assertTrue(state.hasHeader()); + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: HEADER"); + sender1.send(new HeaderBody(TestConfig.LOGIN()), t); + assertTrue(st1.hasHeader()); - // ========================================================= - // 1..3) TEXT NEW - // ========================================================= - 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("USER1: TEXT#1 (NEW)"); + sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #1 (NEW) from IT_03 test"), t); - if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 2: TEXT#2 (NEW)"); - sender.send(new TextBody(TextBody.SUB_NEW, "Hello #2 (NEW) from IT_03 test"), t); + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#2 (NEW)"); + sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #2 (NEW) from IT_03 test"), t); - if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 3: TEXT#3 (NEW)"); - sender.send(new TextBody(TextBody.SUB_NEW, "Hello #3 (NEW) from IT_03 test"), t); + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#3 (NEW)"); + sender1.send(new TextBody(TextBody.SUB_NEW, "Hello #3 (NEW) from IT_03 test"), t); - // Теперь у нас есть: - // global=1 -> TEXT#1 - // global=2 -> TEXT#2 - // global=3 -> TEXT#3 - - byte[] text1Hash = state.getGlobalHash32(1); - byte[] text2Hash = state.getGlobalHash32(2); + byte[] text1Hash = st1.getGlobalHash32(1); + byte[] text2Hash = st1.getGlobalHash32(2); assertNotNull(text1Hash); assertNotNull(text2Hash); - // ========================================================= - // 4) REPLY на TEXT#1 - // ========================================================= - if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 4: TEXT#4 (REPLY -> TEXT#1)"); - sender.send(new TextBody( + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#4 (REPLY -> TEXT#1)"); + sender1.send(new TextBody( TextBody.SUB_REPLY, "Reply to TEXT#1", TestConfig.BCH_NAME(), @@ -100,15 +145,11 @@ public class IT_03_AddBlock_NoAuth { text1Hash ), t); - // global=4 -> REPLY на global=1 - byte[] reply1Hash = state.getGlobalHash32(4); + byte[] reply1Hash = st1.getGlobalHash32(4); assertNotNull(reply1Hash); - // ========================================================= - // 5) REPLY на REPLY (ответ на ответ) - // ========================================================= - if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 5: TEXT#5 (REPLY -> TEXT#4)"); - sender.send(new TextBody( + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: TEXT#5 (REPLY -> TEXT#4)"); + sender1.send(new TextBody( TextBody.SUB_REPLY, "Reply to REPLY (TEXT#4)", TestConfig.BCH_NAME(), @@ -116,39 +157,140 @@ public class IT_03_AddBlock_NoAuth { reply1Hash ), t); - // ========================================================= - // 6) REACTION#1 -> LIKE на TEXT#1 - // ========================================================= - if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 6: REACT#1 (LIKE -> TEXT#1)"); - sender.send(new ReactionBody( + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#1 (LIKE -> TEXT#1)"); + sender1.send(new ReactionBody( ReactionBody.SUB_LIKE, TestConfig.BCH_NAME(), 1, text1Hash ), t); - // ========================================================= - // 7) REACTION#2 -> LIKE на REPLY (TEXT#4) - // ========================================================= - if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 7: REACT#2 (LIKE -> TEXT#4)"); - sender.send(new ReactionBody( + if (TestConfig.DEBUG()) TestLog.stepTitle("USER1: REACT#2 (LIKE -> TEXT#4)"); + sender1.send(new ReactionBody( ReactionBody.SUB_LIKE, TestConfig.BCH_NAME(), 4, reply1Hash ), t); - // ========================================================= - // Итоги: 1 header + 3 new + 2 reply + 2 react = 8 блоков - // globalLastNumber должен быть 7 - // ========================================================= - 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 блока"); + assertEquals(7, st1.globalLastNumber(), "USER1: должно быть 8 блоков: globalLastNumber=7"); + assertEquals(5, st1.lineLastNumber((short) 1), "USER1: line=1 должно быть 5 TEXT блоков (3 new + 2 reply)"); + assertEquals(2, st1.lineLastNumber((short) 2), "USER1: 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); + } + } } } \ No newline at end of file diff --git a/src/test/java/test/it/IT_RunAllMain.java b/src/test/java/test/it/IT_RunAllMain.java index d746508..3fdb350 100644 --- a/src/test/java/test/it/IT_RunAllMain.java +++ b/src/test/java/test/it/IT_RunAllMain.java @@ -5,19 +5,6 @@ import test.it.utils.TestLog; /** * Ручной запуск всех IT тестов БЕЗ JUnit / Suite. - * - * Делает: - * 1) запускает тесты по очереди - * 2) печатает итоговый короткий отчёт - * - * Запуск из IDE: - * Run 'main' этого класса - * - * Запуск из консоли: - * ./gradlew testClasses - * java -cp ... test.it.IT_RunAllMain - * - * (Classpath зависит от твоего Gradle, но в IDE проще всего) */ public class IT_RunAllMain { @@ -25,15 +12,9 @@ public class IT_RunAllMain { ItRunContext.initIfNeeded(); int failed = runAll(); - - // Удобно для CI: код выхода = число упавших тестов System.exit(failed); } - /** - * Основной метод, который возвращает число не пройденных тестов (0 если всё хорошо). - * Его можно вызывать из других раннеров (например, из варианта с очисткой data/). - */ public static int runAll() { final int total = 3; @@ -42,23 +23,18 @@ public class IT_RunAllMain { TestLog.title("IT RUN: запуск всех тестов подряд (без очистки data/)"); - // 1) IT_01_AddUser TestLog.stepTitle("RUN: IT_01_AddUser"); int f1 = IT_01_AddUser.run(); failed += f1; passed += (f1 == 0 ? 1 : 0); - // 2) IT_02_Sessions TestLog.stepTitle("RUN: IT_02_Sessions"); int f2 = IT_02_Sessions.run(); failed += f2; passed += (f2 == 0 ? 1 : 0); - // 3) IT_03_AddBlock_NoAuth (оставлен как есть, поэтому запускаем через его main) - // Если он упадёт — он кинет исключение. Мы перехватим и посчитаем как fail=1. - TestLog.stepTitle("RUN: IT_03_AddBlock_NoAuth (main)"); + TestLog.stepTitle("RUN: IT_03_AddBlock_NoAuth (combined 3+4)"); int f3 = IT_03_AddBlock_NoAuth.run(); failed += f3; passed += (f3 == 0 ? 1 : 0); - // Итоговый короткий отчёт TestLog.titleBlock(""" IT RUN RESULT ---------------------------- diff --git a/src/test/java/test/it/addBlockUtils/AddBlockSender.java b/src/test/java/test/it/addBlockUtils/AddBlockSender.java index c93ebf0..c3c3841 100644 --- a/src/test/java/test/it/addBlockUtils/AddBlockSender.java +++ b/src/test/java/test/it/addBlockUtils/AddBlockSender.java @@ -3,9 +3,7 @@ package test.it.addBlockUtils; import blockchain.BchBlockEntry; import blockchain.BchCryptoVerifier; import blockchain.body.BodyRecord; -import test.it.utils.TestConfig; import test.it.utils.TestLog; -import utils.crypto.Ed25519Util; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -17,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; /** * AddBlockSender — "одна кнопка": - * - принимает ГОТОВЫЙ Body (HeaderBody/TextBody/ReactionBody) + * - принимает ГОТОВЫЙ Body (HeaderBody/TextBody/ReactionBody/ConnectionBody/UserParamsBody и т.п.) * - сам берёт номера/prev-hash из ChainState * - строит raw/hash/signature * - собирает BchBlockEntry (старый, без изменений) @@ -25,8 +23,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; * - проверяет serverLastGlobalHash == localHash * - обновляет ChainState * - * В тестах: - * sender.send(body, timeout); + * ИЗМЕНЕНО: + * - sender больше НЕ завязан на TestConfig.LOGIN()/BCH_NAME()/ключи + * - теперь он работает от параметров конкретного пользователя: + * login, blockchainName, loginPrivKey */ public final class AddBlockSender { @@ -35,17 +35,22 @@ public final class AddBlockSender { 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.login = login; + this.blockchainName = blockchainName; + this.loginPrivKey = (loginPrivKey == null ? null : loginPrivKey.clone()); + if (this.loginPrivKey == null) throw new IllegalArgumentException("loginPrivKey == null"); } - public ChainState state() { - return state; - } + public ChainState state() { return state; } /** * Отправить следующий блок по body.expectedLineIndex(). - * Ничего не возвращает — состояние хранится в ChainState. */ public void send(BodyRecord body, Duration timeout) { if (body == null) throw new IllegalArgumentException("body == null"); @@ -70,10 +75,9 @@ public final class AddBlockSender { byte[] prevLineHash32 = (lineIndex == 0) ? ZERO32 : state.prevLineHash32ForNext(lineIndex); long ts = System.currentTimeMillis() / 1000L; - byte[] bodyBytes = body.toBytes(); - // RAW bytes (ровно то, что подписываем/хэшируем) + // RAW bytes int recordSize = BchBlockEntry.RAW_HEADER_SIZE + bodyBytes.length; byte[] rawBytes = ByteBuffer.allocate(recordSize) @@ -88,13 +92,13 @@ public final class AddBlockSender { // preimage -> sha256 -> signature byte[] preimage = BchCryptoVerifier.buildPreimage( - TestConfig.LOGIN(), + login, prevGlobalHash32, prevLineHash32, rawBytes ); byte[] hash32 = BchCryptoVerifier.sha256(preimage); - byte[] signature64 = Ed25519Util.sign(hash32, TestConfig.LOGIN_PRIV_KEY()); + byte[] signature64 = utils.crypto.Ed25519Util.sign(hash32, loginPrivKey); // Собираем полный блок (BchBlockEntry не меняем) BchBlockEntry entry = new BchBlockEntry( @@ -107,28 +111,30 @@ public final class AddBlockSender { hash32 ); - // отправляем JSON + // JSON AddBlock String prevGlobalHashHex = (globalNumber == 0) ? ZERO64 : state.globalLastHashHex(); String req = buildAddBlockJson( - TestConfig.BCH_NAME(), + blockchainName, globalNumber, prevGlobalHashHex, 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); assert200(op, resp); - String serverLastGlobalHash = extractPayloadString(resp, "serverLastGlobalHash"); + String serverLastGlobalHash = JsonMini.extractPayloadString(resp, "serverLastGlobalHash"); assertNotNull(serverLastGlobalHash, op + ": payload.serverLastGlobalHash must not be null"); assertEquals(64, serverLastGlobalHash.trim().length(), op + ": serverLastGlobalHash must be 64 hex chars"); String localHashHex = bytesToHex64(hash32); - if (TestConfig.DEBUG()) { + if (test.it.utils.TestConfig.DEBUG()) { TestLog.ok(op + ": localHash=" + localHashHex); TestLog.ok(op + ": serverLastGlobalHash=" + serverLastGlobalHash); } @@ -138,7 +144,7 @@ public final class AddBlockSender { // обновляем ChainState state.applyAppendedBlock(globalNumber, lineIndex, lineNumber, hash32); - if (TestConfig.DEBUG()) { + if (test.it.utils.TestConfig.DEBUG()) { TestLog.ok(op + ": state updated"); } } @@ -166,17 +172,7 @@ public final class AddBlockSender { private static void assert200(String op, String resp) { int st = test.it.utils.JsonParsers.status(resp); assertEquals(200, st, op + ": expected status=200, but got=" + st + ", resp=" + resp); - if (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; + if (test.it.utils.TestConfig.DEBUG()) TestLog.ok(op + ": status=200"); } private static String base64(byte[] bytes) { diff --git a/src/test/java/test/it/utils/ItRunContext.java b/src/test/java/test/it/utils/ItRunContext.java index 6a4c761..503eaec 100644 --- a/src/test/java/test/it/utils/ItRunContext.java +++ b/src/test/java/test/it/utils/ItRunContext.java @@ -5,91 +5,104 @@ import utils.crypto.Ed25519Util; /** * Глобальный контекст IT прогона (одна JVM). * - * ТЕПЕРЬ: - * - login берётся из TestConfig.LOGIN() - * - blockchainName = TestConfig.BCH_NAME() - * - ключи генерятся ДЕТЕРМИНИРОВАННО из login (как ты хотела) + * БЫЛО: + * - один пользователь (login/device) * - * ПЛЮС: - * - тесты можно запускать по одному — initIfNeeded() вызовется автоматически. + * СТАЛО: + * - два пользователя (login1/device1 и login2/device2) + * - ключи детерминированы из логинов */ public final class ItRunContext { private static final Object LOCK = new Object(); private static volatile boolean inited = false; - private static String login; - private static String blockchainName; + private static String login1; + private static String bchName1; - private static byte[] loginPrivKey; - private static byte[] loginPubKey; + private static String login2; + private static String bchName2; - private static byte[] devicePrivKey; - private static byte[] devicePubKey; + private static byte[] login1PrivKey; + 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() {} - /** Инициализировать, если ещё не инициализировано. */ public static void initIfNeeded() { if (inited) return; synchronized (LOCK) { if (inited) return; - login = TestConfig.LOGIN(); - blockchainName = TestConfig.BCH_NAME(); + // USER1 + login1 = TestConfig.LOGIN(); + bchName1 = TestConfig.BCH_NAME(); - // 1) Генерация ключей ИЗ login - // loginKey: приватный ключ = SHA-256(login) - loginPrivKey = Ed25519Util.generatePrivateKeyFromString(login); - loginPubKey = Ed25519Util.derivePublicKey(loginPrivKey); + login1PrivKey = Ed25519Util.generatePrivateKeyFromString(login1); + login1PubKey = Ed25519Util.derivePublicKey(login1PrivKey); - // deviceKey: приватный ключ = SHA-256(login + "#device") - String deviceSeedStr = login + "#device"; - devicePrivKey = Ed25519Util.generatePrivateKeyFromString(deviceSeedStr); - devicePubKey = Ed25519Util.derivePublicKey(devicePrivKey); + String deviceSeed1 = login1 + "#device"; + device1PrivKey = Ed25519Util.generatePrivateKeyFromString(deviceSeed1); + device1PubKey = Ed25519Util.derivePublicKey(device1PrivKey); + + // 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; 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("login = " + login); - System.out.println("blockchainName = " + blockchainName); - System.out.println("loginPubKey = " + bytesToHexShort(loginPubKey)); - System.out.println("devicePubKey = " + bytesToHexShort(devicePubKey)); + + System.out.println("USER1 login = " + login1); + System.out.println("USER1 blockchainName = " + bchName1); + 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); } } - public static String login() { - initIfNeeded(); - return login; - } + // ========================= + // USER1 getters + // ========================= + public static String login1() { initIfNeeded(); return login1; } + public static String bchName1(){ initIfNeeded(); return bchName1; } - public static String blockchainName() { - initIfNeeded(); - return blockchainName; - } + public static byte[] login1PrivKey() { initIfNeeded(); return login1PrivKey.clone(); } + public static byte[] login1PubKey() { initIfNeeded(); return login1PubKey.clone(); } + public static byte[] device1PrivKey(){ initIfNeeded(); return device1PrivKey.clone(); } + public static byte[] device1PubKey() { initIfNeeded(); return device1PubKey.clone(); } - public static byte[] loginPrivKey() { - initIfNeeded(); - return loginPrivKey.clone(); - } + // ========================= + // USER2 getters + // ========================= + public static String login2() { initIfNeeded(); return login2; } + public static String bchName2(){ initIfNeeded(); return bchName2; } - public static byte[] loginPubKey() { - initIfNeeded(); - return loginPubKey.clone(); - } - - public static byte[] devicePrivKey() { - initIfNeeded(); - return devicePrivKey.clone(); - } - - public static byte[] devicePubKey() { - initIfNeeded(); - return devicePubKey.clone(); - } + public static byte[] login2PrivKey() { initIfNeeded(); return login2PrivKey.clone(); } + public static byte[] login2PubKey() { initIfNeeded(); return login2PubKey.clone(); } + public static byte[] device2PrivKey(){ initIfNeeded(); return device2PrivKey.clone(); } + public static byte[] device2PubKey() { initIfNeeded(); return device2PubKey.clone(); } private static String bytesToHexShort(byte[] b) { if (b == null) return "null"; diff --git a/src/test/java/test/it/utils/JsonBuilders.java b/src/test/java/test/it/utils/JsonBuilders.java index f2a8059..3e15683 100644 --- a/src/test/java/test/it/utils/JsonBuilders.java +++ b/src/test/java/test/it/utils/JsonBuilders.java @@ -8,7 +8,37 @@ import java.util.Base64; public final class JsonBuilders { private JsonBuilders(){} + // ========================= + // AddUser USER1 (как было) + // ========================= 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 """ { "op": "AddUser", @@ -23,10 +53,10 @@ public final class JsonBuilders { } """.formatted( requestId, - TestConfig.LOGIN(), - TestConfig.BCH_NAME(), - TestConfig.LOGIN_PUBKEY_B64(), - TestConfig.DEVICE_PUBKEY_B64(), + login, + blockchainName, + loginKeyB64, + deviceKeyB64, TestConfig.TEST_BCH_LIMIT ); } @@ -43,7 +73,7 @@ public final class JsonBuilders { public static String createAuthSession(String requestId, String authNonce, String storagePwd) { long timeMs = System.currentTimeMillis(); - String sigB64 = signAuthorificated(authNonce, timeMs); + String sigB64 = signAuthorificated(authNonce, timeMs, TestConfig.DEVICE_PRIV_KEY()); return """ { @@ -105,12 +135,17 @@ public final class JsonBuilders { /** * Подпись для режима AUTH_IN_PROGRESS: * 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; 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); } + + // старый метод оставим для совместимости + public static String signAuthorificated(String authNonce, long timeMs) { + return signAuthorificated(authNonce, timeMs, TestConfig.DEVICE_PRIV_KEY()); + } } \ No newline at end of file diff --git a/src/test/java/test/it/utils/TestConfig.java b/src/test/java/test/it/utils/TestConfig.java index cb0fd8e..2567d9c 100644 --- a/src/test/java/test/it/utils/TestConfig.java +++ b/src/test/java/test/it/utils/TestConfig.java @@ -5,19 +5,11 @@ import java.util.Base64; /** * Конфиг для IT тестов. * - * ЛОГИКА: - * - login по умолчанию берём из DEFAULT_LOGIN - * - можно переопределить запуском: - * -Dit.login=anya24 - * -Dit.bchSuffix=001 + * ДОБАВЛЕНО: + * - Второй пользователь (LOGIN2) + его blockchainName и ключи. * - * ВАЖНО: - * - ключи/имя блокчейна вычисляются из login (через ItRunContext). - * - тесты можно запускать по отдельности, ItRunContext сам инициализируется при первом обращении. - * - * ЛОГИ: - * - детальный вывод включается флагом: - * -Dit.debug=true + * Важно: + * - Имена/ключи вычисляются детерминированно из логина (см. ItRunContext). */ public final class TestConfig { @@ -26,9 +18,12 @@ public final class TestConfig { // Твой WS URI public static final String WS_URI = "ws://localhost:7070/ws"; - // ======= По умолчанию (можно поменять под свою среду) ======= + // ======= Пользователь #1 (по умолчанию) ======= public static final String DEFAULT_LOGIN = "Anya"; + // ======= Пользователь #2 (новый) ======= + public static final String DEFAULT_LOGIN2 = "Anya2"; + // Суффикс блокчейна по твоему правилу: login + 3 цифры 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"; - /** DEBUG-режим: подробные логи отправки/получения/ожиданий (по умолчанию false). */ + /** DEBUG-режим: подробные логи (по умолчанию true, как у тебя). */ public static boolean DEBUG() { return Boolean.parseBoolean(System.getProperty("it.debug", "true")); } - /** login для прогона (по умолчанию DEFAULT_LOGIN, можно переопределить -Dit.login=...). */ + // ========================= + // USER #1 + // ========================= + + /** login для прогона (user1). */ public static String LOGIN() { return System.getProperty("it.login", DEFAULT_LOGIN); } - /** Суффикс для имени блокчейна (по умолчанию DEFAULT_BCH_SUFFIX_3, можно переопределить -Dit.bchSuffix=...). */ + /** Суффикс для имени блокчейна (user1). */ public static String BCH_SUFFIX_3() { return System.getProperty("it.bchSuffix", DEFAULT_BCH_SUFFIX_3); } - /** blockchainName по правилу: login + суффикс. */ + /** blockchainName по правилу: login + суффикс (user1). */ public static String BCH_NAME() { 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() { - return ItRunContext.loginPrivKey(); + 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()); } + + // ========================= + // USER #2 + // ========================= + + /** login второго пользователя. Можно переопределить -Dit.login2=... */ + public static String LOGIN2() { + return System.getProperty("it.login2", DEFAULT_LOGIN2); } - public static byte[] LOGIN_PUB_KEY() { - return ItRunContext.loginPubKey(); + /** blockchainName второго: login2 + тот же суффикс. */ + public static String BCH_NAME2() { + return LOGIN2() + BCH_SUFFIX_3(); } - public static byte[] DEVICE_PRIV_KEY() { - return ItRunContext.devicePrivKey(); - } + public static byte[] LOGIN2_PRIV_KEY() { return ItRunContext.login2PrivKey(); } + 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() { - return ItRunContext.devicePubKey(); - } - - 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 LOGIN2_PUBKEY_B64() { return Base64.getEncoder().encodeToString(LOGIN2_PUB_KEY()); } + public static String DEVICE2_PUBKEY_B64() { return Base64.getEncoder().encodeToString(DEVICE2_PUB_KEY()); } /** Псевдо-пароль хранилища — достаточно для тестов. */ public static String fakeStoragePwd() {