import { executeImageOperation, imageOperationSchema, ImageOperationUncertainError, } from "@/lib/video-canvas/image-operation"; import { mergeStoryTaskResult, requireStoryPromptResult, } from "@/lib/video-canvas/story-task-attachment"; import { test, expect } from "@playwright/test"; import { canvasTaskRequestSchema, reconcileCanvasTasks, requireAnalysisResult, canCancelCanvasTask, type CanvasGenerationTask, } from "@/lib/video-canvas/task-contract"; import { chooseStoryVideoSettings } from "@/lib/video-canvas/model-matching"; type TaskNode = { id: string; draftValues?: Record; [key: string]: any }; const node: TaskNode = { id: "n1", title: "镜头", kind: "media", inputType: "image", x: 0, y: 0, width: 300, height: 200, draftValues: { rows: [{ sceneDescription: "旧内容" }] }, }; const request = { requestId: "11111111-1111-4111-8111-111111111111", projectId: "22222222-2222-4222-8222-222222222222", kind: "image" as const, node, params: { prompt: "cup" }, edges: [], }; const task: CanvasGenerationTask = { id: "task1", requestId: request.requestId, status: "succeeded", createdAt: "2026-09-17T01:01:00Z", request: request as any, result: { images: [ { originalUrl: "https://example.com/1.png" }, { originalUrl: "https://example.com/2.png" }, ], }, }; test("restores accepted tasks missing from a pre-submit canvas snapshot, including every image", () => { const result = reconcileCanvasTasks([], [task], "2026-09-17T01:00:00Z"); expect(result.map((n) => n.id)).toEqual(["n1", "n1-result-task1-1"]); expect(result[0].draftValues?.url).toHaveLength(2); expect(reconcileCanvasTasks(result, [task], "2026-09-17T01:00:00Z")).toBe(result); }); test("does not resurrect nodes removed after task creation or hidden on the server", () => { expect(reconcileCanvasTasks([], [task], "2026-09-17T01:02:00Z")).toEqual([]); expect( reconcileCanvasTasks( [], [{ ...task, request: { ...request, node: node as any, hiddenNodeIds: ["n1"] } }], "2026-09-17T01:00:00Z", ), ).toEqual([]); }); test("late result from an older request cannot overwrite a new generation on the same node", () => { const current = { ...node, draftValues: { taskRequestId: "new-request", mediaUrl: "new-result" }, }; expect(reconcileCanvasTasks([current], [task], "2026-09-17T01:00:00Z")[0]).toBe( current, ); }); test("task failure retains the previous script instead of marking an empty result successful", () => { const result = reconcileCanvasTasks( [node], [{ ...task, status: "failed", error: "模型错误" }], "2026-09-17T01:00:00Z", )[0]; expect(result.draftValues).toMatchObject({ rows: [{ sceneDescription: "旧内容" }], generationStatus: "failed", generationError: "模型错误", }); }); test("queue and running states show actual state without invented progress", () => { const pending = reconcileCanvasTasks( [node], [{ ...task, status: "pending" }], "2026-09-17T01:00:00Z", ); expect(pending[0].draftValues).toMatchObject({ generationProgress: 0, pendingLabel: "排队中" }); expect( reconcileCanvasTasks( pending, [{ ...task, status: "running" }], "2026-09-17T01:00:00Z", )[0].draftValues?.pendingLabel, ).toBe("生成中"); }); test("interrupted tasks accept a late real result while cancelled queued tasks stay visibly cancelled", () => { const pending = reconcileCanvasTasks( [node], [{ ...task, status: "interrupted", error: "待核实" }], "2026-09-17T01:00:00Z", ); expect( reconcileCanvasTasks(pending, [task], "2026-09-17T01:00:00Z")[0].draftValues ?.generationStatus, ).toBe("complete"); expect( reconcileCanvasTasks( [node], [{ ...task, status: "cancelled" }], "2026-09-17T01:00:00Z", )[0].subtitle, ).toBe("已取消"); expect(["pending", "running", "succeeded", "interrupted"].map(canCancelCanvasTask)).toEqual([ true, false, false, false, ]); }); test("analysis rejects success envelopes without meaningful output or script rows", () => { for (const data of [ undefined, {}, { analysisRows: [] }, { analysisRows: [{}], raw: "some summary" }, ]) expect(() => requireAnalysisResult(data, "story-script")).toThrow(); expect(() => requireAnalysisResult({ raw: " " }, "agent-plan")).toThrow(); expect( requireAnalysisResult({ analysisRows: [{ sceneDescription: "杯柄特写" }] }, "story-script"), ).toBeTruthy(); }); test("task snapshots require stable project/request identities and bounded geometry", () => { expect(canvasTaskRequestSchema.safeParse(request).success).toBe(true); for (const patch of [ { requestId: "bad" }, { projectId: "bad" }, { node: { ...node, width: Infinity } }, { node: { ...node, id: "" } }, ]) expect(canvasTaskRequestSchema.safeParse({ ...request, ...patch }).success).toBe(false); }); const model = { id: "video-model", modelId: "wan2.6-i2v-flash", providerType: "qianwen-video", capabilities: { kind: "video", supportedModes: ["first-frame"], durationOptions: [5, 10], supportedRatios: ["adaptive"], supportedResolutions: ["720p"], supportGenerateAudio: true, }, }; test("story batches match the actual input mode and discrete provider parameters", () => { expect(chooseStoryVideoSettings([model], true, 7)).toMatchObject({ mode: "first-frame", duration: 5, ratio: "adaptive", resolution: "720p", generateAudio: true, }); expect(() => chooseStoryVideoSettings([model], false, 5)).toThrow("文生视频"); const text = { ...model, id: "text-model", providerType: "vip98k", modelId: "veo", capabilities: { ...model.capabilities, supportedModes: ["text-to-video"] }, }; expect(chooseStoryVideoSettings([model, text], false, 9, model.id)).toMatchObject({ mode: "text-to-video", model: "text-model", duration: 10, }); }); test("unknown model specifications cannot silently invent 720p or five seconds", () => { expect(() => chooseStoryVideoSettings( [{ ...model, capabilities: { kind: "video", supportedModes: ["first-frame"] } }], true, 5, ), ).toThrow("规格"); }); test("refresh recovers the newest accepted request when the saved node still points to an older result", () => { const newer = { ...task, id: "task2", requestId: "new-request", createdAt: "2026-09-17T01:03:00Z", result: { images: [{ originalUrl: "https://example.com/new.png" }] }, }; const old = { ...node, draftValues: { taskRequestId: task.requestId, mediaUrl: "old" } }; const result = reconcileCanvasTasks([old], [task, newer], "2026-09-17T01:02:00Z"); expect(result[0].draftValues?.mediaUrl).toBe("https://example.com/new.png"); expect(result[0].draftValues?.pendingLabel).toBe(""); expect( reconcileCanvasTasks([], [task, newer], "2026-09-17T01:00:00Z")[0].draftValues ?.taskRequestId, ).toBe("new-request"); }); test("grid results preserve the grid action contract", () => { const grid = { ...node, draftValues: { action: "IMAGE_GENERATE_NINE_GRID" } }; expect( reconcileCanvasTasks([grid], [task], "2026-09-17T01:00:00Z")[0].draftValues?.action, ).toBe("IMAGE_GENERATE_NINE_GRID"); }); test("a rerun retains distinct extra results without reusing the previous request's image", () => { const first = reconcileCanvasTasks([node], [task], "2026-09-17T01:00:00Z"); const newer = { ...task, id: "task2", requestId: "next", createdAt: "2026-09-17T01:03:00Z", result: { images: [{ originalUrl: "new1" }, { originalUrl: "new2" }] }, }; const next = reconcileCanvasTasks(first, [task, newer], "2026-09-17T01:02:00Z"); expect(next.find((n) => n.id === "n1-result-task2-1")?.draftValues?.mediaUrl).toBe("new2"); expect(reconcileCanvasTasks(next, [task, newer], "2026-09-17T01:02:00Z")).toBe(next); }); test("image postprocessing validates provider parameters before dispatch", () => { expect( imageOperationSchema.safeParse({ operation: "angle-edit", imageUrl: "https://example.com/a.png", horizontalAngle: 180, verticalAngle: -30, zoom: 5, }).success, ).toBe(true); for (const value of [ { operation: "shell", imageUrl: "https://example.com/a.png" }, { operation: "enhance", imageUrl: "file:///etc/passwd" }, { operation: "angle-edit", imageUrl: "https://example.com/a.png" }, { operation: "enhance", imageUrl: "https://example.com/a.png", scaleFactor: 100 }, ]) expect(imageOperationSchema.safeParse(value).success).toBe(false); }); test("an accepted enhance task is polled without repeating POST and retains its upstream identity", async () => { let submits = 0, polls = 0; const checkpoints: any[] = []; const handler = async () => Response.json({ success: true, original_url: "https://example.com/result.png" }); const result = await executeImageOperation( { operation: "enhance", imageUrl: "https://example.com/source.png", scaleFactor: 4 }, "owner", { enhance: async (req, user) => { submits++; expect(user).toBe("owner"); expect(await req.json()).toMatchObject({ scaleFactor: 4 }); return Response.json( { success: true, processing: true, taskId: "upstream-1" }, { status: 202 }, ); }, extend: handler, removeBackground: handler, angleEdit: handler, pollEnhance: async (req, user) => { polls++; expect(user).toBe("owner"); expect(new URL(req.url).searchParams.get("taskId")).toBe("upstream-1"); return polls < 2 ? Response.json({ processing: true }, { status: 202 }) : handler(); }, }, { wait: async () => {}, checkpoint: async (value) => { checkpoints.push(value); }, }, ); expect(submits).toBe(1); expect(polls).toBe(2); expect(checkpoints[0].upstreamTask.taskId).toBe("upstream-1"); expect(result.images[0].originalUrl).toBe("https://example.com/result.png"); }); test("a pending or disconnected upstream query is reported as uncertain rather than submitted again", async () => { const handler = async () => Response.json({ processing: true, taskId: "upstream-1" }, { status: 202 }); for (const pollEnhance of [ handler, async () => { throw Error("connection lost"); }, async () => Response.json({ error: "upstream temporarily unavailable" }, { status: 502 }), ]) { await expect( executeImageOperation( { operation: "enhance", imageUrl: "https://example.com/source.png" }, "owner", { enhance: handler, extend: handler, removeBackground: handler, angleEdit: handler, pollEnhance, }, { wait: async () => {}, maxPolls: 2 }, ), ).rejects.toBeInstanceOf(ImageOperationUncertainError); } }); test("angle operation sends normalized numeric fields and accepts the dedicated response shape", async () => { const handler = async (req: Request) => { expect(await req.json()).toEqual({ image_url: "https://example.com/source.png", horizontal_angle: 180, vertical_angle: 45, zoom: 5, }); return Response.json({ success: true, image_url: "https://example.com/angle.png" }); }; const result = await executeImageOperation( { operation: "angle-edit", imageUrl: "https://example.com/source.png", horizontalAngle: 180, verticalAngle: 45, zoom: 5, }, "owner", { enhance: handler, extend: handler, removeBackground: handler, angleEdit: handler, pollEnhance: handler, }, ); expect(result.images[0].originalUrl).toContain("angle.png"); expect( reconcileCanvasTasks( [node], [{ ...task, request: { ...task.request, kind: "image-operation" }, result }], task.createdAt, )[0].inputType, ).toBe("image"); }); test("postprocessing preserves upstream failures and rejects an empty success", async () => { for (const payload of [{ success: false, error: "积分不足" }, { success: true }]) { const handler = async () => Response.json(payload); await expect( executeImageOperation( { operation: "remove-bg", imageUrl: "https://example.com/a.png" }, "owner", { enhance: handler, extend: handler, removeBackground: handler, angleEdit: handler, pollEnhance: handler, }, ), ).rejects.toThrow(); } }); const asset = { id: "asset-1", name: "杯子", description: "白色", type: "prop", imageUrl: "" }; const attachment = { type: "story-asset" as const, nodeId: "script", asset }; test("asset results merge by stable identity after reordering and are only applied once", () => { const draft = { storyAssets: [{ ...asset, id: "other" }, asset] }; const result = mergeStoryTaskResult(draft, attachment, task.result, "job"); expect(result.storyAssets[1].imageUrl).toBe("https://example.com/1.png"); expect(result.storyAssets[0].imageUrl).toBe(""); expect(mergeStoryTaskResult(result, attachment, task.result, "job")).toBe(result); }); test("late asset results preserve user edits and deletions", () => { for (const storyAssets of [ [{ ...asset, imageUrl: "user-upload" }], [{ ...asset, description: "changed" }], [], ]) { const result = mergeStoryTaskResult({ storyAssets }, attachment, task.result, "job"); expect(result.storyAssets).toEqual(storyAssets); expect(result.storyTaskResults.job.status).toBe("conflict"); } }); test("prompt results merge only their original rows without replacing dialogue or edited rows", () => { const rows = [ { id: "r1", dialogue: "原对白", imagePrompt: "", motionPrompt: "" }, { id: "r2", dialogue: "第二行", imagePrompt: "", motionPrompt: "" }, ]; const target = { type: "story-prompts" as const, nodeId: "script", rows }; const data = { analysisRows: [ { imagePrompt: "image 1", motionPrompt: "move 1" }, { imagePrompt: "image 2", motionPrompt: "move 2" }, ], }; const current = [{ ...rows[1], dialogue: "用户修改" }, rows[0]]; const result = mergeStoryTaskResult({ rows: current }, target, data, "job"); expect(result.rows[0]).toEqual(current[0]); expect(result.rows[1]).toEqual({ ...rows[0], imagePrompt: "image 1", motionPrompt: "move 1" }); expect(result.storyTaskResults.job.status).toBe("conflict"); }); test("incomplete prompt batches cannot be marked successful", () => { const target = { type: "story-prompts" as const, nodeId: "script", rows: [{ id: "r1" }] }; for (const data of [{}, { analysisRows: [] }, { analysisRows: [{ imagePrompt: "x" }] }]) expect(() => requireStoryPromptResult(data, target)).toThrow(); }); test("recovered task results populate a script while retaining the independent output node", () => { const script = { ...node, id: "script", draftValues: { storyAssets: [asset] } }; const attached = { ...task, request: { ...task.request, attachment } }; const result = reconcileCanvasTasks([script], [attached], "2026-09-17T01:00:00Z"); expect(result.find((n) => n.id === "script")?.draftValues?.storyAssets[0].imageUrl).toBe( "https://example.com/1.png", ); expect(result.some((n) => n.id === "n1")).toBe(true); expect(reconcileCanvasTasks(result, [attached], "2026-09-17T01:00:00Z")).toBe(result); }); // Recovery only queries an existing, server-recorded task; it never submits another job. import { canResumeCanvasTask, getEnhanceCheckpoint, getVideoCheckpoint, } from "@/lib/video-canvas/task-recovery"; import { CanvasTaskLeaseLostError } from "@/lib/video-canvas/image-operation"; import { normalizeVideoAnalysisRows } from "@/lib/video-canvas/video-story"; import { pollTaskResult, EnhanceTerminalError } from "@/lib/canvas/enhance-task-poll"; const recoveryTime = Date.parse("2026-09-17T04:00:00Z"); const checkpoint = { operation: "enhance" as const, taskId: "saved-upstream", sourceImageUrl: "https://example.com/source.png", }; const recoverable = { status: "interrupted", createdAt: "2026-09-17T03:00:00Z", updatedAt: "2026-09-17T03:59:00Z", attempts: 1, request: { kind: "image-operation", params: { operation: "enhance", imageUrl: checkpoint.sourceImageUrl }, }, result: { upstreamTask: checkpoint }, }; test("recovery enforces checkpoint identity, age, cooldown and bounded attempts", () => { expect(canResumeCanvasTask(recoverable, recoveryTime)).toBe(true); for (const changed of [ { status: "failed" }, { result: null }, { attempts: 20 }, { createdAt: "2026-09-15T00:00:00Z" }, { updatedAt: "2026-09-17T03:59:50Z" }, { attempts: 8 }, { request: { kind: "image-operation", params: { operation: "enhance", imageUrl: "https://example.com/other.png" }, }, }, ]) expect(canResumeCanvasTask({ ...recoverable, ...changed }, recoveryTime)).toBe(false); expect( getEnhanceCheckpoint({ ...recoverable, request: { kind: "video", params: {} } }), ).toBeNull(); }); test("restarting enhancement from its checkpoint makes zero POST calls", async () => { let posts = 0, gets = 0; const post = async () => { posts++; throw Error("must not POST"); }; const result = await executeImageOperation( recoverable.request.params, "owner", { enhance: post, extend: post, removeBackground: post, angleEdit: post, pollEnhance: async (req) => { gets++; expect(new URL(req.url).searchParams.get("taskId")).toBe(checkpoint.taskId); return Response.json({ success: true, original_url: "https://example.com/recovered.png" }); }, }, { resume: checkpoint, wait: async () => {} }, ); expect(result.images[0].originalUrl).toContain("recovered.png"); expect(posts).toBe(0); expect(gets).toBe(1); }); test("invalid checkpoints and a replaced worker lease cannot reach the provider", async () => { let calls = 0; const handler = async () => { calls++; throw Error("provider touched"); }; const handlers = { enhance: handler, extend: handler, removeBackground: handler, angleEdit: handler, pollEnhance: handler, }; await expect( executeImageOperation(recoverable.request.params, "owner", handlers, { resume: { ...checkpoint, sourceImageUrl: "wrong" }, }), ).rejects.toThrow("不匹配"); await expect( executeImageOperation(recoverable.request.params, "owner", handlers, { assertLease: async () => { throw new CanvasTaskLeaseLostError(); }, }), ).rejects.toBeInstanceOf(CanvasTaskLeaseLostError); expect(calls).toBe(0); }); test("terminal provider failure stops query recovery instead of staying pending", async () => { const handler = async () => Response.json({ terminal: true, detail: "AOS任务失败: invalid image" }, { status: 500 }); try { await executeImageOperation( recoverable.request.params, "owner", { enhance: handler, extend: handler, removeBackground: handler, angleEdit: handler, pollEnhance: handler, }, { resume: checkpoint, wait: async () => {} }, ); throw Error("expected provider failure"); } catch (error) { expect(error).not.toBeInstanceOf(ImageOperationUncertainError); expect(String(error)).toContain("AOS任务失败"); } }); test("actual AOS polling propagates negative state immediately and bounds elapsed retries", async () => { let calls = 0; await expect( pollTaskResult("https://example.com", "qa", "id", 1, 1000, async () => { calls++; return Response.json({ data: { state: -1, err_message: "bad image", progress: 0 } }); }), ).rejects.toBeInstanceOf(EnhanceTerminalError); expect(calls).toBe(1); const started = Date.now(); await expect( pollTaskResult("https://example.com", "qa", "id", 1, 25, async () => Response.json({ data: { state: 0, progress: 10 } }), ), ).rejects.toThrow("轮询超时"); expect(Date.now() - started).toBeLessThan(500); }); test("video analysis preserves zero timestamps and all shots without invented camera or audio facts", () => { const rows = normalizeVideoAnalysisRows( Array.from({ length: 12 }, (_, i) => ({ sceneDescription: `镜头${i}`, startTime: 0 })), [], "story", ); expect(rows).toHaveLength(12); expect(rows[0]).toMatchObject({ startTime: "0", lighting: "", backgroundMusic: "", cameraMovement: "", imagePrompt: "", }); expect(normalizeVideoAnalysisRows([{}, { startTime: 0, backgroundMusic: "无" }])).toEqual([]); expect(() => requireAnalysisResult({ summary: "没有镜头", analysisRows: [] }, "video-analysis"), ).toThrow("有效镜头"); }); test("video story refresh restores real rows, pending state and errors without a placeholder shot", () => { const story = { ...node, kind: "video-story", inputType: "text", draftValues: { action: "VIDEO_STORY_ANALYSIS", rows: [] }, }; const analysis = { ...task, request: { ...task.request, node: story as any, kind: "analysis" as const, params: { mode: "video-analysis" }, }, result: { summary: "视频内容", analysisRows: [{ sceneDescription: "杯子", startTime: 0 }], framePreviews: ["https://example.com/frame.png"], }, }; const pending = reconcileCanvasTasks( [story], [{ ...analysis, status: "running" }], "2026-09-17T01:00:00Z", ); expect(pending[0].draftValues).toMatchObject({ status: "loading", rows: [] }); const failed = reconcileCanvasTasks( pending, [{ ...analysis, status: "failed", error: "解析失败" }], "2026-09-17T01:00:00Z", ); expect(failed[0].draftValues).toMatchObject({ status: "error", error: "解析失败", rows: [] }); const restored = reconcileCanvasTasks([], [analysis], "2026-09-17T01:00:00Z")[0]; expect(restored.kind).toBe("video-story"); expect(restored.draftValues).toMatchObject({ status: "ready", summary: "视频内容" }); expect(restored.draftValues?.rows[0]).toMatchObject({ startTime: "0", keyframeUrl: "https://example.com/frame.png", lighting: "", }); }); test("video recovery requires an original server checkpoint and matching selected model", () => { const upstreamTask = { operation: "video", provider: "aliyun", taskId: "video-original", modelRef: "model-row", modelId: "wan-test", providerId: "provider-row", baseUrl: "https://provider.invalid", }; const task = { ...recoverable, request: { kind: "video", params: { preferredModelRef: "model-row" } }, result: { upstreamTask }, }; expect(getVideoCheckpoint(task)).toEqual(upstreamTask); expect(canResumeCanvasTask(task, recoveryTime)).toBe(true); for (const value of [ undefined, { ...upstreamTask, taskId: "" }, { ...upstreamTask, provider: "unknown" }, { ...upstreamTask, modelRef: "other" }, ]) { expect(getVideoCheckpoint({ ...task, result: { upstreamTask: value } })).toBeNull(); expect(canResumeCanvasTask({ ...task, result: { upstreamTask: value } }, recoveryTime)).toBe( false, ); } expect(canResumeCanvasTask({ ...task, attempts: 20 }, recoveryTime)).toBe(false); }); import { buildAudioRequest, officialAudioBase } from "@/lib/video-canvas/audio-contract"; import { getAudioCheckpoint } from "@/lib/video-canvas/task-recovery"; test("official Alibaba audio requests match each model capabilities", () => { const tts = buildAudioRequest( { mode: "tts", modelRef: "a", text: "你好", voice: "Cherry", instructions: "温柔自然" }, "qwen3-tts-instruct-flash", ); expect(tts.path).toContain("multimodal-generation"); expect(tts.body.input).toMatchObject({ text: "你好", voice: "Cherry", instructions: "温柔自然", optimize_instructions: true, }); expect(tts.body.input).not.toHaveProperty("rate"); expect(() => buildAudioRequest( { mode: "tts", modelRef: "a", text: "你好", voice: "Cherry", rate: 2 }, "qwen3-tts-flash", ), ).toThrow("不支持数值"); expect(() => buildAudioRequest( { mode: "tts", modelRef: "a", text: "你好", voice: "unsupported" }, "qwen3-tts-flash", ), ).toThrow("音色"); const modern = buildAudioRequest( { mode: "tts", modelRef: "a", text: "你好", voice: "longanlingxin", format: "mp3", rate: 1.5 }, "qwen-audio-3.0-tts-plus", ); expect(modern.body.input).toMatchObject({ format: "mp3", rate: 1.5, language_hints: ["zh"] }); }); test("music validates lyrics and excludes singing fields from instrumental output", () => { const music = buildAudioRequest( { mode: "music", modelRef: "a", text: "轻快吉他", lyrics: "不应该传的歌词", instrumental: true, format: "mp3", }, "fun-music-v1", ); expect(music.body.input).toEqual({ prompt: "轻快吉他", is_instrumental: true, format: "mp3" }); expect(() => buildAudioRequest( { mode: "music", modelRef: "a", lyrics: "太短", instrumental: false }, "fun-music-v1", ), ).toThrow("歌词"); expect(() => buildAudioRequest({ mode: "music", modelRef: "a", text: "音乐" }, "qwen3-tts-flash"), ).toThrow("不匹配"); }); test("audio credentials can only target official domains and correct region", () => { expect( officialAudioBase("https://dashscope.aliyuncs.com/compatible-mode/v1", "fun-music-v1"), ).toBe("https://dashscope.aliyuncs.com"); expect(() => officialAudioBase("https://dashscope.aliyuncs.com.evil.test")).toThrow(); expect(() => officialAudioBase("http://dashscope.aliyuncs.com")).toThrow(); expect(() => officialAudioBase("https://dashscope-intl.aliyuncs.com", "fun-music-v1")).toThrow( "北京", ); }); test("only matching persisted audio result may resume archival", () => { const task = { request: { kind: "audio", params: { modelRef: "ref" } }, result: { upstreamAudio: { operation: "audio", modelRef: "ref", modelId: "qwen3-tts-flash", url: "https://example.com/audio.wav", format: "wav", }, }, }; expect(getAudioCheckpoint(task)).toMatchObject({ modelRef: "ref" }); expect( getAudioCheckpoint({ ...task, request: { kind: "audio", params: { modelRef: "another" } } }), ).toBeNull(); expect( getAudioCheckpoint({ ...task, result: { upstreamAudio: { ...task.result.upstreamAudio, url: "file:///tmp/private" } }, }), ).toBeNull(); });