import { expect, test } from "bun:test";
import { 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";
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));
}
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);
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(" {
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" \? \(