fix(studio): move AI generation behind short-lived job requests
This commit is contained in:
1 parent
e8c0f06d8f
commit
58687d8679
7 files changed
+248
-8
No files matched your search
@@ -319,3 +319,91 @@ test("a retry followed by an invalid design does not spend a third request or mi
|
||||
expect(batch.diagnostics[0].code).toBe("WRN-STUDIO-PROPOSAL");
|
||||
expect(batch.diagnostics[0].message).toContain("schema");
|
||||
});
|
||||
|
||||
test("async generation acknowledges immediately and polling retrieves the validated batch", async () => {
|
||||
let finish!: () => void;
|
||||
let entered!: () => void;
|
||||
const started = new Promise<void>((resolve) => {
|
||||
entered = resolve;
|
||||
});
|
||||
const blocked = new Promise<void>((resolve) => {
|
||||
finish = resolve;
|
||||
});
|
||||
const handler = studioAI(
|
||||
"test-secret",
|
||||
mockFetch(async () => {
|
||||
entered();
|
||||
await blocked;
|
||||
return completion();
|
||||
}),
|
||||
);
|
||||
const original = await request();
|
||||
const headers = new Headers(original.headers);
|
||||
headers.set("prefer", "respond-async");
|
||||
const accepted = await handler(new Request(original, { headers }));
|
||||
expect(accepted!.status).toBe(202);
|
||||
const { job } = await accepted!.json();
|
||||
await started;
|
||||
const url = `https://components.example/design-studio/api/generate/jobs/${job.id}`;
|
||||
expect((await (await handler(new Request(url)))!.json()).status).toBe(
|
||||
"pending",
|
||||
);
|
||||
expect(
|
||||
(await handler(
|
||||
new Request(url, { headers: { origin: "https://other.example" } }),
|
||||
))!.status,
|
||||
).toBe(403);
|
||||
finish();
|
||||
let result: any;
|
||||
for (let i = 0; i < 50; i++) {
|
||||
result = await (await handler(new Request(url)))!.json();
|
||||
if (result.status !== "pending") break;
|
||||
await Bun.sleep(10);
|
||||
}
|
||||
expect(result.status).toBe("complete");
|
||||
expect(result.result.alternatives).toHaveLength(3);
|
||||
expect(
|
||||
(await handler(
|
||||
new Request(url.replace(job.id, "00000000-0000-4000-8000-000000000000")),
|
||||
))!.status,
|
||||
).toBe(410);
|
||||
});
|
||||
|
||||
test("background generation cancellation aborts the provider and leaves no proposals", async () => {
|
||||
let entered!: () => void;
|
||||
const started = new Promise<void>((resolve) => {
|
||||
entered = resolve;
|
||||
});
|
||||
let aborted = false;
|
||||
const handler = studioAI(
|
||||
"test-secret",
|
||||
mockFetch(async (_url, init) => {
|
||||
entered();
|
||||
return await new Promise<Response>((_, reject) => {
|
||||
init!.signal!.addEventListener(
|
||||
"abort",
|
||||
() => {
|
||||
aborted = true;
|
||||
reject(new DOMException("cancelled", "AbortError"));
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
}),
|
||||
);
|
||||
const original = await request();
|
||||
const headers = new Headers(original.headers);
|
||||
headers.set("prefer", "respond-async");
|
||||
const { job } = await (await handler(
|
||||
new Request(original, { headers }),
|
||||
))!.json();
|
||||
await started;
|
||||
const response = await handler(
|
||||
new Request(
|
||||
`https://components.example/design-studio/api/generate/jobs/${job.id}`,
|
||||
{ method: "DELETE" },
|
||||
),
|
||||
);
|
||||
expect((await response!.json()).status).toBe("cancelled");
|
||||
expect(aborted).toBe(true);
|
||||
});
|
||||
Reference in new issue
Block a user