121 lines
3.6 KiB
TypeScript
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");
|
|
});
|
|
});
|