feat(catalog): undo bulk offer edits with guarded history restoration
This commit is contained in:
1 parent
13bd3695cb
commit
c5a2b44807
33 files changed
+1399
-116
No files matched your search
@@ -27,6 +27,7 @@ const state = vi.hoisted(() => ({
|
||||
],
|
||||
queries: [] as string[],
|
||||
writes: 0,
|
||||
audit: [] as Array<{ before: string; after: string; target: string }>,
|
||||
failAt: 0,
|
||||
commits: 0,
|
||||
rollbacks: 0,
|
||||
@@ -36,11 +37,35 @@ vi.mock("@/lib/db", () => ({
|
||||
execute: async () => [state.pages, []],
|
||||
transaction: async (fn: (tx: unknown) => Promise<unknown>) => {
|
||||
const before = state.writes;
|
||||
const beforeRows = structuredClone(state.rows);
|
||||
const beforeAudit = [...state.audit];
|
||||
try {
|
||||
const result = await fn({
|
||||
execute: async (query: SQL) => {
|
||||
const text = new MySqlDialect().sqlToQuery(query).sql;
|
||||
const { sql: text, params } = new MySqlDialect().sqlToQuery(query);
|
||||
state.queries.push(text);
|
||||
if (
|
||||
text.startsWith("SELECT") &&
|
||||
!text.includes("catalog_name") &&
|
||||
text.includes("catalog_items")
|
||||
) {
|
||||
const row = state.rows.find(
|
||||
(row) => row.id === Number(params.at(-1)),
|
||||
);
|
||||
return [
|
||||
row
|
||||
? [
|
||||
{
|
||||
pageId: String(row.pageId),
|
||||
costCredits: row.costCredits,
|
||||
costPoints: row.costPoints,
|
||||
pointsType: row.pointsType,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[],
|
||||
];
|
||||
}
|
||||
if (text.startsWith("SELECT"))
|
||||
return [
|
||||
text.includes("catalog_pages") ? state.pages : state.rows,
|
||||
@@ -48,13 +73,41 @@ vi.mock("@/lib/db", () => ({
|
||||
];
|
||||
state.writes++;
|
||||
if (state.writes === state.failAt) throw Error("write failed");
|
||||
const row = state.rows.find(
|
||||
(row) => row.id === Number(params.at(-1)),
|
||||
);
|
||||
if (row) {
|
||||
const columns = {
|
||||
page_id: "pageId",
|
||||
cost_credits: "costCredits",
|
||||
cost_points: "costPoints",
|
||||
points_type: "pointsType",
|
||||
} as const;
|
||||
const assignments =
|
||||
text.split(" SET ")[1]?.split(" WHERE ")[0] ?? "";
|
||||
let index = 0;
|
||||
for (const assignment of assignments.split(", ")) {
|
||||
const name = assignment.match(
|
||||
/`([^`]+)`/,
|
||||
)?.[1] as keyof typeof columns;
|
||||
if (columns[name]) row[columns[name]] = Number(params[index++]);
|
||||
}
|
||||
}
|
||||
return [{ affectedRows: 1 }, []];
|
||||
},
|
||||
insert: () => ({
|
||||
values: async (entry: (typeof state.audit)[number]) => {
|
||||
state.audit.push(entry);
|
||||
return [{ insertId: 1000 + state.audit.length }];
|
||||
},
|
||||
}),
|
||||
});
|
||||
state.commits++;
|
||||
return result;
|
||||
} catch (error) {
|
||||
state.writes = before;
|
||||
state.rows = beforeRows;
|
||||
state.audit = beforeAudit;
|
||||
state.rollbacks++;
|
||||
throw error;
|
||||
}
|
||||
@@ -96,6 +149,7 @@ beforeEach(() => {
|
||||
{ id: 9, caption: "Target" },
|
||||
];
|
||||
state.queries = [];
|
||||
state.audit = [];
|
||||
state.writes = 0;
|
||||
state.failAt = 0;
|
||||
state.commits = 0;
|
||||
@@ -192,3 +246,21 @@ it("does not update offers whose chosen values already match", async () => {
|
||||
await applyBulkOffersCommand(request, p.fingerprint);
|
||||
expect(state.writes).toBe(0);
|
||||
});
|
||||
|
||||
it("records category-only bulk edits as restorable history and returns durable IDs", async () => {
|
||||
const request = { ids: [1, 2], changes: { pageId: 9 } };
|
||||
const preview = await previewBulkOffersCommand(request);
|
||||
expect(await applyBulkOffersCommand(request, preview.fingerprint, 7)).toEqual(
|
||||
{ changedCount: 2, historyIds: [1001, 1002] },
|
||||
);
|
||||
expect(state.audit).toHaveLength(2);
|
||||
expect(state.audit[0].target).toBe("catalog_offer");
|
||||
expect(JSON.parse(state.audit[0].before)).toMatchObject({
|
||||
pageId: "4",
|
||||
costCredits: 3,
|
||||
});
|
||||
expect(JSON.parse(state.audit[0].after)).toMatchObject({
|
||||
pageId: "9",
|
||||
costCredits: 3,
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user