Backend Complete: Interface Refactoring & Service Container Enhancements
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
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user