examples/storefront Public
import assert from "node:assert/strict";import { test } from "node:test";import { toMatch } from "../src/search.ts";import { ADMIN_TOKEN, setup } from "./helpers/app.ts";const ids = (res: { body: { products: { id: string }[] } }) => res.body.products.map((p) => p.id);async function shop() { const s = setup(); await s.addProduct("linen-apron", { name: "Linen work apron", description: "Heavy linen with two front pockets and cross-back straps.", price_cents: 5200 }); await s.addProduct("canvas-tote", { name: "Waxed canvas tote", description: "A 20 litre tote with leather handles. Pairs well with the linen apron.", price_cents: 4800 }); await s.addProduct("enamel-mug", { name: "Enamel camp mug", description: "A 350 ml steel mug with a rolled rim.", price_cents: 1600 }); await s.addProduct("creme-soap", { name: "Crème fraîche soap", description: "Cold process, unscented.", price_cents: 900 }); return s;}test("toMatch quotes every word and matches the last one as a prefix", () => { assert.equal(toMatch("linen apr"), '"linen" "apr"*'); assert.equal(toMatch(" Mug! "), '"Mug"*');});test("toMatch turns FTS5 syntax into plain words", () => { assert.equal(toMatch('apron" OR name:mug NEAR(x'), '"apron" "OR" "name" "mug" "NEAR" "x"*'); assert.equal(toMatch("*** --- ()"), null);});test("finds products by name and description, name matches first", async () => { const s = await shop(); const res = await s.call("GET", "/search?q=linen"); assert.equal(res.status, 200); assert.equal(res.body.query, "linen"); assert.deepEqual(ids(res), ["linen-apron", "canvas-tote"]);});test("matches while the shopper is still typing, across word forms and accents", async () => { const s = await shop(); assert.deepEqual(ids(await s.call("GET", "/search?q=enam")), ["enamel-mug"]); assert.deepEqual(ids(await s.call("GET", "/search?q=mugs")), ["enamel-mug"]); assert.deepEqual(ids(await s.call("GET", "/search?q=creme")), ["creme-soap"]);});test("results carry the catalog's current price and stock", async () => { const s = await shop(); const cartId = (await s.call("POST", "/carts")).body.cart.id; await s.call("PUT", `/carts/${cartId}/items/enamel-mug`, { quantity: 2 }); await s.call("POST", `/carts/${cartId}/checkout`, { email: "ada@park.test", ship_to: { name: "Ada Park", line1: "12 Alder St", city: "Portland", postal_code: "97205", country: "US" }, }); const [mug] = (await s.call("GET", "/search?q=mug")).body.products; assert.equal(mug.stock, 3); assert.equal(mug.price_cents, 1600);});test("an edited product is found by its new text, a retired one not at all", async () => { const s = await shop(); await s.addProduct("enamel-mug", { name: "Enamel camp cup", description: "A 350 ml steel cup." }); assert.deepEqual(ids(await s.call("GET", "/search?q=mug")), []); assert.deepEqual(ids(await s.call("GET", "/search?q=cup")), ["enamel-mug"]); await s.addProduct("linen-apron", { name: "Linen work apron", active: false }); assert.deepEqual(ids(await s.call("GET", "/search?q=apron")), ["canvas-tote"]);});test("limit caps the results and q is required", async () => { const s = await shop(); assert.deepEqual(ids(await s.call("GET", "/search?q=linen&limit=1")), ["linen-apron"]); assert.equal((await s.call("GET", "/search?q=%20")).body.error, "bad_query"); assert.equal((await s.call("GET", `/search?q=${"a".repeat(101)}`)).body.error, "bad_query"); assert.equal((await s.call("GET", "/search?q=linen&limit=0")).body.error, "bad_limit"); assert.deepEqual(ids(await s.call("GET", "/search?q=%2A%2A")), []);});test("a rebuild puts every active product back in the index", async () => { const s = await shop(); // A product written straight to the catalog, as a seed script does, is not in the index yet. await s.env.DB.prepare( "INSERT INTO products (id, name, description, price_cents, stock, active, created_at, updated_at) VALUES ('wool-socks', 'Merino wool socks', '', 1800, 40, 1, 0, 0)", ).run(); assert.deepEqual(ids(await s.call("GET", "/search?q=socks")), []); assert.equal((await s.call("POST", "/admin/search/rebuild")).status, 401); const res = await s.call("POST", "/admin/search/rebuild", undefined, { authorization: `Bearer ${ADMIN_TOKEN}` }); assert.deepEqual(res.body, { indexed: 5 }); assert.deepEqual(ids(await s.call("GET", "/search?q=socks")), ["wool-socks"]); assert.deepEqual(ids(await s.call("GET", "/search?q=linen")), ["linen-apron", "canvas-tote"]);});test("a failed index write is reported, and the product is still saved", async () => { const s = await shop(); const search = s.env.SEARCH; s.env.SEARCH = { prepare: (sql: string) => search.prepare(sql), batch: async () => { throw new Error("D1_ERROR: storefront-search is unavailable"); }, } as unknown as D1Database; const res = await s.call("PUT", "/admin/products/enamel-mug", { name: "Enamel camp cup", price_cents: 1600 }, s.admin); assert.equal(res.status, 503); assert.equal(res.body.error, "search_index_failed"); assert.equal((await s.call("GET", "/products/enamel-mug")).body.product.name, "Enamel camp cup");});