import { expect, test } from "bun:test"; import { createModelChannel, defaultConfig } from "../src/stores/use-config-store"; import { runBackendGenerationTask, runBackendGenerationTaskBatch } from "../src/services/api/generation-task"; import { deleteGenerationTask, formatTaskLog, listGenerationTasks, projectBackendSafeTaskLog, splitGenerationTaskObservationIds, type GenerationTask } from "../src/services/api/task-center"; import { isLocalDreaminaBackgroundTask, localDreaminaCancellationCopy, localDreaminaDetachOutcome, projectLocalDreaminaTask } from "../src/services/local-dreamina-task-projection"; import { LocalDreaminaGenerationClientError, runLocalDreaminaGenerationTask, type LocalDreaminaGenerationInput } from "../src/services/local-dreamina-generation"; import { createGenerationBatchRetryContexts, createGenerationRetryContext, generationTaskMetadata, runBackendCanvasGenerationTask, runCanvasGenerationTaskToConsumer } from "../src/lib/canvas/canvas-project-generation"; import { runCanvasAgentGenerationOps } from "../src/pages/canvas/use-canvas-agent-operations"; import { CanvasNodeType, type CanvasNodeData } from "../src/types/canvas"; import { onlineToolToOps } from "../src/components/canvas/canvas-assistant-panel"; import { generationTaskShowsProgress, generationTaskStageLabel, generationTaskStatusLabel } from "../src/lib/generation-task-display"; import { generationErrorMessage } from "../src/lib/generation-error"; import { generationTaskNodeId, syncGenerationTaskToCanvasStore } from "../src/lib/canvas/canvas-generation-task-sync"; test("业务项目任务和无节点任务不会读取画布项目", async () => { const task = { id: "chapter-task", type: "canvas_text", status: "succeeded", projectId: "domain-project", clientContext: { domainProjectId: "domain-project", nodeId: "chapter_character_breakdown:chapter" } } as GenerationTask; expect(await syncGenerationTaskToCanvasStore(task)).toBe(false); expect(await syncGenerationTaskToCanvasStore({ ...task, clientContext: undefined, inputJson: JSON.stringify({ metadata: { domainProjectId: "domain-project", nodeId: "chapter-node" } }) })).toBe(false); expect(await syncGenerationTaskToCanvasStore({ ...task, clientContext: undefined })).toBe(false); }); test("canvas recovery reads node identity from task summaries without full task input", () => { const task: GenerationTask = { id: "task-summary", type: "canvas_image", status: "running", prompt: "", attempts: 1, createdAt: "", updatedAt: "", clientContext: { nodeId: "node-summary" } }; expect(generationTaskNodeId(task)).toBe("node-summary"); expect(generationTaskNodeId({ ...task, inputJson: "invalid json" })).toBe("node-summary"); expect(generationTaskNodeId({ ...task, clientContext: undefined, inputJson: JSON.stringify({ metadata: { nodeId: "node-detail" } }) })).toBe("node-detail"); expect(generationTaskNodeId({ ...task, clientContext: undefined })).toBe(""); }); function compactSource(source: string) { return source.replace(/\s+/g, " ").trim(); } function sourceSection(source: string, startMarker: string, endMarker: string) { const start = source.indexOf(startMarker); const end = source.indexOf(endMarker, start + startMarker.length); expect(start).toBeGreaterThanOrEqual(0); expect(end).toBeGreaterThan(start); return compactSource(source.slice(start, end)); } function backendModelConfig(model: string) { const channel = createModelChannel({ id: "system-test-channel", name: "系统测试渠道", baseUrl: "/api/system-test-channel", apiKey: "system", apiFormat: "openai", scope: "system", models: [model], }); const selectedModel = `${channel.id}::${model}`; return { ...defaultConfig, channelMode: "remote" as const, channels: [channel], model: selectedModel, imageModel: selectedModel, videoModel: selectedModel, audioModel: selectedModel, }; } test("Dreamina submit failure categories have bounded user-facing messages", () => { const cases = [ ["dreamina_submit_spawn_failed", "无法启动官方即梦 CLI,任务尚未提交。"], ["dreamina_submit_exit_nonzero", "官方即梦 CLI 未接受本次提交,任务没有自动重试。"], ["dreamina_submit_timeout", "等待官方即梦 CLI 确认提交超时,为避免重复扣费,任务没有自动重试。"], ["dreamina_submit_receipt_missing", "官方即梦 CLI 未返回任务凭证,为避免重复扣费,任务没有自动重试。"], ] as const; for (const [code, message] of cases) { expect(generationErrorMessage(new LocalDreaminaGenerationClientError(code, "本机即梦生成请求失败", 502))).toBe(message); } }); test("resource storage 403 is not reported as generation channel authentication", () => { const raw = "参考图片上传失败:OSS 上传失败:403 Forbidden UserDisableUserDisable"; expect(generationErrorMessage(raw)).toBe("对象存储账号已停用,请检查或更换对象存储配置。"); }); test("durable Dreamina submit failures keep their stable user-facing category in task center", () => { const task = projectLocalDreaminaTask({ id: "dreamina-submit-timeout-task-0001", provider: "dreamina-cli", mode: "video", operation: "image2video", model: "seedance2.0mini", status: "failed", stage: "submission_unknown", receiptRecorded: false, errorCode: "dreamina_submit_timeout", createdAt: "2026-08-12T08:03:53.118Z", updatedAt: "2026-08-12T08:04:44.191Z", }); expect(task.errorCode).toBe("dreamina_submit_timeout"); expect(task.error).toBe("等待官方即梦 CLI 确认提交超时,为避免重复扣费,任务没有自动重试。"); }); test("local Dreamina projection preserves stable outputs independently from provider success", () => { const task = projectLocalDreaminaTask({ id: "dreamina-output-projection-task-0001", provider: "dreamina-cli", mode: "image", operation: "text2image", model: "image-3.0", status: "succeeded", stage: "succeeded", receiptRecorded: true, officialStatus: "completed", lifecycle: "TERMINAL", terminalOutcome: "SUCCEEDED", resultState: "PENDING_MATERIALIZATION", outputs: [ { outputIndex: 0, mediaType: "image", providerArtifactRef: "provider-artifact-opaque", }, ], createdAt: "2026-08-13T00:00:00.000Z", updatedAt: "2026-08-13T00:01:00.000Z", }); expect(task.status).toBe("succeeded"); expect(task.resultState).toBe("PENDING_MATERIALIZATION"); expect(task.outputs).toEqual([ { outputIndex: 0, mediaType: "image", providerArtifactRef: "provider-artifact-opaque", }, ]); }); test("task display does not claim a submitted Dreamina receipt is actively generating", () => { const submitting = { provider: "dreamina-cli", status: "running", stage: "submitting", receiptRecorded: false, } as GenerationTask; const submitted = { provider: "dreamina-cli", status: "running", stage: "submitted", } as GenerationTask; const generating = { ...submitted, stage: "generating" } as GenerationTask; const officialPending = { ...submitted, officialStatus: "pending" } as GenerationTask; const officialProcessing = { ...submitted, officialStatus: "processing" } as GenerationTask; const officialCompleted = { ...submitted, officialStatus: "completed" } as GenerationTask; const remoteRunning = { provider: "openai-compatible", status: "running", stage: "submitted" } as GenerationTask; expect(generationTaskStatusLabel(submitting)).toBe("正在提交"); expect(generationTaskStageLabel(submitting)).toBe("正在提交,等待官方确认"); expect(generationTaskShowsProgress(submitting)).toBe(false); expect(generationTaskStatusLabel(submitted)).toBe("状态待更新"); expect(generationTaskStageLabel(submitted)).toBe("已提交,等待状态更新"); expect(generationTaskShowsProgress(submitted)).toBe(false); expect(generationTaskStatusLabel(officialPending)).toBe("官方排队中"); expect(generationTaskStageLabel(officialPending)).toBe("官方返回状态:pending"); expect(generationTaskStatusLabel(officialProcessing)).toBe("生成中"); expect(generationTaskStageLabel(officialProcessing)).toBe("官方返回状态:processing"); expect(generationTaskStatusLabel(officialCompleted)).toBe("官方已完成"); expect(generationTaskStageLabel(officialCompleted)).toBe("官方返回状态:completed"); expect(generationTaskStatusLabel(generating)).toBe("生成中"); expect(generationTaskShowsProgress(generating)).toBe(true); for (const stage of ["等待队列调度", "后端接管任务", "正在连接上游", "调用生成模型"]) { expect(generationTaskShowsProgress({ ...generating, provider: "managed", stage })).toBe(false); } expect(generationTaskShowsProgress({ ...generating, provider: "managed", stage: "上游生成中" })).toBe(true); expect(generationTaskStatusLabel(remoteRunning)).toBe("生成中"); }); test("Dreamina submission uncertainty is not an accepted background task", () => { const uncertain = { id: "dreamina:submit-uncertain-0001", provider: "dreamina-cli", status: "failed", stage: "submission_unknown", receiptRecorded: false, errorCode: "dreamina_submission_unknown" } as GenerationTask; const projected = projectLocalDreaminaTask({ id: "submit-uncertain-0001", provider: "dreamina-cli", mode: "video", operation: "text2video", model: "seedance2.0", status: "failed", stage: "submission_unknown", receiptRecorded: false, lifecycle: "SUBMISSION_UNCERTAIN", errorCode: "dreamina_submission_unknown", createdAt: "2026-08-14T00:00:00.000Z", updatedAt: "2026-08-14T00:00:10.000Z", }); expect(projected.status).toBe("failed"); expect(projected.error).toBe("提交结果待确认,为避免重复扣费未自动重试。"); expect(generationTaskStatusLabel(uncertain)).toBe("提交结果待确认"); expect(generationTaskStageLabel(uncertain)).toBe("为避免重复扣费,未自动重试"); expect(generationTaskShowsProgress(uncertain)).toBe(false); expect(isLocalDreaminaBackgroundTask(uncertain)).toBe(false); expect(localDreaminaCancellationCopy(uncertain)).toBeUndefined(); }); test("Canvas task surfaces route Dreamina uncertainty through shared display semantics without cancellation", async () => { const [nodeSource, detailSource, scriptSource, taskCenterSource, createSource] = await Promise.all([ Bun.file(new URL("../src/components/canvas/canvas-node-content.tsx", import.meta.url)).text(), Bun.file(new URL("../src/pages/canvas/canvas-project-status-dialogs.tsx", import.meta.url)).text(), Bun.file(new URL("../src/components/canvas/canvas-script-node.tsx", import.meta.url)).text(), Bun.file(new URL("../src/pages/tasks/index.tsx", import.meta.url)).text(), Bun.file(new URL("../src/pages/create/index.tsx", import.meta.url)).text(), ]); expect(nodeSource).toContain("generationTaskStageLabel(displayTask)"); expect(nodeSource).toContain("generationTaskShowsProgress(displayTask)"); expect(nodeSource).not.toContain("onCancelTask"); expect(nodeSource).not.toContain("取消生成"); expect(nodeSource).not.toContain('node.metadata?.taskStage || (taskId ? "任务处理中" : "正在创建任务")'); expect(detailSource).toContain("generationTaskStageLabel(task)"); expect(detailSource).toContain("generationTaskShowsProgress(task) ? void runAction(task.id)}"); expect(taskCenterSource).toContain(" { const playerCSS = await Bun.file(new URL("../src/components/video-player.css", import.meta.url)).text(); expect(playerCSS).toContain('.canvas-video-player[data-player-variant="compact"]:not([data-fullscreen])'); expect(playerCSS).toContain("--media-fullscreen-button-size: 60px"); expect(playerCSS).toContain("--media-slider-track-height: 8px"); expect(playerCSS).toContain("min-height: 64px"); }); test("task center deletion accepts only local Dreamina records", async () => { await expect(deleteGenerationTask("backend-task-0001")).rejects.toThrow("当前任务不支持删除"); const source = await Bun.file(new URL("../src/pages/tasks/index.tsx", import.meta.url)).text(); const compactedSource = compactSource(source); const deleteActionSource = sourceSection(source, "const deleteLocalTask =", "const refreshLocalTaskStatus ="); expect(compactedSource).toMatch(/\{detailTask\.provider === "dreamina-cli" \? \(