28 12 25
Всё ещё не работает проверка линий. Залил старые тесты какие есть - каке есть
This commit is contained in:
parent
c523816cdf
commit
795341dd8d
@ -1,10 +1,7 @@
|
|||||||
package test.it;
|
package test.it;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import test.it.utils.JsonBuilders;
|
import test.it.utils.*;
|
||||||
import test.it.utils.JsonParsers;
|
|
||||||
import test.it.utils.TestConfig;
|
|
||||||
import test.it.utils.WsTestClient;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
@ -12,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
|
|
||||||
public class IT_01_AddUser {
|
public class IT_01_AddUser {
|
||||||
|
|
||||||
// ANSI цвета (работает в большинстве терминалов)
|
// ANSI цвета
|
||||||
private static final String R = "\u001B[0m";
|
private static final String R = "\u001B[0m";
|
||||||
private static final String G = "\u001B[32m";
|
private static final String G = "\u001B[32m";
|
||||||
private static final String Y = "\u001B[33m";
|
private static final String Y = "\u001B[33m";
|
||||||
@ -33,23 +30,29 @@ public class IT_01_AddUser {
|
|||||||
System.out.println(G + "✅ " + s + R);
|
System.out.println(G + "✅ " + s + R);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void warn(String s) {
|
|
||||||
System.out.println(Y + "⚠️ " + s + R);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void boom(String s) {
|
private static void boom(String s) {
|
||||||
System.out.println(RED + "****************************************************************" + R);
|
System.out.println(RED + "****************************************************************" + R);
|
||||||
System.out.println(RED + "❌ " + s + R);
|
System.out.println(RED + "❌ " + s + R);
|
||||||
System.out.println(RED + "****************************************************************" + R);
|
System.out.println(RED + "****************************************************************" + R);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// чтобы тест можно было запускать вообще без JUnit
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
new IT_01_AddUser().addUser_shouldReturn200_orAlreadyExists();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void addUser_shouldReturn200_orAlreadyExists() {
|
void addUser_shouldReturn200_orAlreadyExists() {
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
|
||||||
title("AddUserIT: проверка добавления пользователя (200 OK) или 'уже существует' (409 USER_ALREADY_EXISTS)");
|
title("AddUserIT: проверка добавления пользователя (200 OK) или 'уже существует' (409 USER_ALREADY_EXISTS)");
|
||||||
|
System.out.println("Используем:");
|
||||||
|
System.out.println(" login = " + TestConfig.LOGIN());
|
||||||
|
System.out.println(" blockchainName = " + TestConfig.BCH_NAME());
|
||||||
System.out.println("Ожидание:");
|
System.out.println("Ожидание:");
|
||||||
System.out.println(" - сервер принимает AddUser и возвращает:");
|
System.out.println(" - 200 (создан)");
|
||||||
System.out.println(" * 200 (пользователь создан/добавлен)");
|
System.out.println(" - или 409 + payload.code=USER_ALREADY_EXISTS\n");
|
||||||
System.out.println(" * либо 409 + payload.code=USER_ALREADY_EXISTS (если уже есть)\n");
|
|
||||||
|
|
||||||
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
|
|
||||||
@ -73,12 +76,9 @@ public class IT_01_AddUser {
|
|||||||
boolean already = (st == 409);
|
boolean already = (st == 409);
|
||||||
|
|
||||||
if (already) {
|
if (already) {
|
||||||
// ВАЖНО: payload.code (не errorCode)
|
|
||||||
String code = JsonParsers.errorCode(resp);
|
String code = JsonParsers.errorCode(resp);
|
||||||
|
|
||||||
System.out.println("ℹ️ server_code=" + code);
|
System.out.println("ℹ️ server_code=" + code);
|
||||||
|
|
||||||
// Если 409 пришёл, требуем понятный code
|
|
||||||
try {
|
try {
|
||||||
assertEquals("USER_ALREADY_EXISTS", code,
|
assertEquals("USER_ALREADY_EXISTS", code,
|
||||||
"Expected code=USER_ALREADY_EXISTS, but got: " + code + ", resp=" + resp);
|
"Expected code=USER_ALREADY_EXISTS, but got: " + code + ", resp=" + resp);
|
||||||
@ -99,7 +99,6 @@ public class IT_01_AddUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (AssertionError | RuntimeException e) {
|
} catch (AssertionError | RuntimeException e) {
|
||||||
// чтобы “красным” было видно даже если Gradle/IDE печатает стек отдельно
|
|
||||||
boom("ТЕСТ УПАЛ: AddUserIT. Причина: " + e.getMessage());
|
boom("ТЕСТ УПАЛ: AddUserIT. Причина: " + e.getMessage());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,7 @@ package test.it;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import test.it.utils.JsonBuilders;
|
import test.it.utils.*;
|
||||||
import test.it.utils.JsonParsers;
|
|
||||||
import test.it.utils.TestConfig;
|
|
||||||
import test.it.utils.WsTestClient;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -39,10 +36,6 @@ public class IT_02_Sessions {
|
|||||||
System.out.println(G + "✅ " + s + R);
|
System.out.println(G + "✅ " + s + R);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void warn(String s) {
|
|
||||||
System.out.println(Y + "⚠️ " + s + R);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void boom(String s) {
|
private static void boom(String s) {
|
||||||
System.out.println(RED + "****************************************************************" + R);
|
System.out.println(RED + "****************************************************************" + R);
|
||||||
System.out.println(RED + "❌ " + s + R);
|
System.out.println(RED + "❌ " + s + R);
|
||||||
@ -72,8 +65,16 @@ public class IT_02_Sessions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
ensureUserExists();
|
||||||
|
new IT_02_Sessions().sessions_flow_shouldCreateListRefreshCloseCorrectly();
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void ensureUserExists() {
|
static void ensureUserExists() {
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
|
||||||
title("SessionsIT (BeforeAll): предусловие — пользователь должен существовать (AddUser: 200 или 409)");
|
title("SessionsIT (BeforeAll): предусловие — пользователь должен существовать (AddUser: 200 или 409)");
|
||||||
|
|
||||||
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
@ -86,7 +87,6 @@ public class IT_02_Sessions {
|
|||||||
|
|
||||||
int st = JsonParsers.status(resp);
|
int st = JsonParsers.status(resp);
|
||||||
|
|
||||||
// 200 или "уже есть" — ок
|
|
||||||
if (st == 200) {
|
if (st == 200) {
|
||||||
ok("BeforeAll: пользователь создан/добавлен (status=200)");
|
ok("BeforeAll: пользователь создан/добавлен (status=200)");
|
||||||
} else if (st == 409) {
|
} else if (st == 409) {
|
||||||
@ -106,7 +106,11 @@ public class IT_02_Sessions {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void sessions_flow_shouldCreateListRefreshCloseCorrectly() {
|
void sessions_flow_shouldCreateListRefreshCloseCorrectly() {
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
|
||||||
title("SessionsIT: полный сценарий сессий (создать 2, проверить list, refresh/close, проверить очистку)");
|
title("SessionsIT: полный сценарий сессий (создать 2, проверить list, refresh/close, проверить очистку)");
|
||||||
|
System.out.println("Используем:");
|
||||||
|
System.out.println(" login = " + TestConfig.LOGIN());
|
||||||
System.out.println("Ожидание сценария:");
|
System.out.println("Ожидание сценария:");
|
||||||
System.out.println(" 1) Создаём SESSION1 через AuthChallenge + CreateAuthSession");
|
System.out.println(" 1) Создаём SESSION1 через AuthChallenge + CreateAuthSession");
|
||||||
System.out.println(" 2) Создаём SESSION2 и делаем ListSessions внутри неё (AUTH_STATUS_USER) → должны быть SESSION1 и SESSION2");
|
System.out.println(" 2) Создаём SESSION2 и делаем ListSessions внутри неё (AUTH_STATUS_USER) → должны быть SESSION1 и SESSION2");
|
||||||
@ -120,7 +124,6 @@ public class IT_02_Sessions {
|
|||||||
String s2Id, s2Pwd;
|
String s2Id, s2Pwd;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// --- create session1 ---
|
|
||||||
stepTitle("ШАГ 1: создать SESSION1 (AuthChallenge -> CreateAuthSession)");
|
stepTitle("ШАГ 1: создать SESSION1 (AuthChallenge -> CreateAuthSession)");
|
||||||
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
String r1 = "it-auth-1";
|
String r1 = "it-auth-1";
|
||||||
@ -150,7 +153,6 @@ public class IT_02_Sessions {
|
|||||||
ok("SESSION1 получена: sessionId=" + s1Id + ", sessionPwd=[получен]");
|
ok("SESSION1 получена: sessionId=" + s1Id + ", sessionPwd=[получен]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- create session2 and list inside (AUTH_STATUS_USER) ---
|
|
||||||
stepTitle("ШАГ 2: создать SESSION2 и ListSessions внутри неё (AUTH_STATUS_USER) → должны быть SESSION1+SESSION2");
|
stepTitle("ШАГ 2: создать SESSION2 и ListSessions внутри неё (AUTH_STATUS_USER) → должны быть SESSION1+SESSION2");
|
||||||
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
String r1 = "it-auth-2";
|
String r1 = "it-auth-2";
|
||||||
@ -178,7 +180,6 @@ public class IT_02_Sessions {
|
|||||||
assertNotNull(s2Pwd);
|
assertNotNull(s2Pwd);
|
||||||
ok("SESSION2 получена: sessionId=" + s2Id + ", sessionPwd=[получен]");
|
ok("SESSION2 получена: sessionId=" + s2Id + ", sessionPwd=[получен]");
|
||||||
|
|
||||||
// list inside session2 (у тебя это AUTH_STATUS_USER без подписи)
|
|
||||||
String r3 = "it-list-in-session2";
|
String r3 = "it-list-in-session2";
|
||||||
String req3 = JsonBuilders.listSessions(r3, 0L, "");
|
String req3 = JsonBuilders.listSessions(r3, 0L, "");
|
||||||
send("ListSessions(in SESSION2)", req3);
|
send("ListSessions(in SESSION2)", req3);
|
||||||
@ -194,7 +195,6 @@ public class IT_02_Sessions {
|
|||||||
ok("Проверка OK: список содержит SESSION1 и SESSION2");
|
ok("Проверка OK: список содержит SESSION1 и SESSION2");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- list in AUTH_IN_PROGRESS (подпись по nonce) ---
|
|
||||||
stepTitle("ШАГ 3: ListSessions в AUTH_IN_PROGRESS (nonce+signature) → должны быть SESSION1+SESSION2");
|
stepTitle("ШАГ 3: ListSessions в AUTH_IN_PROGRESS (nonce+signature) → должны быть SESSION1+SESSION2");
|
||||||
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
String r1 = "it-auth-list";
|
String r1 = "it-auth-list";
|
||||||
@ -228,7 +228,6 @@ public class IT_02_Sessions {
|
|||||||
ok("Проверка OK: AUTH_IN_PROGRESS список содержит SESSION1 и SESSION2");
|
ok("Проверка OK: AUTH_IN_PROGRESS список содержит SESSION1 и SESSION2");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- refresh session1 and close session2 (from session1) ---
|
|
||||||
stepTitle("ШАГ 4: Refresh SESSION1 (входим) и Close SESSION2 (из SESSION1)");
|
stepTitle("ШАГ 4: Refresh SESSION1 (входим) и Close SESSION2 (из SESSION1)");
|
||||||
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
|
|
||||||
@ -252,7 +251,6 @@ public class IT_02_Sessions {
|
|||||||
ok("SESSION2 закрыта");
|
ok("SESSION2 закрыта");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- verify only session1 remains (AUTH_IN_PROGRESS list) ---
|
|
||||||
stepTitle("ШАГ 5: ListSessions(AUTH_IN_PROGRESS) → должна остаться только SESSION1");
|
stepTitle("ШАГ 5: ListSessions(AUTH_IN_PROGRESS) → должна остаться только SESSION1");
|
||||||
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
String r1 = "it-auth-list2";
|
String r1 = "it-auth-list2";
|
||||||
@ -284,7 +282,6 @@ public class IT_02_Sessions {
|
|||||||
ok("Проверка OK: осталась только SESSION1");
|
ok("Проверка OK: осталась только SESSION1");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- close session1 in AUTH_IN_PROGRESS ---
|
|
||||||
stepTitle("ШАГ 6: Close SESSION1 в AUTH_IN_PROGRESS");
|
stepTitle("ШАГ 6: Close SESSION1 в AUTH_IN_PROGRESS");
|
||||||
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
String r1 = "it-auth-close-s1";
|
String r1 = "it-auth-close-s1";
|
||||||
@ -310,7 +307,6 @@ public class IT_02_Sessions {
|
|||||||
ok("SESSION1 закрыта");
|
ok("SESSION1 закрыта");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- verify empty list ---
|
|
||||||
stepTitle("ШАГ 7: ListSessions(AUTH_IN_PROGRESS) → ожидаем пустой список");
|
stepTitle("ШАГ 7: ListSessions(AUTH_IN_PROGRESS) → ожидаем пустой список");
|
||||||
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient c = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
String r1 = "it-auth-list-empty";
|
String r1 = "it-auth-list-empty";
|
||||||
|
|||||||
82
src/test/java/test/it/IT_RunAllMain.java
Normal file
82
src/test/java/test/it/IT_RunAllMain.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package test.it;
|
||||||
|
|
||||||
|
import test.it.utils.TestConfig;
|
||||||
|
import test.it.utils.TestColors;
|
||||||
|
import test.it.utils.ItRunContext;
|
||||||
|
import test.it.ws.IT_03_AddBlock_NoAuth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ручной запуск всех IT тестов БЕЗ JUnit / Suite.
|
||||||
|
*
|
||||||
|
* Делает:
|
||||||
|
* 1) чистит папку data/
|
||||||
|
* 2) запускает 3 теста по очереди (через их main)
|
||||||
|
*
|
||||||
|
* Запуск из IDE:
|
||||||
|
* Run 'main' этого класса
|
||||||
|
*
|
||||||
|
* Запуск из консоли:
|
||||||
|
* ./gradlew testClasses
|
||||||
|
* java -cp build/classes/java/test:build/resources/test:build/classes/java/main:build/resources/main <тут_свой_classpath> test.it.IT_RunAllMain
|
||||||
|
*
|
||||||
|
* (Classpath зависит от твоего Gradle, но в IDE проще всего)
|
||||||
|
*/
|
||||||
|
public class IT_RunAllMain {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
|
||||||
|
banner("ШАГ 0: очистка data/");
|
||||||
|
cleanupDataDir(TestConfig.DATA_DIR);
|
||||||
|
|
||||||
|
banner("ШАГ 1: IT_01_AddUser");
|
||||||
|
IT_01_AddUser.main(new String[0]);
|
||||||
|
|
||||||
|
banner("ШАГ 2: IT_02_Sessions");
|
||||||
|
IT_02_Sessions.main(new String[0]);
|
||||||
|
|
||||||
|
banner("ШАГ 3: IT_03_AddBlock_NoAuth");
|
||||||
|
IT_03_AddBlock_NoAuth.main(new String[0]);
|
||||||
|
|
||||||
|
System.out.println(TestColors.G + "\n✅ ВСЕ 3 IT ТЕСТА УСПЕШНО ЗАВЕРШЕНЫ\n" + TestColors.R);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println(TestColors.RED + "\n❌ IT_RunAllMain: ПРОГОН УПАЛ\n" + TestColors.R);
|
||||||
|
t.printStackTrace(System.out);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void banner(String s) {
|
||||||
|
System.out.println(TestColors.C + "\n============================================================" + TestColors.R);
|
||||||
|
System.out.println(TestColors.C + s + TestColors.R);
|
||||||
|
System.out.println(TestColors.C + "============================================================\n" + TestColors.R);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void cleanupDataDir(String dirName) throws IOException {
|
||||||
|
Path dir = Paths.get(dirName);
|
||||||
|
if (!Files.exists(dir)) {
|
||||||
|
System.out.println("ℹ️ data dir not found: " + dir.toAbsolutePath() + " (создаю)");
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// удаляем ВСЁ внутри папки, но саму папку оставляем
|
||||||
|
Files.walk(dir)
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.filter(p -> !p.equals(dir))
|
||||||
|
.forEach(p -> {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(p);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Не смог удалить: " + p.toAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println("✅ data очищена: " + dir.toAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,17 +2,16 @@ package test.it.utils;
|
|||||||
|
|
||||||
import utils.crypto.Ed25519Util;
|
import utils.crypto.Ed25519Util;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Глобальный контекст интеграционного прогона (один запуск Gradle test).
|
* Глобальный контекст IT прогона (одна JVM).
|
||||||
*
|
*
|
||||||
* ВАЖНО:
|
* ТЕПЕРЬ:
|
||||||
* - инициализируется ровно один раз на весь процесс JVM;
|
* - login берётся из TestConfig.LOGIN()
|
||||||
* - хранит случайный login + blockchainName + ключи, чтобы все IT тесты работали в одной "сессии данных".
|
* - blockchainName = TestConfig.BCH_NAME()
|
||||||
|
* - ключи генерятся ДЕТЕРМИНИРОВАННО из login (как ты хотела)
|
||||||
|
*
|
||||||
|
* ПЛЮС:
|
||||||
|
* - тесты можно запускать по одному — initIfNeeded() вызовется автоматически.
|
||||||
*/
|
*/
|
||||||
public final class ItRunContext {
|
public final class ItRunContext {
|
||||||
|
|
||||||
@ -30,25 +29,16 @@ public final class ItRunContext {
|
|||||||
|
|
||||||
private ItRunContext() {}
|
private ItRunContext() {}
|
||||||
|
|
||||||
public static void initOnce() {
|
/** Инициализировать, если ещё не инициализировано. */
|
||||||
|
public static void initIfNeeded() {
|
||||||
if (inited) return;
|
if (inited) return;
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
if (inited) return;
|
if (inited) return;
|
||||||
|
|
||||||
// 1) Генерим читаемый суффикс по времени + случайности, чтобы не было коллизий.
|
login = TestConfig.LOGIN();
|
||||||
String ts = LocalDateTime.now()
|
blockchainName = TestConfig.BCH_NAME();
|
||||||
.format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss", Locale.ROOT));
|
|
||||||
|
|
||||||
String rnd = randomBase32(6).toLowerCase(Locale.ROOT);
|
// 1) Генерация ключей ИЗ login
|
||||||
|
|
||||||
// login должен быть валидным по твоим правилам (латиница + цифры + _)
|
|
||||||
// Пример: it_20251226_173012_ab12cd
|
|
||||||
login = "it_" + ts + "_" + rnd;
|
|
||||||
|
|
||||||
// 2) blockchainName по правилу: login + 4 цифры
|
|
||||||
blockchainName = login + TestConfig.BCH_SUFFIX_3;
|
|
||||||
|
|
||||||
// 3) Генерация ключей ИЗ login (как ты попросила)
|
|
||||||
// loginKey: приватный ключ = SHA-256(login)
|
// loginKey: приватный ключ = SHA-256(login)
|
||||||
loginPrivKey = Ed25519Util.generatePrivateKeyFromString(login);
|
loginPrivKey = Ed25519Util.generatePrivateKeyFromString(login);
|
||||||
loginPubKey = Ed25519Util.derivePublicKey(loginPrivKey);
|
loginPubKey = Ed25519Util.derivePublicKey(loginPrivKey);
|
||||||
@ -61,7 +51,7 @@ public final class ItRunContext {
|
|||||||
inited = true;
|
inited = true;
|
||||||
|
|
||||||
System.out.println(TestColors.C + "\n============================================================" + TestColors.R);
|
System.out.println(TestColors.C + "\n============================================================" + TestColors.R);
|
||||||
System.out.println(TestColors.C + "IT ПРОГОН: сгенерированы случайные данные" + TestColors.R);
|
System.out.println(TestColors.C + "IT CONTEXT INIT: фиксированные данные из TestConfig" + TestColors.R);
|
||||||
System.out.println(TestColors.C + "============================================================" + TestColors.R);
|
System.out.println(TestColors.C + "============================================================" + TestColors.R);
|
||||||
System.out.println("login = " + login);
|
System.out.println("login = " + login);
|
||||||
System.out.println("blockchainName = " + blockchainName);
|
System.out.println("blockchainName = " + blockchainName);
|
||||||
@ -72,51 +62,35 @@ public final class ItRunContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String login() {
|
public static String login() {
|
||||||
ensureInit();
|
initIfNeeded();
|
||||||
return login;
|
return login;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String blockchainName() {
|
public static String blockchainName() {
|
||||||
ensureInit();
|
initIfNeeded();
|
||||||
return blockchainName;
|
return blockchainName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] loginPrivKey() {
|
public static byte[] loginPrivKey() {
|
||||||
ensureInit();
|
initIfNeeded();
|
||||||
return loginPrivKey.clone();
|
return loginPrivKey.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] loginPubKey() {
|
public static byte[] loginPubKey() {
|
||||||
ensureInit();
|
initIfNeeded();
|
||||||
return loginPubKey.clone();
|
return loginPubKey.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] devicePrivKey() {
|
public static byte[] devicePrivKey() {
|
||||||
ensureInit();
|
initIfNeeded();
|
||||||
return devicePrivKey.clone();
|
return devicePrivKey.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] devicePubKey() {
|
public static byte[] devicePubKey() {
|
||||||
ensureInit();
|
initIfNeeded();
|
||||||
return devicePubKey.clone();
|
return devicePubKey.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ensureInit() {
|
|
||||||
if (!inited) {
|
|
||||||
throw new IllegalStateException("ItRunContext ещё не инициализирован. Он должен быть вызван до тестов (через RussianSummaryListener).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String randomBase32(int len) {
|
|
||||||
final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
||||||
SecureRandom r = new SecureRandom();
|
|
||||||
StringBuilder sb = new StringBuilder(len);
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
sb.append(alphabet.charAt(r.nextInt(alphabet.length())));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bytesToHexShort(byte[] b) {
|
private static String bytesToHexShort(byte[] b) {
|
||||||
if (b == null) return "null";
|
if (b == null) return "null";
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|||||||
@ -23,8 +23,8 @@ public final class JsonBuilders {
|
|||||||
}
|
}
|
||||||
""".formatted(
|
""".formatted(
|
||||||
requestId,
|
requestId,
|
||||||
TestConfig.TEST_LOGIN(),
|
TestConfig.LOGIN(),
|
||||||
TestConfig.TEST_BCH_NAME(),
|
TestConfig.BCH_NAME(),
|
||||||
TestConfig.LOGIN_PUBKEY_B64(),
|
TestConfig.LOGIN_PUBKEY_B64(),
|
||||||
TestConfig.DEVICE_PUBKEY_B64(),
|
TestConfig.DEVICE_PUBKEY_B64(),
|
||||||
TestConfig.TEST_BCH_LIMIT
|
TestConfig.TEST_BCH_LIMIT
|
||||||
@ -38,7 +38,7 @@ public final class JsonBuilders {
|
|||||||
"requestId": "%s",
|
"requestId": "%s",
|
||||||
"payload": { "login": "%s" }
|
"payload": { "login": "%s" }
|
||||||
}
|
}
|
||||||
""".formatted(requestId, TestConfig.TEST_LOGIN());
|
""".formatted(requestId, TestConfig.LOGIN());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String createAuthSession(String requestId, String authNonce, String storagePwd) {
|
public static String createAuthSession(String requestId, String authNonce, String storagePwd) {
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
package test.it.utils;
|
|
||||||
|
|
||||||
import org.junit.platform.launcher.TestExecutionListener;
|
|
||||||
import org.junit.platform.launcher.TestIdentifier;
|
|
||||||
import org.junit.platform.launcher.TestPlan;
|
|
||||||
import org.junit.platform.engine.TestExecutionResult;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Слушатель JUnit, который в конце прогона печатает "главный отчёт" по-русски.
|
|
||||||
*
|
|
||||||
* Подключается через src/test/resources/junit-platform.properties
|
|
||||||
*/
|
|
||||||
public class RussianSummaryListener implements TestExecutionListener {
|
|
||||||
|
|
||||||
private final List<String> failed = new ArrayList<>();
|
|
||||||
private int total = 0;
|
|
||||||
private int passed = 0;
|
|
||||||
private int skipped = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void testPlanExecutionStarted(TestPlan testPlan) {
|
|
||||||
// Инициализируем данные прогона прямо тут, чтобы гарантированно до тестов
|
|
||||||
ItRunContext.initOnce();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) {
|
|
||||||
if (!testIdentifier.isTest()) return;
|
|
||||||
|
|
||||||
total++;
|
|
||||||
|
|
||||||
switch (testExecutionResult.getStatus()) {
|
|
||||||
case SUCCESSFUL -> passed++;
|
|
||||||
case ABORTED -> skipped++;
|
|
||||||
case FAILED -> {
|
|
||||||
String name = prettyName(testIdentifier);
|
|
||||||
String msg = testExecutionResult.getThrowable()
|
|
||||||
.map(t -> t.getClass().getSimpleName() + ": " + safeMsg(t.getMessage()))
|
|
||||||
.orElse("Причина неизвестна");
|
|
||||||
failed.add(name + " — " + msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void testPlanExecutionFinished(TestPlan testPlan) {
|
|
||||||
System.out.println(TestColors.C + "\n============================================================" + TestColors.R);
|
|
||||||
System.out.println(TestColors.C + "ГЛАВНЫЙ ОТЧЁТ IT ПРОГОНА" + TestColors.R);
|
|
||||||
System.out.println(TestColors.C + "============================================================" + TestColors.R);
|
|
||||||
|
|
||||||
System.out.println("Всего тестов: " + total);
|
|
||||||
System.out.println(TestColors.G + "Прошло: " + passed + TestColors.R);
|
|
||||||
System.out.println(TestColors.Y + "Пропущено: " + skipped + TestColors.R);
|
|
||||||
System.out.println(TestColors.RED + "Упало: " + failed.size() + TestColors.R);
|
|
||||||
|
|
||||||
if (failed.isEmpty()) {
|
|
||||||
System.out.println(TestColors.G + "\n✅ ВСЕ ТЕСТЫ ПРОШЛИ УСПЕШНО" + TestColors.R);
|
|
||||||
} else {
|
|
||||||
System.out.println(TestColors.RED + "\n❌ СПИСОК УПАВШИХ ТЕСТОВ:" + TestColors.R);
|
|
||||||
for (int i = 0; i < failed.size(); i++) {
|
|
||||||
System.out.println(TestColors.RED + (i + 1) + ") " + failed.get(i) + TestColors.R);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(TestColors.C + "------------------------------------------------------------" + TestColors.R);
|
|
||||||
System.out.println("login = " + ItRunContext.login());
|
|
||||||
System.out.println("blockchainName = " + ItRunContext.blockchainName());
|
|
||||||
System.out.println(TestColors.C + "============================================================\n" + TestColors.R);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String prettyName(TestIdentifier id) {
|
|
||||||
// displayName обычно типа: "addUser_shouldReturn200_orAlreadyExists()"
|
|
||||||
// Добавим класс, если удастся вытащить из legacyId (обычно там есть полное имя)
|
|
||||||
String dn = id.getDisplayName();
|
|
||||||
String legacy = id.getLegacyReportingName(); // часто содержит "test.it.IT_01_AddUser"
|
|
||||||
if (legacy != null && !legacy.isBlank()) {
|
|
||||||
return legacy + " :: " + (dn == null ? "test" : dn);
|
|
||||||
}
|
|
||||||
return dn == null ? "test" : dn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String safeMsg(String s) {
|
|
||||||
if (s == null) return "";
|
|
||||||
return s.replace("\n", " ").replace("\r", " ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,8 +5,15 @@ import java.util.Base64;
|
|||||||
/**
|
/**
|
||||||
* Конфиг для IT тестов.
|
* Конфиг для IT тестов.
|
||||||
*
|
*
|
||||||
|
* ЛОГИКА:
|
||||||
|
* - login по умолчанию берём из DEFAULT_LOGIN
|
||||||
|
* - можно переопределить запуском:
|
||||||
|
* -Dit.login=anya24
|
||||||
|
* -Dit.bchSuffix=001
|
||||||
|
*
|
||||||
* ВАЖНО:
|
* ВАЖНО:
|
||||||
* - login/blockchainName/ключи берём из ItRunContext (случайные на каждый прогон).
|
* - ключи/имя блокчейна вычисляются из login (через ItRunContext).
|
||||||
|
* - тесты можно запускать по отдельности, ItRunContext сам инициализируется при первом обращении.
|
||||||
*/
|
*/
|
||||||
public final class TestConfig {
|
public final class TestConfig {
|
||||||
|
|
||||||
@ -15,8 +22,11 @@ public final class TestConfig {
|
|||||||
// Твой WS URI
|
// Твой WS URI
|
||||||
public static final String WS_URI = "ws://localhost:7070/ws";
|
public static final String WS_URI = "ws://localhost:7070/ws";
|
||||||
|
|
||||||
|
// ======= По умолчанию (можно поменять под свою среду) =======
|
||||||
|
public static final String DEFAULT_LOGIN = "anya24";
|
||||||
|
|
||||||
// Суффикс блокчейна по твоему правилу: login + 3 цифры
|
// Суффикс блокчейна по твоему правилу: login + 3 цифры
|
||||||
public static final String BCH_SUFFIX_3 = "001";
|
public static final String DEFAULT_BCH_SUFFIX_3 = "001";
|
||||||
|
|
||||||
// Лимит блокчейна для AddUser
|
// Лимит блокчейна для AddUser
|
||||||
public static final long TEST_BCH_LIMIT = 50_000_000L;
|
public static final long TEST_BCH_LIMIT = 50_000_000L;
|
||||||
@ -24,14 +34,26 @@ public final class TestConfig {
|
|||||||
// Любая строка клиента (для логов)
|
// Любая строка клиента (для логов)
|
||||||
public static final String TEST_CLIENT_INFO = "it-tests";
|
public static final String TEST_CLIENT_INFO = "it-tests";
|
||||||
|
|
||||||
public static String TEST_LOGIN() {
|
// Папка данных (которую будет чистить IT_RunAllMain)
|
||||||
return ItRunContext.login();
|
public static final String DATA_DIR = "data";
|
||||||
|
|
||||||
|
/** login для прогона (по умолчанию DEFAULT_LOGIN, можно переопределить -Dit.login=...). */
|
||||||
|
public static String LOGIN() {
|
||||||
|
return System.getProperty("it.login", DEFAULT_LOGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String TEST_BCH_NAME() {
|
/** Суффикс для имени блокчейна (по умолчанию DEFAULT_BCH_SUFFIX_3, можно переопределить -Dit.bchSuffix=...). */
|
||||||
return ItRunContext.blockchainName();
|
public static String BCH_SUFFIX_3() {
|
||||||
|
return System.getProperty("it.bchSuffix", DEFAULT_BCH_SUFFIX_3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** blockchainName по правилу: login + суффикс. */
|
||||||
|
public static String BCH_NAME() {
|
||||||
|
return LOGIN() + BCH_SUFFIX_3();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======= Ключи (берём из ItRunContext) =======
|
||||||
|
|
||||||
public static byte[] LOGIN_PRIV_KEY() {
|
public static byte[] LOGIN_PRIV_KEY() {
|
||||||
return ItRunContext.loginPrivKey();
|
return ItRunContext.loginPrivKey();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,11 @@ package test.it.ws;
|
|||||||
import blockchain.BchBlockEntry;
|
import blockchain.BchBlockEntry;
|
||||||
import blockchain.BchCryptoVerifier;
|
import blockchain.BchCryptoVerifier;
|
||||||
import blockchain.body.HeaderBody;
|
import blockchain.body.HeaderBody;
|
||||||
|
import blockchain.body.ReactionBody;
|
||||||
import blockchain.body.TextBody;
|
import blockchain.body.TextBody;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import test.it.utils.ItRunContext;
|
||||||
import test.it.utils.JsonBuilders;
|
import test.it.utils.JsonBuilders;
|
||||||
import test.it.utils.JsonParsers;
|
import test.it.utils.JsonParsers;
|
||||||
import test.it.utils.TestConfig;
|
import test.it.utils.TestConfig;
|
||||||
@ -22,30 +24,45 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
/**
|
/**
|
||||||
* IT_03_AddBlock_NoAuth
|
* IT_03_AddBlock_NoAuth
|
||||||
*
|
*
|
||||||
* Интеграционный тест добавления блоков в персональный блокчейн без отдельной авторизации,
|
* Интеграционный тест добавления блоков в персональный блокчейн без отдельной авторизации.
|
||||||
* в формате твоих IT-тестов (ANSI, шаги, WsTestClient).
|
|
||||||
*
|
*
|
||||||
* Сценарий:
|
* Сценарий (как ты попросил):
|
||||||
* 1) AddBlock: HEADER (global=0, prevGlobalHash=ZERO64) -> ожидаем 200
|
* 1) AddBlock: HEADER (global=0, line=0, lineNum=0, prevGlobalHash=ZERO64) -> 200
|
||||||
* - забираем payload.serverLastGlobalHash
|
* 2) AddBlock: TEXT#1 (global=1, line=1, lineNum=1, prevGlobalHash=hash(0)) -> 200
|
||||||
* 2) AddBlock: TEXT (global=1, prevGlobalHash=serverLastGlobalHash) -> ожидаем 200
|
* 3) AddBlock: TEXT#2 (global=2, line=1, lineNum=2, prevGlobalHash=hash(1)) -> 200
|
||||||
|
* 4) AddBlock: TEXT#3 (global=3, line=1, lineNum=3, prevGlobalHash=hash(2)) -> 200
|
||||||
|
* 5) AddBlock: REACT#1 (global=4, line=2, lineNum=1, prevGlobalHash=hash(3)) -> 200
|
||||||
|
* - реакция на TEXT#1 (toBchName, toGlobal=1, toHash=hash(TEXT#1))
|
||||||
*
|
*
|
||||||
* Примечание:
|
* Важно по линиям (твоя договорённость):
|
||||||
* - lastLineHash пока равен lastGlobalHash.
|
* - line 0: нулевой блок (HEADER) один на весь блокчейн (глобальный 0)
|
||||||
* - подпись блока делаем ключом логина (loginPrivKey).
|
* - line 1 и line 2: первый блок каждой линии ссылается prevLineHash на hash(нулевого блока)
|
||||||
|
*
|
||||||
|
* В этом тесте мы ведём 2 массива:
|
||||||
|
* - lineLastNumber[line] — сколько блоков в линии (то есть последний lineNum)
|
||||||
|
* - lineLastHashHex[line] — hash последнего блока линии (HEX64)
|
||||||
*/
|
*/
|
||||||
public class IT_03_AddBlock_NoAuth {
|
public class IT_03_AddBlock_NoAuth {
|
||||||
|
|
||||||
// ANSI цвета
|
// ANSI цвета
|
||||||
private static final String R = "\u001B[0m";
|
private static final String R = "\u001B[0m";
|
||||||
private static final String G = "\u001B[32m";
|
private static final String G = "\u001B[32m";
|
||||||
private static final String Y = "\u001B[33m";
|
|
||||||
private static final String RED = "\u001B[31m";
|
private static final String RED = "\u001B[31m";
|
||||||
private static final String C = "\u001B[36m";
|
private static final String C = "\u001B[36m";
|
||||||
|
|
||||||
private static final byte[] ZERO32 = new byte[32];
|
private static final byte[] ZERO32 = new byte[32];
|
||||||
private static final String ZERO64 = "0".repeat(64);
|
private static final String ZERO64 = "0".repeat(64);
|
||||||
|
|
||||||
|
private static final short LINE_HEADER = 0;
|
||||||
|
private static final short LINE_TEXT = 1;
|
||||||
|
private static final short LINE_REACT = 2;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
ensureUserExists();
|
||||||
|
new IT_03_AddBlock_NoAuth().addBlock_shouldAppendHeaderThenTextThenReaction();
|
||||||
|
}
|
||||||
|
|
||||||
private static void line() {
|
private static void line() {
|
||||||
System.out.println(C + "------------------------------------------------------------" + R);
|
System.out.println(C + "------------------------------------------------------------" + R);
|
||||||
}
|
}
|
||||||
@ -95,6 +112,8 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void ensureUserExists() {
|
static void ensureUserExists() {
|
||||||
|
ItRunContext.initIfNeeded();
|
||||||
|
|
||||||
title("AddBlockIT (BeforeAll): предусловие — пользователь должен существовать (AddUser: 200 или 409)");
|
title("AddBlockIT (BeforeAll): предусловие — пользователь должен существовать (AddUser: 200 или 409)");
|
||||||
|
|
||||||
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
@ -125,67 +144,261 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void addBlock_shouldAppendHeaderThenText() {
|
void addBlock_shouldAppendHeaderThenTextThenReaction() {
|
||||||
title("AddBlockIT: добавить HEADER(0) и затем TEXT(1) без auth — с проверкой serverLastGlobalHash");
|
ItRunContext.initIfNeeded();
|
||||||
|
|
||||||
|
title("AddBlockIT: HEADER(0) + TEXT(1,2,3) + REACT(4->text1) без auth");
|
||||||
|
System.out.println("Используем:");
|
||||||
|
System.out.println(" login = " + TestConfig.LOGIN());
|
||||||
|
System.out.println(" blockchainName = " + TestConfig.BCH_NAME());
|
||||||
System.out.println("Ожидание:");
|
System.out.println("Ожидание:");
|
||||||
System.out.println(" 1) AddBlock HEADER (global=0, prev=ZERO64) -> 200");
|
System.out.println(" 1) HEADER (global=0, line=0, lineNum=0, prevGlobal=ZERO64) -> 200");
|
||||||
System.out.println(" - в ответе payload.serverLastGlobalHash (64 hex)");
|
System.out.println(" 2) TEXT#1 (global=1, line=1, lineNum=1, prevGlobal=hash0, prevLine=hash0) -> 200");
|
||||||
System.out.println(" 2) AddBlock TEXT (global=1, prev=serverLastGlobalHash) -> 200\n");
|
System.out.println(" 3) TEXT#2 (global=2, line=1, lineNum=2, prevGlobal=hash1, prevLine=hash1) -> 200");
|
||||||
|
System.out.println(" 4) TEXT#3 (global=3, line=1, lineNum=3, prevGlobal=hash2, prevLine=hash2) -> 200");
|
||||||
|
System.out.println(" 5) REACT#1 (global=4, line=2, lineNum=1, prevGlobal=hash3, prevLine=hash0) -> 200 (to TEXT#1)\n");
|
||||||
|
|
||||||
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) {
|
||||||
|
|
||||||
// -------------------- ШАГ 1: HEADER (global=0) --------------------
|
// ============================
|
||||||
stepTitle("ШАГ 1: AddBlock HEADER (global=0)");
|
// Локальное состояние теста
|
||||||
|
// ============================
|
||||||
|
int[] lineLastNumber = new int[8];
|
||||||
|
String[] lineLastHashHex = new String[8];
|
||||||
|
for (int i = 0; i < 8; i++) lineLastHashHex[i] = "";
|
||||||
|
|
||||||
byte[] headerFull = buildHeaderBlockFullBytes(
|
int globalLastNumber = -1;
|
||||||
|
String globalLastHashHex = ZERO64;
|
||||||
|
|
||||||
|
byte[] headerHash32 = null; // понадобится как prevLineHash для первых блоков линий 1/2
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// ШАГ 1: HEADER (global=0, line=0, lineNum=0)
|
||||||
|
// =========================================================
|
||||||
|
stepTitle("ШАГ 1: AddBlock HEADER (global=0, line=0, lineNum=0)");
|
||||||
|
|
||||||
|
BuiltBlock header = buildHeaderBlock(
|
||||||
0,
|
0,
|
||||||
(short) 0,
|
LINE_HEADER,
|
||||||
0,
|
0,
|
||||||
ZERO32,
|
ZERO32, // prevGlobalHash32
|
||||||
ZERO32
|
ZERO32 // prevLineHash32
|
||||||
);
|
);
|
||||||
|
|
||||||
String reqId1 = "it03-add-header";
|
String reqId1 = "it03-add-header";
|
||||||
String reqJson1 = buildAddBlockJson(reqId1, TestConfig.TEST_BCH_NAME(), 0, ZERO64, base64(headerFull));
|
String reqJson1 = buildAddBlockJson(reqId1, TestConfig.BCH_NAME(), 0, ZERO64, base64(header.fullBytes));
|
||||||
|
|
||||||
send("AddBlock#HEADER", reqJson1);
|
send("AddBlock(" + reqId1 + ")", reqJson1);
|
||||||
String resp1 = client.request(reqId1, reqJson1, Duration.ofSeconds(8));
|
String resp1 = client.request(reqId1, reqJson1, Duration.ofSeconds(8));
|
||||||
recv("AddBlock#HEADER", resp1);
|
recv("AddBlock(" + reqId1 + ")", resp1);
|
||||||
|
|
||||||
assert200("AddBlock#HEADER", resp1);
|
assert200("AddBlock(" + reqId1 + ")", resp1);
|
||||||
|
|
||||||
String serverLastGlobalHash = extractPayloadString(resp1, "serverLastGlobalHash");
|
String serverLastGlobalHash0 = extractPayloadString(resp1, "serverLastGlobalHash");
|
||||||
assertNotNull(serverLastGlobalHash, "HEADER: payload.serverLastGlobalHash must not be null");
|
assertNotNull(serverLastGlobalHash0, "HEADER: payload.serverLastGlobalHash must not be null");
|
||||||
assertFalse(serverLastGlobalHash.isBlank(), "HEADER: payload.serverLastGlobalHash must not be blank");
|
assertEquals(64, serverLastGlobalHash0.trim().length(), "HEADER: serverLastGlobalHash must be 64 hex chars");
|
||||||
assertEquals(64, serverLastGlobalHash.trim().length(), "HEADER: serverLastGlobalHash must be 64 hex chars");
|
|
||||||
|
|
||||||
ok("HEADER принят. serverLastGlobalHash=" + serverLastGlobalHash);
|
String localHash0 = bytesToHex64(header.hash32);
|
||||||
|
assertEquals(localHash0, serverLastGlobalHash0, "HEADER: serverLastGlobalHash должен совпасть с локальным hash");
|
||||||
|
|
||||||
// -------------------- ШАГ 2: TEXT (global=1) --------------------
|
// обновляем локальное состояние
|
||||||
stepTitle("ШАГ 2: AddBlock TEXT (global=1)");
|
headerHash32 = header.hash32;
|
||||||
|
globalLastNumber = 0;
|
||||||
|
globalLastHashHex = localHash0;
|
||||||
|
|
||||||
byte[] prevGlobal32 = hexToBytes32(serverLastGlobalHash);
|
lineLastNumber[0] = 0;
|
||||||
byte[] prevLine32 = prevGlobal32;
|
lineLastHashHex[0] = localHash0;
|
||||||
|
|
||||||
byte[] textFull = buildTextBlockFullBytes(
|
ok("HEADER принят. serverLastGlobalHash=" + serverLastGlobalHash0);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// Общая проверка: headerHash32 уже есть
|
||||||
|
// =========================================================
|
||||||
|
assertNotNull(headerHash32, "internal: headerHash32 must be set after header step");
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// ШАГ 2: TEXT#1 (global=1, line=1, lineNum=1)
|
||||||
|
// prevLineHash для первого блока линии = hash(нулевого блока)
|
||||||
|
// =========================================================
|
||||||
|
stepTitle("ШАГ 2: AddBlock TEXT#1 (global=1, line=1, lineNum=1)");
|
||||||
|
|
||||||
|
int text1LineNum = nextLineNum(lineLastNumber, LINE_TEXT);
|
||||||
|
byte[] prevLineHashText1 = prevLineHash32(lineLastNumber, lineLastHashHex, headerHash32, LINE_TEXT);
|
||||||
|
|
||||||
|
BuiltBlock text1 = buildTextBlock(
|
||||||
1,
|
1,
|
||||||
(short) 0,
|
LINE_TEXT,
|
||||||
1,
|
text1LineNum,
|
||||||
prevGlobal32,
|
hexToBytes32(globalLastHashHex), // prevGlobalHash32
|
||||||
prevLine32,
|
prevLineHashText1, // prevLineHash32
|
||||||
"Hello from IT_03 test"
|
"Hello #1 from IT_03 test"
|
||||||
);
|
);
|
||||||
|
|
||||||
String reqId2 = "it03-add-text";
|
String reqId2 = "it03-add-text-1";
|
||||||
String reqJson2 = buildAddBlockJson(reqId2, TestConfig.TEST_BCH_NAME(), 1, serverLastGlobalHash, base64(textFull));
|
String reqJson2 = buildAddBlockJson(reqId2, TestConfig.BCH_NAME(), 1, globalLastHashHex, base64(text1.fullBytes));
|
||||||
|
|
||||||
send("AddBlock#TEXT", reqJson2);
|
send("AddBlock(" + reqId2 + ")", reqJson2);
|
||||||
String resp2 = client.request(reqId2, reqJson2, Duration.ofSeconds(8));
|
String resp2 = client.request(reqId2, reqJson2, Duration.ofSeconds(8));
|
||||||
recv("AddBlock#TEXT", resp2);
|
recv("AddBlock(" + reqId2 + ")", resp2);
|
||||||
|
|
||||||
assert200("AddBlock#TEXT", resp2);
|
assert200("AddBlock(" + reqId2 + ")", resp2);
|
||||||
|
|
||||||
ok("ТЕСТ ПРОЙДЕН: AddBlock HEADER(0) + TEXT(1) успешно добавлены");
|
String serverLastGlobalHash1 = extractPayloadString(resp2, "serverLastGlobalHash");
|
||||||
|
assertNotNull(serverLastGlobalHash1, "TEXT#1: payload.serverLastGlobalHash must not be null");
|
||||||
|
assertEquals(64, serverLastGlobalHash1.trim().length(), "TEXT#1: serverLastGlobalHash must be 64 hex chars");
|
||||||
|
|
||||||
|
String localHash1 = bytesToHex64(text1.hash32);
|
||||||
|
assertEquals(localHash1, serverLastGlobalHash1, "TEXT#1: serverLastGlobalHash должен совпасть с локальным hash");
|
||||||
|
|
||||||
|
// обновляем состояние
|
||||||
|
globalLastNumber = 1;
|
||||||
|
globalLastHashHex = localHash1;
|
||||||
|
lineLastNumber[LINE_TEXT] = text1LineNum;
|
||||||
|
lineLastHashHex[LINE_TEXT] = localHash1;
|
||||||
|
|
||||||
|
ok("TEXT#1 принят. hash1=" + serverLastGlobalHash1);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// ШАГ 3: TEXT#2 (global=2, line=1, lineNum=2)
|
||||||
|
// prevLineHash для второго блока линии = hash(TEXT#1)
|
||||||
|
// =========================================================
|
||||||
|
stepTitle("ШАГ 3: AddBlock TEXT#2 (global=2, line=1, lineNum=2)");
|
||||||
|
|
||||||
|
int text2LineNum = nextLineNum(lineLastNumber, LINE_TEXT);
|
||||||
|
byte[] prevLineHashText2 = prevLineHash32(lineLastNumber, lineLastHashHex, headerHash32, LINE_TEXT);
|
||||||
|
|
||||||
|
BuiltBlock text2 = buildTextBlock(
|
||||||
|
2,
|
||||||
|
LINE_TEXT,
|
||||||
|
text2LineNum,
|
||||||
|
hexToBytes32(globalLastHashHex),
|
||||||
|
prevLineHashText2,
|
||||||
|
"Hello #2 from IT_03 test"
|
||||||
|
);
|
||||||
|
|
||||||
|
String reqId3 = "it03-add-text-2";
|
||||||
|
String reqJson3 = buildAddBlockJson(reqId3, TestConfig.BCH_NAME(), 2, globalLastHashHex, base64(text2.fullBytes));
|
||||||
|
|
||||||
|
send("AddBlock(" + reqId3 + ")", reqJson3);
|
||||||
|
String resp3 = client.request(reqId3, reqJson3, Duration.ofSeconds(8));
|
||||||
|
recv("AddBlock(" + reqId3 + ")", resp3);
|
||||||
|
|
||||||
|
assert200("AddBlock(" + reqId3 + ")", resp3);
|
||||||
|
|
||||||
|
String serverLastGlobalHash2 = extractPayloadString(resp3, "serverLastGlobalHash");
|
||||||
|
assertNotNull(serverLastGlobalHash2, "TEXT#2: payload.serverLastGlobalHash must not be null");
|
||||||
|
assertEquals(64, serverLastGlobalHash2.trim().length(), "TEXT#2: serverLastGlobalHash must be 64 hex chars");
|
||||||
|
|
||||||
|
String localHash2 = bytesToHex64(text2.hash32);
|
||||||
|
assertEquals(localHash2, serverLastGlobalHash2, "TEXT#2: serverLastGlobalHash должен совпасть с локальным hash");
|
||||||
|
|
||||||
|
// обновляем состояние
|
||||||
|
globalLastNumber = 2;
|
||||||
|
globalLastHashHex = localHash2;
|
||||||
|
lineLastNumber[LINE_TEXT] = text2LineNum;
|
||||||
|
lineLastHashHex[LINE_TEXT] = localHash2;
|
||||||
|
|
||||||
|
ok("TEXT#2 принят. hash2=" + serverLastGlobalHash2);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// ШАГ 4: TEXT#3 (global=3, line=1, lineNum=3)
|
||||||
|
// prevLineHash = hash(TEXT#2)
|
||||||
|
// =========================================================
|
||||||
|
stepTitle("ШАГ 4: AddBlock TEXT#3 (global=3, line=1, lineNum=3)");
|
||||||
|
|
||||||
|
int text3LineNum = nextLineNum(lineLastNumber, LINE_TEXT);
|
||||||
|
byte[] prevLineHashText3 = prevLineHash32(lineLastNumber, lineLastHashHex, headerHash32, LINE_TEXT);
|
||||||
|
|
||||||
|
BuiltBlock text3 = buildTextBlock(
|
||||||
|
3,
|
||||||
|
LINE_TEXT,
|
||||||
|
text3LineNum,
|
||||||
|
hexToBytes32(globalLastHashHex),
|
||||||
|
prevLineHashText3,
|
||||||
|
"Hello #3 from IT_03 test"
|
||||||
|
);
|
||||||
|
|
||||||
|
String reqId4 = "it03-add-text-3";
|
||||||
|
String reqJson4 = buildAddBlockJson(reqId4, TestConfig.BCH_NAME(), 3, globalLastHashHex, base64(text3.fullBytes));
|
||||||
|
|
||||||
|
send("AddBlock(" + reqId4 + ")", reqJson4);
|
||||||
|
String resp4 = client.request(reqId4, reqJson4, Duration.ofSeconds(8));
|
||||||
|
recv("AddBlock(" + reqId4 + ")", resp4);
|
||||||
|
|
||||||
|
assert200("AddBlock(" + reqId4 + ")", resp4);
|
||||||
|
|
||||||
|
String serverLastGlobalHash3 = extractPayloadString(resp4, "serverLastGlobalHash");
|
||||||
|
assertNotNull(serverLastGlobalHash3, "TEXT#3: payload.serverLastGlobalHash must not be null");
|
||||||
|
assertEquals(64, serverLastGlobalHash3.trim().length(), "TEXT#3: serverLastGlobalHash must be 64 hex chars");
|
||||||
|
|
||||||
|
String localHash3 = bytesToHex64(text3.hash32);
|
||||||
|
assertEquals(localHash3, serverLastGlobalHash3, "TEXT#3: serverLastGlobalHash должен совпасть с локальным hash");
|
||||||
|
|
||||||
|
// обновляем состояние
|
||||||
|
globalLastNumber = 3;
|
||||||
|
globalLastHashHex = localHash3;
|
||||||
|
lineLastNumber[LINE_TEXT] = text3LineNum;
|
||||||
|
lineLastHashHex[LINE_TEXT] = localHash3;
|
||||||
|
|
||||||
|
ok("TEXT#3 принят. hash3=" + serverLastGlobalHash3);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// ШАГ 5: REACT#1 (global=4, line=2, lineNum=1) -> на TEXT#1
|
||||||
|
// prevLineHash для первого блока line2 = hash(нулевого блока)
|
||||||
|
// =========================================================
|
||||||
|
stepTitle("ШАГ 5: AddBlock REACT#1 (global=4, line=2, lineNum=1) -> to TEXT#1");
|
||||||
|
|
||||||
|
int react1LineNum = nextLineNum(lineLastNumber, LINE_REACT);
|
||||||
|
byte[] prevLineHashReact1 = prevLineHash32(lineLastNumber, lineLastHashHex, headerHash32, LINE_REACT);
|
||||||
|
|
||||||
|
// ссылка на TEXT#1 (global=1, hash=text1)
|
||||||
|
String text1HashHex = lineHashAtOrThrow(text1, "text1.hash32");
|
||||||
|
|
||||||
|
BuiltBlock react1 = buildReactionBlock(
|
||||||
|
4,
|
||||||
|
LINE_REACT,
|
||||||
|
react1LineNum,
|
||||||
|
hexToBytes32(globalLastHashHex),
|
||||||
|
prevLineHashReact1,
|
||||||
|
1, // reactionCode (пример: 1 = like)
|
||||||
|
TestConfig.BCH_NAME(),
|
||||||
|
1, // toBlockGlobalNumber = 1 (TEXT#1)
|
||||||
|
text1.hash32 // toBlockHash32 = hash(TEXT#1)
|
||||||
|
);
|
||||||
|
|
||||||
|
String reqId5 = "it03-add-react-1";
|
||||||
|
String reqJson5 = buildAddBlockJson(reqId5, TestConfig.BCH_NAME(), 4, globalLastHashHex, base64(react1.fullBytes));
|
||||||
|
|
||||||
|
send("AddBlock(" + reqId5 + ")", reqJson5);
|
||||||
|
String resp5 = client.request(reqId5, reqJson5, Duration.ofSeconds(8));
|
||||||
|
recv("AddBlock(" + reqId5 + ")", resp5);
|
||||||
|
|
||||||
|
assert200("AddBlock(" + reqId5 + ")", resp5);
|
||||||
|
|
||||||
|
String serverLastGlobalHash4 = extractPayloadString(resp5, "serverLastGlobalHash");
|
||||||
|
assertNotNull(serverLastGlobalHash4, "REACT#1: payload.serverLastGlobalHash must not be null");
|
||||||
|
assertEquals(64, serverLastGlobalHash4.trim().length(), "REACT#1: serverLastGlobalHash must be 64 hex chars");
|
||||||
|
|
||||||
|
String localHash4 = bytesToHex64(react1.hash32);
|
||||||
|
assertEquals(localHash4, serverLastGlobalHash4, "REACT#1: serverLastGlobalHash должен совпасть с локальным hash");
|
||||||
|
|
||||||
|
// обновляем состояние
|
||||||
|
globalLastNumber = 4;
|
||||||
|
globalLastHashHex = localHash4;
|
||||||
|
lineLastNumber[LINE_REACT] = react1LineNum;
|
||||||
|
lineLastHashHex[LINE_REACT] = localHash4;
|
||||||
|
|
||||||
|
ok("REACT#1 принят. hash4=" + serverLastGlobalHash4);
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// Итоговый контроль массивов линий
|
||||||
|
// =========================================================
|
||||||
|
ok("ИТОГ по линиям:");
|
||||||
|
ok(" line0: lastNum=" + lineLastNumber[0] + ", lastHash=" + lineLastHashHex[0]);
|
||||||
|
ok(" line1: lastNum=" + lineLastNumber[1] + ", lastHash=" + lineLastHashHex[1]);
|
||||||
|
ok(" line2: lastNum=" + lineLastNumber[2] + ", lastHash=" + lineLastHashHex[2]);
|
||||||
|
|
||||||
|
ok("ТЕСТ ПРОЙДЕН: HEADER + 3xTEXT(line1) + 1xREACT(line2) успешно добавлены и согласованы по globalHash/lineHash");
|
||||||
|
|
||||||
} catch (AssertionError | RuntimeException e) {
|
} catch (AssertionError | RuntimeException e) {
|
||||||
boom("ТЕСТ УПАЛ: AddBlockIT. Причина: " + e.getMessage());
|
boom("ТЕСТ УПАЛ: AddBlockIT. Причина: " + e.getMessage());
|
||||||
@ -193,23 +406,76 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// LINE HELPERS
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
|
/** Следующий lineNum: если в линии было N блоков, новый будет N+1 (для line>0). Для line0 в этом тесте только 0. */
|
||||||
|
private static int nextLineNum(int[] lineLastNumber, short lineIndex) {
|
||||||
|
if (lineIndex < 0 || lineIndex > 7) throw new IllegalArgumentException("lineIndex must be 0..7");
|
||||||
|
if (lineIndex == 0) return 0; // у нас header фиксированно line0/num0
|
||||||
|
return lineLastNumber[lineIndex] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prevLineHash32 по твоему правилу:
|
||||||
|
* - для первого блока линии (lineLastNumber[line]==0): prevLineHash = hash(нулевого блока)
|
||||||
|
* - иначе: prevLineHash = hash последнего блока этой линии
|
||||||
|
*
|
||||||
|
* Важно: для line0 здесь не используем (header имеет prevLine=ZERO32).
|
||||||
|
*/
|
||||||
|
private static byte[] prevLineHash32(int[] lineLastNumber, String[] lineLastHashHex, byte[] headerHash32, short lineIndex) {
|
||||||
|
if (lineIndex < 0 || lineIndex > 7) throw new IllegalArgumentException("lineIndex must be 0..7");
|
||||||
|
if (lineIndex == 0) return ZERO32;
|
||||||
|
|
||||||
|
if (lineLastNumber[lineIndex] == 0) {
|
||||||
|
// первый блок линии -> от нулевого блока
|
||||||
|
if (headerHash32 == null || headerHash32.length != 32) {
|
||||||
|
throw new IllegalStateException("headerHash32 is not set but required for first block of line " + lineIndex);
|
||||||
|
}
|
||||||
|
return headerHash32;
|
||||||
|
}
|
||||||
|
|
||||||
|
String lastHex = lineLastHashHex[lineIndex];
|
||||||
|
if (lastHex == null || lastHex.isBlank()) {
|
||||||
|
throw new IllegalStateException("lineLastHashHex[" + lineIndex + "] is blank but lineLastNumber>0");
|
||||||
|
}
|
||||||
|
return hexToBytes32(lastHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String lineHashAtOrThrow(BuiltBlock b, String name) {
|
||||||
|
if (b == null || b.hash32 == null || b.hash32.length != 32) throw new IllegalArgumentException(name + " must be 32 bytes");
|
||||||
|
return bytesToHex64(b.hash32);
|
||||||
|
}
|
||||||
|
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
// BUILD BLOCKS
|
// BUILD BLOCKS
|
||||||
// =================================================================================
|
// =================================================================================
|
||||||
|
|
||||||
private static byte[] buildHeaderBlockFullBytes(int globalNumber,
|
/** Небольшой холдер, чтобы тест мог использовать hash32 как prevGlobal/prevLine и как toBlockHash. */
|
||||||
|
private static final class BuiltBlock {
|
||||||
|
final byte[] fullBytes;
|
||||||
|
final byte[] hash32;
|
||||||
|
|
||||||
|
BuiltBlock(byte[] fullBytes, byte[] hash32) {
|
||||||
|
this.fullBytes = fullBytes;
|
||||||
|
this.hash32 = hash32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BuiltBlock buildHeaderBlock(int globalNumber,
|
||||||
short lineIndex,
|
short lineIndex,
|
||||||
int lineBlockNumber,
|
int lineBlockNumber,
|
||||||
byte[] prevGlobalHash32,
|
byte[] prevGlobalHash32,
|
||||||
byte[] prevLineHash32) {
|
byte[] prevLineHash32) {
|
||||||
|
|
||||||
HeaderBody body = new HeaderBody(TestConfig.TEST_LOGIN());
|
HeaderBody body = new HeaderBody(TestConfig.LOGIN());
|
||||||
byte[] bodyBytes = body.toBytes();
|
byte[] bodyBytes = body.toBytes();
|
||||||
|
|
||||||
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] buildTextBlockFullBytes(int globalNumber,
|
private static BuiltBlock buildTextBlock(int globalNumber,
|
||||||
short lineIndex,
|
short lineIndex,
|
||||||
int lineBlockNumber,
|
int lineBlockNumber,
|
||||||
byte[] prevGlobalHash32,
|
byte[] prevGlobalHash32,
|
||||||
@ -222,7 +488,29 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] buildSignedBlockFullBytes(int globalNumber,
|
private static BuiltBlock buildReactionBlock(int globalNumber,
|
||||||
|
short lineIndex,
|
||||||
|
int lineBlockNumber,
|
||||||
|
byte[] prevGlobalHash32,
|
||||||
|
byte[] prevLineHash32,
|
||||||
|
int reactionCode,
|
||||||
|
String toBlockchainName,
|
||||||
|
int toBlockGlobalNumber,
|
||||||
|
byte[] toBlockHash32) {
|
||||||
|
|
||||||
|
ReactionBody body = new ReactionBody(
|
||||||
|
reactionCode,
|
||||||
|
toBlockchainName,
|
||||||
|
toBlockGlobalNumber,
|
||||||
|
toBlockHash32 // [32] сырые 32 байта, как ты утвердил
|
||||||
|
);
|
||||||
|
|
||||||
|
byte[] bodyBytes = body.toBytes();
|
||||||
|
|
||||||
|
return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BuiltBlock buildSignedBlockFullBytes(int globalNumber,
|
||||||
short lineIndex,
|
short lineIndex,
|
||||||
int lineBlockNumber,
|
int lineBlockNumber,
|
||||||
byte[] bodyBytes,
|
byte[] bodyBytes,
|
||||||
@ -243,8 +531,11 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
.put(bodyBytes)
|
.put(bodyBytes)
|
||||||
.array();
|
.array();
|
||||||
|
|
||||||
|
// Ключевой момент: preimage должен совпасть с серверным правилом.
|
||||||
|
// Сервер НЕ получает prevLineHash по сети — он берёт его из своего состояния линии.
|
||||||
|
// Поэтому в тесте мы обязаны передавать сюда ровно тот же prevLineHash32 (см. prevLineHash32()).
|
||||||
byte[] preimage = BchCryptoVerifier.buildPreimage(
|
byte[] preimage = BchCryptoVerifier.buildPreimage(
|
||||||
TestConfig.TEST_LOGIN(),
|
TestConfig.LOGIN(),
|
||||||
prevGlobalHash32,
|
prevGlobalHash32,
|
||||||
prevLineHash32,
|
prevLineHash32,
|
||||||
rawBytes
|
rawBytes
|
||||||
@ -252,10 +543,9 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
|
|
||||||
byte[] hash32 = BchCryptoVerifier.sha256(preimage);
|
byte[] hash32 = BchCryptoVerifier.sha256(preimage);
|
||||||
|
|
||||||
// Подпись делаем ключом логина
|
|
||||||
byte[] signature64 = Ed25519Util.sign(hash32, TestConfig.LOGIN_PRIV_KEY());
|
byte[] signature64 = Ed25519Util.sign(hash32, TestConfig.LOGIN_PRIV_KEY());
|
||||||
|
|
||||||
return new BchBlockEntry(
|
byte[] full = new BchBlockEntry(
|
||||||
globalNumber,
|
globalNumber,
|
||||||
ts,
|
ts,
|
||||||
lineIndex,
|
lineIndex,
|
||||||
@ -264,8 +554,14 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
signature64,
|
signature64,
|
||||||
hash32
|
hash32
|
||||||
).toBytes();
|
).toBytes();
|
||||||
|
|
||||||
|
return new BuiltBlock(full, hash32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// JSON HELPERS
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
private static String buildAddBlockJson(String requestId,
|
private static String buildAddBlockJson(String requestId,
|
||||||
String blockchainName,
|
String blockchainName,
|
||||||
int globalNumber,
|
int globalNumber,
|
||||||
@ -301,6 +597,10 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
return Base64.getEncoder().encodeToString(bytes);
|
return Base64.getEncoder().encodeToString(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =================================================================================
|
||||||
|
// HEX HELPERS
|
||||||
|
// =================================================================================
|
||||||
|
|
||||||
private static byte[] hexToBytes32(String hex) {
|
private static byte[] hexToBytes32(String hex) {
|
||||||
if (hex == null) throw new IllegalArgumentException("hex is null");
|
if (hex == null) throw new IllegalArgumentException("hex is null");
|
||||||
String s = hex.trim();
|
String s = hex.trim();
|
||||||
@ -314,4 +614,16 @@ public class IT_03_AddBlock_NoAuth {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String bytesToHex64(byte[] b32) {
|
||||||
|
if (b32 == null || b32.length != 32) throw new IllegalArgumentException("b32 must be 32 bytes");
|
||||||
|
char[] out = new char[64];
|
||||||
|
final char[] HEX = "0123456789abcdef".toCharArray();
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
int v = b32[i] & 0xFF;
|
||||||
|
out[i * 2] = HEX[v >>> 4];
|
||||||
|
out[i * 2 + 1] = HEX[v & 0x0F];
|
||||||
|
}
|
||||||
|
return new String(out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user