import { describe, it, expect, beforeEach } from "bun:test"; import { Hono } from "hono"; import { createTestDb } from "../helpers/db.ts"; import { setupRoutes } from "../../src/server/routes/setups.ts"; import { itemRoutes } from "../../src/server/routes/items.ts"; function createTestApp() { const db = createTestDb(); const app = new Hono(); app.use("*", async (c, next) => { c.set("db", db); await next(); }); app.route("/api/setups", setupRoutes); app.route("/api/items", itemRoutes); return { app, db }; } async function createSetupViaAPI(app: Hono, name: string) { const res = await app.request("/api/setups", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }); return res.json(); } async function createItemViaAPI(app: Hono, data: any) { const res = await app.request("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); return res.json(); } describe("Setup Routes", () => { let app: Hono; beforeEach(() => { const testApp = createTestApp(); app = testApp.app; }); describe("POST /api/setups", () => { it("with valid body returns 201 + setup object", async () => { const res = await app.request("/api/setups", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Day Hike" }), }); expect(res.status).toBe(201); const body = await res.json(); expect(body.name).toBe("Day Hike"); expect(body.id).toBeGreaterThan(0); }); it("with empty name returns 400", async () => { const res = await app.request("/api/setups", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "" }), }); expect(res.status).toBe(400); }); }); describe("GET /api/setups", () => { it("returns array of setups with totals", async () => { const setup = await createSetupViaAPI(app, "Backpacking"); const item = await createItemViaAPI(app, { name: "Tent", categoryId: 1, weightGrams: 1200, priceCents: 30000, }); // Sync items await app.request(`/api/setups/${setup.id}/items`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ itemIds: [item.id] }), }); const res = await app.request("/api/setups"); expect(res.status).toBe(200); const body = await res.json(); expect(Array.isArray(body)).toBe(true); expect(body.length).toBeGreaterThanOrEqual(1); expect(body[0].itemCount).toBeDefined(); expect(body[0].totalWeight).toBeDefined(); expect(body[0].totalCost).toBeDefined(); }); }); describe("GET /api/setups/:id", () => { it("returns setup with items", async () => { const setup = await createSetupViaAPI(app, "Day Hike"); const item = await createItemViaAPI(app, { name: "Water Bottle", categoryId: 1, weightGrams: 200, priceCents: 2500, }); await app.request(`/api/setups/${setup.id}/items`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ itemIds: [item.id] }), }); const res = await app.request(`/api/setups/${setup.id}`); expect(res.status).toBe(200); const body = await res.json(); expect(body.name).toBe("Day Hike"); expect(body.items).toHaveLength(1); expect(body.items[0].name).toBe("Water Bottle"); }); it("returns 404 for non-existent setup", async () => { const res = await app.request("/api/setups/9999"); expect(res.status).toBe(404); }); }); describe("PUT /api/setups/:id", () => { it("updates setup name", async () => { const setup = await createSetupViaAPI(app, "Original"); const res = await app.request(`/api/setups/${setup.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Renamed" }), }); expect(res.status).toBe(200); const body = await res.json(); expect(body.name).toBe("Renamed"); }); it("returns 404 for non-existent setup", async () => { const res = await app.request("/api/setups/9999", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Ghost" }), }); expect(res.status).toBe(404); }); }); describe("DELETE /api/setups/:id", () => { it("removes setup", async () => { const setup = await createSetupViaAPI(app, "To Delete"); const res = await app.request(`/api/setups/${setup.id}`, { method: "DELETE", }); expect(res.status).toBe(200); const body = await res.json(); expect(body.success).toBe(true); // Verify gone const getRes = await app.request(`/api/setups/${setup.id}`); expect(getRes.status).toBe(404); }); it("returns 404 for non-existent setup", async () => { const res = await app.request("/api/setups/9999", { method: "DELETE" }); expect(res.status).toBe(404); }); }); describe("PUT /api/setups/:id/items", () => { it("syncs items to setup", async () => { const setup = await createSetupViaAPI(app, "Kit"); const item1 = await createItemViaAPI(app, { name: "Item 1", categoryId: 1 }); const item2 = await createItemViaAPI(app, { name: "Item 2", categoryId: 1 }); const res = await app.request(`/api/setups/${setup.id}/items`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ itemIds: [item1.id, item2.id] }), }); expect(res.status).toBe(200); const body = await res.json(); expect(body.success).toBe(true); // Verify items const getRes = await app.request(`/api/setups/${setup.id}`); const getBody = await getRes.json(); expect(getBody.items).toHaveLength(2); }); }); describe("DELETE /api/setups/:id/items/:itemId", () => { it("removes single item from setup", async () => { const setup = await createSetupViaAPI(app, "Kit"); const item1 = await createItemViaAPI(app, { name: "Item 1", categoryId: 1 }); const item2 = await createItemViaAPI(app, { name: "Item 2", categoryId: 1 }); // Sync both items await app.request(`/api/setups/${setup.id}/items`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ itemIds: [item1.id, item2.id] }), }); // Remove one const res = await app.request(`/api/setups/${setup.id}/items/${item1.id}`, { method: "DELETE", }); expect(res.status).toBe(200); // Verify only one remains const getRes = await app.request(`/api/setups/${setup.id}`); const getBody = await getRes.json(); expect(getBody.items).toHaveLength(1); expect(getBody.items[0].name).toBe("Item 2"); }); }); });