Add comprehensive auth middleware test coverage for scraper routes
All checks were successful
CI/CD Pipeline - Apartment API / Scan Dependencies (pull_request) Successful in 13s
CI/CD Pipeline - Apartment API / Lint & Test (pull_request) Successful in 41s
CI/CD Pipeline - Apartment API / Send Webhook Notification (pull_request) Successful in 2s
CI/CD Pipeline - Apartment API / Build & Push Image (pull_request) Has been skipped
CI/CD Pipeline - Apartment API / Deploy to Production (pull_request) Has been skipped
All checks were successful
CI/CD Pipeline - Apartment API / Scan Dependencies (pull_request) Successful in 13s
CI/CD Pipeline - Apartment API / Lint & Test (pull_request) Successful in 41s
CI/CD Pipeline - Apartment API / Send Webhook Notification (pull_request) Successful in 2s
CI/CD Pipeline - Apartment API / Build & Push Image (pull_request) Has been skipped
CI/CD Pipeline - Apartment API / Deploy to Production (pull_request) Has been skipped
Add 29 new tests verifying requireAuth and requireAdmin middleware behavior across all 3 scraper endpoints (POST /run, GET /status, GET /history). Test coverage includes: - Missing JWT token returns 401 - Invalid JWT token returns 401 - Expired JWT token returns 401 - Token signed with wrong secret returns 401 - Disabled user returns 401 - Non-existent user returns 401 - Non-admin user returns 403 - Valid admin user receives expected 200/202 responses - Middleware ordering: auth failures return 401 before admin check Total scraperRoutes tests: 81 (52 existing + 29 new)
This commit is contained in:
@ -33,6 +33,7 @@
|
||||
*/
|
||||
|
||||
const request = require('supertest');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { MongoClient, ObjectId } = require('mongodb');
|
||||
const {
|
||||
createTestApp,
|
||||
@ -1333,4 +1334,223 @@ describe('Scraper Routes', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Authentication & Authorization Middleware Tests (SCRAPE-21)
|
||||
// Comprehensive tests for requireAuth + requireAdmin on all
|
||||
// /admin/scraper/* routes
|
||||
// ============================================================
|
||||
describe('Authentication & Authorization Middleware', () => {
|
||||
|
||||
const scraperRoutes = [
|
||||
{ method: 'post', path: '/api/admin/scraper/run', expectedAdminStatus: 202 },
|
||||
{ method: 'get', path: '/api/admin/scraper/status', expectedAdminStatus: 200 },
|
||||
{ method: 'get', path: '/api/admin/scraper/history', expectedAdminStatus: 200 }
|
||||
];
|
||||
|
||||
// ============================================================
|
||||
// 401 - Missing JWT token
|
||||
// ============================================================
|
||||
describe('missing JWT token (401)', () => {
|
||||
scraperRoutes.forEach(({ method, path }) => {
|
||||
it(`should return 401 for ${method.toUpperCase()} ${path} without auth token`, async () => {
|
||||
const res = await request(app)[method](path)
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toHaveProperty('error');
|
||||
expect(res.body.error).toBe('Authentication required');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 401 - Invalid JWT token
|
||||
// ============================================================
|
||||
describe('invalid JWT token (401)', () => {
|
||||
scraperRoutes.forEach(({ method, path }) => {
|
||||
it(`should return 401 for ${method.toUpperCase()} ${path} with invalid token`, async () => {
|
||||
const res = await request(app)[method](path)
|
||||
.set('Cookie', ['auth_token=this-is-not-a-valid-jwt-token'])
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toHaveProperty('error');
|
||||
expect(res.body.error).toBe('Invalid token');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 401 - Expired JWT token
|
||||
// ============================================================
|
||||
describe('expired JWT token (401)', () => {
|
||||
scraperRoutes.forEach(({ method, path }) => {
|
||||
it(`should return 401 for ${method.toUpperCase()} ${path} with expired token`, async () => {
|
||||
const adminUser = createTestAdmin();
|
||||
await insertTestUser(db, adminUser);
|
||||
|
||||
// Create a token that expired 1 hour ago
|
||||
const secret = process.env.JWT_SECRET || 'test-jwt-secret-for-testing-only';
|
||||
const expiredToken = jwt.sign(
|
||||
{ userId: adminUser._id.toString() },
|
||||
secret,
|
||||
{ expiresIn: '-1h' }
|
||||
);
|
||||
|
||||
const res = await request(app)[method](path)
|
||||
.set('Cookie', [`auth_token=${expiredToken}`])
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toHaveProperty('error');
|
||||
expect(res.body.error).toBe('Invalid token');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 401 - Token signed with wrong secret
|
||||
// ============================================================
|
||||
describe('token signed with wrong secret (401)', () => {
|
||||
scraperRoutes.forEach(({ method, path }) => {
|
||||
it(`should return 401 for ${method.toUpperCase()} ${path} with wrong-secret token`, async () => {
|
||||
const adminUser = createTestAdmin();
|
||||
await insertTestUser(db, adminUser);
|
||||
|
||||
// Sign token with a different secret
|
||||
const wrongSecretToken = jwt.sign(
|
||||
{ userId: adminUser._id.toString() },
|
||||
'completely-wrong-secret-key',
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
const res = await request(app)[method](path)
|
||||
.set('Cookie', [`auth_token=${wrongSecretToken}`])
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toHaveProperty('error');
|
||||
expect(res.body.error).toBe('Invalid token');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 401 - Disabled user (isActive: false)
|
||||
// ============================================================
|
||||
describe('disabled user (401)', () => {
|
||||
scraperRoutes.forEach(({ method, path }) => {
|
||||
it(`should return 401 for ${method.toUpperCase()} ${path} when user is disabled`, async () => {
|
||||
const disabledAdmin = createTestAdmin({ isActive: false });
|
||||
await insertTestUser(db, disabledAdmin);
|
||||
const token = generateTestToken(disabledAdmin._id);
|
||||
|
||||
const res = await request(app)[method](path)
|
||||
.set('Cookie', [`auth_token=${token}`])
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toHaveProperty('error');
|
||||
expect(res.body.error).toBe('Authentication required');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 401 - Token for non-existent user
|
||||
// ============================================================
|
||||
describe('non-existent user (401)', () => {
|
||||
scraperRoutes.forEach(({ method, path }) => {
|
||||
it(`should return 401 for ${method.toUpperCase()} ${path} when user does not exist`, async () => {
|
||||
const nonExistentId = new ObjectId();
|
||||
const token = generateTestToken(nonExistentId);
|
||||
|
||||
const res = await request(app)[method](path)
|
||||
.set('Cookie', [`auth_token=${token}`])
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body).toHaveProperty('error');
|
||||
expect(res.body.error).toBe('Authentication required');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 403 - Non-admin user
|
||||
// ============================================================
|
||||
describe('non-admin user (403)', () => {
|
||||
scraperRoutes.forEach(({ method, path }) => {
|
||||
it(`should return 403 for ${method.toUpperCase()} ${path} for non-admin user`, async () => {
|
||||
const regularUser = createTestUser({ role: 'user' });
|
||||
await insertTestUser(db, regularUser);
|
||||
const token = generateTestToken(regularUser._id);
|
||||
|
||||
const res = await request(app)[method](path)
|
||||
.set('Cookie', [`auth_token=${token}`])
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(res.body).toHaveProperty('error');
|
||||
expect(res.body.error).toBe('Admin access required');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 200/202 - Valid admin user
|
||||
// ============================================================
|
||||
describe('valid admin user (200/202)', () => {
|
||||
let adminUser;
|
||||
let adminToken;
|
||||
|
||||
beforeEach(async () => {
|
||||
adminUser = createTestAdmin();
|
||||
await insertTestUser(db, adminUser);
|
||||
adminToken = generateTestToken(adminUser._id);
|
||||
});
|
||||
|
||||
scraperRoutes.forEach(({ method, path, expectedAdminStatus }) => {
|
||||
it(`should return ${expectedAdminStatus} for ${method.toUpperCase()} ${path} for admin user`, async () => {
|
||||
const res = await request(app)[method](path)
|
||||
.set('Cookie', [`auth_token=${adminToken}`])
|
||||
.send(method === 'post' ? {} : undefined);
|
||||
|
||||
expect(res.status).toBe(expectedAdminStatus);
|
||||
expect(res.body).toHaveProperty('data');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Middleware ordering: requireAuth runs before requireAdmin
|
||||
// ============================================================
|
||||
describe('middleware ordering', () => {
|
||||
it('should return 401 (not 403) when token is missing, even for non-admin scenario', async () => {
|
||||
// Without any token, requireAuth should reject with 401
|
||||
// before requireAdmin ever runs
|
||||
const res = await request(app)
|
||||
.get('/api/admin/scraper/status');
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body.error).toBe('Authentication required');
|
||||
});
|
||||
|
||||
it('should return 401 (not 403) for disabled admin user', async () => {
|
||||
// A disabled admin should get 401 from requireAuth
|
||||
// even though they have the admin role
|
||||
const disabledAdmin = createTestAdmin({ isActive: false, role: 'admin' });
|
||||
await insertTestUser(db, disabledAdmin);
|
||||
const token = generateTestToken(disabledAdmin._id);
|
||||
|
||||
const res = await request(app)
|
||||
.get('/api/admin/scraper/status')
|
||||
.set('Cookie', [`auth_token=${token}`]);
|
||||
|
||||
expect(res.status).toBe(401);
|
||||
expect(res.body.error).toBe('Authentication required');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user