import { describe, it, expect, vi, beforeEach } from 'vitest'; import { ResetPasswordMutation } from './ResetPasswordMutation'; import { AuthService } from '@/lib/services/auth/AuthService'; import { Result } from '@/lib/contracts/Result'; // Mock dependencies vi.mock('@/lib/services/auth/AuthService', () => { return { AuthService: vi.fn(), }; }); describe('ResetPasswordMutation', () => { let mutation: ResetPasswordMutation; let mockServiceInstance: { resetPassword: ReturnType }; beforeEach(() => { vi.clearAllMocks(); mutation = new ResetPasswordMutation(); mockServiceInstance = { resetPassword: vi.fn(), }; // Use mockImplementation to return the instance (AuthService as any).mockImplementation(function() { return mockServiceInstance; }); }); describe('execute', () => { describe('happy paths', () => { it('should successfully reset password with valid token', async () => { // Arrange const input = { token: 'valid-token-123', newPassword: 'newSecurePassword123' }; const serviceOutput = { message: 'Password reset successfully' }; mockServiceInstance.resetPassword.mockResolvedValue(Result.ok(serviceOutput)); // Act const result = await mutation.execute(input); // Assert expect(result.isOk()).toBe(true); expect(result.unwrap()).toEqual(serviceOutput); expect(mockServiceInstance.resetPassword).toHaveBeenCalledWith(input); expect(mockServiceInstance.resetPassword).toHaveBeenCalledTimes(1); }); it('should handle reset password with complex password', async () => { // Arrange const input = { token: 'valid-token-456', newPassword: 'ComplexP@ssw0rd!2024' }; const serviceOutput = { message: 'Password reset successfully' }; mockServiceInstance.resetPassword.mockResolvedValue(Result.ok(serviceOutput)); // Act const result = await mutation.execute(input); // Assert expect(result.isOk()).toBe(true); expect(result.unwrap()).toEqual(serviceOutput); expect(mockServiceInstance.resetPassword).toHaveBeenCalledWith(input); }); }); describe('failure modes', () => { it('should handle service failure during password reset', async () => { // Arrange const input = { token: 'expired-token', newPassword: 'newPassword123' }; const serviceError = new Error('Token expired'); mockServiceInstance.resetPassword.mockRejectedValue(serviceError); // Act const result = await mutation.execute(input); // Assert expect(result.isErr()).toBe(true); expect(result.getError()).toBe('Token expired'); expect(mockServiceInstance.resetPassword).toHaveBeenCalledTimes(1); }); it('should handle service returning error result', async () => { // Arrange const input = { token: 'invalid-token', newPassword: 'newPassword123' }; const domainError = { type: 'validationError', message: 'Invalid token' }; mockServiceInstance.resetPassword.mockResolvedValue(Result.err(domainError)); // Act const result = await mutation.execute(input); // Assert expect(result.isErr()).toBe(true); expect(result.getError()).toBe('Invalid token'); expect(mockServiceInstance.resetPassword).toHaveBeenCalledTimes(1); }); it('should handle service returning tokenExpired error', async () => { // Arrange const input = { token: 'expired-token', newPassword: 'newPassword123' }; const domainError = { type: 'validationError', message: 'Reset token has expired' }; mockServiceInstance.resetPassword.mockResolvedValue(Result.err(domainError)); // Act const result = await mutation.execute(input); // Assert expect(result.isErr()).toBe(true); expect(result.getError()).toBe('Reset token has expired'); expect(mockServiceInstance.resetPassword).toHaveBeenCalledTimes(1); }); it('should handle service returning weakPassword error', async () => { // Arrange const input = { token: 'valid-token', newPassword: 'weak' }; const domainError = { type: 'validationError', message: 'Password does not meet requirements' }; mockServiceInstance.resetPassword.mockResolvedValue(Result.err(domainError)); // Act const result = await mutation.execute(input); // Assert expect(result.isErr()).toBe(true); expect(result.getError()).toBe('Password does not meet requirements'); expect(mockServiceInstance.resetPassword).toHaveBeenCalledTimes(1); }); it('should handle service returning serverError', async () => { // Arrange const input = { token: 'valid-token', newPassword: 'newPassword123' }; const domainError = { type: 'serverError', message: 'Database connection failed' }; mockServiceInstance.resetPassword.mockResolvedValue(Result.err(domainError)); // Act const result = await mutation.execute(input); // Assert expect(result.isErr()).toBe(true); expect(result.getError()).toBe('Database connection failed'); expect(mockServiceInstance.resetPassword).toHaveBeenCalledTimes(1); }); }); describe('error mapping', () => { it('should map various domain errors to mutation errors', async () => { // Arrange const input = { token: 'valid-token', newPassword: 'newPassword123' }; const testCases = [ { domainError: { type: 'validationError', message: 'Invalid token' }, expectedError: 'Invalid token' }, { domainError: { type: 'serverError', message: 'Server error' }, expectedError: 'Server error' }, { domainError: { type: 'notFound', message: 'User not found' }, expectedError: 'User not found' }, { domainError: { type: 'unauthorized', message: 'Unauthorized' }, expectedError: 'Unauthorized' }, ]; for (const testCase of testCases) { mockServiceInstance.resetPassword.mockResolvedValue(Result.err(testCase.domainError)); const result = await mutation.execute(input); expect(result.isErr()).toBe(true); expect(result.getError()).toBe(testCase.expectedError); } }); }); describe('input validation', () => { it('should accept valid token and password input', async () => { // Arrange const input = { token: 'valid-token-123', newPassword: 'newSecurePassword123' }; mockServiceInstance.resetPassword.mockResolvedValue( Result.ok({ message: 'Password reset successfully' }) ); // Act const result = await mutation.execute(input); // Assert expect(result.isOk()).toBe(true); expect(mockServiceInstance.resetPassword).toHaveBeenCalledTimes(1); }); it('should handle empty token gracefully', async () => { // Arrange const input = { token: '', newPassword: 'newPassword123' }; mockServiceInstance.resetPassword.mockResolvedValue( Result.ok({ message: 'Password reset successfully' }) ); // Act const result = await mutation.execute(input); // Assert expect(result.isOk()).toBe(true); expect(mockServiceInstance.resetPassword).toHaveBeenCalledWith(input); }); it('should handle empty password gracefully', async () => { // Arrange const input = { token: 'valid-token', newPassword: '' }; mockServiceInstance.resetPassword.mockResolvedValue( Result.ok({ message: 'Password reset successfully' }) ); // Act const result = await mutation.execute(input); // Assert expect(result.isOk()).toBe(true); expect(mockServiceInstance.resetPassword).toHaveBeenCalledWith(input); }); }); describe('service instantiation', () => { it('should create AuthService instance', () => { // Arrange & Act const mutation = new ResetPasswordMutation(); // Assert expect(mutation).toBeInstanceOf(ResetPasswordMutation); }); }); describe('result shape', () => { it('should return message on success', async () => { // Arrange const input = { token: 'valid-token', newPassword: 'newPassword123' }; const serviceOutput = { message: 'Password reset successfully' }; mockServiceInstance.resetPassword.mockResolvedValue(Result.ok(serviceOutput)); // Act const result = await mutation.execute(input); // Assert expect(result.isOk()).toBe(true); const resultData = result.unwrap(); expect(resultData).toEqual(serviceOutput); expect(resultData.message).toBe('Password reset successfully'); }); }); }); });