mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
Merge pull request #55 from sinanyuntu/feat/onetalk-diagnostic-log
feat: 增加 OneTalk 诊断 payload 文件日志
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
HOST=127.0.0.1
|
||||
PORT=7878
|
||||
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/trade_message_center
|
||||
# 仅开发排障:将未脱敏 OneTalk WebSocket payload 写入 logs/<BUILD_HASH>.log;默认关闭。
|
||||
ONETALK_DIAGNOSTIC_LOG=false
|
||||
|
||||
# Chrome 扩展临时下载签名及本地 Mind 联调页的 server-proxy OSS 上传。TMC_PACKAGE_VERSION 由 workspace 命令从根 package.json 注入;AccessKey 只能留在 server 侧环境文件。
|
||||
OSS_BUCKET=sinanpilot-bucket
|
||||
|
||||
@@ -92,6 +92,7 @@ export type OneTalkBrightDiagnostic = {
|
||||
requestedPermissions?: OneTalkPermission[];
|
||||
bindingPresent?: boolean;
|
||||
redactedFields?: readonly string[];
|
||||
payload?: unknown;
|
||||
};
|
||||
|
||||
export type OneTalkBrightClientOptions = {
|
||||
@@ -323,12 +324,14 @@ export const createOneTalkBrightClient = (
|
||||
| "requestedPermissions"
|
||||
| "bindingPresent"
|
||||
| "redactedFields"
|
||||
| "payload"
|
||||
> => ({
|
||||
...socketDiagnostic(),
|
||||
protocolVersion: frame.protocolVersion,
|
||||
connectionType: frame.connectionType,
|
||||
frameType: frame.type,
|
||||
requestId: frame.requestId,
|
||||
payload: frame.payload,
|
||||
...(frame.type === "ws.hello"
|
||||
? {
|
||||
requestedPermissions: [...frame.payload.requestedPermissions],
|
||||
@@ -410,6 +413,7 @@ export const createOneTalkBrightClient = (
|
||||
direction: "outbound",
|
||||
requestId: frame.requestId,
|
||||
frameType: frame.type,
|
||||
payload: frame.payload,
|
||||
code: "send_failed",
|
||||
});
|
||||
report(new OneTalkBrightClientError("send_failed", error));
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// 启动页面桥并管理可配置的 OneTalk 同步运行时
|
||||
|
||||
import {
|
||||
composeOneTalkDiagnosticLog,
|
||||
isPlainRecord,
|
||||
type OneTalkDiagnosticLogEvent,
|
||||
} from "@trade-message-center/onetalk-contract";
|
||||
import {
|
||||
clearOneTalkExtensionConfig,
|
||||
isMaskedOneTalkBinding,
|
||||
@@ -16,7 +21,6 @@ import {
|
||||
type OneTalkExtensionStorageArea,
|
||||
type OneTalkPopupErrorCode,
|
||||
} from "./onetalk/config.ts";
|
||||
import { isPlainRecord } from "@trade-message-center/onetalk-contract";
|
||||
import {
|
||||
createOneTalkServiceWorkerSyncController,
|
||||
type OneTalkServiceWorkerSyncController,
|
||||
@@ -25,13 +29,7 @@ import {
|
||||
import { logOneTalkError } from "./onetalk/service-worker/error-diagnostics.ts";
|
||||
import type { OneTalkServiceWorkerPort } from "./onetalk/service-worker/runtime.ts";
|
||||
import { BRIGHT_WEBSOCKET_URL, ENABLE_ONE_TALK_DIAGNOSTICS } from "./onetalk/build-config.ts";
|
||||
import type { OneTalkPageDiagnostic } from "./onetalk/service-worker/runtime.ts";
|
||||
import type { OneTalkSyncEngineDiagnostic } from "./onetalk/service-worker/sync-engine.ts";
|
||||
import {
|
||||
OneTalkBrightClientError,
|
||||
type OneTalkBrightDiagnostic,
|
||||
} from "./onetalk/service-worker/transport/bright-client.ts";
|
||||
import type { OneTalkContactProfileDiagnostic } from "./onetalk/service-worker/contact-profile-coordinator.ts";
|
||||
import { OneTalkBrightClientError } from "./onetalk/service-worker/transport/bright-client.ts";
|
||||
|
||||
declare const chrome: {
|
||||
runtime: {
|
||||
@@ -59,28 +57,10 @@ declare const chrome: {
|
||||
};
|
||||
};
|
||||
|
||||
type OneTalkDiagnostic =
|
||||
| OneTalkBrightDiagnostic
|
||||
| OneTalkPageDiagnostic
|
||||
| OneTalkSyncEngineDiagnostic
|
||||
| OneTalkContactProfileDiagnostic;
|
||||
|
||||
const heartbeatDiagnosticText = (event: OneTalkDiagnostic): string | undefined => {
|
||||
if (event.event !== "frame") return undefined;
|
||||
if (event.frameType === "heartbeat.ack") return "心跳确认";
|
||||
if (event.frameType !== "heartbeat") return undefined;
|
||||
return event.direction === "inbound" ? "心跳接收" : "心跳发送";
|
||||
};
|
||||
|
||||
const logOneTalkDiagnostic = (source: string, event: OneTalkDiagnostic): void => {
|
||||
const logOneTalkDiagnostic = <T extends OneTalkDiagnosticLogEvent>(event: T): void => {
|
||||
if (!ENABLE_ONE_TALK_DIAGNOSTICS) return;
|
||||
const heartbeatText = heartbeatDiagnosticText(event);
|
||||
if (heartbeatText !== undefined) {
|
||||
console.info(`[Trade Message Center][OneTalk ${source}] ${heartbeatText}`);
|
||||
return;
|
||||
}
|
||||
const level = event.status === "failed" || event.code ? "warn" : "info";
|
||||
console[level](`[Trade Message Center][OneTalk ${source}]`, event);
|
||||
const diagnostic = composeOneTalkDiagnosticLog("extension", event);
|
||||
console[diagnostic.level](diagnostic.headline, diagnostic.payload);
|
||||
};
|
||||
|
||||
const isNoPopupReceiverError = (error: unknown): boolean => {
|
||||
@@ -131,15 +111,13 @@ const broadcastStatus = (snapshot: OneTalkServiceWorkerSyncSnapshot): void => {
|
||||
};
|
||||
|
||||
const controller: OneTalkServiceWorkerSyncController = createOneTalkServiceWorkerSyncController({
|
||||
onBrightDiagnostic: (event: OneTalkBrightDiagnostic) => logOneTalkDiagnostic("Bright", event),
|
||||
onPageDiagnostic: (event: OneTalkPageDiagnostic) => logOneTalkDiagnostic("Page", event),
|
||||
onEngineDiagnostic: (event: OneTalkSyncEngineDiagnostic) => logOneTalkDiagnostic("Sync", event),
|
||||
onProfileDiagnostic: (event: OneTalkContactProfileDiagnostic) =>
|
||||
logOneTalkDiagnostic("Profile", event),
|
||||
onBrightDiagnostic: logOneTalkDiagnostic,
|
||||
onPageDiagnostic: logOneTalkDiagnostic,
|
||||
onEngineDiagnostic: logOneTalkDiagnostic,
|
||||
onProfileDiagnostic: logOneTalkDiagnostic,
|
||||
onStatusChange: (snapshot) => {
|
||||
broadcastStatus(snapshot);
|
||||
if (!ENABLE_ONE_TALK_DIAGNOSTICS) return;
|
||||
console.info("[Trade Message Center][OneTalk Sync]", {
|
||||
logOneTalkDiagnostic({
|
||||
event: "state_snapshot",
|
||||
online: snapshot.engineStatus?.online ?? false,
|
||||
brightStatus: snapshot.connectionStatus,
|
||||
|
||||
@@ -356,6 +356,10 @@ test("keeps ws.error visible and uses a browser-valid client close code", () =>
|
||||
assert.deepEqual(hello.requestedPermissions, ["read", "rebuild"]);
|
||||
assert.equal(hello.bindingPresent, true);
|
||||
assert.ok(hello.redactedFields.includes("binding"));
|
||||
assert.deepEqual(hello.payload, {
|
||||
binding: "binding-1",
|
||||
requestedPermissions: ["read", "rebuild"],
|
||||
});
|
||||
const close = diagnostics.find((event) => event.event === "socket_close");
|
||||
assert.equal(close.closeCode, 1000);
|
||||
assert.equal(close.closeReason, "binding_revoked");
|
||||
@@ -373,7 +377,7 @@ test("keeps ws.error visible and uses a browser-valid client close code", () =>
|
||||
assert.equal(JSON.stringify(diagnostics).includes(socket.url), false);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("credential=secret"), false);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("fragment"), false);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("binding-1"), false);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("binding-1"), true);
|
||||
});
|
||||
|
||||
for (const code of Object.values(ONETALK_ERROR_CODES)) {
|
||||
@@ -499,7 +503,7 @@ for (const code of [
|
||||
});
|
||||
}
|
||||
|
||||
test("diagnoses guarded sends and invalid inbound frames without payloads", () => {
|
||||
test("keeps guarded-send payloads while invalid inbound frames have no decoded payload", () => {
|
||||
const diagnostics = [];
|
||||
const socket = new FakeSocket("wss://bright.example/ws");
|
||||
const client = createOneTalkBrightClient({
|
||||
@@ -530,11 +534,18 @@ test("diagnoses guarded sends and invalid inbound frames without payloads", () =
|
||||
assert.ok(
|
||||
diagnostics.some((event) => event.event === "frame" && event.code === "invalid_message"),
|
||||
);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("secret-binding"), false);
|
||||
const guardedSend = diagnostics.find((event) => event.event === "guarded_send");
|
||||
const invalidInbound = diagnostics.find(
|
||||
(event) => event.event === "frame" && event.code === "invalid_message",
|
||||
);
|
||||
assert.equal(guardedSend.payload.conversationId, "conversation-1");
|
||||
assert.equal(Object.hasOwn(invalidInbound, "payload"), false);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("secret-binding"), true);
|
||||
});
|
||||
|
||||
test("uses a browser-valid close code when the initial hello cannot be sent", () => {
|
||||
const socket = new FakeSocket("wss://bright.example/ws");
|
||||
const diagnostics = [];
|
||||
socket.send = () => {
|
||||
throw new Error("send failed");
|
||||
};
|
||||
@@ -544,6 +555,7 @@ test("uses a browser-valid close code when the initial hello cannot be sent", ()
|
||||
binding: "binding-1",
|
||||
webSocket: () => socket,
|
||||
autoReconnect: false,
|
||||
onDiagnostic: (event) => diagnostics.push(event),
|
||||
});
|
||||
|
||||
client.connect();
|
||||
@@ -553,6 +565,11 @@ test("uses a browser-valid close code when the initial hello cannot be sent", ()
|
||||
socket.closed.at(-1).code === 1000 ||
|
||||
(socket.closed.at(-1).code >= 3000 && socket.closed.at(-1).code <= 4999),
|
||||
);
|
||||
assert.deepEqual(
|
||||
diagnostics.find((event) => event.event === "frame" && event.code === "send_failed")
|
||||
.payload,
|
||||
{ binding: "binding-1", requestedPermissions: ["read", "rebuild"] },
|
||||
);
|
||||
});
|
||||
|
||||
test("sends a complete confirmed_sent message through the strict confirmation contract", () => {
|
||||
|
||||
@@ -6,7 +6,10 @@ import { loadEnvFile } from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { startServer } from "./entry.ts";
|
||||
import type { OneTalkDiagnostic } from "./websocket/diagnostics.ts";
|
||||
import {
|
||||
createOneTalkDiagnosticFileLogger,
|
||||
isOneTalkDiagnosticFileLogEnabled,
|
||||
} from "./websocket/diagnostic-file-log.ts";
|
||||
|
||||
const environmentFileNames = [".env", ".env.local", ".env.development", ".env.development.local"];
|
||||
|
||||
@@ -41,13 +44,6 @@ const loadEnvironmentFiles = (): string[] => {
|
||||
return existingFiles.map((filePath) => relative(projectRoot, filePath));
|
||||
};
|
||||
|
||||
const heartbeatDiagnosticText = (event: OneTalkDiagnostic): string | undefined => {
|
||||
if (event.event !== "ws_frame") return undefined;
|
||||
if (event.frameType === "heartbeat.ack") return "心跳确认";
|
||||
if (event.frameType !== "heartbeat") return undefined;
|
||||
return event.direction === "inbound" ? "心跳接收" : "心跳发送";
|
||||
};
|
||||
|
||||
/** 加载开发环境配置并启动服务。 */
|
||||
const startDevelopmentServer = async (): Promise<void> => {
|
||||
const loadedFiles = loadEnvironmentFiles();
|
||||
@@ -56,19 +52,15 @@ const startDevelopmentServer = async (): Promise<void> => {
|
||||
? `[env] loaded ${loadedFiles.join(", ")}`
|
||||
: "[env] no environment file loaded",
|
||||
);
|
||||
await startServer(
|
||||
(event) => {
|
||||
const heartbeatText = heartbeatDiagnosticText(event);
|
||||
if (heartbeatText !== undefined) {
|
||||
console.info(`[onetalk][diagnostic] ${heartbeatText}`);
|
||||
return;
|
||||
}
|
||||
console.info("[onetalk][diagnostic]", JSON.stringify(event));
|
||||
},
|
||||
(event) => {
|
||||
console.info("[mind-auth][diagnostic]", JSON.stringify(event));
|
||||
},
|
||||
);
|
||||
const oneTalkDiagnostic = isOneTalkDiagnosticFileLogEnabled(process.env.ONETALK_DIAGNOSTIC_LOG)
|
||||
? createOneTalkDiagnosticFileLogger({
|
||||
logsDirectory: resolve(projectRoot, "logs"),
|
||||
buildHash: process.env.BUILD_HASH,
|
||||
})
|
||||
: undefined;
|
||||
await startServer(oneTalkDiagnostic, (event) => {
|
||||
console.info("[mind-auth][diagnostic]", JSON.stringify(event));
|
||||
});
|
||||
};
|
||||
|
||||
void startDevelopmentServer().catch((error: unknown) => {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { appendFileSync, mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import {
|
||||
composeOneTalkDiagnosticLog,
|
||||
type OneTalkDiagnosticLogEvent,
|
||||
} from "@trade-message-center/onetalk-contract";
|
||||
|
||||
const logFileName = (buildHash: string | undefined): string => {
|
||||
const normalizedBuildHash = buildHash?.trim();
|
||||
if (!normalizedBuildHash) return "dev.log";
|
||||
if (!/^[a-zA-Z0-9_-]+$/u.test(normalizedBuildHash)) {
|
||||
throw new Error("BUILD_HASH must contain only letters, numbers, underscores, or dashes");
|
||||
}
|
||||
return `${normalizedBuildHash}.log`;
|
||||
};
|
||||
|
||||
export const isOneTalkDiagnosticFileLogEnabled = (value: string | undefined): boolean => {
|
||||
return value?.trim() === "true";
|
||||
};
|
||||
|
||||
export const createOneTalkDiagnosticFileLogger = (options: {
|
||||
logsDirectory: string;
|
||||
buildHash?: string;
|
||||
}): ((event: OneTalkDiagnosticLogEvent) => void) => {
|
||||
mkdirSync(options.logsDirectory, { recursive: true });
|
||||
const logPath = join(options.logsDirectory, logFileName(options.buildHash));
|
||||
|
||||
return (event) => {
|
||||
const diagnostic = composeOneTalkDiagnosticLog("server", event);
|
||||
appendFileSync(
|
||||
logPath,
|
||||
`${diagnostic.headline} ${JSON.stringify(diagnostic.payload)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -13,6 +13,7 @@ export type OneTalkDiagnostic = {
|
||||
code?: string;
|
||||
closeCode?: number;
|
||||
closeReason?: string;
|
||||
payload?: unknown;
|
||||
};
|
||||
|
||||
export type OneTalkDiagnosticsSink = (event: OneTalkDiagnostic) => void;
|
||||
|
||||
@@ -142,6 +142,7 @@ const sendFrame = (
|
||||
connectionType: frame.connectionType,
|
||||
frameType: frame.type,
|
||||
direction: "outbound",
|
||||
payload: frame.payload,
|
||||
...(code === undefined ? {} : { code }),
|
||||
});
|
||||
if (socket.readyState !== WEBSOCKET_OPEN) {
|
||||
@@ -240,6 +241,7 @@ export const createOneTalkWebSocketHandler =
|
||||
requestId: frame.requestId,
|
||||
connectionType: frame.connectionType,
|
||||
frameType: frame.type,
|
||||
payload: frame.payload,
|
||||
}),
|
||||
reportDecision: (frame, code) =>
|
||||
reportDiagnostic(options.onDiagnostic, {
|
||||
@@ -247,6 +249,7 @@ export const createOneTalkWebSocketHandler =
|
||||
requestId: frame.requestId,
|
||||
connectionType: frame.connectionType,
|
||||
frameType: frame.type,
|
||||
payload: frame.payload,
|
||||
code,
|
||||
}),
|
||||
};
|
||||
@@ -285,6 +288,7 @@ export const createOneTalkWebSocketHandler =
|
||||
connectionType: frame.connectionType,
|
||||
frameType: frame.type,
|
||||
direction: "inbound",
|
||||
payload: frame.payload,
|
||||
}),
|
||||
onUnauthenticatedFrame: (_socket, frame) => {
|
||||
sendError(frame, ONETALK_ERROR_CODES.authRequired);
|
||||
|
||||
@@ -1121,7 +1121,7 @@ test("publishes exact false profile updates only for newer committed profiles",
|
||||
}
|
||||
});
|
||||
|
||||
test("emits only safe lifecycle fields through the injected diagnostics sink", async () => {
|
||||
test("emits complete frame payloads through the injected diagnostics sink", async () => {
|
||||
const diagnostics: Record<string, unknown>[] = [];
|
||||
const app = createApp(testConfig, {
|
||||
database: createDatabaseStub(),
|
||||
@@ -1154,7 +1154,11 @@ test("emits only safe lifecycle fields through the injected diagnostics sink", a
|
||||
(event) => event.event === "ws_frame" && event.frameType === "ws.hello",
|
||||
),
|
||||
);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("binding-1"), false);
|
||||
assert.deepEqual(diagnostics[0].payload, {
|
||||
binding: "binding-1",
|
||||
requestedPermissions: ["read", "send"],
|
||||
});
|
||||
assert.equal(JSON.stringify(diagnostics).includes("binding-1"), true);
|
||||
assert.equal(JSON.stringify(diagnostics).includes("device-1"), false);
|
||||
} finally {
|
||||
await closeApp(app, [socket]);
|
||||
|
||||
@@ -1,10 +1,57 @@
|
||||
// 验证 WebSocket 通用诊断不含旧资料投递契约
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import test from "node:test";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
import { composeOneTalkDiagnosticLog } from "@trade-message-center/onetalk-contract";
|
||||
import * as diagnosticFileLog from "../src/websocket/diagnostic-file-log.ts";
|
||||
import type { OneTalkDiagnostic } from "../src/websocket/diagnostics.ts";
|
||||
|
||||
test("puts the WebSocket event and frame type in the diagnostic headline", () => {
|
||||
const helloLog = composeOneTalkDiagnosticLog("server", {
|
||||
event: "ws_frame",
|
||||
frameType: "ws.hello",
|
||||
});
|
||||
const closeLog = composeOneTalkDiagnosticLog("server", { event: "ws_close" });
|
||||
|
||||
assert.equal(helloLog.headline, "[server] event=ws_frame frameType=ws.hello");
|
||||
assert.equal(closeLog.headline, "[server] event=ws_close frameType=-");
|
||||
});
|
||||
|
||||
test("appends complete diagnostics to the build-hash log file", () => {
|
||||
const logsDirectory = mkdtempSync(join(tmpdir(), "onetalk-diagnostic-log-"));
|
||||
const payload = { binding: "binding-1", content: { text: "full message" } };
|
||||
try {
|
||||
const writeDiagnostic = diagnosticFileLog.createOneTalkDiagnosticFileLogger({
|
||||
logsDirectory,
|
||||
buildHash: "0123456789abcdef",
|
||||
});
|
||||
writeDiagnostic({ event: "ws_frame", frameType: "ws.hello", payload });
|
||||
writeDiagnostic({ event: "ws_close" });
|
||||
|
||||
assert.equal(
|
||||
readFileSync(join(logsDirectory, "0123456789abcdef.log"), "utf8"),
|
||||
[
|
||||
'[server] event=ws_frame frameType=ws.hello {"binding":"binding-1","content":{"text":"full message"}}',
|
||||
'[server] event=ws_close frameType=- {"event":"ws_close"}',
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
} finally {
|
||||
rmSync(logsDirectory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps file diagnostics disabled unless explicitly enabled", () => {
|
||||
assert.equal(typeof diagnosticFileLog.isOneTalkDiagnosticFileLogEnabled, "function");
|
||||
assert.equal(diagnosticFileLog.isOneTalkDiagnosticFileLogEnabled(undefined), false);
|
||||
assert.equal(diagnosticFileLog.isOneTalkDiagnosticFileLogEnabled("false"), false);
|
||||
assert.equal(diagnosticFileLog.isOneTalkDiagnosticFileLogEnabled("true"), true);
|
||||
});
|
||||
|
||||
test("does not accept removed profile delivery diagnostic fields", () => {
|
||||
const lifecycleDiagnostic: OneTalkDiagnostic = {
|
||||
event: "ws_close",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
export type OneTalkDiagnosticLogSource = "extension" | "server";
|
||||
|
||||
export type OneTalkDiagnosticLogEvent = {
|
||||
event: string;
|
||||
frameType?: string;
|
||||
status?: string;
|
||||
code?: string;
|
||||
payload?: unknown;
|
||||
};
|
||||
|
||||
export type OneTalkDiagnosticLog = {
|
||||
headline: string;
|
||||
payload: unknown;
|
||||
level: "info" | "warn";
|
||||
};
|
||||
|
||||
const payloadForLog = <T extends OneTalkDiagnosticLogEvent>(event: T): unknown => {
|
||||
if (!Object.hasOwn(event, "payload")) return event;
|
||||
return event.payload;
|
||||
};
|
||||
|
||||
/** 组合开发期 OneTalk 诊断日志,不裁剪显式携带的原始 payload。 */
|
||||
export const composeOneTalkDiagnosticLog = <T extends OneTalkDiagnosticLogEvent>(
|
||||
source: OneTalkDiagnosticLogSource,
|
||||
event: T,
|
||||
): OneTalkDiagnosticLog => ({
|
||||
headline: `[${source}] event=${event.event} frameType=${event.frameType ?? "-"}`,
|
||||
payload: payloadForLog(event),
|
||||
level: event.status === "failed" || event.code !== undefined ? "warn" : "info",
|
||||
});
|
||||
@@ -1,6 +1,12 @@
|
||||
// 暴露 OneTalk 跨包公共协议边界
|
||||
|
||||
export { isOneTalkAvatarUrl, isPlainRecord } from "./guards.ts";
|
||||
export { composeOneTalkDiagnosticLog } from "./diagnostic-log.ts";
|
||||
export type {
|
||||
OneTalkDiagnosticLog,
|
||||
OneTalkDiagnosticLogEvent,
|
||||
OneTalkDiagnosticLogSource,
|
||||
} from "./diagnostic-log.ts";
|
||||
export * from "./authorization.ts";
|
||||
export * from "./content.ts";
|
||||
export * from "./decoder.ts";
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { composeOneTalkDiagnosticLog } from "../src/diagnostic-log.ts";
|
||||
|
||||
test("composes a source-prefixed headline and preserves the complete payload", () => {
|
||||
const payload = { binding: "binding-1", content: { text: "full message" } };
|
||||
|
||||
assert.deepEqual(
|
||||
composeOneTalkDiagnosticLog("extension", {
|
||||
event: "frame",
|
||||
frameType: "ws.hello",
|
||||
payload,
|
||||
}),
|
||||
{
|
||||
headline: "[extension] event=frame frameType=ws.hello",
|
||||
payload,
|
||||
level: "info",
|
||||
},
|
||||
);
|
||||
assert.deepEqual(composeOneTalkDiagnosticLog("server", { event: "ws_close" }), {
|
||||
headline: "[server] event=ws_close frameType=-",
|
||||
payload: { event: "ws_close" },
|
||||
level: "info",
|
||||
});
|
||||
});
|
||||
|
||||
test("marks failed or coded diagnostics as warnings", () => {
|
||||
assert.equal(
|
||||
composeOneTalkDiagnosticLog("extension", {
|
||||
event: "frame",
|
||||
frameType: "ws.error",
|
||||
code: "binding_revoked",
|
||||
}).level,
|
||||
"warn",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user