fix: recover matching durable image video submission receipts

This commit is contained in:
Wenxin Duan
2026-09-15 01:14:25 +08:00
parent 40e03e393c
commit cc80265bb2
2 changed files with 59 additions and 3 deletions
@@ -95,4 +95,46 @@ describe("durable image video tasks", () => {
const receipt = JSON.parse(await readFile(join(f.options.root, task.id, "submission-receipt.json"), "utf8")) as { jobId: string };
expect(receipt.jobId).toBe("late_job");
});
it("recovers a matching persisted submit receipt after restart and polls that job without resubmitting", async () => {
const f = await fixture(); const task = await f.service.create("ses_owner", f.draft);
await f.service.upload("ses_owner", task.id); await f.service.refresh(task.id);
const file = join(f.options.root, task.id, "record.json"); const record = JSON.parse(await readFile(file, "utf8")) as ImageVideoRecord;
record.state = "submitting"; record.revision++;
await writeFile(file, JSON.stringify(record));
await writeFile(join(f.options.root, task.id, "submission-receipt.json"), JSON.stringify({ version: 1, id: task.id, requestHash: record.requestHash, revision: record.revision, jobId: "accepted_before_crash" }));
f.calls.length = 0;
f.remote.status = (r) => { f.calls.push(`status:${r.jobId ?? "missing"}`); return Promise.resolve({ state: "running" }); };
const restored = new harness.ImageVideoService(f.options);
expect(await restored.view(task.id)).toMatchObject({ state: "queued", jobId: "accepted_before_crash" });
expect(JSON.parse(await readFile(file, "utf8")) as ImageVideoRecord).toMatchObject({ state: "queued", jobId: "accepted_before_crash", revision: record.revision + 1 });
expect(await restored.refresh(task.id)).toMatchObject({ state: "running", jobId: "accepted_before_crash" });
await new harness.ImageVideoService(f.options).submit("ses_owner", task.id);
expect(f.calls).toEqual(["status:accepted_before_crash"]);
});
it.each(["task", "hash", "stale", "future", "malformed", "empty_job", "version", "extra_field", "conflicting_job"] as const)("quarantines a %s receipt without polling it or submitting again", async (kind) => {
const f = await fixture(); const task = await f.service.create("ses_owner", f.draft);
await f.service.upload("ses_owner", task.id); await f.service.refresh(task.id);
const file = join(f.options.root, task.id, "record.json"); const record = JSON.parse(await readFile(file, "utf8")) as ImageVideoRecord;
record.state = "submitting"; record.revision++;
delete record.lastPollAt;
if (kind === "conflicting_job") record.jobId = "different_record_job";
const receipt: Record<string, unknown> = { version: 1, id: task.id, requestHash: record.requestHash, revision: record.revision, jobId: "untrusted_receipt_job" };
if (kind === "task") receipt.id = "i2v_11111111-1111-1111-1111-111111111111";
if (kind === "hash") receipt.requestHash = "a".repeat(64);
if (kind === "stale") receipt.revision = record.revision - 1;
if (kind === "future") receipt.revision = record.revision + 1;
if (kind === "empty_job") receipt.jobId = "";
if (kind === "version") receipt.version = 2;
if (kind === "extra_field") receipt.unexpected = "conflicting format";
const serialized = kind === "malformed" ? "{broken-json" : JSON.stringify(receipt);
await writeFile(file, JSON.stringify(record));
const receiptPath = join(f.options.root, task.id, "submission-receipt.json"); await writeFile(receiptPath, serialized);
f.calls.length = 0;
const restored = new harness.ImageVideoService(f.options);
expect((await restored.view(task.id)).state).toBe("unknown_submission");
expect((await restored.refresh(task.id)).state).toBe("unknown_submission");
await expect(restored.submit("ses_owner", task.id)).rejects.toMatchObject({ code: "I2V_UNKNOWN_SUBMISSION" });
expect(f.calls).toEqual([]);
expect(await readFile(receiptPath, "utf8")).toBe(serialized);
});
});