rwadurian/backend/services/backup-service/test/unit/shared/service-auth.guard.spec.ts

201 lines
6.3 KiB
TypeScript

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<string, string> = {}, 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>(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');
});
});
});