import { describe, it, expect, vi, type Mock, beforeEach } from 'vitest'; import { LoginWithEmailUseCase } from './LoginWithEmailUseCase'; import type { IUserRepository } from '../../domain/repositories/IUserRepository'; import type { IdentitySessionPort } from '../ports/IdentitySessionPort'; import type { Logger } from '@core/shared/application'; // Mock the PasswordHash module vi.mock('@core/identity/domain/value-objects/PasswordHash', () => ({ PasswordHash: { fromHash: vi.fn((hash: string) => ({ verify: vi.fn().mockResolvedValue(hash === 'hashed-password'), value: hash, })), }, })); describe('LoginWithEmailUseCase', () => { let userRepository: { findByEmail: Mock; }; let sessionPort: { createSession: Mock; }; let logger: Logger; let useCase: LoginWithEmailUseCase; beforeEach(() => { userRepository = { findByEmail: vi.fn(), }; sessionPort = { createSession: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger; useCase = new LoginWithEmailUseCase( userRepository as unknown as IUserRepository, sessionPort as unknown as IdentitySessionPort, logger, ); }); it('returns ok and presents session result for valid credentials', async () => { const storedUser = { id: 'user-1', email: 'test@example.com', displayName: 'John Smith', passwordHash: 'hashed-password', createdAt: new Date(), }; userRepository.findByEmail.mockResolvedValue(storedUser); sessionPort.createSession.mockResolvedValue({ token: 'token-123', user: { id: 'user-1', email: 'test@example.com', displayName: 'John Smith', }, issuedAt: Date.now(), expiresAt: Date.now() + 1000, }); const result = await useCase.execute({ email: 'test@example.com', password: 'Password123', }); expect(result.isOk()).toBe(true); const loginResult = result.unwrap(); expect(loginResult.sessionToken).toBe('token-123'); expect(loginResult.userId).toBe('user-1'); expect(loginResult.displayName).toBe('John Smith'); expect(loginResult.email).toBe('test@example.com'); expect(userRepository.findByEmail).toHaveBeenCalledWith('test@example.com'); expect(sessionPort.createSession).toHaveBeenCalled(); }); it('returns INVALID_INPUT when email or password is missing', async () => { const result = await useCase.execute({ email: '', password: 'Password123', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('INVALID_INPUT'); }); it('returns INVALID_CREDENTIALS when user does not exist', async () => { userRepository.findByEmail.mockResolvedValue(null); const result = await useCase.execute({ email: 'nonexistent@example.com', password: 'Password123', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS'); }); it('returns INVALID_CREDENTIALS when password is invalid', async () => { const storedUser = { id: 'user-1', email: 'test@example.com', displayName: 'John Smith', passwordHash: 'wrong-hash', // Different hash to simulate wrong password createdAt: new Date(), }; userRepository.findByEmail.mockResolvedValue(storedUser); const result = await useCase.execute({ email: 'test@example.com', password: 'WrongPassword', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS'); }); it('wraps unexpected errors as REPOSITORY_ERROR and logs them', async () => { userRepository.findByEmail.mockRejectedValue(new Error('Database connection failed')); const result = await useCase.execute({ email: 'test@example.com', password: 'Password123', }); expect(result.isErr()).toBe(true); expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR'); expect(logger.error).toHaveBeenCalled(); }); });