Spaces:
Sleeping
Sleeping
| // server/test/authProtect.test.js | |
| // Tests for protect and adminOnly middleware (authMiddleware.js) | |
| // Uses node:test + node:assert/strict β no real DB or server required. | |
| // Set JWT_SECRET BEFORE any module require so configs/env.js captures it. | |
| // (If authLogin.test.js already set it in this process, this is a no-op | |
| // because the value is the same.) | |
| process.env.JWT_SECRET = 'test-secret-for-auth-login-tests-min32ch'; | |
| const test = require('node:test'); | |
| const assert = require('node:assert/strict'); | |
| const jwt = require('jsonwebtoken'); | |
| const { protect, adminOnly } = require('../middleware/authMiddleware'); | |
| const AdminUser = require('../models/AdminUser'); | |
| // Secret used by authMiddleware β must match what was set in process.env above | |
| // at the time the module was first loaded (or in authLogin.test.js). | |
| const TEST_SECRET = 'test-secret-for-auth-login-tests-min32ch'; | |
| // βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function makeProtectRes() { | |
| const res = { | |
| _status: null, | |
| _body: null, | |
| status(code) { this._status = code; return this; }, | |
| json(body) { this._body = body; return this; }, | |
| }; | |
| return res; | |
| } | |
| // βββ protect tests ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| test('protect rejects request with no cookie', async () => { | |
| const req = { cookies: {} }; // no token | |
| const res = makeProtectRes(); | |
| let nextCalled = false; | |
| await protect(req, res, () => { nextCalled = true; }); | |
| assert.equal(nextCalled, false, 'next() must not be called when no token is present'); | |
| assert.equal(res._status, 401, 'should respond 401 when no cookie token'); | |
| }); | |
| test('protect rejects invalid/expired JWT', async () => { | |
| const req = { cookies: { token: 'this.is.not.a.valid.jwt' } }; | |
| const res = makeProtectRes(); | |
| let nextCalled = false; | |
| await protect(req, res, () => { nextCalled = true; }); | |
| assert.equal(nextCalled, false, 'next() must not be called for invalid token'); | |
| assert.equal(res._status, 401, 'should respond 401 for invalid/expired JWT'); | |
| }); | |
| test('protect rejects deactivated user', async () => { | |
| // Create a valid token signed with the test secret | |
| const token = jwt.sign( | |
| { id: '507f1f77bcf86cd799439011' }, | |
| TEST_SECRET, | |
| { expiresIn: '1h' } | |
| ); | |
| const originalFindById = AdminUser.findById.bind(AdminUser); | |
| // Simulate the chained .select('-passwordHash').lean() call | |
| AdminUser.findById = () => ({ | |
| select: () => ({ | |
| lean: async () => ({ | |
| _id: '507f1f77bcf86cd799439011', | |
| username: 'deactivated', | |
| name: 'Deactivated User', | |
| role: 'admin', | |
| isActive: false, | |
| }), | |
| }), | |
| }); | |
| try { | |
| const req = { cookies: { token } }; | |
| const res = makeProtectRes(); | |
| let nextCalled = false; | |
| await protect(req, res, () => { nextCalled = true; }); | |
| assert.equal(nextCalled, false, 'next() must not be called for deactivated user'); | |
| assert.equal(res._status, 403, 'should respond 403 for deactivated user'); | |
| } finally { | |
| AdminUser.findById = originalFindById; | |
| } | |
| }); | |
| test('protect passes and sets req.user for valid active user', async () => { | |
| const token = jwt.sign( | |
| { id: '507f1f77bcf86cd799439011' }, | |
| TEST_SECRET, | |
| { expiresIn: '1h' } | |
| ); | |
| const originalFindById = AdminUser.findById.bind(AdminUser); | |
| AdminUser.findById = () => ({ | |
| select: () => ({ | |
| lean: async () => ({ | |
| _id: '507f1f77bcf86cd799439011', | |
| username: 'admin', | |
| name: 'Administrator', | |
| role: 'admin', | |
| isActive: true, | |
| }), | |
| }), | |
| }); | |
| try { | |
| const req = { cookies: { token } }; | |
| const res = makeProtectRes(); | |
| let nextCalled = false; | |
| await protect(req, res, () => { nextCalled = true; }); | |
| assert.equal(nextCalled, true, 'next() must be called for valid active user'); | |
| assert.ok(req.user, 'req.user must be set'); | |
| assert.equal(req.user.username, 'admin'); | |
| } finally { | |
| AdminUser.findById = originalFindById; | |
| } | |
| }); | |
| // βββ adminOnly tests ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| test('adminOnly rejects non-admin role', () => { | |
| const req = { user: { role: 'user' } }; | |
| const res = makeProtectRes(); | |
| let nextCalled = false; | |
| adminOnly(req, res, () => { nextCalled = true; }); | |
| assert.equal(nextCalled, false, 'next() must not be called for non-admin role'); | |
| assert.equal(res._status, 403, 'should respond 403 for non-admin role'); | |
| }); | |
| test('adminOnly passes for admin role', () => { | |
| const req = { user: { role: 'admin' } }; | |
| const res = makeProtectRes(); | |
| let nextCalled = false; | |
| adminOnly(req, res, () => { nextCalled = true; }); | |
| assert.equal(nextCalled, true, 'next() must be called for admin role'); | |
| assert.equal(res._status, null, 'no error response should be set for admin role'); | |
| }); | |