136 lines
5.5 KiB
TypeScript
136 lines
5.5 KiB
TypeScript
// src/tests/integration/auth.integration.test.ts
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import supertest from 'supertest';
|
|
import app from '../../../server';
|
|
import { getPool } from '../../services/db/connection.db';
|
|
import { createAndLoginUser, TEST_PASSWORD } from '../utils/testHelpers';
|
|
import type { UserProfile } from '../../types';
|
|
|
|
/**
|
|
* @vitest-environment node
|
|
*/
|
|
|
|
const request = supertest(app);
|
|
|
|
/**
|
|
* These are integration tests that verify the authentication flow against a running backend server.
|
|
* Make sure your Express server is running before executing these tests.
|
|
*
|
|
* To run only these tests: `vitest run src/tests/auth.integration.test.ts`
|
|
*/
|
|
describe('Authentication API Integration', () => {
|
|
let testUserEmail: string;
|
|
let testUser: UserProfile;
|
|
|
|
beforeAll(async () => {
|
|
({ user: testUser } = await createAndLoginUser({ fullName: 'Auth Test User' }));
|
|
testUserEmail = testUser.user.email;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (testUserEmail) {
|
|
await getPool().query('DELETE FROM public.users WHERE email = $1', [testUserEmail]);
|
|
}
|
|
});
|
|
|
|
// This test migrates the logic from the old DevTestRunner.tsx component.
|
|
it('should successfully log in a registered user', async () => {
|
|
// The `rememberMe` parameter is required. For a test, `false` is a safe default.
|
|
const response = await request
|
|
.post('/api/auth/login')
|
|
.send({ email: testUserEmail, password: TEST_PASSWORD, rememberMe: false });
|
|
const data = response.body;
|
|
|
|
// Assert that the API returns the expected structure
|
|
expect(data).toBeDefined();
|
|
expect(response.status).toBe(200);
|
|
expect(data.userprofile).toBeDefined();
|
|
expect(data.userprofile.user.email).toBe(testUserEmail);
|
|
expect(data.userprofile.user.user_id).toBeTypeOf('string');
|
|
expect(data.token).toBeTypeOf('string');
|
|
});
|
|
|
|
it('should fail to log in with an incorrect password', async () => {
|
|
// Use the user we just created
|
|
const adminEmail = testUserEmail;
|
|
const wrongPassword = 'wrongpassword';
|
|
|
|
// The loginUser function returns a Response object. We check its status.
|
|
const response = await request
|
|
.post('/api/auth/login')
|
|
.send({ email: adminEmail, password: wrongPassword, rememberMe: false });
|
|
expect(response.status).toBe(401);
|
|
const errorData = response.body;
|
|
expect(errorData.message).toBe('Incorrect email or password.');
|
|
});
|
|
|
|
it('should fail to log in with a non-existent email', async () => {
|
|
const nonExistentEmail = 'nobody-here@example.com';
|
|
const anyPassword = 'any-password';
|
|
|
|
// The loginUser function returns a Response object. We check its status.
|
|
const response = await request
|
|
.post('/api/auth/login')
|
|
.send({ email: nonExistentEmail, password: anyPassword, rememberMe: false });
|
|
expect(response.status).toBe(401);
|
|
const errorData = response.body;
|
|
// Security best practice: the error message should be identical for wrong password and wrong email
|
|
// to prevent user enumeration attacks.
|
|
expect(errorData.message).toBe('Incorrect email or password.');
|
|
});
|
|
|
|
it('should successfully refresh an access token using a refresh token cookie', async () => {
|
|
// Arrange: Log in to get a fresh, valid refresh token cookie for this specific test.
|
|
// This ensures the test is self-contained and not affected by other tests.
|
|
const loginResponse = await request
|
|
.post('/api/auth/login')
|
|
.send({ email: testUserEmail, password: TEST_PASSWORD, rememberMe: true });
|
|
const refreshTokenCookie = loginResponse.headers['set-cookie'][0].split(';')[0];
|
|
|
|
expect(refreshTokenCookie).toBeDefined();
|
|
|
|
// Act: Make a request to the refresh-token endpoint, including the cookie.
|
|
const response = await request
|
|
.post('/api/auth/refresh-token')
|
|
.set('Cookie', refreshTokenCookie!);
|
|
|
|
// Assert: Check for a successful response and a new access token.
|
|
expect(response.status).toBe(200);
|
|
const data = response.body;
|
|
expect(data.token).toBeTypeOf('string');
|
|
});
|
|
|
|
it('should fail to refresh an access token with an invalid refresh token cookie', async () => {
|
|
// Arrange: Create a fake/invalid cookie.
|
|
const invalidRefreshTokenCookie = 'refreshToken=this-is-not-a-valid-token';
|
|
|
|
// Act: Make a request to the refresh-token endpoint with the invalid cookie.
|
|
const response = await request
|
|
.post('/api/auth/refresh-token')
|
|
.set('Cookie', invalidRefreshTokenCookie);
|
|
|
|
// Assert: Check for a 403 Forbidden response.
|
|
expect(response.status).toBe(403);
|
|
const data = response.body;
|
|
expect(data.message).toBe('Invalid or expired refresh token.');
|
|
});
|
|
|
|
it('should successfully log out and clear the refresh token cookie', async () => {
|
|
// Arrange: Log in to get a valid refresh token cookie.
|
|
const loginResponse = await request
|
|
.post('/api/auth/login')
|
|
.send({ email: testUserEmail, password: TEST_PASSWORD, rememberMe: true });
|
|
const refreshTokenCookie = loginResponse.headers['set-cookie'][0].split(';')[0];
|
|
expect(refreshTokenCookie).toBeDefined();
|
|
|
|
// Act: Make a request to the new logout endpoint, including the cookie.
|
|
const response = await request.post('/api/auth/logout').set('Cookie', refreshTokenCookie!);
|
|
|
|
// Assert: Check for a successful response and a cookie-clearing header.
|
|
expect(response.status).toBe(200);
|
|
const logoutSetCookieHeader = response.headers['set-cookie'][0];
|
|
expect(logoutSetCookieHeader).toContain('refreshToken=;');
|
|
expect(logoutSetCookieHeader).toContain('Max-Age=0');
|
|
});
|
|
});
|