UNPKG

autotel

Version:
1,105 lines (915 loc) 33.5 kB
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { Event, getEvents, resetEvents } from './event'; import { type Logger } from './logger'; import { init } from './init'; import { shutdown } from './shutdown'; import { trace } from './functional'; import { context, propagation } from '@opentelemetry/api'; describe('Events', () => { let mockLogger: Logger; beforeEach(() => { resetEvents(); mockLogger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }; }); describe('trackEvent', () => { it('should track business events', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackEvent('application.submitted', { jobId: '123', userId: '456', }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( { event: 'application.submitted', attributes: { service: 'test-service', jobId: '123', userId: '456' }, }, 'Event tracked', ); }); it('should track events without attributes', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackEvent('user.login'); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( { event: 'user.login', attributes: { service: 'test-service' }, }, 'Event tracked', ); }); }); describe('trackFunnelStep', () => { it('should track funnel progression', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 }); event.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 }); expect(mockLogger.info).toHaveBeenCalledTimes(2); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( { funnel: 'checkout', status: 'started', attributes: { service: 'test-service', cartValue: 99.99 }, }, 'Funnel step tracked', ); }); it('should track funnel abandonment', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackFunnelStep('checkout', 'abandoned', { reason: 'timeout' }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( { funnel: 'checkout', status: 'abandoned', attributes: { service: 'test-service', reason: 'timeout' }, }, 'Funnel step tracked', ); }); }); describe('trackOutcome', () => { it('should track successful outcomes', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackOutcome('email.delivery', 'success', { recipientType: 'school', }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( { operation: 'email.delivery', status: 'success', attributes: { service: 'test-service', recipientType: 'school' }, }, 'Outcome tracked', ); }); it('should track failed outcomes', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackOutcome('email.delivery', 'failure', { error: 'invalid_email', }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( { operation: 'email.delivery', status: 'failure', attributes: { service: 'test-service', error: 'invalid_email' }, }, 'Outcome tracked', ); }); it('should track partial outcomes', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackOutcome('batch.process', 'partial', { successCount: 8, failureCount: 2, }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( { operation: 'batch.process', status: 'partial', attributes: { service: 'test-service', successCount: 8, failureCount: 2, }, }, 'Outcome tracked', ); }); }); describe('trackValue', () => { it('should track revenue metrics', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackValue('order.revenue', 149.99, { currency: 'USD', productCategory: 'electronics', }); // Pino-native: (extra, message) expect(mockLogger.debug).toHaveBeenCalledWith( { metric: 'order.revenue', value: 149.99, attributes: { service: 'test-service', metric: 'order.revenue', currency: 'USD', productCategory: 'electronics', }, }, 'Value tracked', ); }); it('should track processing time', () => { const event = new Event('test-service', { logger: mockLogger }); event.trackValue('application.processing_time', 2500, { unit: 'ms', }); // Pino-native: (extra, message) expect(mockLogger.debug).toHaveBeenCalledWith( { metric: 'application.processing_time', value: 2500, attributes: { service: 'test-service', metric: 'application.processing_time', unit: 'ms', }, }, 'Value tracked', ); }); }); describe('getEvents', () => { it('should return singleton instance', () => { const events1 = getEvents('test-service'); const events2 = getEvents('test-service'); expect(events1).toBe(events2); }); it('should return different instances for different services', () => { const events1 = getEvents('service-1'); const events2 = getEvents('service-2'); expect(events1).not.toBe(events2); }); it('should reset instances', () => { const events1 = getEvents('test-service'); resetEvents(); const events2 = getEvents('test-service'); expect(events1).not.toBe(events2); }); }); describe('real-world usage example', () => { it('should track job application flow', () => { const event = new Event('job-application', { logger: mockLogger, }); // User starts application event.trackFunnelStep('application', 'started', { jobId: '123' }); // User submits application event.trackEvent('application.submitted', { jobId: '123', userId: '456', }); event.trackFunnelStep('application', 'completed', { jobId: '123' }); // Email sent successfully event.trackOutcome('email.sent', 'success', { recipientType: 'school', jobId: '123', }); expect(mockLogger.info).toHaveBeenCalledTimes(4); }); it('should track email delivery failures', () => { const event = new Event('email-service', { logger: mockLogger }); // Failed email delivery event.trackOutcome('email.delivery', 'failure', { error: 'invalid_email', recipientEmail: 'redacted', }); // Track event for alerting event.trackEvent('email.bounce', { bounceType: 'permanent', }); expect(mockLogger.info).toHaveBeenCalledTimes(2); }); }); describe('automatic telemetry context enrichment', () => { beforeEach(() => { resetEvents(); }); afterEach(async () => { await shutdown(); }); // Test without config first (before any init() is called) it('should still work without config (graceful degradation)', () => { // Don't initialize - no config available const event = new Event('test-service', { logger: mockLogger }); event.trackEvent('user.login'); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: { service: 'test-service', // No version/environment - gracefully omitted }, }), 'Event tracked', ); }); it('should auto-capture resource attributes (service.version, deployment.environment)', () => { // Initialize with config init({ service: 'test-service', version: '2.1.0', environment: 'production', }); const event = new Event('test-service', { logger: mockLogger }); event.trackEvent('user.signup', { userId: '123' }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ service: 'test-service', 'service.version': '2.1.0', 'deployment.environment': 'production', userId: '123', }), }), 'Event tracked', ); }); it('should auto-capture trace context (traceId, spanId, correlationId) when inside a trace', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const tracedOperation = trace('test.operation', async () => { event.trackEvent('operation.started', { step: 1 }); }); await tracedOperation(); // Pino-native: first arg is the extra object const capturedCall = (mockLogger.info as ReturnType<typeof vi.fn>).mock .calls[0]; const attributes = capturedCall[0].attributes; expect(attributes).toHaveProperty('traceId'); expect(attributes).toHaveProperty('spanId'); expect(attributes).toHaveProperty('correlationId'); expect(typeof attributes.traceId).toBe('string'); expect(typeof attributes.spanId).toBe('string'); expect(typeof attributes.correlationId).toBe('string'); // Correlation ID should be first 16 chars of traceId expect(attributes.correlationId).toBe(attributes.traceId.slice(0, 16)); }); it('should enrich trackFunnelStep with telemetry context', async () => { init({ service: 'test-service', version: '1.5.0', environment: 'staging', }); const event = new Event('test-service', { logger: mockLogger }); const tracedOperation = trace('checkout.flow', async () => { event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 }); }); await tracedOperation(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ service: 'test-service', 'service.version': '1.5.0', 'deployment.environment': 'staging', cartValue: 99.99, traceId: expect.any(String), spanId: expect.any(String), correlationId: expect.any(String), }), }), 'Funnel step tracked', ); }); it('should enrich trackOutcome with telemetry context', async () => { init({ service: 'test-service', version: '3.0.0', environment: 'development', }); const event = new Event('test-service', { logger: mockLogger }); const tracedOperation = trace('email.send', async () => { event.trackOutcome('email.delivery', 'success', { recipientType: 'user', }); }); await tracedOperation(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ service: 'test-service', 'service.version': '3.0.0', 'deployment.environment': 'development', recipientType: 'user', traceId: expect.any(String), spanId: expect.any(String), correlationId: expect.any(String), }), }), 'Outcome tracked', ); }); it('should enrich trackValue with telemetry context', async () => { init({ service: 'test-service', version: '4.2.1', environment: 'production', }); const event = new Event('test-service', { logger: mockLogger }); const tracedOperation = trace('order.process', async () => { event.trackValue('order.revenue', 149.99, { currency: 'USD' }); }); await tracedOperation(); // Pino-native: (extra, message) expect(mockLogger.debug).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ service: 'test-service', metric: 'order.revenue', 'service.version': '4.2.1', 'deployment.environment': 'production', currency: 'USD', traceId: expect.any(String), spanId: expect.any(String), correlationId: expect.any(String), }), }), 'Value tracked', ); }); it('should still work outside a trace (no trace context)', () => { init({ service: 'test-service', version: '1.0.0', environment: 'test', }); const event = new Event('test-service', { logger: mockLogger }); // Call outside a trace event.trackEvent('background.job.completed', { jobId: 'job-123' }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: { service: 'test-service', 'service.version': '1.0.0', 'deployment.environment': 'test', jobId: 'job-123', // No traceId/spanId/correlationId - gracefully omitted }, }), 'Event tracked', ); }); }); describe('automatic operation context enrichment', () => { beforeEach(() => { resetEvents(); }); afterEach(async () => { await shutdown(); }); it('should auto-capture operation.name when inside trace() with string name', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const operation = trace('user.create', async () => { event.trackEvent('user.created', { userId: '123' }); }); await operation(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'user.create', userId: '123', }), }), 'Event tracked', ); }); it('should auto-capture operation.name when inside trace() with named function', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const createUser = trace(async function createUser() { event.trackEvent('user.created', { userId: '456' }); }); await createUser(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ // Function name might be inferred with slight variations (e.g., 'createUser2') // The important thing is that operation.name is auto-captured 'operation.name': expect.stringMatching(/createUser/), userId: '456', }), }), 'Event tracked', ); }); it('should reliably infer function names in both factory and non-factory patterns', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); // Test 1: Named function declaration (non-factory pattern) // Should infer name from function declaration const updateUser = trace(async function updateUser(userId: string) { event.trackEvent('user.updated', { userId }); }); await updateUser('user-123'); // Test 2: Named function with factory pattern (ctx parameter) // Explicit name should take precedence const deleteUser = trace( 'user.delete', (ctx) => async function deleteUser(userId: string) { ctx.setAttribute('user.id', userId); event.trackEvent('user.deleted', { userId }); }, ); await deleteUser('user-456'); // Test 3: Named function in factory pattern (should infer inner function name) const createOrder = trace( (ctx) => async function createOrder(orderId: string) { ctx.setAttribute('order.id', orderId); event.trackEvent('order.created', { orderId }); }, ); await createOrder('order-789'); // Verify all operations captured correct names // Pino-native: first arg is the extra object const calls = (mockLogger.info as ReturnType<typeof vi.fn>).mock.calls; // First call: updateUser - should infer from named function declaration expect(calls[0][0].attributes['operation.name']).toMatch(/updateUser/); // Second call: user.delete - explicit name takes precedence expect(calls[1][0].attributes['operation.name']).toBe('user.delete'); // Third call: createOrder - should infer from inner named function in factory pattern expect(calls[2][0].attributes['operation.name']).toMatch(/createOrder/); }); it('should auto-capture operation.name in nested spans', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const { span } = await import('./functional'); const operation = trace('order.process', async () => { span({ name: 'order.validate' }, () => { // Should capture the innermost operation name event.trackEvent('order.validated', { orderId: 'ord_123' }); }); }); await operation(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'order.validate', orderId: 'ord_123', }), }), 'Event tracked', ); }); it('should auto-capture operation.name in trackFunnelStep', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const checkout = trace('checkout.flow', async () => { event.trackFunnelStep('checkout', 'started', { cartValue: 99.99, }); }); await checkout(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'checkout.flow', cartValue: 99.99, }), }), 'Funnel step tracked', ); }); it('should auto-capture operation.name in trackOutcome', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const sendEmail = trace('email.send', async () => { event.trackOutcome('email.delivery', 'success', { recipientType: 'user', }); }); await sendEmail(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'email.send', recipientType: 'user', }), }), 'Outcome tracked', ); }); it('should auto-capture operation.name in trackValue', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const processOrder = trace('order.process', async () => { event.trackValue('order.revenue', 149.99, { currency: 'USD' }); }); await processOrder(); // Pino-native: (extra, message) expect(mockLogger.debug).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'order.process', currency: 'USD', }), }), 'Value tracked', ); }); it('should handle missing operation.name gracefully (outside trace)', () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); // Call outside any trace event.trackEvent('background.job', { jobId: 'job-123' }); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: { service: 'test-service', 'service.version': undefined, 'deployment.environment': undefined, jobId: 'job-123', // No operation.name - gracefully omitted }, }), 'Event tracked', ); }); it('should capture parent operation.name when not in nested span', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const parentOperation = trace('parent.operation', async () => { // Track event in parent context (not in a nested span) event.trackEvent('parent.event', { step: 1 }); // Then create a nested span const { span } = await import('./functional'); span({ name: 'child.operation' }, () => { event.trackEvent('child.event', { step: 2 }); }); }); await parentOperation(); // Check parent event has parent operation name // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenNthCalledWith( 1, expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'parent.operation', step: 1, }), }), 'Event tracked', ); // Check child event has child operation name // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenNthCalledWith( 2, expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'child.operation', step: 2, }), }), 'Event tracked', ); }); it('should work with trace() factory pattern', async () => { init({ service: 'test-service' }); const event = new Event('test-service', { logger: mockLogger }); const operation = trace('factory.operation', (ctx) => async () => { ctx.setAttribute('custom', 'attribute'); event.trackEvent('factory.event', { data: 'test' }); }); await operation(); // Pino-native: (extra, message) expect(mockLogger.info).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ 'operation.name': 'factory.operation', data: 'test', }), }), 'Event tracked', ); }); }); describe('autotel trace context', () => { beforeEach(() => { resetEvents(); }); afterEach(async () => { await shutdown(); }); it('should always include correlation_id even without includeTraceContext', async () => { init({ service: 'test-service', // events.includeTraceContext is false by default }); const mockSubscriber = { name: 'MockSubscriber', trackEvent: vi.fn().mockResolvedValue(undefined), trackFunnelStep: vi.fn().mockResolvedValue(undefined), trackOutcome: vi.fn().mockResolvedValue(undefined), trackValue: vi.fn().mockResolvedValue(undefined), }; const event = new Event('test-service', { subscribers: [mockSubscriber], }); event.trackEvent('test.event', { userId: '123' }); // Wait for async subscriber notification await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockSubscriber.trackEvent).toHaveBeenCalledWith( 'test.event', expect.any(Object), expect.objectContaining({ autotel: expect.objectContaining({ correlation_id: expect.any(String), }), }), ); // Correlation ID should be 16 hex chars const call = mockSubscriber.trackEvent.mock.calls[0]; const autotelContext = call[2].autotel; expect(autotelContext.correlation_id).toHaveLength(16); expect(/^[0-9a-f]{16}$/.test(autotelContext.correlation_id)).toBe(true); }); it('should include full trace context when includeTraceContext is enabled', async () => { init({ service: 'test-service', events: { includeTraceContext: true, }, }); const mockSubscriber = { name: 'MockSubscriber', trackEvent: vi.fn().mockResolvedValue(undefined), trackFunnelStep: vi.fn().mockResolvedValue(undefined), trackOutcome: vi.fn().mockResolvedValue(undefined), trackValue: vi.fn().mockResolvedValue(undefined), }; const event = new Event('test-service', { subscribers: [mockSubscriber], }); // Track event inside a trace const tracedOperation = trace('test.operation', async () => { event.trackEvent('traced.event', { data: 'test' }); }); await tracedOperation(); // Wait for async subscriber notification await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockSubscriber.trackEvent).toHaveBeenCalledWith( 'traced.event', expect.any(Object), expect.objectContaining({ autotel: expect.objectContaining({ trace_id: expect.any(String), span_id: expect.any(String), trace_flags: expect.any(String), correlation_id: expect.any(String), }), }), ); // Verify trace_id is 32 hex chars const call = mockSubscriber.trackEvent.mock.calls[0]; const autotelContext = call[2].autotel; expect(autotelContext.trace_id).toHaveLength(32); expect(/^[0-9a-f]{32}$/.test(autotelContext.trace_id)).toBe(true); // Verify span_id is 16 hex chars expect(autotelContext.span_id).toHaveLength(16); expect(/^[0-9a-f]{16}$/.test(autotelContext.span_id)).toBe(true); // Verify trace_flags is 2 hex chars expect(autotelContext.trace_flags).toHaveLength(2); }); it('should call traceUrl function when configured', async () => { const traceUrlFn = vi .fn() .mockReturnValue('https://traces.example.com/trace/abc123'); init({ service: 'test-service', events: { includeTraceContext: true, traceUrl: traceUrlFn, }, }); const mockSubscriber = { name: 'MockSubscriber', trackEvent: vi.fn().mockResolvedValue(undefined), trackFunnelStep: vi.fn().mockResolvedValue(undefined), trackOutcome: vi.fn().mockResolvedValue(undefined), trackValue: vi.fn().mockResolvedValue(undefined), }; const event = new Event('test-service', { subscribers: [mockSubscriber], }); // Track event inside a trace const tracedOperation = trace('test.operation', async () => { event.trackEvent('traced.event', {}); }); await tracedOperation(); await new Promise((resolve) => setTimeout(resolve, 50)); expect(traceUrlFn).toHaveBeenCalledWith( expect.objectContaining({ traceId: expect.any(String), spanId: expect.any(String), correlationId: expect.any(String), serviceName: 'test-service', }), ); expect(mockSubscriber.trackEvent).toHaveBeenCalledWith( 'traced.event', expect.any(Object), expect.objectContaining({ autotel: expect.objectContaining({ trace_url: 'https://traces.example.com/trace/abc123', }), }), ); }); it('should include autotel context in trackFunnelStep', async () => { init({ service: 'test-service', events: { includeTraceContext: true, }, }); const mockSubscriber = { name: 'MockSubscriber', trackEvent: vi.fn().mockResolvedValue(undefined), trackFunnelStep: vi.fn().mockResolvedValue(undefined), trackOutcome: vi.fn().mockResolvedValue(undefined), trackValue: vi.fn().mockResolvedValue(undefined), }; const event = new Event('test-service', { subscribers: [mockSubscriber], }); const tracedOperation = trace('checkout.flow', async () => { event.trackFunnelStep('checkout', 'started', { cartValue: 99.99 }); }); await tracedOperation(); await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockSubscriber.trackFunnelStep).toHaveBeenCalledWith( 'checkout', 'started', expect.any(Object), expect.objectContaining({ autotel: expect.objectContaining({ trace_id: expect.any(String), correlation_id: expect.any(String), }), }), ); }); it('should include autotel context in trackOutcome', async () => { init({ service: 'test-service', events: { includeTraceContext: true, }, }); const mockSubscriber = { name: 'MockSubscriber', trackEvent: vi.fn().mockResolvedValue(undefined), trackFunnelStep: vi.fn().mockResolvedValue(undefined), trackOutcome: vi.fn().mockResolvedValue(undefined), trackValue: vi.fn().mockResolvedValue(undefined), }; const event = new Event('test-service', { subscribers: [mockSubscriber], }); const tracedOperation = trace('payment.process', async () => { event.trackOutcome('payment', 'success', { amount: 99.99 }); }); await tracedOperation(); await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockSubscriber.trackOutcome).toHaveBeenCalledWith( 'payment', 'success', expect.any(Object), expect.objectContaining({ autotel: expect.objectContaining({ trace_id: expect.any(String), correlation_id: expect.any(String), }), }), ); }); it('should include autotel context in trackValue', async () => { init({ service: 'test-service', events: { includeTraceContext: true, }, }); const mockSubscriber = { name: 'MockSubscriber', trackEvent: vi.fn().mockResolvedValue(undefined), trackFunnelStep: vi.fn().mockResolvedValue(undefined), trackOutcome: vi.fn().mockResolvedValue(undefined), trackValue: vi.fn().mockResolvedValue(undefined), }; const event = new Event('test-service', { subscribers: [mockSubscriber], }); const tracedOperation = trace('order.process', async () => { event.trackValue('revenue', 149.99, { currency: 'USD' }); }); await tracedOperation(); await new Promise((resolve) => setTimeout(resolve, 50)); expect(mockSubscriber.trackValue).toHaveBeenCalledWith( 'revenue', 149.99, expect.any(Object), expect.objectContaining({ autotel: expect.objectContaining({ trace_id: expect.any(String), correlation_id: expect.any(String), }), }), ); }); }); describe('baggage enrichment', () => { afterEach(async () => { await shutdown(); }); it('should apply maxBytes after hashing baggage values', async () => { init({ service: 'test-service', events: { enrichFromBaggage: { allow: ['user.id'], maxBytes: 16, transform: { 'user.id': 'hash', }, }, }, }); const mockSubscriber = { name: 'MockSubscriber', trackEvent: vi.fn().mockResolvedValue(undefined), trackFunnelStep: vi.fn().mockResolvedValue(undefined), trackOutcome: vi.fn().mockResolvedValue(undefined), trackValue: vi.fn().mockResolvedValue(undefined), }; const event = new Event('test-service', { subscribers: [mockSubscriber], }); const largeValue = 'x'.repeat(2048); const baggage = propagation .createBaggage() .setEntry('user.id', { value: largeValue }); const ctx = propagation.setBaggage(context.active(), baggage); context.with(ctx, () => { event.trackEvent('test.event', { foo: 'bar' }); }); await new Promise((resolve) => setTimeout(resolve, 50)); const call = mockSubscriber.trackEvent.mock.calls[0]; const attributes = call[1] as Record<string, unknown>; expect(attributes['user.id']).toBeDefined(); expect(String(attributes['user.id'])).toHaveLength(8); }); }); });