🪻 distributed transcription service thistle.dunkirk.sh

chore: fix biome lint issues

- Sort imports in index.test.ts
- Auto-format schema.ts, email.ts, vtt-cleaner.test.ts

💘 Generated with Crush

Assisted-by: Claude Sonnet 4.5 via Crush <crush@charm.land>

dunkirk.sh 8caa2a98 a8e4ff56

verified
Changed files
+80 -71
src
+2 -1
src/db/schema.ts
···
import { Database } from "bun:sqlite";
// Use test database when NODE_ENV is test
-
const dbPath = process.env.NODE_ENV === "test" ? "thistle.test.db" : "thistle.db";
+
const dbPath =
+
process.env.NODE_ENV === "test" ? "thistle.test.db" : "thistle.db";
export const db = new Database(dbPath);
console.log(`[Database] Using database: ${dbPath}`);
+72 -66
src/index.test.ts
···
expect,
test,
} from "bun:test";
-
import { hashPasswordClient } from "./lib/client-auth";
import type { Subprocess } from "bun";
+
import { hashPasswordClient } from "./lib/client-auth";
// Test server configuration
const TEST_PORT = 3001;
···
const stdoutReader = serverProcess.stdout.getReader();
const stderrReader = serverProcess.stderr.getReader();
const decoder = new TextDecoder();
-
+
(async () => {
try {
while (true) {
···
}
} catch {}
})();
-
+
(async () => {
try {
while (true) {
···
// Clear database between each test
beforeEach(async () => {
const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
-
+
// Delete all data from tables (preserve schema)
db.run("DELETE FROM rate_limit_attempts");
db.run("DELETE FROM email_change_tokens");
···
db.run("DELETE FROM classes");
db.run("DELETE FROM class_waitlist");
db.run("DELETE FROM users WHERE id != 0"); // Keep ghost user
-
+
db.close();
});
···
}
// Helper to register a user, verify email, and get session via login
-
async function registerAndLogin(user: { email: string; password: string; name?: string }): Promise<string> {
+
async function registerAndLogin(user: {
+
email: string;
+
password: string;
+
name?: string;
+
}): Promise<string> {
const hashedPassword = await clientHashPassword(user.email, user.password);
// Register the user
···
// Helper to add active subscription to a user
function addSubscription(userEmail: string): void {
const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
-
const user = db.query("SELECT id FROM users WHERE email = ?").get(userEmail) as { id: number };
+
const user = db
+
.query("SELECT id FROM users WHERE email = ?")
+
.get(userEmail) as { id: number };
if (!user) {
db.close();
throw new Error(`User ${userEmail} not found`);
}
-
+
db.run(
"INSERT INTO subscriptions (id, user_id, customer_id, status) VALUES (?, ?, ?, ?)",
-
[`test-sub-${user.id}`, user.id, `test-customer-${user.id}`, "active"]
+
[`test-sub-${user.id}`, user.id, `test-customer-${user.id}`, "active"],
);
db.close();
}
···
}
expect(response.status).toBe(201);
-
+
const data = await response.json();
expect(data.user).toBeDefined();
expect(data.user.email).toBe(TEST_USER.email);
···
expect(data.error).toBe("Email and password required");
});
-
test(
-
"should reject registration with invalid password format",
-
async () => {
-
const response = await fetch(`${BASE_URL}/api/auth/register`, {
-
method: "POST",
-
headers: { "Content-Type": "application/json" },
-
body: JSON.stringify({
-
email: TEST_USER.email,
-
password: "short",
-
}),
-
});
+
test("should reject registration with invalid password format", async () => {
+
const response = await fetch(`${BASE_URL}/api/auth/register`, {
+
method: "POST",
+
headers: { "Content-Type": "application/json" },
+
body: JSON.stringify({
+
email: TEST_USER.email,
+
password: "short",
+
}),
+
});
-
expect(response.status).toBe(400);
-
const data = await response.json();
-
expect(data.error).toBe("Invalid password format");
-
},
-
);
+
expect(response.status).toBe(400);
+
const data = await response.json();
+
expect(data.error).toBe("Invalid password format");
+
});
test("should reject duplicate email registration", async () => {
const hashedPassword = await clientHashPassword(
···
// Manually complete the email change in the database (simulating verification)
const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
-
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 };
-
db.run("UPDATE users SET email = ?, email_verified = 1 WHERE id = ?", [tokenData.new_email, tokenData.user_id]);
-
db.run("DELETE FROM email_change_tokens WHERE user_id = ?", [tokenData.user_id]);
+
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 };
+
db.run("UPDATE users SET email = ?, email_verified = 1 WHERE id = ?", [
+
tokenData.new_email,
+
tokenData.user_id,
+
]);
+
db.run("DELETE FROM email_change_tokens WHERE user_id = ?", [
+
tokenData.user_id,
+
]);
db.close();
// Verify email updated
···
describe("API Endpoints - Health", () => {
describe("GET /api/health", () => {
-
test(
-
"should return service health status with details",
-
async () => {
-
const response = await fetch(`${BASE_URL}/api/health`);
+
test("should return service health status with details", async () => {
+
const response = await fetch(`${BASE_URL}/api/health`);
-
expect(response.status).toBe(200);
-
const data = await response.json();
-
expect(data).toHaveProperty("status");
-
expect(data).toHaveProperty("timestamp");
-
expect(data).toHaveProperty("services");
-
expect(data.services).toHaveProperty("database");
-
expect(data.services).toHaveProperty("whisper");
-
expect(data.services).toHaveProperty("storage");
-
},
-
);
+
expect(response.status).toBe(200);
+
const data = await response.json();
+
expect(data).toHaveProperty("status");
+
expect(data).toHaveProperty("timestamp");
+
expect(data).toHaveProperty("services");
+
expect(data.services).toHaveProperty("database");
+
expect(data.services).toHaveProperty("whisper");
+
expect(data.services).toHaveProperty("storage");
+
});
});
});
···
test("should return user transcriptions", async () => {
// Register and login
const sessionCookie = await registerAndLogin(TEST_USER);
-
+
// Add subscription
addSubscription(TEST_USER.email);
···
test("should upload audio file and start transcription", async () => {
// Register and login
const sessionCookie = await registerAndLogin(TEST_USER);
-
+
// Add subscription
addSubscription(TEST_USER.email);
···
test("should reject non-audio files", async () => {
// Register and login
const sessionCookie = await registerAndLogin(TEST_USER);
-
+
// Add subscription
addSubscription(TEST_USER.email);
···
test("should reject files exceeding size limit", async () => {
// Register and login
const sessionCookie = await registerAndLogin(TEST_USER);
-
+
// Add subscription
addSubscription(TEST_USER.email);
···
beforeEach(async () => {
// Create admin user
adminCookie = await registerAndLogin(TEST_ADMIN);
-
+
// Manually set admin role in database
const db = require("bun:sqlite").Database.open(TEST_DB_PATH);
db.run("UPDATE users SET role = 'admin' WHERE email = ?", [
···
.query<{ id: number }, [string]>("SELECT id FROM users WHERE email = ?")
.get(TEST_USER.email);
userId = userIdResult?.id;
-
+
db.close();
});
···
});
describe("POST /api/passkeys/register/options", () => {
-
test(
-
"should return registration options for authenticated user",
-
async () => {
-
const response = await authRequest(
-
`${BASE_URL}/api/passkeys/register/options`,
-
sessionCookie,
-
{
-
method: "POST",
-
},
-
);
+
test("should return registration options for authenticated user", async () => {
+
const response = await authRequest(
+
`${BASE_URL}/api/passkeys/register/options`,
+
sessionCookie,
+
{
+
method: "POST",
+
},
+
);
-
expect(response.status).toBe(200);
-
const data = await response.json();
-
expect(data).toHaveProperty("challenge");
-
expect(data).toHaveProperty("rp");
-
expect(data).toHaveProperty("user");
-
},
-
);
+
expect(response.status).toBe(200);
+
const data = await response.json();
+
expect(data).toHaveProperty("challenge");
+
expect(data).toHaveProperty("rp");
+
expect(data).toHaveProperty("user");
+
});
test("should require authentication", async () => {
const response = await fetch(
+3 -1
src/lib/email.ts
···
export async function sendEmail(options: SendEmailOptions): Promise<void> {
// Skip sending emails in test mode
if (process.env.NODE_ENV === "test" || process.env.SKIP_EMAILS === "true") {
-
console.log(`[Email] SKIPPED (test mode): "${options.subject}" to ${typeof options.to === "string" ? options.to : options.to.email}`);
+
console.log(
+
`[Email] SKIPPED (test mode): "${options.subject}" to ${typeof options.to === "string" ? options.to : options.to.email}`,
+
);
return;
}
+3 -3
src/lib/vtt-cleaner.test.ts
···
test("cleanVTT preserves empty VTT", async () => {
const emptyVTT = "WEBVTT\n\n";
-
+
// Save and remove API key to avoid burning tokens
const originalKey = process.env.LLM_API_KEY;
delete process.env.LLM_API_KEY;
-
+
const result = await cleanVTT("test-empty", emptyVTT);
expect(result).toBe(emptyVTT);
-
+
// Restore original key
if (originalKey) {
process.env.LLM_API_KEY = originalKey;