/** * Tests for Logout and Token Verification Endpoints * Task 14: User Logout & Token Verification */ const request = require('supertest'); const app = require('../server'); const { User } = require('../models'); const jwt = require('jsonwebtoken'); const config = require('../config/config'); describe('POST /api/auth/logout', () => { test('Should logout successfully (stateless JWT approach)', async () => { const response = await request(app) .post('/api/auth/logout') .expect(200); expect(response.body.success).toBe(true); expect(response.body.message).toContain('Logout successful'); }); test('Should return success even without token (stateless approach)', async () => { // In a stateless JWT system, logout is client-side only const response = await request(app) .post('/api/auth/logout') .expect(200); expect(response.body.success).toBe(true); }); }); describe('GET /api/auth/verify', () => { let testUser; let validToken; beforeAll(async () => { // Create a test user testUser = await User.create({ username: 'verifyuser', email: 'verify@test.com', password: 'Test@123', role: 'user' }); // Generate valid token validToken = jwt.sign( { userId: testUser.id, email: testUser.email, username: testUser.username, role: testUser.role }, config.jwt.secret, { expiresIn: config.jwt.expire } ); }); afterAll(async () => { // Cleanup if (testUser) { await testUser.destroy({ force: true }); } }); test('Should verify valid token and return user info', async () => { const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${validToken}`) .expect(200); expect(response.body.success).toBe(true); expect(response.body.message).toBe('Token valid'); expect(response.body.data.user).toBeDefined(); expect(response.body.data.user.id).toBe(testUser.id); expect(response.body.data.user.email).toBe(testUser.email); expect(response.body.data.user.username).toBe(testUser.username); // Password should not be included expect(response.body.data.user.password).toBeUndefined(); }); test('Should reject request without token', async () => { const response = await request(app) .get('/api/auth/verify') .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toContain('No token provided'); }); test('Should reject invalid token', async () => { const response = await request(app) .get('/api/auth/verify') .set('Authorization', 'Bearer invalid_token_here') .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toContain('Invalid token'); }); test('Should reject expired token', async () => { // Create an expired token const expiredToken = jwt.sign( { userId: testUser.id, email: testUser.email, username: testUser.username, role: testUser.role }, config.jwt.secret, { expiresIn: '0s' } // Immediately expired ); // Wait a moment to ensure expiration await new Promise(resolve => setTimeout(resolve, 1000)); const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${expiredToken}`) .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toContain('expired'); }); test('Should reject token with invalid format (no Bearer prefix)', async () => { const response = await request(app) .get('/api/auth/verify') .set('Authorization', validToken) // Missing "Bearer " prefix .expect(401); expect(response.body.success).toBe(false); }); test('Should reject token for inactive user', async () => { // Deactivate the user await testUser.update({ is_active: false }); const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${validToken}`) .expect(404); expect(response.body.success).toBe(false); expect(response.body.message).toContain('not found or inactive'); // Reactivate for cleanup await testUser.update({ is_active: true }); }); test('Should reject token for non-existent user', async () => { // Create token with non-existent user ID const fakeToken = jwt.sign( { userId: '00000000-0000-0000-0000-000000000000', email: 'fake@test.com', username: 'fakeuser', role: 'user' }, config.jwt.secret, { expiresIn: config.jwt.expire } ); const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${fakeToken}`) .expect(404); expect(response.body.success).toBe(false); expect(response.body.message).toContain('User not found'); }); test('Should handle malformed Authorization header', async () => { const response = await request(app) .get('/api/auth/verify') .set('Authorization', 'InvalidFormat') .expect(401); expect(response.body.success).toBe(false); }); }); describe('Token Verification Integration Tests', () => { let registeredUser; let userToken; beforeAll(async () => { // Register a new user const registerResponse = await request(app) .post('/api/auth/register') .send({ username: `integrationuser_${Date.now()}`, email: `integration_${Date.now()}@test.com`, password: 'Test@123' }) .expect(201); registeredUser = registerResponse.body.data.user; userToken = registerResponse.body.data.token; }); afterAll(async () => { // Cleanup if (registeredUser && registeredUser.id) { const user = await User.findByPk(registeredUser.id); if (user) { await user.destroy({ force: true }); } } }); test('Should verify token immediately after registration', async () => { const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${userToken}`) .expect(200); expect(response.body.success).toBe(true); expect(response.body.data.user.id).toBe(registeredUser.id); }); test('Should verify token after login', async () => { // Login with the registered user const loginResponse = await request(app) .post('/api/auth/login') .send({ email: registeredUser.email, password: 'Test@123' }) .expect(200); const loginToken = loginResponse.body.data.token; // Verify the login token const verifyResponse = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${loginToken}`) .expect(200); expect(verifyResponse.body.success).toBe(true); expect(verifyResponse.body.data.user.id).toBe(registeredUser.id); }); test('Should complete full auth flow: register -> verify -> logout', async () => { // 1. Register const registerResponse = await request(app) .post('/api/auth/register') .send({ username: `flowuser_${Date.now()}`, email: `flow_${Date.now()}@test.com`, password: 'Test@123' }) .expect(201); const token = registerResponse.body.data.token; const userId = registerResponse.body.data.user.id; // 2. Verify token const verifyResponse = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${token}`) .expect(200); expect(verifyResponse.body.success).toBe(true); // 3. Logout const logoutResponse = await request(app) .post('/api/auth/logout') .expect(200); expect(logoutResponse.body.success).toBe(true); // 4. Token should still be valid (stateless JWT) // In a real app, client would delete the token const verifyAfterLogout = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${token}`) .expect(200); expect(verifyAfterLogout.body.success).toBe(true); // Cleanup const user = await User.findByPk(userId); if (user) { await user.destroy({ force: true }); } }); }); describe('Token Security Tests', () => { test('Should reject token signed with wrong secret', async () => { const fakeToken = jwt.sign( { userId: '12345', email: 'fake@test.com', username: 'fakeuser', role: 'user' }, 'wrong_secret_key', { expiresIn: '24h' } ); const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${fakeToken}`) .expect(401); expect(response.body.success).toBe(false); expect(response.body.message).toContain('Invalid token'); }); test('Should reject tampered token', async () => { // Create a valid token const validToken = jwt.sign( { userId: '12345', email: 'test@test.com', username: 'testuser', role: 'user' }, config.jwt.secret, { expiresIn: '24h' } ); // Tamper with the token by changing a character const tamperedToken = validToken.slice(0, -5) + 'XXXXX'; const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${tamperedToken}`) .expect(401); expect(response.body.success).toBe(false); }); test('Should reject token with missing payload fields', async () => { // Create token with incomplete payload const incompleteToken = jwt.sign( { userId: '12345' // Missing email, username, role }, config.jwt.secret, { expiresIn: '24h' } ); const response = await request(app) .get('/api/auth/verify') .set('Authorization', `Bearer ${incompleteToken}`) .expect(404); // Token is valid but user doesn't exist expect(response.body.success).toBe(false); }); });