- Add comprehensive tests for MailgunService (439 lines) * Email sending functionality with template generation * Configuration status validation * Error handling and edge cases * Mock setup for fetch API and FormData - Add DatabaseService tests (451 lines) * Strategy pattern testing (Mock vs Production) * All CRUD operations for users, medications, settings * Legacy compatibility method testing * Proper TypeScript typing - Add MockDatabaseStrategy tests (434 lines) * Complete coverage of mock database implementation * User operations, medication management * Settings and custom reminders functionality * Data persistence and error handling - Add React hooks tests * useLocalStorage hook with comprehensive edge cases (340 lines) * useSettings hook with fetch operations and error handling (78 lines) - Fix auth integration tests * Update mocking to use new database service instead of legacy couchdb.factory * Fix service variable references and expectations - Simplify mailgun config tests * Remove redundant edge case testing * Focus on core functionality validation - Fix all TypeScript and ESLint issues * Proper FormData mock typing * Correct database entity type usage * Remove non-existent property references Test Results: - 184 total tests passing - Comprehensive coverage of core services - Zero TypeScript compilation errors - Full ESLint compliance
341 lines
11 KiB
TypeScript
341 lines
11 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|