import { describe, it, expect, vi, type Mock } from 'vitest'; import { SignupSponsorUseCase } from './SignupSponsorUseCase'; import { EmailAddress } from '../../domain/value-objects/EmailAddress'; import { UserId } from '../../domain/value-objects/UserId'; import { User } from '../../domain/entities/User'; import type { IAuthRepository } from '../../domain/repositories/IAuthRepository'; import type { ICompanyRepository } from '../../domain/repositories/ICompanyRepository'; import type { IPasswordHashingService } from '../../domain/services/PasswordHashingService'; import type { Logger, UseCaseOutputPort } from '@core/shared/application'; vi.mock('../../domain/value-objects/PasswordHash', () => ({ PasswordHash: { fromHash: (hash: string) => ({ value: hash }), }, })); type SignupSponsorOutput = unknown; describe('SignupSponsorUseCase', () => { let authRepo: { findByEmail: Mock; save: Mock; }; let companyRepo: { create: Mock; save: Mock; delete: Mock; }; let passwordService: { hash: Mock; }; let logger: Logger; let output: UseCaseOutputPort & { present: Mock }; let useCase: SignupSponsorUseCase; beforeEach(() => { authRepo = { findByEmail: vi.fn(), save: vi.fn(), }; companyRepo = { create: vi.fn(), save: vi.fn(), delete: vi.fn(), }; passwordService = { hash: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger; output = { present: vi.fn(), }; useCase = new SignupSponsorUseCase( authRepo as unknown as IAuthRepository, companyRepo as unknown as ICompanyRepository, passwordService as unknown as IPasswordHashingService, logger, output, ); }); it('creates user and company successfully when email is free', async () => { const input = { email: 'sponsor@example.com', password: 'Password123', displayName: 'John Doe', companyName: 'Acme Racing Co.', }; authRepo.findByEmail.mockResolvedValue(null); passwordService.hash.mockResolvedValue('hashed-password'); companyRepo.create.mockImplementation((data) => ({ getId: () => 'company-123', getName: data.getName, getOwnerUserId: data.getOwnerUserId, getContactEmail: data.getContactEmail, })); companyRepo.save.mockResolvedValue(undefined); authRepo.save.mockResolvedValue(undefined); const result = await useCase.execute(input); // Verify the basic flow worked expect(result.isOk()).toBe(true); expect(output.present).toHaveBeenCalled(); // Verify key repository methods were called expect(authRepo.findByEmail).toHaveBeenCalled(); expect(passwordService.hash).toHaveBeenCalled(); expect(companyRepo.create).toHaveBeenCalled(); expect(companyRepo.save).toHaveBeenCalled(); expect(authRepo.save).toHaveBeenCalled(); }); it('rolls back company creation when user save fails', async () => { const input = { email: 'sponsor@example.com', password: 'Password123', displayName: 'John Doe', companyName: 'Acme Racing Co.', }; authRepo.findByEmail.mockResolvedValue(null); passwordService.hash.mockResolvedValue('hashed-password'); companyRepo.create.mockImplementation((data) => ({ getId: () => 'company-123', getName: data.getName, getOwnerUserId: data.getOwnerUserId, getContactEmail: data.getContactEmail, })); companyRepo.save.mockResolvedValue(undefined); authRepo.save.mockRejectedValue(new Error('Database error')); const result = await useCase.execute(input); // Verify company was deleted (rollback) expect(companyRepo.delete).toHaveBeenCalled(); const deletedCompanyId = companyRepo.delete.mock.calls[0][0]; expect(deletedCompanyId).toBeDefined(); // Verify error result expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('REPOSITORY_ERROR'); }); it('fails when user already exists', async () => { const input = { email: 'existing@example.com', password: 'Password123', displayName: 'John Doe', companyName: 'Acme Racing Co.', }; const existingUser = User.create({ id: UserId.create(), displayName: 'Existing User', email: input.email, }); authRepo.findByEmail.mockResolvedValue(existingUser); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('USER_ALREADY_EXISTS'); // Verify no company was created expect(companyRepo.create).not.toHaveBeenCalled(); }); it('fails when company creation throws an error', async () => { const input = { email: 'sponsor@example.com', password: 'Password123', displayName: 'John Doe', companyName: 'Acme Racing Co.', }; authRepo.findByEmail.mockResolvedValue(null); passwordService.hash.mockResolvedValue('hashed-password'); companyRepo.create.mockImplementation(() => { throw new Error('Invalid company data'); }); const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('REPOSITORY_ERROR'); // The error message might be wrapped, so just check it's a repository error }); it('fails with weak password', async () => { const input = { email: 'sponsor@example.com', password: 'weak', displayName: 'John Doe', companyName: 'Acme Racing Co.', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('WEAK_PASSWORD'); // Verify no repository calls expect(authRepo.findByEmail).not.toHaveBeenCalled(); expect(companyRepo.create).not.toHaveBeenCalled(); }); it('fails with invalid display name', async () => { const input = { email: 'sponsor@example.com', password: 'Password123', displayName: 'user123', // Invalid - alphanumeric only companyName: 'Acme Racing Co.', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const error = result.unwrapErr(); expect(error.code).toBe('INVALID_DISPLAY_NAME'); // Verify no repository calls expect(authRepo.findByEmail).not.toHaveBeenCalled(); expect(companyRepo.create).not.toHaveBeenCalled(); }); });