fix(catalog): make the Builder Club catalog read and write its own offers
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 28s
CI / tests-unit (push) Successful in 1m39s
CI / tests-integration (push) Successful in 1m42s
CI / tests-ui (push) Successful in 2m23s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m6s
Gitea Actions Runner Test / test-job (push) Successful in 0s
CI / check (push) Successful in 28s
CI / tests-unit (push) Successful in 1m39s
CI / tests-integration (push) Successful in 1m42s
CI / tests-ui (push) Successful in 2m23s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m6s
The previous commit taught bulk editing and delete-with-restore about the BC catalog. Neither actually worked, and one of them was destructive. `catalog_items_bc` has six columns: id, item_ids, page_id, catalog_name, order_number, extradata. There is no price, points, currency, offer_id, limit or membership column on it. The bulk path read and wrote columns that do not exist, and the UPDATE was aimed at catalog_items while the SELECT came from catalog_items_bc — so a BC category move wrote into the normal catalog. Two tests now pin that pairing: reads and writes have to stay in the same table. Underneath it the BC table was never being read at all. The inline editor fetched `/api/admin/catalog/items?pageId=N` without the catalog, so opening a BC category showed the normal catalog's offers, and the route selected BC rows directly instead of going through the loader, skipping the furni enrichment the table needs to render anything but a bare caption. Both catalogs now take the same path, and the catalog is in the fetch callback's dependencies — without that, a switch keeps reading the previous catalog's rows through a stale closure. Because a BC offer has no price, the editor no longer offers one. The server refuses price, points and currency changes with a readable message instead of letting them reach the database as an unknown-column error, and a BC bulk edit is what it can actually be: a category move. BC deletions also went through a bare DELETE, which made them the one catalog mutation with no way back. They now keep their rows and hand back a restoreId like the normal ones. The catalog is recorded in the audit target rather than in the payload, so a restore can never put a BC row into the normal offers table.
This commit is contained in:
1 parent
cebcf440c5
commit
e0efbef30d
12 files changed
+548
-218
No files matched your search
@@ -1,5 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import { eq } from "drizzle-orm";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const state = vi.hoisted(() => ({
|
||||
@@ -50,6 +49,13 @@ vi.mock("@/features/catalog/server/page-commands", () => ({
|
||||
togglePageCommand: mockTogglePageCommand,
|
||||
}));
|
||||
|
||||
const mockDeleteCatalogItemsCommand = vi.hoisted(() =>
|
||||
vi.fn(async () => ({ deleted: 1, restoreId: 55 })),
|
||||
);
|
||||
vi.mock("@/features/catalog/server/item-deletes", () => ({
|
||||
deleteCatalogItemsCommand: mockDeleteCatalogItemsCommand,
|
||||
}));
|
||||
|
||||
const mockSendCatalogUpdate = vi.hoisted(() => vi.fn());
|
||||
vi.mock("@/features/catalog/server/sync-status", () => ({
|
||||
sendCatalogUpdate: mockSendCatalogUpdate,
|
||||
@@ -86,7 +92,6 @@ vi.mock("@/lib/db", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
import { CatalogItemsBc } from "@/lib/db";
|
||||
import {
|
||||
createBcItem,
|
||||
createBcPage,
|
||||
@@ -179,16 +184,25 @@ describe("updateBcPage", () => {
|
||||
});
|
||||
|
||||
describe("deleteBcItem", () => {
|
||||
it("deletes the bc item and logs activity", async () => {
|
||||
it("deletes through the restorable path and logs activity", async () => {
|
||||
const result = await deleteBcItem({ id: 7 });
|
||||
expect(result).toEqual({ ok: true });
|
||||
expect(state.deletes).toHaveLength(1);
|
||||
expect(state.deletes[0].table).toBe(CatalogItemsBc);
|
||||
expect(state.deletes[0].where).toEqual(eq(CatalogItemsBc.id, 7));
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
data: { deleted: 1, restoreId: 55 },
|
||||
});
|
||||
// Not a raw DELETE: BC rows are kept so the delete can be undone, the same
|
||||
// as the normal catalog.
|
||||
expect(mockDeleteCatalogItemsCommand).toHaveBeenCalledWith(
|
||||
[7],
|
||||
expect.anything(),
|
||||
undefined,
|
||||
"bc",
|
||||
);
|
||||
expect(state.deletes).toHaveLength(0);
|
||||
expect(mockLogStaffActivity).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "bc_item_delete",
|
||||
description: "Deleted BC catalog item #7",
|
||||
description: "Deleted BC catalog offer #7",
|
||||
}),
|
||||
);
|
||||
expect(mockSendCatalogUpdate).toHaveBeenCalled();
|
||||
@@ -197,9 +211,14 @@ describe("deleteBcItem", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("propagates delete failures", async () => {
|
||||
state.deleteError = new Error("db down");
|
||||
await expect(deleteBcItem({ id: 7 })).rejects.toThrow("db down");
|
||||
it("reports a failed delete instead of pretending it worked", async () => {
|
||||
mockDeleteCatalogItemsCommand.mockRejectedValueOnce(Error("db down"));
|
||||
mockCatalogFailure.mockReturnValueOnce({ message: "Delete failed" });
|
||||
|
||||
const result = await deleteBcItem({ id: 7 });
|
||||
|
||||
expect(result).toEqual({ ok: false, error: "Delete failed" });
|
||||
expect(mockSendCatalogUpdate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user