@relaybus/testing 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,8 @@
1
- import { EventStore, NewDelivery, EventEnvelope, Clock, Transport, TransportMessageHandler } from '@relaybus/core';
1
+ import { NewDelivery, EventEnvelope, EventStore, Clock, Transport, TransportMessageHandler } from '@relaybus/core';
2
+
3
+ declare function at(offsetMs: number): Date;
4
+ declare function createEnvelope(overrides?: Partial<EventEnvelope>): EventEnvelope;
5
+ declare function createDelivery(handler: string, availableAt?: Date): NewDelivery;
2
6
 
3
7
  interface EventStoreContractOptions<TTransaction> {
4
8
  /** Creates an empty, set-up store. Called before every test. */
@@ -18,9 +22,6 @@ interface EventStoreContractOptions<TTransaction> {
18
22
  */
19
23
  readonly keepsCompletedRecords?: boolean;
20
24
  }
21
- declare function at(offsetMs: number): Date;
22
- declare function createEnvelope(overrides?: Partial<EventEnvelope>): EventEnvelope;
23
- declare function createDelivery(handler: string, availableAt?: Date): NewDelivery;
24
25
  /**
25
26
  * Behaviour every {@link EventStore} must have. Call it inside a test file of
26
27
  * the store package:
@@ -82,4 +83,145 @@ declare class InMemoryTransport implements Transport {
82
83
  close(): Promise<void>;
83
84
  }
84
85
 
85
- export { type EventStoreContractOptions, FakeClock, InMemoryTransport, type PublishedMessage, at, createDelivery, createEnvelope, eventStoreContract };
86
+ /** How message bodies are compared: byte for byte, or as JSON regardless of key order and spacing. */
87
+ type BodyComparison = 'bytes' | 'json';
88
+ /** Bytes as text or bytes, as captured or encoded. */
89
+ type WireBytes = string | Uint8Array;
90
+
91
+ /** A record as captured from a topic: what an existing producer wrote. */
92
+ interface KafkaCapturedRecord {
93
+ readonly topic: string;
94
+ /** Default 0. */
95
+ readonly partition?: number;
96
+ /** Default `'0'`. */
97
+ readonly offset?: string;
98
+ readonly key: WireBytes | null;
99
+ readonly value: WireBytes | null;
100
+ /** Default none. */
101
+ readonly headers?: Readonly<Record<string, WireBytes>>;
102
+ /** Epoch milliseconds in a string; compared only when the codec sets a timestamp. */
103
+ readonly timestamp?: string;
104
+ }
105
+ /** What a codec's `encode` returns: a `KafkaCodec` record. */
106
+ interface KafkaWrittenRecord {
107
+ readonly key: WireBytes | null;
108
+ readonly value: WireBytes | null;
109
+ readonly headers: Readonly<Record<string, WireBytes | readonly WireBytes[]>>;
110
+ readonly timestamp?: string;
111
+ }
112
+ /** A record as a consumer hands it over: bytes for the key, the value and every header. */
113
+ interface KafkaDeliveredRecord {
114
+ readonly topic: string;
115
+ readonly partition: number;
116
+ readonly offset: string;
117
+ readonly key: Buffer | null;
118
+ readonly value: Buffer | null;
119
+ readonly headers: Readonly<Record<string, Buffer>>;
120
+ readonly timestamp?: string;
121
+ }
122
+ /** One message of the format: the event and the record that carries it. */
123
+ interface KafkaCodecCase {
124
+ readonly name: string;
125
+ readonly envelope: EventEnvelope;
126
+ readonly record: KafkaCapturedRecord;
127
+ /**
128
+ * The event decoding the record gives, when the wire does not carry all of `envelope` (for
129
+ * example a record written without a key). Default `envelope`.
130
+ */
131
+ readonly decoded?: EventEnvelope;
132
+ /**
133
+ * Checks one direction only: `'decode'` for a message only other producers write (for
134
+ * example one without an id, which relaybus would add), `'encode'` for one only other
135
+ * consumers read. Default both.
136
+ */
137
+ readonly only?: 'encode' | 'decode';
138
+ }
139
+ interface KafkaCodecContractOptions {
140
+ /** Writes an event: a codec's `encode`, or a transport's publish captured by an injected producer. */
141
+ readonly encode: (envelope: EventEnvelope) => KafkaWrittenRecord | Promise<KafkaWrittenRecord>;
142
+ /** Reads a record the way the transport does: `(record) => decodeKafkaRecord(codec, record)`. */
143
+ readonly decode: (record: KafkaDeliveredRecord) => EventEnvelope | Promise<EventEnvelope>;
144
+ readonly cases: readonly KafkaCodecCase[];
145
+ /** How values are compared. Default `'bytes'`. */
146
+ readonly compare?: BodyComparison;
147
+ /** Headers left out of the comparison, such as ones relaybus adds and other consumers ignore. */
148
+ readonly ignoreHeaders?: readonly string[];
149
+ }
150
+ /**
151
+ * What differs between `testCase` and what the codec writes and reads, one line each: the key,
152
+ * the value, the headers (a repeated header by its last value) and the timestamp of
153
+ * `encode(envelope)`, and `decode(record)` against the expected event. Empty when they agree.
154
+ */
155
+ declare function kafkaCodecMismatches(options: KafkaCodecContractOptions, testCase: KafkaCodecCase): Promise<string[]>;
156
+ /**
157
+ * Registers one test per case asserting {@link kafkaCodecMismatches} finds nothing: a format of
158
+ * existing producers and consumers is adopted only once relaybus writes and reads it exactly.
159
+ */
160
+ declare function kafkaCodecContract(name: string, options: KafkaCodecContractOptions): void;
161
+
162
+ /** A message as captured from a queue: what an existing publisher wrote. */
163
+ interface RabbitMqCapturedMessage {
164
+ readonly routingKey: string;
165
+ /**
166
+ * The message properties as publish options name them (`persistent`, `messageId`,
167
+ * `contentType`, `timestamp`, ...), without `headers`. Default none.
168
+ */
169
+ readonly properties?: Readonly<Record<string, unknown>>;
170
+ /** Default none. */
171
+ readonly headers?: Readonly<Record<string, unknown>>;
172
+ readonly body: WireBytes;
173
+ }
174
+ /** What a codec's `encode` returns: a `RabbitMqCodec` message. */
175
+ interface RabbitMqWrittenMessage {
176
+ readonly content: Uint8Array;
177
+ /** The publish options: properties, and the headers under `headers`. */
178
+ readonly options: object;
179
+ }
180
+ /** A message as a consumer receives it. */
181
+ interface RabbitMqDeliveredMessage {
182
+ readonly content: Buffer;
183
+ readonly fields: {
184
+ readonly routingKey: string;
185
+ };
186
+ readonly properties: Readonly<Record<string, unknown>> & {
187
+ readonly headers: Readonly<Record<string, unknown>>;
188
+ };
189
+ }
190
+ /** One message of the format: the event and the message that carries it. */
191
+ interface RabbitMqCodecCase {
192
+ readonly name: string;
193
+ readonly envelope: EventEnvelope;
194
+ readonly message: RabbitMqCapturedMessage;
195
+ /** The event decoding the message gives, when the wire does not carry all of `envelope`. */
196
+ readonly decoded?: EventEnvelope;
197
+ /**
198
+ * Checks one direction only: `'decode'` for a message only other producers write (for
199
+ * example one without an id, which relaybus would add), `'encode'` for one only other
200
+ * consumers read. Default both.
201
+ */
202
+ readonly only?: 'encode' | 'decode';
203
+ }
204
+ interface RabbitMqCodecContractOptions {
205
+ /** Writes an event: a codec's `encode`. The routing key is the route's destination, not the codec's. */
206
+ readonly encode: (envelope: EventEnvelope) => RabbitMqWrittenMessage | Promise<RabbitMqWrittenMessage>;
207
+ /** Reads a message the way the transport does: `(message) => decodeRabbitMqMessage(codec, message)`. */
208
+ readonly decode: (message: RabbitMqDeliveredMessage) => EventEnvelope | Promise<EventEnvelope>;
209
+ readonly cases: readonly RabbitMqCodecCase[];
210
+ /** How bodies are compared. Default `'bytes'`. */
211
+ readonly compare?: BodyComparison;
212
+ /** Properties left out of the comparison, such as a `timestamp` that differs per send. */
213
+ readonly ignoreProperties?: readonly string[];
214
+ /** Headers left out of the comparison. */
215
+ readonly ignoreHeaders?: readonly string[];
216
+ }
217
+ /**
218
+ * What differs between `testCase` and what the codec writes and reads, one line each: the
219
+ * properties, headers and body of `encode(envelope)`, and `decode(message)` against the expected
220
+ * event. Empty when they agree.
221
+ */
222
+ declare function rabbitMqCodecMismatches(options: RabbitMqCodecContractOptions, testCase: RabbitMqCodecCase): Promise<string[]>;
223
+ /** Registers one test per case asserting {@link rabbitMqCodecMismatches} finds nothing. */
224
+ declare function rabbitMqCodecContract(name: string, options: RabbitMqCodecContractOptions): void;
225
+
226
+ export { FakeClock, InMemoryTransport, at, createDelivery, createEnvelope, eventStoreContract, kafkaCodecContract, kafkaCodecMismatches, rabbitMqCodecContract, rabbitMqCodecMismatches };
227
+ export type { BodyComparison, EventStoreContractOptions, KafkaCapturedRecord, KafkaCodecCase, KafkaCodecContractOptions, KafkaDeliveredRecord, KafkaWrittenRecord, PublishedMessage, RabbitMqCapturedMessage, RabbitMqCodecCase, RabbitMqCodecContractOptions, RabbitMqDeliveredMessage, RabbitMqWrittenMessage, WireBytes };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { EventStore, NewDelivery, EventEnvelope, Clock, Transport, TransportMessageHandler } from '@relaybus/core';
1
+ import { NewDelivery, EventEnvelope, EventStore, Clock, Transport, TransportMessageHandler } from '@relaybus/core';
2
+
3
+ declare function at(offsetMs: number): Date;
4
+ declare function createEnvelope(overrides?: Partial<EventEnvelope>): EventEnvelope;
5
+ declare function createDelivery(handler: string, availableAt?: Date): NewDelivery;
2
6
 
3
7
  interface EventStoreContractOptions<TTransaction> {
4
8
  /** Creates an empty, set-up store. Called before every test. */
@@ -18,9 +22,6 @@ interface EventStoreContractOptions<TTransaction> {
18
22
  */
19
23
  readonly keepsCompletedRecords?: boolean;
20
24
  }
21
- declare function at(offsetMs: number): Date;
22
- declare function createEnvelope(overrides?: Partial<EventEnvelope>): EventEnvelope;
23
- declare function createDelivery(handler: string, availableAt?: Date): NewDelivery;
24
25
  /**
25
26
  * Behaviour every {@link EventStore} must have. Call it inside a test file of
26
27
  * the store package:
@@ -82,4 +83,145 @@ declare class InMemoryTransport implements Transport {
82
83
  close(): Promise<void>;
83
84
  }
84
85
 
85
- export { type EventStoreContractOptions, FakeClock, InMemoryTransport, type PublishedMessage, at, createDelivery, createEnvelope, eventStoreContract };
86
+ /** How message bodies are compared: byte for byte, or as JSON regardless of key order and spacing. */
87
+ type BodyComparison = 'bytes' | 'json';
88
+ /** Bytes as text or bytes, as captured or encoded. */
89
+ type WireBytes = string | Uint8Array;
90
+
91
+ /** A record as captured from a topic: what an existing producer wrote. */
92
+ interface KafkaCapturedRecord {
93
+ readonly topic: string;
94
+ /** Default 0. */
95
+ readonly partition?: number;
96
+ /** Default `'0'`. */
97
+ readonly offset?: string;
98
+ readonly key: WireBytes | null;
99
+ readonly value: WireBytes | null;
100
+ /** Default none. */
101
+ readonly headers?: Readonly<Record<string, WireBytes>>;
102
+ /** Epoch milliseconds in a string; compared only when the codec sets a timestamp. */
103
+ readonly timestamp?: string;
104
+ }
105
+ /** What a codec's `encode` returns: a `KafkaCodec` record. */
106
+ interface KafkaWrittenRecord {
107
+ readonly key: WireBytes | null;
108
+ readonly value: WireBytes | null;
109
+ readonly headers: Readonly<Record<string, WireBytes | readonly WireBytes[]>>;
110
+ readonly timestamp?: string;
111
+ }
112
+ /** A record as a consumer hands it over: bytes for the key, the value and every header. */
113
+ interface KafkaDeliveredRecord {
114
+ readonly topic: string;
115
+ readonly partition: number;
116
+ readonly offset: string;
117
+ readonly key: Buffer | null;
118
+ readonly value: Buffer | null;
119
+ readonly headers: Readonly<Record<string, Buffer>>;
120
+ readonly timestamp?: string;
121
+ }
122
+ /** One message of the format: the event and the record that carries it. */
123
+ interface KafkaCodecCase {
124
+ readonly name: string;
125
+ readonly envelope: EventEnvelope;
126
+ readonly record: KafkaCapturedRecord;
127
+ /**
128
+ * The event decoding the record gives, when the wire does not carry all of `envelope` (for
129
+ * example a record written without a key). Default `envelope`.
130
+ */
131
+ readonly decoded?: EventEnvelope;
132
+ /**
133
+ * Checks one direction only: `'decode'` for a message only other producers write (for
134
+ * example one without an id, which relaybus would add), `'encode'` for one only other
135
+ * consumers read. Default both.
136
+ */
137
+ readonly only?: 'encode' | 'decode';
138
+ }
139
+ interface KafkaCodecContractOptions {
140
+ /** Writes an event: a codec's `encode`, or a transport's publish captured by an injected producer. */
141
+ readonly encode: (envelope: EventEnvelope) => KafkaWrittenRecord | Promise<KafkaWrittenRecord>;
142
+ /** Reads a record the way the transport does: `(record) => decodeKafkaRecord(codec, record)`. */
143
+ readonly decode: (record: KafkaDeliveredRecord) => EventEnvelope | Promise<EventEnvelope>;
144
+ readonly cases: readonly KafkaCodecCase[];
145
+ /** How values are compared. Default `'bytes'`. */
146
+ readonly compare?: BodyComparison;
147
+ /** Headers left out of the comparison, such as ones relaybus adds and other consumers ignore. */
148
+ readonly ignoreHeaders?: readonly string[];
149
+ }
150
+ /**
151
+ * What differs between `testCase` and what the codec writes and reads, one line each: the key,
152
+ * the value, the headers (a repeated header by its last value) and the timestamp of
153
+ * `encode(envelope)`, and `decode(record)` against the expected event. Empty when they agree.
154
+ */
155
+ declare function kafkaCodecMismatches(options: KafkaCodecContractOptions, testCase: KafkaCodecCase): Promise<string[]>;
156
+ /**
157
+ * Registers one test per case asserting {@link kafkaCodecMismatches} finds nothing: a format of
158
+ * existing producers and consumers is adopted only once relaybus writes and reads it exactly.
159
+ */
160
+ declare function kafkaCodecContract(name: string, options: KafkaCodecContractOptions): void;
161
+
162
+ /** A message as captured from a queue: what an existing publisher wrote. */
163
+ interface RabbitMqCapturedMessage {
164
+ readonly routingKey: string;
165
+ /**
166
+ * The message properties as publish options name them (`persistent`, `messageId`,
167
+ * `contentType`, `timestamp`, ...), without `headers`. Default none.
168
+ */
169
+ readonly properties?: Readonly<Record<string, unknown>>;
170
+ /** Default none. */
171
+ readonly headers?: Readonly<Record<string, unknown>>;
172
+ readonly body: WireBytes;
173
+ }
174
+ /** What a codec's `encode` returns: a `RabbitMqCodec` message. */
175
+ interface RabbitMqWrittenMessage {
176
+ readonly content: Uint8Array;
177
+ /** The publish options: properties, and the headers under `headers`. */
178
+ readonly options: object;
179
+ }
180
+ /** A message as a consumer receives it. */
181
+ interface RabbitMqDeliveredMessage {
182
+ readonly content: Buffer;
183
+ readonly fields: {
184
+ readonly routingKey: string;
185
+ };
186
+ readonly properties: Readonly<Record<string, unknown>> & {
187
+ readonly headers: Readonly<Record<string, unknown>>;
188
+ };
189
+ }
190
+ /** One message of the format: the event and the message that carries it. */
191
+ interface RabbitMqCodecCase {
192
+ readonly name: string;
193
+ readonly envelope: EventEnvelope;
194
+ readonly message: RabbitMqCapturedMessage;
195
+ /** The event decoding the message gives, when the wire does not carry all of `envelope`. */
196
+ readonly decoded?: EventEnvelope;
197
+ /**
198
+ * Checks one direction only: `'decode'` for a message only other producers write (for
199
+ * example one without an id, which relaybus would add), `'encode'` for one only other
200
+ * consumers read. Default both.
201
+ */
202
+ readonly only?: 'encode' | 'decode';
203
+ }
204
+ interface RabbitMqCodecContractOptions {
205
+ /** Writes an event: a codec's `encode`. The routing key is the route's destination, not the codec's. */
206
+ readonly encode: (envelope: EventEnvelope) => RabbitMqWrittenMessage | Promise<RabbitMqWrittenMessage>;
207
+ /** Reads a message the way the transport does: `(message) => decodeRabbitMqMessage(codec, message)`. */
208
+ readonly decode: (message: RabbitMqDeliveredMessage) => EventEnvelope | Promise<EventEnvelope>;
209
+ readonly cases: readonly RabbitMqCodecCase[];
210
+ /** How bodies are compared. Default `'bytes'`. */
211
+ readonly compare?: BodyComparison;
212
+ /** Properties left out of the comparison, such as a `timestamp` that differs per send. */
213
+ readonly ignoreProperties?: readonly string[];
214
+ /** Headers left out of the comparison. */
215
+ readonly ignoreHeaders?: readonly string[];
216
+ }
217
+ /**
218
+ * What differs between `testCase` and what the codec writes and reads, one line each: the
219
+ * properties, headers and body of `encode(envelope)`, and `decode(message)` against the expected
220
+ * event. Empty when they agree.
221
+ */
222
+ declare function rabbitMqCodecMismatches(options: RabbitMqCodecContractOptions, testCase: RabbitMqCodecCase): Promise<string[]>;
223
+ /** Registers one test per case asserting {@link rabbitMqCodecMismatches} finds nothing. */
224
+ declare function rabbitMqCodecContract(name: string, options: RabbitMqCodecContractOptions): void;
225
+
226
+ export { FakeClock, InMemoryTransport, at, createDelivery, createEnvelope, eventStoreContract, kafkaCodecContract, kafkaCodecMismatches, rabbitMqCodecContract, rabbitMqCodecMismatches };
227
+ export type { BodyComparison, EventStoreContractOptions, KafkaCapturedRecord, KafkaCodecCase, KafkaCodecContractOptions, KafkaDeliveredRecord, KafkaWrittenRecord, PublishedMessage, RabbitMqCapturedMessage, RabbitMqCodecCase, RabbitMqCodecContractOptions, RabbitMqDeliveredMessage, RabbitMqWrittenMessage, WireBytes };