1302 lines
57 KiB
TypeScript
1302 lines
57 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { EventEmitter } from "node:events";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { test } from "node:test";
|
|
|
|
import { dreaminaCliInputSchema } from "../src/dreamina-cli-contract.js";
|
|
import { DreaminaCliArbiter } from "../src/dreamina-cli-arbiter.js";
|
|
import { LocalDreaminaGenerationError } from "../src/dreamina-generation.js";
|
|
import { projectDreaminaModelCatalog } from "../src/dreamina-model-catalog.js";
|
|
import { DreaminaProviderArtifactStore } from "../src/dreamina-provider-artifacts.js";
|
|
import { DreaminaTaskProjector } from "../src/dreamina-task-projection.js";
|
|
import { DreaminaTaskStore } from "../src/dreamina-task-store.js";
|
|
import { createDreaminaHttpModule } from "../src/modules/dreamina-http.js";
|
|
import { DreaminaCliError } from "../src/dreamina-cli-process.js";
|
|
import type { DreaminaPublicStatus } from "../src/dreamina-cli.js";
|
|
|
|
const installed: DreaminaPublicStatus = {
|
|
provider: "dreamina-cli",
|
|
state: "installed",
|
|
installed: true,
|
|
authenticated: false,
|
|
code: "dreamina_login_required",
|
|
message: "Dreamina CLI 已安装,需要登录",
|
|
};
|
|
|
|
test("Dreamina module stages generated and configured references when another optional Canvas root is absent", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-reference-"));
|
|
const canvasRoot = path.join(configDir, "canvas-workspace");
|
|
const absentCanvasRoot = path.join(configDir, "canvas-not-created-yet");
|
|
const canvasReference = path.join(canvasRoot, "reference.png");
|
|
const calls: string[][] = [];
|
|
let submitCalls = 0;
|
|
await fs.mkdir(canvasRoot);
|
|
await fs.writeFile(canvasReference, pngFixture());
|
|
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
referenceRoots: () => [absentCanvasRoot, canvasRoot],
|
|
dreamina: authenticatedLifecycleFixture(),
|
|
runtimeDependencies: {
|
|
discover: async () => ({ installed: true, executable: "dreamina-fixture" }),
|
|
maxPollAttempts: 1,
|
|
pollIntervalMs: 0,
|
|
runProcess: async (input) => {
|
|
calls.push([...input.args]);
|
|
input.onSpawn?.(4242);
|
|
if (input.args[0] === "query_result") {
|
|
const outputRoot = input.args.find((arg) => arg.startsWith("--download_dir="))?.slice("--download_dir=".length);
|
|
assert(outputRoot);
|
|
await fs.writeFile(path.join(outputRoot, "result.mp4"), mp4Fixture());
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-1","gen_status":"success"}', stderr: "" };
|
|
}
|
|
submitCalls += 1;
|
|
return { exitCode: 0, stdout: `{"submit_id":"receipt-submit-${submitCalls}"}`, stderr: "" };
|
|
},
|
|
},
|
|
});
|
|
|
|
try {
|
|
const generated = await invoke(module, "/dreamina/generate", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-reference-route-0001",
|
|
operation: "image-to-video",
|
|
model: "seedance2.0mini",
|
|
prompt: "Animate this reference",
|
|
settings: { resolution: "720", duration: 4 },
|
|
references: [{ kind: "image", mimeType: "image/png", contentBase64: pngFixture().toString("base64") }],
|
|
})));
|
|
assert.equal((generated as { ok?: boolean }).ok, true, JSON.stringify({ generated, calls }));
|
|
assert.equal(calls[0]?.[0], "image2video");
|
|
const generatedReference = calls[0]?.find((arg) => arg.startsWith("--image="))?.slice("--image=".length);
|
|
assert(generatedReference && path.isAbsolute(generatedReference));
|
|
assert(generatedReference.includes(`${path.sep}.dreamina-references-`));
|
|
|
|
const accepted = await invoke(module, "/dreamina/run", Buffer.from(JSON.stringify({
|
|
operation: "image2image",
|
|
idempotencyKey: "canvas-reference-route-0001",
|
|
prompt: "Edit this Canvas reference",
|
|
resolutionType: "2k",
|
|
referenceImages: [canvasReference],
|
|
})));
|
|
assert.deepEqual(accepted, { ok: true, result: { state: "accepted", receiptRecorded: true } });
|
|
assert.doesNotMatch(JSON.stringify(accepted), /submitId|receipt-submit-2/);
|
|
const journal = JSON.parse(await fs.readFile(path.join(configDir, "dreamina-runtime-state.json"), "utf8")) as {
|
|
records: Array<{ idempotencyKey?: string; submitId?: string }>;
|
|
};
|
|
assert.equal(journal.records.find((record) => record.idempotencyKey === "canvas-reference-route-0001")?.submitId, "receipt-submit-2");
|
|
assert.equal(calls.filter((args) => args[0] !== "query_result")[1]?.[0], "image2image");
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina production module startup reconciles due accepted work without route traffic", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-startup-"));
|
|
const journalFile = path.join(configDir, "dreamina-runtime-state.json");
|
|
let queries = 0;
|
|
await fs.writeFile(journalFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId: "owner-dreamina-module-0001",
|
|
idempotencyKey: "dreamina-module-startup-0001",
|
|
requestHash: "a".repeat(64),
|
|
state: "accepted",
|
|
updatedAt: "2026-08-13T00:00:00.000Z",
|
|
submitId: "receipt-module-startup",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z",
|
|
nextPollAt: "2020-01-01T00:00:00.000Z",
|
|
}],
|
|
}));
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: authenticatedLifecycleFixture(),
|
|
runtimeDependencies: {
|
|
pollIntervalMs: 5,
|
|
discover: async () => ({ installed: true, executable: "dreamina-fixture" }),
|
|
runProcess: async () => {
|
|
queries += 1;
|
|
return { exitCode: 0, stdout: '{"status":"cancelled"}', stderr: "" };
|
|
},
|
|
},
|
|
});
|
|
const startup = (module as typeof module & { start?: () => Promise<void> }).start;
|
|
try {
|
|
assert.equal(typeof startup, "function");
|
|
await startup?.();
|
|
await waitForCondition(async () => {
|
|
const disk = JSON.parse(await fs.readFile(journalFile, "utf8"));
|
|
return disk.records[0]?.state === "cancelled";
|
|
});
|
|
assert.equal(queries, 1);
|
|
assert.deepEqual(await invoke(module, "/dreamina/run", Buffer.from(JSON.stringify({
|
|
operation: "query_result",
|
|
submitId: "receipt-module-startup",
|
|
}))), {
|
|
ok: false,
|
|
code: "dreamina_request_invalid",
|
|
message: "Dreamina 请求参数无效",
|
|
});
|
|
assert.equal(queries, 1);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina module declares model reads without probing CLI or Canvas", () => {
|
|
const calls: string[] = [];
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
|
|
assert.deepEqual(module.descriptor, {
|
|
id: "dreamina",
|
|
displayName: "Dreamina CLI",
|
|
apiVersion: 1,
|
|
scopes: ["dreamina:status", "dreamina:login", "dreamina:logout", "dreamina:run", "dreamina:models", "dreamina:generate"],
|
|
});
|
|
assert.deepEqual(module.routes.map((route) => [route.method, route.path, route.scope]), [
|
|
["GET", "/dreamina/status", "dreamina:status"],
|
|
["POST", "/dreamina/login", "dreamina:login"],
|
|
["POST", "/dreamina/logout", "dreamina:logout"],
|
|
["POST", "/dreamina/run", "dreamina:run"],
|
|
["GET", "/dreamina/models", "dreamina:models"],
|
|
["POST", "/dreamina/generate", "dreamina:generate"],
|
|
["GET", "/dreamina/generate/tasks", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/query", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/wait", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/refresh", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/cancel", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/delete", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/effects/claim", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/effects/renew", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/effects/complete", "dreamina:generate"],
|
|
["POST", "/dreamina/generate/effects/release", "dreamina:generate"],
|
|
]);
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test("Dreamina module recovers product task projections from durable journal versions before task reads", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-task-store-"));
|
|
const journalFile = path.join(configDir, "dreamina-runtime-state.json");
|
|
const storeFile = path.join(configDir, "dreamina-generation-task-store.json");
|
|
const calls: string[] = [];
|
|
await fs.writeFile(journalFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId: "owner-dreamina-module-0001",
|
|
idempotencyKey: "dreamina-module-projection-0001",
|
|
requestHash: "f".repeat(64),
|
|
state: "queued",
|
|
journalVersion: 3,
|
|
updatedAt: "2026-08-13T00:03:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z",
|
|
}],
|
|
}));
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
try {
|
|
await invoke(module, "/dreamina/generate/tasks", undefined);
|
|
const disk = JSON.parse(await fs.readFile(storeFile, "utf8")) as {
|
|
tasks: Array<{ taskId: string; lifecycle: string; projectedJournalVersion: number; context: unknown; accountBinding?: string }>;
|
|
};
|
|
assert.equal(disk.tasks.length, 1);
|
|
assert.equal(disk.tasks[0]?.taskId, "dreamina:dreamina-module-projection-0001");
|
|
assert.equal(disk.tasks[0]?.lifecycle, "QUEUED_LOCAL");
|
|
assert.equal(disk.tasks[0]?.projectedJournalVersion, 3);
|
|
assert.deepEqual(disk.tasks[0]?.context, { scope: "legacy_unscoped" });
|
|
assert.equal(disk.tasks[0]?.accountBinding, undefined);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina model reads require the official CLI login and never execute generation", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-model-logged-out-"));
|
|
const calls: string[] = [];
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
|
|
const rejected = await invoke(module, "/dreamina/models", undefined);
|
|
assert.deepEqual(rejected, {
|
|
ok: false,
|
|
code: "dreamina_login_required",
|
|
message: "Dreamina CLI 需要先登录",
|
|
});
|
|
assert.deepEqual(calls, []);
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("Dreamina model catalog returns the last authenticated account cache scope without another status read", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-model-scope-"));
|
|
const calls: string[] = [];
|
|
const accountBinding = "d".repeat(64);
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(configDir, "dreamina-cli-arbiter.json"), pollMs: 1 });
|
|
const lease = await arbiter.acquire();
|
|
const session = await arbiter.commitSession(lease, accountBinding);
|
|
await lease.release();
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
arbiter,
|
|
dreamina: {
|
|
...lifecycleFixture(calls),
|
|
status: async () => {
|
|
calls.push("status");
|
|
return { ...installed, state: "authenticated" as const, authenticated: true, accountBinding, sessionEpoch: 7, message: "authenticated" };
|
|
},
|
|
},
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
|
|
const response = await invoke(module, "/dreamina/models", undefined) as Record<string, unknown>;
|
|
assert.equal(response.ok, true);
|
|
assert.equal(response.accountBinding, accountBinding);
|
|
assert.equal(response.sessionEpoch, session.sessionEpoch);
|
|
assert.equal(Array.isArray(response.models), true);
|
|
assert.deepEqual(calls, []);
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("Dreamina model catalog uses the last authenticated session scope without spawning a CLI status check", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-model-session-"));
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(configDir, "dreamina-cli-arbiter.json"), pollMs: 1 });
|
|
const lease = await arbiter.acquire();
|
|
const accountBinding = "e".repeat(64);
|
|
const session = await arbiter.commitSession(lease, accountBinding);
|
|
await lease.release();
|
|
let statusCalls = 0;
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
arbiter,
|
|
dreamina: {
|
|
...lifecycleFixture([]),
|
|
status: async () => {
|
|
statusCalls += 1;
|
|
throw new Error("model catalog must not spawn Dreamina CLI status");
|
|
},
|
|
},
|
|
dreaminaRuntime: runtimeFixture([]),
|
|
});
|
|
try {
|
|
const response = await invoke(module, "/dreamina/models", undefined) as Record<string, unknown>;
|
|
assert.equal(response.ok, true);
|
|
assert.equal(response.accountBinding, accountBinding);
|
|
assert.equal(response.sessionEpoch, session.sessionEpoch);
|
|
assert.equal(Array.isArray(response.models), true);
|
|
assert.equal(statusCalls, 0);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina generation fails closed on projection recovery before crossing the paid submit boundary", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-projection-fail-"));
|
|
const calls: string[] = [];
|
|
let generationCalls = 0;
|
|
await fs.writeFile(path.join(configDir, "dreamina-runtime-state.json"), JSON.stringify({ version: 1, records: "invalid" }));
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
generation: {
|
|
async run() { throw new Error("synchronous generation must not run"); },
|
|
async submit() {
|
|
generationCalls += 1;
|
|
return taskFixture("seedance-projection-fail-0001", "running", "submitted");
|
|
},
|
|
},
|
|
});
|
|
try {
|
|
const result = await invoke(module, "/dreamina/generate", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-projection-fail-0001",
|
|
operation: "text-to-video",
|
|
model: "seedance2.0mini",
|
|
prompt: "fixture",
|
|
settings: { resolution: "720p", duration: 4 },
|
|
references: [],
|
|
})));
|
|
assert.equal((result as { ok?: boolean }).ok, false);
|
|
assert.equal(generationCalls, 0);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina post-submit projection failure returns the accepted task and recovers from the durable journal later", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-post-projection-fail-"));
|
|
const journalFile = path.join(configDir, "dreamina-runtime-state.json");
|
|
const storeFile = path.join(configDir, "dreamina-generation-task-store.json");
|
|
const calls: string[] = [];
|
|
let generationCalls = 0;
|
|
await fs.writeFile(journalFile, JSON.stringify({ version: 1, records: [] }));
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
generation: {
|
|
async run() { throw new Error("synchronous generation must not run"); },
|
|
async submit() {
|
|
generationCalls += 1;
|
|
await fs.writeFile(journalFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId: "owner-dreamina-module-0001",
|
|
idempotencyKey: "seedance-post-projection-0001",
|
|
requestHash: "7".repeat(64),
|
|
state: "accepted",
|
|
journalVersion: 1,
|
|
submitId: "provider-task-post-projection-fixture",
|
|
updatedAt: "2026-08-13T00:01:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z",
|
|
}],
|
|
}));
|
|
await fs.mkdir(storeFile);
|
|
return taskFixture("seedance-post-projection-0001", "running", "submitted");
|
|
},
|
|
},
|
|
});
|
|
try {
|
|
const result = await invoke(module, "/dreamina/generate", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-post-projection-0001",
|
|
operation: "text-to-video",
|
|
model: "seedance2.0mini",
|
|
prompt: "fixture",
|
|
settings: { resolution: "720p", duration: 4 },
|
|
references: [],
|
|
})));
|
|
assert.deepEqual(result, {
|
|
ok: true,
|
|
result: taskFixture("seedance-post-projection-0001", "running", "submitted"),
|
|
});
|
|
assert.equal(generationCalls, 1);
|
|
|
|
await fs.rm(storeFile, { recursive: true, force: true });
|
|
const laterRead = await invoke(module, "/dreamina/generate/tasks", undefined);
|
|
assert.equal((laterRead as { ok?: boolean }).ok, true);
|
|
const disk = JSON.parse(await fs.readFile(storeFile, "utf8")) as { tasks: Array<{ taskId: string; lifecycle: string }> };
|
|
assert.deepEqual(disk.tasks.map((task) => [task.taskId, task.lifecycle]), [
|
|
["dreamina:seedance-post-projection-0001", "ACCEPTED"],
|
|
]);
|
|
assert.equal(generationCalls, 1);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina task list returns the projected product Store contract instead of Runtime journal DTOs", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-store-list-authority-"));
|
|
const id = "dreamina-store-list-authority-0001";
|
|
const accountBinding = "a".repeat(64);
|
|
const calls: string[] = [];
|
|
await writeModuleJournal(configDir, moduleJournalRecord(id, {
|
|
accountBinding,
|
|
sessionEpoch: 1,
|
|
context: { scope: "scoped", projectId: "project-store-authority-0001", nodeId: "node-store-authority-0001" },
|
|
}));
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
try {
|
|
const response = await invoke(module, "/dreamina/generate/tasks", undefined) as { ok: true; result: unknown };
|
|
const tasks = Array.isArray(response.result)
|
|
? response.result as Array<Record<string, unknown>>
|
|
: (response.result as { tasks: Array<Record<string, unknown>> }).tasks;
|
|
assert.equal(tasks.length, 1);
|
|
assert.equal(tasks[0]?.id, id);
|
|
assert.equal(tasks[0]?.lifecycle, "ACCEPTED");
|
|
assert.equal(tasks[0]?.syncState, "SYNC_OK");
|
|
assert.equal(tasks[0]?.resultState, "NOT_AVAILABLE");
|
|
assert.equal(tasks[0]?.accountBinding, accountBinding);
|
|
assert.deepEqual(tasks[0]?.context, { scope: "scoped", projectId: "project-store-authority-0001", nodeId: "node-store-authority-0001" });
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina task query keeps a Store terminal and restores its provider artifact result over a stale Runtime accepted view", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-store-query-authority-"));
|
|
const id = "dreamina-store-query-authority-0001";
|
|
const accountBinding = "b".repeat(64);
|
|
const { journalFile, providerOutputs, result } = await seedSucceededProductStore(configDir, id, accountBinding);
|
|
await fs.writeFile(journalFile, JSON.stringify({ version: 1, records: [moduleJournalRecord(id, {
|
|
state: "accepted",
|
|
journalVersion: 2,
|
|
accountBinding,
|
|
sessionEpoch: 1,
|
|
})] }));
|
|
const calls: string[] = [];
|
|
const runtime = runtimeFixture(calls);
|
|
runtime.getTask = async (idempotencyKey: string) => {
|
|
calls.push(`get:${idempotencyKey}`);
|
|
return taskFixture(idempotencyKey, "running", "submitted");
|
|
};
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtime,
|
|
});
|
|
try {
|
|
const response = await invoke(module, "/dreamina/generate/query", Buffer.from(JSON.stringify({ idempotencyKey: id, mode: "video" }))) as {
|
|
ok: true; result: Record<string, unknown>;
|
|
};
|
|
assert.equal(response.result.id, id);
|
|
assert.equal(response.result.lifecycle, "TERMINAL");
|
|
assert.equal(response.result.terminalOutcome, "SUCCEEDED");
|
|
assert.equal(response.result.syncState, "SYNC_OK");
|
|
assert.equal(response.result.resultState, "PENDING_MATERIALIZATION");
|
|
assert.deepEqual(response.result.outputs, providerOutputs);
|
|
assert.deepEqual(response.result.result, result);
|
|
assert.equal(response.result.accountBinding, accountBinding);
|
|
assert.equal(JSON.stringify(response.result).includes("submitId"), false);
|
|
assert.deepEqual(calls, [`get:${id}`]);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina task wait returns the Store terminal produced after provider completion", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-store-wait-authority-"));
|
|
const id = "dreamina-store-wait-authority-0001";
|
|
const accountBinding = "c".repeat(64);
|
|
const artifactStore = new DreaminaProviderArtifactStore({ root: path.join(configDir, "dreamina-provider-artifacts") });
|
|
const result = {
|
|
mode: "video" as const,
|
|
video: { dataUrl: `data:video/mp4;base64,${mp4Fixture().toString("base64")}`, mimeType: "video/mp4", bytes: mp4Fixture().byteLength },
|
|
};
|
|
const providerOutputs = await artifactStore.persistResult(result, {
|
|
ownerId: "owner-dreamina-module-0001",
|
|
idempotencyKey: id,
|
|
accountBinding,
|
|
mode: "video",
|
|
});
|
|
const journalFile = await writeModuleJournal(configDir, moduleJournalRecord(id, { accountBinding, sessionEpoch: 1 }));
|
|
const calls: string[] = [];
|
|
const runtime = runtimeFixture(calls);
|
|
runtime.getTask = async (idempotencyKey: string) => {
|
|
calls.push(`get:${idempotencyKey}`);
|
|
return taskFixture(idempotencyKey, "running", "submitted");
|
|
};
|
|
runtime.waitForTask = async (idempotencyKey: string, mode: "image" | "video") => {
|
|
calls.push(`wait:${idempotencyKey}:${mode}`);
|
|
await fs.writeFile(journalFile, JSON.stringify({ version: 1, records: [moduleJournalRecord(id, {
|
|
state: "succeeded",
|
|
journalVersion: 2,
|
|
officialStatus: "completed",
|
|
providerOutputs,
|
|
accountBinding,
|
|
sessionEpoch: 1,
|
|
})] }));
|
|
return result;
|
|
};
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtime,
|
|
});
|
|
try {
|
|
const response = await invoke(module, "/dreamina/generate/wait", Buffer.from(JSON.stringify({ idempotencyKey: id, mode: "video" }))) as {
|
|
ok: true; result: Record<string, unknown>;
|
|
};
|
|
assert.equal(response.result.lifecycle, "TERMINAL");
|
|
assert.equal(response.result.terminalOutcome, "SUCCEEDED");
|
|
assert.equal(response.result.resultState, "PENDING_MATERIALIZATION");
|
|
assert.deepEqual(response.result.outputs, providerOutputs);
|
|
assert.deepEqual(response.result.result, result);
|
|
assert.deepEqual(calls, [`get:${id}`, `wait:${id}:video`, `get:${id}`]);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina task read fails closed when the post-action journal cannot be projected", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-store-post-action-projection-"));
|
|
const id = "dreamina-store-post-action-projection-0001";
|
|
const journalFile = await writeModuleJournal(configDir, moduleJournalRecord(id));
|
|
const calls: string[] = [];
|
|
const runtime = runtimeFixture(calls);
|
|
runtime.refreshTask = async (idempotencyKey: string) => {
|
|
calls.push(`refresh:${idempotencyKey}`);
|
|
await fs.writeFile(journalFile, JSON.stringify({ version: 1, records: [moduleJournalRecord(id, {
|
|
journalVersion: 2,
|
|
requestHash: "e".repeat(64),
|
|
updatedAt: "2026-08-13T00:02:00.000Z",
|
|
})] }));
|
|
return taskFixture(idempotencyKey, "running", "generating");
|
|
};
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtime,
|
|
});
|
|
try {
|
|
const response = await invoke(module, "/dreamina/generate/refresh", Buffer.from(JSON.stringify({ idempotencyKey: id }))) as {
|
|
ok: boolean; result?: Record<string, unknown>; code?: string;
|
|
};
|
|
assert.equal(response.ok, false);
|
|
assert.equal(response.code, "dreamina_internal_error");
|
|
assert.equal(response.result, undefined);
|
|
assert.deepEqual(calls, [`refresh:${id}`]);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina task refresh preserves a Store success and exposes a contradictory provider failure as sync conflict", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-store-refresh-conflict-"));
|
|
const id = "dreamina-store-refresh-conflict-0001";
|
|
const { journalFile, providerOutputs, result } = await seedSucceededProductStore(configDir, id);
|
|
await fs.writeFile(journalFile, JSON.stringify({ version: 1, records: [moduleJournalRecord(id, {
|
|
state: "failed",
|
|
journalVersion: 2,
|
|
officialStatus: "failed",
|
|
})] }));
|
|
const calls: string[] = [];
|
|
const runtime = runtimeFixture(calls);
|
|
runtime.refreshTask = async (idempotencyKey: string) => {
|
|
calls.push(`refresh:${idempotencyKey}`);
|
|
return taskFixture(idempotencyKey, "failed", "failed");
|
|
};
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtime,
|
|
});
|
|
try {
|
|
const response = await invoke(module, "/dreamina/generate/refresh", Buffer.from(JSON.stringify({ idempotencyKey: id }))) as {
|
|
ok: true; result: Record<string, unknown>;
|
|
};
|
|
assert.equal(response.result.lifecycle, "TERMINAL");
|
|
assert.equal(response.result.terminalOutcome, "SUCCEEDED");
|
|
assert.equal(response.result.syncState, "SYNC_CONFLICT");
|
|
assert.deepEqual(response.result.outputs, providerOutputs);
|
|
assert.deepEqual(response.result.result, result);
|
|
assert.deepEqual(calls, [`refresh:${id}`]);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina accepted cancel detaches local waiting without claiming official cancellation", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-store-cancel-authority-"));
|
|
const id = "dreamina-store-cancel-authority-0001";
|
|
const calls: string[] = [];
|
|
await writeModuleJournal(configDir, moduleJournalRecord(id));
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
try {
|
|
const response = await invoke(module, "/dreamina/generate/cancel", Buffer.from(JSON.stringify({ idempotencyKey: id }))) as {
|
|
ok: true; result: Record<string, unknown>;
|
|
};
|
|
assert.equal(response.result.lifecycle, "ACCEPTED");
|
|
assert.equal(response.result.terminalOutcome, undefined);
|
|
assert.equal(response.result.status, "running");
|
|
assert.equal(response.result.stage, "submitted");
|
|
assert.equal(response.result.officialStatus, undefined);
|
|
assert.deepEqual(calls, [`cancel:${id}`]);
|
|
} finally {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina generation uses its signed route and query recovery never submits a second request", async () => {
|
|
const calls: string[] = [];
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-route-contract-"));
|
|
await writeModuleJournal(configDir, moduleJournalRecord("seedance-route-0001"));
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
generation: {
|
|
async run() { throw new Error("synchronous generation must not run"); },
|
|
async submit(input) {
|
|
calls.push(`generate:${(input as { operation?: string }).operation}`);
|
|
return taskFixture("seedance-route-0001", "running", "submitted");
|
|
},
|
|
},
|
|
});
|
|
const request = Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-route-0001",
|
|
operation: "text-to-video",
|
|
model: "seedance2.0mini",
|
|
prompt: "A short test clip",
|
|
settings: { resolution: "720p", duration: 4 },
|
|
references: [],
|
|
}));
|
|
|
|
const generated = await invoke(module, "/dreamina/generate", request) as { ok: true; result: Record<string, unknown> };
|
|
assert.equal(generated.ok, true);
|
|
assert.equal(generated.result.lifecycle, "ACCEPTED");
|
|
const listed = await invoke(module, "/dreamina/generate/tasks", undefined) as { ok: true; result: { tasks: Array<Record<string, unknown>> } };
|
|
assert.equal(listed.result.tasks[0]?.lifecycle, "ACCEPTED");
|
|
const waited = await invoke(module, "/dreamina/generate/wait", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-route-0001",
|
|
mode: "video",
|
|
}))) as { ok: true; result: Record<string, unknown> };
|
|
assert.equal(waited.result.lifecycle, "ACCEPTED");
|
|
const queried = await invoke(module, "/dreamina/generate/query", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-route-0001",
|
|
mode: "video",
|
|
}))) as { ok: true; result: Record<string, unknown> };
|
|
assert.equal(queried.result.lifecycle, "ACCEPTED");
|
|
const refreshed = await invoke(module, "/dreamina/generate/refresh", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-route-0001",
|
|
}))) as { ok: true; result: Record<string, unknown> };
|
|
assert.equal(refreshed.result.lifecycle, "ACCEPTED");
|
|
const cancelled = await invoke(module, "/dreamina/generate/cancel", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-route-0001",
|
|
}))) as { ok: true; result: Record<string, unknown> };
|
|
assert.equal(cancelled.result.lifecycle, "ACCEPTED");
|
|
assert.deepEqual(await invoke(module, "/dreamina/generate/delete", Buffer.from(JSON.stringify({
|
|
idempotencyKey: "seedance-route-0001",
|
|
}))), { ok: true, result: { deleted: true } });
|
|
assert.deepEqual(calls, ["generate:text-to-video", "get:seedance-route-0001", "wait:seedance-route-0001:video", "get:seedance-route-0001", "get:seedance-route-0001", "refresh:seedance-route-0001", "cancel:seedance-route-0001", "delete:seedance-route-0001"]);
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("Dreamina catalog projects the official Seedance minimum from its execution contract", () => {
|
|
const seedanceMini = projectDreaminaModelCatalog().find((model) => (
|
|
model.id === "seedance2.0mini" && model.modality === "video"
|
|
));
|
|
|
|
assert.deepEqual(seedanceMini, {
|
|
provider: "dreamina-cli",
|
|
id: "seedance2.0mini",
|
|
displayName: "seedance2.0mini",
|
|
modality: "video",
|
|
operations: ["text-to-video", "image-to-video", "reference-to-video"],
|
|
adapterSupported: true,
|
|
accountEntitlement: "unknown",
|
|
currentlyObservedAvailable: "unknown",
|
|
settings: {
|
|
aliases: [],
|
|
aspects: ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16"],
|
|
maxReferenceImages: 9,
|
|
minDuration: 4,
|
|
maxDuration: 15,
|
|
tiers: ["720p"],
|
|
},
|
|
source: "runtime-execution-contract",
|
|
});
|
|
});
|
|
|
|
test("Seedance 2.5 accepts the complete legal 30 image 10 video 10 audio reference set", () => {
|
|
const parsed = dreaminaCliInputSchema.safeParse({
|
|
operation: "multimodal2video",
|
|
idempotencyKey: "seedance-2-5-complete-reference-set-0001",
|
|
modelVersion: "seedance2.5",
|
|
videoResolution: "720p",
|
|
duration: 30,
|
|
referenceImages: Array.from({ length: 30 }, (_, index) => `/fixture/image-${index}.png`),
|
|
referenceVideos: Array.from({ length: 10 }, (_, index) => `/fixture/video-${index}.mp4`),
|
|
referenceAudios: Array.from({ length: 10 }, (_, index) => `/fixture/audio-${index}.wav`),
|
|
});
|
|
|
|
assert.equal(parsed.success, true);
|
|
});
|
|
|
|
test("Dreamina module lifecycle routes work without a Canvas session and accept only empty JSON mutations", async () => {
|
|
const calls: string[] = [];
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
|
|
const status = await invoke(module, "/dreamina/status", undefined);
|
|
const login = await invoke(module, "/dreamina/login", Buffer.from("{}"));
|
|
const logout = await invoke(module, "/dreamina/logout", Buffer.from("{}"));
|
|
|
|
assert.deepEqual(calls, ["status", "login", "logout"]);
|
|
assert.equal((status as { status: DreaminaPublicStatus }).status.state, "installed");
|
|
assert.equal((login as { status: DreaminaPublicStatus }).status.state, "login_pending");
|
|
assert.equal((logout as { status: DreaminaPublicStatus }).status.authenticated, false);
|
|
|
|
const invalid = await invoke(module, "/dreamina/login", Buffer.from('{"ownerId":"other"}'));
|
|
assert.deepEqual(invalid, {
|
|
ok: false,
|
|
code: "dreamina_request_invalid",
|
|
message: "Dreamina 请求参数无效",
|
|
});
|
|
assert.deepEqual(calls, ["status", "login", "logout"]);
|
|
});
|
|
|
|
test("Dreamina module invokes generation only through the explicit strict run route", async (t) => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-strict-run-"));
|
|
const calls: string[] = [];
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: lifecycleFixture(calls),
|
|
dreaminaRuntime: runtimeFixture(calls),
|
|
});
|
|
t.after(async () => {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
});
|
|
const body = Buffer.from(JSON.stringify({
|
|
operation: "text2image",
|
|
idempotencyKey: "attempt-module-0001",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
}));
|
|
|
|
const accepted = await invoke(module, "/dreamina/run", body);
|
|
assert.deepEqual(accepted, {
|
|
ok: true,
|
|
result: { state: "accepted", receiptRecorded: true },
|
|
});
|
|
assert.deepEqual(calls, ["run:text2image"]);
|
|
|
|
const rejected = await invoke(module, "/dreamina/run", Buffer.from(JSON.stringify({
|
|
operation: "text2image",
|
|
idempotencyKey: "attempt-module-0002",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
ownerId: "attacker-owner-0001",
|
|
})));
|
|
assert.deepEqual(rejected, {
|
|
ok: false,
|
|
code: "dreamina_request_invalid",
|
|
message: "Dreamina 请求参数无效",
|
|
});
|
|
assert.deepEqual(calls, ["run:text2image"]);
|
|
});
|
|
|
|
test("Dreamina run HTTP boundary fences hostile accepted Runtime results as submission unknown", async (t) => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-hostile-result-"));
|
|
let runCalls = 0;
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: authenticatedLifecycleFixture(),
|
|
dreaminaRuntime: {
|
|
...runtimeFixture([]),
|
|
run: async () => {
|
|
runCalls += 1;
|
|
return ({
|
|
state: "accepted",
|
|
submitId: "receipt-must-not-cross",
|
|
prompt: "prompt-must-not-cross",
|
|
path: "C:\\private\\result.png",
|
|
token: "token-must-not-cross",
|
|
}) as never;
|
|
},
|
|
},
|
|
});
|
|
t.after(async () => {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
});
|
|
|
|
const response = await invoke(module, "/dreamina/run", Buffer.from(JSON.stringify({
|
|
operation: "text2image",
|
|
idempotencyKey: "attempt-hostile-result-0001",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
})));
|
|
assert.deepEqual(response, {
|
|
ok: false,
|
|
code: "dreamina_submission_unknown",
|
|
message: "Dreamina 提交结果不确定,已禁止自动重试;请按 receipt 查询或人工确认",
|
|
});
|
|
assert.equal(runCalls, 1);
|
|
assert.doesNotMatch(JSON.stringify(response), /submitId|receipt-must-not-cross|prompt-must-not-cross|private|token-must-not-cross/);
|
|
});
|
|
|
|
test("Dreamina run HTTP boundary maps hostile unknown action failures to submission unknown after one invocation", async (t) => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-hostile-action-"));
|
|
let runCalls = 0;
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: authenticatedLifecycleFixture(),
|
|
dreaminaRuntime: {
|
|
...runtimeFixture([]),
|
|
run: async () => {
|
|
runCalls += 1;
|
|
throw Object.assign(new DreaminaCliError(
|
|
"hostile_transport_code",
|
|
"hostile action failure prompt-must-not-cross token-must-not-cross",
|
|
599,
|
|
), {
|
|
submitId: "receipt-must-not-cross",
|
|
prompt: "prompt-must-not-cross",
|
|
path: "C:\\private\\result.png",
|
|
token: "token-must-not-cross",
|
|
});
|
|
},
|
|
},
|
|
});
|
|
t.after(async () => {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
});
|
|
|
|
const response = await invoke(module, "/dreamina/run", Buffer.from(JSON.stringify({
|
|
operation: "text2image",
|
|
idempotencyKey: "attempt-hostile-action-0001",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
})));
|
|
assert.deepEqual(response, {
|
|
ok: false,
|
|
code: "dreamina_submission_unknown",
|
|
message: "Dreamina 提交结果不确定,已禁止自动重试;请按 receipt 查询或人工确认",
|
|
});
|
|
assert.equal(runCalls, 1);
|
|
assert.doesNotMatch(JSON.stringify(response), /submitId|receipt-must-not-cross|prompt-must-not-cross|private|token-must-not-cross|hostile action failure/);
|
|
});
|
|
|
|
test("Dreamina run HTTP boundary uses fixed public errors and fences post-dispatch failures", async (t) => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-public-errors-"));
|
|
const submissionUnknown = {
|
|
expectedCode: "dreamina_submission_unknown",
|
|
expectedMessage: "Dreamina 提交结果不确定,已禁止自动重试;请按 receipt 查询或人工确认",
|
|
expectedStatus: 409,
|
|
} as const;
|
|
const cases: Array<{
|
|
error: Error;
|
|
expectedCode: string;
|
|
expectedMessage: string;
|
|
expectedStatus: number;
|
|
}> = [
|
|
{
|
|
error: new DreaminaCliError("dreamina_request_invalid", "private request prompt token path", 599),
|
|
expectedCode: "dreamina_request_invalid",
|
|
expectedMessage: "Dreamina 请求参数无效",
|
|
expectedStatus: 400,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_idempotency_conflict", "private idempotency detail", 599),
|
|
expectedCode: "dreamina_idempotency_conflict",
|
|
expectedMessage: "同一幂等键不能用于不同 Dreamina 请求",
|
|
expectedStatus: 409,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_login_required", "private account detail", 599),
|
|
expectedCode: "dreamina_login_required",
|
|
expectedMessage: "Dreamina CLI 需要先登录",
|
|
expectedStatus: 401,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_missing", "private executable path", 599),
|
|
expectedCode: "dreamina_missing",
|
|
expectedMessage: "未检测到 Dreamina CLI",
|
|
expectedStatus: 404,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_reference_invalid", "private reference path", 599),
|
|
expectedCode: "dreamina_reference_invalid",
|
|
expectedMessage: "Dreamina 参考素材无效或不受信任",
|
|
expectedStatus: 400,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_reference_budget_exceeded", "private reference budget", 599),
|
|
expectedCode: "dreamina_reference_budget_exceeded",
|
|
expectedMessage: "Dreamina 参考素材超出大小限制",
|
|
expectedStatus: 413,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_generation_capacity_full", "private capacity detail", 599),
|
|
expectedCode: "dreamina_generation_capacity_full",
|
|
expectedMessage: "Dreamina 官方生成名额已满",
|
|
expectedStatus: 409,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_submit_spawn_failed", "private spawn path", 599),
|
|
expectedCode: "dreamina_submit_spawn_failed",
|
|
expectedMessage: "无法启动 Dreamina CLI 提交进程",
|
|
expectedStatus: 503,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_submission_unknown", "private uncertain receipt", 599),
|
|
...submissionUnknown,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_internal_error", "prompt-must-not-cross token-must-not-cross path-must-not-cross", 500),
|
|
...submissionUnknown,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_command_timeout", "private timeout detail", 504),
|
|
...submissionUnknown,
|
|
},
|
|
{
|
|
error: new DreaminaCliError("dreamina_cancelled", "private cancel detail", 499),
|
|
...submissionUnknown,
|
|
},
|
|
{
|
|
error: new LocalDreaminaGenerationError("local_generation_result_invalid", "private result path", 502),
|
|
...submissionUnknown,
|
|
},
|
|
];
|
|
let runCalls = 0;
|
|
let nextError: Error = cases[0]!.error;
|
|
const module = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: authenticatedLifecycleFixture(),
|
|
dreaminaRuntime: {
|
|
...runtimeFixture([]),
|
|
run: async () => {
|
|
runCalls += 1;
|
|
throw nextError;
|
|
},
|
|
},
|
|
});
|
|
t.after(async () => {
|
|
await module.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
});
|
|
|
|
for (const [index, item] of cases.entries()) {
|
|
nextError = item.error;
|
|
const response = await invoke(module, "/dreamina/run", Buffer.from(JSON.stringify({
|
|
operation: "text2image",
|
|
idempotencyKey: `attempt-public-error-${String(index).padStart(4, "0")}`,
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
})), item.expectedStatus);
|
|
assert.deepEqual(response, {
|
|
ok: false,
|
|
code: item.expectedCode,
|
|
message: item.expectedMessage,
|
|
});
|
|
assert.equal(runCalls, index + 1);
|
|
assert.doesNotMatch(JSON.stringify(response), /private|prompt-must-not-cross|token-must-not-cross|path-must-not-cross/);
|
|
}
|
|
});
|
|
|
|
test("Dreamina module exposes atomic product effect claim renew complete and replay", async () => {
|
|
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-module-effects-"));
|
|
await fs.writeFile(path.join(configDir, "dreamina-runtime-state.json"), JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId: "owner-dreamina-module-0001",
|
|
idempotencyKey: "dreamina-module-product-effect-0001",
|
|
requestHash: "e".repeat(64),
|
|
state: "queued",
|
|
journalVersion: 1,
|
|
updatedAt: "2026-08-13T00:00:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2image",
|
|
mode: "image",
|
|
model: "dreamina-image-3.1",
|
|
createdAt: "2026-08-13T00:00:00.000Z",
|
|
}],
|
|
}));
|
|
const first = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: authenticatedLifecycleFixture(),
|
|
dreaminaRuntime: runtimeFixture([]),
|
|
});
|
|
const second = createDreaminaHttpModule({
|
|
ownerId: "owner-dreamina-module-0001",
|
|
configDir,
|
|
dreamina: authenticatedLifecycleFixture(),
|
|
dreaminaRuntime: runtimeFixture([]),
|
|
});
|
|
const claimBody = Buffer.from(JSON.stringify({
|
|
consumerId: "web-generation-materializer",
|
|
taskId: "dreamina:dreamina-module-product-effect-0001",
|
|
effectKey: "materialize:dreamina:dreamina-module-product-effect-0001:0",
|
|
}));
|
|
try {
|
|
await invoke(first, "/dreamina/generate/tasks", undefined);
|
|
assert.deepEqual(await invoke(first, "/dreamina/generate/effects/claim", Buffer.from(JSON.stringify({
|
|
consumerId: "web-generation-materializer",
|
|
taskId: "dreamina:dreamina-module-product-effect-0001",
|
|
effectKey: "materialize:dreamina:dreamina-module-product-effect-0001:strict-json",
|
|
unexpected: true,
|
|
}))), {
|
|
ok: false,
|
|
code: "dreamina_request_invalid",
|
|
message: "Dreamina 请求参数无效",
|
|
});
|
|
const claims = await Promise.all([
|
|
invoke(first, "/dreamina/generate/effects/claim", claimBody),
|
|
invoke(second, "/dreamina/generate/effects/claim", claimBody),
|
|
]);
|
|
const statuses = claims.map((value) => (value as { result: { status: string } }).result.status).sort();
|
|
assert.deepEqual(statuses, ["busy", "claimed"]);
|
|
const claimed = claims.find((value) => (value as { result: { status: string } }).result.status === "claimed") as {
|
|
result: { leaseToken: string; fence: number };
|
|
};
|
|
const ownership = {
|
|
consumerId: "web-generation-materializer",
|
|
taskId: "dreamina:dreamina-module-product-effect-0001",
|
|
effectKey: "materialize:dreamina:dreamina-module-product-effect-0001:0",
|
|
leaseToken: claimed.result.leaseToken,
|
|
fence: claimed.result.fence,
|
|
};
|
|
const renewed = await invoke(first, "/dreamina/generate/effects/renew", Buffer.from(JSON.stringify(ownership))) as {
|
|
ok: true; result: { leaseExpiresAt: string; fence: number };
|
|
};
|
|
assert.equal(renewed.ok, true);
|
|
assert.equal(renewed.result.fence, claimed.result.fence);
|
|
assert.equal(Number.isFinite(Date.parse(renewed.result.leaseExpiresAt)), true);
|
|
assert.deepEqual(await invoke(first, "/dreamina/generate/effects/complete", Buffer.from(JSON.stringify({
|
|
...ownership,
|
|
result: { materializedAssetId: "asset-http-durable-id" },
|
|
}))), { ok: true, result: { completed: true } });
|
|
assert.deepEqual(await invoke(second, "/dreamina/generate/effects/claim", claimBody), {
|
|
ok: true,
|
|
result: {
|
|
status: "completed",
|
|
result: { materializedAssetId: "asset-http-durable-id" },
|
|
},
|
|
});
|
|
} finally {
|
|
await first.dispose?.();
|
|
await second.dispose?.();
|
|
await fs.rm(configDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
async function invoke(
|
|
module: ReturnType<typeof createDreaminaHttpModule>,
|
|
path: string,
|
|
body: Buffer | undefined,
|
|
expectedStatus?: number,
|
|
) {
|
|
const route = module.routes.find((item) => item.path === path);
|
|
assert(route);
|
|
const req = new EventEmitter() as EventEmitter & { body?: Buffer };
|
|
req.body = body;
|
|
const res = new EventEmitter() as EventEmitter & {
|
|
destroyed: boolean;
|
|
writableEnded: boolean;
|
|
statusCode: number;
|
|
json(value: unknown): void;
|
|
status(code: number): typeof res;
|
|
};
|
|
res.destroyed = false;
|
|
res.writableEnded = false;
|
|
res.statusCode = 200;
|
|
let resolve!: (value: unknown) => void;
|
|
const result = new Promise<unknown>((done) => { resolve = done; });
|
|
res.json = (value) => { res.writableEnded = true; resolve(value); };
|
|
res.status = (code) => { res.statusCode = code; return res; };
|
|
route.handler(req as never, res as never, (error?: unknown) => {
|
|
if (error) resolve(Promise.reject(error));
|
|
});
|
|
const value = await result;
|
|
if (expectedStatus !== undefined) assert.equal(res.statusCode, expectedStatus);
|
|
return value;
|
|
}
|
|
|
|
async function waitForCondition(condition: () => Promise<boolean>) {
|
|
const deadline = Date.now() + 2_000;
|
|
while (!(await condition())) {
|
|
if (Date.now() >= deadline) throw new Error("timed out waiting for Dreamina module state");
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
}
|
|
}
|
|
|
|
async function writeModuleJournal(configDir: string, record: Record<string, unknown>) {
|
|
const journalFile = path.join(configDir, "dreamina-runtime-state.json");
|
|
await fs.writeFile(journalFile, JSON.stringify({ version: 1, records: [record] }));
|
|
return journalFile;
|
|
}
|
|
|
|
function moduleJournalRecord(id: string, overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
ownerId: "owner-dreamina-module-0001",
|
|
idempotencyKey: id,
|
|
requestHash: "f".repeat(64),
|
|
state: "accepted",
|
|
journalVersion: 1,
|
|
submitId: `receipt-${id}`,
|
|
updatedAt: "2026-08-13T00:01:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
async function seedSucceededProductStore(configDir: string, id: string, accountBinding?: string) {
|
|
const result = {
|
|
mode: "video" as const,
|
|
video: {
|
|
dataUrl: `data:video/mp4;base64,${mp4Fixture().toString("base64")}`,
|
|
mimeType: "video/mp4",
|
|
bytes: mp4Fixture().byteLength,
|
|
},
|
|
};
|
|
const artifactStore = new DreaminaProviderArtifactStore({ root: path.join(configDir, "dreamina-provider-artifacts") });
|
|
const providerOutputs = await artifactStore.persistResult(result, {
|
|
ownerId: "owner-dreamina-module-0001",
|
|
idempotencyKey: id,
|
|
...(accountBinding ? { accountBinding } : {}),
|
|
mode: "video",
|
|
});
|
|
const journalFile = await writeModuleJournal(configDir, moduleJournalRecord(id, {
|
|
state: "succeeded",
|
|
officialStatus: "completed",
|
|
providerOutputs,
|
|
...(accountBinding ? { accountBinding, sessionEpoch: 1 } : {}),
|
|
}));
|
|
const store = new DreaminaTaskStore({ stateFile: path.join(configDir, "dreamina-generation-task-store.json") });
|
|
const projector = new DreaminaTaskProjector({ store, ownerId: "owner-dreamina-module-0001", journalFile });
|
|
await projector.recover();
|
|
return { journalFile, providerOutputs, result };
|
|
}
|
|
|
|
function lifecycleFixture(calls: string[]) {
|
|
return {
|
|
status: async () => { calls.push("status"); return installed; },
|
|
login: async () => {
|
|
calls.push("login");
|
|
return {
|
|
...installed,
|
|
state: "login_pending" as const,
|
|
verificationUri: "https://jimeng.jianying.com/ai-tool/cli-auth",
|
|
userCode: "CODE",
|
|
};
|
|
},
|
|
logout: async () => { calls.push("logout"); return installed; },
|
|
};
|
|
}
|
|
|
|
function runtimeFixture(calls: string[]) {
|
|
return {
|
|
run: async (input: unknown) => {
|
|
calls.push(`run:${(input as { operation?: string }).operation ?? "unknown"}`);
|
|
return { state: "accepted" as const, submitId: "receipt-module" };
|
|
},
|
|
generateToResult: async () => ({ mode: "video" as const, video: { dataUrl: "data:video/mp4;base64,AAAA", mimeType: "video/mp4", bytes: 3 } }),
|
|
resumeToResult: async () => ({ mode: "video" as const, video: { dataUrl: "data:video/mp4;base64,AAAA", mimeType: "video/mp4", bytes: 3 } }),
|
|
enqueue: async () => taskFixture("seedance-route-0001", "running", "submitted"),
|
|
listTasks: async () => [taskFixture("seedance-list-0001", "running", "submitted")],
|
|
getTask: async (idempotencyKey: string) => {
|
|
calls.push(`get:${idempotencyKey}`);
|
|
return taskFixture(idempotencyKey, "succeeded", "succeeded", {
|
|
mode: "video" as const,
|
|
video: { dataUrl: "data:video/mp4;base64,AAAA", mimeType: "video/mp4", bytes: 3 },
|
|
});
|
|
},
|
|
refreshTask: async (idempotencyKey: string) => {
|
|
calls.push(`refresh:${idempotencyKey}`);
|
|
return taskFixture(idempotencyKey, "running", "generating");
|
|
},
|
|
cancelTask: async (idempotencyKey: string) => {
|
|
calls.push(`cancel:${idempotencyKey}`);
|
|
return taskFixture(idempotencyKey, "cancelled", "cancelled");
|
|
},
|
|
deleteTask: async (idempotencyKey: string) => {
|
|
calls.push(`delete:${idempotencyKey}`);
|
|
return { deleted: true as const };
|
|
},
|
|
waitForTask: async (idempotencyKey: string, mode: "image" | "video") => {
|
|
calls.push(`wait:${idempotencyKey}:${mode}`);
|
|
return { mode: "video" as const, video: { dataUrl: "data:video/mp4;base64,AAAA", mimeType: "video/mp4", bytes: 3 } };
|
|
},
|
|
};
|
|
}
|
|
|
|
function taskFixture(
|
|
id: string,
|
|
status: "queued" | "running" | "succeeded" | "failed" | "cancelled",
|
|
stage: "queued" | "submitted" | "generating" | "succeeded" | "failed" | "cancelled" | "submission_unknown",
|
|
result?: { mode: "video"; video: { dataUrl: string; mimeType: string; bytes: number } },
|
|
) {
|
|
return {
|
|
id,
|
|
provider: "dreamina-cli" as const,
|
|
mode: "video" as const,
|
|
operation: "text2video" as const,
|
|
model: "seedance2.0mini",
|
|
status,
|
|
stage,
|
|
progress: status === "succeeded" ? 100 : 10,
|
|
receiptRecorded: true,
|
|
createdAt: "2026-08-12T00:00:00.000Z",
|
|
updatedAt: "2026-08-12T00:00:00.000Z",
|
|
...(result ? { result } : {}),
|
|
};
|
|
}
|
|
|
|
function authenticatedLifecycleFixture() {
|
|
const authenticated: DreaminaPublicStatus = {
|
|
provider: "dreamina-cli",
|
|
state: "authenticated",
|
|
installed: true,
|
|
authenticated: true,
|
|
version: "1.2.3",
|
|
message: "Dreamina CLI authenticated",
|
|
};
|
|
return {
|
|
status: async () => authenticated,
|
|
statusWithSession: async () => ({ status: authenticated, session: undefined }),
|
|
login: async () => authenticated,
|
|
logout: async () => installed,
|
|
};
|
|
}
|
|
|
|
function pngFixture() {
|
|
return Buffer.from("89504e470d0a1a0a", "hex");
|
|
}
|
|
|
|
function mp4Fixture() {
|
|
return Buffer.from("00000018667479706d703432", "hex");
|
|
}
|