import { Test, TestingModule } from '@nestjs/testing'; import { ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { ServiceAuthGuard } from '../../../src/shared/guards/service-auth.guard'; import { generateServiceToken, generateExpiredServiceToken, TEST_SERVICE_JWT_SECRET, } from '../../utils/test-utils'; describe('ServiceAuthGuard', () => { let guard: ServiceAuthGuard; const createMockContext = (headers: Record = {}, connection: any = {}): ExecutionContext => { const mockRequest = { headers, connection, socket: connection, }; return { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ ServiceAuthGuard, { provide: ConfigService, useValue: { get: (key: string) => { switch (key) { case 'SERVICE_JWT_SECRET': return TEST_SERVICE_JWT_SECRET; case 'ALLOWED_SERVICES': return 'identity-service,recovery-service'; default: return undefined; } }, }, }, ], }).compile(); guard = module.get(ServiceAuthGuard); }); describe('canActivate', () => { it('should allow request with valid identity-service token', () => { const token = generateServiceToken('identity-service'); const context = createMockContext({ 'x-service-token': token }); expect(guard.canActivate(context)).toBe(true); }); it('should allow request with valid recovery-service token', () => { const token = generateServiceToken('recovery-service'); const context = createMockContext({ 'x-service-token': token }); expect(guard.canActivate(context)).toBe(true); }); it('should reject request without service token', () => { const context = createMockContext({}); expect(() => guard.canActivate(context)).toThrow(UnauthorizedException); expect(() => guard.canActivate(context)).toThrow('Missing service token'); }); it('should reject request with invalid token', () => { const context = createMockContext({ 'x-service-token': 'invalid-token' }); expect(() => guard.canActivate(context)).toThrow(UnauthorizedException); expect(() => guard.canActivate(context)).toThrow('Invalid service token'); }); it('should reject request with expired token', () => { const token = generateExpiredServiceToken('identity-service'); const context = createMockContext({ 'x-service-token': token }); expect(() => guard.canActivate(context)).toThrow(UnauthorizedException); }); it('should reject request from unauthorized service', () => { const token = generateServiceToken('unknown-service'); const context = createMockContext({ 'x-service-token': token }); expect(() => guard.canActivate(context)).toThrow(UnauthorizedException); expect(() => guard.canActivate(context)).toThrow('Service not authorized'); }); it('should reject token signed with wrong secret', () => { const token = generateServiceToken('identity-service', 'wrong-secret'); const context = createMockContext({ 'x-service-token': token }); expect(() => guard.canActivate(context)).toThrow(UnauthorizedException); }); it('should attach sourceService to request', () => { const token = generateServiceToken('identity-service'); const mockRequest: any = { headers: { 'x-service-token': token }, connection: {}, socket: {}, }; const context = { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; guard.canActivate(context); expect(mockRequest.sourceService).toBe('identity-service'); }); it('should extract client IP from x-forwarded-for header', () => { const token = generateServiceToken('identity-service'); const mockRequest: any = { headers: { 'x-service-token': token, 'x-forwarded-for': '203.0.113.195, 70.41.3.18, 150.172.238.178', }, connection: {}, socket: {}, }; const context = { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; guard.canActivate(context); expect(mockRequest.sourceIp).toBe('203.0.113.195'); }); it('should extract client IP from x-real-ip header', () => { const token = generateServiceToken('identity-service'); const mockRequest: any = { headers: { 'x-service-token': token, 'x-real-ip': '192.168.1.100', }, connection: {}, socket: {}, }; const context = { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; guard.canActivate(context); expect(mockRequest.sourceIp).toBe('192.168.1.100'); }); it('should extract client IP from connection.remoteAddress', () => { const token = generateServiceToken('identity-service'); const mockRequest: any = { headers: { 'x-service-token': token }, connection: { remoteAddress: '10.0.0.1' }, socket: {}, }; const context = { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; guard.canActivate(context); expect(mockRequest.sourceIp).toBe('10.0.0.1'); }); it('should return "unknown" when no IP can be determined', () => { const token = generateServiceToken('identity-service'); const mockRequest: any = { headers: { 'x-service-token': token }, connection: {}, socket: {}, }; const context = { switchToHttp: () => ({ getRequest: () => mockRequest, }), } as ExecutionContext; guard.canActivate(context); expect(mockRequest.sourceIp).toBe('unknown'); }); }); });