ai-chatbot/tests/routes/canvas-agent-operations.test.ts

197 lines
5.3 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
canvasAgentOperationRequestSchema,
parseCanvasAgentOperationRequestBody,
} from "@/lib/canvas/agent-operation-request";
const projectId = "11111111-1111-4111-8111-111111111111";
function buildMediaItem(index = 1) {
return {
mediaItemId: `media-${index}`,
type: "image" as const,
url: `https://example.com/generated-${index}.png`,
assetId: "22222222-2222-4222-8222-222222222222",
width: 1024,
height: 768,
title: `generated-${index}`,
prompt: "make the product cleaner",
sourceNodeIds: ["image-1"],
metadata: {
provider: "test",
},
};
}
function buildPlan(overrides: Record<string, unknown>) {
return {
version: 1,
operationId: "message-1:canvas-agent:test",
projectId,
baseCanvasRevision: "2026-06-22T00:00:00.000Z",
baseCanvasUpdatedAt: "2026-06-22T00:00:00.000Z",
...overrides,
};
}
function parsePlan(overrides: Record<string, unknown>) {
return parseCanvasAgentOperationRequestBody({
operationPlan: buildPlan(overrides),
});
}
function issuePathsFor(overrides: Record<string, unknown>) {
const result = canvasAgentOperationRequestSchema.safeParse({
operationPlan: buildPlan(overrides),
});
expect(result.success).toBe(false);
return result.success
? []
: result.error.issues.map((issue) => issue.path.join("."));
}
test.describe("canvas agent operation request schema", () => {
test("accepts slot fill requests with batch media and revision guards", () => {
const body = parsePlan({
operation: "fill_slot",
targetSlotId: "slot-1",
placement: "viewport-center",
preserveOriginal: true,
mediaItems: [buildMediaItem(1), buildMediaItem(2)],
});
expect(body.operationPlan).toMatchObject({
operation: "fill_slot",
targetSlotId: "slot-1",
baseCanvasRevision: "2026-06-22T00:00:00.000Z",
mediaItems: [
{ mediaItemId: "media-1", url: "https://example.com/generated-1.png" },
{ mediaItemId: "media-2", url: "https://example.com/generated-2.png" },
],
});
});
test("accepts append revision requests with legacy media payloads", () => {
const body = parsePlan({
operation: "append_revision",
sourceNodeIds: ["image-1"],
targetNodeIds: ["image-1"],
media: {
type: "image",
url: "https://example.com/legacy.png",
width: 1200,
height: 900,
title: "legacy-result",
},
});
expect(body.operationPlan).toMatchObject({
operation: "append_revision",
media: {
url: "https://example.com/legacy.png",
},
});
});
test("accepts slot-only, annotation-only, and text-only operations", () => {
expect(
parsePlan({
operation: "create_agent_slot",
targetPosition: { x: 120, y: 160 },
slot: {
slotType: "image",
label: "主图结果槽",
aspectRatio: "4:3",
fillPolicy: "replace-empty",
acceptedSources: ["text", "image", "annotation"],
},
}).operationPlan.operation
).toBe("create_agent_slot");
expect(
parsePlan({
operation: "add_annotation",
targetNodeIds: ["image-1"],
annotations: [
{
id: "annotation-1",
sourceNodeId: "image-1",
type: "box",
text: "tighten crop",
bounds: { x: 0.1, y: 0.2, width: 0.3, height: 0.4 },
color: "#ff3366",
},
],
}).operationPlan.operation
).toBe("add_annotation");
expect(
parsePlan({
operation: "update_text",
targetNodeIds: ["text-1"],
textContent: "New canvas title",
}).operationPlan.operation
).toBe("update_text");
});
test("caps batch media at twenty items", () => {
const accepted = parsePlan({
operation: "insert_beside",
sourceNodeIds: ["image-1"],
placementStrategy: "grid",
mediaItems: Array.from({ length: 20 }, (_, index) => buildMediaItem(index + 1)),
});
expect(accepted.operationPlan.mediaItems).toHaveLength(20);
expect(
issuePathsFor({
operation: "insert_beside",
sourceNodeIds: ["image-1"],
mediaItems: Array.from({ length: 21 }, (_, index) => buildMediaItem(index + 1)),
})
).toContain("operationPlan.mediaItems");
});
test("rejects media operations without generated media", () => {
expect(
issuePathsFor({
operation: "insert_beside",
sourceNodeIds: ["image-1"],
placement: "right",
})
).toContain("operationPlan.mediaItems");
});
test("rejects annotation and text operations without required content", () => {
expect(
issuePathsFor({
operation: "add_annotation",
targetNodeIds: ["image-1"],
})
).toContain("operationPlan.annotations");
expect(
issuePathsFor({
operation: "update_text",
targetNodeIds: ["text-1"],
})
).toContain("operationPlan.textContent");
});
test("rejects invalid media urls", () => {
expect(
issuePathsFor({
operation: "replace_node_media",
targetNodeIds: ["image-1"],
mediaItems: [
{
...buildMediaItem(),
url: "not-a-url",
},
],
})
).toContain("operationPlan.mediaItems.0.url");
});
});