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