From 8a83ac85d90917f2d568d6f4ba6cdefe270ff20acbb739b2bc53e70caeee579b Mon Sep 17 00:00:00 2001 From: AidarKC Date: Fri, 3 Apr 2026 11:04:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BD=D0=B0=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit в которой надо дорабоать 1. Исправить ошибки и сделать что бы работала вторая слева вкладка. ТОесть АПИ для сервера я сделал (пока они возвращают весь список сообщений целиком - всем большим списком сообщений в канал - для мвп это устраивает,и по этому только три АПИ функции добавилось) Там какието ошибки на клиенте ( я только сгенерил код - но гдето вылетает) по UI можешь исправлять переделывать - моешь оставить калечное как есть - мне пока не важно. Важно увидить что каналы и сообщения и публичная переписка в каналах блокчейна работает 2. потестировать и сделать корректное завершение сессии (там есть глюки при завершении сесии) --- build.gradle | 2 +- shine-UI/js/services/client-error-reporter.js | 105 ++++++++++++++++++ .../system/Net_ClientErrorLog_Handler.java | 100 +++++++++++++++++ .../system/Net_GetServerInfo_Handler.java | 38 +++++++ .../entyties/Net_ClientErrorLog_Request.java | 81 ++++++++++++++ .../entyties/Net_ClientErrorLog_Response.java | 15 +++ .../entyties/Net_GetServerInfo_Request.java | 14 +++ .../entyties/Net_GetServerInfo_Response.java | 47 ++++++++ .../ws_protocol/JSON/utils/AuthKeyUtils.java | 44 ++++++++ .../it/IT_DeployRestartAndRunRemoteMain.java | 4 +- .../it/cases/IT_00_TechnicalRequests.java | 94 ++++++++++++++++ 11 files changed, 541 insertions(+), 3 deletions(-) create mode 100644 shine-UI/js/services/client-error-reporter.js create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_ClientErrorLog_Handler.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_GetServerInfo_Handler.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Request.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Response.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Request.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Response.java create mode 100644 shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthKeyUtils.java create mode 100644 src/test/java/test/it/cases/IT_00_TechnicalRequests.java diff --git a/build.gradle b/build.gradle index 7a2abff..0d8dc0c 100644 --- a/build.gradle +++ b/build.gradle @@ -169,7 +169,7 @@ tasks.register('deployServer', JavaExec) { // можно переопределить при запуске: // ./gradlew deployServer -Dit.remoteHost=... -Dit.wsUri=... dependsOn shadowJar - systemProperty "it.remoteHost", System.getProperty("it.remoteHost", "10.147.20.7") + systemProperty "it.remoteHost", System.getProperty("it.remoteHost", "194.87.0.247") systemProperty "it.remoteUser", System.getProperty("it.remoteUser", "user") systemProperty "it.remoteDir", System.getProperty("it.remoteDir", "/home/user/docker/shine-server") systemProperty "it.remoteDataDir", System.getProperty("it.remoteDataDir", "/home/user/docker/shine-server/data") diff --git a/shine-UI/js/services/client-error-reporter.js b/shine-UI/js/services/client-error-reporter.js new file mode 100644 index 0000000..ce75528 --- /dev/null +++ b/shine-UI/js/services/client-error-reporter.js @@ -0,0 +1,105 @@ +const MAX_CONTEXT_LEN = 2000; +const RECENT_WINDOW_MS = 5000; + +let transport = null; +let transportDepth = 0; +const recentFingerprints = new Map(); + +function nowTs() { + return Date.now(); +} + +function cleanString(value, maxLen = 1000) { + if (value == null) return ''; + const normalized = String(value).replace(/\s+/g, ' ').trim(); + if (normalized.length <= maxLen) return normalized; + return `${normalized.slice(0, Math.max(0, maxLen - 3))}...`; +} + +function stringifyContext(context) { + if (context == null) return ''; + try { + const raw = JSON.stringify(context); + if (!raw) return ''; + if (raw.length <= MAX_CONTEXT_LEN) return raw; + return `${raw.slice(0, MAX_CONTEXT_LEN - 3)}...`; + } catch (error) { + return cleanString(`context_json_error:${error?.message || error}`, MAX_CONTEXT_LEN); + } +} + +function makeFingerprint(payload) { + return [ + payload.kind, + payload.message, + payload.sourceUrl, + payload.lineNumber, + payload.columnNumber, + payload.requestOp, + ].join('|'); +} + +function isDuplicate(fingerprint) { + const ts = nowTs(); + const prev = recentFingerprints.get(fingerprint); + recentFingerprints.set(fingerprint, ts); + + for (const [key, time] of recentFingerprints.entries()) { + if (ts - time > RECENT_WINDOW_MS) { + recentFingerprints.delete(key); + } + } + + return prev != null && ts - prev < RECENT_WINDOW_MS; +} + +function buildPayload(details = {}) { + return { + kind: cleanString(details.kind || 'client_error', 64), + message: cleanString(details.message || details.reason || 'Unknown client error', 500), + stack: cleanString(details.stack || details.error?.stack || '', 8000), + sourceUrl: cleanString(details.sourceUrl || details.fileName || '', 240), + lineNumber: Number.isFinite(details.lineNumber) ? details.lineNumber : null, + columnNumber: Number.isFinite(details.columnNumber) ? details.columnNumber : null, + route: cleanString(details.route || window.location?.hash || '', 200), + href: cleanString(details.href || window.location?.href || '', 240), + userAgent: cleanString(details.userAgent || navigator.userAgent || '', 240), + clientTs: Number.isFinite(details.clientTs) ? details.clientTs : nowTs(), + requestOp: cleanString(details.requestOp || '', 64), + requestIdRef: cleanString(details.requestIdRef || '', 128), + contextJson: stringifyContext({ + title: document.title || '', + pageVisibility: document.visibilityState || '', + ...details.context, + }), + }; +} + +export function setClientErrorTransport(fn) { + transport = typeof fn === 'function' ? fn : null; +} + +export async function captureClientError(details = {}) { + const payload = buildPayload(details); + if (!payload.message) return false; + + const fingerprint = details.dedupeKey || makeFingerprint(payload); + if (isDuplicate(fingerprint)) return false; + + console.error('[client-error]', payload.kind, payload.message, details.error || ''); + + if (!transport || details.skipTransport === true || transportDepth > 0) { + return false; + } + + try { + transportDepth += 1; + await transport(payload); + return true; + } catch (error) { + console.warn('client error transport failed', error); + return false; + } finally { + transportDepth = Math.max(0, transportDepth - 1); + } +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_ClientErrorLog_Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_ClientErrorLog_Handler.java new file mode 100644 index 0000000..6afc8f0 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_ClientErrorLog_Handler.java @@ -0,0 +1,100 @@ +package server.logic.ws_protocol.JSON.handlers.system; + +import org.eclipse.jetty.websocket.api.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import server.logic.ws_protocol.JSON.ConnectionContext; +import server.logic.ws_protocol.JSON.entyties.Net_Request; +import server.logic.ws_protocol.JSON.entyties.Net_Response; +import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; +import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ClientErrorLog_Request; +import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_ClientErrorLog_Response; +import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory; +import server.logic.ws_protocol.WireCodes; + +import java.net.SocketAddress; + +/** + * ClientErrorLog — технический endpoint для фронтенд-ошибок. + * Не требует авторизации: клиент должен иметь возможность отправить ошибку + * даже если логин/сессия ещё не установлены. + */ +public class Net_ClientErrorLog_Handler implements JsonMessageHandler { + + private static final Logger log = LoggerFactory.getLogger(Net_ClientErrorLog_Handler.class); + + @Override + public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) { + Net_ClientErrorLog_Request req = (Net_ClientErrorLog_Request) baseRequest; + + if (req.getMessage() == null || req.getMessage().isBlank()) { + return NetExceptionResponseFactory.error( + req, + WireCodes.Status.BAD_REQUEST, + "BAD_FIELDS", + "Поле message обязательно для ClientErrorLog" + ); + } + + long serverTs = System.currentTimeMillis(); + String login = safe(ctx != null ? ctx.getLogin() : null); + String sessionId = safe(ctx != null ? ctx.getSessionId() : null); + String remote = safe(remoteAddress(ctx)); + + log.error( + "CLIENT_FRONTEND_ERROR kind={} clientTs={} serverTs={} login={} sessionId={} remote={} route={} href={} sourceUrl={} line={} column={} requestOp={} requestIdRef={} message={} userAgent={} context={}", + clip(req.getKind(), 64), + req.getClientTs(), + serverTs, + clip(login, 64), + clip(sessionId, 128), + clip(remote, 128), + clip(req.getRoute(), 200), + clip(req.getHref(), 240), + clip(req.getSourceUrl(), 240), + req.getLineNumber(), + req.getColumnNumber(), + clip(req.getRequestOp(), 64), + clip(req.getRequestIdRef(), 128), + clip(req.getMessage(), 500), + clip(req.getUserAgent(), 240), + clip(req.getContextJson(), 2000) + ); + + if (req.getStack() != null && !req.getStack().isBlank()) { + log.error("CLIENT_FRONTEND_ERROR_STACK requestId={} stack={}", + clip(req.getRequestId(), 128), + clip(req.getStack(), 8000)); + } + + Net_ClientErrorLog_Response resp = new Net_ClientErrorLog_Response(); + resp.setOp(req.getOp()); + resp.setRequestId(req.getRequestId()); + resp.setStatus(WireCodes.Status.OK); + resp.setAccepted(true); + resp.setServerTs(serverTs); + return resp; + } + + private static String remoteAddress(ConnectionContext ctx) { + if (ctx == null) return ""; + Session ws = ctx.getWsSession(); + if (ws == null) return ""; + SocketAddress remote = ws.getRemoteAddress(); + return remote != null ? remote.toString() : ""; + } + + private static String safe(String value) { + return value == null ? "" : value.trim(); + } + + private static String clip(String value, int maxLen) { + String cleaned = safe(value) + .replace('\n', ' ') + .replace('\r', ' '); + if (cleaned.length() <= maxLen) { + return cleaned; + } + return cleaned.substring(0, Math.max(0, maxLen - 3)) + "..."; + } +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_GetServerInfo_Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_GetServerInfo_Handler.java new file mode 100644 index 0000000..bf16e39 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/Net_GetServerInfo_Handler.java @@ -0,0 +1,38 @@ +package server.logic.ws_protocol.JSON.handlers.system; + +import server.logic.ws_protocol.JSON.ConnectionContext; +import server.logic.ws_protocol.JSON.entyties.Net_Request; +import server.logic.ws_protocol.JSON.entyties.Net_Response; +import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; +import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetServerInfo_Request; +import server.logic.ws_protocol.JSON.handlers.system.entyties.Net_GetServerInfo_Response; +import server.logic.ws_protocol.WireCodes; +import utils.config.AppConfig; + +/** + * GetServerInfo — технический запрос без авторизации. + * Возвращает базовую публичную информацию о сервере, чтобы клиент + * мог проверить доступность узла и показать его в списке серверов. + */ +public class Net_GetServerInfo_Handler implements JsonMessageHandler { + + private static final AppConfig CONFIG = AppConfig.getInstance(); + + @Override + public Net_Response handle(Net_Request baseRequest, ConnectionContext ctx) { + Net_GetServerInfo_Request req = (Net_GetServerInfo_Request) baseRequest; + + Net_GetServerInfo_Response resp = new Net_GetServerInfo_Response(); + resp.setOp(req.getOp()); + resp.setRequestId(req.getRequestId()); + resp.setStatus(WireCodes.Status.OK); + resp.setUrl(CONFIG.getStringOrEmpty("server.info.url")); + resp.setVersion(CONFIG.getStringOrEmpty("server.version")); + resp.setPhysicalRegion(CONFIG.getStringOrEmpty("server.info.physicalRegion")); + resp.setDescription(CONFIG.getStringOrEmpty("server.info.description")); + resp.setOrigin(CONFIG.getStringOrEmpty("server.info.origin")); + resp.setExtraInfo(CONFIG.getStringOrEmpty("server.info.extraInfo")); + + return resp; + } +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Request.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Request.java new file mode 100644 index 0000000..54b7ed8 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Request.java @@ -0,0 +1,81 @@ +package server.logic.ws_protocol.JSON.handlers.system.entyties; + +import server.logic.ws_protocol.JSON.entyties.Net_Request; + +/** + * ClientErrorLog: + * { + * "op": "ClientErrorLog", + * "requestId": "req-1", + * "payload": { + * "kind": "global_error", + * "message": "...", + * "stack": "...", + * "sourceUrl": "...", + * "lineNumber": 10, + * "columnNumber": 20, + * "route": "#/channel-view/own-0", + * "href": "https://example/#/channel-view/own-0", + * "userAgent": "...", + * "clientTs": 1700000000123, + * "requestOp": "GetChannelMessages", + * "requestIdRef": "GetChannelMessages-123", + * "contextJson": "{...}" + * } + * } + */ +public class Net_ClientErrorLog_Request extends Net_Request { + + private String kind; + private String message; + private String stack; + private String sourceUrl; + private Integer lineNumber; + private Integer columnNumber; + private String route; + private String href; + private String userAgent; + private Long clientTs; + private String requestOp; + private String requestIdRef; + private String contextJson; + + public String getKind() { return kind; } + public void setKind(String kind) { this.kind = kind; } + + public String getMessage() { return message; } + public void setMessage(String message) { this.message = message; } + + public String getStack() { return stack; } + public void setStack(String stack) { this.stack = stack; } + + public String getSourceUrl() { return sourceUrl; } + public void setSourceUrl(String sourceUrl) { this.sourceUrl = sourceUrl; } + + public Integer getLineNumber() { return lineNumber; } + public void setLineNumber(Integer lineNumber) { this.lineNumber = lineNumber; } + + public Integer getColumnNumber() { return columnNumber; } + public void setColumnNumber(Integer columnNumber) { this.columnNumber = columnNumber; } + + public String getRoute() { return route; } + public void setRoute(String route) { this.route = route; } + + public String getHref() { return href; } + public void setHref(String href) { this.href = href; } + + public String getUserAgent() { return userAgent; } + public void setUserAgent(String userAgent) { this.userAgent = userAgent; } + + public Long getClientTs() { return clientTs; } + public void setClientTs(Long clientTs) { this.clientTs = clientTs; } + + public String getRequestOp() { return requestOp; } + public void setRequestOp(String requestOp) { this.requestOp = requestOp; } + + public String getRequestIdRef() { return requestIdRef; } + public void setRequestIdRef(String requestIdRef) { this.requestIdRef = requestIdRef; } + + public String getContextJson() { return contextJson; } + public void setContextJson(String contextJson) { this.contextJson = contextJson; } +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Response.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Response.java new file mode 100644 index 0000000..69fc85c --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_ClientErrorLog_Response.java @@ -0,0 +1,15 @@ +package server.logic.ws_protocol.JSON.handlers.system.entyties; + +import server.logic.ws_protocol.JSON.entyties.Net_Response; + +public class Net_ClientErrorLog_Response extends Net_Response { + + private long serverTs; + private boolean accepted; + + public long getServerTs() { return serverTs; } + public void setServerTs(long serverTs) { this.serverTs = serverTs; } + + public boolean isAccepted() { return accepted; } + public void setAccepted(boolean accepted) { this.accepted = accepted; } +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Request.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Request.java new file mode 100644 index 0000000..0c08da3 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Request.java @@ -0,0 +1,14 @@ +package server.logic.ws_protocol.JSON.handlers.system.entyties; + +import server.logic.ws_protocol.JSON.entyties.Net_Request; + +/** + * GetServerInfo: + * { + * "op": "GetServerInfo", + * "requestId": "req-1", + * "payload": {} + * } + */ +public class Net_GetServerInfo_Request extends Net_Request { +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Response.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Response.java new file mode 100644 index 0000000..daf1848 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/system/entyties/Net_GetServerInfo_Response.java @@ -0,0 +1,47 @@ +package server.logic.ws_protocol.JSON.handlers.system.entyties; + +import server.logic.ws_protocol.JSON.entyties.Net_Response; + +/** + * Ответ GetServerInfo: + * { + * "op": "GetServerInfo", + * "requestId": "req-1", + * "status": 200, + * "payload": { + * "url": "...", + * "version": "...", + * "physicalRegion": "...", + * "description": "...", + * "origin": "...", + * "extraInfo": "..." + * } + * } + */ +public class Net_GetServerInfo_Response extends Net_Response { + + private String url; + private String version; + private String physicalRegion; + private String description; + private String origin; + private String extraInfo; + + public String getUrl() { return url; } + public void setUrl(String url) { this.url = url; } + + public String getVersion() { return version; } + public void setVersion(String version) { this.version = version; } + + public String getPhysicalRegion() { return physicalRegion; } + public void setPhysicalRegion(String physicalRegion) { this.physicalRegion = physicalRegion; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public String getOrigin() { return origin; } + public void setOrigin(String origin) { this.origin = origin; } + + public String getExtraInfo() { return extraInfo; } + public void setExtraInfo(String extraInfo) { this.extraInfo = extraInfo; } +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthKeyUtils.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthKeyUtils.java new file mode 100644 index 0000000..4cd76a2 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/utils/AuthKeyUtils.java @@ -0,0 +1,44 @@ +package server.logic.ws_protocol.JSON.utils; + +import server.logic.ws_protocol.Base64Ws; + +/** + * Утилиты для строковых публичных ключей, используемых в auth/session API. + * + * Поддерживаемые форматы: + * - legacy: BASE64(32 bytes) + * - explicit: ed25519/BASE64(32 bytes) + */ +public final class AuthKeyUtils { + + private AuthKeyUtils() {} + + public static String normalize(String key, String fieldName) { + if (key == null) throw new IllegalArgumentException(fieldName + " is null"); + String trimmed = key.trim(); + if (trimmed.isEmpty()) throw new IllegalArgumentException(fieldName + " is empty"); + return trimmed; + } + + public static byte[] parseEd25519PublicKey(String key, String fieldName) { + String normalized = normalize(key, fieldName); + + int slash = normalized.indexOf('/'); + if (slash < 0) { + return Base64Ws.decodeLen(normalized, 32, fieldName); + } + + String algorithm = normalized.substring(0, slash).trim(); + String encodedKey = normalized.substring(slash + 1).trim(); + + if (algorithm.isEmpty() || encodedKey.isEmpty()) { + throw new IllegalArgumentException(fieldName + " has bad algorithm/key format"); + } + + if (!"ed25519".equalsIgnoreCase(algorithm)) { + throw new UnsupportedOperationException(fieldName + " algorithm is not supported: " + algorithm); + } + + return Base64Ws.decodeLen(encodedKey, 32, fieldName); + } +} diff --git a/src/test/java/test/it/IT_DeployRestartAndRunRemoteMain.java b/src/test/java/test/it/IT_DeployRestartAndRunRemoteMain.java index 1fd5453..de738b0 100644 --- a/src/test/java/test/it/IT_DeployRestartAndRunRemoteMain.java +++ b/src/test/java/test/it/IT_DeployRestartAndRunRemoteMain.java @@ -7,7 +7,7 @@ import java.util.Objects; public class IT_DeployRestartAndRunRemoteMain { // ====== НАСТРОЙКИ (можно переопределять systemProperty) ====== - private static final String REMOTE_HOST = System.getProperty("it.remoteHost", "10.147.20.7"); + private static final String REMOTE_HOST = System.getProperty("it.remoteHost", "194.87.0.247"); private static final String REMOTE_USER = System.getProperty("it.remoteUser", "user"); private static final String REMOTE_DIR = System.getProperty("it.remoteDir", "/home/user/docker/shine-server"); @@ -103,4 +103,4 @@ public class IT_DeployRestartAndRunRemoteMain { try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } -} \ No newline at end of file +} diff --git a/src/test/java/test/it/cases/IT_00_TechnicalRequests.java b/src/test/java/test/it/cases/IT_00_TechnicalRequests.java new file mode 100644 index 0000000..c474633 --- /dev/null +++ b/src/test/java/test/it/cases/IT_00_TechnicalRequests.java @@ -0,0 +1,94 @@ +package test.it.cases; + +import test.it.utils.json.JsonBuilders; +import test.it.utils.json.JsonParsers; +import test.it.utils.log.TestResult; +import test.it.utils.ws.WsSession; + +import java.time.Duration; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * IT_00_TechnicalRequests + * Проверяет технические запросы без авторизации: + * - Ping + * - GetServerInfo + */ +public class IT_00_TechnicalRequests { + + public static void main(String[] args) { + String summary = run(); + System.out.println(summary); + } + + public static String run() { + TestResult r = new TestResult("IT_00_TechnicalRequests"); + Duration t = Duration.ofSeconds(5); + + try (WsSession ws = WsSession.open()) { + checkPing(r, ws, t); + checkGetServerInfo(r, ws, t); + } catch (Throwable e) { + r.fail("IT_00_TechnicalRequests упал: " + e.getMessage()); + } + + return r.summaryLine(); + } + + private static void checkPing(TestResult r, WsSession ws, Duration t) { + String resp = ws.call("Ping", JsonBuilders.ping(System.currentTimeMillis()), t); + + if (JsonParsers.status(resp) != 200) { + r.fail("Ping: ожидали status=200, resp=" + resp); + fail("Ping unexpected status"); + } + if (!Boolean.TRUE.equals(JsonParsers.ok(resp))) { + r.fail("Ping: ожидали ok=true, resp=" + resp); + fail("Ping unexpected ok"); + } + + Long ts = JsonParsers.pingTs(resp); + if (ts == null || ts <= 0) { + r.fail("Ping: сервер не вернул payload.ts, resp=" + resp); + fail("Ping missing ts"); + } + + r.ok("Ping: OK, ts=" + ts); + } + + private static void checkGetServerInfo(TestResult r, WsSession ws, Duration t) { + String resp = ws.call("GetServerInfo", JsonBuilders.getServerInfo(), t); + + if (JsonParsers.status(resp) != 200) { + r.fail("GetServerInfo: ожидали status=200, resp=" + resp); + fail("GetServerInfo unexpected status"); + } + if (!Boolean.TRUE.equals(JsonParsers.ok(resp))) { + r.fail("GetServerInfo: ожидали ok=true, resp=" + resp); + fail("GetServerInfo unexpected ok"); + } + if (!JsonParsers.payloadIsObject(resp)) { + r.fail("GetServerInfo: payload должен быть объектом, resp=" + resp); + fail("GetServerInfo payload is not object"); + } + + assertStringField(resp, "url", r); + String version = assertStringField(resp, "version", r); + assertStringField(resp, "physicalRegion", r); + assertStringField(resp, "description", r); + assertStringField(resp, "origin", r); + assertStringField(resp, "extraInfo", r); + + r.ok("GetServerInfo: OK, version=" + version); + } + + private static String assertStringField(String resp, String field, TestResult r) { + String value = JsonParsers.payloadText(resp, field); + if (value == null) { + r.fail("GetServerInfo: отсутствует поле payload." + field + ", resp=" + resp); + fail("GetServerInfo missing field: " + field); + } + return value; + } +}