feat(catalog): persist idempotent bulk operations and retryable deliveries
This commit is contained in:
1 parent
76e46d295c
commit
13665e0c3c
53 files changed
+1316
-76
No files matched your search
@@ -57,10 +57,8 @@ it("gates edits before database and sends one update after commit", async () =>
|
||||
expect((await applyBulkOffers(input, "x")).ok).toBe(true);
|
||||
expect(state.permission).toHaveBeenCalledWith("edit");
|
||||
expect(state.export).toHaveBeenCalledTimes(1);
|
||||
expect(state.send).toHaveBeenCalledTimes(1);
|
||||
expect(state.apply.mock.invocationCallOrder[0]).toBeLessThan(
|
||||
state.send.mock.invocationCallOrder[0],
|
||||
);
|
||||
expect(state.send).not.toHaveBeenCalled();
|
||||
expect(state.refresh).toHaveBeenCalled();
|
||||
});
|
||||
it("stops denied permission before export or data access", async () => {
|
||||
state.permission.mockRejectedValueOnce(Error("Denied"));
|
||||
@@ -76,7 +74,7 @@ it("does not notify hotel when a transaction fails", async () => {
|
||||
it("does not report a committed edit as failed on audit failure", async () => {
|
||||
state.audit.mockRejectedValueOnce(Error("log failed"));
|
||||
expect((await applyBulkOffers(input, "x")).ok).toBe(true);
|
||||
expect(state.send).toHaveBeenCalledTimes(1);
|
||||
expect(state.send).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("loads destination choices with view permission only", async () => {
|
||||
@@ -95,9 +93,9 @@ it("undo enforces edit access and exports only successful restores", async () =>
|
||||
state.undo.mockResolvedValueOnce({ changedCount: 2 });
|
||||
expect((await undoBulkOffers([10, 11])).ok).toBe(true);
|
||||
expect(state.permission).toHaveBeenCalledWith("edit");
|
||||
expect(state.undo).toHaveBeenCalledWith([10, 11], 1);
|
||||
expect(state.undo).toHaveBeenCalledWith([10, 11], 1, undefined);
|
||||
expect(state.export).toHaveBeenCalledTimes(1);
|
||||
expect(state.send).toHaveBeenCalledTimes(1);
|
||||
expect(state.send).not.toHaveBeenCalled();
|
||||
});
|
||||
it("undo denial stops before export and a conflict does not notify", async () => {
|
||||
state.permission.mockRejectedValueOnce(Error("Denied"));
|
||||
@@ -107,3 +105,10 @@ it("undo denial stops before export and a conflict does not notify", async () =>
|
||||
expect((await undoBulkOffers([10])).ok).toBe(false);
|
||||
expect(state.send).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes a stable operation key and does not synchronously notify after commit", async () => {
|
||||
const key = "123e4567-e89b-42d3-a456-426614174000";
|
||||
expect((await applyBulkOffers(input, "x", key)).ok).toBe(true);
|
||||
expect(state.apply).toHaveBeenCalledWith(input, "x", 1, key);
|
||||
expect(state.send).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -115,6 +115,17 @@ vi.mock("@/lib/db", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/features/operations/server", async () => {
|
||||
const { db } = await import("@/lib/db");
|
||||
return {
|
||||
runOperation: (
|
||||
_input: unknown,
|
||||
work: (tx: unknown, id: string) => Promise<unknown>,
|
||||
) => db.transaction((tx) => work(tx, "operation-fixture")),
|
||||
enqueueEffect: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import {
|
||||
applyBulkOffersCommand,
|
||||
listBulkOfferDestinationsCommand,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -79,6 +79,17 @@ vi.mock("@/lib/db", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/features/operations/server", async () => {
|
||||
const { db } = await import("@/lib/db");
|
||||
return {
|
||||
runOperation: (
|
||||
_input: unknown,
|
||||
work: (tx: unknown, id: string) => Promise<unknown>,
|
||||
) => db.transaction((tx) => work(tx, "operation-fixture")),
|
||||
enqueueEffect: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
import { undoBulkOffersCommand } from "./bulk-offers";
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
Reference in new issue
Block a user