219 lines
5.6 KiB
JavaScript
219 lines
5.6 KiB
JavaScript
const JwtService = require('../../../src/application/services/JwtService');
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
// Mock jsonwebtoken
|
|
jest.mock('jsonwebtoken');
|
|
|
|
describe('JwtService', () => {
|
|
let jwtService;
|
|
const mockSecret = 'test-secret';
|
|
const mockExpiresIn = '1h';
|
|
|
|
beforeEach(() => {
|
|
// Setup environment variables
|
|
process.env.JWT_SECRET = mockSecret;
|
|
process.env.JWT_EXPIRES_IN = mockExpiresIn;
|
|
|
|
jwtService = new JwtService();
|
|
|
|
// Reset all mocks
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('generateToken', () => {
|
|
it('should generate a JWT token with payload', () => {
|
|
// Arrange
|
|
const payload = { userId: 1, email: 'john@example.com' };
|
|
const mockToken = 'mock_jwt_token_abc123';
|
|
|
|
jwt.sign.mockReturnValue(mockToken);
|
|
|
|
// Act
|
|
const result = jwtService.generateToken(payload);
|
|
|
|
// Assert
|
|
expect(jwt.sign).toHaveBeenCalledWith(payload, mockSecret, { expiresIn: mockExpiresIn });
|
|
expect(result).toBe(mockToken);
|
|
});
|
|
|
|
it('should use default secret if JWT_SECRET not set', () => {
|
|
// Arrange
|
|
delete process.env.JWT_SECRET;
|
|
jwtService = new JwtService();
|
|
const payload = { userId: 1, email: 'john@example.com' };
|
|
|
|
jwt.sign.mockReturnValue('token');
|
|
|
|
// Act
|
|
jwtService.generateToken(payload);
|
|
|
|
// Assert
|
|
expect(jwt.sign).toHaveBeenCalledWith(payload, 'default-secret-change-me', expect.any(Object));
|
|
});
|
|
});
|
|
|
|
describe('verifyToken', () => {
|
|
it('should verify and return decoded token', () => {
|
|
// Arrange
|
|
const token = 'valid_token';
|
|
const mockDecoded = { userId: 1, email: 'john@example.com' };
|
|
|
|
jwt.verify.mockReturnValue(mockDecoded);
|
|
|
|
// Act
|
|
const result = jwtService.verifyToken(token);
|
|
|
|
// Assert
|
|
expect(jwt.verify).toHaveBeenCalledWith(token, mockSecret);
|
|
expect(result).toEqual(mockDecoded);
|
|
});
|
|
|
|
it('should throw error for invalid token', () => {
|
|
// Arrange
|
|
const token = 'invalid_token';
|
|
|
|
jwt.verify.mockImplementation(() => {
|
|
throw new Error('jwt malformed');
|
|
});
|
|
|
|
// Act & Assert
|
|
expect(() => jwtService.verifyToken(token)).toThrow('Invalid or expired token');
|
|
});
|
|
|
|
it('should throw error for expired token', () => {
|
|
// Arrange
|
|
const token = 'expired_token';
|
|
|
|
jwt.verify.mockImplementation(() => {
|
|
throw new Error('jwt expired');
|
|
});
|
|
|
|
// Act & Assert
|
|
expect(() => jwtService.verifyToken(token)).toThrow('Invalid or expired token');
|
|
});
|
|
});
|
|
|
|
describe('extractTokenFromCookies', () => {
|
|
it('should extract token from cookies object', () => {
|
|
// Arrange
|
|
const cookies = { auth_token: 'abc123xyz' };
|
|
|
|
// Act
|
|
const result = jwtService.extractTokenFromCookies(cookies);
|
|
|
|
// Assert
|
|
expect(result).toBe('abc123xyz');
|
|
});
|
|
|
|
it('should return null if cookies object is empty', () => {
|
|
// Arrange
|
|
const cookies = {};
|
|
|
|
// Act
|
|
const result = jwtService.extractTokenFromCookies(cookies);
|
|
|
|
// Assert
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null if cookies is null', () => {
|
|
// Arrange
|
|
const cookies = null;
|
|
|
|
// Act
|
|
const result = jwtService.extractTokenFromCookies(cookies);
|
|
|
|
// Assert
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null if cookies is undefined', () => {
|
|
// Arrange
|
|
const cookies = undefined;
|
|
|
|
// Act
|
|
const result = jwtService.extractTokenFromCookies(cookies);
|
|
|
|
// Assert
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('getCookieOptions', () => {
|
|
it('should return secure cookie options in production', () => {
|
|
// Arrange
|
|
process.env.NODE_ENV = 'production';
|
|
jwtService = new JwtService();
|
|
|
|
// Act
|
|
const options = jwtService.getCookieOptions();
|
|
|
|
// Assert
|
|
expect(options.httpOnly).toBe(true);
|
|
expect(options.secure).toBe(true);
|
|
expect(options.sameSite).toBe('strict');
|
|
expect(options.path).toBe('/');
|
|
expect(options.maxAge).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should return non-secure cookie options in development', () => {
|
|
// Arrange
|
|
process.env.NODE_ENV = 'development';
|
|
jwtService = new JwtService();
|
|
|
|
// Act
|
|
const options = jwtService.getCookieOptions();
|
|
|
|
// Assert
|
|
expect(options.httpOnly).toBe(true);
|
|
expect(options.secure).toBe(false);
|
|
expect(options.sameSite).toBe('strict');
|
|
});
|
|
});
|
|
|
|
describe('getCookieName', () => {
|
|
it('should return the cookie name', () => {
|
|
// Act
|
|
const name = jwtService.getCookieName();
|
|
|
|
// Assert
|
|
expect(name).toBe('auth_token');
|
|
});
|
|
});
|
|
|
|
describe('extractTokenFromHeader (legacy)', () => {
|
|
it('should extract token from valid Authorization header', () => {
|
|
// Arrange
|
|
const authHeader = 'Bearer abc123xyz';
|
|
|
|
// Act
|
|
const result = jwtService.extractTokenFromHeader(authHeader);
|
|
|
|
// Assert
|
|
expect(result).toBe('abc123xyz');
|
|
});
|
|
|
|
it('should return null if Authorization header is missing', () => {
|
|
// Arrange
|
|
const authHeader = null;
|
|
|
|
// Act
|
|
const result = jwtService.extractTokenFromHeader(authHeader);
|
|
|
|
// Assert
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('should return null if Authorization header does not start with Bearer', () => {
|
|
// Arrange
|
|
const authHeader = 'Basic abc123xyz';
|
|
|
|
// Act
|
|
const result = jwtService.extractTokenFromHeader(authHeader);
|
|
|
|
// Assert
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
});
|