import { afterEach, expect, it, vi } from "vitest"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { ImageVideoMcpRemote, imageVideoResponse } from "../src/image-video/mcp.js"; import type { ImageVideoRecord } from "../src/image-video/contracts.js"; import { mkdtemp, mkdir, readdir, readFile, stat } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; vi.mock("node:dns/promises", () => ({ lookup: () => Promise.resolve([{ address: "8.8.8.8", family: 4 }]) })); afterEach(() => vi.restoreAllMocks()); it("accepts the live reused-image receipt only when hash, type and size match the frozen reference", async () => { vi.spyOn(Client.prototype, "connect").mockResolvedValue(); vi.spyOn(Client.prototype, "listTools").mockResolvedValue({ tools: ["swads_whoami", "creative_upload_from_url", "creative_import_status", "video_h3_submit", "video_job_status"].map(name => ({ name, inputSchema: { type: "object", properties: Object.fromEntries(["tenant_id", "platform", "platforms", "media_kind", "review_status", "asset_role", "ratio", "prompt", "file_name", "content_type", "size_bytes", "content_sha256"].map(key => [key, {}])) } })) }); const file = { file_id: "file_1", filename: "i2v_test.jpeg", stored_path: "https://cdn.example/image.jpeg", object_key: "image.jpeg", asset_url: "https://cdn.example/image.jpeg", mime_type: "image/jpeg", size_bytes: 9, attachment_kind: "image", creative_factory_asset_id: "existing_asset", creative_factory_request_id: "request_1", asset_metadata: { content_sha256: "a".repeat(64) } }; let reusedFile: unknown = file; vi.spyOn(Client.prototype, "callTool").mockImplementation(request => Promise.resolve({ content: [], structuredContent: request.name === "swads_whoami" ? { access: { current_account: { tenant_id: "tenant", platform: "tiktok" } } } : { upload_id: "du-reuse-1", object_key: "image.jpeg", upload_url: "", public_url: "https://cdn.example/image.jpeg", method: "PUT", headers: {}, expires_in_seconds: 0, reused: true, reused_file: reusedFile } })); const remote = new ImageVideoMcpRemote({ token: "test" }); const record = { id: "i2v_test", identity: { tenantId: "tenant", platform: "tiktok" }, draft: { prompt: "waves" }, referenceType: "image/jpeg", referenceHash: "a".repeat(64) } as ImageVideoRecord; await expect(remote.initialize(record, Buffer.from("reference"))).resolves.toEqual({ reused: true, assetId: "existing_asset" }); for (const changed of [{ asset_metadata: { content_sha256: "b".repeat(64) } }, { mime_type: "image/png" }, { size_bytes: 10 }, { creative_factory_asset_id: "" }]) { reusedFile = { ...file, ...changed }; await expect(remote.initialize(record, Buffer.from("reference"))).rejects.toBeDefined(); } }); it("collects the registered job's storage_url MP4 and ignores JSON artifacts, rejecting foreign or conflicting receipts", async () => { vi.spyOn(Client.prototype, "connect").mockResolvedValue(); vi.spyOn(Client.prototype, "listTools").mockResolvedValue({ tools: ["swads_whoami", "creative_upload_from_url", "creative_import_status", "video_h3_submit", "video_job_status"].map(name => ({ name, inputSchema: { type: "object", properties: { job_id: {} } } })) }); const video = { artifact_id: "artifact_video", job_id: "job_1", org_id: "org", asset_id: null, version_id: null, artifact_kind: "video", content_type: "video/mp4", storage_url: "https://cdn.example/video.mp4", object_key: "video.mp4", json_payload: null, metadata: {}, created_at: "2026-09-15T00:00:00Z" }; let result: Record = { job_id: "job_1", status: "succeeded", artifacts: [video, { ...video, artifact_id: "artifact_json", artifact_kind: "metadata", content_type: "application/json", storage_url: null, object_key: null, json_payload: { provider: "h3" } }] }; vi.spyOn(Client.prototype, "callTool").mockImplementation(request => Promise.resolve({ content: [], structuredContent: request.name === "swads_whoami" ? { access: { current_account: { tenant_id: "tenant", platform: "tiktok" } } } : result })); const remote = new ImageVideoMcpRemote({ token: "test" }); const record = { jobId: "job_1" } as ImageVideoRecord; await expect(remote.status(record)).resolves.toEqual({ state: "succeeded", artifactUrl: "https://cdn.example/video.mp4" }); result = { job_id: "job_1", status: "succeeded", artifacts: [{ ...video, job_id: "job_other" }] }; await expect(remote.status(record)).rejects.toBeDefined(); result = { job_id: "job_1", status: "succeeded", artifacts: [{ ...video, url: "https://cdn.example/other.mp4" }] }; await expect(remote.status(record)).rejects.toBeDefined(); }); it("submits within the gateway key budget and deduplicates the same frozen request without merging different requests", async () => { vi.spyOn(Client.prototype, "connect").mockResolvedValue(); vi.spyOn(Client.prototype, "listTools").mockResolvedValue({ tools: ["swads_whoami", "creative_upload_from_url", "creative_import_status", "video_h3_submit", "video_job_status"].map(name => ({ name, inputSchema: { type: "object", properties: Object.fromEntries(["prompt", "title", "reference_asset_ids", "aspect_ratio", "duration_seconds", "resolution", "h3_mode", "idempotency_key", "skill_id"].map(key => [key, {}])) } })) }); const jobs = new Map(); vi.spyOn(Client.prototype, "callTool").mockImplementation(request => { if (request.name === "swads_whoami") return Promise.resolve({ content: [], structuredContent: { access: { current_account: { tenant_id: "tenant", platform: "tiktok" } } } }); const key = String(request.arguments?.idempotency_key); if (key.length > 87) return Promise.resolve({ content: [], structuredContent: { error_code: "invalid_arguments", detail: "idempotency_key must be at most 87 characters" } }); if (!jobs.has(key)) jobs.set(key, `job_${jobs.size + 1}`); return Promise.resolve({ content: [], structuredContent: { job_id: jobs.get(key) } }); }); const remote = new ImageVideoMcpRemote({ token: "test" }); const record = { id: "i2v_11111111-1111-1111-1111-111111111111", requestHash: "a".repeat(64), draft: { prompt: "waves", title: "test", durationSeconds: 10 }, assetId: "asset_1" } as ImageVideoRecord; await expect(remote.submit(record)).resolves.toEqual({ jobId: "job_1" }); await expect(remote.submit(record)).resolves.toEqual({ jobId: "job_1" }); await expect(remote.submit({ ...record, requestHash: "b".repeat(64) })).resolves.toEqual({ jobId: "job_2" }); await expect(remote.submit({ ...record, id: "i2v_22222222-2222-2222-2222-222222222222" })).resolves.toEqual({ jobId: "job_3" }); }); it("uploads with the frozen prompt and ratio required by the live begin/finish contract", async () => { vi.spyOn(Client.prototype, "connect").mockResolvedValue(); const properties = Object.fromEntries(["tenant_id", "platform", "platforms", "media_kind", "review_status", "asset_role", "ratio", "prompt", "file_name", "content_type", "size_bytes", "content_sha256", "upload_id", "object_key"].map(key => [key, {}])); vi.spyOn(Client.prototype, "listTools").mockResolvedValue({ tools: ["swads_whoami", "creative_upload_from_url", "creative_import_status", "video_h3_submit", "video_job_status"].map(name => ({ name, inputSchema: { type: "object", properties } })) }); vi.spyOn(Client.prototype, "callTool").mockImplementation(request => { const args = request.arguments; const result = request.name === "swads_whoami" ? { access: { current_account: { tenant_id: "tenant", platform: "tiktok" } } } : args?.ratio !== "9:16" || args.prompt !== "Subtle waves and breeze" ? { error_code: "invalid_arguments", detail: "push begin needs: ratio, prompt" } : args.file_name !== "i2v_test.jpeg" || args.content_type !== "image/jpeg" || args.size_bytes !== 9 ? { error_code: "invalid_arguments", detail: "push finish needs: file_name, content_type, size_bytes" } : args.upload_id ? { status: "finalizing" } : { upload_id: "upload_1", object_key: "image.jpeg", upload_url: "https://public.example/put?signature=private" }; return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(result) }] }); }); const remote = new ImageVideoMcpRemote({ token: "test" }); const record = { id: "i2v_test", identity: { tenantId: "tenant", platform: "tiktok" }, draft: { prompt: "Subtle waves and breeze" }, referenceType: "image/jpeg", referenceHash: "a".repeat(64) } as ImageVideoRecord; const upload = await remote.initialize(record, Buffer.from("reference")); if ("reused" in upload) throw new Error("Expected a new upload ticket"); expect(upload).toEqual({ uploadId: "upload_1", objectKey: "image.jpeg", uploadUrl: "https://public.example/put?signature=private" }); await expect(remote.finalize({ ...record, upload }, Buffer.from("reference"))).resolves.toBeUndefined(); }); it("recognizes error_code failures even when MCP isError is false without exposing remote details", () => { expect(() => imageVideoResponse({ isError: false, content: [{ type: "text", text: JSON.stringify({ error_code: "invalid_arguments", detail: "token=private https://private.example/?signature=secret" }) }] })).toThrow(expect.objectContaining({ code: "I2V_REMOTE_REJECTED", message: "SW Ads rejected the request (invalid_arguments)" })); }); it("retains an unparseable submission response privately before rejecting it, without a second submit", async () => { const root = await mkdtemp(join(tmpdir(), "i2v-receipt-")); const id = "i2v_11111111-1111-1111-1111-111111111111"; await mkdir(join(root, id), { mode: 0o700 }); vi.spyOn(Client.prototype, "connect").mockResolvedValue(); vi.spyOn(Client.prototype, "listTools").mockResolvedValue({ tools: ["swads_whoami", "creative_upload_from_url", "creative_import_status", "video_h3_submit", "video_job_status"].map(name => ({ name, inputSchema: { type: "object", properties: Object.fromEntries(["prompt", "title", "reference_asset_ids", "aspect_ratio", "duration_seconds", "resolution", "h3_mode", "idempotency_key", "skill_id"].map(key => [key, {}])) } })) }); let submissions = 0; const raw = { content: [{ type: "text" as const, text: '{"unexpected_job":{"id":"recoverable_job"},"private_url":"https://public.example/?signature=secret"}' }] }; vi.spyOn(Client.prototype, "callTool").mockImplementation(request => { if (request.name === "swads_whoami") return Promise.resolve({ structuredContent: { access: { current_account: { tenant_id: "tenant", platform: "tiktok" } } }, content: [] }); submissions++; return Promise.resolve(raw); }); const remote = new ImageVideoMcpRemote({ token: "private-token", receiptRoot: root }); const record = { id, revision: 4, requestHash: "a".repeat(64), draft: { prompt: "waves", title: "test", durationSeconds: 10 }, assetId: "asset_1" } as ImageVideoRecord; await expect(remote.submit(record)).rejects.toBeDefined(); const files = await readdir(join(root, id)); expect(files).toHaveLength(1); const file = join(root, id, files[0]!); expect(JSON.parse(await readFile(file, "utf8"))).toMatchObject({ id, revision: 4, requestHash: "a".repeat(64), operation: "submit", response: raw }); expect((await stat(file)).mode & 0o777).toBe(0o600); expect(await readFile(file, "utf8")).not.toContain("private-token"); expect(submissions).toBe(1); });