import { DyNTS_OAuth2_AuthService } from './oauth2.auth-service'; import { DyNTS_OAuth2_ControlService } from './oauth2.control-service'; import { Request, Response } from 'express'; import { DyFM_Error } from '@futdevpro/fsm-dynamo'; import { DyNTS_global_settings } from '../../../_collections/global-settings.const'; describe('| DyNTS_OAuth2_AuthService', () => { let service: DyNTS_OAuth2_AuthService; let mockControlService: jasmine.SpyObj; let mockRequest: Partial; let mockResponse: Partial; beforeEach(() => { // Reset singleton instances to prevent state leakage between tests (DyNTS_OAuth2_ControlService as any).instance = undefined; (DyNTS_OAuth2_AuthService as any).instance = undefined; // Mock the ControlService.getInstance() to prevent circular dependency mockControlService = jasmine.createSpyObj('DyNTS_OAuth2_ControlService', [ 'getAccessTokenData', 'getRefreshTokenData', 'getClient', 'getUser', 'validateClientCredentials', 'validateUserCredentials', ]); spyOn(DyNTS_OAuth2_ControlService, 'getInstance').and.returnValue(mockControlService); // Now we can safely get the AuthService instance service = DyNTS_OAuth2_AuthService.getInstance(); // Replace the controlService with our mock (service as any).controlService = mockControlService; mockRequest = { headers: {}, }; mockResponse = { setHeader: jasmine.createSpy('setHeader'), }; }); it('| should be a singleton instance', () => { const instance1 = DyNTS_OAuth2_AuthService.getInstance(); const instance2 = DyNTS_OAuth2_AuthService.getInstance(); expect(instance1).toBe(instance2); expect(instance1).toBeInstanceOf(DyNTS_OAuth2_AuthService); }); describe('| authenticate_token', () => { it('| should authenticate valid token', async () => { mockRequest.headers = { authorization: 'Bearer valid-token-123', }; spyOn(service, 'getTokenFromRequest').and.returnValue('Bearer valid-token-123'); mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'read', expiresAt: Date.now() + 3600000, // 1 hour from now }); await service.authenticate_token(mockRequest as Request, mockResponse as Response); expect(mockControlService.getAccessTokenData).toHaveBeenCalledWith('valid-token-123'); expect(mockResponse.setHeader).toHaveBeenCalledWith('authorization', 'Bearer valid-token-123'); }); it('| should throw error when token format is invalid', async () => { mockRequest.headers = { authorization: 'InvalidFormat token', }; await expectAsync( service.authenticate_token(mockRequest as Request, mockResponse as Response) ).toBeRejected(); }); it('| should throw error when token is missing', async () => { mockRequest.headers = {}; await expectAsync( service.authenticate_token(mockRequest as Request, mockResponse as Response) ).toBeRejected(); }); it('| should throw error when token is expired', async () => { mockRequest.headers = { authorization: 'Bearer expired-token', }; mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'read', expiresAt: Date.now() - 1000, // Expired }); await expectAsync( service.authenticate_token(mockRequest as Request, mockResponse as Response) ).toBeRejected(); }); it('| should throw error when token data is not found', async () => { mockRequest.headers = { authorization: 'Bearer invalid-token', }; mockControlService.getAccessTokenData.and.returnValue(null); await expectAsync( service.authenticate_token(mockRequest as Request, mockResponse as Response) ).toBeRejected(); }); }); describe('| authenticate_tokenSelf', () => { it('| should authenticate valid token for self', async () => { mockRequest.headers = { authorization: 'Bearer valid-token-123', }; mockRequest.params = { userId: 'client-123', }; spyOn(service, 'getTokenFromRequest').and.returnValue('Bearer valid-token-123'); spyOn(service, 'getIssuerFromRequest').and.returnValue('client-123'); mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'read', expiresAt: Date.now() + 3600000, } as any); await service.authenticate_tokenSelf(mockRequest as Request, mockResponse as Response); expect(mockControlService.getAccessTokenData).toHaveBeenCalledWith('valid-token-123'); expect(mockResponse.setHeader).toHaveBeenCalledWith('authorization', 'Bearer valid-token-123'); }); it('| should throw error when userId does not match', async () => { mockRequest.headers = { authorization: 'Bearer valid-token-123', }; mockRequest.params = { userId: 'user-456', }; mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'read', expiresAt: Date.now() + 3600000, } as any); await expectAsync( service.authenticate_tokenSelf(mockRequest as Request, mockResponse as Response) ).toBeRejected(); }); }); describe('| authenticate_tokenPerm_accUsageData', () => { it('| should authenticate token with usage data permission', async () => { mockRequest.headers = { authorization: 'Bearer valid-token-123', }; spyOn(service, 'getTokenFromRequest').and.returnValue('Bearer valid-token-123'); mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'usage_data read', expiresAt: Date.now() + 3600000, } as any); await service.authenticate_tokenPerm_accUsageData(mockRequest as Request, mockResponse as Response); expect(mockResponse.setHeader).toHaveBeenCalledWith('authorization', 'Bearer valid-token-123'); }); it('| should throw error when permission is missing', async () => { mockRequest.headers = { authorization: 'Bearer valid-token-123', }; mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'read', expiresAt: Date.now() + 3600000, } as any); await expectAsync( service.authenticate_tokenPerm_accUsageData(mockRequest as Request, mockResponse as Response) ).toBeRejected(); }); }); describe('| getIssuerFromRequest', () => { it('| should return issuer from valid token', () => { mockRequest.headers = { authorization: 'Bearer valid-token-123', }; spyOn(service, 'getTokenFromRequest').and.returnValue('Bearer valid-token-123'); mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'read', expiresAt: Date.now() + 3600000, } as any); const result = service.getIssuerFromRequest(mockRequest as Request); expect(result).toBe('client-123'); }); it('| should return undefined when token is missing', () => { mockRequest.headers = {}; const result = service.getIssuerFromRequest(mockRequest as Request); expect(result).toBeUndefined(); }); it('| should return undefined when token data is not found', () => { mockRequest.headers = { authorization: 'Bearer invalid-token', }; mockControlService.getAccessTokenData.and.returnValue(null); const result = service.getIssuerFromRequest(mockRequest as Request); expect(result).toBeUndefined(); }); }); describe('| getUsernameFromRequest', () => { it('| should return username from valid token', () => { mockRequest.headers = { authorization: 'Bearer valid-token-123', }; spyOn(service, 'getTokenFromRequest').and.returnValue('Bearer valid-token-123'); mockControlService.getAccessTokenData.and.returnValue({ clientId: 'client-123', scope: 'read', expiresAt: Date.now() + 3600000, } as any); const result = service.getUsernameFromRequest(mockRequest as Request); // The implementation returns clientId as username (see TODO comment in code) expect(result).toBe('client-123'); }); it('| should return undefined when token is missing', () => { mockRequest.headers = {}; const result = service.getUsernameFromRequest(mockRequest as Request); expect(result).toBeUndefined(); }); }); });