diff --git a/build.gradle b/build.gradle index d301162..ff1043b 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,43 @@ dependencies { implementation project(':shine-server-net-server') // Хэндлеры для обработки сетевых запросов + +// +// +// // -------------------- ТЕСТЫ -------------------- +// testImplementation platform('org.junit:junit-bom:5.11.4') +// testImplementation 'org.junit.jupiter:junit-jupiter' +// testImplementation 'org.junit.platform:junit-platform-suite-api' +// testRuntimeOnly 'org.junit.platform:junit-platform-suite-engine' +// +// testRuntimeOnly "org.junit.platform:junit-platform-launcher" +// +// // Suite (чтобы запустить классы в заданном порядке) +// testImplementation "org.junit.platform:junit-platform-suite:1.10.2" +// +// // Нужно, чтобы TestExecutionListener (RussianSummaryListener) корректно подхватывался +// testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +//} + + // -------------------- ТЕСТЫ (JUnit 5) -------------------- + // Один BOM на всё семейство JUnit (Jupiter + Platform модули) + testImplementation platform("org.junit:junit-bom:5.10.2") + + // JUnit Jupiter (Test, BeforeAll, Assertions) + testImplementation "org.junit.jupiter:junit-jupiter" + + // JUnit Platform Suite (@Suite, @SelectClasses) + testImplementation "org.junit.platform:junit-platform-suite-api" + testRuntimeOnly "org.junit.platform:junit-platform-suite-engine" + + // Нужно для компиляции RussianSummaryListener (org.junit.platform.launcher.*) + // и чтобы JUnit мог подхватить listener при запуске + testImplementation "org.junit.platform:junit-platform-launcher" + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() } application { @@ -79,12 +116,3 @@ shadowJar { ) } } - -test { - useJUnitPlatform() - - testLogging { - events "passed", "skipped", "failed" - showStandardStreams = true - } -} diff --git a/src/test/concat_to_file.sh b/src/test/concat_to_file.sh new file mode 100755 index 0000000..901712c --- /dev/null +++ b/src/test/concat_to_file.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +OUTFILE="all_files.txt" + +# очищаем или создаём файл +: > "$OUTFILE" + +# собрать только *.java файлы и вывести их содержимое в файл +find . -type f -name "*.java" | sort | while read -r f; do + cat "$f" >> "$OUTFILE" + echo >> "$OUTFILE" # пустая строка-разделитель +done + +echo "Готово! Все .java файлы собраны в $OUTFILE" + diff --git a/src/test/java/test/it/IT_00_Suite.java b/src/test/java/test/it/IT_00_Suite.java new file mode 100644 index 0000000..9ccd233 --- /dev/null +++ b/src/test/java/test/it/IT_00_Suite.java @@ -0,0 +1,24 @@ +package test.it; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import test.it.ws.*; + +/** + * Сьют, который запускает IT тесты строго в заданном порядке. + * + * Запуск: + * ./gradlew test --tests test.it.IT_00_Suite + * + * Важно: + * - ItRunContext.initOnce() будет вызван слушателем RussianSummaryListener ДО тестов. + */ +@Suite +@SelectClasses({ + IT_01_AddUser.class, + IT_02_Sessions.class, + IT_03_AddBlock_NoAuth.class +}) +public class IT_00_Suite { + // пусто +} \ No newline at end of file diff --git a/src/test/java/test/it/utils/ItRunContext.java b/src/test/java/test/it/utils/ItRunContext.java new file mode 100644 index 0000000..953fb07 --- /dev/null +++ b/src/test/java/test/it/utils/ItRunContext.java @@ -0,0 +1,128 @@ +package test.it.utils; + +import utils.crypto.Ed25519Util; + +import java.security.SecureRandom; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +/** + * Глобальный контекст интеграционного прогона (один запуск Gradle test). + * + * ВАЖНО: + * - инициализируется ровно один раз на весь процесс JVM; + * - хранит случайный login + blockchainName + ключи, чтобы все IT тесты работали в одной "сессии данных". + */ +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 byte[] loginPrivKey; + private static byte[] loginPubKey; + + private static byte[] devicePrivKey; + private static byte[] devicePubKey; + + private ItRunContext() {} + + public static void initOnce() { + if (inited) return; + synchronized (LOCK) { + if (inited) return; + + // 1) Генерим читаемый суффикс по времени + случайности, чтобы не было коллизий. + String ts = LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss", Locale.ROOT)); + + String rnd = randomBase32(6).toLowerCase(Locale.ROOT); + + // 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) + loginPrivKey = Ed25519Util.generatePrivateKeyFromString(login); + loginPubKey = Ed25519Util.derivePublicKey(loginPrivKey); + + // deviceKey: приватный ключ = SHA-256(login + "#device") + String deviceSeedStr = login + "#device"; + devicePrivKey = Ed25519Util.generatePrivateKeyFromString(deviceSeedStr); + devicePubKey = Ed25519Util.derivePublicKey(devicePrivKey); + + inited = true; + + 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("login = " + login); + System.out.println("blockchainName = " + blockchainName); + System.out.println("loginPubKey = " + bytesToHexShort(loginPubKey)); + System.out.println("devicePubKey = " + bytesToHexShort(devicePubKey)); + System.out.println(TestColors.C + "------------------------------------------------------------\n" + TestColors.R); + } + } + + public static String login() { + ensureInit(); + return login; + } + + public static String blockchainName() { + ensureInit(); + return blockchainName; + } + + public static byte[] loginPrivKey() { + ensureInit(); + return loginPrivKey.clone(); + } + + public static byte[] loginPubKey() { + ensureInit(); + return loginPubKey.clone(); + } + + public static byte[] devicePrivKey() { + ensureInit(); + return devicePrivKey.clone(); + } + + public static byte[] devicePubKey() { + ensureInit(); + 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) { + if (b == null) return "null"; + StringBuilder sb = new StringBuilder(); + int n = Math.min(b.length, 10); + for (int i = 0; i < n; i++) sb.append(String.format("%02x", b[i])); + if (b.length > n) sb.append("..."); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/test/java/test/it/utils/JsonBuilders.java b/src/test/java/test/it/utils/JsonBuilders.java index 98f2c7a..0428514 100644 --- a/src/test/java/test/it/utils/JsonBuilders.java +++ b/src/test/java/test/it/utils/JsonBuilders.java @@ -23,10 +23,10 @@ public final class JsonBuilders { } """.formatted( requestId, - TestConfig.TEST_LOGIN, - TestConfig.TEST_BCH_NAME, - TestConfig.LOGIN_PUBKEY_B64, - TestConfig.DEVICE_PUBKEY_B64, + TestConfig.TEST_LOGIN(), + TestConfig.TEST_BCH_NAME(), + TestConfig.LOGIN_PUBKEY_B64(), + TestConfig.DEVICE_PUBKEY_B64(), TestConfig.TEST_BCH_LIMIT ); } @@ -38,7 +38,7 @@ public final class JsonBuilders { "requestId": "%s", "payload": { "login": "%s" } } - """.formatted(requestId, TestConfig.TEST_LOGIN); + """.formatted(requestId, TestConfig.TEST_LOGIN()); } public static String createAuthSession(String requestId, String authNonce, String storagePwd) { @@ -102,10 +102,15 @@ public final class JsonBuilders { """.formatted(requestId, sessionId, timeMs, signatureB64); } + /** + * Подпись для режима AUTH_IN_PROGRESS: + * preimage = "AUTHORIFICATED:" + timeMs + authNonce + * подписываем devicePrivKey (как в твоём протоколе). + */ public static String signAuthorificated(String authNonce, long timeMs) { 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, TestConfig.DEVICE_PRIV_KEY()); return Base64.getEncoder().encodeToString(sig); } } \ No newline at end of file diff --git a/src/test/java/test/it/utils/RussianSummaryListener.java b/src/test/java/test/it/utils/RussianSummaryListener.java new file mode 100644 index 0000000..c54f825 --- /dev/null +++ b/src/test/java/test/it/utils/RussianSummaryListener.java @@ -0,0 +1,90 @@ +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 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", " "); + } +} \ No newline at end of file diff --git a/src/test/java/test/it/utils/TestColors.java b/src/test/java/test/it/utils/TestColors.java new file mode 100644 index 0000000..c7520cb --- /dev/null +++ b/src/test/java/test/it/utils/TestColors.java @@ -0,0 +1,12 @@ +package test.it.utils; + +/** ANSI цвета для красивого вывода в терминал. */ +public final class TestColors { + private TestColors() {} + + public static final String R = "\u001B[0m"; + public static final String G = "\u001B[32m"; + public static final String Y = "\u001B[33m"; + public static final String RED = "\u001B[31m"; + public static final String C = "\u001B[36m"; +} \ No newline at end of file diff --git a/src/test/java/test/it/utils/TestConfig.java b/src/test/java/test/it/utils/TestConfig.java index 11cbdff..724a5e1 100644 --- a/src/test/java/test/it/utils/TestConfig.java +++ b/src/test/java/test/it/utils/TestConfig.java @@ -1,37 +1,63 @@ package test.it.utils; -import utils.crypto.Ed25519Util; - import java.util.Base64; +/** + * Конфиг для IT тестов. + * + * ВАЖНО: + * - login/blockchainName/ключи берём из ItRunContext (случайные на каждый прогон). + */ public final class TestConfig { - private TestConfig(){} + private TestConfig() {} + + // Твой WS URI public static final String WS_URI = "ws://localhost:7070/ws"; - public static final String TEST_LOGIN = "anya24"; - public static final String TEST_BCH_NAME = TEST_LOGIN + "001"; - public static final int TEST_BCH_LIMIT = 1_000_000; - public static final String TEST_CLIENT_INFO = "JavaTestClient/1.0"; - public static final byte[] LOGIN_PRIV_KEY; - public static final String LOGIN_PUBKEY_B64; + // Суффикс блокчейна по твоему правилу: login + 3 цифры + public static final String BCH_SUFFIX_3 = "001"; - public static final byte[] DEVICE_PRIV_KEY; - public static final String DEVICE_PUBKEY_B64; + // Лимит блокчейна для AddUser + public static final long TEST_BCH_LIMIT = 50_000_000L; - static { - LOGIN_PRIV_KEY = Ed25519Util.generatePrivateKeyFromString("test-ed25519-login-11" + TEST_LOGIN); - byte[] loginPub = Ed25519Util.derivePublicKey(LOGIN_PRIV_KEY); - LOGIN_PUBKEY_B64 = Ed25519Util.keyToBase64(loginPub); + // Любая строка клиента (для логов) + public static final String TEST_CLIENT_INFO = "it-tests"; - DEVICE_PRIV_KEY = Ed25519Util.generatePrivateKeyFromString("test-ed25519-device-" + TEST_LOGIN); - byte[] devicePub = Ed25519Util.derivePublicKey(DEVICE_PRIV_KEY); - DEVICE_PUBKEY_B64 = Ed25519Util.keyToBase64(devicePub); + public static String TEST_LOGIN() { + return ItRunContext.login(); } + public static String TEST_BCH_NAME() { + return ItRunContext.blockchainName(); + } + + public static byte[] LOGIN_PRIV_KEY() { + return ItRunContext.loginPrivKey(); + } + + public static byte[] LOGIN_PUB_KEY() { + return ItRunContext.loginPubKey(); + } + + public static byte[] DEVICE_PRIV_KEY() { + return ItRunContext.devicePrivKey(); + } + + 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 fakeStoragePwd() { - byte[] data = new byte[32]; - for (int i = 0; i < data.length; i++) data[i] = (byte) (i + 1); - return Base64.getEncoder().encodeToString(data); + return "pwd-" + System.nanoTime(); } -} \ No newline at end of file +} 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 6181b0c..bf76e12 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 @@ -1,4 +1,4 @@ -package test.it; +package test.it.ws; import blockchain.BchBlockEntry; import blockchain.BchCryptoVerifier; @@ -23,7 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; * IT_03_AddBlock_NoAuth * * Интеграционный тест добавления блоков в персональный блокчейн без отдельной авторизации, - * в формате твоих IT-тестов (ANSI, шаги, WsTestClient, JsonBuilders/JsonParsers). + * в формате твоих IT-тестов (ANSI, шаги, WsTestClient). * * Сценарий: * 1) AddBlock: HEADER (global=0, prevGlobalHash=ZERO64) -> ожидаем 200 @@ -31,11 +31,12 @@ import static org.junit.jupiter.api.Assertions.*; * 2) AddBlock: TEXT (global=1, prevGlobalHash=serverLastGlobalHash) -> ожидаем 200 * * Примечание: - * - lastLineHash пока равен lastGlobalHash (как ты говорил). + * - lastLineHash пока равен lastGlobalHash. + * - подпись блока делаем ключом логина (loginPrivKey). */ public class IT_03_AddBlock_NoAuth { - // ANSI цвета (работает в большинстве терминалов) + // ANSI цвета private static final String R = "\u001B[0m"; private static final String G = "\u001B[32m"; private static final String Y = "\u001B[33m"; @@ -63,10 +64,6 @@ public class IT_03_AddBlock_NoAuth { System.out.println(G + "✅ " + s + R); } - private static void warn(String s) { - System.out.println(Y + "⚠️ " + s + R); - } - private static void boom(String s) { System.out.println(RED + "****************************************************************" + R); System.out.println(RED + "❌ " + s + R); @@ -137,21 +134,19 @@ public class IT_03_AddBlock_NoAuth { try (WsTestClient client = new WsTestClient(TestConfig.WS_URI)) { - // ================================================================================= - // ШАГ 1: HEADER (global=0) - // ================================================================================= + // -------------------- ШАГ 1: HEADER (global=0) -------------------- stepTitle("ШАГ 1: AddBlock HEADER (global=0)"); byte[] headerFull = buildHeaderBlockFullBytes( - 0, // globalNumber - (short) 0, // lineIndex - 0, // lineBlockNumber - ZERO32, // prevGlobalHash32 - ZERO32 // prevLineHash32 (пока равно prevGlobal) + 0, + (short) 0, + 0, + ZERO32, + ZERO32 ); String reqId1 = "it03-add-header"; - String reqJson1 = buildAddBlockJson(reqId1, TestConfig.TEST_BCH_NAME, 0, ZERO64, base64(headerFull)); + String reqJson1 = buildAddBlockJson(reqId1, TestConfig.TEST_BCH_NAME(), 0, ZERO64, base64(headerFull)); send("AddBlock#HEADER", reqJson1); String resp1 = client.request(reqId1, reqJson1, Duration.ofSeconds(8)); @@ -166,25 +161,23 @@ public class IT_03_AddBlock_NoAuth { ok("HEADER принят. serverLastGlobalHash=" + serverLastGlobalHash); - // ================================================================================= - // ШАГ 2: TEXT (global=1) - // ================================================================================= + // -------------------- ШАГ 2: TEXT (global=1) -------------------- stepTitle("ШАГ 2: AddBlock TEXT (global=1)"); byte[] prevGlobal32 = hexToBytes32(serverLastGlobalHash); - byte[] prevLine32 = prevGlobal32; // пока lineHash = globalHash + byte[] prevLine32 = prevGlobal32; byte[] textFull = buildTextBlockFullBytes( - 1, // globalNumber - (short) 0, // lineIndex - 1, // lineBlockNumber + 1, + (short) 0, + 1, prevGlobal32, prevLine32, "Hello from IT_03 test" ); String reqId2 = "it03-add-text"; - String reqJson2 = buildAddBlockJson(reqId2, TestConfig.TEST_BCH_NAME, 1, serverLastGlobalHash, base64(textFull)); + String reqJson2 = buildAddBlockJson(reqId2, TestConfig.TEST_BCH_NAME(), 1, serverLastGlobalHash, base64(textFull)); send("AddBlock#TEXT", reqJson2); String resp2 = client.request(reqId2, reqJson2, Duration.ofSeconds(8)); @@ -201,7 +194,7 @@ public class IT_03_AddBlock_NoAuth { } // ================================================================================= - // BUILD BLOCKS + // BUILD BLOCKS // ================================================================================= private static byte[] buildHeaderBlockFullBytes(int globalNumber, @@ -210,9 +203,7 @@ public class IT_03_AddBlock_NoAuth { byte[] prevGlobalHash32, byte[] prevLineHash32) { - // HeaderBody формата type=0 ver=1: - // [type][ver][tag "SHiNE001"][loginLen][login] - HeaderBody body = new HeaderBody(TestConfig.TEST_LOGIN); + HeaderBody body = new HeaderBody(TestConfig.TEST_LOGIN()); byte[] bodyBytes = body.toBytes(); return buildSignedBlockFullBytes(globalNumber, lineIndex, lineBlockNumber, bodyBytes, prevGlobalHash32, prevLineHash32); @@ -240,7 +231,6 @@ public class IT_03_AddBlock_NoAuth { long ts = System.currentTimeMillis() / 1000L; - // recordSize = RAW header + body (без подписи/хэша — это внутренняя "raw"-часть записи) int recordSize = BchBlockEntry.RAW_HEADER_SIZE + bodyBytes.length; byte[] rawBytes = ByteBuffer.allocate(recordSize) @@ -254,7 +244,7 @@ public class IT_03_AddBlock_NoAuth { .array(); byte[] preimage = BchCryptoVerifier.buildPreimage( - TestConfig.TEST_LOGIN, + TestConfig.TEST_LOGIN(), prevGlobalHash32, prevLineHash32, rawBytes @@ -262,8 +252,8 @@ public class IT_03_AddBlock_NoAuth { 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( globalNumber, @@ -276,15 +266,11 @@ public class IT_03_AddBlock_NoAuth { ).toBytes(); } - // ================================================================================= - // JSON BUILD - // ================================================================================= - private static String buildAddBlockJson(String requestId, - String blockchainName, - int globalNumber, - String prevGlobalHashHex, - String blockBytesB64) { + String blockchainName, + int globalNumber, + String prevGlobalHashHex, + String blockBytesB64) { return """ { "op": "AddBlock", @@ -299,14 +285,8 @@ public class IT_03_AddBlock_NoAuth { """.formatted(requestId, blockchainName, globalNumber, prevGlobalHashHex, blockBytesB64); } - // ================================================================================= - // HELPERS - // ================================================================================= - private static String extractPayloadString(String json, String field) { try { - // JsonParsers у тебя уже есть, но тут проще и не ломать совместимость: - // Если захочешь — можем добавить в JsonParsers отдельный метод payloadString(...) com.fasterxml.jackson.databind.JsonNode root = new com.fasterxml.jackson.databind.ObjectMapper().readTree(json); com.fasterxml.jackson.databind.JsonNode payload = root.get("payload"); @@ -334,4 +314,4 @@ public class IT_03_AddBlock_NoAuth { } return out; } -} +} \ No newline at end of file diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..2fe8504 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,5 @@ +# ???????????? ??? ?????????, ????? ? ????? ??????? ??? "??????? ?????" ??-?????? +junit.platform.listeners.default=test.it.utils.RussianSummaryListener + +# ?? ?????? ??????: ????????? ???????????? ?????????? ?? ?????? JUnit +junit.jupiter.execution.parallel.enabled=false