add changes
This commit is contained in:
354
backend/__tests__/logout-verify.test.js
Normal file
354
backend/__tests__/logout-verify.test.js
Normal file
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user