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'); }); }); });