UNPKG

autotel

Version:
346 lines (342 loc) 10 kB
import { Link, SpanContext } from '@opentelemetry/api'; import { OutOfOrderInfo, RebalanceEvent } from './messaging.js'; import './trace-context-t5X1AP-e.js'; /** * Testing utilities for messaging instrumentation * * Provides mock producers, consumers, and assertion helpers * for testing event-driven code with Autotel's messaging module. * * @example Basic test setup * ```typescript * import { createMessagingTestHarness } from 'autotel/messaging-testing'; * * describe('Order processing', () => { * const harness = createMessagingTestHarness(); * * beforeEach(() => harness.reset()); * afterAll(() => harness.shutdown()); * * it('should process order and publish event', async () => { * await processOrder({ id: 'order-123' }); * * harness.assertProducerCalled('orders', { * messageCount: 1, * hasTraceHeaders: true, * }); * }); * }); * ``` * * @module */ /** * Recorded producer call */ interface RecordedProducerCall { /** Destination (topic/queue) */ destination: string; /** System (kafka, sqs, etc.) */ system: string; /** Message payload */ payload: unknown; /** Headers injected */ headers: Record<string, string>; /** Timestamp of call */ timestamp: number; /** Trace ID from headers */ traceId?: string; /** Span ID from headers */ spanId?: string; } /** * Recorded consumer call */ interface RecordedConsumerCall { /** Destination (topic/queue) */ destination: string; /** System (kafka, sqs, etc.) */ system: string; /** Consumer group */ consumerGroup?: string; /** Message payload */ payload: unknown; /** Headers extracted */ headers?: Record<string, string>; /** Timestamp of call */ timestamp: number; /** Producer links extracted */ producerLinks: Link[]; /** Whether message was duplicate */ isDuplicate: boolean; /** Out of order info if detected */ outOfOrderInfo: OutOfOrderInfo | null; /** DLQ reason if routed to DLQ */ dlqReason?: string; /** Retry attempt number */ retryAttempt?: number; } /** * Recorded rebalance event */ interface RecordedRebalanceEvent extends RebalanceEvent { /** Destination (topic) */ destination: string; /** Consumer group */ consumerGroup: string; } /** * Mock message for testing */ interface MockMessage<T = unknown> { /** Message payload */ payload: T; /** Headers */ headers?: Record<string, string>; /** Offset/sequence number */ offset?: number; /** Partition */ partition?: number; /** Key */ key?: string; /** Message ID */ messageId?: string; /** Timestamp */ timestamp?: number; } /** * Producer assertion options */ interface ProducerAssertionOptions { /** Expected number of messages */ messageCount?: number; /** Whether trace headers should be present */ hasTraceHeaders?: boolean; /** Expected destination */ destination?: string; /** Custom matcher for payload */ payloadMatcher?: (payload: unknown) => boolean; /** Expected trace ID */ traceId?: string; } /** * Consumer assertion options */ interface ConsumerAssertionOptions { /** Expected number of messages processed */ messageCount?: number; /** Whether producer links should be present */ hasProducerLinks?: boolean; /** Expected destination */ destination?: string; /** Expected consumer group */ consumerGroup?: string; /** Whether any messages were duplicates */ hasDuplicates?: boolean; /** Whether any messages were out of order */ hasOutOfOrder?: boolean; /** Whether any messages went to DLQ */ hasDLQ?: boolean; } /** * Messaging test harness */ interface MessagingTestHarness { /** All recorded producer calls */ producerCalls: RecordedProducerCall[]; /** All recorded consumer calls */ consumerCalls: RecordedConsumerCall[]; /** All recorded rebalance events */ rebalanceEvents: RecordedRebalanceEvent[]; /** * Record a producer call */ recordProducerCall(call: Omit<RecordedProducerCall, 'timestamp'>): void; /** * Record a consumer call */ recordConsumerCall(call: Omit<RecordedConsumerCall, 'timestamp'>): void; /** * Record a rebalance event */ recordRebalanceEvent(event: RecordedRebalanceEvent): void; /** * Create a mock message with trace headers */ createMockMessage<T>(payload: T, options?: Partial<MockMessage<T>>): MockMessage<T>; /** * Create mock trace headers */ createMockTraceHeaders(traceId?: string, spanId?: string): Record<string, string>; /** * Assert producer was called with expected options */ assertProducerCalled(destination: string, options?: ProducerAssertionOptions): void; /** * Assert producer was not called */ assertProducerNotCalled(destination?: string): void; /** * Assert consumer processed messages with expected options */ assertConsumerProcessed(destination: string, options?: ConsumerAssertionOptions): void; /** * Assert consumer was not called */ assertConsumerNotCalled(destination?: string): void; /** * Assert rebalance occurred */ assertRebalanceOccurred(destination: string, type: RebalanceEvent['type'], partitionCount?: number): void; /** * Get producer calls for destination */ getProducerCalls(destination?: string): RecordedProducerCall[]; /** * Get consumer calls for destination */ getConsumerCalls(destination?: string): RecordedConsumerCall[]; /** * Get the last producer call */ getLastProducerCall(destination?: string): RecordedProducerCall | undefined; /** * Get the last consumer call */ getLastConsumerCall(destination?: string): RecordedConsumerCall | undefined; /** * Reset all recorded calls */ reset(): void; /** * Shutdown the harness */ shutdown(): void; } /** * Create a messaging test harness * * Provides utilities for recording and asserting on producer/consumer calls * during testing. * * @example * ```typescript * const harness = createMessagingTestHarness(); * * // In your test setup * beforeEach(() => harness.reset()); * * // In your tests * it('should publish order event', async () => { * await orderService.createOrder({ id: '123' }); * * harness.assertProducerCalled('orders', { * messageCount: 1, * hasTraceHeaders: true, * }); * * const lastCall = harness.getLastProducerCall('orders'); * expect(lastCall?.payload).toMatchObject({ orderId: '123' }); * }); * ``` */ declare function createMessagingTestHarness(): MessagingTestHarness; /** * Mock message broker for testing */ interface MockMessageBroker { /** Topics/queues in the broker */ topics: Map<string, MockMessage[]>; /** * Publish a message to a topic */ publish(topic: string, message: MockMessage): void; /** * Consume messages from a topic */ consume(topic: string, count?: number): MockMessage[]; /** * Peek at messages without consuming */ peek(topic: string, count?: number): MockMessage[]; /** * Get message count for topic */ getMessageCount(topic: string): number; /** * Clear all messages */ clear(topic?: string): void; /** * Create a topic */ createTopic(topic: string): void; /** * Delete a topic */ deleteTopic(topic: string): void; /** * List all topics */ listTopics(): string[]; } /** * Create a mock message broker for testing * * Simulates a message broker (Kafka, SQS, RabbitMQ, etc.) for unit testing. * * @example * ```typescript * const broker = createMockMessageBroker(); * * // Producer publishes * broker.publish('orders', { payload: { orderId: '123' }, headers: {} }); * * // Consumer receives * const messages = broker.consume('orders'); * expect(messages).toHaveLength(1); * expect(messages[0].payload).toEqual({ orderId: '123' }); * ``` */ declare function createMockMessageBroker(): MockMessageBroker; /** * Extract trace ID from traceparent header */ declare function extractTraceIdFromHeader(traceparent: string): string | null; /** * Extract span ID from traceparent header */ declare function extractSpanIdFromHeader(traceparent: string): string | null; /** * Create a mock span context */ declare function createMockSpanContext(traceId?: string, spanId?: string): SpanContext; /** * Create a mock link to a producer span */ declare function createMockProducerLink(traceId?: string, spanId?: string): Link; /** * Create a batch of mock messages */ declare function createMockMessageBatch<T>(payloads: T[], options?: { startOffset?: number; partition?: number; addTraceHeaders?: boolean; traceId?: string; }): MockMessage<T>[]; /** * Create a rebalance scenario */ declare function createRebalanceScenario(topic: string, consumerGroup: string, partitions: number[]): { assignEvent: RecordedRebalanceEvent; revokeEvent: RecordedRebalanceEvent; }; /** * Create an out-of-order scenario */ declare function createOutOfOrderScenario<T>(payloads: T[], outOfOrderIndices: number[]): MockMessage<T>[]; /** * Create a duplicate message scenario */ declare function createDuplicateScenario<T>(payloads: T[], duplicateIndices: number[]): MockMessage<T>[]; export { type ConsumerAssertionOptions, type MessagingTestHarness, type MockMessage, type MockMessageBroker, type ProducerAssertionOptions, type RecordedConsumerCall, type RecordedProducerCall, type RecordedRebalanceEvent, createDuplicateScenario, createMessagingTestHarness, createMockMessageBatch, createMockMessageBroker, createMockProducerLink, createMockSpanContext, createOutOfOrderScenario, createRebalanceScenario, extractSpanIdFromHeader, extractTraceIdFromHeader };