fix(review): adopt latest server_rev on PUT; retry once on 409

Review was POSTing price/sku to /review before each save; that endpoint
persists those overrides and bumps server_rev, so the following PUT sent
a stale client_rev and hit 409 — Continue to Publish stayed disabled.
Stop sending price/sku on review probes (PUT already persists them) and
have updateSlab refetch + retry once on revision_conflict.
This commit is contained in:
grokbot_ty
2026-09-25 18:14:38 -07:00
parent f5b84f1ad7
commit 2ac0f6f48e
3 changed files with 185 additions and 5 deletions
+146
View File
@@ -0,0 +1,146 @@
import assert from 'node:assert/strict';
import { afterEach, test } from 'node:test';
import { ApiError, updateSlab, type Slab } from './slabs';
const SLAB_ID = 'slab-rev-1';
function baseSlab(server_rev: number): Slab {
return {
id: SLAB_ID,
sku: 'SLAB-01',
status: 'calibrated',
price: 100,
price_source: 'recommendation',
length_in: 48,
thickness_in: 2,
sqft: 2.4,
bdft: 4.8,
width_min_in: 6,
width_max_in: 9,
width_avg_in: 8,
species_id: 1,
wood_category_ids: [2],
edge_type_term_id: 3,
figure_term_ids: [],
grade_term_id: 4,
thickness_term_id: 5,
moisture_term_id: 6,
fig_tag_ids: [],
feat_tag_ids: [],
title: 'Maple Slab',
short_description: 'short',
description: null,
content_source: 'manual',
species_confidence: null,
figure_confidence: null,
grade_confidence: null,
inference_status: null,
calibration_photo_id: null,
woo_product_id: null,
client_rev: 1,
server_rev,
created_at: 't',
updated_at: 't',
};
}
type Call = { method: string; url: string; body: unknown };
const calls: Call[] = [];
let handler: (method: string, url: string, body: unknown) => { status: number; body: unknown };
const origFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = origFetch;
calls.length = 0;
});
function mockFetch() {
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = String(input);
const method = (init?.method ?? 'GET').toUpperCase();
const body = init?.body ? JSON.parse(String(init.body)) : null;
calls.push({ method, url, body });
const { status, body: resp } = handler(method, url, body);
return new Response(JSON.stringify(resp), {
status,
headers: { 'Content-Type': 'application/json' },
});
}) as typeof fetch;
}
test('updateSlab succeeds on first PUT when client_rev matches', async () => {
mockFetch();
handler = (method, _url, body) => {
assert.equal(method, 'PUT');
const patch = body as { client_rev: number };
assert.equal(patch.client_rev, 3);
return { status: 200, body: baseSlab(4) };
};
const out = await updateSlab(SLAB_ID, { client_rev: 3, title: 'Maple Slab' });
assert.equal(out.server_rev, 4);
assert.equal(calls.length, 1);
});
test('updateSlab on 409 refetches, adopts server_rev, retries once', async () => {
mockFetch();
let puts = 0;
handler = (method, url, body) => {
if (method === 'PUT') {
puts += 1;
const patch = body as { client_rev: number; title?: string };
if (puts === 1) {
assert.equal(patch.client_rev, 9); // stale (e.g. review POST bumped)
return {
status: 409,
body: {
error: {
code: 'revision_conflict',
message: 'client_rev mismatch',
detail: { current_server_rev: 10 },
},
},
};
}
assert.equal(patch.client_rev, 10); // adopted from GET
assert.equal(patch.title, 'Maple Slab');
return { status: 200, body: baseSlab(11) };
}
if (method === 'GET' && url.includes(SLAB_ID)) {
return { status: 200, body: baseSlab(10) };
}
throw new Error(`unexpected ${method} ${url}`);
};
const out = await updateSlab(SLAB_ID, { client_rev: 9, title: 'Maple Slab' });
assert.equal(out.server_rev, 11);
assert.equal(puts, 2);
assert.equal(calls.map((c) => c.method).join(','), 'PUT,GET,PUT');
});
test('updateSlab second 409 surfaces a reload message', async () => {
mockFetch();
handler = (method) => {
if (method === 'PUT') {
return {
status: 409,
body: {
error: {
code: 'revision_conflict',
message: 'client_rev mismatch',
detail: { current_server_rev: 99 },
},
},
};
}
return { status: 200, body: baseSlab(99) };
};
await assert.rejects(
() => updateSlab(SLAB_ID, { client_rev: 1, sku: 'X' }),
(e: unknown) => {
assert.ok(e instanceof ApiError);
assert.equal(e.status, 409);
assert.match(e.message, /Reload the page/i);
return true;
},
);
});