All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 25m23s
219 lines
9.1 KiB
TypeScript
219 lines
9.1 KiB
TypeScript
// src/tests/e2e/auth.e2e.test.ts
|
|
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
|
import * as apiClient from '../../services/apiClient';
|
|
import { cleanupDb } from '../utils/cleanup';
|
|
import { createAndLoginUser, TEST_PASSWORD } from '../utils/testHelpers';
|
|
import type { UserProfile } from '../../types';
|
|
|
|
/**
|
|
* @vitest-environment node
|
|
*/
|
|
|
|
describe('Authentication E2E Flow', () => {
|
|
let testUser: UserProfile;
|
|
const createdUserIds: string[] = [];
|
|
|
|
beforeAll(async () => {
|
|
// Create a user that can be used for login-related tests in this suite.
|
|
const { user } = await createAndLoginUser({
|
|
email: `e2e-login-user-${Date.now()}@example.com`,
|
|
fullName: 'E2E Login User',
|
|
// E2E tests use apiClient which doesn't need the `request` object.
|
|
});
|
|
testUser = user;
|
|
createdUserIds.push(user.user.user_id);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (createdUserIds.length > 0) {
|
|
await cleanupDb({ userIds: createdUserIds });
|
|
}
|
|
});
|
|
|
|
describe('Registration Flow', () => {
|
|
it('should successfully register a new user', async () => {
|
|
const email = `e2e-register-success-${Date.now()}@example.com`;
|
|
const fullName = 'E2E Register User';
|
|
|
|
// Act
|
|
const response = await apiClient.registerUser(email, TEST_PASSWORD, fullName);
|
|
const data = await response.json();
|
|
|
|
// Assert
|
|
expect(response.status).toBe(201);
|
|
expect(data.message).toBe('User registered successfully!');
|
|
expect(data.userprofile).toBeDefined();
|
|
expect(data.userprofile.user.email).toBe(email);
|
|
expect(data.token).toBeTypeOf('string');
|
|
|
|
// Add to cleanup
|
|
createdUserIds.push(data.userprofile.user.user_id);
|
|
});
|
|
|
|
it('should fail to register a user with a weak password', async () => {
|
|
const email = `e2e-register-weakpass-${Date.now()}@example.com`;
|
|
const weakPassword = '123';
|
|
|
|
// Act
|
|
const response = await apiClient.registerUser(email, weakPassword, 'Weak Pass User');
|
|
const errorData = await response.json();
|
|
|
|
// Assert
|
|
expect(response.status).toBe(400);
|
|
expect(errorData.errors[0].message).toContain('Password must be at least 8 characters long.');
|
|
});
|
|
|
|
it('should fail to register a user with a duplicate email', async () => {
|
|
const email = `e2e-register-duplicate-${Date.now()}@example.com`;
|
|
|
|
// Act 1: Register the user successfully
|
|
const firstResponse = await apiClient.registerUser(email, TEST_PASSWORD, 'Duplicate User');
|
|
const firstData = await firstResponse.json();
|
|
expect(firstResponse.status).toBe(201);
|
|
createdUserIds.push(firstData.userprofile.user.user_id); // Add for cleanup
|
|
|
|
// Act 2: Attempt to register the same user again
|
|
const secondResponse = await apiClient.registerUser(email, TEST_PASSWORD, 'Duplicate User');
|
|
const errorData = await secondResponse.json();
|
|
|
|
// Assert
|
|
expect(secondResponse.status).toBe(409); // Conflict
|
|
expect(errorData.message).toContain('A user with this email address already exists.');
|
|
});
|
|
});
|
|
|
|
describe('Login Flow', () => {
|
|
it('should successfully log in a registered user', async () => {
|
|
// Act: Attempt to log in with the user created in beforeAll
|
|
const response = await apiClient.loginUser(testUser.user.email, TEST_PASSWORD, false);
|
|
const data = await response.json();
|
|
|
|
// Assert
|
|
expect(response.status).toBe(200);
|
|
expect(data.userprofile).toBeDefined();
|
|
expect(data.userprofile.user.email).toBe(testUser.user.email);
|
|
expect(data.token).toBeTypeOf('string');
|
|
});
|
|
|
|
it('should fail to log in with an incorrect password', async () => {
|
|
// Act: Attempt to log in with the wrong password
|
|
const response = await apiClient.loginUser(testUser.user.email, 'wrong-password', false);
|
|
const errorData = await response.json();
|
|
|
|
// Assert
|
|
expect(response.status).toBe(401);
|
|
expect(errorData.message).toBe('Incorrect email or password.');
|
|
});
|
|
|
|
it('should fail to log in with a non-existent email', async () => {
|
|
const response = await apiClient.loginUser('no-one-here@example.com', TEST_PASSWORD, false);
|
|
const errorData = await response.json();
|
|
|
|
expect(response.status).toBe(401);
|
|
expect(errorData.message).toBe('Incorrect email or password.');
|
|
});
|
|
|
|
it('should be able to access a protected route after logging in', async () => {
|
|
// Arrange: Log in to get a token
|
|
const loginResponse = await apiClient.loginUser(testUser.user.email, TEST_PASSWORD, false);
|
|
const loginData = await loginResponse.json();
|
|
const token = loginData.token;
|
|
|
|
expect(loginResponse.status).toBe(200);
|
|
expect(token).toBeDefined();
|
|
|
|
// Act: Use the token to access a protected route
|
|
const profileResponse = await apiClient.getAuthenticatedUserProfile({ tokenOverride: token });
|
|
const profileData = await profileResponse.json();
|
|
|
|
// Assert
|
|
expect(profileResponse.status).toBe(200);
|
|
expect(profileData).toBeDefined();
|
|
expect(profileData.user.user_id).toBe(testUser.user.user_id);
|
|
expect(profileData.user.email).toBe(testUser.user.email);
|
|
expect(profileData.role).toBe('user');
|
|
});
|
|
|
|
it('should allow an authenticated user to update their profile', async () => {
|
|
// Arrange: Log in to get a token
|
|
const loginResponse = await apiClient.loginUser(testUser.user.email, TEST_PASSWORD, false);
|
|
const loginData = await loginResponse.json();
|
|
const token = loginData.token;
|
|
expect(loginResponse.status).toBe(200);
|
|
|
|
const profileUpdates = {
|
|
full_name: 'E2E Updated Name',
|
|
avatar_url: 'https://www.projectium.com/updated-avatar.png',
|
|
};
|
|
|
|
// Act: Call the update endpoint
|
|
const updateResponse = await apiClient.updateUserProfile(profileUpdates, { tokenOverride: token });
|
|
const updatedProfileData = await updateResponse.json();
|
|
|
|
// Assert: Check the response from the update call
|
|
expect(updateResponse.status).toBe(200);
|
|
expect(updatedProfileData.full_name).toBe(profileUpdates.full_name);
|
|
expect(updatedProfileData.avatar_url).toBe(profileUpdates.avatar_url);
|
|
|
|
// Act 2: Fetch the profile again to verify persistence
|
|
const verifyResponse = await apiClient.getAuthenticatedUserProfile({ tokenOverride: token });
|
|
const verifiedProfileData = await verifyResponse.json();
|
|
|
|
// Assert 2: Check the fetched data
|
|
expect(verifiedProfileData.full_name).toBe(profileUpdates.full_name);
|
|
expect(verifiedProfileData.avatar_url).toBe(profileUpdates.avatar_url);
|
|
});
|
|
});
|
|
|
|
describe('Forgot/Reset Password Flow', () => {
|
|
it('should allow a user to reset their password and log in with the new one', async () => {
|
|
// Arrange: Create a user to reset the password for
|
|
const email = `e2e-reset-pass-${Date.now()}@example.com`;
|
|
const registerResponse = await apiClient.registerUser(email, TEST_PASSWORD, 'Reset Pass User');
|
|
const registerData = await registerResponse.json();
|
|
expect(registerResponse.status).toBe(201);
|
|
createdUserIds.push(registerData.userprofile.user.user_id);
|
|
|
|
// Add a small delay to mitigate potential DB replication lag or race conditions
|
|
// where the user might not be found immediately after creation.
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
|
|
// Act 1: Request a password reset.
|
|
// The test environment returns the token directly in the response for E2E testing.
|
|
const forgotResponse = await apiClient.requestPasswordReset(email);
|
|
const forgotData = await forgotResponse.json();
|
|
const resetToken = forgotData.token;
|
|
|
|
// Assert 1: Check that we received a token.
|
|
expect(forgotResponse.status).toBe(200);
|
|
expect(resetToken).toBeDefined();
|
|
expect(resetToken).toBeTypeOf('string');
|
|
|
|
// Act 2: Use the token to set a new password.
|
|
const newPassword = 'my-new-e2e-password-!@#$';
|
|
const resetResponse = await apiClient.resetPassword(resetToken, newPassword);
|
|
const resetData = await resetResponse.json();
|
|
|
|
// Assert 2: Check for a successful password reset message.
|
|
expect(resetResponse.status).toBe(200);
|
|
expect(resetData.message).toBe('Password has been reset successfully.');
|
|
|
|
// Act 3 & Assert 3 (Verification): Log in with the NEW password to confirm the change.
|
|
const loginResponse = await apiClient.loginUser(email, newPassword, false);
|
|
const loginData = await loginResponse.json();
|
|
|
|
expect(loginResponse.status).toBe(200);
|
|
expect(loginData.userprofile).toBeDefined();
|
|
expect(loginData.userprofile.user.email).toBe(email);
|
|
});
|
|
|
|
it('should return a generic success message for a non-existent email to prevent enumeration', async () => {
|
|
const nonExistentEmail = `non-existent-e2e-${Date.now()}@example.com`;
|
|
const response = await apiClient.requestPasswordReset(nonExistentEmail);
|
|
const data = await response.json();
|
|
expect(response.status).toBe(200);
|
|
expect(data.message).toBe('If an account with that email exists, a password reset link has been sent.');
|
|
expect(data.token).toBeUndefined();
|
|
});
|
|
});
|
|
}); |