From 9cbff47194160898a46f8bd708cc25456e3eef51fa3b2138f270b9c87a88f283 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Mon, 6 Apr 2026 10:28:22 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BC=D0=B0=D1=81=D1=81=D0=BE=D0=B2=D0=BE=D0=B5=20=D1=82=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D0=B2=D0=BE=D0=B5=20=D0=B7=D0=B0=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=87=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=20API:=20A1..A10=20=D0=B8=20=D0=B4=D1=80=D1=83=D0=B6?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Переименован тест в Seed_TestDataPopulation (не IT_07_*)\n- Тест создаёт пользователей A1..A10 через AddUser\n- Дружеские связи формируются через AddBlock (CONNECTION_FRIEND)\n- Добавлен контроль количества друзей (A1=5, A2=7, A3=3)\n- Тест включён в обязательный запуск всех IT и в suite\n- Обновлена TASKS-документация по тестовым логинам --- TASKS/CHAT_PWA_HANDOFF.md | 22 ++ .../it/cases/Seed_TestDataPopulation.java | 237 ++++++++++++++++++ .../java/test/it/runner/IT_RunAllMain.java | 4 + src/test/java/test/it/suite/IT_00_Suite.java | 6 +- 4 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 src/test/java/test/it/cases/Seed_TestDataPopulation.java diff --git a/TASKS/CHAT_PWA_HANDOFF.md b/TASKS/CHAT_PWA_HANDOFF.md index a732835..630e288 100644 --- a/TASKS/CHAT_PWA_HANDOFF.md +++ b/TASKS/CHAT_PWA_HANDOFF.md @@ -179,3 +179,25 @@ ID генерируются в формате: - `AddCloseFriend` сейчас пишет прямое состояние связи (upsert), а не полный blockchain-поток создания блоков. Это ожидаемо для handoff: задача следующего исполнителя — довести до production-стабильности. + +--- + +## 9) Тестовые логины для быстрой проверки + +Для проверки работы системы можно использовать специальные тестовые аккаунты +после выполнения теста `Seed_TestDataPopulation` (он создаёт их через API): + +- `A1` +- `A2` +- `A3` +- `A4` +- `A5` +- `A6` +- `A7` +- `A8` +- `A9` +- `A10` + +Общий пароль для этих аккаунтов: + +- `1` diff --git a/src/test/java/test/it/cases/Seed_TestDataPopulation.java b/src/test/java/test/it/cases/Seed_TestDataPopulation.java new file mode 100644 index 0000000..2354613 --- /dev/null +++ b/src/test/java/test/it/cases/Seed_TestDataPopulation.java @@ -0,0 +1,237 @@ +package test.it.cases; + +import blockchain.MsgSubType; +import blockchain.body.ConnectionBody; +import blockchain.body.HeaderBody; +import test.it.blockchain.AddBlockSender; +import test.it.blockchain.ChainState; +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 utils.crypto.Ed25519Util; +import utils.crypto.HashSHA256Util; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Seed_TestDataPopulation + * + * ВАЖНО: + * - НЕ заполняет БД напрямую. + * - Создаёт тестовых пользователей A1..A10 через API AddUser. + * - Создаёт сеть дружбы через API AddBlock (CONNECTION_FRIEND). + */ +public class Seed_TestDataPopulation { + + private static final String PASSWORD = "1"; + + public static void main(String[] args) { + System.out.println(run()); + } + + public static String run() { + TestResult r = new TestResult("Seed_TestDataPopulation"); + Duration t = Duration.ofSeconds(5); + + try (WsSession ws = WsSession.open()) { + UserKeys keys = deriveKeysFromPassword(PASSWORD); + + List users = List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10"); + + for (String login : users) { + createUserViaApi(ws, r, login, keys, t); + } + + Map states = new HashMap<>(); + Map senders = new HashMap<>(); + Map headerHashes = new HashMap<>(); + + for (String login : users) { + ChainState state = new ChainState(); + AddBlockSender sender = new AddBlockSender(ws, state, login, bch(login), keys.blockchainPrivate32); + sender.send(new HeaderBody(login), t); + byte[] headerHash = state.getHash32(0); + if (headerHash == null) { + r.fail("Не удалось получить hash HEADER для " + login); + fail("Header hash missing for " + login); + } + states.put(login, state); + senders.put(login, sender); + headerHashes.put(login, headerHash); + } + + // Насыщенная сеть дружбы (взаимно). Целевые контрольные значения: + // A1=5 друзей, A2=7 друзей (<8), A3=3 друга. + addMutualFriend(senders, states, headerHashes, "A1", "A2", t); + addMutualFriend(senders, states, headerHashes, "A1", "A3", t); + addMutualFriend(senders, states, headerHashes, "A1", "A4", t); + addMutualFriend(senders, states, headerHashes, "A1", "A5", t); + addMutualFriend(senders, states, headerHashes, "A1", "A6", t); + + addMutualFriend(senders, states, headerHashes, "A2", "A3", t); + addMutualFriend(senders, states, headerHashes, "A2", "A4", t); + addMutualFriend(senders, states, headerHashes, "A2", "A5", t); + addMutualFriend(senders, states, headerHashes, "A2", "A6", t); + addMutualFriend(senders, states, headerHashes, "A2", "A7", t); + addMutualFriend(senders, states, headerHashes, "A2", "A8", t); + + addMutualFriend(senders, states, headerHashes, "A3", "A4", t); + addMutualFriend(senders, states, headerHashes, "A4", "A5", t); + addMutualFriend(senders, states, headerHashes, "A5", "A6", t); + addMutualFriend(senders, states, headerHashes, "A5", "A7", t); + addMutualFriend(senders, states, headerHashes, "A6", "A8", t); + addMutualFriend(senders, states, headerHashes, "A6", "A9", t); + addMutualFriend(senders, states, headerHashes, "A7", "A10", t); + addMutualFriend(senders, states, headerHashes, "A8", "A9", t); + addMutualFriend(senders, states, headerHashes, "A8", "A10", t); + addMutualFriend(senders, states, headerHashes, "A9", "A10", t); + + verifyOutFriendsCount(ws, r, "A1", 5, t); + verifyOutFriendsCount(ws, r, "A2", 7, t); + verifyOutFriendsCount(ws, r, "A3", 3, t); + + r.ok("Пользователи A1..A10 созданы через API, дружеские связи созданы через AddBlock"); + } catch (Throwable e) { + r.fail("Ошибка IT_07: " + e.getMessage()); + fail("IT_07 failed", e); + } + + return r.summaryLine(); + } + + private static void createUserViaApi(WsSession ws, TestResult r, String login, UserKeys keys, Duration t) { + String requestId = TestIds.next("adduser_a"); + String req = """ + { + "op": "AddUser", + "requestId": "%s", + "payload": { + "login": "%s", + "blockchainName": "%s", + "solanaKey": "%s", + "blockchainKey": "%s", + "deviceKey": "%s", + "bchLimit": 50000000 + } + } + """.formatted( + requestId, + login, + bch(login), + keys.solanaPublicB64, + keys.blockchainPublicB64, + keys.devicePublicB64 + ); + + String resp = ws.call("AddUser#" + login, req, t); + int st = JsonParsers.status(resp); + + if (st == 200) { + r.ok("AddUser " + login + ": created"); + return; + } + + if (st == 409) { + String code = JsonParsers.errorCode(resp); + if ("USER_ALREADY_EXISTS".equals(code) + || "BLOCKCHAIN_ALREADY_EXISTS".equals(code) + || "BLOCKCHAIN_STATE_ALREADY_EXISTS".equals(code)) { + r.ok("AddUser " + login + ": already exists (" + code + ")"); + return; + } + } + + r.fail("AddUser " + login + " unexpected status=" + st + ", resp=" + resp); + fail("AddUser failed for " + login); + } + + private static void sendFriendConnection(AddBlockSender sender, + ChainState st, + String targetBch, + byte[] targetHeaderHash, + Duration timeout) { + ChainState.NextLine ln = st.nextLineByType(ChainState.TYPE_CONNECTION); + sender.send(new ConnectionBody( + 0, + ln.prevLineNumber, + ln.prevLineHash32, + ln.thisLineNumber, + MsgSubType.CONNECTION_FRIEND, + targetBch, + 0, + targetHeaderHash + ), timeout); + } + + private static void addMutualFriend(Map senders, + Map states, + Map headerHashes, + String a, + String b, + Duration t) { + sendFriendConnection(senders.get(a), states.get(a), bch(b), headerHashes.get(b), t); + sendFriendConnection(senders.get(b), states.get(b), bch(a), headerHashes.get(a), t); + } + + private static void verifyOutFriendsCount(WsSession ws, TestResult r, String login, int expectedCount, Duration t) { + String resp = ws.call("GetFriendsLists#" + login, JsonBuilders.getFriendsLists(login), t); + int st = JsonParsers.status(resp); + if (st != 200) { + r.fail("GetFriendsLists " + login + " status=" + st + ", resp=" + resp); + fail("GetFriendsLists failed for " + login); + } + + List out = JsonParsers.friendsOut(resp); + if (out.size() != expectedCount) { + r.fail("У " + login + " ожидалось out_friends=" + expectedCount + ", фактически=" + out.size() + ", resp=" + resp); + fail("Unexpected friends count for " + login); + } + + r.ok("GetFriendsLists " + login + ": out_friends=" + out.size()); + } + + private static String bch(String login) { + return login + "-001"; + } + + private static UserKeys deriveKeysFromPassword(String password) { + byte[] base = HashSHA256Util.sha256(password.getBytes(StandardCharsets.UTF_8)); + String baseB64 = Base64.getEncoder().encodeToString(base); + + byte[] rootPriv = HashSHA256Util.sha256((baseB64 + "root.key").getBytes(StandardCharsets.UTF_8)); + byte[] bchPriv = HashSHA256Util.sha256((baseB64 + "bch.key").getBytes(StandardCharsets.UTF_8)); + byte[] devPriv = HashSHA256Util.sha256((baseB64 + "dev.key").getBytes(StandardCharsets.UTF_8)); + + String rootPubB64 = Base64.getEncoder().encodeToString(Ed25519Util.derivePublicKey(rootPriv)); + String bchPubB64 = Base64.getEncoder().encodeToString(Ed25519Util.derivePublicKey(bchPriv)); + String devPubB64 = Base64.getEncoder().encodeToString(Ed25519Util.derivePublicKey(devPriv)); + + return new UserKeys(rootPubB64, bchPubB64, devPubB64, bchPriv); + } + + private static final class UserKeys { + final String solanaPublicB64; + final String blockchainPublicB64; + final String devicePublicB64; + final byte[] blockchainPrivate32; + + private UserKeys(String solanaPublicB64, + String blockchainPublicB64, + String devicePublicB64, + byte[] blockchainPrivate32) { + this.solanaPublicB64 = solanaPublicB64; + this.blockchainPublicB64 = blockchainPublicB64; + this.devicePublicB64 = devicePublicB64; + this.blockchainPrivate32 = blockchainPrivate32; + } + } +} diff --git a/src/test/java/test/it/runner/IT_RunAllMain.java b/src/test/java/test/it/runner/IT_RunAllMain.java index 199db7e..6524bed 100644 --- a/src/test/java/test/it/runner/IT_RunAllMain.java +++ b/src/test/java/test/it/runner/IT_RunAllMain.java @@ -7,6 +7,7 @@ import test.it.cases.IT_03_AddBlock_NoAuth; import test.it.cases.IT_04_UserParams_NoAuth; import test.it.cases.IT_05_UserConnections; import test.it.cases.IT_06_ChannelsApi; +import test.it.cases.Seed_TestDataPopulation; import test.it.utils.log.TestLog; import java.util.ArrayList; @@ -60,6 +61,9 @@ public class IT_RunAllMain { String s6 = IT_06_ChannelsApi.run(); summaries.add(s6); if (s6.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); } + String s7 = Seed_TestDataPopulation.run(); summaries.add(s7); + if (s7.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); } + return finish(summaries, failed); } diff --git a/src/test/java/test/it/suite/IT_00_Suite.java b/src/test/java/test/it/suite/IT_00_Suite.java index bd7a47f..a7c7516 100644 --- a/src/test/java/test/it/suite/IT_00_Suite.java +++ b/src/test/java/test/it/suite/IT_00_Suite.java @@ -5,6 +5,7 @@ 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; /** * Сьют, который запускает IT тесты строго в заданном порядке. @@ -16,8 +17,9 @@ import test.it.cases.IT_03_AddBlock_NoAuth; @SelectClasses({ IT_01_AddUser.class, IT_02_Sessions.class, - IT_03_AddBlock_NoAuth.class + IT_03_AddBlock_NoAuth.class, + Seed_TestDataPopulation.class }) public class IT_00_Suite { // пусто -} \ No newline at end of file +}