From ddfe2e3279706f6476364cecfe23427dcabcfd3e Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 15 Mar 2026 21:53:52 -0700 Subject: tests --- test/api.test.ts | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 test/api.test.ts (limited to 'test/api.test.ts') diff --git a/test/api.test.ts b/test/api.test.ts new file mode 100644 index 00000000..92e506f3 --- /dev/null +++ b/test/api.test.ts @@ -0,0 +1,126 @@ +import { describe, test, expect, beforeAll } from "bun:test"; +import app from "$lib/app"; +import { auth, optional } from "$lib/auth"; +import users from "$routes/users"; +import info from "$routes/info"; +import invoices from "$routes/invoices"; + +// Register routes (subset of index.ts — no side effects, no Bun.serve) +app.get("/health", info.health); +app.post("/register", users.create); +app.post("/login", users.login); +app.get("/me", auth, users.me); +app.get("/users/:key", users.get); +app.post("/invoice", optional, invoices.create); +app.get("/invoice/:id", invoices.get); + +const post = (path: string, body: any) => + app.request(path, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + +// Use unique username to avoid collisions with other test files +const USERNAME = `apiuser${Date.now()}`; +const PASSWORD = "testpass123"; + +beforeAll(() => { + // Clear any leftover state from other test files + const store = globalThis.__testStore; + for (const k of Object.keys(store.kvStore)) delete store.kvStore[k]; + for (const k of Object.keys(store.listStore)) delete store.listStore[k]; + for (const k of Object.keys(store.setStore)) delete store.setStore[k]; +}); + +describe("Health", () => { + test("GET /health returns 200", async () => { + const res = await app.request("/health"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.healthy).toBe(true); + }); +}); + +describe("Registration", () => { + test("creates user with valid credentials", async () => { + const res = await post("/register", { + user: { username: USERNAME, password: PASSWORD }, + }); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.token).toBeDefined(); + expect(body.username).toBe(USERNAME); + }); + + test("rejects duplicate username", async () => { + const res = await post("/register", { + user: { username: USERNAME, password: PASSWORD }, + }); + expect(res.status).toBe(500); + }); + + test("rejects missing user object", async () => { + const res = await post("/register", { password: PASSWORD }); + expect(res.status).toBe(500); + }); + + test("rejects invalid username", async () => { + const res = await post("/register", { + user: { username: "ab!@#", password: PASSWORD }, + }); + expect(res.status).toBe(500); + }); +}); + +describe("Login", () => { + test("succeeds with valid credentials", async () => { + const res = await post("/login", { + username: USERNAME, + password: PASSWORD, + }); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.token).toBeDefined(); + expect(body.user).toBeDefined(); + }); + + test("fails with wrong password", async () => { + const res = await post("/login", { + username: USERNAME, + password: "wrongpass", + }); + expect(res.status).toBe(401); + }); + + test("fails with nonexistent user", async () => { + const res = await post("/login", { + username: "noone", + password: PASSWORD, + }); + expect(res.status).toBe(401); + }); +}); + +describe("Authenticated endpoints", () => { + test("GET /me returns user data", async () => { + const res = await app.request("/me"); + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.username).toBeDefined(); + }); +}); + +describe("User lookup", () => { + test("GET /users/:key finds registered user", async () => { + const res = await app.request(`/users/${USERNAME}`); + expect(res.status).toBe(200); + }); +}); + +describe("Routing", () => { + test("unknown route returns 404", async () => { + const res = await app.request("/nonexistent"); + expect(res.status).toBe(404); + }); +}); -- cgit v1.2.3