chore: sync infra docs and coverage
This commit is contained in:
		
							
								
								
									
										79
									
								
								backend/src/tests/applications.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								backend/src/tests/applications.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const { registerUser, createEmployerProfile } = require('./utils'); | ||||
|  | ||||
| async function createJob(token) { | ||||
|   const response = await request(app) | ||||
|     .post('/api/jobs') | ||||
|     .set('Authorization', `Bearer ${token}`) | ||||
|     .send({ | ||||
|       title: 'QA Engineer', | ||||
|       description: 'Ensure software quality', | ||||
|       requirements: ['Attention to detail'], | ||||
|       responsibilities: ['Write test plans'], | ||||
|       location: 'Remote', | ||||
|       employmentType: 'full-time', | ||||
|       remoteAllowed: true | ||||
|     }) | ||||
|     .expect(201); | ||||
|  | ||||
|   return response.body.job; | ||||
| } | ||||
|  | ||||
| describe('Applications API', () => { | ||||
|   it('supports full application lifecycle', async () => { | ||||
|     const { token: employerToken } = await registerUser('employer'); | ||||
|     await createEmployerProfile(employerToken); | ||||
|     const job = await createJob(employerToken); | ||||
|  | ||||
|     const { token: candidateToken } = await registerUser('candidate'); | ||||
|  | ||||
|     await request(app) | ||||
|       .post('/api/candidates') | ||||
|       .set('Authorization', `Bearer ${candidateToken}`) | ||||
|       .send({ | ||||
|         location: 'Remote', | ||||
|         skills: ['Automation'], | ||||
|         experienceLevel: 'mid' | ||||
|       }) | ||||
|       .expect(201); | ||||
|  | ||||
|     const applicationResponse = await request(app) | ||||
|       .post('/api/applications') | ||||
|       .set('Authorization', `Bearer ${candidateToken}`) | ||||
|       .send({ | ||||
|         jobId: job.id, | ||||
|         coverLetter: 'Excited to apply' | ||||
|       }) | ||||
|       .expect(201); | ||||
|  | ||||
|     const applicationId = applicationResponse.body.application.id; | ||||
|  | ||||
|     const listForCandidate = await request(app) | ||||
|       .get('/api/applications') | ||||
|       .set('Authorization', `Bearer ${candidateToken}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(listForCandidate.body.applications).toHaveLength(1); | ||||
|  | ||||
|     await request(app) | ||||
|       .put(`/api/applications/${applicationId}/status`) | ||||
|       .set('Authorization', `Bearer ${employerToken}`) | ||||
|       .send({ status: 'reviewed' }) | ||||
|       .expect(200); | ||||
|  | ||||
|     await request(app) | ||||
|       .put(`/api/applications/${applicationId}/notes`) | ||||
|       .set('Authorization', `Bearer ${employerToken}`) | ||||
|       .send({ notes: 'Strong automation background' }) | ||||
|       .expect(200); | ||||
|  | ||||
|     const detailResponse = await request(app) | ||||
|       .get(`/api/applications/${applicationId}`) | ||||
|       .set('Authorization', `Bearer ${employerToken}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(detailResponse.body.status).toBe('reviewed'); | ||||
|     expect(detailResponse.body.notes).toBe('Strong automation background'); | ||||
|   }); | ||||
| }); | ||||
| @@ -1,23 +1,13 @@ | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const pool = require('../database/connection'); | ||||
|  | ||||
| describe('Authentication', () => { | ||||
|   beforeEach(async () => { | ||||
|     // Clean up database before each test | ||||
|     await pool.query('DELETE FROM users WHERE email LIKE $1', ['test%']); | ||||
|   }); | ||||
|  | ||||
|   afterAll(async () => { | ||||
|     await pool.end(); | ||||
|   }); | ||||
|  | ||||
|   describe('POST /api/auth/register', () => { | ||||
|     it('should register a new user successfully', async () => { | ||||
|       const email = `test-${Date.now()}@example.com`; | ||||
|       const userData = { | ||||
|         firstName: 'Test', | ||||
|         lastName: 'User', | ||||
|         email: 'test@example.com', | ||||
|         email, | ||||
|         password: 'password123', | ||||
|         role: 'candidate' | ||||
|       }; | ||||
| @@ -34,10 +24,11 @@ describe('Authentication', () => { | ||||
|     }); | ||||
|  | ||||
|     it('should return error for duplicate email', async () => { | ||||
|       const email = `test-${Date.now()}@example.com`; | ||||
|       const userData = { | ||||
|         firstName: 'Test', | ||||
|         lastName: 'User', | ||||
|         email: 'test@example.com', | ||||
|         email, | ||||
|         password: 'password123', | ||||
|         role: 'candidate' | ||||
|       }; | ||||
| @@ -57,10 +48,11 @@ describe('Authentication', () => { | ||||
|     }); | ||||
|  | ||||
|     it('should return error for invalid role', async () => { | ||||
|       const email = `test-${Date.now()}@example.com`; | ||||
|       const userData = { | ||||
|         firstName: 'Test', | ||||
|         lastName: 'User', | ||||
|         email: 'test@example.com', | ||||
|         email, | ||||
|         password: 'password123', | ||||
|         role: 'invalid_role' | ||||
|       }; | ||||
| @@ -75,12 +67,15 @@ describe('Authentication', () => { | ||||
|   }); | ||||
|  | ||||
|   describe('POST /api/auth/login', () => { | ||||
|     let loginEmail; | ||||
|  | ||||
|     beforeEach(async () => { | ||||
|       // Create a test user | ||||
|       loginEmail = `test-${Date.now()}@example.com`; | ||||
|       const userData = { | ||||
|         firstName: 'Test', | ||||
|         lastName: 'User', | ||||
|         email: 'test@example.com', | ||||
|         email: loginEmail, | ||||
|         password: 'password123', | ||||
|         role: 'candidate' | ||||
|       }; | ||||
| @@ -92,7 +87,7 @@ describe('Authentication', () => { | ||||
|  | ||||
|     it('should login with valid credentials', async () => { | ||||
|       const loginData = { | ||||
|         email: 'test@example.com', | ||||
|         email: loginEmail, | ||||
|         password: 'password123' | ||||
|       }; | ||||
|  | ||||
| @@ -108,7 +103,7 @@ describe('Authentication', () => { | ||||
|  | ||||
|     it('should return error for invalid credentials', async () => { | ||||
|       const loginData = { | ||||
|         email: 'test@example.com', | ||||
|         email: loginEmail, | ||||
|         password: 'wrongpassword' | ||||
|       }; | ||||
|  | ||||
| @@ -137,13 +132,15 @@ describe('Authentication', () => { | ||||
|  | ||||
|   describe('GET /api/auth/me', () => { | ||||
|     let token; | ||||
|     let email; | ||||
|  | ||||
|     beforeEach(async () => { | ||||
|       // Create a test user and get token | ||||
|       email = `test-${Date.now()}@example.com`; | ||||
|       const userData = { | ||||
|         firstName: 'Test', | ||||
|         lastName: 'User', | ||||
|         email: 'test@example.com', | ||||
|         email, | ||||
|         password: 'password123', | ||||
|         role: 'candidate' | ||||
|       }; | ||||
| @@ -162,7 +159,7 @@ describe('Authentication', () => { | ||||
|         .expect(200); | ||||
|  | ||||
|       expect(response.body).toHaveProperty('user'); | ||||
|       expect(response.body.user.email).toBe('test@example.com'); | ||||
|       expect(response.body.user.email).toBe(email); | ||||
|     }); | ||||
|  | ||||
|     it('should return error without token', async () => { | ||||
|   | ||||
							
								
								
									
										44
									
								
								backend/src/tests/candidates.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								backend/src/tests/candidates.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const { registerUser } = require('./utils'); | ||||
|  | ||||
| describe('Candidates API', () => { | ||||
|   it('creates and retrieves candidate profiles with filters', async () => { | ||||
|     const { token } = await registerUser('candidate'); | ||||
|  | ||||
|     const createResponse = await request(app) | ||||
|       .post('/api/candidates') | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .send({ | ||||
|         phone: '+1-555-5555', | ||||
|         location: 'Austin, TX', | ||||
|         skills: ['JavaScript', 'React'], | ||||
|         experienceLevel: 'mid' | ||||
|       }) | ||||
|       .expect(201); | ||||
|  | ||||
|     const candidateId = createResponse.body.candidate.id; | ||||
|  | ||||
|     const listResponse = await request(app) | ||||
|       .get('/api/candidates?skills=React&location=Austin') | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(listResponse.body.candidates).toHaveLength(1); | ||||
|  | ||||
|     const detailResponse = await request(app) | ||||
|       .get(`/api/candidates/${candidateId}`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(detailResponse.body.skills).toContain('React'); | ||||
|  | ||||
|     const updateResponse = await request(app) | ||||
|       .put(`/api/candidates/${candidateId}`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .send({ availability: 'Immediate' }) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(updateResponse.body.candidate.availability).toBe('Immediate'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										34
									
								
								backend/src/tests/employers.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								backend/src/tests/employers.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const { registerUser, createEmployerProfile } = require('./utils'); | ||||
|  | ||||
| describe('Employers API', () => { | ||||
|   it('allows an employer to create and update their profile', async () => { | ||||
|     const { token } = await registerUser('employer'); | ||||
|  | ||||
|     const employer = await createEmployerProfile(token, { | ||||
|       companyName: 'Acme Corp', | ||||
|       industry: 'Manufacturing' | ||||
|     }); | ||||
|  | ||||
|     expect(employer).toMatchObject({ | ||||
|       company_name: 'Acme Corp', | ||||
|       industry: 'Manufacturing' | ||||
|     }); | ||||
|  | ||||
|     const listResponse = await request(app) | ||||
|       .get('/api/employers') | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(listResponse.body.length).toBeGreaterThan(0); | ||||
|  | ||||
|     const updateResponse = await request(app) | ||||
|       .put(`/api/employers/${employer.id}`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .send({ description: 'Updated description' }) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(updateResponse.body.employer.description).toBe('Updated description'); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										1
									
								
								backend/src/tests/fixtures/resume.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								backend/src/tests/fixtures/resume.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| Sample resume content for testing. | ||||
							
								
								
									
										65
									
								
								backend/src/tests/globalSetup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								backend/src/tests/globalSetup.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| const { spawnSync } = require('child_process'); | ||||
| const path = require('path'); | ||||
|  | ||||
| const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); | ||||
|  | ||||
| module.exports = async () => { | ||||
|   process.env.NODE_ENV = process.env.NODE_ENV || 'test'; | ||||
|   process.env.JWT_SECRET = process.env.JWT_SECRET || 'merchantsofhope_test_secret'; | ||||
|   process.env.POSTGRES_DB = process.env.POSTGRES_DB || 'merchantsofhope_test'; | ||||
|   process.env.POSTGRES_USER = process.env.POSTGRES_USER || 'postgres'; | ||||
|   process.env.POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD || 'postgres'; | ||||
|   process.env.POSTGRES_HOST = process.env.POSTGRES_HOST || '127.0.0.1'; | ||||
|   process.env.POSTGRES_PORT = process.env.POSTGRES_PORT || '55432'; | ||||
|  | ||||
|   const composeFile = path.join(__dirname, '..', '..', '..', 'docker-compose.test.yml'); | ||||
|  | ||||
|   const upResult = spawnSync( | ||||
|     'docker', | ||||
|     ['compose', '-f', composeFile, 'up', '-d', 'merchantsofhope-supplyanddemandportal-test-database'], | ||||
|     { | ||||
|       cwd: path.join(__dirname, '..', '..'), | ||||
|       stdio: 'inherit', | ||||
|       env: process.env | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   if (upResult.status !== 0) { | ||||
|     throw new Error('Failed to start test database container'); | ||||
|   } | ||||
|  | ||||
|   let ready = false; | ||||
|   for (let attempt = 0; attempt < 30; attempt += 1) { | ||||
|     const health = spawnSync( | ||||
|       'docker', | ||||
|       ['compose', '-f', composeFile, 'exec', '-T', 'merchantsofhope-supplyanddemandportal-test-database', 'pg_isready', '-U', process.env.POSTGRES_USER], | ||||
|       { | ||||
|         cwd: path.join(__dirname, '..', '..'), | ||||
|         env: process.env, | ||||
|         stdio: 'ignore' | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     if (health.status === 0) { | ||||
|       ready = true; | ||||
|       break; | ||||
|     } | ||||
|  | ||||
|     sleep(1000); | ||||
|   } | ||||
|  | ||||
|   if (!ready) { | ||||
|     throw new Error('Database did not become ready in time'); | ||||
|   } | ||||
|  | ||||
|   const migratePath = path.join(__dirname, '..', 'database', 'migrate.js'); | ||||
|   const result = spawnSync('node', [migratePath], { | ||||
|     cwd: path.join(__dirname, '..', '..'), | ||||
|     stdio: 'inherit', | ||||
|     env: process.env | ||||
|   }); | ||||
|  | ||||
|   if (result.status !== 0) { | ||||
|     throw new Error('Database migration failed before running tests'); | ||||
|   } | ||||
| }; | ||||
							
								
								
									
										20
									
								
								backend/src/tests/globalTeardown.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								backend/src/tests/globalTeardown.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| const { spawnSync } = require('child_process'); | ||||
| const path = require('path'); | ||||
| const pool = require('../database/connection'); | ||||
| const { cleanupUploads } = require('./utils'); | ||||
|  | ||||
| module.exports = async () => { | ||||
|   await cleanupUploads(); | ||||
|   await pool.end(); | ||||
|  | ||||
|   const composeFile = path.join(__dirname, '..', '..', '..', 'docker-compose.test.yml'); | ||||
|   spawnSync( | ||||
|     'docker', | ||||
|     ['compose', '-f', composeFile, 'down', '--volumes'], | ||||
|     { | ||||
|       cwd: path.join(__dirname, '..', '..'), | ||||
|       stdio: 'inherit', | ||||
|       env: process.env | ||||
|     } | ||||
|   ); | ||||
| }; | ||||
| @@ -1,48 +1,14 @@ | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const pool = require('../database/connection'); | ||||
| const { registerUser, createEmployerProfile } = require('./utils'); | ||||
|  | ||||
| describe('Jobs API', () => { | ||||
|   let authToken; | ||||
|   let employerId; | ||||
|  | ||||
|   beforeAll(async () => { | ||||
|     // Create test user and get auth token | ||||
|     const userData = { | ||||
|       firstName: 'Test', | ||||
|       lastName: 'Employer', | ||||
|       email: 'employer@test.com', | ||||
|       password: 'password123', | ||||
|       role: 'employer' | ||||
|     }; | ||||
|  | ||||
|     const registerResponse = await request(app) | ||||
|       .post('/api/auth/register') | ||||
|       .send(userData); | ||||
|  | ||||
|     authToken = registerResponse.body.token; | ||||
|  | ||||
|     // Create employer profile | ||||
|     const employerData = { | ||||
|       companyName: 'Test Company', | ||||
|       industry: 'Technology', | ||||
|       companySize: '50-200', | ||||
|       website: 'https://testcompany.com', | ||||
|       description: 'A test company', | ||||
|       address: '123 Test St', | ||||
|       phone: '+1-555-0123' | ||||
|     }; | ||||
|  | ||||
|     const employerResponse = await request(app) | ||||
|       .post('/api/employers') | ||||
|       .set('Authorization', `Bearer ${authToken}`) | ||||
|       .send(employerData); | ||||
|  | ||||
|     employerId = employerResponse.body.employer.id; | ||||
|   }); | ||||
|  | ||||
|   afterAll(async () => { | ||||
|     await pool.end(); | ||||
|   beforeEach(async () => { | ||||
|     const { token } = await registerUser('employer'); | ||||
|     authToken = token; | ||||
|     await createEmployerProfile(token); | ||||
|   }); | ||||
|  | ||||
|   describe('POST /api/jobs', () => { | ||||
|   | ||||
							
								
								
									
										55
									
								
								backend/src/tests/resumes.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								backend/src/tests/resumes.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| const path = require('path'); | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const { registerUser, ensureUploadDir } = require('./utils'); | ||||
|  | ||||
| describe('Resumes API', () => { | ||||
|   it('handles resume upload lifecycle for candidates', async () => { | ||||
|     const { token } = await registerUser('candidate'); | ||||
|  | ||||
|     const candidateResponse = await request(app) | ||||
|       .post('/api/candidates') | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .send({ | ||||
|         location: 'Houston, TX', | ||||
|         skills: ['Node.js'] | ||||
|       }) | ||||
|       .expect(201); | ||||
|  | ||||
|     const candidateId = candidateResponse.body.candidate.id; | ||||
|     const uploadDir = await ensureUploadDir(); | ||||
|     expect(uploadDir).toBeTruthy(); | ||||
|  | ||||
|     const uploadResponse = await request(app) | ||||
|       .post('/api/resumes/upload') | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .field('isPrimary', 'true') | ||||
|       .attach('resume', path.join(__dirname, 'fixtures', 'resume.txt')) | ||||
|       .expect(201); | ||||
|  | ||||
|     const resumeId = uploadResponse.body.resume.id; | ||||
|  | ||||
|     const listResponse = await request(app) | ||||
|       .get(`/api/resumes/candidate/${candidateId}`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(listResponse.body).toHaveLength(1); | ||||
|     expect(listResponse.body[0].is_primary).toBe(true); | ||||
|  | ||||
|     await request(app) | ||||
|       .put(`/api/resumes/${resumeId}/primary`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     await request(app) | ||||
|       .get(`/api/resumes/${resumeId}/download`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     await request(app) | ||||
|       .delete(`/api/resumes/${resumeId}`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .expect(200); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										17
									
								
								backend/src/tests/setup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								backend/src/tests/setup.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| const pool = require('../database/connection'); | ||||
| const { cleanupUploads } = require('./utils'); | ||||
|  | ||||
| afterEach(async () => { | ||||
|   await pool.query(` | ||||
|     TRUNCATE TABLE | ||||
|       applications, | ||||
|       resumes, | ||||
|       interviews, | ||||
|       jobs, | ||||
|       candidates, | ||||
|       employers, | ||||
|       users | ||||
|     RESTART IDENTITY CASCADE | ||||
|   `); | ||||
|   await cleanupUploads(); | ||||
| }); | ||||
							
								
								
									
										35
									
								
								backend/src/tests/users.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								backend/src/tests/users.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const { registerUser, createAdminUser } = require('./utils'); | ||||
|  | ||||
| describe('Users API', () => { | ||||
|   it('allows admin to list users and manage activation', async () => { | ||||
|     const { token: adminToken } = await createAdminUser(); | ||||
|     const { user, token } = await registerUser('candidate'); | ||||
|  | ||||
|     const listResponse = await request(app) | ||||
|       .get('/api/users') | ||||
|       .set('Authorization', `Bearer ${adminToken}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(Array.isArray(listResponse.body)).toBe(true); | ||||
|  | ||||
|     await request(app) | ||||
|       .put(`/api/users/${user.id}`) | ||||
|       .set('Authorization', `Bearer ${token}`) | ||||
|       .send({ firstName: 'Updated' }) | ||||
|       .expect(200); | ||||
|  | ||||
|     await request(app) | ||||
|       .put(`/api/users/${user.id}/deactivate`) | ||||
|       .set('Authorization', `Bearer ${adminToken}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     const reactivate = await request(app) | ||||
|       .put(`/api/users/${user.id}/activate`) | ||||
|       .set('Authorization', `Bearer ${adminToken}`) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(reactivate.body.user.is_active).toBe(true); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										112
									
								
								backend/src/tests/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								backend/src/tests/utils.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| const { randomUUID } = require('crypto'); | ||||
| const path = require('path'); | ||||
| const fs = require('fs'); | ||||
| const bcrypt = require('bcryptjs'); | ||||
| const request = require('supertest'); | ||||
| const app = require('../server'); | ||||
| const config = require('../config'); | ||||
| const pool = require('../database/connection'); | ||||
|  | ||||
| const uniqueEmail = (prefix = 'user') => `${prefix}-${randomUUID()}@example.com`; | ||||
|  | ||||
| const defaultPassword = 'Password123!'; | ||||
|  | ||||
| async function registerUser(role = 'candidate', overrides = {}) { | ||||
|   const userData = { | ||||
|     firstName: overrides.firstName || 'Test', | ||||
|     lastName: overrides.lastName || 'User', | ||||
|     email: overrides.email || uniqueEmail(role), | ||||
|     password: overrides.password || defaultPassword, | ||||
|     role, | ||||
|     ...overrides | ||||
|   }; | ||||
|  | ||||
|   const response = await request(app) | ||||
|     .post('/api/auth/register') | ||||
|     .send(userData); | ||||
|  | ||||
|   return { | ||||
|     token: response.body.token, | ||||
|     user: response.body.user, | ||||
|     credentials: { | ||||
|       email: userData.email, | ||||
|       password: userData.password | ||||
|     } | ||||
|   }; | ||||
| } | ||||
|  | ||||
| async function createEmployerProfile(token, overrides = {}) { | ||||
|   const employerData = { | ||||
|     companyName: overrides.companyName || `Company ${randomUUID().slice(0, 8)}`, | ||||
|     industry: overrides.industry || 'Technology', | ||||
|     companySize: overrides.companySize || '11-50', | ||||
|     website: overrides.website || 'https://example.com', | ||||
|     description: overrides.description || 'Test company description', | ||||
|     address: overrides.address || '123 Test St', | ||||
|     phone: overrides.phone || '+1-555-0000', | ||||
|     ...overrides | ||||
|   }; | ||||
|  | ||||
|   const response = await request(app) | ||||
|     .post('/api/employers') | ||||
|     .set('Authorization', `Bearer ${token}`) | ||||
|     .send(employerData); | ||||
|  | ||||
|   return response.body.employer; | ||||
| } | ||||
|  | ||||
| async function createAdminUser() { | ||||
|   const email = uniqueEmail('admin'); | ||||
|   const password = defaultPassword; | ||||
|   const passwordHash = await bcrypt.hash(password, 10); | ||||
|  | ||||
|   const result = await pool.query( | ||||
|     'INSERT INTO users (email, password_hash, first_name, last_name, role) VALUES ($1, $2, $3, $4, $5) RETURNING id', | ||||
|     [email, passwordHash, 'Admin', 'User', 'admin'] | ||||
|   ); | ||||
|  | ||||
|   const loginResponse = await request(app) | ||||
|     .post('/api/auth/login') | ||||
|     .send({ email, password }); | ||||
|  | ||||
|   return { | ||||
|     token: loginResponse.body.token, | ||||
|     user: loginResponse.body.user, | ||||
|     userId: result.rows[0].id, | ||||
|     credentials: { email, password } | ||||
|   }; | ||||
| } | ||||
|  | ||||
| async function ensureUploadDir() { | ||||
|   const uploadDir = path.join(__dirname, '..', '..', config.uploadDir); | ||||
|   if (!fs.existsSync(uploadDir)) { | ||||
|     fs.mkdirSync(uploadDir, { recursive: true }); | ||||
|   } | ||||
|   return uploadDir; | ||||
| } | ||||
|  | ||||
| async function cleanupUploads() { | ||||
|   const uploadDir = await ensureUploadDir(); | ||||
|   if (!fs.existsSync(uploadDir)) { | ||||
|     return; | ||||
|   } | ||||
|   const entries = fs.readdirSync(uploadDir); | ||||
|   for (const entry of entries) { | ||||
|     const filePath = path.join(uploadDir, entry); | ||||
|     try { | ||||
|       fs.unlinkSync(filePath); | ||||
|     } catch (error) { | ||||
|       // Ignore deletion errors in tests | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   registerUser, | ||||
|   createEmployerProfile, | ||||
|   createAdminUser, | ||||
|   uniqueEmail, | ||||
|   defaultPassword, | ||||
|   ensureUploadDir, | ||||
|   cleanupUploads | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user