Skip to main content
Glama

ClinicalTrials.gov MCP Server

trace.test.ts•15.1 kB
/** * @fileoverview Test suite for OpenTelemetry tracing * @module tests/utils/telemetry/trace.test */ import { describe, test, expect, beforeEach, afterEach, vi, type MockInstance, } from 'vitest'; import { context as otContext, propagation, trace, SpanStatusCode, type Span, type SpanContext, } from '@opentelemetry/api'; import * as traceUtils from '@/utils/telemetry/trace.js'; import type { RequestContext } from '@/utils/internal/requestContext.js'; describe('OpenTelemetry Tracing', () => { describe('buildTraceparent', () => { let getActiveSpanSpy: MockInstance; beforeEach(() => { getActiveSpanSpy = vi.spyOn(trace, 'getActiveSpan'); }); afterEach(() => { vi.restoreAllMocks(); }); test('should build traceparent from RequestContext', () => { const ctx: RequestContext = { requestId: 'test-req', timestamp: Date.now() as any, operation: 'test', traceId: 'a'.repeat(32), spanId: 'b'.repeat(16), }; const result = traceUtils.buildTraceparent(ctx); expect(result).toBe(`00-${'a'.repeat(32)}-${'b'.repeat(16)}-01`); }); test('should build traceparent from active span when context missing', () => { const mockSpanContext: SpanContext = { traceId: '1'.repeat(32), spanId: '2'.repeat(16), traceFlags: 1, }; const mockSpan = { spanContext: () => mockSpanContext, } as Span; getActiveSpanSpy.mockReturnValue(mockSpan); const result = traceUtils.buildTraceparent(); expect(result).toBe(`00-${'1'.repeat(32)}-${'2'.repeat(16)}-01`); }); test('should return undefined when no context or active span', () => { getActiveSpanSpy.mockReturnValue(undefined as unknown as Span); const result = traceUtils.buildTraceparent(); expect(result).toBeUndefined(); }); test('should return undefined when context has incomplete trace data', () => { const ctx: RequestContext = { requestId: 'test-req', timestamp: Date.now() as any, operation: 'test', traceId: 'a'.repeat(32), // Missing spanId }; getActiveSpanSpy.mockReturnValue(undefined as unknown as Span); const result = traceUtils.buildTraceparent(ctx); expect(result).toBeUndefined(); }); test('should prefer context traceId/spanId over active span', () => { const ctx: RequestContext = { requestId: 'test-req', timestamp: Date.now() as any, operation: 'test', traceId: 'c'.repeat(32), spanId: 'd'.repeat(16), }; const mockSpanContext: SpanContext = { traceId: '1'.repeat(32), spanId: '2'.repeat(16), traceFlags: 1, }; const mockSpan = { spanContext: () => mockSpanContext, } as Span; getActiveSpanSpy.mockReturnValue(mockSpan); const result = traceUtils.buildTraceparent(ctx); // Should use context values, not span values expect(result).toBe(`00-${'c'.repeat(32)}-${'d'.repeat(16)}-01`); }); test('should always use sampled flag "01"', () => { const ctx: RequestContext = { requestId: 'test-req', timestamp: Date.now() as any, operation: 'test', traceId: 'e'.repeat(32), spanId: 'f'.repeat(16), }; const result = traceUtils.buildTraceparent(ctx); expect(result).toMatch(/-01$/); }); }); describe('extractTraceparent', () => { test('should extract valid traceparent from Web Headers', () => { const headers = new Headers(); const traceId = 'a'.repeat(32); const spanId = 'b'.repeat(16); headers.set('traceparent', `00-${traceId}-${spanId}-01`); const result = traceUtils.extractTraceparent(headers); expect(result).toEqual({ traceId, spanId, sampled: true, }); }); test('should extract valid traceparent from plain object', () => { const traceId = 'c'.repeat(32); const spanId = 'd'.repeat(16); const headers = { traceparent: `00-${traceId}-${spanId}-00`, }; const result = traceUtils.extractTraceparent(headers); expect(result).toEqual({ traceId, spanId, sampled: false, }); }); test('should return undefined when traceparent header missing', () => { const headers = new Headers(); const result = traceUtils.extractTraceparent(headers); expect(result).toBeUndefined(); }); test('should return undefined for malformed traceparent', () => { const headers = { traceparent: 'invalid-format' }; const result = traceUtils.extractTraceparent(headers); expect(result).toBeUndefined(); }); test('should return undefined for wrong version', () => { const headers = { traceparent: `01-${'a'.repeat(32)}-${'b'.repeat(16)}-01`, }; const result = traceUtils.extractTraceparent(headers); expect(result).toBeUndefined(); }); test('should return undefined for short traceId', () => { const headers = { traceparent: `00-${'a'.repeat(31)}-${'b'.repeat(16)}-01`, }; const result = traceUtils.extractTraceparent(headers); expect(result).toBeUndefined(); }); test('should return undefined for short spanId', () => { const headers = { traceparent: `00-${'a'.repeat(32)}-${'b'.repeat(15)}-01`, }; const result = traceUtils.extractTraceparent(headers); expect(result).toBeUndefined(); }); test('should parse sampled flag correctly', () => { const traceId = 'e'.repeat(32); const spanId = 'f'.repeat(16); const sampledHeaders = { traceparent: `00-${traceId}-${spanId}-01`, }; const unsampledHeaders = { traceparent: `00-${traceId}-${spanId}-00`, }; expect(traceUtils.extractTraceparent(sampledHeaders)?.sampled).toBe(true); expect(traceUtils.extractTraceparent(unsampledHeaders)?.sampled).toBe( false, ); }); }); describe('createContextWithParentTrace', () => { test('should create context with parent trace from headers', () => { const traceId = 'a'.repeat(32); const spanId = 'b'.repeat(16); const headers = { traceparent: `00-${traceId}-${spanId}-01`, }; const result = traceUtils.createContextWithParentTrace( headers, 'test-operation', ); expect(result.operation).toBe('test-operation'); expect(result.traceId).toBe(traceId); expect(result.parentSpanId).toBe(spanId); }); test('should work with Web Headers', () => { const traceId = 'c'.repeat(32); const spanId = 'd'.repeat(16); const headers = new Headers(); headers.set('traceparent', `00-${traceId}-${spanId}-01`); const result = traceUtils.createContextWithParentTrace( headers, 'web-request', ); expect(result.operation).toBe('web-request'); expect(result.traceId).toBe(traceId); expect(result.parentSpanId).toBe(spanId); }); }); describe('injectCurrentContextInto', () => { let injectSpy: MockInstance; beforeEach(() => { injectSpy = vi.spyOn(propagation, 'inject'); }); afterEach(() => { vi.restoreAllMocks(); }); test('should inject current context into carrier', () => { const carrier = { 'Content-Type': 'application/json' }; const result = traceUtils.injectCurrentContextInto(carrier); expect(injectSpy).toHaveBeenCalledWith(otContext.active(), carrier); expect(result).toBe(carrier); }); test('should return same carrier object', () => { const carrier = { key: 'value' }; const result = traceUtils.injectCurrentContextInto(carrier); expect(result).toBe(carrier); }); test('should work with empty carrier', () => { const carrier = {}; traceUtils.injectCurrentContextInto(carrier); expect(injectSpy).toHaveBeenCalled(); }); }); describe('withSpan', () => { let mockTracer: any; // getTracerSpy kept for potential future verification // let getTracerSpy: MockInstance; let mockSpan: any; beforeEach(() => { mockSpan = { setAttributes: vi.fn(), setStatus: vi.fn(), recordException: vi.fn(), end: vi.fn(), spanContext: () => ({ traceId: 'a'.repeat(32), spanId: 'b'.repeat(16), traceFlags: 1, }), }; mockTracer = { startActiveSpan: vi.fn((_name, fn) => { return fn(mockSpan); }), }; vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer); }); afterEach(() => { vi.restoreAllMocks(); }); test('should create span and execute function successfully', async () => { const testFn = vi.fn(async () => 'result'); const result = await traceUtils.withSpan('test-operation', testFn); expect(result).toBe('result'); expect(mockTracer.startActiveSpan).toHaveBeenCalledWith( 'test-operation', expect.any(Function), ); expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK, }); expect(mockSpan.end).toHaveBeenCalled(); }); test('should set attributes on span', async () => { const attributes = { 'db.table': 'users', 'db.operation': 'select', count: 42, enabled: true, }; await traceUtils.withSpan('db-query', async () => {}, attributes); expect(mockSpan.setAttributes).toHaveBeenCalledWith(attributes); }); test('should handle errors and mark span as error', async () => { const error = new Error('Test error'); const testFn = async () => { throw error; }; await expect(traceUtils.withSpan('failing-op', testFn)).rejects.toThrow( 'Test error', ); expect(mockSpan.recordException).toHaveBeenCalledWith(error); expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.ERROR, message: 'Test error', }); expect(mockSpan.end).toHaveBeenCalled(); }); test('should handle non-Error exceptions', async () => { const testFn = async () => { throw 'string error'; }; await expect(traceUtils.withSpan('failing-op', testFn)).rejects.toThrow(); expect(mockSpan.recordException).toHaveBeenCalledWith( new Error('string error'), ); expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.ERROR, message: 'string error', }); }); test('should end span even on error', async () => { const testFn = async () => { throw new Error('fail'); }; await expect(traceUtils.withSpan('error-op', testFn)).rejects.toThrow(); expect(mockSpan.end).toHaveBeenCalled(); }); test('should pass span to function', async () => { const testFn = vi.fn(async (span: Span) => { expect(span).toBe(mockSpan); }); await traceUtils.withSpan('test-op', testFn); expect(testFn).toHaveBeenCalledWith(mockSpan); }); }); describe('runInContext', () => { test('should run function directly when no context provided', () => { const fn = vi.fn(() => 'result'); const result = traceUtils.runInContext(undefined, fn); expect(result).toBe('result'); expect(fn).toHaveBeenCalled(); }); test('should run function directly when context missing traceId', () => { const ctx: RequestContext = { requestId: 'test', timestamp: Date.now() as any, operation: 'test', // No traceId }; const fn = vi.fn(() => 'result'); const result = traceUtils.runInContext(ctx, fn); expect(result).toBe('result'); expect(fn).toHaveBeenCalled(); }); test('should run function directly when context missing spanId', () => { const ctx: RequestContext = { requestId: 'test', timestamp: Date.now() as any, operation: 'test', traceId: 'a'.repeat(32), // No spanId }; const fn = vi.fn(() => 'result'); const result = traceUtils.runInContext(ctx, fn); expect(result).toBe('result'); expect(fn).toHaveBeenCalled(); }); test('should execute function with context when trace data present', () => { const ctx: RequestContext = { requestId: 'test', timestamp: Date.now() as any, operation: 'test', traceId: 'a'.repeat(32), spanId: 'b'.repeat(16), }; const fn = vi.fn(() => 'result'); const result = traceUtils.runInContext(ctx, fn); expect(result).toBe('result'); expect(fn).toHaveBeenCalled(); }); test('should preserve function return value', () => { const ctx: RequestContext = { requestId: 'test', timestamp: Date.now() as any, operation: 'test', traceId: 'a'.repeat(32), spanId: 'b'.repeat(16), }; const result = traceUtils.runInContext(ctx, () => 42); expect(result).toBe(42); }); test('should propagate exceptions', () => { const ctx: RequestContext = { requestId: 'test', timestamp: Date.now() as any, operation: 'test', traceId: 'a'.repeat(32), spanId: 'b'.repeat(16), }; expect(() => traceUtils.runInContext(ctx, () => { throw new Error('test error'); }), ).toThrow('test error'); }); }); describe('Integration tests', () => { test('should build and extract traceparent in round-trip', () => { const originalTraceId = 'a'.repeat(32); const originalSpanId = 'b'.repeat(16); const ctx: RequestContext = { requestId: 'test', timestamp: Date.now() as any, operation: 'test', traceId: originalTraceId, spanId: originalSpanId, }; const traceparent = traceUtils.buildTraceparent(ctx); expect(traceparent).toBeDefined(); const headers = { traceparent: traceparent! }; const extracted = traceUtils.extractTraceparent(headers); expect(extracted?.traceId).toBe(originalTraceId); expect(extracted?.spanId).toBe(originalSpanId); expect(extracted?.sampled).toBe(true); }); test('should create child context from parent traceparent', () => { const parentTraceId = 'c'.repeat(32); const parentSpanId = 'd'.repeat(16); const headers = { traceparent: `00-${parentTraceId}-${parentSpanId}-01`, }; const childContext = traceUtils.createContextWithParentTrace( headers, 'child-operation', ); expect(childContext.traceId).toBe(parentTraceId); expect(childContext.parentSpanId).toBe(parentSpanId); expect(childContext.operation).toBe('child-operation'); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/clinicaltrialsgov-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server