/* eslint-disable */ /** * Smoke test — exercise the re-price flow end-to-end. * * 1. Pick an existing quote with at least one quote_item. * 2. Snapshot the current state of N items. * 3. Set SKU on each item to a real catalogue row. * 4. Run the same matcher the endpoint uses (findFirstPriceListBySku), * emulate the patch the endpoint would write. * 5. Restore the original values (so we don't leave the live DB * in a half-baked state for the test). * * Run with: * env PB_ADMIN_EMAIL=… PB_ADMIN_PASSWORD=… NEXT_PUBLIC_POCKETBASE_URL=… * npx tsx scripts/smoke/re-price.ts */ import { findFirstPriceListBySku, type PriceListMatch, } from "../../src/lib/server/price-list-match"; import PocketBase from "pocketbase"; type QuoteItemSnapshot = { id: string; cost_price?: number; selling_price?: number; description?: string; supplier?: string; sku?: string; pricing_source?: string; matched_price_list_id?: string; pricing_notes?: string; }; async function main() { const URL = process.env.NEXT_PUBLIC_POCKETBASE_URL; const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL; const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD; if (!URL || !ADMIN_EMAIL || !ADMIN_PASSWORD) { throw new Error("Set PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, NEXT_PUBLIC_POCKETBASE_URL."); } const pb = new PocketBase(URL); await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD); // 1. Find a quote with at least 2 items we can play with. const candidates = await pb.collection("quote_items").getList(1, 30, { sort: "+created", }); if (candidates.items.length < 2) { throw new Error("Need ≥2 quote items to run this smoke test."); } const items = candidates.items.slice(0, 3); const quoteId = items[0].quote; // 2. Snapshot the originals so we can restore at the end. const snapshots: QuoteItemSnapshot[] = items.map((it) => ({ id: it.id, cost_price: it.cost_price ?? 0, selling_price: it.selling_price ?? 0, description: it.description ?? "", supplier: it.supplier ?? "", sku: it.sku ?? "", pricing_source: it.pricing_source ?? "", matched_price_list_id: it.matched_price_list_id ?? "", pricing_notes: it.pricing_notes ?? "", })); console.log(`[re-price smoke] quote=${quoteId}, picked items: ${items.map((it) => it.id).join(", ")}`); // 3. SKUs to inject. Two real catalogue SKUs + one nonsense. const testCases: Array<{ id: string; sku: string; label: string }> = [ { id: items[0].id, sku: "JEITD-06", label: "exact (twist drill 6mm)" }, { id: items[1].id, sku: "jeitd-08", label: "case-insensitive (twist drill 8mm)" }, { id: items[2].id, sku: "NOPE-NOPE-99", label: "garbage (expect no match)" }, ]; for (const tc of testCases) { await pb.collection("quote_items").update(tc.id, { sku: tc.sku }); } // 4. Run the matcher + emulate the patch the endpoint writes. console.log("\n[re-price smoke] Running matcher over each item"); for (const tc of testCases) { const match = await findFirstPriceListBySku(tc.sku, {}, pb as unknown as PocketBase); if (!match) { console.log(` ${tc.label.padEnd(38)} → no match (expected for "${tc.sku}")`); await pb.collection("quote_items").update(tc.id, { pricing_notes: `No match for SKU "${tc.sku}"`, }); continue; } const patch = { cost_price: match.cost_price, selling_price: match.selling_price, description: (match.description ?? "").trim() || (snapshots.find((s) => s.id === tc.id)?.description ?? ""), pricing_source: "catalogue", matched_price_list_id: match.id, pricing_notes: `Auto-matched from ${match.supplier} on ${new Date().toISOString().slice(0, 10)}`, }; console.log(` ${tc.label.padEnd(38)} → matched ${match.sku} (${match.supplier})`); console.log(` patch: cost=${patch.cost_price}, sell=${patch.selling_price}, pnotes="${patch.pricing_notes}"`); await pb.collection("quote_items").update(tc.id, patch); } // 5. Verify by re-reading. console.log("\n[re-price smoke] Verifying patched state"); for (const tc of testCases) { const it = await pb.collection("quote_items").getOne(tc.id); console.log(` ${tc.label.padEnd(38)} sku="${it.sku}" cost=${it.cost_price} sell=${it.selling_price} source="${it.pricing_source}" mid="${it.matched_price_list_id}"`); } // 6. Restore — don't leave the live DB half-set. console.log("\n[re-price smoke] Restoring snapshots"); for (const snap of snapshots) { await pb.collection("quote_items").update(snap.id, { cost_price: snap.cost_price, selling_price: snap.selling_price, description: snap.description, supplier: snap.supplier || null, sku: snap.sku, pricing_source: snap.pricing_source, matched_price_list_id: snap.matched_price_list_id, pricing_notes: snap.pricing_notes, }); } console.log("[re-price smoke] Done. DB restored."); } main().catch((e) => { console.error(e); process.exit(1); });