@durable-streams/client-conformance-tests 0.1.5 → 0.1.7
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/adapters/typescript-adapter.cjs +75 -2
- package/dist/adapters/typescript-adapter.js +76 -3
- package/dist/{benchmark-runner-C_Yghc8f.js → benchmark-runner-CrE6JkbX.js} +106 -12
- package/dist/{benchmark-runner-CLAR9oLd.cjs → benchmark-runner-Db4he452.cjs} +107 -12
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +126 -11
- package/dist/index.d.ts +126 -11
- package/dist/index.js +1 -1
- package/dist/{protocol-3cf94Xyb.d.cts → protocol-D37G3c4e.d.cts} +80 -4
- package/dist/{protocol-DyEvTHPF.d.ts → protocol-Mcbiq3nQ.d.ts} +80 -4
- package/dist/protocol.d.cts +2 -2
- package/dist/protocol.d.ts +2 -2
- package/package.json +3 -3
- package/src/adapters/typescript-adapter.ts +127 -5
- package/src/protocol.ts +85 -1
- package/src/runner.ts +202 -17
- package/src/test-cases.ts +130 -8
- package/test-cases/consumer/error-handling.yaml +42 -0
- package/test-cases/consumer/fault-injection.yaml +202 -0
- package/test-cases/consumer/offset-handling.yaml +209 -0
- package/test-cases/producer/idempotent/autoclaim.yaml +214 -0
- package/test-cases/producer/idempotent/batching.yaml +98 -0
- package/test-cases/producer/idempotent/concurrent-requests.yaml +100 -0
- package/test-cases/producer/idempotent/epoch-management.yaml +333 -0
- package/test-cases/producer/idempotent/error-handling.yaml +194 -0
- package/test-cases/producer/idempotent/multi-producer.yaml +322 -0
- package/test-cases/producer/idempotent/sequence-validation.yaml +339 -0
- package/test-cases/producer/idempotent-json-batching.yaml +134 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
id: idempotent-sequence-validation
|
|
2
|
+
name: Idempotent Producer - Sequence Validation
|
|
3
|
+
description: |
|
|
4
|
+
Tests for producer sequence number validation.
|
|
5
|
+
Sequences must start at 0 and increment monotonically within an epoch.
|
|
6
|
+
category: producer
|
|
7
|
+
tags:
|
|
8
|
+
- idempotent
|
|
9
|
+
- sequence
|
|
10
|
+
|
|
11
|
+
tests:
|
|
12
|
+
- id: first-append-accepted
|
|
13
|
+
name: First append with producer headers is accepted
|
|
14
|
+
description: Server should accept first append with epoch=0, seq=0
|
|
15
|
+
setup:
|
|
16
|
+
- action: create
|
|
17
|
+
as: streamPath
|
|
18
|
+
contentType: text/plain
|
|
19
|
+
operations:
|
|
20
|
+
- action: server-append
|
|
21
|
+
path: ${streamPath}
|
|
22
|
+
data: "hello"
|
|
23
|
+
producerId: test-producer
|
|
24
|
+
producerEpoch: 0
|
|
25
|
+
producerSeq: 0
|
|
26
|
+
expect:
|
|
27
|
+
status: 200
|
|
28
|
+
producerEpoch: 0
|
|
29
|
+
|
|
30
|
+
- id: sequential-sequences
|
|
31
|
+
name: Sequential producer sequences accepted
|
|
32
|
+
description: Server should accept monotonically increasing sequences
|
|
33
|
+
setup:
|
|
34
|
+
- action: create
|
|
35
|
+
as: streamPath
|
|
36
|
+
contentType: text/plain
|
|
37
|
+
operations:
|
|
38
|
+
- action: server-append
|
|
39
|
+
path: ${streamPath}
|
|
40
|
+
data: "msg0"
|
|
41
|
+
producerId: test-producer
|
|
42
|
+
producerEpoch: 0
|
|
43
|
+
producerSeq: 0
|
|
44
|
+
expect:
|
|
45
|
+
status: 200
|
|
46
|
+
- action: server-append
|
|
47
|
+
path: ${streamPath}
|
|
48
|
+
data: "msg1"
|
|
49
|
+
producerId: test-producer
|
|
50
|
+
producerEpoch: 0
|
|
51
|
+
producerSeq: 1
|
|
52
|
+
expect:
|
|
53
|
+
status: 200
|
|
54
|
+
- action: server-append
|
|
55
|
+
path: ${streamPath}
|
|
56
|
+
data: "msg2"
|
|
57
|
+
producerId: test-producer
|
|
58
|
+
producerEpoch: 0
|
|
59
|
+
producerSeq: 2
|
|
60
|
+
expect:
|
|
61
|
+
status: 200
|
|
62
|
+
|
|
63
|
+
- id: sequence-gap-rejected
|
|
64
|
+
name: Sequence gap returns 409
|
|
65
|
+
description: Skipping a sequence number should return 409 with expected/received seq
|
|
66
|
+
setup:
|
|
67
|
+
- action: create
|
|
68
|
+
as: streamPath
|
|
69
|
+
contentType: text/plain
|
|
70
|
+
operations:
|
|
71
|
+
- action: server-append
|
|
72
|
+
path: ${streamPath}
|
|
73
|
+
data: "msg0"
|
|
74
|
+
producerId: test-producer
|
|
75
|
+
producerEpoch: 0
|
|
76
|
+
producerSeq: 0
|
|
77
|
+
expect:
|
|
78
|
+
status: 200
|
|
79
|
+
# Skip seq=1, try seq=2
|
|
80
|
+
- action: server-append
|
|
81
|
+
path: ${streamPath}
|
|
82
|
+
data: "msg2"
|
|
83
|
+
producerId: test-producer
|
|
84
|
+
producerEpoch: 0
|
|
85
|
+
producerSeq: 2
|
|
86
|
+
expect:
|
|
87
|
+
status: 409
|
|
88
|
+
producerExpectedSeq: 1
|
|
89
|
+
producerReceivedSeq: 2
|
|
90
|
+
|
|
91
|
+
- id: new-producer-seq-not-zero
|
|
92
|
+
name: New producer starting with seq > 0 returns 409
|
|
93
|
+
description: A never-seen producer sending seq > 0 should get 409 (gap), not 400
|
|
94
|
+
setup:
|
|
95
|
+
- action: create
|
|
96
|
+
as: streamPath
|
|
97
|
+
contentType: text/plain
|
|
98
|
+
operations:
|
|
99
|
+
- action: server-append
|
|
100
|
+
path: ${streamPath}
|
|
101
|
+
data: "msg"
|
|
102
|
+
producerId: brand-new-producer
|
|
103
|
+
producerEpoch: 0
|
|
104
|
+
producerSeq: 5
|
|
105
|
+
expect:
|
|
106
|
+
status: 409
|
|
107
|
+
producerExpectedSeq: 0
|
|
108
|
+
producerReceivedSeq: 5
|
|
109
|
+
|
|
110
|
+
- id: duplicate-returns-204
|
|
111
|
+
name: Duplicate sequence returns 204 (idempotent success)
|
|
112
|
+
description: Retrying the same seq should return 204 without duplicating data
|
|
113
|
+
setup:
|
|
114
|
+
- action: create
|
|
115
|
+
as: streamPath
|
|
116
|
+
contentType: text/plain
|
|
117
|
+
operations:
|
|
118
|
+
- action: server-append
|
|
119
|
+
path: ${streamPath}
|
|
120
|
+
data: "hello"
|
|
121
|
+
producerId: test-producer
|
|
122
|
+
producerEpoch: 0
|
|
123
|
+
producerSeq: 0
|
|
124
|
+
expect:
|
|
125
|
+
status: 200
|
|
126
|
+
- action: server-append
|
|
127
|
+
path: ${streamPath}
|
|
128
|
+
data: "hello"
|
|
129
|
+
producerId: test-producer
|
|
130
|
+
producerEpoch: 0
|
|
131
|
+
producerSeq: 0
|
|
132
|
+
expect:
|
|
133
|
+
status: 204
|
|
134
|
+
duplicate: true
|
|
135
|
+
# Verify only one message in stream
|
|
136
|
+
- action: read
|
|
137
|
+
path: ${streamPath}
|
|
138
|
+
expect:
|
|
139
|
+
data: "hello"
|
|
140
|
+
upToDate: true
|
|
141
|
+
|
|
142
|
+
- id: duplicate-seq-zero-preserves-state
|
|
143
|
+
name: Duplicate of seq=0 does not corrupt state
|
|
144
|
+
description: Retrying seq=0 should not affect subsequent sequence tracking
|
|
145
|
+
setup:
|
|
146
|
+
- action: create
|
|
147
|
+
as: streamPath
|
|
148
|
+
contentType: text/plain
|
|
149
|
+
operations:
|
|
150
|
+
- action: server-append
|
|
151
|
+
path: ${streamPath}
|
|
152
|
+
data: "first"
|
|
153
|
+
producerId: test-producer
|
|
154
|
+
producerEpoch: 0
|
|
155
|
+
producerSeq: 0
|
|
156
|
+
expect:
|
|
157
|
+
status: 200
|
|
158
|
+
# Retry seq=0
|
|
159
|
+
- action: server-append
|
|
160
|
+
path: ${streamPath}
|
|
161
|
+
data: "first"
|
|
162
|
+
producerId: test-producer
|
|
163
|
+
producerEpoch: 0
|
|
164
|
+
producerSeq: 0
|
|
165
|
+
expect:
|
|
166
|
+
status: 204
|
|
167
|
+
# seq=1 should still work
|
|
168
|
+
- action: server-append
|
|
169
|
+
path: ${streamPath}
|
|
170
|
+
data: "second"
|
|
171
|
+
producerId: test-producer
|
|
172
|
+
producerEpoch: 0
|
|
173
|
+
producerSeq: 1
|
|
174
|
+
expect:
|
|
175
|
+
status: 200
|
|
176
|
+
|
|
177
|
+
- id: duplicate-ignores-different-data
|
|
178
|
+
name: Duplicate seq ignores payload differences
|
|
179
|
+
description: Same (producer, epoch, seq) returns 204 even with different data - dedup is by headers only
|
|
180
|
+
setup:
|
|
181
|
+
- action: create
|
|
182
|
+
as: streamPath
|
|
183
|
+
contentType: text/plain
|
|
184
|
+
operations:
|
|
185
|
+
- action: server-append
|
|
186
|
+
path: ${streamPath}
|
|
187
|
+
data: "original payload"
|
|
188
|
+
producerId: test-producer
|
|
189
|
+
producerEpoch: 0
|
|
190
|
+
producerSeq: 0
|
|
191
|
+
expect:
|
|
192
|
+
status: 200
|
|
193
|
+
# Retry with different data - should still be 204 (idempotent)
|
|
194
|
+
- action: server-append
|
|
195
|
+
path: ${streamPath}
|
|
196
|
+
data: "different payload"
|
|
197
|
+
producerId: test-producer
|
|
198
|
+
producerEpoch: 0
|
|
199
|
+
producerSeq: 0
|
|
200
|
+
expect:
|
|
201
|
+
status: 204
|
|
202
|
+
duplicate: true
|
|
203
|
+
# Verify only original data is in stream
|
|
204
|
+
- action: read
|
|
205
|
+
path: ${streamPath}
|
|
206
|
+
expect:
|
|
207
|
+
data: "original payload"
|
|
208
|
+
upToDate: true
|
|
209
|
+
|
|
210
|
+
- id: ordering-preserved-in-stream
|
|
211
|
+
name: Message ordering preserved in stream
|
|
212
|
+
description: Messages from idempotent producer maintain sequence order
|
|
213
|
+
setup:
|
|
214
|
+
- action: create
|
|
215
|
+
as: streamPath
|
|
216
|
+
contentType: application/json
|
|
217
|
+
operations:
|
|
218
|
+
- action: server-append
|
|
219
|
+
path: ${streamPath}
|
|
220
|
+
data: '{"seq": 0}'
|
|
221
|
+
producerId: order-test
|
|
222
|
+
producerEpoch: 0
|
|
223
|
+
producerSeq: 0
|
|
224
|
+
expect:
|
|
225
|
+
status: 200
|
|
226
|
+
- action: server-append
|
|
227
|
+
path: ${streamPath}
|
|
228
|
+
data: '{"seq": 1}'
|
|
229
|
+
producerId: order-test
|
|
230
|
+
producerEpoch: 0
|
|
231
|
+
producerSeq: 1
|
|
232
|
+
expect:
|
|
233
|
+
status: 200
|
|
234
|
+
- action: server-append
|
|
235
|
+
path: ${streamPath}
|
|
236
|
+
data: '{"seq": 2}'
|
|
237
|
+
producerId: order-test
|
|
238
|
+
producerEpoch: 0
|
|
239
|
+
producerSeq: 2
|
|
240
|
+
expect:
|
|
241
|
+
status: 200
|
|
242
|
+
- action: read
|
|
243
|
+
path: ${streamPath}
|
|
244
|
+
expect:
|
|
245
|
+
dataContainsAll:
|
|
246
|
+
- '"seq":0'
|
|
247
|
+
- '"seq":1'
|
|
248
|
+
- '"seq":2'
|
|
249
|
+
upToDate: true
|
|
250
|
+
|
|
251
|
+
- id: duplicate-old-sequence
|
|
252
|
+
name: Duplicate of old (non-tail) sequence returns 204
|
|
253
|
+
description: |
|
|
254
|
+
Retrying a sequence far back in history should still return 204.
|
|
255
|
+
This tests that the server tracks more than just the last sequence.
|
|
256
|
+
(Kafka distinguishes DuplicateSequenceException vs OutOfOrderSequenceException)
|
|
257
|
+
setup:
|
|
258
|
+
- action: create
|
|
259
|
+
as: streamPath
|
|
260
|
+
contentType: text/plain
|
|
261
|
+
operations:
|
|
262
|
+
# Send sequences 0-5
|
|
263
|
+
- action: server-append
|
|
264
|
+
path: ${streamPath}
|
|
265
|
+
data: "msg0"
|
|
266
|
+
producerId: test-producer
|
|
267
|
+
producerEpoch: 0
|
|
268
|
+
producerSeq: 0
|
|
269
|
+
expect:
|
|
270
|
+
status: 200
|
|
271
|
+
- action: server-append
|
|
272
|
+
path: ${streamPath}
|
|
273
|
+
data: "msg1"
|
|
274
|
+
producerId: test-producer
|
|
275
|
+
producerEpoch: 0
|
|
276
|
+
producerSeq: 1
|
|
277
|
+
expect:
|
|
278
|
+
status: 200
|
|
279
|
+
- action: server-append
|
|
280
|
+
path: ${streamPath}
|
|
281
|
+
data: "msg2"
|
|
282
|
+
producerId: test-producer
|
|
283
|
+
producerEpoch: 0
|
|
284
|
+
producerSeq: 2
|
|
285
|
+
expect:
|
|
286
|
+
status: 200
|
|
287
|
+
- action: server-append
|
|
288
|
+
path: ${streamPath}
|
|
289
|
+
data: "msg3"
|
|
290
|
+
producerId: test-producer
|
|
291
|
+
producerEpoch: 0
|
|
292
|
+
producerSeq: 3
|
|
293
|
+
expect:
|
|
294
|
+
status: 200
|
|
295
|
+
- action: server-append
|
|
296
|
+
path: ${streamPath}
|
|
297
|
+
data: "msg4"
|
|
298
|
+
producerId: test-producer
|
|
299
|
+
producerEpoch: 0
|
|
300
|
+
producerSeq: 4
|
|
301
|
+
expect:
|
|
302
|
+
status: 200
|
|
303
|
+
- action: server-append
|
|
304
|
+
path: ${streamPath}
|
|
305
|
+
data: "msg5"
|
|
306
|
+
producerId: test-producer
|
|
307
|
+
producerEpoch: 0
|
|
308
|
+
producerSeq: 5
|
|
309
|
+
expect:
|
|
310
|
+
status: 200
|
|
311
|
+
# Retry seq=1 (old, non-tail sequence)
|
|
312
|
+
- action: server-append
|
|
313
|
+
path: ${streamPath}
|
|
314
|
+
data: "msg1"
|
|
315
|
+
producerId: test-producer
|
|
316
|
+
producerEpoch: 0
|
|
317
|
+
producerSeq: 1
|
|
318
|
+
expect:
|
|
319
|
+
status: 204
|
|
320
|
+
duplicate: true
|
|
321
|
+
# Retry seq=0 (first sequence)
|
|
322
|
+
- action: server-append
|
|
323
|
+
path: ${streamPath}
|
|
324
|
+
data: "msg0"
|
|
325
|
+
producerId: test-producer
|
|
326
|
+
producerEpoch: 0
|
|
327
|
+
producerSeq: 0
|
|
328
|
+
expect:
|
|
329
|
+
status: 204
|
|
330
|
+
duplicate: true
|
|
331
|
+
# New sequence should still work
|
|
332
|
+
- action: server-append
|
|
333
|
+
path: ${streamPath}
|
|
334
|
+
data: "msg6"
|
|
335
|
+
producerId: test-producer
|
|
336
|
+
producerEpoch: 0
|
|
337
|
+
producerSeq: 6
|
|
338
|
+
expect:
|
|
339
|
+
status: 200
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
id: idempotent-json-batching
|
|
2
|
+
name: Idempotent Producer JSON Batching
|
|
3
|
+
description: Tests for client-side JSON batching in IdempotentProducer
|
|
4
|
+
category: producer
|
|
5
|
+
tags:
|
|
6
|
+
- idempotent
|
|
7
|
+
- json
|
|
8
|
+
- batching
|
|
9
|
+
|
|
10
|
+
tests:
|
|
11
|
+
- id: json-batch-single-item
|
|
12
|
+
name: Single JSON item via IdempotentProducer
|
|
13
|
+
description: A single JSON value should be wrapped in array and stored correctly
|
|
14
|
+
setup:
|
|
15
|
+
- action: create
|
|
16
|
+
as: streamPath
|
|
17
|
+
contentType: application/json
|
|
18
|
+
operations:
|
|
19
|
+
- action: idempotent-append
|
|
20
|
+
path: ${streamPath}
|
|
21
|
+
producerId: test-producer
|
|
22
|
+
epoch: 0
|
|
23
|
+
data: '{"message": "hello"}'
|
|
24
|
+
expect:
|
|
25
|
+
status: 200
|
|
26
|
+
- action: read
|
|
27
|
+
path: ${streamPath}
|
|
28
|
+
expect:
|
|
29
|
+
# JSON streams return data as array
|
|
30
|
+
data: '[{"message":"hello"}]'
|
|
31
|
+
upToDate: true
|
|
32
|
+
|
|
33
|
+
- id: json-batch-multiple-items
|
|
34
|
+
name: Multiple JSON items batched via IdempotentProducer
|
|
35
|
+
description: Multiple JSON values should be batched into single request and stored as separate entries
|
|
36
|
+
setup:
|
|
37
|
+
- action: create
|
|
38
|
+
as: streamPath
|
|
39
|
+
contentType: application/json
|
|
40
|
+
operations:
|
|
41
|
+
- action: idempotent-append-batch
|
|
42
|
+
path: ${streamPath}
|
|
43
|
+
producerId: test-producer
|
|
44
|
+
epoch: 0
|
|
45
|
+
items:
|
|
46
|
+
- data: '{"id": 1}'
|
|
47
|
+
- data: '{"id": 2}'
|
|
48
|
+
- data: '{"id": 3}'
|
|
49
|
+
expect:
|
|
50
|
+
allSucceed: true
|
|
51
|
+
- action: read
|
|
52
|
+
path: ${streamPath}
|
|
53
|
+
expect:
|
|
54
|
+
# Each item should be stored separately, read back as JSON array
|
|
55
|
+
dataContainsAll:
|
|
56
|
+
- '"id":1'
|
|
57
|
+
- '"id":2'
|
|
58
|
+
- '"id":3'
|
|
59
|
+
upToDate: true
|
|
60
|
+
|
|
61
|
+
- id: json-batch-nested-arrays
|
|
62
|
+
name: JSON arrays batched correctly
|
|
63
|
+
description: Batched JSON arrays should each be stored as single entries, not flattened further
|
|
64
|
+
setup:
|
|
65
|
+
- action: create
|
|
66
|
+
as: streamPath
|
|
67
|
+
contentType: application/json
|
|
68
|
+
operations:
|
|
69
|
+
- action: idempotent-append-batch
|
|
70
|
+
path: ${streamPath}
|
|
71
|
+
producerId: test-producer
|
|
72
|
+
epoch: 0
|
|
73
|
+
items:
|
|
74
|
+
- data: "[1, 2, 3]"
|
|
75
|
+
- data: "[4, 5, 6]"
|
|
76
|
+
expect:
|
|
77
|
+
allSucceed: true
|
|
78
|
+
- action: read
|
|
79
|
+
path: ${streamPath}
|
|
80
|
+
expect:
|
|
81
|
+
# Each array should be stored as a single entry (JSON normalized without spaces)
|
|
82
|
+
dataContainsAll:
|
|
83
|
+
- "[1,2,3]"
|
|
84
|
+
- "[4,5,6]"
|
|
85
|
+
upToDate: true
|
|
86
|
+
|
|
87
|
+
- id: json-batch-mixed-types
|
|
88
|
+
name: Mixed JSON types batched correctly
|
|
89
|
+
description: Batched values of different JSON types should each be stored correctly
|
|
90
|
+
setup:
|
|
91
|
+
- action: create
|
|
92
|
+
as: streamPath
|
|
93
|
+
contentType: application/json
|
|
94
|
+
operations:
|
|
95
|
+
- action: idempotent-append-batch
|
|
96
|
+
path: ${streamPath}
|
|
97
|
+
producerId: test-producer
|
|
98
|
+
epoch: 0
|
|
99
|
+
items:
|
|
100
|
+
- data: "42"
|
|
101
|
+
- data: '"string"'
|
|
102
|
+
- data: "null"
|
|
103
|
+
- data: "true"
|
|
104
|
+
- data: '{"obj": 1}'
|
|
105
|
+
expect:
|
|
106
|
+
allSucceed: true
|
|
107
|
+
- action: read
|
|
108
|
+
path: ${streamPath}
|
|
109
|
+
expect:
|
|
110
|
+
upToDate: true
|
|
111
|
+
|
|
112
|
+
- id: bytes-batch-concatenates
|
|
113
|
+
name: Byte stream batching concatenates data
|
|
114
|
+
description: For non-JSON streams, batched items should be concatenated
|
|
115
|
+
setup:
|
|
116
|
+
- action: create
|
|
117
|
+
as: streamPath
|
|
118
|
+
contentType: text/plain
|
|
119
|
+
operations:
|
|
120
|
+
- action: idempotent-append-batch
|
|
121
|
+
path: ${streamPath}
|
|
122
|
+
producerId: test-producer
|
|
123
|
+
epoch: 0
|
|
124
|
+
items:
|
|
125
|
+
- data: "Hello"
|
|
126
|
+
- data: " "
|
|
127
|
+
- data: "World"
|
|
128
|
+
expect:
|
|
129
|
+
allSucceed: true
|
|
130
|
+
- action: read
|
|
131
|
+
path: ${streamPath}
|
|
132
|
+
expect:
|
|
133
|
+
data: "Hello World"
|
|
134
|
+
upToDate: true
|