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