馃 distributed transcription service
thistle.dunkirk.sh
1import { describe, test, expect, beforeEach, afterEach } from "bun:test";
2import db from "../db/schema";
3import {
4 createUser,
5 createEmailVerificationToken,
6 verifyEmailToken,
7 isEmailVerified,
8 createPasswordResetToken,
9 verifyPasswordResetToken,
10 consumePasswordResetToken,
11} from "./auth";
12
13describe("Email Verification", () => {
14 let userId: number;
15 const testEmail = `test-verify-${Date.now()}@example.com`;
16
17 beforeEach(async () => {
18 // Create test user
19 const user = await createUser(testEmail, "a".repeat(64), "Test User");
20 userId = user.id;
21 });
22
23 afterEach(() => {
24 // Cleanup
25 db.run("DELETE FROM users WHERE email = ?", [testEmail]);
26 db.run("DELETE FROM email_verification_tokens WHERE user_id = ?", [
27 userId,
28 ]);
29 });
30
31 test("creates verification token", () => {
32 const token = createEmailVerificationToken(userId);
33 expect(token).toBeDefined();
34 expect(typeof token).toBe("string");
35 expect(token.length).toBeGreaterThan(0);
36 });
37
38 test("verifies valid token", () => {
39 const token = createEmailVerificationToken(userId);
40 const result = verifyEmailToken(token);
41
42 expect(result).not.toBeNull();
43 expect(result?.userId).toBe(userId);
44 expect(result?.email).toBe(testEmail);
45 expect(isEmailVerified(userId)).toBe(true);
46 });
47
48 test("rejects invalid token", () => {
49 const result = verifyEmailToken("invalid-token-12345");
50 expect(result).toBeNull();
51 expect(isEmailVerified(userId)).toBe(false);
52 });
53
54 test("token is one-time use", () => {
55 const token = createEmailVerificationToken(userId);
56
57 // First use succeeds
58 const firstResult = verifyEmailToken(token);
59 expect(firstResult).not.toBeNull();
60
61 // Second use fails
62 const secondResult = verifyEmailToken(token);
63 expect(secondResult).toBeNull();
64 });
65
66 test("rejects expired token", () => {
67 const token = createEmailVerificationToken(userId);
68
69 // Manually expire the token
70 db.run(
71 "UPDATE email_verification_tokens SET expires_at = ? WHERE token = ?",
72 [Math.floor(Date.now() / 1000) - 100, token],
73 );
74
75 const result = verifyEmailToken(token);
76 expect(result).toBeNull();
77 });
78
79 test("replaces existing token when creating new one", () => {
80 const token1 = createEmailVerificationToken(userId);
81 const token2 = createEmailVerificationToken(userId);
82
83 // First token should be invalidated
84 expect(verifyEmailToken(token1)).toBeNull();
85
86 // Second token should work
87 expect(verifyEmailToken(token2)).not.toBeNull();
88 });
89});
90
91describe("Password Reset", () => {
92 let userId: number;
93 const testEmail = `test-reset-${Date.now()}@example.com`;
94
95 beforeEach(async () => {
96 const user = await createUser(testEmail, "a".repeat(64), "Test User");
97 userId = user.id;
98 });
99
100 afterEach(() => {
101 db.run("DELETE FROM users WHERE email = ?", [testEmail]);
102 db.run("DELETE FROM password_reset_tokens WHERE user_id = ?", [userId]);
103 });
104
105 test("creates reset token", () => {
106 const token = createPasswordResetToken(userId);
107 expect(token).toBeDefined();
108 expect(typeof token).toBe("string");
109 expect(token.length).toBeGreaterThan(0);
110 });
111
112 test("verifies valid reset token", () => {
113 const token = createPasswordResetToken(userId);
114 const verifiedUserId = verifyPasswordResetToken(token);
115
116 expect(verifiedUserId).toBe(userId);
117 });
118
119 test("rejects invalid reset token", () => {
120 const verifiedUserId = verifyPasswordResetToken("invalid-token-12345");
121 expect(verifiedUserId).toBeNull();
122 });
123
124 test("consumes reset token", () => {
125 const token = createPasswordResetToken(userId);
126
127 // Token works before consumption
128 expect(verifyPasswordResetToken(token)).toBe(userId);
129
130 // Consume token
131 consumePasswordResetToken(token);
132
133 // Token no longer works
134 expect(verifyPasswordResetToken(token)).toBeNull();
135 });
136
137 test("rejects expired reset token", () => {
138 const token = createPasswordResetToken(userId);
139
140 // Manually expire the token
141 db.run(
142 "UPDATE password_reset_tokens SET expires_at = ? WHERE token = ?",
143 [Math.floor(Date.now() / 1000) - 100, token],
144 );
145
146 const verifiedUserId = verifyPasswordResetToken(token);
147 expect(verifiedUserId).toBeNull();
148 });
149
150 test("replaces existing reset token when creating new one", () => {
151 const token1 = createPasswordResetToken(userId);
152 const token2 = createPasswordResetToken(userId);
153
154 // First token should be invalidated
155 expect(verifyPasswordResetToken(token1)).toBeNull();
156
157 // Second token should work
158 expect(verifyPasswordResetToken(token2)).toBe(userId);
159 });
160});