import { authService } from '../auth.service'; import { AccountStatus } from '../auth.constants'; import { AuthenticatedUser } from '../auth.types'; // Mock the new database service jest.mock('../../database', () => ({ databaseService: { findUserByEmail: jest.fn(), createUserWithPassword: jest.fn(), createUserFromOAuth: jest.fn(), updateUser: jest.fn(), getUserById: jest.fn(), changeUserPassword: jest.fn(), }, })); // Mock the emailVerification service jest.mock('../emailVerification.service', () => ({ EmailVerificationService: jest.fn().mockImplementation(() => ({ generateVerificationToken: jest.fn().mockResolvedValue({ token: 'mock-verification-token', userId: 'user1', email: 'testuser@example.com', expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), }), validateVerificationToken: jest.fn(), markEmailVerified: jest.fn(), sendPasswordResetEmail: jest.fn().mockResolvedValue(true), })), })); describe('Authentication Integration Tests', () => { const testCredentials = { username: 'testuser', password: 'Passw0rd!', email: 'testuser@example.com', }; let mockUser: AuthenticatedUser; let mockDatabaseService: any; beforeEach(async () => { localStorage.clear(); jest.clearAllMocks(); // Get the mocked database service const { databaseService } = await import('../../database'); mockDatabaseService = databaseService; // Setup default mock user mockUser = { _id: 'user1', _rev: 'mock-rev-1', email: testCredentials.email, username: testCredentials.username, password: testCredentials.password, emailVerified: false, status: AccountStatus.PENDING, }; }); describe('User Registration', () => { test('should create a pending account for new user', async () => { mockDatabaseService.findUserByEmail.mockResolvedValue(null); mockDatabaseService.createUserWithPassword.mockResolvedValue(mockUser); const result = await authService.register( testCredentials.email, testCredentials.password, testCredentials.username ); expect(result).toBeDefined(); expect(result.user.username).toBe(testCredentials.username); expect(result.user.email).toBe(testCredentials.email); expect(result.user.status).toBe(AccountStatus.PENDING); expect(result.user.emailVerified).toBe(false); expect(result.verificationToken).toBeDefined(); expect(result.verificationToken.token).toBe('mock-verification-token'); expect(mockDatabaseService.findUserByEmail).toHaveBeenCalledWith( testCredentials.email ); expect(mockDatabaseService.createUserWithPassword).toHaveBeenCalledWith( testCredentials.email, testCredentials.password, testCredentials.username ); }); test('should fail when user already exists', async () => { mockDatabaseService.findUserByEmail.mockResolvedValue(mockUser); await expect( authService.register( testCredentials.email, testCredentials.password, testCredentials.username ) ).rejects.toThrow('User already exists'); expect(mockDatabaseService.createUserWithPassword).not.toHaveBeenCalled(); }); }); describe('User Login', () => { test('should fail for unverified (pending) account', async () => { mockDatabaseService.findUserByEmail.mockResolvedValue(mockUser); await expect( authService.login({ email: testCredentials.email, password: testCredentials.password, }) ).rejects.toThrow('Email verification required'); }); test('should succeed after email verification', async () => { const verifiedUser = { ...mockUser, emailVerified: true, status: AccountStatus.ACTIVE, }; mockDatabaseService.findUserByEmail.mockResolvedValue(verifiedUser); const tokens = await authService.login({ email: testCredentials.email, password: testCredentials.password, }); expect(tokens).toBeDefined(); expect(tokens.accessToken).toBeTruthy(); expect(tokens.refreshToken).toBeTruthy(); expect(tokens.user).toBeDefined(); expect(tokens.user.status).toBe(AccountStatus.ACTIVE); }); test('should fail with wrong password', async () => { const verifiedUser = { ...mockUser, emailVerified: true, status: AccountStatus.ACTIVE, }; mockDatabaseService.findUserByEmail.mockResolvedValue(verifiedUser); await expect( authService.login({ email: testCredentials.email, password: 'wrongpassword', }) ).rejects.toThrow('Invalid credentials'); }); test('should fail for non-existent user', async () => { mockDatabaseService.findUserByEmail.mockResolvedValue(null); await expect( authService.login({ email: 'nonexistent@example.com', password: testCredentials.password, }) ).rejects.toThrow('User not found'); }); }); describe('OAuth Authentication', () => { const oauthUserData = { email: 'oauthuser@example.com', username: 'OAuth User', }; test('should register new OAuth user', async () => { const oauthUser: AuthenticatedUser = { _id: 'oauth-user1', _rev: 'mock-rev-oauth-1', email: oauthUserData.email, username: oauthUserData.username, password: '', emailVerified: true, status: AccountStatus.ACTIVE, }; mockDatabaseService.findUserByEmail.mockResolvedValue(null); mockDatabaseService.createUserFromOAuth.mockResolvedValue(oauthUser); const result = await authService.loginWithOAuth('google', oauthUserData); expect(result).toBeDefined(); expect(result.user.email).toBe(oauthUserData.email); expect(result.user.username).toBe(oauthUserData.username); expect(result.user.status).toBe(AccountStatus.ACTIVE); expect(result.user.emailVerified).toBe(true); expect(result.accessToken).toBeTruthy(); expect(result.refreshToken).toBeTruthy(); expect(mockDatabaseService.createUserFromOAuth).toHaveBeenCalledWith( oauthUserData.email, oauthUserData.username, 'google' ); }); test('should login existing OAuth user', async () => { const existingUser: AuthenticatedUser = { _id: 'existing-user1', _rev: 'mock-rev-existing-1', email: oauthUserData.email, username: oauthUserData.username, password: 'existing-password', emailVerified: true, status: AccountStatus.ACTIVE, }; mockDatabaseService.findUserByEmail.mockResolvedValue(existingUser); const result = await authService.loginWithOAuth('google', oauthUserData); expect(result).toBeDefined(); expect(result.user.email).toBe(oauthUserData.email); expect(result.user._id).toBe('existing-user1'); expect(result.accessToken).toBeTruthy(); expect(result.refreshToken).toBeTruthy(); expect(mockDatabaseService.createUserFromOAuth).not.toHaveBeenCalled(); }); test('should handle OAuth login errors gracefully', async () => { mockDatabaseService.findUserByEmail.mockRejectedValue( new Error('Database error') ); await expect( authService.loginWithOAuth('google', oauthUserData) ).rejects.toThrow('OAuth login failed: Database error'); }); }); describe('Password Management', () => { test('should change password with valid current password', async () => { const userId = 'user1'; const currentPassword = 'currentPassword'; const newPassword = 'newPassword123'; const userWithPassword = { ...mockUser, password: currentPassword, }; const updatedUser = { ...userWithPassword, password: newPassword, }; mockDatabaseService.getUserById.mockResolvedValue(userWithPassword); mockDatabaseService.updateUser.mockResolvedValue(updatedUser); const result = await authService.changePassword( userId, currentPassword, newPassword ); expect(result).toBeDefined(); expect(result.user.password).toBe(newPassword); expect(result.message).toBe('Password changed successfully'); expect(mockDatabaseService.updateUser).toHaveBeenCalledWith({ ...userWithPassword, password: newPassword, }); }); test('should fail password change with incorrect current password', async () => { const userId = 'user1'; const currentPassword = 'wrongPassword'; const newPassword = 'newPassword123'; const userWithPassword = { ...mockUser, password: 'correctPassword', }; mockDatabaseService.getUserById.mockResolvedValue(userWithPassword); await expect( authService.changePassword(userId, currentPassword, newPassword) ).rejects.toThrow('Current password is incorrect'); }); test('should fail password change for OAuth users', async () => { const userId = 'user1'; const oauthUser = { ...mockUser, password: '', }; mockDatabaseService.getUserById.mockResolvedValue(oauthUser); await expect( authService.changePassword(userId, 'any', 'newPassword') ).rejects.toThrow('Cannot change password for OAuth accounts'); }); }); describe('Password Reset', () => { test('should request password reset for existing user', async () => { const userWithPassword = { ...mockUser, password: 'hasPassword', }; mockDatabaseService.findUserByEmail.mockResolvedValue(userWithPassword); const result = await authService.requestPasswordReset( testCredentials.email ); expect(result).toBeDefined(); expect(result.message).toContain('password reset link has been sent'); }); test('should handle password reset for non-existent user gracefully', async () => { mockDatabaseService.findUserByEmail.mockResolvedValue(null); const result = await authService.requestPasswordReset( 'nonexistent@example.com' ); expect(result).toBeDefined(); expect(result.message).toContain('password reset link has been sent'); }); test('should fail password reset for OAuth users', async () => { const oauthUser = { ...mockUser, password: '', }; mockDatabaseService.findUserByEmail.mockResolvedValue(oauthUser); await expect( authService.requestPasswordReset(testCredentials.email) ).rejects.toThrow('Cannot reset password for OAuth accounts'); }); }); });