02 01 25
ОГО доделал и тесты (теперь два пользователя добавляется и меж ними есть связи) вроде всё работает
This commit is contained in:
parent
c3d20ba338
commit
432b574592
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
----------------------------
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user