Files
ttstd-infinite-canvas/web/test/director-diagnostics-recorder.test.ts
T
echoD886 c2766544d7 fix(canvas): 3D 导演台 - 完成 Issue #305 P0 稳定性治理 (#324)
* fix(canvas): 3D 导演台 - 修复状态回弹与保存恢复生命周期

* style(canvas): 3D 导演台 - 对齐 CI 格式门禁
2026-08-27 10:32:45 +08:00

152 lines
7.5 KiB
TypeScript

import { beforeEach, describe, expect, test } from "bun:test";
import { recordDirectorDiagnostic, resetDirectorDiagnosticDedupe } from "../src/lib/canvas/director/director-diagnostics-recorder";
import { getClientDiagnosticEvents } from "../src/services/diagnostics/client-diagnostics";
/** 只看导演台自己的稳定码事件,避免与其它测试写入的诊断流互相干扰。 */
function directorEvents() {
return getClientDiagnosticEvents().filter((event) => (event.code || "").startsWith("DIRECTOR_"));
}
function newestDirectorEvent() {
return directorEvents().at(-1);
}
const LEAK_PROBES = [
"https://cdn.example.com/actor.glb?token=abc123#frag",
"authorization: Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig",
"cookie=session=deadbeef; Path=/",
"api_key=sk-live-0000",
"Error: boom\n at DirectorModel (director-viewport.tsx:571:29)",
"演员 1 的镜头意图正文",
];
beforeEach(() => {
resetDirectorDiagnosticDedupe();
});
describe("记录进统一诊断缓冲区", () => {
test("写入的事件带稳定码、固定文案与 runtime 分类", () => {
const before = directorEvents().length;
expect(recordDirectorDiagnostic("DIRECTOR_VIEWPORT_RENDER_FAILED")).toBe(true);
expect(directorEvents().length).toBe(before + 1);
const event = newestDirectorEvent();
expect(event?.code).toBe("DIRECTOR_VIEWPORT_RENDER_FAILED");
expect(event?.level).toBe("error");
expect(event?.category).toBe("runtime");
expect(event?.message).toBe("导演台 3D 视口渲染失败");
// 绝不落 stack。
expect(event?.stack || "").toBe("");
});
test("安全字段以枚举后缀进入 code,便于检索", () => {
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectId: "obj-1", objectKind: "model", attempt: 2 })).toBe(true);
const event = newestDirectorEvent();
expect(event?.code).toBe("DIRECTOR_MODEL_LOAD_FAILED object=obj-1 kind=model attempt=2");
});
test("sceneId 只进 code 后缀,不冒充 canvasId", () => {
expect(recordDirectorDiagnostic("DIRECTOR_SAVE_FLUSH_FAILED", { sceneId: "repro-scene-1", revision: 3 })).toBe(true);
const event = newestDirectorEvent();
// DirectorScene.id 不是 canvas id,绝不写入既有后端契约字段。
expect(event?.canvasId).toBeUndefined();
expect(event?.code).toBe("DIRECTOR_SAVE_FLUSH_FAILED scene=repro-scene-1 revision=3");
});
test("三个保存相关码都能写入且级别正确", () => {
recordDirectorDiagnostic("DIRECTOR_SAVE_RETRY_RECOVERED", { revision: 1 });
expect(newestDirectorEvent()?.level).toBe("info");
recordDirectorDiagnostic("DIRECTOR_SAVE_DRAFT_UNAVAILABLE", { revision: 1 });
expect(newestDirectorEvent()?.level).toBe("error");
recordDirectorDiagnostic("DIRECTOR_CLOSE_BLOCKED", { saveOutcome: "stay" });
expect(newestDirectorEvent()?.level).toBe("warning");
});
});
describe("去重与噪声控制", () => {
test("同一 code+字段在窗口内只记一次", () => {
const before = directorEvents().length;
expect(recordDirectorDiagnostic("DIRECTOR_VIEWPORT_CONTEXT_LOST")).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_VIEWPORT_CONTEXT_LOST")).toBe(false);
expect(recordDirectorDiagnostic("DIRECTOR_VIEWPORT_CONTEXT_LOST")).toBe(false);
expect(directorEvents().length).toBe(before + 1);
});
test("字段不同视为不同事件,不会被误去重", () => {
const before = directorEvents().length;
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectKind: "model", attempt: 1 })).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectKind: "model", attempt: 2 })).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectKind: "actor", attempt: 1 })).toBe(true);
expect(directorEvents().length).toBe(before + 3);
});
test("不同 objectId 在窗口内不互相吞掉", () => {
const before = directorEvents().length;
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectId: "obj-1", objectKind: "model" })).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectId: "obj-2", objectKind: "model" })).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectId: "obj-3", objectKind: "model" })).toBe(true);
// 同一 objectId 重复仍要被去重。
expect(recordDirectorDiagnostic("DIRECTOR_MODEL_LOAD_FAILED", { objectId: "obj-1", objectKind: "model" })).toBe(false);
expect(directorEvents().length).toBe(before + 3);
});
test("不同 sceneId 在窗口内不互相吞掉", () => {
const before = directorEvents().length;
expect(recordDirectorDiagnostic("DIRECTOR_SAVE_FLUSH_FAILED", { sceneId: "scene-1", revision: 1 })).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_SAVE_FLUSH_FAILED", { sceneId: "scene-2", revision: 1 })).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_SAVE_FLUSH_FAILED", { sceneId: "scene-1", revision: 1 })).toBe(false);
expect(directorEvents().length).toBe(before + 2);
});
test("所有写入的 code 都以稳定 DIRECTOR_ 前缀开头", () => {
resetDirectorDiagnosticDedupe();
recordDirectorDiagnostic("DIRECTOR_CLOSE_BLOCKED", { sceneId: "scene-1", saveOutcome: "stay" });
for (const event of directorEvents()) {
expect((event.code || "").startsWith("DIRECTOR_")).toBe(true);
}
});
test("重置去重状态后可再次记录同一事件", () => {
expect(recordDirectorDiagnostic("DIRECTOR_VIEWPORT_CONTEXT_RESTORED")).toBe(true);
expect(recordDirectorDiagnostic("DIRECTOR_VIEWPORT_CONTEXT_RESTORED")).toBe(false);
resetDirectorDiagnosticDedupe();
expect(recordDirectorDiagnostic("DIRECTOR_VIEWPORT_CONTEXT_RESTORED")).toBe(true);
});
});
describe("不泄漏敏感内容", () => {
test("伪造的敏感字段不会进入缓冲区任何字段", () => {
for (const probe of LEAK_PROBES) {
resetDirectorDiagnosticDedupe();
recordDirectorDiagnostic("DIRECTOR_MODEL_ADOPT_FAILED", {
objectId: probe,
sceneId: probe,
// @ts-expect-error 故意传入非法枚举,验证运行时白名单而非仅靠类型。
objectKind: probe,
// @ts-expect-error 故意传入非法数值类型。
attempt: probe,
});
const event = newestDirectorEvent();
const serialized = JSON.stringify(event);
expect(serialized).not.toContain("cdn.example.com");
expect(serialized).not.toContain("token=abc123");
expect(serialized).not.toContain("sk-live-0000");
expect(serialized).not.toContain("deadbeef");
expect(serialized).not.toContain("Bearer");
expect(serialized).not.toContain("演员");
expect(serialized).not.toContain("director-viewport.tsx");
expect(event?.canvasId).toBeUndefined();
expect(event?.code).toBe("DIRECTOR_MODEL_ADOPT_FAILED");
}
});
test("未知 code 既不写入也不抛错", () => {
const before = directorEvents().length;
// @ts-expect-error 故意传入白名单外的 code。
expect(recordDirectorDiagnostic("DIRECTOR_NOT_A_REAL_CODE", { objectId: "obj-1" })).toBe(false);
expect(directorEvents().length).toBe(before);
});
});