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; }, ); });