@relaybus/testing 0.4.0 → 0.4.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"codec-contract.cjs"}
@@ -0,0 +1,178 @@
1
+ import { RelayBusError, EventEnvelope } from '@relaybus/core';
2
+
3
+ /** How message bodies are compared: byte for byte, or as JSON regardless of key order and spacing. */
4
+ type BodyComparison = 'bytes' | 'json';
5
+ /** Bytes as text or bytes, as captured or encoded. */
6
+ type WireBytes = string | Uint8Array;
7
+
8
+ /** The two functions of a test framework the codec contract registers its tests with. */
9
+ interface TestFramework {
10
+ readonly describe: (name: string, body: () => void) => void;
11
+ readonly it: (name: string, body: () => Promise<void>) => void;
12
+ }
13
+ /** Thrown by a codec contract assertion: one line per mismatch between the codec and the case. */
14
+ declare class CodecContractError extends RelayBusError {
15
+ name: string;
16
+ readonly code: string;
17
+ readonly mismatches: readonly string[];
18
+ constructor(message: string, mismatches?: readonly string[]);
19
+ }
20
+
21
+ /** A record as captured from a topic: what an existing producer wrote. */
22
+ interface KafkaCapturedRecord {
23
+ readonly topic: string;
24
+ /** Default 0. */
25
+ readonly partition?: number;
26
+ /** Default `'0'`. */
27
+ readonly offset?: string;
28
+ readonly key: WireBytes | null;
29
+ readonly value: WireBytes | null;
30
+ /** Default none. */
31
+ readonly headers?: Readonly<Record<string, WireBytes>>;
32
+ /** Epoch milliseconds in a string; compared only when the codec sets a timestamp. */
33
+ readonly timestamp?: string;
34
+ }
35
+ /** What a codec's `encode` returns: a `KafkaCodec` record. */
36
+ interface KafkaWrittenRecord {
37
+ readonly key: WireBytes | null;
38
+ readonly value: WireBytes | null;
39
+ readonly headers: Readonly<Record<string, WireBytes | readonly WireBytes[]>>;
40
+ readonly timestamp?: string;
41
+ }
42
+ /** A record as a consumer hands it over: bytes for the key, the value and every header. */
43
+ interface KafkaDeliveredRecord {
44
+ readonly topic: string;
45
+ readonly partition: number;
46
+ readonly offset: string;
47
+ readonly key: Buffer | null;
48
+ readonly value: Buffer | null;
49
+ readonly headers: Readonly<Record<string, Buffer>>;
50
+ readonly timestamp?: string;
51
+ }
52
+ /** One message of the format: the event and the record that carries it. */
53
+ interface KafkaCodecCase {
54
+ readonly name: string;
55
+ readonly envelope: EventEnvelope;
56
+ readonly record: KafkaCapturedRecord;
57
+ /**
58
+ * The event decoding the record gives, when the wire does not carry all of `envelope` (for
59
+ * example a record written without a key). Default `envelope`.
60
+ */
61
+ readonly decoded?: EventEnvelope;
62
+ /**
63
+ * Checks one direction only: `'decode'` for a message only other producers write (for
64
+ * example one without an id, which relaybus would add), `'encode'` for one only other
65
+ * consumers read. Default both.
66
+ */
67
+ readonly only?: 'encode' | 'decode';
68
+ }
69
+ interface KafkaCodecContractOptions {
70
+ /** Writes an event: a codec's `encode`, or a transport's publish captured by an injected producer. */
71
+ readonly encode: (envelope: EventEnvelope) => KafkaWrittenRecord | Promise<KafkaWrittenRecord>;
72
+ /** Reads a record the way the transport does: `(record) => decodeKafkaRecord(codec, record)`. */
73
+ readonly decode: (record: KafkaDeliveredRecord) => EventEnvelope | Promise<EventEnvelope>;
74
+ readonly cases: readonly KafkaCodecCase[];
75
+ /** How values are compared. Default `'bytes'`. */
76
+ readonly compare?: BodyComparison;
77
+ /** Headers left out of the comparison, such as ones relaybus adds and other consumers ignore. */
78
+ readonly ignoreHeaders?: readonly string[];
79
+ }
80
+ /**
81
+ * What differs between `testCase` and what the codec writes and reads, one line each: the key,
82
+ * the value, the headers (a repeated header by its last value) and the timestamp of
83
+ * `encode(envelope)`, and `decode(record)` against the expected event. Empty when they agree.
84
+ */
85
+ declare function kafkaCodecMismatches(options: KafkaCodecContractOptions, testCase: KafkaCodecCase): Promise<string[]>;
86
+ /**
87
+ * Throws a {@link CodecContractError} listing what {@link kafkaCodecMismatches} finds, so any test framework
88
+ * (or a plain script) can check one case.
89
+ */
90
+ declare function assertKafkaCodecCase(options: KafkaCodecContractOptions, testCase: KafkaCodecCase): Promise<void>;
91
+ /**
92
+ * Registers one test per case through `framework` (the `describe` and `it` of Jest, Vitest,
93
+ * Mocha or `node:test`), each asserting {@link assertKafkaCodecCase}: a format of existing
94
+ * producers and consumers is adopted only once relaybus writes and reads it exactly.
95
+ */
96
+ declare function registerKafkaCodecContract(framework: TestFramework, { name, options }: {
97
+ readonly name: string;
98
+ readonly options: KafkaCodecContractOptions;
99
+ }): void;
100
+
101
+ /** A message as captured from a queue: what an existing publisher wrote. */
102
+ interface RabbitMqCapturedMessage {
103
+ readonly routingKey: string;
104
+ /**
105
+ * The message properties as publish options name them (`persistent`, `messageId`,
106
+ * `contentType`, `timestamp`, ...), without `headers`. Default none.
107
+ */
108
+ readonly properties?: Readonly<Record<string, unknown>>;
109
+ /** Default none. */
110
+ readonly headers?: Readonly<Record<string, unknown>>;
111
+ readonly body: WireBytes;
112
+ }
113
+ /** What a codec's `encode` returns: a `RabbitMqCodec` message. */
114
+ interface RabbitMqWrittenMessage {
115
+ readonly content: Uint8Array;
116
+ /** The publish options: properties, and the headers under `headers`. */
117
+ readonly options: object;
118
+ }
119
+ /** A message as a consumer receives it. */
120
+ interface RabbitMqDeliveredMessage {
121
+ readonly content: Buffer;
122
+ readonly fields: {
123
+ readonly routingKey: string;
124
+ };
125
+ readonly properties: Readonly<Record<string, unknown>> & {
126
+ readonly headers: Readonly<Record<string, unknown>>;
127
+ };
128
+ }
129
+ /** One message of the format: the event and the message that carries it. */
130
+ interface RabbitMqCodecCase {
131
+ readonly name: string;
132
+ readonly envelope: EventEnvelope;
133
+ readonly message: RabbitMqCapturedMessage;
134
+ /** The event decoding the message gives, when the wire does not carry all of `envelope`. */
135
+ readonly decoded?: EventEnvelope;
136
+ /**
137
+ * Checks one direction only: `'decode'` for a message only other producers write (for
138
+ * example one without an id, which relaybus would add), `'encode'` for one only other
139
+ * consumers read. Default both.
140
+ */
141
+ readonly only?: 'encode' | 'decode';
142
+ }
143
+ interface RabbitMqCodecContractOptions {
144
+ /** Writes an event: a codec's `encode`. The routing key is the route's destination, not the codec's. */
145
+ readonly encode: (envelope: EventEnvelope) => RabbitMqWrittenMessage | Promise<RabbitMqWrittenMessage>;
146
+ /** Reads a message the way the transport does: `(message) => decodeRabbitMqMessage(codec, message)`. */
147
+ readonly decode: (message: RabbitMqDeliveredMessage) => EventEnvelope | Promise<EventEnvelope>;
148
+ readonly cases: readonly RabbitMqCodecCase[];
149
+ /** How bodies are compared. Default `'bytes'`. */
150
+ readonly compare?: BodyComparison;
151
+ /** Properties left out of the comparison, such as a `timestamp` that differs per send. */
152
+ readonly ignoreProperties?: readonly string[];
153
+ /** Headers left out of the comparison. */
154
+ readonly ignoreHeaders?: readonly string[];
155
+ }
156
+ /**
157
+ * What differs between `testCase` and what the codec writes and reads, one line each: the
158
+ * properties, headers and body of `encode(envelope)`, and `decode(message)` against the expected
159
+ * event. Empty when they agree.
160
+ */
161
+ declare function rabbitMqCodecMismatches(options: RabbitMqCodecContractOptions, testCase: RabbitMqCodecCase): Promise<string[]>;
162
+ /**
163
+ * Throws a {@link CodecContractError} listing what {@link rabbitMqCodecMismatches} finds, so any test framework
164
+ * (or a plain script) can check one case.
165
+ */
166
+ declare function assertRabbitMqCodecCase(options: RabbitMqCodecContractOptions, testCase: RabbitMqCodecCase): Promise<void>;
167
+ /**
168
+ * Registers one test per case through `framework` (the `describe` and `it` of Jest, Vitest,
169
+ * Mocha or `node:test`), each asserting {@link assertRabbitMqCodecCase}: a format of existing
170
+ * producers and consumers is adopted only once relaybus writes and reads it exactly.
171
+ */
172
+ declare function registerRabbitMqCodecContract(framework: TestFramework, { name, options }: {
173
+ readonly name: string;
174
+ readonly options: RabbitMqCodecContractOptions;
175
+ }): void;
176
+
177
+ export { CodecContractError, assertKafkaCodecCase, assertRabbitMqCodecCase, kafkaCodecMismatches, rabbitMqCodecMismatches, registerKafkaCodecContract, registerRabbitMqCodecContract };
178
+ export type { BodyComparison, KafkaCapturedRecord, KafkaCodecCase, KafkaCodecContractOptions, KafkaDeliveredRecord, KafkaWrittenRecord, RabbitMqCapturedMessage, RabbitMqCodecCase, RabbitMqCodecContractOptions, RabbitMqDeliveredMessage, RabbitMqWrittenMessage, TestFramework, WireBytes };
@@ -0,0 +1,178 @@
1
+ import { RelayBusError, EventEnvelope } from '@relaybus/core';
2
+
3
+ /** How message bodies are compared: byte for byte, or as JSON regardless of key order and spacing. */
4
+ type BodyComparison = 'bytes' | 'json';
5
+ /** Bytes as text or bytes, as captured or encoded. */
6
+ type WireBytes = string | Uint8Array;
7
+
8
+ /** The two functions of a test framework the codec contract registers its tests with. */
9
+ interface TestFramework {
10
+ readonly describe: (name: string, body: () => void) => void;
11
+ readonly it: (name: string, body: () => Promise<void>) => void;
12
+ }
13
+ /** Thrown by a codec contract assertion: one line per mismatch between the codec and the case. */
14
+ declare class CodecContractError extends RelayBusError {
15
+ name: string;
16
+ readonly code: string;
17
+ readonly mismatches: readonly string[];
18
+ constructor(message: string, mismatches?: readonly string[]);
19
+ }
20
+
21
+ /** A record as captured from a topic: what an existing producer wrote. */
22
+ interface KafkaCapturedRecord {
23
+ readonly topic: string;
24
+ /** Default 0. */
25
+ readonly partition?: number;
26
+ /** Default `'0'`. */
27
+ readonly offset?: string;
28
+ readonly key: WireBytes | null;
29
+ readonly value: WireBytes | null;
30
+ /** Default none. */
31
+ readonly headers?: Readonly<Record<string, WireBytes>>;
32
+ /** Epoch milliseconds in a string; compared only when the codec sets a timestamp. */
33
+ readonly timestamp?: string;
34
+ }
35
+ /** What a codec's `encode` returns: a `KafkaCodec` record. */
36
+ interface KafkaWrittenRecord {
37
+ readonly key: WireBytes | null;
38
+ readonly value: WireBytes | null;
39
+ readonly headers: Readonly<Record<string, WireBytes | readonly WireBytes[]>>;
40
+ readonly timestamp?: string;
41
+ }
42
+ /** A record as a consumer hands it over: bytes for the key, the value and every header. */
43
+ interface KafkaDeliveredRecord {
44
+ readonly topic: string;
45
+ readonly partition: number;
46
+ readonly offset: string;
47
+ readonly key: Buffer | null;
48
+ readonly value: Buffer | null;
49
+ readonly headers: Readonly<Record<string, Buffer>>;
50
+ readonly timestamp?: string;
51
+ }
52
+ /** One message of the format: the event and the record that carries it. */
53
+ interface KafkaCodecCase {
54
+ readonly name: string;
55
+ readonly envelope: EventEnvelope;
56
+ readonly record: KafkaCapturedRecord;
57
+ /**
58
+ * The event decoding the record gives, when the wire does not carry all of `envelope` (for
59
+ * example a record written without a key). Default `envelope`.
60
+ */
61
+ readonly decoded?: EventEnvelope;
62
+ /**
63
+ * Checks one direction only: `'decode'` for a message only other producers write (for
64
+ * example one without an id, which relaybus would add), `'encode'` for one only other
65
+ * consumers read. Default both.
66
+ */
67
+ readonly only?: 'encode' | 'decode';
68
+ }
69
+ interface KafkaCodecContractOptions {
70
+ /** Writes an event: a codec's `encode`, or a transport's publish captured by an injected producer. */
71
+ readonly encode: (envelope: EventEnvelope) => KafkaWrittenRecord | Promise<KafkaWrittenRecord>;
72
+ /** Reads a record the way the transport does: `(record) => decodeKafkaRecord(codec, record)`. */
73
+ readonly decode: (record: KafkaDeliveredRecord) => EventEnvelope | Promise<EventEnvelope>;
74
+ readonly cases: readonly KafkaCodecCase[];
75
+ /** How values are compared. Default `'bytes'`. */
76
+ readonly compare?: BodyComparison;
77
+ /** Headers left out of the comparison, such as ones relaybus adds and other consumers ignore. */
78
+ readonly ignoreHeaders?: readonly string[];
79
+ }
80
+ /**
81
+ * What differs between `testCase` and what the codec writes and reads, one line each: the key,
82
+ * the value, the headers (a repeated header by its last value) and the timestamp of
83
+ * `encode(envelope)`, and `decode(record)` against the expected event. Empty when they agree.
84
+ */
85
+ declare function kafkaCodecMismatches(options: KafkaCodecContractOptions, testCase: KafkaCodecCase): Promise<string[]>;
86
+ /**
87
+ * Throws a {@link CodecContractError} listing what {@link kafkaCodecMismatches} finds, so any test framework
88
+ * (or a plain script) can check one case.
89
+ */
90
+ declare function assertKafkaCodecCase(options: KafkaCodecContractOptions, testCase: KafkaCodecCase): Promise<void>;
91
+ /**
92
+ * Registers one test per case through `framework` (the `describe` and `it` of Jest, Vitest,
93
+ * Mocha or `node:test`), each asserting {@link assertKafkaCodecCase}: a format of existing
94
+ * producers and consumers is adopted only once relaybus writes and reads it exactly.
95
+ */
96
+ declare function registerKafkaCodecContract(framework: TestFramework, { name, options }: {
97
+ readonly name: string;
98
+ readonly options: KafkaCodecContractOptions;
99
+ }): void;
100
+
101
+ /** A message as captured from a queue: what an existing publisher wrote. */
102
+ interface RabbitMqCapturedMessage {
103
+ readonly routingKey: string;
104
+ /**
105
+ * The message properties as publish options name them (`persistent`, `messageId`,
106
+ * `contentType`, `timestamp`, ...), without `headers`. Default none.
107
+ */
108
+ readonly properties?: Readonly<Record<string, unknown>>;
109
+ /** Default none. */
110
+ readonly headers?: Readonly<Record<string, unknown>>;
111
+ readonly body: WireBytes;
112
+ }
113
+ /** What a codec's `encode` returns: a `RabbitMqCodec` message. */
114
+ interface RabbitMqWrittenMessage {
115
+ readonly content: Uint8Array;
116
+ /** The publish options: properties, and the headers under `headers`. */
117
+ readonly options: object;
118
+ }
119
+ /** A message as a consumer receives it. */
120
+ interface RabbitMqDeliveredMessage {
121
+ readonly content: Buffer;
122
+ readonly fields: {
123
+ readonly routingKey: string;
124
+ };
125
+ readonly properties: Readonly<Record<string, unknown>> & {
126
+ readonly headers: Readonly<Record<string, unknown>>;
127
+ };
128
+ }
129
+ /** One message of the format: the event and the message that carries it. */
130
+ interface RabbitMqCodecCase {
131
+ readonly name: string;
132
+ readonly envelope: EventEnvelope;
133
+ readonly message: RabbitMqCapturedMessage;
134
+ /** The event decoding the message gives, when the wire does not carry all of `envelope`. */
135
+ readonly decoded?: EventEnvelope;
136
+ /**
137
+ * Checks one direction only: `'decode'` for a message only other producers write (for
138
+ * example one without an id, which relaybus would add), `'encode'` for one only other
139
+ * consumers read. Default both.
140
+ */
141
+ readonly only?: 'encode' | 'decode';
142
+ }
143
+ interface RabbitMqCodecContractOptions {
144
+ /** Writes an event: a codec's `encode`. The routing key is the route's destination, not the codec's. */
145
+ readonly encode: (envelope: EventEnvelope) => RabbitMqWrittenMessage | Promise<RabbitMqWrittenMessage>;
146
+ /** Reads a message the way the transport does: `(message) => decodeRabbitMqMessage(codec, message)`. */
147
+ readonly decode: (message: RabbitMqDeliveredMessage) => EventEnvelope | Promise<EventEnvelope>;
148
+ readonly cases: readonly RabbitMqCodecCase[];
149
+ /** How bodies are compared. Default `'bytes'`. */
150
+ readonly compare?: BodyComparison;
151
+ /** Properties left out of the comparison, such as a `timestamp` that differs per send. */
152
+ readonly ignoreProperties?: readonly string[];
153
+ /** Headers left out of the comparison. */
154
+ readonly ignoreHeaders?: readonly string[];
155
+ }
156
+ /**
157
+ * What differs between `testCase` and what the codec writes and reads, one line each: the
158
+ * properties, headers and body of `encode(envelope)`, and `decode(message)` against the expected
159
+ * event. Empty when they agree.
160
+ */
161
+ declare function rabbitMqCodecMismatches(options: RabbitMqCodecContractOptions, testCase: RabbitMqCodecCase): Promise<string[]>;
162
+ /**
163
+ * Throws a {@link CodecContractError} listing what {@link rabbitMqCodecMismatches} finds, so any test framework
164
+ * (or a plain script) can check one case.
165
+ */
166
+ declare function assertRabbitMqCodecCase(options: RabbitMqCodecContractOptions, testCase: RabbitMqCodecCase): Promise<void>;
167
+ /**
168
+ * Registers one test per case through `framework` (the `describe` and `it` of Jest, Vitest,
169
+ * Mocha or `node:test`), each asserting {@link assertRabbitMqCodecCase}: a format of existing
170
+ * producers and consumers is adopted only once relaybus writes and reads it exactly.
171
+ */
172
+ declare function registerRabbitMqCodecContract(framework: TestFramework, { name, options }: {
173
+ readonly name: string;
174
+ readonly options: RabbitMqCodecContractOptions;
175
+ }): void;
176
+
177
+ export { CodecContractError, assertKafkaCodecCase, assertRabbitMqCodecCase, kafkaCodecMismatches, rabbitMqCodecMismatches, registerKafkaCodecContract, registerRabbitMqCodecContract };
178
+ export type { BodyComparison, KafkaCapturedRecord, KafkaCodecCase, KafkaCodecContractOptions, KafkaDeliveredRecord, KafkaWrittenRecord, RabbitMqCapturedMessage, RabbitMqCodecCase, RabbitMqCodecContractOptions, RabbitMqDeliveredMessage, RabbitMqWrittenMessage, TestFramework, WireBytes };
@@ -0,0 +1,3 @@
1
+ export { CodecContractError, assertKafkaCodecCase, assertRabbitMqCodecCase, kafkaCodecMismatches, rabbitMqCodecMismatches, registerKafkaCodecContract, registerRabbitMqCodecContract } from './chunk-ZZZXYZJ3.js';
2
+ //# sourceMappingURL=codec-contract.js.map
3
+ //# sourceMappingURL=codec-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"codec-contract.js"}
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ var chunkIOWTZMTG_cjs = require('./chunk-IOWTZMTG.cjs');
3
4
  var vitest = require('vitest');
4
5
  var core = require('@relaybus/core');
5
6
 
6
- // src/event-store-contract.ts
7
7
  function createDeferred() {
8
8
  let resolve;
9
9
  const promise = new Promise((onResolve) => {
@@ -1244,6 +1244,60 @@ function lostLeasesContract(suite) {
1244
1244
  });
1245
1245
  });
1246
1246
  }
1247
+ function unsortedPayload() {
1248
+ return {
1249
+ zulu: "last letter first",
1250
+ b: { yankee: [{ zz: 1, a: 2, mm: { q: null, c: true } }, [], {}], alpha: {} },
1251
+ longestKeyOfTheObject: 1,
1252
+ a: '\xE7\u011F\u0131\xF6\u015F\xFC \u{1F680} \u2028 "quoted" \\ tab ',
1253
+ exponent: 1e21,
1254
+ tiny: 5e-324,
1255
+ fraction: 0.1 + 0.2,
1256
+ negative: -42.5,
1257
+ maxSafe: Number.MAX_SAFE_INTEGER,
1258
+ minSafe: Number.MIN_SAFE_INTEGER,
1259
+ emptyObject: {},
1260
+ emptyArray: [],
1261
+ "7": "integer-like key",
1262
+ mixed: ["z", { d: 4, c: 3 }, 0, false, null]
1263
+ };
1264
+ }
1265
+ function payloadFidelityContract(suite) {
1266
+ const { append, claim } = suite;
1267
+ vitest.describe("payload fidelity", () => {
1268
+ vitest.it("hands out the payload and headers with their key order, as published", async () => {
1269
+ const event = createEnvelope({
1270
+ payload: unsortedPayload(),
1271
+ headers: { zz: "last", traceparent: "00-abc-def-01", a: "first", tenant: "acme" }
1272
+ });
1273
+ await append([{ event, deliveries: [createDelivery("handler-a")] }]);
1274
+ const [claimed] = await claim(["handler-a"]);
1275
+ vitest.expect(JSON.stringify(claimed.event.payload)).toBe(JSON.stringify(event.payload));
1276
+ vitest.expect(JSON.stringify(claimed.event.headers)).toBe(JSON.stringify(event.headers));
1277
+ });
1278
+ vitest.it("keeps the order of a payload appended in a batch and of a top-level array", async () => {
1279
+ const object = createEnvelope({ payload: unsortedPayload() });
1280
+ const list = createEnvelope({ payload: [{ zz: 1, a: { y: 2, b: 1 } }, "x", 1e21] });
1281
+ const scalar = createEnvelope({ payload: "plain text" });
1282
+ await append([
1283
+ { event: object, deliveries: [createDelivery("handler-a", at(1))] },
1284
+ { event: list, deliveries: [createDelivery("handler-a", at(2))] },
1285
+ { event: scalar, deliveries: [createDelivery("handler-a", at(3))] }
1286
+ ]);
1287
+ const claimed = await claim(["handler-a"], at(10));
1288
+ vitest.expect(claimed.map((delivery) => JSON.stringify(delivery.event.payload))).toEqual(
1289
+ [object, list, scalar].map((event) => JSON.stringify(event.payload))
1290
+ );
1291
+ });
1292
+ vitest.it("keeps the order for a handler added to an event that already exists", async () => {
1293
+ const event = createEnvelope({ payload: unsortedPayload() });
1294
+ await append([{ event, deliveries: [createDelivery("handler-a")] }]);
1295
+ await append([{ event, deliveries: [createDelivery("handler-b")] }]);
1296
+ const [claimed] = await claim(["handler-b"]);
1297
+ vitest.expect(JSON.stringify(claimed.event.payload)).toBe(JSON.stringify(event.payload));
1298
+ });
1299
+ });
1300
+ }
1247
1301
 
1248
1302
  // src/event-store-contract.ts
1249
1303
  function eventStoreContract(name, options) {
@@ -1292,6 +1346,7 @@ function eventStoreContract(name, options) {
1292
1346
  orderingAndLeasesContract(suite);
1293
1347
  completionContract(suite);
1294
1348
  lostLeasesContract(suite);
1349
+ payloadFidelityContract(suite);
1295
1350
  });
1296
1351
  }
1297
1352
 
@@ -1406,186 +1461,52 @@ var InMemoryTransport = class {
1406
1461
  }
1407
1462
  };
1408
1463
  async function deliverWithRedeliveries(handler, delivery) {
1409
- for (let attempt2 = 0; ; attempt2 += 1) {
1464
+ for (let attempt = 0; ; attempt += 1) {
1410
1465
  try {
1411
1466
  await handler(delivery.envelope, { destination: delivery.destination });
1412
- return attempt2 + 1;
1467
+ return attempt + 1;
1413
1468
  } catch (error) {
1414
- if (attempt2 >= delivery.maxRedeliveries) {
1469
+ if (attempt >= delivery.maxRedeliveries) {
1415
1470
  throw error;
1416
1471
  }
1417
1472
  }
1418
1473
  }
1419
1474
  }
1420
- function toBuffer(bytes) {
1421
- return typeof bytes === "string" ? Buffer.from(bytes, "utf8") : Buffer.from(bytes);
1422
- }
1423
- function canonicalJson(value) {
1424
- return JSON.stringify(sortKeys(JSON.parse(JSON.stringify(value) ?? "null")));
1425
- }
1426
- function sortKeys(value) {
1427
- if (Array.isArray(value)) {
1428
- return value.map(sortKeys);
1429
- }
1430
- if (typeof value !== "object" || value === null) {
1431
- return value;
1432
- }
1433
- return Object.fromEntries(
1434
- Object.keys(value).sort().map((name) => [name, sortKeys(value[name])])
1435
- );
1436
- }
1437
- function compareBodies(label, {
1438
- expected,
1439
- encoded,
1440
- comparison
1441
- }) {
1442
- const text = (bytes) => bytes === null ? "null" : toBuffer(bytes).toString("utf8");
1443
- const same = expected === null || encoded === null ? expected === encoded : comparison === "json" ? canonicalJson(JSON.parse(text(expected))) === canonicalJson(JSON.parse(text(encoded))) : toBuffer(expected).equals(toBuffer(encoded));
1444
- return same ? [] : [`${label}: expected ${text(expected)}, encoded ${text(encoded)}`];
1445
- }
1446
- function compareObjects(label, {
1447
- expected,
1448
- encoded,
1449
- ignored
1450
- }) {
1451
- const kept = (object) => canonicalJson(
1452
- Object.fromEntries(Object.entries(object).filter(([name]) => !ignored.includes(name)))
1453
- );
1454
- const [left, right] = [kept(expected), kept(encoded)];
1455
- return left === right ? [] : [`${label}: expected ${left}, encoded ${right}`];
1456
- }
1457
- function compareDecoded(expected, decoded) {
1458
- const [left, right] = [canonicalJson(expected), canonicalJson(decoded)];
1459
- return left === right ? [] : [`decoded: expected ${left}, decoded ${right}`];
1460
- }
1461
- async function attempt(label, step) {
1462
- try {
1463
- return { result: await step(), mismatches: [] };
1464
- } catch (error) {
1465
- return { mismatches: [`${label} threw: ${core.toErrorInfo(error).message}`] };
1466
- }
1467
- }
1468
-
1469
- // src/codec-contract/kafka.ts
1470
- async function kafkaCodecMismatches(options, testCase) {
1471
- const { record } = testCase;
1472
- const encoding = testCase.only === "decode" ? { mismatches: [] } : await attempt("encode", () => options.encode(testCase.envelope));
1473
- const encoded = encoding.result;
1474
- const decoding = testCase.only === "encode" ? { mismatches: [] } : await attempt("decode", () => options.decode(delivered(record)));
1475
- return [
1476
- ...encoding.mismatches,
1477
- ...encoded === void 0 ? [] : compareRecords(options, { record, encoded }),
1478
- ...decoding.mismatches,
1479
- ...decoding.result === void 0 ? [] : compareDecoded(testCase.decoded ?? testCase.envelope, decoding.result)
1480
- ];
1481
- }
1482
1475
  function kafkaCodecContract(name, options) {
1483
- vitest.describe(`${name} writes and reads the captured Kafka records`, () => {
1484
- vitest.it.each(options.cases.map((testCase) => [testCase.name, testCase]))(
1485
- "%s",
1486
- async (_name, testCase) => {
1487
- vitest.expect(await kafkaCodecMismatches(options, testCase)).toEqual([]);
1488
- }
1489
- );
1490
- });
1491
- }
1492
- function compareRecords(options, {
1493
- record,
1494
- encoded
1495
- }) {
1496
- const text = (bytes) => bytes === null ? null : toBuffer(bytes).toString("utf8");
1497
- const timestamp = encoded.timestamp;
1498
- return [
1499
- ...text(record.key) === text(encoded.key) ? [] : [`key: expected ${text(record.key)}, encoded ${text(encoded.key)}`],
1500
- ...compareBodies("value", {
1501
- expected: record.value,
1502
- encoded: encoded.value,
1503
- comparison: options.compare ?? "bytes"
1504
- }),
1505
- ...compareObjects("headers", {
1506
- expected: textHeaders(record.headers ?? {}),
1507
- encoded: textHeaders(encoded.headers),
1508
- ignored: options.ignoreHeaders ?? []
1509
- }),
1510
- ...timestamp === void 0 || timestamp === record.timestamp ? [] : [`timestamp: expected ${record.timestamp}, encoded ${timestamp}`]
1511
- ];
1512
- }
1513
- function textHeaders(headers) {
1514
- return Object.fromEntries(
1515
- Object.entries(headers).map(([name, value]) => [
1516
- name,
1517
- toBuffer(Array.isArray(value) ? value.at(-1) : value).toString(
1518
- "utf8"
1519
- )
1520
- ])
1521
- );
1522
- }
1523
- function delivered(record) {
1524
- return {
1525
- topic: record.topic,
1526
- partition: record.partition ?? 0,
1527
- offset: record.offset ?? "0",
1528
- key: record.key === null ? null : toBuffer(record.key),
1529
- value: record.value === null ? null : toBuffer(record.value),
1530
- headers: Object.fromEntries(
1531
- Object.entries(record.headers ?? {}).map(([name, value]) => [name, toBuffer(value)])
1532
- ),
1533
- ...record.timestamp === void 0 ? {} : { timestamp: record.timestamp }
1534
- };
1535
- }
1536
- async function rabbitMqCodecMismatches(options, testCase) {
1537
- const { message } = testCase;
1538
- const encoding = testCase.only === "decode" ? { mismatches: [] } : await attempt("encode", () => options.encode(testCase.envelope));
1539
- const encoded = encoding.result;
1540
- const decoding = testCase.only === "encode" ? { mismatches: [] } : await attempt("decode", () => options.decode(delivered2(message)));
1541
- return [
1542
- ...encoding.mismatches,
1543
- ...encoded === void 0 ? [] : compareMessages(options, { message, encoded }),
1544
- ...decoding.mismatches,
1545
- ...decoding.result === void 0 ? [] : compareDecoded(testCase.decoded ?? testCase.envelope, decoding.result)
1546
- ];
1476
+ chunkIOWTZMTG_cjs.registerKafkaCodecContract({ describe: vitest.describe, it: vitest.it }, { name, options });
1547
1477
  }
1548
1478
  function rabbitMqCodecContract(name, options) {
1549
- vitest.describe(`${name} writes and reads the captured RabbitMQ messages`, () => {
1550
- vitest.it.each(options.cases.map((testCase) => [testCase.name, testCase]))(
1551
- "%s",
1552
- async (_name, testCase) => {
1553
- vitest.expect(await rabbitMqCodecMismatches(options, testCase)).toEqual([]);
1554
- }
1555
- );
1556
- });
1557
- }
1558
- function compareMessages(options, {
1559
- message,
1560
- encoded
1561
- }) {
1562
- const { headers, ...properties } = encoded.options;
1563
- return [
1564
- ...compareObjects("properties", {
1565
- expected: message.properties ?? {},
1566
- encoded: properties,
1567
- ignored: options.ignoreProperties ?? []
1568
- }),
1569
- ...compareObjects("headers", {
1570
- expected: message.headers ?? {},
1571
- encoded: headers ?? {},
1572
- ignored: options.ignoreHeaders ?? []
1573
- }),
1574
- ...compareBodies("body", {
1575
- expected: message.body,
1576
- encoded: encoded.content,
1577
- comparison: options.compare ?? "bytes"
1578
- })
1579
- ];
1580
- }
1581
- function delivered2(message) {
1582
- return {
1583
- content: toBuffer(message.body),
1584
- fields: { routingKey: message.routingKey },
1585
- properties: { ...message.properties, headers: { ...message.headers } }
1586
- };
1479
+ chunkIOWTZMTG_cjs.registerRabbitMqCodecContract({ describe: vitest.describe, it: vitest.it }, { name, options });
1587
1480
  }
1588
1481
 
1482
+ Object.defineProperty(exports, "CodecContractError", {
1483
+ enumerable: true,
1484
+ get: function () { return chunkIOWTZMTG_cjs.CodecContractError; }
1485
+ });
1486
+ Object.defineProperty(exports, "assertKafkaCodecCase", {
1487
+ enumerable: true,
1488
+ get: function () { return chunkIOWTZMTG_cjs.assertKafkaCodecCase; }
1489
+ });
1490
+ Object.defineProperty(exports, "assertRabbitMqCodecCase", {
1491
+ enumerable: true,
1492
+ get: function () { return chunkIOWTZMTG_cjs.assertRabbitMqCodecCase; }
1493
+ });
1494
+ Object.defineProperty(exports, "kafkaCodecMismatches", {
1495
+ enumerable: true,
1496
+ get: function () { return chunkIOWTZMTG_cjs.kafkaCodecMismatches; }
1497
+ });
1498
+ Object.defineProperty(exports, "rabbitMqCodecMismatches", {
1499
+ enumerable: true,
1500
+ get: function () { return chunkIOWTZMTG_cjs.rabbitMqCodecMismatches; }
1501
+ });
1502
+ Object.defineProperty(exports, "registerKafkaCodecContract", {
1503
+ enumerable: true,
1504
+ get: function () { return chunkIOWTZMTG_cjs.registerKafkaCodecContract; }
1505
+ });
1506
+ Object.defineProperty(exports, "registerRabbitMqCodecContract", {
1507
+ enumerable: true,
1508
+ get: function () { return chunkIOWTZMTG_cjs.registerRabbitMqCodecContract; }
1509
+ });
1589
1510
  exports.FakeClock = FakeClock;
1590
1511
  exports.InMemoryTransport = InMemoryTransport;
1591
1512
  exports.at = at;
@@ -1593,8 +1514,6 @@ exports.createDelivery = createDelivery;
1593
1514
  exports.createEnvelope = createEnvelope;
1594
1515
  exports.eventStoreContract = eventStoreContract;
1595
1516
  exports.kafkaCodecContract = kafkaCodecContract;
1596
- exports.kafkaCodecMismatches = kafkaCodecMismatches;
1597
1517
  exports.rabbitMqCodecContract = rabbitMqCodecContract;
1598
- exports.rabbitMqCodecMismatches = rabbitMqCodecMismatches;
1599
1518
  //# sourceMappingURL=index.cjs.map
1600
1519
  //# sourceMappingURL=index.cjs.map