fix(import): honor cancellation after ID reservation waits
This commit is contained in:
1 parent
36ac116dc2
commit
e7549bbbd2
2 files changed
+43
No files matched your search
@@ -60,6 +60,7 @@ vi.mock("@/lib/services/swf-to-nitro", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
import { importSingleFurni } from "./furni-import";
|
import { importSingleFurni } from "./furni-import";
|
||||||
|
import { reserveFurnitureId } from "./furniture-id-reservation";
|
||||||
|
|
||||||
const input = {
|
const input = {
|
||||||
id: 42,
|
id: 42,
|
||||||
@@ -120,6 +121,46 @@ describe("completion of existing furniture", () => {
|
|||||||
expect(mocks.execute).not.toHaveBeenCalled();
|
expect(mocks.execute).not.toHaveBeenCalled();
|
||||||
expect(mocks.append).not.toHaveBeenCalled();
|
expect(mocks.append).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
it("cancels while waiting for another ID reservation without subsequent writes", async () => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const held = Promise.withResolvers<void>();
|
||||||
|
const release = Promise.withResolvers<void>();
|
||||||
|
const writing = Promise.withResolvers<void>();
|
||||||
|
mocks.select.mockImplementation(() => ({
|
||||||
|
from: () => ({ where: () => ({ limit: async () => [] }) }),
|
||||||
|
}));
|
||||||
|
mocks.execute.mockImplementation(async (query) => {
|
||||||
|
const { sql } = dialect.sqlToQuery(query);
|
||||||
|
return sql.includes("SELECT id, page_id")
|
||||||
|
? [[{ id: 123, page_id: "15" }], []]
|
||||||
|
: [[], []];
|
||||||
|
});
|
||||||
|
const prior = reserveFurnitureId(41, "prior_furniture", async () => {
|
||||||
|
held.resolve();
|
||||||
|
await release.promise;
|
||||||
|
});
|
||||||
|
await held.promise;
|
||||||
|
const importing = importSingleFurni({
|
||||||
|
...input,
|
||||||
|
signal: controller.signal,
|
||||||
|
onProgress: (stage) => {
|
||||||
|
if (stage === "writing_db") writing.resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await writing.promise;
|
||||||
|
controller.abort();
|
||||||
|
release.resolve();
|
||||||
|
await prior;
|
||||||
|
await expect(importing).rejects.toMatchObject({ name: "AbortError" });
|
||||||
|
expect(
|
||||||
|
queries().every((query) => query.sql.trim().startsWith("SELECT")),
|
||||||
|
).toBe(true);
|
||||||
|
expect(mocks.append).not.toHaveBeenCalled();
|
||||||
|
// Cancellation also releases the reservation queue for the next importer.
|
||||||
|
await expect(
|
||||||
|
reserveFurnitureId(43, "next_furniture", async () => {}),
|
||||||
|
).resolves.toBe(43);
|
||||||
|
});
|
||||||
it("never deletes existing SQL when the nitro cannot be recovered", async () => {
|
it("never deletes existing SQL when the nitro cannot be recovered", async () => {
|
||||||
mocks.stat.mockRejectedValue(new Error("missing"));
|
mocks.stat.mockRejectedValue(new Error("missing"));
|
||||||
const result = await importSingleFurni(input);
|
const result = await importSingleFurni(input);
|
||||||
|
|||||||
@@ -857,6 +857,7 @@ export async function importSingleFurni(params: {
|
|||||||
originalId,
|
originalId,
|
||||||
classname,
|
classname,
|
||||||
async (allocatedId) => {
|
async (allocatedId) => {
|
||||||
|
signal?.throwIfAborted();
|
||||||
await db.execute(
|
await db.execute(
|
||||||
sql`INSERT INTO items_base (id, sprite_id, public_name, item_name, type) VALUES (${allocatedId}, ${allocatedId}, ${bestName}, ${classname}, ${itemType})`,
|
sql`INSERT INTO items_base (id, sprite_id, public_name, item_name, type) VALUES (${allocatedId}, ${allocatedId}, ${bestName}, ${classname}, ${itemType})`,
|
||||||
);
|
);
|
||||||
@@ -869,6 +870,7 @@ export async function importSingleFurni(params: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (signal?.aborted) throw err;
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
warnings,
|
warnings,
|
||||||
|
|||||||
Reference in new issue
Block a user