import type { Logger } from '@core/shared/domain/Logger'; import { describe, expect, it, vi, type Mock } from 'vitest'; import { User } from '../../domain/entities/User'; import { PasswordHash } from '../../domain/value-objects/PasswordHash'; import { UserId } from '../../domain/value-objects/UserId'; import { ResetPasswordUseCase } from './ResetPasswordUseCase'; describe('ResetPasswordUseCase', () => { let authRepo: { findByEmail: Mock; save: Mock; }; let magicLinkRepo: { findByToken: Mock; markAsUsed: Mock; }; let passwordService: { hash: Mock; }; let logger: Logger; let useCase: ResetPasswordUseCase; beforeEach(() => { authRepo = { findByEmail: vi.fn(), save: vi.fn(), }; magicLinkRepo = { findByToken: vi.fn(), markAsUsed: vi.fn(), }; passwordService = { hash: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger; useCase = new ResetPasswordUseCase( authRepo as unknown as AuthRepository, magicLinkRepo as unknown as MagicLinkRepository, passwordService as unknown as PasswordHashingService, logger, ); }); it('successfully resets password with valid token', async () => { const user = User.create({ id: UserId.create(), displayName: 'John Smith', email: 'test@example.com', passwordHash: PasswordHash.fromHash('old-hash'), }); const validToken = 'a'.repeat(32); // 32 characters minimum magicLinkRepo.findByToken.mockResolvedValue({ email: 'test@example.com', token: validToken, expiresAt: new Date(Date.now() + 60000), userId: user.getId().value, used: false, }); authRepo.findByEmail.mockResolvedValue(user); passwordService.hash.mockResolvedValue('new-hashed-password'); const result = await useCase.execute({ token: validToken, newPassword: 'NewPassword123', }); expect(result.isOk()).toBe(true); const resetResult = result.unwrap(); expect(resetResult.message).toBe('Password reset successfully. You can now log in with your new password.'); expect(authRepo.save).toHaveBeenCalled(); expect(magicLinkRepo.markAsUsed).toHaveBeenCalledWith(validToken); }); it('returns error for invalid token', async () => { magicLinkRepo.findByToken.mockResolvedValue(null); const result = await useCase.execute({ token: 'invalid-token-that-is-too-short', newPassword: 'NewPassword123', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('INVALID_TOKEN'); }); it('returns error for expired token', async () => { const expiredToken = 'b'.repeat(32); magicLinkRepo.findByToken.mockResolvedValue({ email: 'test@example.com', token: expiredToken, expiresAt: new Date(Date.now() - 60000), userId: 'user-1', used: false, }); const result = await useCase.execute({ token: expiredToken, newPassword: 'NewPassword123', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('EXPIRED_TOKEN'); }); it('returns error for weak password', async () => { const validToken = 'c'.repeat(32); magicLinkRepo.findByToken.mockResolvedValue({ email: 'test@example.com', token: validToken, expiresAt: new Date(Date.now() + 60000), userId: 'user-1', used: false, }); const result = await useCase.execute({ token: validToken, newPassword: 'weak', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('WEAK_PASSWORD'); }); it('returns error when user no longer exists', async () => { const validToken = 'd'.repeat(32); magicLinkRepo.findByToken.mockResolvedValue({ email: 'test@example.com', token: validToken, expiresAt: new Date(Date.now() + 60000), userId: 'user-1', used: false, }); authRepo.findByEmail.mockResolvedValue(null); const result = await useCase.execute({ token: validToken, newPassword: 'NewPassword123', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('INVALID_TOKEN'); }); });