Добавил массовое тестовое заполнение через API: A1..A10 и дружеские связи

- Переименован тест в Seed_TestDataPopulation (не IT_07_*)\n- Тест создаёт пользователей A1..A10 через AddUser\n- Дружеские связи формируются через AddBlock (CONNECTION_FRIEND)\n- Добавлен контроль количества друзей (A1=5, A2=7, A3=3)\n- Тест включён в обязательный запуск всех IT и в suite\n- Обновлена TASKS-документация по тестовым логинам
This commit is contained in:
AidarKC 2026-04-06 10:28:22 +03:00
parent 6d8777da83
commit 9cbff47194
4 changed files with 267 additions and 2 deletions

View File

@ -179,3 +179,25 @@ ID генерируются в формате:
- `AddCloseFriend` сейчас пишет прямое состояние связи (upsert), а не полный blockchain-поток создания блоков. - `AddCloseFriend` сейчас пишет прямое состояние связи (upsert), а не полный blockchain-поток создания блоков.
Это ожидаемо для handoff: задача следующего исполнителя — довести до production-стабильности. Это ожидаемо для handoff: задача следующего исполнителя — довести до production-стабильности.
---
## 9) Тестовые логины для быстрой проверки
Для проверки работы системы можно использовать специальные тестовые аккаунты
после выполнения теста `Seed_TestDataPopulation` (он создаёт их через API):
- `A1`
- `A2`
- `A3`
- `A4`
- `A5`
- `A6`
- `A7`
- `A8`
- `A9`
- `A10`
Общий пароль для этих аккаунтов:
- `1`

View File

@ -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<String> users = List.of("A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10");
for (String login : users) {
createUserViaApi(ws, r, login, keys, t);
}
Map<String, ChainState> states = new HashMap<>();
Map<String, AddBlockSender> senders = new HashMap<>();
Map<String, byte[]> 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<String, AddBlockSender> senders,
Map<String, ChainState> states,
Map<String, byte[]> 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<String> 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;
}
}
}

View File

@ -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_04_UserParams_NoAuth;
import test.it.cases.IT_05_UserConnections; import test.it.cases.IT_05_UserConnections;
import test.it.cases.IT_06_ChannelsApi; import test.it.cases.IT_06_ChannelsApi;
import test.it.cases.Seed_TestDataPopulation;
import test.it.utils.log.TestLog; import test.it.utils.log.TestLog;
import java.util.ArrayList; import java.util.ArrayList;
@ -60,6 +61,9 @@ public class IT_RunAllMain {
String s6 = IT_06_ChannelsApi.run(); summaries.add(s6); String s6 = IT_06_ChannelsApi.run(); summaries.add(s6);
if (s6.contains("FAIL:")) { failed++; if (STOP_ON_FIRST_FAIL) return finishEarly(summaries, failed); } 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); return finish(summaries, failed);
} }

View File

@ -5,6 +5,7 @@ import org.junit.platform.suite.api.Suite;
import test.it.cases.IT_01_AddUser; import test.it.cases.IT_01_AddUser;
import test.it.cases.IT_02_Sessions; import test.it.cases.IT_02_Sessions;
import test.it.cases.IT_03_AddBlock_NoAuth; import test.it.cases.IT_03_AddBlock_NoAuth;
import test.it.cases.Seed_TestDataPopulation;
/** /**
* Сьют, который запускает IT тесты строго в заданном порядке. * Сьют, который запускает IT тесты строго в заданном порядке.
@ -16,8 +17,9 @@ import test.it.cases.IT_03_AddBlock_NoAuth;
@SelectClasses({ @SelectClasses({
IT_01_AddUser.class, IT_01_AddUser.class,
IT_02_Sessions.class, IT_02_Sessions.class,
IT_03_AddBlock_NoAuth.class IT_03_AddBlock_NoAuth.class,
Seed_TestDataPopulation.class
}) })
public class IT_00_Suite { public class IT_00_Suite {
// пусто // пусто
} }