|
|
@@ -1,12 +1,10 @@
|
|
|
import { ConfigSource } from '@growi/core/dist/interfaces';
|
|
|
-import { Resource } from '@opentelemetry/resources';
|
|
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
|
|
|
|
import { configManager } from '~/server/service/config-manager';
|
|
|
|
|
|
-import { detectServiceInstanceId, initInstrumentation } from './node-sdk';
|
|
|
+import { setupAdditionalResourceAttributes, initInstrumentation } from './node-sdk';
|
|
|
import { getResource } from './node-sdk-resource';
|
|
|
-import { getSdkInstance, resetSdkInstance } from './node-sdk.testing';
|
|
|
|
|
|
// Only mock configManager as it's external to what we're testing
|
|
|
vi.mock('~/server/service/config-manager', () => ({
|
|
|
@@ -16,55 +14,141 @@ vi.mock('~/server/service/config-manager', () => ({
|
|
|
},
|
|
|
}));
|
|
|
|
|
|
-describe('node-sdk', () => {
|
|
|
- beforeEach(() => {
|
|
|
- vi.clearAllMocks();
|
|
|
- vi.resetModules();
|
|
|
- resetSdkInstance();
|
|
|
+// 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'),
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ },
|
|
|
+}));
|
|
|
|
|
|
- // Reset configManager mock implementation
|
|
|
+describe('node-sdk', () => {
|
|
|
+ // Helper functions to reduce duplication
|
|
|
+ const mockInstrumentationEnabled = () => {
|
|
|
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;
|
|
|
}
|
|
|
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('detectServiceInstanceId', () => {
|
|
|
- it('should update service.instance.id when app:serviceInstanceId is available', async() => {
|
|
|
- // Initialize SDK first
|
|
|
+ describe('initInstrumentation', () => {
|
|
|
+ it('should call setupCustomMetrics when instrumentation is enabled', async() => {
|
|
|
+ const { setupCustomMetrics } = await import('./custom-metrics');
|
|
|
+
|
|
|
+ // Mock instrumentation as enabled
|
|
|
+ mockInstrumentationEnabled();
|
|
|
+
|
|
|
+ await initInstrumentation();
|
|
|
+
|
|
|
+ // Verify setupCustomMetrics was called
|
|
|
+ expect(setupCustomMetrics).toHaveBeenCalledOnce();
|
|
|
+ });
|
|
|
+
|
|
|
+ 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 sdkInstance = getSdkInstance();
|
|
|
+ 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');
|
|
|
- }
|
|
|
+ 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();
|
|
|
+ });
|
|
|
+ });
|
|
|
|
|
|
- // Mock app:serviceInstanceId is available
|
|
|
+ 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).toBeInstanceOf(Resource);
|
|
|
+ expect(resource).toBeDefined();
|
|
|
expect(resource.attributes['service.instance.id']).toBeUndefined();
|
|
|
|
|
|
- // Call detectServiceInstanceId
|
|
|
- await detectServiceInstanceId();
|
|
|
+ // Call setupAdditionalResourceAttributes
|
|
|
+ await setupAdditionalResourceAttributes();
|
|
|
|
|
|
// Verify that resource was updated with app:serviceInstanceId
|
|
|
const updatedResource = getResource(sdkInstance);
|
|
|
@@ -72,18 +156,7 @@ describe('node-sdk', () => {
|
|
|
});
|
|
|
|
|
|
it('should update service.instance.id with otel:serviceInstanceId if available', async() => {
|
|
|
- // Initialize SDK
|
|
|
- await initInstrumentation();
|
|
|
-
|
|
|
- // Get instance and verify initial state
|
|
|
- const sdkInstance = getSdkInstance();
|
|
|
- if (sdkInstance == null) {
|
|
|
- throw new Error('SDK instance should be defined');
|
|
|
- }
|
|
|
- const resource = getResource(sdkInstance);
|
|
|
- expect(resource.attributes['service.instance.id']).toBeUndefined();
|
|
|
-
|
|
|
- // Mock otel:serviceInstanceId is available
|
|
|
+ // 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') {
|
|
|
@@ -99,37 +172,35 @@ describe('node-sdk', () => {
|
|
|
return undefined;
|
|
|
});
|
|
|
|
|
|
- // Call detectServiceInstanceId
|
|
|
- await detectServiceInstanceId();
|
|
|
+ // 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 not create SDK instance if instrumentation is disabled', async() => {
|
|
|
+ it('should handle gracefully when instrumentation is disabled', async() => {
|
|
|
// Mock instrumentation as disabled
|
|
|
- vi.mocked(configManager.getConfig).mockImplementation((key: string, source?: ConfigSource) => {
|
|
|
- // For otel:enabled, always expect ConfigSource.env and return false
|
|
|
- if (key === 'otel:enabled') {
|
|
|
- return source === ConfigSource.env ? false : undefined;
|
|
|
- }
|
|
|
- return undefined;
|
|
|
- });
|
|
|
+ mockInstrumentationDisabled();
|
|
|
|
|
|
- // Initialize SDK
|
|
|
+ // Initialize SDK (should not create instance)
|
|
|
await initInstrumentation();
|
|
|
|
|
|
- // Verify that no SDK instance was created
|
|
|
- const sdkInstance = getSdkInstance();
|
|
|
- expect(sdkInstance).toBeUndefined();
|
|
|
-
|
|
|
- // Call detectServiceInstanceId
|
|
|
- await detectServiceInstanceId();
|
|
|
-
|
|
|
- // Verify that still no SDK instance exists
|
|
|
- const updatedSdkInstance = getSdkInstance();
|
|
|
- expect(updatedSdkInstance).toBeUndefined();
|
|
|
+ // Call setupAdditionalResourceAttributes should not throw error
|
|
|
+ await expect(setupAdditionalResourceAttributes()).resolves.toBeUndefined();
|
|
|
});
|
|
|
});
|
|
|
});
|