import { beforeEach, describe, expect, it } from "bun:test"; import { Hono } from "hono"; import { categoryRoutes } from "../../src/server/routes/categories.ts"; import { itemRoutes } from "../../src/server/routes/items.ts"; import { createTestDb } from "../helpers/db.ts"; function createTestApp() { const db = createTestDb(); const app = new Hono(); // Inject test DB into context for all routes app.use("*", async (c, next) => { c.set("db", db); await next(); }); app.route("/api/items", itemRoutes); app.route("/api/categories", categoryRoutes); return { app, db }; } describe("Item Routes", () => { let app: Hono; beforeEach(() => { const testApp = createTestApp(); app = testApp.app; }); it("POST /api/items with valid data returns 201", async () => { const res = await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Tent", weightGrams: 1200, priceCents: 35000, categoryId: 1, }), }); expect(res.status).toBe(201); const body = await res.json(); expect(body.name).toBe("Tent"); expect(body.id).toBeGreaterThan(0); }); it("POST /api/items with missing name returns 400", async () => { const res = await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ categoryId: 1 }), }); expect(res.status).toBe(400); }); it("GET /api/items returns array", async () => { // Create an item first await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Tent", categoryId: 1 }), }); const res = await app.request("/api/items"); expect(res.status).toBe(200); const body = await res.json(); expect(Array.isArray(body)).toBe(true); expect(body.length).toBeGreaterThanOrEqual(1); }); it("PUT /api/items/:id updates fields", async () => { // Create first const createRes = await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Tent", weightGrams: 1200, categoryId: 1, }), }); const created = await createRes.json(); // Update const res = await app.request(`/api/items/${created.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Big Agnes Tent", weightGrams: 1100 }), }); expect(res.status).toBe(200); const body = await res.json(); expect(body.name).toBe("Big Agnes Tent"); expect(body.weightGrams).toBe(1100); }); it("DELETE /api/items/:id returns success", async () => { // Create first const createRes = await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Tent", categoryId: 1 }), }); const created = await createRes.json(); const res = await app.request(`/api/items/${created.id}`, { method: "DELETE", }); expect(res.status).toBe(200); const body = await res.json(); expect(body.success).toBe(true); }); it("GET /api/items/:id returns 404 for non-existent item", async () => { const res = await app.request("/api/items/9999"); expect(res.status).toBe(404); }); it("POST /api/items/:id/duplicate returns 201 with the copy", async () => { const createRes = await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Tent", categoryId: 1, weightGrams: 1200 }), }); const created = await createRes.json(); const res = await app.request(`/api/items/${created.id}/duplicate`, { method: "POST", }); expect(res.status).toBe(201); const body = await res.json(); expect(body.name).toBe("Tent (copy)"); expect(body.weightGrams).toBe(1200); expect(body.id).not.toBe(created.id); }); it("POST /api/items/999/duplicate returns 404", async () => { const res = await app.request("/api/items/999/duplicate", { method: "POST", }); expect(res.status).toBe(404); }); it("GET /api/items/export returns CSV with correct content-type", async () => { // Create an item first await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Tent", weightGrams: 1200, priceCents: 35000, categoryId: 1, }), }); const res = await app.request("/api/items/export"); expect(res.status).toBe(200); expect(res.headers.get("Content-Type")).toContain("text/csv"); const text = await res.text(); const lines = text.split("\n"); expect(lines[0]).toBe( "name,quantity,weightGrams,priceCents,category,notes,productUrl", ); expect(lines.length).toBeGreaterThanOrEqual(2); expect(lines[1]).toContain("Tent"); }); it("POST /api/items/import with CSV file creates items", async () => { const csvContent = [ "name,quantity,weightGrams,priceCents,category,notes,productUrl", "Sleeping Bag,1,800,25000,Camping,,", ].join("\n"); const formData = new FormData(); formData.append( "file", new Blob([csvContent], { type: "text/csv" }), "import.csv", ); const res = await app.request("/api/items/import", { method: "POST", body: formData, }); expect(res.status).toBe(200); const body = await res.json(); expect(body.imported).toBe(1); expect(body.errors).toHaveLength(0); }); it("POST /api/items/import with no file returns 400", async () => { const res = await app.request("/api/items/import", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({}), }); expect(res.status).toBe(400); }); });