Files
rentall-app/backend/tests/unit/sockets/socketAuth.test.js
2026-01-18 19:18:35 -05:00

189 lines
5.9 KiB
JavaScript

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