Промежуточная версия
в которой надо дорабоать 1. Исправить ошибки и сделать что бы работала вторая слева вкладка. ТОесть АПИ для сервера я сделал (пока они возвращают весь список сообщений целиком - всем большим списком сообщений в канал - для мвп это устраивает,и по этому только три АПИ функции добавилось) Там какието ошибки на клиенте ( я только сгенерил код - но гдето вылетает) по UI можешь исправлять переделывать - моешь оставить калечное как есть - мне пока не важно. Важно увидить что каналы и сообщения и публичная переписка в каналах блокчейна работает 2. потестировать и сделать корректное завершение сессии (там есть глюки при завершении сесии)
This commit is contained in:
parent
78e62997d1
commit
8a83ac85d9
@ -169,7 +169,7 @@ tasks.register('deployServer', JavaExec) {
|
|||||||
// можно переопределить при запуске:
|
// можно переопределить при запуске:
|
||||||
// ./gradlew deployServer -Dit.remoteHost=... -Dit.wsUri=...
|
// ./gradlew deployServer -Dit.remoteHost=... -Dit.wsUri=...
|
||||||
dependsOn shadowJar
|
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.remoteUser", System.getProperty("it.remoteUser", "user")
|
||||||
systemProperty "it.remoteDir", System.getProperty("it.remoteDir", "/home/user/docker/shine-server")
|
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")
|
systemProperty "it.remoteDataDir", System.getProperty("it.remoteDataDir", "/home/user/docker/shine-server/data")
|
||||||
|
|||||||
105
shine-UI/js/services/client-error-reporter.js
Normal file
105
shine-UI/js/services/client-error-reporter.js
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)) + "...";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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; }
|
||||||
|
}
|
||||||
@ -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; }
|
||||||
|
}
|
||||||
@ -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 {
|
||||||
|
}
|
||||||
@ -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; }
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ import java.util.Objects;
|
|||||||
public class IT_DeployRestartAndRunRemoteMain {
|
public class IT_DeployRestartAndRunRemoteMain {
|
||||||
|
|
||||||
// ====== НАСТРОЙКИ (можно переопределять systemProperty) ======
|
// ====== НАСТРОЙКИ (можно переопределять 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_USER = System.getProperty("it.remoteUser", "user");
|
||||||
|
|
||||||
private static final String REMOTE_DIR = System.getProperty("it.remoteDir", "/home/user/docker/shine-server");
|
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); }
|
try { Thread.sleep(ms); }
|
||||||
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
94
src/test/java/test/it/cases/IT_00_TechnicalRequests.java
Normal file
94
src/test/java/test/it/cases/IT_00_TechnicalRequests.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user