autotel
Version:
Write Once, Observe Anywhere
346 lines (342 loc) • 10 kB
TypeScript
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 };