@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.
Files changed (30) hide show
  1. package/dist/adapters/typescript-adapter.cjs +75 -2
  2. package/dist/adapters/typescript-adapter.js +76 -3
  3. package/dist/{benchmark-runner-C_Yghc8f.js → benchmark-runner-CrE6JkbX.js} +106 -12
  4. package/dist/{benchmark-runner-CLAR9oLd.cjs → benchmark-runner-Db4he452.cjs} +107 -12
  5. package/dist/cli.cjs +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.d.cts +126 -11
  9. package/dist/index.d.ts +126 -11
  10. package/dist/index.js +1 -1
  11. package/dist/{protocol-3cf94Xyb.d.cts → protocol-D37G3c4e.d.cts} +80 -4
  12. package/dist/{protocol-DyEvTHPF.d.ts → protocol-Mcbiq3nQ.d.ts} +80 -4
  13. package/dist/protocol.d.cts +2 -2
  14. package/dist/protocol.d.ts +2 -2
  15. package/package.json +3 -3
  16. package/src/adapters/typescript-adapter.ts +127 -5
  17. package/src/protocol.ts +85 -1
  18. package/src/runner.ts +202 -17
  19. package/src/test-cases.ts +130 -8
  20. package/test-cases/consumer/error-handling.yaml +42 -0
  21. package/test-cases/consumer/fault-injection.yaml +202 -0
  22. package/test-cases/consumer/offset-handling.yaml +209 -0
  23. package/test-cases/producer/idempotent/autoclaim.yaml +214 -0
  24. package/test-cases/producer/idempotent/batching.yaml +98 -0
  25. package/test-cases/producer/idempotent/concurrent-requests.yaml +100 -0
  26. package/test-cases/producer/idempotent/epoch-management.yaml +333 -0
  27. package/test-cases/producer/idempotent/error-handling.yaml +194 -0
  28. package/test-cases/producer/idempotent/multi-producer.yaml +322 -0
  29. package/test-cases/producer/idempotent/sequence-validation.yaml +339 -0
  30. package/test-cases/producer/idempotent-json-batching.yaml +134 -0
@@ -0,0 +1,98 @@
1
+ id: idempotent-batching
2
+ name: Idempotent Producer - Batching
3
+ description: |
4
+ Tests for producer batching behavior and header interactions.
5
+ Batches are atomic - the entire batch shares a single sequence number.
6
+ category: producer
7
+ tags:
8
+ - idempotent
9
+ - batching
10
+
11
+ tests:
12
+ - id: batch-retry-deduplication
13
+ name: Retrying entire batch returns 204 for all
14
+ description: If client retries an entire JSON batch, server should deduplicate
15
+ setup:
16
+ - action: create
17
+ as: streamPath
18
+ contentType: application/json
19
+ operations:
20
+ # Send a batch of 3 items
21
+ - action: server-append
22
+ path: ${streamPath}
23
+ data: '[{"id":1},{"id":2},{"id":3}]'
24
+ producerId: batch-retry-producer
25
+ producerEpoch: 0
26
+ producerSeq: 0
27
+ expect:
28
+ status: 200
29
+ # Retry the exact same batch - should get 204
30
+ - action: server-append
31
+ path: ${streamPath}
32
+ data: '[{"id":1},{"id":2},{"id":3}]'
33
+ producerId: batch-retry-producer
34
+ producerEpoch: 0
35
+ producerSeq: 0
36
+ expect:
37
+ status: 204
38
+ duplicate: true
39
+ # Verify only 3 items in stream (not 6)
40
+ - action: read
41
+ path: ${streamPath}
42
+ expect:
43
+ upToDate: true
44
+ # Next seq should still work
45
+ - action: server-append
46
+ path: ${streamPath}
47
+ data: '[{"id":4}]'
48
+ producerId: batch-retry-producer
49
+ producerEpoch: 0
50
+ producerSeq: 1
51
+ expect:
52
+ status: 200
53
+
54
+ - id: producer-with-stream-seq
55
+ name: Producer headers work with Stream-Seq header
56
+ description: Both producer deduplication and writer coordination should work together
57
+ setup:
58
+ - action: create
59
+ as: streamPath
60
+ contentType: text/plain
61
+ operations:
62
+ - action: server-append
63
+ path: ${streamPath}
64
+ data: "msg"
65
+ producerId: test-producer
66
+ producerEpoch: 0
67
+ producerSeq: 0
68
+ headers:
69
+ Stream-Seq: "app-seq-001"
70
+ expect:
71
+ status: 200
72
+
73
+ - id: duplicate-json-different-payload
74
+ name: Duplicate JSON with different payload returns 204
75
+ description: Same test for JSON content type - dedup is by headers only
76
+ setup:
77
+ - action: create
78
+ as: streamPath
79
+ contentType: application/json
80
+ operations:
81
+ - action: server-append
82
+ path: ${streamPath}
83
+ data: '{"key": "original"}'
84
+ producerId: test-producer
85
+ producerEpoch: 0
86
+ producerSeq: 0
87
+ expect:
88
+ status: 200
89
+ # Retry with different JSON payload
90
+ - action: server-append
91
+ path: ${streamPath}
92
+ data: '{"key": "different", "extra": true}'
93
+ producerId: test-producer
94
+ producerEpoch: 0
95
+ producerSeq: 0
96
+ expect:
97
+ status: 204
98
+ duplicate: true
@@ -0,0 +1,100 @@
1
+ id: idempotent-concurrent-requests
2
+ name: Idempotent Producer - Concurrent Requests
3
+ description: |
4
+ Tests for client handling of concurrent HTTP requests (pipelining).
5
+ When maxInFlight > 1, requests may arrive out of order causing 409 errors.
6
+ The client must retry and ensure all messages are delivered.
7
+ category: producer
8
+ tags:
9
+ - idempotent
10
+ - concurrent
11
+ - pipelining
12
+ - retry
13
+
14
+ tests:
15
+ - id: concurrent-batches-all-delivered
16
+ name: All messages delivered with high concurrency
17
+ description: |
18
+ When using maxInFlight > 1, HTTP requests may arrive out of order causing 409 errors.
19
+ The client should retry and ensure all messages are delivered.
20
+ setup:
21
+ - action: create
22
+ as: streamPath
23
+ contentType: text/plain
24
+ operations:
25
+ # Send 20 messages with maxInFlight=5
26
+ # This forces concurrent batches that may arrive out of order
27
+ - action: idempotent-append-batch
28
+ path: ${streamPath}
29
+ producerId: test-producer
30
+ epoch: 0
31
+ maxInFlight: 5
32
+ items:
33
+ - data: "msg-00"
34
+ - data: "msg-01"
35
+ - data: "msg-02"
36
+ - data: "msg-03"
37
+ - data: "msg-04"
38
+ - data: "msg-05"
39
+ - data: "msg-06"
40
+ - data: "msg-07"
41
+ - data: "msg-08"
42
+ - data: "msg-09"
43
+ - data: "msg-10"
44
+ - data: "msg-11"
45
+ - data: "msg-12"
46
+ - data: "msg-13"
47
+ - data: "msg-14"
48
+ - data: "msg-15"
49
+ - data: "msg-16"
50
+ - data: "msg-17"
51
+ - data: "msg-18"
52
+ - data: "msg-19"
53
+ expect:
54
+ allSucceed: true
55
+ # Verify all messages are in the stream
56
+ - action: read
57
+ path: ${streamPath}
58
+ expect:
59
+ dataContainsAll:
60
+ - "msg-00"
61
+ - "msg-05"
62
+ - "msg-10"
63
+ - "msg-15"
64
+ - "msg-19"
65
+ upToDate: true
66
+
67
+ - id: concurrent-json-batches-all-delivered
68
+ name: All JSON messages delivered with high concurrency
69
+ description: Same test with JSON content type to verify JSON batching works with concurrency
70
+ setup:
71
+ - action: create
72
+ as: streamPath
73
+ contentType: application/json
74
+ operations:
75
+ - action: idempotent-append-batch
76
+ path: ${streamPath}
77
+ producerId: test-producer
78
+ epoch: 0
79
+ maxInFlight: 5
80
+ items:
81
+ - data: '{"id": 0}'
82
+ - data: '{"id": 1}'
83
+ - data: '{"id": 2}'
84
+ - data: '{"id": 3}'
85
+ - data: '{"id": 4}'
86
+ - data: '{"id": 5}'
87
+ - data: '{"id": 6}'
88
+ - data: '{"id": 7}'
89
+ - data: '{"id": 8}'
90
+ - data: '{"id": 9}'
91
+ expect:
92
+ allSucceed: true
93
+ - action: read
94
+ path: ${streamPath}
95
+ expect:
96
+ dataContainsAll:
97
+ - '"id":0'
98
+ - '"id":5'
99
+ - '"id":9'
100
+ upToDate: true
@@ -0,0 +1,333 @@
1
+ id: idempotent-epoch-management
2
+ name: Idempotent Producer - Epoch Management
3
+ description: |
4
+ Tests for producer epoch validation and zombie fencing.
5
+ Epochs enable split-brain protection - only the latest instance can write.
6
+ category: producer
7
+ tags:
8
+ - idempotent
9
+ - epoch
10
+ - fencing
11
+
12
+ tests:
13
+ - id: epoch-upgrade-accepted
14
+ name: Epoch upgrade resets sequence to 0
15
+ description: New epoch must start at seq=0 and should be accepted
16
+ setup:
17
+ - action: create
18
+ as: streamPath
19
+ contentType: text/plain
20
+ operations:
21
+ - action: server-append
22
+ path: ${streamPath}
23
+ data: "epoch0-msg0"
24
+ producerId: test-producer
25
+ producerEpoch: 0
26
+ producerSeq: 0
27
+ expect:
28
+ status: 200
29
+ - action: server-append
30
+ path: ${streamPath}
31
+ data: "epoch0-msg1"
32
+ producerId: test-producer
33
+ producerEpoch: 0
34
+ producerSeq: 1
35
+ expect:
36
+ status: 200
37
+ # Upgrade to epoch=1
38
+ - action: server-append
39
+ path: ${streamPath}
40
+ data: "epoch1-msg0"
41
+ producerId: test-producer
42
+ producerEpoch: 1
43
+ producerSeq: 0
44
+ expect:
45
+ status: 200
46
+ producerEpoch: 1
47
+
48
+ - id: stale-epoch-rejected
49
+ name: Stale epoch returns 403 (zombie fencing)
50
+ description: Old epoch should be rejected with 403 and current epoch in response
51
+ setup:
52
+ - action: create
53
+ as: streamPath
54
+ contentType: text/plain
55
+ operations:
56
+ # Establish epoch=1
57
+ - action: server-append
58
+ path: ${streamPath}
59
+ data: "msg"
60
+ producerId: test-producer
61
+ producerEpoch: 1
62
+ producerSeq: 0
63
+ expect:
64
+ status: 200
65
+ # Try to write with stale epoch=0
66
+ - action: server-append
67
+ path: ${streamPath}
68
+ data: "zombie"
69
+ producerId: test-producer
70
+ producerEpoch: 0
71
+ producerSeq: 0
72
+ expect:
73
+ status: 403
74
+ producerEpoch: 1
75
+
76
+ - id: epoch-rollback-rejected
77
+ name: Epoch rollback is rejected
78
+ description: Cannot go back to a lower epoch
79
+ setup:
80
+ - action: create
81
+ as: streamPath
82
+ contentType: text/plain
83
+ operations:
84
+ # Establish epoch=2
85
+ - action: server-append
86
+ path: ${streamPath}
87
+ data: "msg"
88
+ producerId: test-producer
89
+ producerEpoch: 2
90
+ producerSeq: 0
91
+ expect:
92
+ status: 200
93
+ # Try epoch=1 (rollback)
94
+ - action: server-append
95
+ path: ${streamPath}
96
+ data: "rollback"
97
+ producerId: test-producer
98
+ producerEpoch: 1
99
+ producerSeq: 0
100
+ expect:
101
+ status: 403
102
+
103
+ - id: epoch-increase-requires-seq-zero
104
+ name: Epoch increase with seq != 0 is rejected
105
+ description: New epoch must start at seq=0
106
+ setup:
107
+ - action: create
108
+ as: streamPath
109
+ contentType: text/plain
110
+ operations:
111
+ - action: server-append
112
+ path: ${streamPath}
113
+ data: "msg"
114
+ producerId: test-producer
115
+ producerEpoch: 0
116
+ producerSeq: 0
117
+ expect:
118
+ status: 200
119
+ # Try epoch=1 with seq=5
120
+ - action: server-append
121
+ path: ${streamPath}
122
+ data: "bad"
123
+ producerId: test-producer
124
+ producerEpoch: 1
125
+ producerSeq: 5
126
+ expect:
127
+ status: 400
128
+
129
+ - id: epoch-gap-allowed
130
+ name: Epoch can skip values (gap allowed)
131
+ description: |
132
+ Epochs don't need to be sequential. Jumping from epoch=0 to epoch=10
133
+ should be allowed (unlike sequences which must be sequential).
134
+ setup:
135
+ - action: create
136
+ as: streamPath
137
+ contentType: text/plain
138
+ operations:
139
+ - action: server-append
140
+ path: ${streamPath}
141
+ data: "epoch0"
142
+ producerId: test-producer
143
+ producerEpoch: 0
144
+ producerSeq: 0
145
+ expect:
146
+ status: 200
147
+ # Jump to epoch=10 (skipping 1-9)
148
+ - action: server-append
149
+ path: ${streamPath}
150
+ data: "epoch10"
151
+ producerId: test-producer
152
+ producerEpoch: 10
153
+ producerSeq: 0
154
+ expect:
155
+ status: 200
156
+ # Previous epochs now fenced
157
+ - action: server-append
158
+ path: ${streamPath}
159
+ data: "epoch5"
160
+ producerId: test-producer
161
+ producerEpoch: 5
162
+ producerSeq: 0
163
+ expect:
164
+ status: 403
165
+ producerEpoch: 10
166
+
167
+ - id: epoch-zero-after-higher-epoch
168
+ name: Epoch 0 rejected after higher epoch established
169
+ description: |
170
+ Once a higher epoch is established, epoch=0 should be rejected.
171
+ This is critical for zombie fencing.
172
+ setup:
173
+ - action: create
174
+ as: streamPath
175
+ contentType: text/plain
176
+ operations:
177
+ # Establish epoch=5
178
+ - action: server-append
179
+ path: ${streamPath}
180
+ data: "epoch5"
181
+ producerId: test-producer
182
+ producerEpoch: 5
183
+ producerSeq: 0
184
+ expect:
185
+ status: 200
186
+ # Epoch 0 should be fenced
187
+ - action: server-append
188
+ path: ${streamPath}
189
+ data: "epoch0-zombie"
190
+ producerId: test-producer
191
+ producerEpoch: 0
192
+ producerSeq: 0
193
+ expect:
194
+ status: 403
195
+ producerEpoch: 5
196
+ # Epoch 4 should also be fenced
197
+ - action: server-append
198
+ path: ${streamPath}
199
+ data: "epoch4-zombie"
200
+ producerId: test-producer
201
+ producerEpoch: 4
202
+ producerSeq: 0
203
+ expect:
204
+ status: 403
205
+ producerEpoch: 5
206
+
207
+ - id: split-brain-fencing
208
+ name: Split-brain fencing scenario
209
+ description: Old producer instance (zombie) should be fenced when new instance claims higher epoch
210
+ setup:
211
+ - action: create
212
+ as: streamPath
213
+ contentType: text/plain
214
+ operations:
215
+ # Producer A (original): epoch=0
216
+ - action: server-append
217
+ path: ${streamPath}
218
+ data: "A0"
219
+ producerId: shared-producer
220
+ producerEpoch: 0
221
+ producerSeq: 0
222
+ expect:
223
+ status: 200
224
+ # Producer B (new instance): claims epoch=1
225
+ - action: server-append
226
+ path: ${streamPath}
227
+ data: "B0"
228
+ producerId: shared-producer
229
+ producerEpoch: 1
230
+ producerSeq: 0
231
+ expect:
232
+ status: 200
233
+ # Producer A (zombie): tries epoch=0, seq=1 - should be fenced
234
+ - action: server-append
235
+ path: ${streamPath}
236
+ data: "A1"
237
+ producerId: shared-producer
238
+ producerEpoch: 0
239
+ producerSeq: 1
240
+ expect:
241
+ status: 403
242
+ producerEpoch: 1
243
+
244
+ - id: large-epoch-numbers
245
+ name: Large epoch numbers handled correctly
246
+ description: Test that large epoch values work correctly
247
+ setup:
248
+ - action: create
249
+ as: streamPath
250
+ contentType: text/plain
251
+ operations:
252
+ - action: server-append
253
+ path: ${streamPath}
254
+ data: "large-epoch"
255
+ producerId: test-producer
256
+ producerEpoch: 2147483640
257
+ producerSeq: 0
258
+ expect:
259
+ status: 200
260
+ # Stale epoch should still be rejected
261
+ - action: server-append
262
+ path: ${streamPath}
263
+ data: "stale"
264
+ producerId: test-producer
265
+ producerEpoch: 100
266
+ producerSeq: 0
267
+ expect:
268
+ status: 403
269
+ producerEpoch: 2147483640
270
+
271
+ - id: sequence-resets-on-epoch-upgrade
272
+ name: Each epoch has independent sequence space
273
+ description: |
274
+ Sequence numbers reset to 0 with each epoch upgrade.
275
+ The old epoch's sequences don't affect the new epoch.
276
+ setup:
277
+ - action: create
278
+ as: streamPath
279
+ contentType: text/plain
280
+ operations:
281
+ # Epoch 0: sequences 0, 1, 2
282
+ - action: server-append
283
+ path: ${streamPath}
284
+ data: "e0s0"
285
+ producerId: test-producer
286
+ producerEpoch: 0
287
+ producerSeq: 0
288
+ expect:
289
+ status: 200
290
+ - action: server-append
291
+ path: ${streamPath}
292
+ data: "e0s1"
293
+ producerId: test-producer
294
+ producerEpoch: 0
295
+ producerSeq: 1
296
+ expect:
297
+ status: 200
298
+ - action: server-append
299
+ path: ${streamPath}
300
+ data: "e0s2"
301
+ producerId: test-producer
302
+ producerEpoch: 0
303
+ producerSeq: 2
304
+ expect:
305
+ status: 200
306
+ # Epoch 1: starts fresh at seq=0
307
+ - action: server-append
308
+ path: ${streamPath}
309
+ data: "e1s0"
310
+ producerId: test-producer
311
+ producerEpoch: 1
312
+ producerSeq: 0
313
+ expect:
314
+ status: 200
315
+ - action: server-append
316
+ path: ${streamPath}
317
+ data: "e1s1"
318
+ producerId: test-producer
319
+ producerEpoch: 1
320
+ producerSeq: 1
321
+ expect:
322
+ status: 200
323
+ # Verify all 5 messages present
324
+ - action: read
325
+ path: ${streamPath}
326
+ expect:
327
+ dataContainsAll:
328
+ - "e0s0"
329
+ - "e0s1"
330
+ - "e0s2"
331
+ - "e1s0"
332
+ - "e1s1"
333
+ upToDate: true