86211923db
Repository Interface Optimization: - Created IBaseRepository.ts and IPaginatedRepository.ts - Refactored all 7 repository interfaces to extend base interfaces - Eliminated ~200 lines of redundant code (70% reduction) - Improved type safety and maintainability Dependency Injection Improvements: - Added EmailService and GameTokenService to DIContainer - Updated CreateUserCommandHandler constructor for DI - Updated RequestPasswordResetCommandHandler constructor for DI - Enhanced testability and service consistency Environment Configuration: - Created comprehensive .env.example with 40+ variables - Organized into 12 logical sections (Database, Security, Email, etc.) - Added security guidelines and best practices - Documented all backend environment requirements Documentation: - Added comprehensive codebase review - Created refactoring summary report - Added frontend implementation guide Impact: Improved code quality, reduced maintenance overhead, enhanced developer experience
140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
import { JWTService, TokenPayload } from '../../../src/Application/Services/JWTService';
|
|
import { Request, Response } from 'express';
|
|
import { UserState } from '../../../src/Domain/User/UserAggregate';
|
|
|
|
describe('JWTService - Token Refresh Logic', () => {
|
|
let jwtService: JWTService;
|
|
let mockRequest: Partial<Request>;
|
|
let mockResponse: Partial<Response>;
|
|
let dateNowSpy: jest.SpyInstance;
|
|
|
|
beforeEach(() => {
|
|
jwtService = new JWTService();
|
|
|
|
mockRequest = {
|
|
cookies: {}
|
|
};
|
|
|
|
mockResponse = {
|
|
cookie: jest.fn()
|
|
};
|
|
|
|
// Create a fresh spy for Date.now in each test
|
|
dateNowSpy = jest.spyOn(Date, 'now');
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Always restore Date.now after each test
|
|
dateNowSpy.mockRestore();
|
|
});
|
|
|
|
describe('shouldRefreshToken', () => {
|
|
it('should return true when token is 75% through its lifetime', () => {
|
|
// Token issued at time 100, expires at 900 (lifetime: 800)
|
|
// 75% of 800 = 600, so at time 700 (100 + 600), it should refresh
|
|
const payload: TokenPayload = {
|
|
userId: 'test-user',
|
|
authLevel: 0 as 0 | 1,
|
|
userStatus: UserState.VERIFIED_REGULAR,
|
|
orgId: 'test-org',
|
|
iat: 100,
|
|
exp: 900
|
|
};
|
|
|
|
// Mock current time as 700 (which is 75% through the token lifetime)
|
|
dateNowSpy.mockReturnValue(700 * 1000);
|
|
|
|
const result = jwtService.shouldRefreshToken(payload);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return true when token is more than 75% through its lifetime', () => {
|
|
const payload: TokenPayload = {
|
|
userId: 'test-user',
|
|
authLevel: 0 as 0 | 1,
|
|
userStatus: UserState.VERIFIED_REGULAR,
|
|
orgId: 'test-org',
|
|
iat: 100,
|
|
exp: 900
|
|
};
|
|
|
|
// Mock current time as 750 (which is 81.25% through the token lifetime)
|
|
dateNowSpy.mockReturnValue(750 * 1000);
|
|
|
|
const result = jwtService.shouldRefreshToken(payload);
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should return false when token is less than 75% through its lifetime', () => {
|
|
const payload: TokenPayload = {
|
|
userId: 'test-user',
|
|
authLevel: 0 as 0 | 1,
|
|
userStatus: UserState.VERIFIED_REGULAR,
|
|
orgId: 'test-org',
|
|
iat: 100,
|
|
exp: 900
|
|
};
|
|
|
|
// Mock current time as 600 (which is 62.5% through the token lifetime)
|
|
dateNowSpy.mockReturnValue(600 * 1000);
|
|
|
|
const result = jwtService.shouldRefreshToken(payload);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('should return false when payload does not have required timestamp fields', () => {
|
|
const payload: TokenPayload = {
|
|
userId: 'test-user',
|
|
authLevel: 0 as 0 | 1,
|
|
userStatus: UserState.VERIFIED_REGULAR,
|
|
orgId: 'test-org'
|
|
};
|
|
|
|
const result = jwtService.shouldRefreshToken(payload);
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('refreshIfNeeded', () => {
|
|
it('should return new token when refresh is needed', () => {
|
|
// Setup a payload that needs refresh (75% through lifetime)
|
|
const payload: TokenPayload = {
|
|
userId: 'test-user',
|
|
authLevel: 0 as 0 | 1,
|
|
userStatus: UserState.VERIFIED_REGULAR,
|
|
orgId: 'test-org',
|
|
iat: 100,
|
|
exp: 900
|
|
};
|
|
|
|
// Mock current time as 700 (75% through the token lifetime)
|
|
dateNowSpy.mockReturnValue(700 * 1000);
|
|
|
|
const result = jwtService.refreshIfNeeded(payload, mockResponse as Response);
|
|
|
|
expect(result).toBe(true);
|
|
expect(mockResponse.cookie).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return false when refresh is not needed', () => {
|
|
// Setup a payload that doesn't need refresh (less than 75% through lifetime)
|
|
const payload: TokenPayload = {
|
|
userId: 'test-user',
|
|
authLevel: 0 as 0 | 1,
|
|
userStatus: UserState.VERIFIED_REGULAR,
|
|
orgId: 'test-org',
|
|
iat: 100,
|
|
exp: 900
|
|
};
|
|
|
|
// Mock current time as 600 (62.5% through the token lifetime)
|
|
dateNowSpy.mockReturnValue(600 * 1000);
|
|
|
|
const result = jwtService.refreshIfNeeded(payload, mockResponse as Response);
|
|
|
|
expect(result).toBe(false);
|
|
expect(mockResponse.cookie).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|