Files
Tasks/backend/__tests__/logout-verify.test.js
2025-11-11 00:25:50 +02:00

355 lines
9.8 KiB
JavaScript

/**
* 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);
});
});