feat(catalog): persist idempotent bulk operations and retryable deliveries

This commit is contained in:
Simo committed 2026-09-13 18:05:03 +02:00
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();
});