From ae3838ccf289ea40356994f799fe167d45b896fa962cf3ec15b183fb4a9f0f87 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Mon, 29 Dec 2025 14:14:53 +0300 Subject: [PATCH] =?UTF-8?q?29=2012=2025=20=D0=A3=D1=80=D0=B0=20=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=B8=D0=B8=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D1=8E=D1=82=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=88=D0=BB=D0=B8!=20=D0=A2=D1=80=D0=B5=D1=82=D0=B8?= =?UTF-8?q?=D0=B9=20=D1=82=D0=B5=D1=81=D1=82=20=D0=B2=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B8=D1=82=D1=8C=D1=81=D1=8F?= =?UTF-8?q?!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/test/it/IT_CleanAllDate.java | 3 +- src/test/java/test/it/utils/TestConfig.java | 9 +++ src/test/java/test/it/utils/TestLog.java | 59 ++++++++++--------- src/test/java/test/it/ws/AddBlockFlow.java | 49 ++++++++++++++- .../test/it/ws/IT_03_AddBlock_NoAuth.java | 45 ++++++++------ src/test/java/test/it/ws/WsJsonOneShot.java | 36 +++++++++-- 6 files changed, 146 insertions(+), 55 deletions(-) diff --git a/src/test/java/test/it/IT_CleanAllDate.java b/src/test/java/test/it/IT_CleanAllDate.java index 5364ffe..fd8c7e0 100644 --- a/src/test/java/test/it/IT_CleanAllDate.java +++ b/src/test/java/test/it/IT_CleanAllDate.java @@ -1,5 +1,6 @@ package test.it; +import test.it.utils.TestConfig; import test.it.utils.TestLog; import java.io.IOException; @@ -24,7 +25,7 @@ public class IT_CleanAllDate { cleanupDataDir(DATA_DIR); } catch (Throwable t) { TestLog.boom("Не смог очистить data/. Причина: " + t.getMessage()); - if (TestLog.VERBOSE) t.printStackTrace(System.out); + if (TestConfig.DEBUG()) t.printStackTrace(System.out); System.exit(1); } diff --git a/src/test/java/test/it/utils/TestConfig.java b/src/test/java/test/it/utils/TestConfig.java index 8283f9d..8110e23 100644 --- a/src/test/java/test/it/utils/TestConfig.java +++ b/src/test/java/test/it/utils/TestConfig.java @@ -14,6 +14,10 @@ import java.util.Base64; * ВАЖНО: * - ключи/имя блокчейна вычисляются из login (через ItRunContext). * - тесты можно запускать по отдельности, ItRunContext сам инициализируется при первом обращении. + * + * ЛОГИ: + * - детальный вывод включается флагом: + * -Dit.debug=true */ public final class TestConfig { @@ -34,6 +38,11 @@ public final class TestConfig { // Любая строка клиента (для логов) public static final String TEST_CLIENT_INFO = "it-tests"; + /** DEBUG-режим: подробные логи отправки/получения/ожиданий (по умолчанию false). */ + public static boolean DEBUG() { + return Boolean.parseBoolean(System.getProperty("it.debug", "true")); + } + /** login для прогона (по умолчанию DEFAULT_LOGIN, можно переопределить -Dit.login=...). */ public static String LOGIN() { return System.getProperty("it.login", DEFAULT_LOGIN); diff --git a/src/test/java/test/it/utils/TestLog.java b/src/test/java/test/it/utils/TestLog.java index e1899aa..8f417b9 100644 --- a/src/test/java/test/it/utils/TestLog.java +++ b/src/test/java/test/it/utils/TestLog.java @@ -3,22 +3,22 @@ package test.it.utils; /** * TestLog — единое место для: * - ANSI цветов - * - стандартных красивых сообщений (title/ok/boom/line/step/send/recv) + * - стандартных красивых сообщений (title/line/step/send/recv) * - * Включение/выключение подробных логов: - * -Dit.verbose=false - * - * По умолчанию verbose=true (удобно для ручного прогона). + * РЕЖИМЫ: + * - it.debug=false (по умолчанию): + * печатаем ТОЛЬКО итог: PASS/FAIL по каждому тесту + * - it.debug=true: + * печатаем всё: ожидания, отправка/ответ (JSON), промежуточные проверки */ public final class TestLog { private TestLog() {} // ============================ - // VERBOSE + // DEBUG SWITCH // ============================ - // включается так: ./gradlew test -Dit.verbose=true - public static final boolean VERBOSE = Boolean.parseBoolean(System.getProperty("it.verbose", "true")); + public static final boolean DEBUG = TestConfig.DEBUG(); // ============================ // ANSI COLORS @@ -34,54 +34,55 @@ public final class TestLog { // BASIC OUTPUT // ============================ + /** Дебаг-инфо (печатается только при DEBUG=true). */ public static void info(String s) { - if (VERBOSE) System.out.println(s); + if (DEBUG) System.out.println(s); } public static void line() { - if (!VERBOSE) return; + if (!DEBUG) return; System.out.println(C + "------------------------------------------------------------" + R); } - /** Короткое заглавие. */ + /** Короткое заглавие (только DEBUG). */ public static void title(String s) { - if (!VERBOSE) return; + if (!DEBUG) return; System.out.println(C + "\n============================================================" + R); System.out.println(C + s + R); System.out.println(C + "============================================================\n" + R); } - /** - * Длинное заглавие (под многострочный текст). - * - * Пример: - * TestLog.titleBlock(""" - * ТЕСТ: ... - * Ожидание: ... - * """); - */ + /** Длинное заглавие (только DEBUG). */ public static void titleBlock(String multiLineText) { - if (!VERBOSE) return; + if (!DEBUG) return; System.out.println(C + "\n============================================================" + R); System.out.println(C + multiLineText + R); System.out.println(C + "============================================================\n" + R); } + /** Заголовок шага (только DEBUG). */ public static void stepTitle(String s) { - if (!VERBOSE) return; + if (!DEBUG) return; System.out.println(C + "\n-------------------- " + s + " --------------------" + R); } + /** Промежуточное ОК (только DEBUG). */ public static void ok(String s) { - if (!VERBOSE) return; + if (!DEBUG) return; + System.out.println(G + "✅ " + s + R); + } + + /** Итоговый PASS (печатается ВСЕГДА). */ + public static void pass(String s) { System.out.println(G + "✅ " + s + R); } public static void warn(String s) { - if (!VERBOSE) return; + if (!DEBUG) return; System.out.println(Y + "⚠️ " + s + R); } + /** FAIL (печатается ВСЕГДА). */ public static void boom(String s) { System.out.println(RED + "****************************************************************" + R); System.out.println(RED + "❌ " + s + R); @@ -89,14 +90,14 @@ public final class TestLog { } public static void send(String op, String json) { - if (!VERBOSE) return; + if (!DEBUG) return; System.out.println("📤 [" + op + "] Request JSON:"); System.out.println(json); line(); } public static void recv(String op, String json) { - if (!VERBOSE) return; + if (!DEBUG) return; System.out.println("📥 [" + op + "] Response JSON:"); System.out.println(json); line(); @@ -117,11 +118,11 @@ public final class TestLog { public static int runOne(String testName, Runnable body) { try { body.run(); - ok(testName + ": OK"); + pass(testName + ": OK"); return 0; } catch (Throwable t) { boom(testName + ": FAIL. Причина: " + t.getMessage()); - if (VERBOSE) t.printStackTrace(System.out); + if (DEBUG) t.printStackTrace(System.out); return 1; } } diff --git a/src/test/java/test/it/ws/AddBlockFlow.java b/src/test/java/test/it/ws/AddBlockFlow.java index b94fe73..a15f9fd 100644 --- a/src/test/java/test/it/ws/AddBlockFlow.java +++ b/src/test/java/test/it/ws/AddBlockFlow.java @@ -7,6 +7,7 @@ import blockchain.body.ReactionBody; import blockchain.body.TextBody; import test.it.utils.JsonParsers; import test.it.utils.TestConfig; +import test.it.utils.TestLog; import utils.crypto.Ed25519Util; import java.nio.ByteBuffer; @@ -31,6 +32,13 @@ import static org.junit.jupiter.api.Assertions.*; * * Важно: * - Этот класс НЕ занимается красивыми логами. Только логика + проверки. + * + * ДОБАВЛЕНО: + * - При it.debug=true печатаем: + * * какой блок шлём (global/line/lineNum) + * * локальный hash + * * serverLastGlobalHash + * * итоги проверок */ public final class AddBlockFlow { @@ -72,7 +80,7 @@ public final class AddBlockFlow { ); String req = buildAddBlockJson(TestConfig.BCH_NAME(), 0, ZERO64, base64(header.fullBytes)); - String resp = WsJsonOneShot.request(req, timeout); + String resp = WsJsonOneShot.request("AddBlock#HEADER", req, timeout); assert200("AddBlock(HEADER)", resp); @@ -81,6 +89,12 @@ public final class AddBlockFlow { assertEquals(64, serverLastGlobalHash0.trim().length(), "HEADER: serverLastGlobalHash must be 64 hex chars"); String localHash0 = bytesToHex64(header.hash32); + + if (TestConfig.DEBUG()) { + TestLog.ok("HEADER: локальный hash=" + localHash0); + TestLog.ok("HEADER: serverLastGlobalHash=" + serverLastGlobalHash0); + } + assertEquals(localHash0, serverLastGlobalHash0, "HEADER: serverLastGlobalHash должен совпасть с локальным hash"); // обновляем локальное состояние @@ -90,6 +104,10 @@ public final class AddBlockFlow { lineLastNumber[LINE_HEADER] = 0; lineLastHashHex[LINE_HEADER] = localHash0; + + if (TestConfig.DEBUG()) { + TestLog.ok("HEADER: проверка OK, состояние обновлено (globalLastNumber=0)"); + } } /** Шлём следующий TEXT блок в line=1. */ @@ -110,7 +128,8 @@ public final class AddBlockFlow { ); String req = buildAddBlockJson(TestConfig.BCH_NAME(), nextGlobal, globalLastHashHex, base64(b.fullBytes)); - String resp = WsJsonOneShot.request(req, timeout); + String op = "AddBlock#TEXT (global=" + nextGlobal + ", line=1, lineNum=" + lineNum + ")"; + String resp = WsJsonOneShot.request(op, req, timeout); assert200("AddBlock(TEXT)", resp); @@ -119,6 +138,12 @@ public final class AddBlockFlow { assertEquals(64, serverLastGlobalHash.trim().length(), "TEXT: serverLastGlobalHash must be 64 hex chars"); String localHash = bytesToHex64(b.hash32); + + if (TestConfig.DEBUG()) { + TestLog.ok("TEXT: локальный hash=" + localHash); + TestLog.ok("TEXT: serverLastGlobalHash=" + serverLastGlobalHash); + } + assertEquals(localHash, serverLastGlobalHash, "TEXT: serverLastGlobalHash должен совпасть с локальным hash"); // обновляем состояние @@ -127,6 +152,10 @@ public final class AddBlockFlow { lineLastNumber[LINE_TEXT] = lineNum; lineLastHashHex[LINE_TEXT] = localHash; + if (TestConfig.DEBUG()) { + TestLog.ok("TEXT: проверка OK, состояние обновлено (globalLastNumber=" + globalLastNumber + ")"); + } + return b; } @@ -157,7 +186,8 @@ public final class AddBlockFlow { ); String req = buildAddBlockJson(TestConfig.BCH_NAME(), nextGlobal, globalLastHashHex, base64(b.fullBytes)); - String resp = WsJsonOneShot.request(req, timeout); + String op = "AddBlock#REACT (global=" + nextGlobal + ", line=2, lineNum=" + lineNum + ")"; + String resp = WsJsonOneShot.request(op, req, timeout); assert200("AddBlock(REACT)", resp); @@ -166,6 +196,12 @@ public final class AddBlockFlow { assertEquals(64, serverLastGlobalHash.trim().length(), "REACT: serverLastGlobalHash must be 64 hex chars"); String localHash = bytesToHex64(b.hash32); + + if (TestConfig.DEBUG()) { + TestLog.ok("REACT: локальный hash=" + localHash); + TestLog.ok("REACT: serverLastGlobalHash=" + serverLastGlobalHash); + } + assertEquals(localHash, serverLastGlobalHash, "REACT: serverLastGlobalHash должен совпасть с локальным hash"); // обновляем состояние @@ -174,6 +210,10 @@ public final class AddBlockFlow { lineLastNumber[LINE_REACT] = lineNum; lineLastHashHex[LINE_REACT] = localHash; + if (TestConfig.DEBUG()) { + TestLog.ok("REACT: проверка OK, состояние обновлено (globalLastNumber=" + globalLastNumber + ")"); + } + return b; } @@ -355,6 +395,9 @@ public final class AddBlockFlow { private static void assert200(String op, String resp) { int st = 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) { diff --git a/src/test/java/test/it/ws/IT_03_AddBlock_NoAuth.java b/src/test/java/test/it/ws/IT_03_AddBlock_NoAuth.java index 262fa87..5b83d0d 100644 --- a/src/test/java/test/it/ws/IT_03_AddBlock_NoAuth.java +++ b/src/test/java/test/it/ws/IT_03_AddBlock_NoAuth.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import test.it.utils.ItRunContext; import test.it.utils.JsonBuilders; -import test.it.utils.JsonParsers; import test.it.utils.TestConfig; +import test.it.utils.TestLog; import java.time.Duration; @@ -38,6 +38,7 @@ public class IT_03_AddBlock_NoAuth { ItRunContext.initIfNeeded(); ensureUserExists(); new IT_03_AddBlock_NoAuth().addBlock_shouldAppendHeaderThenTextThenReaction(); + TestLog.pass("IT_03_AddBlock_NoAuth: OK"); } @BeforeAll @@ -47,23 +48,11 @@ public class IT_03_AddBlock_NoAuth { // ВАЖНО: // - requestId тут не важен, но пусть будет. // - отдельная авторизация не нужна, но пользователь должен существовать. - String reqJson = JsonBuilders.addUser("it03-adduser-beforeall"); -/** - String resp = WsJsonOneShot.request(reqJson, Duration.ofSeconds(5)); - int st = JsonParsers.status(resp); - - if (st == 200) { - // ok - return; - } - if (st == 409) { - String code = JsonParsers.errorCode(resp); - if ("USER_ALREADY_EXISTS".equals(code)) return; - fail("User precondition failed. status=409, code=" + code + ", resp=" + resp); - } - - fail("User precondition failed. status=" + st + ", resp=" + resp); - */ + // + // Если хочешь реально включить предусловие здесь — просто раскомментируй блок, + // но сейчас у тебя он закомментирован. +// String reqJson = JsonBuilders.addUser("it03-adduser-beforeall"); + // ничего не делаем — предусловие временно отключено } @Test @@ -73,24 +62,41 @@ public class IT_03_AddBlock_NoAuth { // таймаут на каждый one-shot запрос Duration t = Duration.ofSeconds(8); + if (TestConfig.DEBUG()) { + TestLog.titleBlock(""" + IT_03_AddBlock_NoAuth: сценарий AddBlock без отдельной авторизации + Используем: + login = %s + blockchainName = %s + debug=true: покажем отправку/ответ (JSON) и проверки hash + """.formatted(TestConfig.LOGIN(), TestConfig.BCH_NAME())); + } + // 1) состояние + сборка + отправка AddBlockFlow flow = new AddBlockFlow(); // ========================================================= // ШАГ 0: ВАЖНО — первым всегда HEADER global=0 // ========================================================= + if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 0: AddBlock HEADER (global=0, line=0, lineNum=0)"); flow.sendHeader0(t); // ========================================================= // ШАГ 1..3: TEXT (line=1) // ========================================================= + if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 1: AddBlock TEXT#1 (line=1)"); AddBlockFlow.BuiltBlock text1 = flow.sendNextText("Hello #1 from IT_03 test", t); + + if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 2: AddBlock TEXT#2 (line=1)"); flow.sendNextText("Hello #2 from IT_03 test", t); + + if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 3: AddBlock TEXT#3 (line=1)"); flow.sendNextText("Hello #3 from IT_03 test", t); // ========================================================= // ШАГ 4: REACT#1 (line=2) -> на TEXT#1 (global=1, hash=text1) // ========================================================= + if (TestConfig.DEBUG()) TestLog.stepTitle("ШАГ 4: AddBlock REACT#1 (line=2) -> на TEXT#1 (global=1)"); flow.sendNextReaction( 1, // reactionCode (пример: 1 = like) TestConfig.BCH_NAME(), // toBlockchainName @@ -105,5 +111,8 @@ public class IT_03_AddBlock_NoAuth { assertEquals(1, flow.lineLastNumber(AddBlockFlow.LINE_REACT), "В line=2 должен быть 1 блок"); assertNotNull(flow.globalLastHashHex()); assertEquals(64, flow.globalLastHashHex().length()); + + // Итог (в обычном режиме это будет единственная строка) + TestLog.pass("IT_03_AddBlock_NoAuth: OK"); } } \ No newline at end of file diff --git a/src/test/java/test/it/ws/WsJsonOneShot.java b/src/test/java/test/it/ws/WsJsonOneShot.java index 00cc767..5dda5fc 100644 --- a/src/test/java/test/it/ws/WsJsonOneShot.java +++ b/src/test/java/test/it/ws/WsJsonOneShot.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import test.it.utils.TestConfig; +import test.it.utils.TestLog; import test.it.utils.WsTestClient; import java.time.Duration; @@ -32,15 +33,33 @@ public final class WsJsonOneShot { private WsJsonOneShot() {} /** - * Отправить JSON строкой и вернуть JSON ответ строкой. - * Соединение создаётся и закрывается ВНУТРИ. + * Старый API (без имени операции) — оставляем для совместимости. */ public static String request(String json, Duration timeout) { + return request("WS", json, timeout); + } + + /** + * Отправить JSON строкой и вернуть JSON ответ строкой. + * Соединение создаётся и закрывается ВНУТРИ. + * + * Если включён it.debug=true — печатаем request/response. + */ + public static String request(String op, String json, Duration timeout) { String patched = forceRequestId(json, FIXED_REQUEST_ID); + if (TestConfig.DEBUG()) { + TestLog.send(op, prettyOrRaw(patched)); + } + try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) { - // requestId нам нужен только как ключ ожидания в WsTestClient - return client.request(FIXED_REQUEST_ID, patched, timeout); + String resp = client.request(FIXED_REQUEST_ID, patched, timeout); + + if (TestConfig.DEBUG()) { + TestLog.recv(op, prettyOrRaw(resp)); + } + + return resp; } } @@ -59,4 +78,13 @@ public final class WsJsonOneShot { return json; } } + + private static String prettyOrRaw(String json) { + try { + JsonNode n = MAPPER.readTree(json); + return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(n); + } catch (Exception ignore) { + return json; + } + } } \ No newline at end of file