馃 distributed transcription service
thistle.dunkirk.sh
1import { expect, test } from "bun:test";
2import db from "../db/schema";
3import {
4 consumeEmailChangeToken,
5 createEmailChangeToken,
6 createUser,
7 getUserByEmail,
8 updateUserEmail,
9 verifyEmailChangeToken,
10} from "./auth";
11
12test("email change token lifecycle", async () => {
13 // Create a test user with unique email
14 const timestamp = Date.now();
15 const user = await createUser(
16 `test-email-change-${timestamp}@example.com`,
17 "password123",
18 "Test User",
19 );
20
21 // Create an email change token
22 const newEmail = `new-email-${timestamp}@example.com`;
23 const token = createEmailChangeToken(user.id, newEmail);
24
25 expect(token).toBeTruthy();
26 expect(token.length).toBeGreaterThan(0);
27
28 // Verify the token
29 const result = verifyEmailChangeToken(token);
30 expect(result).toBeTruthy();
31 expect(result?.userId).toBe(user.id);
32 expect(result?.newEmail).toBe(newEmail);
33
34 // Update the email
35 if (result) {
36 updateUserEmail(result.userId, result.newEmail);
37 }
38
39 // Consume the token
40 consumeEmailChangeToken(token);
41
42 // Verify the email was updated
43 const updatedUser = getUserByEmail(newEmail);
44 expect(updatedUser).toBeTruthy();
45 expect(updatedUser?.id).toBe(user.id);
46 expect(updatedUser?.email).toBe(newEmail);
47
48 // Verify the token can't be used again
49 const result2 = verifyEmailChangeToken(token);
50 expect(result2).toBeNull();
51
52 // Clean up
53 db.run("DELETE FROM users WHERE id = ?", [user.id]);
54});
55
56test("email change token expires", async () => {
57 // Create a test user with unique email
58 const timestamp = Date.now();
59 const user = await createUser(
60 `test-expire-${timestamp}@example.com`,
61 "password123",
62 "Test User",
63 );
64
65 // Create an email change token
66 const newEmail = `new-expire-${timestamp}@example.com`;
67 const token = createEmailChangeToken(user.id, newEmail);
68
69 // Manually expire the token
70 db.run("UPDATE email_change_tokens SET expires_at = ? WHERE token = ?", [
71 Math.floor(Date.now() / 1000) - 1,
72 token,
73 ]);
74
75 // Verify the token is expired
76 const result = verifyEmailChangeToken(token);
77 expect(result).toBeNull();
78
79 // Clean up
80 db.run("DELETE FROM users WHERE id = ?", [user.id]);
81});
82
83test("only one email change token per user", async () => {
84 // Create a test user with unique email
85 const timestamp = Date.now();
86 const user = await createUser(
87 `test-single-token-${timestamp}@example.com`,
88 "password123",
89 "Test User",
90 );
91
92 // Create first token
93 const token1 = createEmailChangeToken(
94 user.id,
95 `email1-${timestamp}@example.com`,
96 );
97
98 // Create second token (should delete first)
99 const token2 = createEmailChangeToken(
100 user.id,
101 `email2-${timestamp}@example.com`,
102 );
103
104 // First token should be invalid
105 const result1 = verifyEmailChangeToken(token1);
106 expect(result1).toBeNull();
107
108 // Second token should work
109 const result2 = verifyEmailChangeToken(token2);
110 expect(result2).toBeTruthy();
111 expect(result2?.newEmail).toBe(`email2-${timestamp}@example.com`);
112
113 // Clean up
114 db.run("DELETE FROM users WHERE id = ?", [user.id]);
115 db.run("DELETE FROM email_change_tokens WHERE user_id = ?", [user.id]);
116});