Some checks failed
Deploy to Staging / Build Images (pull_request) Successful in 6m41s
Deploy to Staging / Deploy to Staging (pull_request) Successful in 52s
Deploy to Staging / Verify Staging (pull_request) Failing after 4m7s
Deploy to Staging / Notify Staging Ready (pull_request) Has been skipped
Deploy to Staging / Notify Staging Failure (pull_request) Successful in 9s
Backend test fixtures: - Replace auth0|xxx format with UUID in all test userId values - Update admin tests for new id/userProfileId schema - Add missing deletionRequestedAt/deletionScheduledFor to auth test mocks - Fix admin integration test supertest usage (app.server) Frontend: - AdminUser type: auth0Sub -> id + userProfileId - admin.api.ts: all user management methods use userId (UUID) params - useUsers/useAdmins hooks: auth0Sub -> userId/id in mutations - AdminUsersPage + AdminUsersMobileScreen: user.auth0Sub -> user.id - Remove encodeURIComponent (UUIDs don't need encoding) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
216 lines
6.7 KiB
TypeScript
216 lines
6.7 KiB
TypeScript
/**
|
|
* @ai-summary Integration tests for auth API endpoints
|
|
* @ai-context Tests complete request/response cycle with mocked Auth0
|
|
*/
|
|
|
|
import request from 'supertest';
|
|
import { buildApp } from '../../../../app';
|
|
import { pool } from '../../../../core/config/database';
|
|
import { auth0ManagementClient } from '../../../../core/auth/auth0-management.client';
|
|
import fastifyPlugin from 'fastify-plugin';
|
|
import { FastifyInstance } from 'fastify';
|
|
|
|
// Mock Auth0 Management client
|
|
jest.mock('../../../../core/auth/auth0-management.client');
|
|
const mockAuth0Client = jest.mocked(auth0ManagementClient);
|
|
|
|
// Mock auth plugin for protected routes
|
|
jest.mock('../../../../core/plugins/auth.plugin', () => {
|
|
return {
|
|
default: fastifyPlugin(async function (fastify) {
|
|
fastify.decorate('authenticate', async function (request, _reply) {
|
|
// JWT sub is still auth0|xxx format
|
|
request.user = { sub: 'auth0|test-user-123' };
|
|
});
|
|
}, { name: 'auth-plugin' }),
|
|
};
|
|
});
|
|
|
|
describe('Auth Integration Tests', () => {
|
|
let app: FastifyInstance;
|
|
|
|
beforeAll(async () => {
|
|
app = await buildApp();
|
|
await app.ready();
|
|
|
|
// Ensure user_profiles table exists (should be created by migrations)
|
|
// We don't need to run migration here as it should already exist
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
await pool.end();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
jest.clearAllMocks();
|
|
|
|
// Clean up test data before each test
|
|
await pool.query('DELETE FROM user_profiles WHERE auth0_sub = $1', [
|
|
'auth0|test-user-123',
|
|
]);
|
|
await pool.query('DELETE FROM user_profiles WHERE email = $1', [
|
|
'newuser@example.com',
|
|
]);
|
|
});
|
|
|
|
describe('POST /api/auth/signup', () => {
|
|
it('should create a new user successfully', async () => {
|
|
const email = 'newuser@example.com';
|
|
const password = 'Password123';
|
|
const auth0UserId = 'auth0|new-user-456';
|
|
|
|
mockAuth0Client.createUser.mockResolvedValue(auth0UserId);
|
|
|
|
const response = await request(app.server)
|
|
.post('/api/auth/signup')
|
|
.send({ email, password })
|
|
.expect(201);
|
|
|
|
expect(response.body).toMatchObject({
|
|
userId: auth0UserId,
|
|
email,
|
|
message: expect.stringContaining('check your email'),
|
|
});
|
|
|
|
expect(mockAuth0Client.createUser).toHaveBeenCalledWith({ email, password });
|
|
|
|
// Verify user was created in local database
|
|
const userResult = await pool.query(
|
|
'SELECT * FROM user_profiles WHERE auth0_sub = $1',
|
|
[auth0UserId]
|
|
);
|
|
expect(userResult.rows).toHaveLength(1);
|
|
expect(userResult.rows[0].email).toBe(email);
|
|
expect(userResult.rows[0].email_verified).toBe(false);
|
|
});
|
|
|
|
it('should reject signup with invalid email', async () => {
|
|
const response = await request(app.server)
|
|
.post('/api/auth/signup')
|
|
.send({ email: 'invalid-email', password: 'Password123' })
|
|
.expect(400);
|
|
|
|
expect(response.body.message).toContain('validation');
|
|
});
|
|
|
|
it('should reject signup with weak password', async () => {
|
|
const response = await request(app.server)
|
|
.post('/api/auth/signup')
|
|
.send({ email: 'test@example.com', password: 'weak' })
|
|
.expect(400);
|
|
|
|
expect(response.body.message).toContain('validation');
|
|
});
|
|
|
|
it('should reject signup when email already exists', async () => {
|
|
const email = 'existing@example.com';
|
|
const password = 'Password123';
|
|
|
|
mockAuth0Client.createUser.mockRejectedValue(
|
|
new Error('User already exists')
|
|
);
|
|
|
|
const response = await request(app.server)
|
|
.post('/api/auth/signup')
|
|
.send({ email, password })
|
|
.expect(409);
|
|
|
|
expect(response.body.error).toBe('Conflict');
|
|
expect(response.body.message).toContain('already exists');
|
|
});
|
|
});
|
|
|
|
describe('GET /api/auth/verify-status', () => {
|
|
beforeEach(async () => {
|
|
// Create a test user profile
|
|
await pool.query(
|
|
'INSERT INTO user_profiles (auth0_sub, email, email_verified) VALUES ($1, $2, $3)',
|
|
['auth0|test-user-123', 'test@example.com', false]
|
|
);
|
|
});
|
|
|
|
it('should return verification status', async () => {
|
|
const email = 'test@example.com';
|
|
|
|
mockAuth0Client.getUser.mockResolvedValue({
|
|
userId: 'auth0|test-user-123',
|
|
email,
|
|
emailVerified: false,
|
|
});
|
|
|
|
const response = await request(app.server)
|
|
.get('/api/auth/verify-status')
|
|
.expect(200);
|
|
|
|
expect(response.body).toMatchObject({
|
|
emailVerified: false,
|
|
email,
|
|
});
|
|
|
|
expect(mockAuth0Client.getUser).toHaveBeenCalledWith('auth0|test-user-123');
|
|
});
|
|
|
|
it('should update local database when verification status changes', async () => {
|
|
const email = 'test@example.com';
|
|
|
|
mockAuth0Client.getUser.mockResolvedValue({
|
|
userId: 'auth0|test-user-123',
|
|
email,
|
|
emailVerified: true,
|
|
});
|
|
|
|
const response = await request(app.server)
|
|
.get('/api/auth/verify-status')
|
|
.expect(200);
|
|
|
|
expect(response.body.emailVerified).toBe(true);
|
|
|
|
// Verify local database was updated
|
|
const userResult = await pool.query(
|
|
'SELECT email_verified FROM user_profiles WHERE auth0_sub = $1',
|
|
['auth0|test-user-123']
|
|
);
|
|
expect(userResult.rows[0].email_verified).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/auth/resend-verification', () => {
|
|
beforeEach(async () => {
|
|
// Create a test user profile
|
|
await pool.query(
|
|
'INSERT INTO user_profiles (auth0_sub, email, email_verified) VALUES ($1, $2, $3)',
|
|
['auth0|test-user-123', 'test@example.com', false]
|
|
);
|
|
});
|
|
|
|
it('should resend verification email when user is not verified', async () => {
|
|
mockAuth0Client.checkEmailVerified.mockResolvedValue(false);
|
|
mockAuth0Client.resendVerificationEmail.mockResolvedValue(undefined);
|
|
|
|
const response = await request(app.server)
|
|
.post('/api/auth/resend-verification')
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toContain('Verification email sent');
|
|
expect(mockAuth0Client.checkEmailVerified).toHaveBeenCalledWith(
|
|
'auth0|test-user-123'
|
|
);
|
|
expect(mockAuth0Client.resendVerificationEmail).toHaveBeenCalledWith(
|
|
'auth0|test-user-123'
|
|
);
|
|
});
|
|
|
|
it('should skip sending email when user is already verified', async () => {
|
|
mockAuth0Client.checkEmailVerified.mockResolvedValue(true);
|
|
|
|
const response = await request(app.server)
|
|
.post('/api/auth/resend-verification')
|
|
.expect(200);
|
|
|
|
expect(response.body.message).toContain('already verified');
|
|
expect(mockAuth0Client.resendVerificationEmail).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|