馃 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});