···11import { Database } from "bun:sqlite";
2233// Use test database when NODE_ENV is test
44-const dbPath = process.env.NODE_ENV === "test" ? "thistle.test.db" : "thistle.db";
44+const dbPath =
55+ process.env.NODE_ENV === "test" ? "thistle.test.db" : "thistle.db";
56export const db = new Database(dbPath);
6778console.log(`[Database] Using database: ${dbPath}`);
+72-66
src/index.test.ts
···66 expect,
77 test,
88} from "bun:test";
99-import { hashPasswordClient } from "./lib/client-auth";
109import type { Subprocess } from "bun";
1010+import { hashPasswordClient } from "./lib/client-auth";
11111212// Test server configuration
1313const TEST_PORT = 3001;
···5454 const stdoutReader = serverProcess.stdout.getReader();
5555 const stderrReader = serverProcess.stderr.getReader();
5656 const decoder = new TextDecoder();
5757-5757+5858 (async () => {
5959 try {
6060 while (true) {
···6565 }
6666 } catch {}
6767 })();
6868-6868+6969 (async () => {
7070 try {
7171 while (true) {
···123123// Clear database between each test
124124beforeEach(async () => {
125125 const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
126126-126126+127127 // Delete all data from tables (preserve schema)
128128 db.run("DELETE FROM rate_limit_attempts");
129129 db.run("DELETE FROM email_change_tokens");
···138138 db.run("DELETE FROM classes");
139139 db.run("DELETE FROM class_waitlist");
140140 db.run("DELETE FROM users WHERE id != 0"); // Keep ghost user
141141-141141+142142 db.close();
143143});
144144···194194}
195195196196// Helper to register a user, verify email, and get session via login
197197-async function registerAndLogin(user: { email: string; password: string; name?: string }): Promise<string> {
197197+async function registerAndLogin(user: {
198198+ email: string;
199199+ password: string;
200200+ name?: string;
201201+}): Promise<string> {
198202 const hashedPassword = await clientHashPassword(user.email, user.password);
199203200204 // Register the user
···242246// Helper to add active subscription to a user
243247function addSubscription(userEmail: string): void {
244248 const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
245245- const user = db.query("SELECT id FROM users WHERE email = ?").get(userEmail) as { id: number };
249249+ const user = db
250250+ .query("SELECT id FROM users WHERE email = ?")
251251+ .get(userEmail) as { id: number };
246252 if (!user) {
247253 db.close();
248254 throw new Error(`User ${userEmail} not found`);
249255 }
250250-256256+251257 db.run(
252258 "INSERT INTO subscriptions (id, user_id, customer_id, status) VALUES (?, ?, ?, ?)",
253253- [`test-sub-${user.id}`, user.id, `test-customer-${user.id}`, "active"]
259259+ [`test-sub-${user.id}`, user.id, `test-customer-${user.id}`, "active"],
254260 );
255261 db.close();
256262}
···281287 }
282288283289 expect(response.status).toBe(201);
284284-290290+285291 const data = await response.json();
286292 expect(data.user).toBeDefined();
287293 expect(data.user.email).toBe(TEST_USER.email);
···302308 expect(data.error).toBe("Email and password required");
303309 });
304310305305- test(
306306- "should reject registration with invalid password format",
307307- async () => {
308308- const response = await fetch(`${BASE_URL}/api/auth/register`, {
309309- method: "POST",
310310- headers: { "Content-Type": "application/json" },
311311- body: JSON.stringify({
312312- email: TEST_USER.email,
313313- password: "short",
314314- }),
315315- });
311311+ test("should reject registration with invalid password format", async () => {
312312+ const response = await fetch(`${BASE_URL}/api/auth/register`, {
313313+ method: "POST",
314314+ headers: { "Content-Type": "application/json" },
315315+ body: JSON.stringify({
316316+ email: TEST_USER.email,
317317+ password: "short",
318318+ }),
319319+ });
316320317317- expect(response.status).toBe(400);
318318- const data = await response.json();
319319- expect(data.error).toBe("Invalid password format");
320320- },
321321- );
321321+ expect(response.status).toBe(400);
322322+ const data = await response.json();
323323+ expect(data.error).toBe("Invalid password format");
324324+ });
322325323326 test("should reject duplicate email registration", async () => {
324327 const hashedPassword = await clientHashPassword(
···473476474477 // Manually complete the email change in the database (simulating verification)
475478 const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
476476- const tokenData = db.query("SELECT user_id, new_email FROM email_change_tokens ORDER BY created_at DESC LIMIT 1").get() as { user_id: number, new_email: string };
477477- db.run("UPDATE users SET email = ?, email_verified = 1 WHERE id = ?", [tokenData.new_email, tokenData.user_id]);
478478- db.run("DELETE FROM email_change_tokens WHERE user_id = ?", [tokenData.user_id]);
479479+ const tokenData = db
480480+ .query(
481481+ "SELECT user_id, new_email FROM email_change_tokens ORDER BY created_at DESC LIMIT 1",
482482+ )
483483+ .get() as { user_id: number; new_email: string };
484484+ db.run("UPDATE users SET email = ?, email_verified = 1 WHERE id = ?", [
485485+ tokenData.new_email,
486486+ tokenData.user_id,
487487+ ]);
488488+ db.run("DELETE FROM email_change_tokens WHERE user_id = ?", [
489489+ tokenData.user_id,
490490+ ]);
479491 db.close();
480492481493 // Verify email updated
···646658647659describe("API Endpoints - Health", () => {
648660 describe("GET /api/health", () => {
649649- test(
650650- "should return service health status with details",
651651- async () => {
652652- const response = await fetch(`${BASE_URL}/api/health`);
661661+ test("should return service health status with details", async () => {
662662+ const response = await fetch(`${BASE_URL}/api/health`);
653663654654- expect(response.status).toBe(200);
655655- const data = await response.json();
656656- expect(data).toHaveProperty("status");
657657- expect(data).toHaveProperty("timestamp");
658658- expect(data).toHaveProperty("services");
659659- expect(data.services).toHaveProperty("database");
660660- expect(data.services).toHaveProperty("whisper");
661661- expect(data.services).toHaveProperty("storage");
662662- },
663663- );
664664+ expect(response.status).toBe(200);
665665+ const data = await response.json();
666666+ expect(data).toHaveProperty("status");
667667+ expect(data).toHaveProperty("timestamp");
668668+ expect(data).toHaveProperty("services");
669669+ expect(data.services).toHaveProperty("database");
670670+ expect(data.services).toHaveProperty("whisper");
671671+ expect(data.services).toHaveProperty("storage");
672672+ });
664673 });
665674});
666675···669678 test("should return user transcriptions", async () => {
670679 // Register and login
671680 const sessionCookie = await registerAndLogin(TEST_USER);
672672-681681+673682 // Add subscription
674683 addSubscription(TEST_USER.email);
675684···696705 test("should upload audio file and start transcription", async () => {
697706 // Register and login
698707 const sessionCookie = await registerAndLogin(TEST_USER);
699699-708708+700709 // Add subscription
701710 addSubscription(TEST_USER.email);
702711···725734 test("should reject non-audio files", async () => {
726735 // Register and login
727736 const sessionCookie = await registerAndLogin(TEST_USER);
728728-737737+729738 // Add subscription
730739 addSubscription(TEST_USER.email);
731740···749758 test("should reject files exceeding size limit", async () => {
750759 // Register and login
751760 const sessionCookie = await registerAndLogin(TEST_USER);
752752-761761+753762 // Add subscription
754763 addSubscription(TEST_USER.email);
755764···797806 beforeEach(async () => {
798807 // Create admin user
799808 adminCookie = await registerAndLogin(TEST_ADMIN);
800800-809809+801810 // Manually set admin role in database
802811 const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
803812 db.run("UPDATE users SET role = 'admin' WHERE email = ?", [
···812821 .query<{ id: number }, [string]>("SELECT id FROM users WHERE email = ?")
813822 .get(TEST_USER.email);
814823 userId = userIdResult?.id;
815815-824824+816825 db.close();
817826 });
818827···10951104 });
1096110510971106 describe("POST /api/passkeys/register/options", () => {
10981098- test(
10991099- "should return registration options for authenticated user",
11001100- async () => {
11011101- const response = await authRequest(
11021102- `${BASE_URL}/api/passkeys/register/options`,
11031103- sessionCookie,
11041104- {
11051105- method: "POST",
11061106- },
11071107- );
11071107+ test("should return registration options for authenticated user", async () => {
11081108+ const response = await authRequest(
11091109+ `${BASE_URL}/api/passkeys/register/options`,
11101110+ sessionCookie,
11111111+ {
11121112+ method: "POST",
11131113+ },
11141114+ );
1108111511091109- expect(response.status).toBe(200);
11101110- const data = await response.json();
11111111- expect(data).toHaveProperty("challenge");
11121112- expect(data).toHaveProperty("rp");
11131113- expect(data).toHaveProperty("user");
11141114- },
11151115- );
11161116+ expect(response.status).toBe(200);
11171117+ const data = await response.json();
11181118+ expect(data).toHaveProperty("challenge");
11191119+ expect(data).toHaveProperty("rp");
11201120+ expect(data).toHaveProperty("user");
11211121+ });
1116112211171123 test("should require authentication", async () => {
11181124 const response = await fetch(