import { describe, it, expect, vi, type Mock } from 'vitest'; import { SignupWithEmailUseCase, type SignupCommandDTO } from './SignupWithEmailUseCase'; import type { IUserRepository, StoredUser } from '../../domain/repositories/IUserRepository'; import type { IdentitySessionPort } from '../ports/IdentitySessionPort'; import type { AuthSessionDTO } from '../dto/AuthSessionDTO'; describe('SignupWithEmailUseCase', () => { let userRepository: { findByEmail: Mock; create: Mock; }; let sessionPort: { createSession: Mock; getCurrentSession: Mock; clearSession: Mock; }; let useCase: SignupWithEmailUseCase; beforeEach(() => { userRepository = { findByEmail: vi.fn(), create: vi.fn(), }; sessionPort = { createSession: vi.fn(), getCurrentSession: vi.fn(), clearSession: vi.fn(), }; useCase = new SignupWithEmailUseCase( userRepository as unknown as IUserRepository, sessionPort as unknown as IdentitySessionPort, ); }); it('creates a new user and session for valid input', async () => { const command: SignupCommandDTO = { email: 'new@example.com', password: 'password123', displayName: 'New User', }; userRepository.findByEmail.mockResolvedValue(null); const session: AuthSessionDTO = { user: { id: 'user-1', email: command.email.toLowerCase(), displayName: command.displayName, }, issuedAt: Date.now(), expiresAt: Date.now() + 1000, token: 'session-token', }; sessionPort.createSession.mockResolvedValue(session); const result = await useCase.execute(command); expect(userRepository.findByEmail).toHaveBeenCalledWith(command.email); expect(userRepository.create).toHaveBeenCalled(); expect(sessionPort.createSession).toHaveBeenCalledWith({ id: expect.any(String), email: command.email.toLowerCase(), displayName: command.displayName, }); expect(result.session).toEqual(session); expect(result.isNewUser).toBe(true); }); it('throws when email format is invalid', async () => { const command: SignupCommandDTO = { email: 'invalid-email', password: 'password123', displayName: 'User', }; await expect(useCase.execute(command)).rejects.toThrow('Invalid email format'); }); it('throws when password is too short', async () => { const command: SignupCommandDTO = { email: 'valid@example.com', password: 'short', displayName: 'User', }; await expect(useCase.execute(command)).rejects.toThrow('Password must be at least 8 characters'); }); it('throws when display name is too short', async () => { const command: SignupCommandDTO = { email: 'valid@example.com', password: 'password123', displayName: ' ', }; await expect(useCase.execute(command)).rejects.toThrow('Display name must be at least 2 characters'); }); it('throws when email already exists', async () => { const command: SignupCommandDTO = { email: 'existing@example.com', password: 'password123', displayName: 'Existing User', }; const existingUser: StoredUser = { id: 'user-1', email: command.email, displayName: command.displayName, passwordHash: 'hash', salt: 'salt', createdAt: new Date(), }; userRepository.findByEmail.mockResolvedValue(existingUser); await expect(useCase.execute(command)).rejects.toThrow('An account with this email already exists'); }); });