- All 8 route test files destructure { db, userId } from createTestDb()
- All route test middleware sets c.set("userId", userId)
- MCP tools.test.ts passes userId to all registerXTools(db, userId) calls
- MCP tools.test.ts passes userId to getCollectionSummary(db, userId)
- Added 4 cross-user isolation tests for MCP tools (items, item by ID, threads, collection summary)
- OAuth test db type annotation updated for new createTestDb return shape
- Images test now uses createTestDb with userId context
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
209 lines
5.6 KiB
TypeScript
209 lines
5.6 KiB
TypeScript
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, userId } = createTestDb();
|
|
const app = new Hono();
|
|
|
|
// Inject test DB and userId into context for all routes
|
|
app.use("*", async (c, next) => {
|
|
c.set("db", db);
|
|
c.set("userId", userId);
|
|
await next();
|
|
});
|
|
|
|
app.route("/api/items", itemRoutes);
|
|
app.route("/api/categories", categoryRoutes);
|
|
return { app, db, userId };
|
|
}
|
|
|
|
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);
|
|
});
|
|
});
|