UNPKG

autotel

Version:
1 lines 30.7 kB
{"version":3,"file":"messaging-testing.cjs","names":[],"sources":["../src/messaging-testing.ts"],"sourcesContent":["/**\n * Testing utilities for messaging instrumentation\n *\n * Provides mock producers, consumers, and assertion helpers\n * for testing event-driven code with Autotel's messaging module.\n *\n * @example Basic test setup\n * ```typescript\n * import { createMessagingTestHarness } from 'autotel/messaging-testing';\n *\n * describe('Order processing', () => {\n * const harness = createMessagingTestHarness();\n *\n * beforeEach(() => harness.reset());\n * afterAll(() => harness.shutdown());\n *\n * it('should process order and publish event', async () => {\n * await processOrder({ id: 'order-123' });\n *\n * harness.assertProducerCalled('orders', {\n * messageCount: 1,\n * hasTraceHeaders: true,\n * });\n * });\n * });\n * ```\n *\n * @module\n */\n\nimport type { Link, SpanContext } from '@opentelemetry/api';\nimport type {\n RebalanceEvent,\n PartitionAssignment,\n OutOfOrderInfo,\n} from './messaging';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Recorded producer call\n */\nexport interface RecordedProducerCall {\n /** Destination (topic/queue) */\n destination: string;\n\n /** System (kafka, sqs, etc.) */\n system: string;\n\n /** Message payload */\n payload: unknown;\n\n /** Headers injected */\n headers: Record<string, string>;\n\n /** Timestamp of call */\n timestamp: number;\n\n /** Trace ID from headers */\n traceId?: string;\n\n /** Span ID from headers */\n spanId?: string;\n}\n\n/**\n * Recorded consumer call\n */\nexport interface RecordedConsumerCall {\n /** Destination (topic/queue) */\n destination: string;\n\n /** System (kafka, sqs, etc.) */\n system: string;\n\n /** Consumer group */\n consumerGroup?: string;\n\n /** Message payload */\n payload: unknown;\n\n /** Headers extracted */\n headers?: Record<string, string>;\n\n /** Timestamp of call */\n timestamp: number;\n\n /** Producer links extracted */\n producerLinks: Link[];\n\n /** Whether message was duplicate */\n isDuplicate: boolean;\n\n /** Out of order info if detected */\n outOfOrderInfo: OutOfOrderInfo | null;\n\n /** DLQ reason if routed to DLQ */\n dlqReason?: string;\n\n /** Retry attempt number */\n retryAttempt?: number;\n}\n\n/**\n * Recorded rebalance event\n */\nexport interface RecordedRebalanceEvent extends RebalanceEvent {\n /** Destination (topic) */\n destination: string;\n\n /** Consumer group */\n consumerGroup: string;\n}\n\n/**\n * Mock message for testing\n */\nexport interface MockMessage<T = unknown> {\n /** Message payload */\n payload: T;\n\n /** Headers */\n headers?: Record<string, string>;\n\n /** Offset/sequence number */\n offset?: number;\n\n /** Partition */\n partition?: number;\n\n /** Key */\n key?: string;\n\n /** Message ID */\n messageId?: string;\n\n /** Timestamp */\n timestamp?: number;\n}\n\n/**\n * Producer assertion options\n */\nexport interface ProducerAssertionOptions {\n /** Expected number of messages */\n messageCount?: number;\n\n /** Whether trace headers should be present */\n hasTraceHeaders?: boolean;\n\n /** Expected destination */\n destination?: string;\n\n /** Custom matcher for payload */\n payloadMatcher?: (payload: unknown) => boolean;\n\n /** Expected trace ID */\n traceId?: string;\n}\n\n/**\n * Consumer assertion options\n */\nexport interface ConsumerAssertionOptions {\n /** Expected number of messages processed */\n messageCount?: number;\n\n /** Whether producer links should be present */\n hasProducerLinks?: boolean;\n\n /** Expected destination */\n destination?: string;\n\n /** Expected consumer group */\n consumerGroup?: string;\n\n /** Whether any messages were duplicates */\n hasDuplicates?: boolean;\n\n /** Whether any messages were out of order */\n hasOutOfOrder?: boolean;\n\n /** Whether any messages went to DLQ */\n hasDLQ?: boolean;\n}\n\n/**\n * Messaging test harness\n */\nexport interface MessagingTestHarness {\n /** All recorded producer calls */\n producerCalls: RecordedProducerCall[];\n\n /** All recorded consumer calls */\n consumerCalls: RecordedConsumerCall[];\n\n /** All recorded rebalance events */\n rebalanceEvents: RecordedRebalanceEvent[];\n\n /**\n * Record a producer call\n */\n recordProducerCall(call: Omit<RecordedProducerCall, 'timestamp'>): void;\n\n /**\n * Record a consumer call\n */\n recordConsumerCall(call: Omit<RecordedConsumerCall, 'timestamp'>): void;\n\n /**\n * Record a rebalance event\n */\n recordRebalanceEvent(event: RecordedRebalanceEvent): void;\n\n /**\n * Create a mock message with trace headers\n */\n createMockMessage<T>(\n payload: T,\n options?: Partial<MockMessage<T>>,\n ): MockMessage<T>;\n\n /**\n * Create mock trace headers\n */\n createMockTraceHeaders(\n traceId?: string,\n spanId?: string,\n ): Record<string, string>;\n\n /**\n * Assert producer was called with expected options\n */\n assertProducerCalled(\n destination: string,\n options?: ProducerAssertionOptions,\n ): void;\n\n /**\n * Assert producer was not called\n */\n assertProducerNotCalled(destination?: string): void;\n\n /**\n * Assert consumer processed messages with expected options\n */\n assertConsumerProcessed(\n destination: string,\n options?: ConsumerAssertionOptions,\n ): void;\n\n /**\n * Assert consumer was not called\n */\n assertConsumerNotCalled(destination?: string): void;\n\n /**\n * Assert rebalance occurred\n */\n assertRebalanceOccurred(\n destination: string,\n type: RebalanceEvent['type'],\n partitionCount?: number,\n ): void;\n\n /**\n * Get producer calls for destination\n */\n getProducerCalls(destination?: string): RecordedProducerCall[];\n\n /**\n * Get consumer calls for destination\n */\n getConsumerCalls(destination?: string): RecordedConsumerCall[];\n\n /**\n * Get the last producer call\n */\n getLastProducerCall(destination?: string): RecordedProducerCall | undefined;\n\n /**\n * Get the last consumer call\n */\n getLastConsumerCall(destination?: string): RecordedConsumerCall | undefined;\n\n /**\n * Reset all recorded calls\n */\n reset(): void;\n\n /**\n * Shutdown the harness\n */\n shutdown(): void;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Generate a random hex string\n */\nfunction randomHex(length: number): string {\n let result = '';\n const chars = '0123456789abcdef';\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n}\n\n/**\n * Create a messaging test harness\n *\n * Provides utilities for recording and asserting on producer/consumer calls\n * during testing.\n *\n * @example\n * ```typescript\n * const harness = createMessagingTestHarness();\n *\n * // In your test setup\n * beforeEach(() => harness.reset());\n *\n * // In your tests\n * it('should publish order event', async () => {\n * await orderService.createOrder({ id: '123' });\n *\n * harness.assertProducerCalled('orders', {\n * messageCount: 1,\n * hasTraceHeaders: true,\n * });\n *\n * const lastCall = harness.getLastProducerCall('orders');\n * expect(lastCall?.payload).toMatchObject({ orderId: '123' });\n * });\n * ```\n */\nexport function createMessagingTestHarness(): MessagingTestHarness {\n const producerCalls: RecordedProducerCall[] = [];\n const consumerCalls: RecordedConsumerCall[] = [];\n const rebalanceEvents: RecordedRebalanceEvent[] = [];\n\n return {\n producerCalls,\n consumerCalls,\n rebalanceEvents,\n\n recordProducerCall(call) {\n producerCalls.push({\n ...call,\n timestamp: Date.now(),\n });\n },\n\n recordConsumerCall(call) {\n consumerCalls.push({\n ...call,\n timestamp: Date.now(),\n });\n },\n\n recordRebalanceEvent(event) {\n rebalanceEvents.push(event);\n },\n\n createMockMessage<T>(\n payload: T,\n options: Partial<MockMessage<T>> = {},\n ): MockMessage<T> {\n return {\n payload,\n headers: options.headers ?? this.createMockTraceHeaders(),\n offset: options.offset ?? Math.floor(Math.random() * 10_000),\n partition: options.partition ?? 0,\n key: options.key,\n messageId: options.messageId ?? `msg-${randomHex(8)}`,\n timestamp: options.timestamp ?? Date.now(),\n };\n },\n\n createMockTraceHeaders(\n traceId?: string,\n spanId?: string,\n ): Record<string, string> {\n const tid = traceId ?? randomHex(32);\n const sid = spanId ?? randomHex(16);\n return {\n traceparent: `00-${tid}-${sid}-01`,\n };\n },\n\n assertProducerCalled(\n destination: string,\n options: ProducerAssertionOptions = {},\n ) {\n const calls = producerCalls.filter((c) => c.destination === destination);\n\n if (calls.length === 0) {\n throw new Error(\n `Expected producer to be called for destination '${destination}', but it was not called`,\n );\n }\n\n if (\n options.messageCount !== undefined &&\n calls.length !== options.messageCount\n ) {\n throw new Error(\n `Expected ${options.messageCount} producer calls for '${destination}', got ${calls.length}`,\n );\n }\n\n if (options.hasTraceHeaders) {\n const withoutHeaders = calls.filter((c) => !c.headers?.traceparent);\n if (withoutHeaders.length > 0) {\n throw new Error(\n `Expected all producer calls for '${destination}' to have trace headers, but ${withoutHeaders.length} did not`,\n );\n }\n }\n\n if (options.traceId) {\n const matchingTraceId = calls.filter(\n (c) => c.traceId === options.traceId,\n );\n if (matchingTraceId.length === 0) {\n throw new Error(\n `Expected producer call for '${destination}' with traceId '${options.traceId}', but none found`,\n );\n }\n }\n\n if (options.payloadMatcher) {\n const matching = calls.filter((c) =>\n options.payloadMatcher!(c.payload),\n );\n if (matching.length === 0) {\n throw new Error(\n `Expected producer call for '${destination}' to match payload matcher, but none did`,\n );\n }\n }\n },\n\n assertProducerNotCalled(destination?: string) {\n if (destination) {\n const calls = producerCalls.filter(\n (c) => c.destination === destination,\n );\n if (calls.length > 0) {\n throw new Error(\n `Expected producer not to be called for '${destination}', but it was called ${calls.length} times`,\n );\n }\n } else {\n if (producerCalls.length > 0) {\n throw new Error(\n `Expected no producer calls, but ${producerCalls.length} calls were made`,\n );\n }\n }\n },\n\n assertConsumerProcessed(\n destination: string,\n options: ConsumerAssertionOptions = {},\n ) {\n const calls = consumerCalls.filter((c) => c.destination === destination);\n\n if (calls.length === 0) {\n throw new Error(\n `Expected consumer to process messages for destination '${destination}', but none were processed`,\n );\n }\n\n if (\n options.messageCount !== undefined &&\n calls.length !== options.messageCount\n ) {\n throw new Error(\n `Expected ${options.messageCount} consumer calls for '${destination}', got ${calls.length}`,\n );\n }\n\n if (options.consumerGroup) {\n const wrongGroup = calls.filter(\n (c) => c.consumerGroup !== options.consumerGroup,\n );\n if (wrongGroup.length > 0) {\n throw new Error(\n `Expected consumer group '${options.consumerGroup}' for '${destination}', but found different groups`,\n );\n }\n }\n\n if (options.hasProducerLinks) {\n const withoutLinks = calls.filter((c) => c.producerLinks.length === 0);\n if (withoutLinks.length > 0) {\n throw new Error(\n `Expected all consumer calls for '${destination}' to have producer links, but ${withoutLinks.length} did not`,\n );\n }\n }\n\n if (options.hasDuplicates !== undefined) {\n const duplicates = calls.filter((c) => c.isDuplicate);\n if (options.hasDuplicates && duplicates.length === 0) {\n throw new Error(\n `Expected duplicate messages for '${destination}', but none were detected`,\n );\n }\n if (!options.hasDuplicates && duplicates.length > 0) {\n throw new Error(\n `Expected no duplicate messages for '${destination}', but ${duplicates.length} were detected`,\n );\n }\n }\n\n if (options.hasOutOfOrder !== undefined) {\n const outOfOrder = calls.filter((c) => c.outOfOrderInfo !== null);\n if (options.hasOutOfOrder && outOfOrder.length === 0) {\n throw new Error(\n `Expected out-of-order messages for '${destination}', but none were detected`,\n );\n }\n if (!options.hasOutOfOrder && outOfOrder.length > 0) {\n throw new Error(\n `Expected no out-of-order messages for '${destination}', but ${outOfOrder.length} were detected`,\n );\n }\n }\n\n if (options.hasDLQ !== undefined) {\n const dlqCalls = calls.filter((c) => c.dlqReason !== undefined);\n if (options.hasDLQ && dlqCalls.length === 0) {\n throw new Error(\n `Expected DLQ routing for '${destination}', but none occurred`,\n );\n }\n if (!options.hasDLQ && dlqCalls.length > 0) {\n throw new Error(\n `Expected no DLQ routing for '${destination}', but ${dlqCalls.length} occurred`,\n );\n }\n }\n },\n\n assertConsumerNotCalled(destination?: string) {\n if (destination) {\n const calls = consumerCalls.filter(\n (c) => c.destination === destination,\n );\n if (calls.length > 0) {\n throw new Error(\n `Expected consumer not to be called for '${destination}', but it processed ${calls.length} messages`,\n );\n }\n } else {\n if (consumerCalls.length > 0) {\n throw new Error(\n `Expected no consumer calls, but ${consumerCalls.length} messages were processed`,\n );\n }\n }\n },\n\n assertRebalanceOccurred(\n destination: string,\n type: RebalanceEvent['type'],\n partitionCount?: number,\n ) {\n const events = rebalanceEvents.filter(\n (e) => e.destination === destination && e.type === type,\n );\n\n if (events.length === 0) {\n throw new Error(\n `Expected rebalance '${type}' for '${destination}', but none occurred`,\n );\n }\n\n if (partitionCount !== undefined) {\n const matching = events.filter(\n (e) => e.partitions.length === partitionCount,\n );\n if (matching.length === 0) {\n throw new Error(\n `Expected rebalance '${type}' for '${destination}' with ${partitionCount} partitions, but none matched`,\n );\n }\n }\n },\n\n getProducerCalls(destination?: string) {\n if (destination) {\n return producerCalls.filter((c) => c.destination === destination);\n }\n return [...producerCalls];\n },\n\n getConsumerCalls(destination?: string) {\n if (destination) {\n return consumerCalls.filter((c) => c.destination === destination);\n }\n return [...consumerCalls];\n },\n\n getLastProducerCall(destination?: string) {\n const calls = this.getProducerCalls(destination);\n return calls.at(-1);\n },\n\n getLastConsumerCall(destination?: string) {\n const calls = this.getConsumerCalls(destination);\n return calls.at(-1);\n },\n\n reset() {\n producerCalls.length = 0;\n consumerCalls.length = 0;\n rebalanceEvents.length = 0;\n },\n\n shutdown() {\n this.reset();\n },\n };\n}\n\n// ============================================================================\n// Mock Broker\n// ============================================================================\n\n/**\n * Mock message broker for testing\n */\nexport interface MockMessageBroker {\n /** Topics/queues in the broker */\n topics: Map<string, MockMessage[]>;\n\n /**\n * Publish a message to a topic\n */\n publish(topic: string, message: MockMessage): void;\n\n /**\n * Consume messages from a topic\n */\n consume(topic: string, count?: number): MockMessage[];\n\n /**\n * Peek at messages without consuming\n */\n peek(topic: string, count?: number): MockMessage[];\n\n /**\n * Get message count for topic\n */\n getMessageCount(topic: string): number;\n\n /**\n * Clear all messages\n */\n clear(topic?: string): void;\n\n /**\n * Create a topic\n */\n createTopic(topic: string): void;\n\n /**\n * Delete a topic\n */\n deleteTopic(topic: string): void;\n\n /**\n * List all topics\n */\n listTopics(): string[];\n}\n\n/**\n * Create a mock message broker for testing\n *\n * Simulates a message broker (Kafka, SQS, RabbitMQ, etc.) for unit testing.\n *\n * @example\n * ```typescript\n * const broker = createMockMessageBroker();\n *\n * // Producer publishes\n * broker.publish('orders', { payload: { orderId: '123' }, headers: {} });\n *\n * // Consumer receives\n * const messages = broker.consume('orders');\n * expect(messages).toHaveLength(1);\n * expect(messages[0].payload).toEqual({ orderId: '123' });\n * ```\n */\nexport function createMockMessageBroker(): MockMessageBroker {\n const topics = new Map<string, MockMessage[]>();\n\n return {\n topics,\n\n publish(topic: string, message: MockMessage) {\n if (!topics.has(topic)) {\n topics.set(topic, []);\n }\n topics.get(topic)!.push({\n ...message,\n timestamp: message.timestamp ?? Date.now(),\n offset: message.offset ?? topics.get(topic)!.length,\n });\n },\n\n consume(topic: string, count?: number) {\n const messages = topics.get(topic) ?? [];\n if (count === undefined) {\n const all = [...messages];\n messages.length = 0;\n return all;\n }\n return messages.splice(0, count);\n },\n\n peek(topic: string, count?: number) {\n const messages = topics.get(topic) ?? [];\n if (count === undefined) {\n return [...messages];\n }\n return messages.slice(0, count);\n },\n\n getMessageCount(topic: string) {\n return topics.get(topic)?.length ?? 0;\n },\n\n clear(topic?: string) {\n if (topic) {\n topics.set(topic, []);\n } else {\n topics.clear();\n }\n },\n\n createTopic(topic: string) {\n if (!topics.has(topic)) {\n topics.set(topic, []);\n }\n },\n\n deleteTopic(topic: string) {\n topics.delete(topic);\n },\n\n listTopics() {\n return [...topics.keys()];\n },\n };\n}\n\n// ============================================================================\n// Context Propagation Helpers\n// ============================================================================\n\n/**\n * Extract trace ID from traceparent header\n */\nexport function extractTraceIdFromHeader(traceparent: string): string | null {\n const parts = traceparent.split('-');\n if (parts.length >= 3 && parts[1] !== undefined) {\n return parts[1];\n }\n return null;\n}\n\n/**\n * Extract span ID from traceparent header\n */\nexport function extractSpanIdFromHeader(traceparent: string): string | null {\n const parts = traceparent.split('-');\n if (parts.length >= 4 && parts[2] !== undefined) {\n return parts[2];\n }\n return null;\n}\n\n/**\n * Create a mock span context\n */\nexport function createMockSpanContext(\n traceId?: string,\n spanId?: string,\n): SpanContext {\n return {\n traceId: traceId ?? randomHex(32),\n spanId: spanId ?? randomHex(16),\n traceFlags: 1,\n isRemote: true,\n };\n}\n\n/**\n * Create a mock link to a producer span\n */\nexport function createMockProducerLink(\n traceId?: string,\n spanId?: string,\n): Link {\n return {\n context: createMockSpanContext(traceId, spanId),\n attributes: {\n 'messaging.link.source': 'producer',\n },\n };\n}\n\n// ============================================================================\n// Scenario Builders\n// ============================================================================\n\n/**\n * Create a batch of mock messages\n */\nexport function createMockMessageBatch<T>(\n payloads: T[],\n options: {\n startOffset?: number;\n partition?: number;\n addTraceHeaders?: boolean;\n traceId?: string;\n } = {},\n): MockMessage<T>[] {\n const startOffset = options.startOffset ?? 0;\n const addTraceHeaders = options.addTraceHeaders ?? true;\n const traceId = options.traceId ?? randomHex(32);\n\n return payloads.map((payload, index) => ({\n payload,\n headers: addTraceHeaders\n ? { traceparent: `00-${traceId}-${randomHex(16)}-01` }\n : undefined,\n offset: startOffset + index,\n partition: options.partition ?? 0,\n messageId: `msg-${randomHex(8)}`,\n timestamp: Date.now() + index,\n }));\n}\n\n/**\n * Create a rebalance scenario\n */\nexport function createRebalanceScenario(\n topic: string,\n consumerGroup: string,\n partitions: number[],\n): {\n assignEvent: RecordedRebalanceEvent;\n revokeEvent: RecordedRebalanceEvent;\n} {\n const assignments: PartitionAssignment[] = partitions.map((p) => ({\n topic,\n partition: p,\n offset: 0,\n }));\n\n return {\n assignEvent: {\n type: 'assigned',\n partitions: assignments,\n timestamp: Date.now(),\n generation: 1,\n destination: topic,\n consumerGroup,\n },\n revokeEvent: {\n type: 'revoked',\n partitions: assignments,\n timestamp: Date.now() + 1000,\n generation: 2,\n destination: topic,\n consumerGroup,\n },\n };\n}\n\n/**\n * Create an out-of-order scenario\n */\nexport function createOutOfOrderScenario<T>(\n payloads: T[],\n outOfOrderIndices: number[],\n): MockMessage<T>[] {\n const messages = createMockMessageBatch(payloads, { addTraceHeaders: true });\n\n // Shuffle specified indices to create out-of-order scenario\n const shuffled = [...messages];\n for (const index of outOfOrderIndices) {\n if (index > 0 && index < shuffled.length) {\n // Swap with previous to create out-of-order\n const prev = shuffled[index - 1]!;\n const curr = shuffled[index]!;\n shuffled[index - 1] = curr;\n shuffled[index] = prev;\n }\n }\n\n return shuffled;\n}\n\n/**\n * Create a duplicate message scenario\n */\nexport function createDuplicateScenario<T>(\n payloads: T[],\n duplicateIndices: number[],\n): MockMessage<T>[] {\n const messages = createMockMessageBatch(payloads, { addTraceHeaders: true });\n const result = [...messages];\n\n for (const index of duplicateIndices) {\n const originalMessage = messages[index];\n if (index >= 0 && index < messages.length && originalMessage) {\n // Insert duplicate after the original\n result.splice(index + 1, 0, { ...originalMessage });\n }\n }\n\n return result;\n}\n"],"mappings":"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"}