ai-chatbot/tests/routes/openclaw-3d-artifacts.test.ts

121 lines
3.6 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
buildOpenClawArtifactProxyUrl,
extractOpenClawArtifacts,
isOpenClawInteractive3DArtifact,
normalizeOpenClawArtifacts,
} from "@/lib/openclaw-chat/artifacts";
import { normalizeClawdTaskMetadata } from "@/lib/openclaw-chat/task-state";
const modelFile = {
type: "file",
filename: "wood-demo-interactive.glb",
display_name: "可旋转3D拆件模型",
artifact_role: "interactive_3d",
display_mode: "interactive",
mime_type: "model/gltf-binary",
download_url: "/files/wood-demo-interactive.glb",
viewer: {
type: "model_3d",
format: "glb",
camera_controls: true,
auto_rotate: false,
background: "#F7F9FC",
fallback_artifact_role: "cad_preview",
},
};
const previewFile = {
type: "image",
filename: "wood-demo-preview.png",
artifact_role: "cad_preview",
display_mode: "inline",
mime_type: "image/png",
download_url: "https://files.example.com/wood-demo-preview.png",
};
test.describe("OpenClaw 3D artifacts", () => {
test("normalizes the documented GLB viewer contract", () => {
const artifacts = normalizeOpenClawArtifacts([modelFile, previewFile], {
baseUrl: "https://claw.example.com",
});
expect(artifacts).toHaveLength(2);
expect(artifacts[0]).toMatchObject({
artifactRole: "interactive_3d",
displayMode: "interactive",
displayName: "可旋转3D拆件模型",
downloadUrl: "https://claw.example.com/files/wood-demo-interactive.glb",
mimeType: "model/gltf-binary",
viewer: {
type: "model_3d",
format: "glb",
cameraControls: true,
autoRotate: false,
background: "#F7F9FC",
fallbackArtifactRole: "cad_preview",
},
});
expect(isOpenClawInteractive3DArtifact(artifacts[0])).toBe(true);
expect(isOpenClawInteractive3DArtifact(artifacts[1])).toBe(false);
});
test("extracts files from durable task results without duplicating them", () => {
const artifacts = extractOpenClawArtifacts(
{
output: { files: [modelFile, previewFile] },
task: { result: { files: [modelFile] } },
},
{ baseUrl: "https://claw.example.com" }
);
expect(artifacts).toHaveLength(2);
expect(artifacts.map((artifact) => artifact.artifactRole)).toEqual([
"interactive_3d",
"cad_preview",
]);
});
test("rejects private schemes and keeps the original URL out of proxy links", () => {
expect(
normalizeOpenClawArtifacts([
{ ...modelFile, download_url: "file:///tmp/private.glb" },
{ ...modelFile, download_url: "javascript:alert(1)" },
])
).toEqual([]);
const proxyUrl = buildOpenClawArtifactProxyUrl({
chatId: "11111111-1111-4111-8111-111111111111",
messageId: "22222222-2222-4222-8222-222222222222",
artifactIndex: 0,
download: true,
});
expect(proxyUrl).toContain("download=1");
expect(proxyUrl).not.toContain("files.example.com");
});
test("restores persisted artifacts with Claw task metadata", () => {
const task = normalizeClawdTaskMetadata({
composerMode: "clawd",
source: "openclaw",
taskKind: "clawd_chat",
status: "completed",
userMessageId: "user-message-id",
assistantMessageId: "assistant-message-id",
processingHint: "",
progressEvents: [],
artifacts: [
{
...modelFile,
download_url: "https://files.example.com/model.glb",
},
],
createdAt: 100,
updatedAt: 200,
});
expect(task?.artifacts).toHaveLength(1);
expect(task?.artifacts?.[0].viewer?.format).toBe("glb");
});
});