WFDASH-WEBAPP/scripts/smoke/match-endpoint.ts
SJCoetz 05abe499b0 feat(sprint): add new sprint pricing workflow and supporting code
Add full sprint pricing workflow and supporting infrastructure:
- New sidebar navigation entry for the sprint dashboard
- PocketBase category collection schema and migration script
- Server API routes for sprint item claims, attachments, counts, and file proxying
- Dev identity utilities and UI switcher for multi-user testing
- Reusable FilterTabs component replacing ItemStatusTabs
- Updated TypeScript types for sprint-related entities

Also include:
- Fix PocketBase file URL method casing (getUrl → getURL) across codebase
- Add development utility, smoke test, and helper scripts
- Update package.json with new sprint migration command
2026-07-03 07:29:43 +02:00

78 lines
No EOL
3.1 KiB
TypeScript

/* eslint-disable */
/**
* Smoke test — exercise the new /match endpoint end-to-end against
* the live PB + n8n workflow.
*
* Run with:
* env PB_USER_EMAIL=… PB_USER_PASSWORD=… NEXT_PUBLIC_POCKETBASE_URL=…
* npx tsx scripts/smoke/match-endpoint.ts
*
* The script:
* 1. Authenticates as a PB `users` (not superuser) account so the
* route's `assertBrowserSession` passes.
* 2. POSTs { sku, description } bodies to /api/v1/quotes/<id>/items/<itemId>/match
* using a known item id from your live PB.
* 3. Prints the candidates (sku, tier, why, cost).
*
* Replace QUOTE_ID and ITEM_ID with real ids from your PB before
* running. The endpoint exists at /api/v1/quotes/[id]/items/[itemId]/match
* — it requires the Next.js dev server to be running (or a built
* server).
*/
import PocketBase from "pocketbase";
async function main() {
const URL = process.env.NEXT_PUBLIC_POCKETBASE_URL;
const USER_EMAIL = process.env.PB_USER_EMAIL;
const USER_PASSWORD = process.env.PB_USER_PASSWORD;
const QUOTE_ID = process.env.QUOTE_ID;
const ITEM_ID = process.env.ITEM_ID;
const DASHBOARD_URL = process.env.DASHBOARD_URL || "http://127.0.0.1:3000";
if (!URL || !USER_EMAIL || !USER_PASSWORD || !QUOTE_ID || !ITEM_ID) {
throw new Error(
"Set NEXT_PUBLIC_POCKETBASE_URL, PB_USER_EMAIL, PB_USER_PASSWORD, QUOTE_ID, ITEM_ID."
);
}
// Auth as a regular PB user.
const pb = new PocketBase(URL);
await pb.collection("users").authWithPassword(USER_EMAIL, USER_PASSWORD);
const token = pb.authStore.token;
if (!token) throw new Error("PB auth succeeded but no token returned.");
console.log(`Authed as ${USER_EMAIL} (token length ${token.length})`);
async function tryMatch(label: string, body: Record<string, string>) {
const url = `${DASHBOARD_URL.replace(/\/$/, "")}/api/v1/quotes/${QUOTE_ID}/items/${ITEM_ID}/match`;
console.log(`\n ${label}`);
console.log(` body: ${JSON.stringify(body)}`);
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: token },
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) {
console.log(` HTTP ${res.status}: ${data.error || "(no error message)"}`);
return;
}
const candidates = Array.isArray(data.candidates) ? data.candidates : [];
console.log(` ${candidates.length} candidate(s)`);
for (const c of candidates) {
console.log(` tier ${c.tier} ${c.sku} R${c.cost_price} "${c.why}"`);
}
}
console.log("=== /match endpoint smoke test ===");
await tryMatch("Tier 1 — exact SKU", { sku: "JEITD-06" });
await tryMatch("Tier 2 — SKU substring", { sku: "JEITD" });
await tryMatch("Tier 3 — fuzzy description", { description: "TURBO" });
await tryMatch("Tier 3 — fuzzy 'twist'", { description: "twist" });
await tryMatch("SKU + description combo", { sku: "JEITD", description: "turbo" });
await tryMatch("Both empty (400 expected)", { sku: "", description: "" });
await tryMatch("Garbage (no match expected)", { sku: "ZZZGARBAGE", description: "" });
}
main().catch((e) => {
console.error(e);
process.exit(1);
});