| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- import { ConfigSource } from '@growi/core/dist/interfaces';
- import { NodeSDK } from '@opentelemetry/sdk-node';
- import { configManager } from '~/server/service/config-manager';
- import { setupAdditionalResourceAttributes, initInstrumentation, startOpenTelemetry } from './node-sdk';
- import { getResource } from './node-sdk-resource';
- // Only mock configManager as it's external to what we're testing
- vi.mock('~/server/service/config-manager', () => ({
- configManager: {
- getConfig: vi.fn(),
- loadConfigs: vi.fn(),
- },
- }));
- // Mock custom metrics setup
- vi.mock('./custom-metrics', () => ({
- setupCustomMetrics: vi.fn(),
- }));
- // Mock growi-info service to avoid database dependencies
- vi.mock('~/server/service/growi-info', () => ({
- growiInfoService: {
- getGrowiInfo: vi.fn().mockResolvedValue({
- type: 'app',
- deploymentType: 'standalone',
- additionalInfo: {
- attachmentType: 'local',
- installedAt: new Date('2023-01-01T00:00:00.000Z'),
- installedAtByOldestUser: new Date('2023-01-01T00:00:00.000Z'),
- },
- }),
- },
- }));
- describe('node-sdk', () => {
- // Helper functions to reduce duplication
- const mockInstrumentationEnabled = () => {
- vi.mocked(configManager.getConfig).mockImplementation((key: string, source?: ConfigSource) => {
- if (key === 'otel:enabled') {
- return source === ConfigSource.env ? true : undefined;
- }
- return undefined;
- });
- };
- const mockInstrumentationDisabled = () => {
- vi.mocked(configManager.getConfig).mockImplementation((key: string, source?: ConfigSource) => {
- if (key === 'otel:enabled') {
- return source === ConfigSource.env ? false : undefined;
- }
- return undefined;
- });
- };
- beforeEach(async() => {
- vi.clearAllMocks();
- // Reset SDK instance using __testing__ export
- const { __testing__ } = await import('./node-sdk');
- __testing__.reset();
- // Mock loadConfigs to resolve immediately
- vi.mocked(configManager.loadConfigs).mockResolvedValue(undefined);
- });
- describe('initInstrumentation', () => {
- it('should call setupCustomMetrics when instrumentation is enabled', async() => {
- // Mock instrumentation as enabled
- mockInstrumentationEnabled();
- await initInstrumentation();
- });
- it('should not call setupCustomMetrics when instrumentation is disabled', async() => {
- const { setupCustomMetrics } = await import('./custom-metrics');
- // Mock instrumentation as disabled
- mockInstrumentationDisabled();
- await initInstrumentation();
- // Verify setupCustomMetrics was not called
- expect(setupCustomMetrics).not.toHaveBeenCalled();
- });
- it('should create SDK instance when instrumentation is enabled', async() => {
- // Mock instrumentation as enabled
- mockInstrumentationEnabled();
- await initInstrumentation();
- // Get instance for testing
- const { __testing__ } = await import('./node-sdk');
- const sdkInstance = __testing__.getSdkInstance();
- expect(sdkInstance).toBeDefined();
- expect(sdkInstance).toBeInstanceOf(NodeSDK);
- });
- it('should not create SDK instance when instrumentation is disabled', async() => {
- // Mock instrumentation as disabled
- mockInstrumentationDisabled();
- await initInstrumentation();
- // Verify that no SDK instance was created
- const { __testing__ } = await import('./node-sdk');
- const sdkInstance = __testing__.getSdkInstance();
- expect(sdkInstance).toBeUndefined();
- });
- });
- describe('setupAdditionalResourceAttributes', () => {
- it('should update service.instance.id when app:serviceInstanceId is available', async() => {
- // Set up mocks for this specific test
- vi.mocked(configManager.getConfig).mockImplementation((key: string, source?: ConfigSource) => {
- // For otel:enabled, always expect ConfigSource.env
- if (key === 'otel:enabled') {
- return source === ConfigSource.env ? true : undefined;
- }
- // For service instance IDs, only respond when no source is specified
- if (key === 'app:serviceInstanceId') return 'test-instance-id';
- return undefined;
- });
- // Initialize SDK first
- await initInstrumentation();
- // Get instance for testing
- const { __testing__ } = await import('./node-sdk');
- const sdkInstance = __testing__.getSdkInstance();
- expect(sdkInstance).toBeDefined();
- expect(sdkInstance).toBeInstanceOf(NodeSDK);
- // Verify initial state (service.instance.id should not be set)
- if (sdkInstance == null) {
- throw new Error('SDK instance should be defined');
- }
- const resource = getResource(sdkInstance);
- expect(resource).toBeDefined();
- expect(resource.attributes['service.instance.id']).toBeUndefined();
- // Call setupAdditionalResourceAttributes
- await setupAdditionalResourceAttributes();
- // Verify that resource was updated with app:serviceInstanceId
- const updatedResource = getResource(sdkInstance);
- expect(updatedResource.attributes['service.instance.id']).toBe('test-instance-id');
- });
- it('should update service.instance.id with otel:serviceInstanceId if available', async() => {
- // Set up mocks for this specific test
- vi.mocked(configManager.getConfig).mockImplementation((key: string, source?: ConfigSource) => {
- // For otel:enabled, always expect ConfigSource.env
- if (key === 'otel:enabled') {
- return source === ConfigSource.env ? true : undefined;
- }
- // For service instance IDs, only respond when no source is specified
- if (source === undefined) {
- if (key === 'otel:serviceInstanceId') return 'otel-instance-id';
- if (key === 'app:serviceInstanceId') return 'test-instance-id';
- }
- return undefined;
- });
- // Initialize SDK
- await initInstrumentation();
- // Get instance and verify initial state
- const { __testing__ } = await import('./node-sdk');
- const sdkInstance = __testing__.getSdkInstance();
- if (sdkInstance == null) {
- throw new Error('SDK instance should be defined');
- }
- const resource = getResource(sdkInstance);
- expect(resource.attributes['service.instance.id']).toBeUndefined();
- // Call setupAdditionalResourceAttributes
- await setupAdditionalResourceAttributes();
- // Verify that otel:serviceInstanceId was used
- const updatedResource = getResource(sdkInstance);
- expect(updatedResource.attributes['service.instance.id']).toBe('otel-instance-id');
- });
- it('should handle gracefully when instrumentation is disabled', async() => {
- // Mock instrumentation as disabled
- mockInstrumentationDisabled();
- // Initialize SDK (should not create instance)
- await initInstrumentation();
- // Call setupAdditionalResourceAttributes should not throw error
- await expect(setupAdditionalResourceAttributes()).resolves.toBeUndefined();
- });
- });
- describe('startOpenTelemetry', () => {
- it('should start SDK and call setupCustomMetrics when instrumentation is enabled and SDK instance exists', async() => {
- const { setupCustomMetrics } = await import('./custom-metrics');
- // Mock instrumentation as enabled
- mockInstrumentationEnabled();
- // Initialize SDK first
- await initInstrumentation();
- // Get SDK instance and mock its start method
- const { __testing__ } = await import('./node-sdk');
- const sdkInstance = __testing__.getSdkInstance();
- expect(sdkInstance).toBeDefined();
- if (sdkInstance != null) {
- const startSpy = vi.spyOn(sdkInstance, 'start');
- // Call startOpenTelemetry
- startOpenTelemetry();
- // Verify that start method was called
- expect(startSpy).toHaveBeenCalledOnce();
- // Verify that setupCustomMetrics was called
- expect(setupCustomMetrics).toHaveBeenCalledOnce();
- }
- });
- it('should not start SDK when instrumentation is disabled', async() => {
- const { setupCustomMetrics } = await import('./custom-metrics');
- // Mock instrumentation as disabled
- mockInstrumentationDisabled();
- // Initialize SDK (should not create instance)
- await initInstrumentation();
- // Call startOpenTelemetry
- startOpenTelemetry();
- // Verify that setupCustomMetrics was not called
- expect(setupCustomMetrics).not.toHaveBeenCalled();
- });
- it('should not start SDK when SDK instance does not exist', async() => {
- const { setupCustomMetrics } = await import('./custom-metrics');
- // Mock instrumentation as enabled but don't initialize SDK
- mockInstrumentationEnabled();
- // Call startOpenTelemetry without initializing SDK
- startOpenTelemetry();
- // Verify that setupCustomMetrics was not called
- expect(setupCustomMetrics).not.toHaveBeenCalled();
- });
- });
- });
|