Backend Infrastructure: - Complete Express server setup with security middleware (helmet, CORS, rate limiting) - Comprehensive error handling and logging with Winston - Authentication system with JWT tokens and session management - Database models and migrations for Users, Documents, Feedback, and Processing Jobs - API routes structure for authentication and document management - Integration tests for all server components (86 tests passing) Frontend Infrastructure: - React application with TypeScript and Vite - Authentication UI with login form, protected routes, and logout functionality - Authentication context with proper async state management - Component tests with proper async handling (25 tests passing) - Tailwind CSS styling and responsive design Key Features: - User registration, login, and authentication - Protected routes with role-based access control - Comprehensive error handling and user feedback - Database schema with proper relationships - Security middleware and validation - Production-ready build configuration Test Coverage: 111/111 tests passing Tasks Completed: 1-5 (Project setup, Database, Auth system, Frontend UI, Backend infrastructure) Ready for Task 6: File upload backend infrastructure
227 lines
6.2 KiB
TypeScript
227 lines
6.2 KiB
TypeScript
import { UserModel } from '../UserModel';
|
|
import { CreateUserInput } from '../types';
|
|
|
|
// Mock the database pool
|
|
jest.mock('../../config/database', () => ({
|
|
query: jest.fn()
|
|
}));
|
|
|
|
// Mock the logger
|
|
jest.mock('../../utils/logger', () => ({
|
|
info: jest.fn(),
|
|
error: jest.fn(),
|
|
warn: jest.fn()
|
|
}));
|
|
|
|
describe('UserModel', () => {
|
|
let mockPool: any;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
mockPool = require('../../config/database');
|
|
});
|
|
|
|
describe('create', () => {
|
|
it('should create a new user successfully', async () => {
|
|
const userData: CreateUserInput = {
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
password: 'password123',
|
|
role: 'user'
|
|
};
|
|
|
|
const mockUser = {
|
|
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
email: userData.email,
|
|
name: userData.name,
|
|
password_hash: 'hashed_password',
|
|
role: userData.role,
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
is_active: true
|
|
};
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [mockUser] });
|
|
|
|
const result = await UserModel.create(userData);
|
|
|
|
expect(mockPool.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('INSERT INTO users'),
|
|
[userData.email, userData.name, userData.password, userData.role]
|
|
);
|
|
expect(result).toEqual(mockUser);
|
|
});
|
|
|
|
it('should handle database errors', async () => {
|
|
const userData: CreateUserInput = {
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
password: 'password123'
|
|
};
|
|
|
|
const error = new Error('Database error');
|
|
mockPool.query.mockRejectedValueOnce(error);
|
|
|
|
await expect(UserModel.create(userData)).rejects.toThrow('Database error');
|
|
});
|
|
});
|
|
|
|
describe('findById', () => {
|
|
it('should find user by ID successfully', async () => {
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const mockUser = {
|
|
id: userId,
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
password_hash: 'hashed_password',
|
|
role: 'user',
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
is_active: true
|
|
};
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [mockUser] });
|
|
|
|
const result = await UserModel.findById(userId);
|
|
|
|
expect(mockPool.query).toHaveBeenCalledWith(
|
|
'SELECT * FROM users WHERE id = $1 AND is_active = true',
|
|
[userId]
|
|
);
|
|
expect(result).toEqual(mockUser);
|
|
});
|
|
|
|
it('should return null when user not found', async () => {
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [] });
|
|
|
|
const result = await UserModel.findById(userId);
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('findByEmail', () => {
|
|
it('should find user by email successfully', async () => {
|
|
const email = 'test@example.com';
|
|
const mockUser = {
|
|
id: '123e4567-e89b-12d3-a456-426614174000',
|
|
email,
|
|
name: 'Test User',
|
|
password_hash: 'hashed_password',
|
|
role: 'user',
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
is_active: true
|
|
};
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [mockUser] });
|
|
|
|
const result = await UserModel.findByEmail(email);
|
|
|
|
expect(mockPool.query).toHaveBeenCalledWith(
|
|
'SELECT * FROM users WHERE email = $1 AND is_active = true',
|
|
[email]
|
|
);
|
|
expect(result).toEqual(mockUser);
|
|
});
|
|
});
|
|
|
|
describe('update', () => {
|
|
it('should update user successfully', async () => {
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const updates = {
|
|
name: 'Updated Name',
|
|
email: 'updated@example.com'
|
|
};
|
|
|
|
const mockUpdatedUser = {
|
|
id: userId,
|
|
...updates,
|
|
password_hash: 'hashed_password',
|
|
role: 'user',
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
is_active: true
|
|
};
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [mockUpdatedUser] });
|
|
|
|
const result = await UserModel.update(userId, updates);
|
|
|
|
expect(mockPool.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('UPDATE users'),
|
|
expect.arrayContaining([updates.name, updates.email, userId])
|
|
);
|
|
expect(result).toEqual(mockUpdatedUser);
|
|
});
|
|
});
|
|
|
|
describe('delete', () => {
|
|
it('should soft delete user successfully', async () => {
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [{ id: userId }] });
|
|
|
|
const result = await UserModel.delete(userId);
|
|
|
|
expect(mockPool.query).toHaveBeenCalledWith(
|
|
'UPDATE users SET is_active = false WHERE id = $1 RETURNING id',
|
|
[userId]
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false when user not found', async () => {
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [] });
|
|
|
|
const result = await UserModel.delete(userId);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('emailExists', () => {
|
|
it('should return true when email exists', async () => {
|
|
const email = 'test@example.com';
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [{ id: '123' }] });
|
|
|
|
const result = await UserModel.emailExists(email);
|
|
|
|
expect(mockPool.query).toHaveBeenCalledWith(
|
|
'SELECT id FROM users WHERE email = $1 AND is_active = true',
|
|
[email]
|
|
);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false when email does not exist', async () => {
|
|
const email = 'test@example.com';
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [] });
|
|
|
|
const result = await UserModel.emailExists(email);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('count', () => {
|
|
it('should return correct user count', async () => {
|
|
const expectedCount = 5;
|
|
|
|
mockPool.query.mockResolvedValueOnce({ rows: [{ count: expectedCount.toString() }] });
|
|
|
|
const result = await UserModel.count();
|
|
|
|
expect(mockPool.query).toHaveBeenCalledWith(
|
|
'SELECT COUNT(*) FROM users WHERE is_active = true'
|
|
);
|
|
expect(result).toBe(expectedCount);
|
|
});
|
|
});
|
|
});
|