import { beforeEach, describe, expect, it } from 'vitest'; import { InMemoryMagicLinkRepository } from './InMemoryMagicLinkRepository'; const mockLogger = { debug: () => {}, info: () => {}, warn: () => {}, error: () => {}, }; describe('InMemoryMagicLinkRepository', () => { let repository: InMemoryMagicLinkRepository; beforeEach(() => { repository = new InMemoryMagicLinkRepository(mockLogger as any); }); describe('createPasswordResetRequest', () => { it('should create a password reset request', async () => { const request = { email: 'test@example.com', token: 'abc123', expiresAt: new Date(Date.now() + 15 * 60 * 1000), userId: 'user-123', }; await repository.createPasswordResetRequest(request); const found = await repository.findByToken('abc123'); expect(found).toEqual(request); }); it('should enforce rate limiting', async () => { const request = { email: 'test@example.com', token: 'token1', expiresAt: new Date(Date.now() + 15 * 60 * 1000), userId: 'user-123', }; // Create 3 requests for same email await repository.createPasswordResetRequest({ ...request, token: 'token1' }); await repository.createPasswordResetRequest({ ...request, token: 'token2' }); await repository.createPasswordResetRequest({ ...request, token: 'token3' }); // 4th should fail const result = await repository.checkRateLimit('test@example.com'); expect(result.isErr()).toBe(true); }); it('should allow requests after time window expires', async () => { const now = Date.now(); const request = { email: 'test@example.com', token: 'token1', expiresAt: new Date(now + 15 * 60 * 1000), userId: 'user-123', }; // Mock Date.now to return time after rate limit window const originalNow = Date.now; Date.now = () => now + (16 * 60 * 1000); // 16 minutes later try { await repository.createPasswordResetRequest(request); const found = await repository.findByToken('token1'); expect(found).toBeDefined(); } finally { Date.now = originalNow; } }); }); describe('findByToken', () => { it('should find existing token', async () => { const request = { email: 'test@example.com', token: 'abc123', expiresAt: new Date(Date.now() + 15 * 60 * 1000), userId: 'user-123', }; await repository.createPasswordResetRequest(request); const found = await repository.findByToken('abc123'); expect(found).toEqual(request); }); it('should return null for non-existent token', async () => { const found = await repository.findByToken('nonexistent'); expect(found).toBeNull(); }); }); describe('markAsUsed', () => { it('should mark token as used', async () => { const request = { email: 'test@example.com', token: 'abc123', expiresAt: new Date(Date.now() + 15 * 60 * 1000), userId: 'user-123', }; await repository.createPasswordResetRequest(request); await repository.markAsUsed('abc123'); const found = await repository.findByToken('abc123'); expect(found).toBeNull(); }); it('should handle non-existent token gracefully', async () => { await expect(repository.markAsUsed('nonexistent')).resolves.not.toThrow(); }); }); describe('checkRateLimit', () => { it('should allow requests under limit', async () => { const result = await repository.checkRateLimit('test@example.com'); expect(result.isOk()).toBe(true); }); it('should reject requests over limit', async () => { const email = 'test@example.com'; const request = { email, token: 'token', expiresAt: new Date(Date.now() + 15 * 60 * 1000), userId: 'user-123', }; // Create 3 requests await repository.createPasswordResetRequest({ ...request, token: 'token1' }); await repository.createPasswordResetRequest({ ...request, token: 'token2' }); await repository.createPasswordResetRequest({ ...request, token: 'token3' }); const result = await repository.checkRateLimit(email); expect(result.isErr()).toBe(true); }); }); });