more backend unit test coverage
This commit is contained in:
188
backend/tests/unit/sockets/socketAuth.test.js
Normal file
188
backend/tests/unit/sockets/socketAuth.test.js
Normal file
@@ -0,0 +1,188 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const cookie = require('cookie');
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('jsonwebtoken');
|
||||
jest.mock('cookie');
|
||||
jest.mock('../../../models', () => ({
|
||||
User: {
|
||||
findByPk: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
}));
|
||||
|
||||
const { User } = require('../../../models');
|
||||
const { authenticateSocket } = require('../../../sockets/socketAuth');
|
||||
|
||||
describe('Socket Authentication', () => {
|
||||
let mockSocket;
|
||||
let mockNext;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSocket = {
|
||||
id: 'socket-123',
|
||||
handshake: {
|
||||
headers: {},
|
||||
auth: {},
|
||||
address: '127.0.0.1',
|
||||
},
|
||||
};
|
||||
mockNext = jest.fn();
|
||||
jest.clearAllMocks();
|
||||
process.env = { ...originalEnv, JWT_ACCESS_SECRET: 'test-secret' };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('Token extraction', () => {
|
||||
it('should extract token from cookie', async () => {
|
||||
mockSocket.handshake.headers.cookie = 'accessToken=cookie-token';
|
||||
cookie.parse.mockReturnValue({ accessToken: 'cookie-token' });
|
||||
jwt.verify.mockReturnValue({ id: 'user-123', jwtVersion: 1 });
|
||||
User.findByPk.mockResolvedValue({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
jwtVersion: 1,
|
||||
});
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(jwt.verify).toHaveBeenCalledWith('cookie-token', 'test-secret');
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should extract token from auth object when cookie not present', async () => {
|
||||
mockSocket.handshake.auth.token = 'auth-token';
|
||||
jwt.verify.mockReturnValue({ id: 'user-123', jwtVersion: 1 });
|
||||
User.findByPk.mockResolvedValue({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
jwtVersion: 1,
|
||||
});
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(jwt.verify).toHaveBeenCalledWith('auth-token', 'test-secret');
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should prefer cookie token over auth object token', async () => {
|
||||
mockSocket.handshake.headers.cookie = 'accessToken=cookie-token';
|
||||
mockSocket.handshake.auth.token = 'auth-token';
|
||||
cookie.parse.mockReturnValue({ accessToken: 'cookie-token' });
|
||||
jwt.verify.mockReturnValue({ id: 'user-123', jwtVersion: 1 });
|
||||
User.findByPk.mockResolvedValue({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
jwtVersion: 1,
|
||||
});
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(jwt.verify).toHaveBeenCalledWith('cookie-token', 'test-secret');
|
||||
});
|
||||
|
||||
it('should reject when no token provided', async () => {
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
|
||||
expect(mockNext.mock.calls[0][0].message).toBe('Authentication required');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Token verification', () => {
|
||||
it('should authenticate user with valid token', async () => {
|
||||
mockSocket.handshake.auth.token = 'valid-token';
|
||||
jwt.verify.mockReturnValue({ id: 'user-123', jwtVersion: 1 });
|
||||
User.findByPk.mockResolvedValue({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
jwtVersion: 1,
|
||||
});
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(mockSocket.userId).toBe('user-123');
|
||||
expect(mockSocket.user).toMatchObject({
|
||||
id: 'user-123',
|
||||
email: 'test@example.com',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
});
|
||||
expect(mockNext).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should reject token without user id', async () => {
|
||||
mockSocket.handshake.auth.token = 'invalid-token';
|
||||
jwt.verify.mockReturnValue({ someOtherField: 'value' });
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
|
||||
expect(mockNext.mock.calls[0][0].message).toBe('Invalid token format');
|
||||
});
|
||||
|
||||
it('should reject when user not found', async () => {
|
||||
mockSocket.handshake.auth.token = 'valid-token';
|
||||
jwt.verify.mockReturnValue({ id: 'nonexistent-user', jwtVersion: 1 });
|
||||
User.findByPk.mockResolvedValue(null);
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
|
||||
expect(mockNext.mock.calls[0][0].message).toBe('User not found');
|
||||
});
|
||||
|
||||
it('should reject when JWT version mismatch', async () => {
|
||||
mockSocket.handshake.auth.token = 'old-token';
|
||||
jwt.verify.mockReturnValue({ id: 'user-123', jwtVersion: 1 });
|
||||
User.findByPk.mockResolvedValue({
|
||||
id: 'user-123',
|
||||
jwtVersion: 2, // Different version
|
||||
});
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
|
||||
expect(mockNext.mock.calls[0][0].message).toContain('password change');
|
||||
});
|
||||
|
||||
it('should handle expired token', async () => {
|
||||
mockSocket.handshake.auth.token = 'expired-token';
|
||||
const error = new Error('jwt expired');
|
||||
error.name = 'TokenExpiredError';
|
||||
jwt.verify.mockImplementation(() => { throw error; });
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
|
||||
expect(mockNext.mock.calls[0][0].message).toBe('Token expired');
|
||||
});
|
||||
|
||||
it('should handle generic authentication errors', async () => {
|
||||
mockSocket.handshake.auth.token = 'invalid-token';
|
||||
jwt.verify.mockImplementation(() => { throw new Error('Invalid token'); });
|
||||
|
||||
await authenticateSocket(mockSocket, mockNext);
|
||||
|
||||
expect(mockNext).toHaveBeenCalledWith(expect.any(Error));
|
||||
expect(mockNext.mock.calls[0][0].message).toBe('Authentication failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user