Files
ttstd-infinite-canvas/web/test/director-diagnostics.test.ts
T

187 lines
9.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { PREVIS_DIAGNOSTIC_CODES, previsDiagnosticObjectKind, formatPrevisDiagnosticCode, isPrevisDiagnosticCode, projectPrevisDiagnostic } from "../src/lib/canvas/previs/previs-diagnostics";
/** 任何一个都不允许出现在投影输出里。 */
const LEAK_PROBES = [
"https://cdn.example.com/actor.glb?token=abc123",
"http://localhost:3000/api/tasks?authorization=Bearer%20xyz",
"Bearer eyJhbGciOiJIUzI1NiJ9.payload.signature",
"authorization: Bearer secret-token",
"cookie=session=deadbeef; Path=/",
"api_key=sk-live-0000",
"Error: adopt failed\n at PrevisModel (previs-viewport.tsx:571:29)",
"演员 1 的镜头意图正文",
"/Users/someone/secret/path/scene.glb",
];
function serialize(value: unknown) {
return JSON.stringify(value ?? {});
}
describe("code 白名单", () => {
test("白名单内的 code 全部被接受且有稳定文案与级别", () => {
for (const code of PREVIS_DIAGNOSTIC_CODES) {
expect(isPrevisDiagnosticCode(code)).toBe(true);
const event = projectPrevisDiagnostic(code, {});
expect(event).not.toBeNull();
expect(event?.code).toBe(code);
expect(event?.message.length).toBeGreaterThan(0);
expect(["info", "warning", "error"]).toContain(event?.level ?? "");
}
});
test("未知 code 一律拒绝,不透传", () => {
const rejected = ["", "previs_viewport_render_failed", "PREVIS_UNKNOWN", "SOME_OTHER_CODE", "PREVIS_VIEWPORT_RENDER_FAILED ", null, undefined, 42, {}, []];
for (const code of rejected) {
expect(isPrevisDiagnosticCode(code)).toBe(false);
expect(projectPrevisDiagnostic(code, {})).toBeNull();
}
});
});
describe("字段白名单", () => {
test("未知字段不会出现在输出中", () => {
const event = projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", {
objectId: "obj-1",
sceneName: "演员 1 场景",
url: "https://cdn.example.com/a.glb",
stack: "Error: boom\n at x",
prompt: "镜头意图正文",
storageKey: "assets/secret.glb",
token: "sk-live-0000",
});
expect(event?.fields).toEqual({ objectId: "obj-1" });
expect(serialize(event)).not.toContain("cdn.example.com");
expect(serialize(event)).not.toContain("镜头意图正文");
expect(serialize(event)).not.toContain("sk-live-0000");
});
test("伪造的敏感输入无法通过任一字段进入输出", () => {
for (const probe of LEAK_PROBES) {
const event = projectPrevisDiagnostic("PREVIS_SAVE_FLUSH_FAILED", {
objectId: probe,
sceneId: probe,
objectKind: probe,
saveOutcome: probe,
attempt: probe,
revision: probe,
draftStored: probe,
userInitiated: probe,
});
expect(event).not.toBeNull();
// 所有字段都应被拒绝:probe 既不是 safe-id,也不在枚举内,也不是数值/布尔。
expect(event?.fields).toEqual({});
const text = serialize(event);
expect(text).not.toContain("http");
expect(text).not.toContain("token");
expect(text).not.toContain("cookie");
expect(text).not.toContain("Bearer");
expect(text).not.toContain("演员");
}
});
test("safe-id 只接受受限字符集", () => {
expect(projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { objectId: "obj-1_a.b:c" })?.fields.objectId).toBe("obj-1_a.b:c");
for (const bad of ["obj 1", "obj/1", "obj?1", "obj#1", "a".repeat(97), "", " ", "obj\n1", "obj%201"]) {
expect(projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { objectId: bad })?.fields.objectId).toBeUndefined();
}
});
test("枚举字段只接受白名单值", () => {
expect(projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { objectKind: "actor" })?.fields.objectKind).toBe("actor");
expect(projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { objectKind: "spaceship" })?.fields.objectKind).toBeUndefined();
expect(projectPrevisDiagnostic("PREVIS_CLOSE_BLOCKED", { saveOutcome: "stay" })?.fields.saveOutcome).toBe("stay");
expect(projectPrevisDiagnostic("PREVIS_CLOSE_BLOCKED", { saveOutcome: "explode" })?.fields.saveOutcome).toBeUndefined();
});
test("数值字段有界且拒绝非有限值", () => {
expect(projectPrevisDiagnostic("PREVIS_MODEL_LOAD_RETRY", { attempt: 3 })?.fields.attempt).toBe(3);
expect(projectPrevisDiagnostic("PREVIS_MODEL_LOAD_RETRY", { attempt: 2.6 })?.fields.attempt).toBe(3);
for (const bad of [-1, 1000, Number.NaN, Number.POSITIVE_INFINITY, "3", null]) {
expect(projectPrevisDiagnostic("PREVIS_MODEL_LOAD_RETRY", { attempt: bad })?.fields.attempt).toBeUndefined();
}
expect(projectPrevisDiagnostic("PREVIS_SAVE_FLUSH_FAILED", { revision: 0 })?.fields.revision).toBe(0);
expect(projectPrevisDiagnostic("PREVIS_SAVE_FLUSH_FAILED", { revision: -1 })?.fields.revision).toBeUndefined();
});
test("布尔字段只接受真布尔", () => {
expect(projectPrevisDiagnostic("PREVIS_SAVE_RETRY_FAILED", { draftStored: false })?.fields.draftStored).toBe(false);
expect(projectPrevisDiagnostic("PREVIS_SAVE_RETRY_FAILED", { draftStored: true })?.fields.draftStored).toBe(true);
for (const bad of ["true", 1, 0, null, {}]) {
expect(projectPrevisDiagnostic("PREVIS_SAVE_RETRY_FAILED", { draftStored: bad })?.fields.draftStored).toBeUndefined();
}
});
test("非对象 fields 不抛错且产出空字段", () => {
for (const fields of [null, undefined, 42, "objectId=1", [1, 2, 3]]) {
const event = projectPrevisDiagnostic("PREVIS_VIEWPORT_CONTEXT_LOST", fields);
expect(event?.fields).toEqual({});
}
});
});
describe("稳定码格式化", () => {
test("只拼接安全枚举与数值", () => {
const event = projectPrevisDiagnostic("PREVIS_SAVE_RETRY_FAILED", { sceneId: "scene-1", revision: 4, draftStored: true, userInitiated: true });
expect(event).not.toBeNull();
if (!event) return;
const formatted = formatPrevisDiagnosticCode(event);
expect(formatted).toBe("PREVIS_SAVE_RETRY_FAILED scene=scene-1 revision=4 draft=1 user=1");
// scene/object 后缀只可能是 safe-id 投影后的值。
expect(formatted.startsWith("PREVIS_")).toBe(true);
});
test("scene 与 object 后缀都进入 code,顺序确定", () => {
const event = projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { sceneId: "scene-1", objectId: "obj-9", objectKind: "model", attempt: 2 });
expect(event).not.toBeNull();
if (!event) return;
expect(formatPrevisDiagnosticCode(event)).toBe("PREVIS_MODEL_LOAD_FAILED scene=scene-1 object=obj-9 kind=model attempt=2");
});
test("不同 objectId / sceneId 产出不同签名", () => {
const a = projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { objectId: "obj-1" });
const b = projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { objectId: "obj-2" });
const c = projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { sceneId: "scene-1" });
const d = projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { sceneId: "scene-2" });
expect(a && b && c && d).toBeTruthy();
if (!a || !b || !c || !d) return;
const signatures = [a, b, c, d].map(formatPrevisDiagnosticCode);
expect(new Set(signatures).size).toBe(4);
});
test("非法 id 不进入 code 后缀", () => {
for (const bad of ["https://cdn.example.com/a.glb?token=abc", "obj 1", "obj/1", "a".repeat(97)]) {
const event = projectPrevisDiagnostic("PREVIS_MODEL_LOAD_FAILED", { objectId: bad, sceneId: bad });
expect(event).not.toBeNull();
if (!event) continue;
const formatted = formatPrevisDiagnosticCode(event);
expect(formatted).toBe("PREVIS_MODEL_LOAD_FAILED");
expect(formatted).not.toContain("scene=");
expect(formatted).not.toContain("object=");
expect(formatted).not.toContain("cdn.example.com");
}
});
test("无字段时只有 code 本身", () => {
const event = projectPrevisDiagnostic("PREVIS_VIEWPORT_RENDER_FAILED", {});
expect(event).not.toBeNull();
if (!event) return;
expect(formatPrevisDiagnosticCode(event)).toBe("PREVIS_VIEWPORT_RENDER_FAILED");
});
});
describe("对象种类推导", () => {
test("按形态映射,不读取名称或地址", () => {
expect(previsDiagnosticObjectKind({ kind: "actor" })).toBe("actor");
expect(previsDiagnosticObjectKind({ kind: "model" })).toBe("model");
expect(previsDiagnosticObjectKind({ kind: "billboard" })).toBe("billboard");
expect(previsDiagnosticObjectKind({ primitive: "box" })).toBe("primitive");
expect(previsDiagnosticObjectKind({})).toBe("unknown");
expect(previsDiagnosticObjectKind(null)).toBe("unknown");
expect(previsDiagnosticObjectKind(undefined)).toBe("unknown");
});
});