@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,194 @@
1
+ id: idempotent-error-handling
2
+ name: Idempotent Producer - Error Handling
3
+ description: |
4
+ Tests for producer header validation and error conditions.
5
+ Includes atomicity guarantees when appends fail.
6
+ category: producer
7
+ tags:
8
+ - idempotent
9
+ - validation
10
+ - errors
11
+
12
+ tests:
13
+ - id: partial-headers-rejected
14
+ name: Partial producer headers are rejected
15
+ description: All three producer headers must be provided together
16
+ setup:
17
+ - action: create
18
+ as: streamPath
19
+ contentType: text/plain
20
+ operations:
21
+ # Only Producer-Id
22
+ - action: server-append
23
+ path: ${streamPath}
24
+ data: "msg"
25
+ headers:
26
+ Producer-Id: test-producer
27
+ expect:
28
+ status: 400
29
+ # Missing Producer-Seq
30
+ - action: server-append
31
+ path: ${streamPath}
32
+ data: "msg"
33
+ headers:
34
+ Producer-Id: test-producer
35
+ Producer-Epoch: "0"
36
+ expect:
37
+ status: 400
38
+
39
+ - id: invalid-integer-formats-rejected
40
+ name: Invalid integer formats in producer headers are rejected
41
+ description: Server should reject non-integer values like "1abc" or "1e3"
42
+ setup:
43
+ - action: create
44
+ as: streamPath
45
+ contentType: text/plain
46
+ operations:
47
+ # Trailing junk in Producer-Seq
48
+ - action: server-append
49
+ path: ${streamPath}
50
+ data: "msg"
51
+ headers:
52
+ Producer-Id: test-producer
53
+ Producer-Epoch: "0"
54
+ Producer-Seq: "1abc"
55
+ expect:
56
+ status: 400
57
+ # Scientific notation
58
+ - action: server-append
59
+ path: ${streamPath}
60
+ data: "msg"
61
+ headers:
62
+ Producer-Id: test-producer
63
+ Producer-Epoch: "1e3"
64
+ Producer-Seq: "0"
65
+ expect:
66
+ status: 400
67
+ # Negative value
68
+ - action: server-append
69
+ path: ${streamPath}
70
+ data: "msg"
71
+ headers:
72
+ Producer-Id: test-producer
73
+ Producer-Epoch: "-1"
74
+ Producer-Seq: "0"
75
+ expect:
76
+ status: 400
77
+
78
+ - id: empty-producer-id-rejected
79
+ name: Empty producer ID is rejected
80
+ description: Producer-Id header with empty string should be rejected
81
+ setup:
82
+ - action: create
83
+ as: streamPath
84
+ contentType: text/plain
85
+ operations:
86
+ - action: server-append
87
+ path: ${streamPath}
88
+ data: "msg"
89
+ headers:
90
+ Producer-Id: ""
91
+ Producer-Epoch: "0"
92
+ Producer-Seq: "0"
93
+ expect:
94
+ status: 400
95
+
96
+ - id: failed-append-does-not-advance-producer-state
97
+ name: Failed append does not advance producer state (atomicity)
98
+ description: If append fails after producer validation (e.g., invalid JSON), producer seq should not advance
99
+ setup:
100
+ - action: create
101
+ as: streamPath
102
+ contentType: application/json
103
+ operations:
104
+ # First append succeeds
105
+ - action: server-append
106
+ path: ${streamPath}
107
+ data: '{"msg": 1}'
108
+ producerId: test-producer
109
+ producerEpoch: 0
110
+ producerSeq: 0
111
+ expect:
112
+ status: 200
113
+ # Second append with invalid JSON - should fail
114
+ - action: server-append
115
+ path: ${streamPath}
116
+ data: "not valid json"
117
+ producerId: test-producer
118
+ producerEpoch: 0
119
+ producerSeq: 1
120
+ expect:
121
+ status: 400
122
+ # Retry seq=1 with valid JSON - should succeed (not be rejected as duplicate or gap)
123
+ # If producer state was incorrectly advanced, this would fail
124
+ - action: server-append
125
+ path: ${streamPath}
126
+ data: '{"msg": 2}'
127
+ producerId: test-producer
128
+ producerEpoch: 0
129
+ producerSeq: 1
130
+ expect:
131
+ status: 200
132
+ # seq=2 should also work
133
+ - action: server-append
134
+ path: ${streamPath}
135
+ data: '{"msg": 3}'
136
+ producerId: test-producer
137
+ producerEpoch: 0
138
+ producerSeq: 2
139
+ expect:
140
+ status: 200
141
+
142
+ - id: gap-fill-resume
143
+ name: Gap can be filled and producer can resume
144
+ description: |
145
+ After a 409 gap error, the producer should be able to fill the gap
146
+ and then continue. Some implementations accidentally "lock out" a
147
+ sequence once a gap was observed, or mutate state on the 409 path.
148
+ setup:
149
+ - action: create
150
+ as: streamPath
151
+ contentType: text/plain
152
+ operations:
153
+ # seq=0 succeeds
154
+ - action: server-append
155
+ path: ${streamPath}
156
+ data: "msg0"
157
+ producerId: test-producer
158
+ producerEpoch: 0
159
+ producerSeq: 0
160
+ expect:
161
+ status: 200
162
+ # seq=2 creates a gap -> 409
163
+ - action: server-append
164
+ path: ${streamPath}
165
+ data: "msg2"
166
+ producerId: test-producer
167
+ producerEpoch: 0
168
+ producerSeq: 2
169
+ expect:
170
+ status: 409
171
+ # Fill the gap with seq=1
172
+ - action: server-append
173
+ path: ${streamPath}
174
+ data: "msg1"
175
+ producerId: test-producer
176
+ producerEpoch: 0
177
+ producerSeq: 1
178
+ expect:
179
+ status: 200
180
+ # Now seq=2 should succeed
181
+ - action: server-append
182
+ path: ${streamPath}
183
+ data: "msg2"
184
+ producerId: test-producer
185
+ producerEpoch: 0
186
+ producerSeq: 2
187
+ expect:
188
+ status: 200
189
+ # Verify correct order in stream (text/plain concatenates all data)
190
+ - action: read
191
+ path: ${streamPath}
192
+ expect:
193
+ data: "msg0msg1msg2"
194
+ upToDate: true
@@ -0,0 +1,322 @@
1
+ id: idempotent-multi-producer
2
+ name: Idempotent Producer - Multi-Producer Scenarios
3
+ description: |
4
+ Tests for multiple producers on the same stream and producer isolation.
5
+ Each (stream, producerId) has independent epoch/sequence state.
6
+ category: producer
7
+ tags:
8
+ - idempotent
9
+ - multi-producer
10
+ - isolation
11
+
12
+ tests:
13
+ - id: multiple-producers-independent
14
+ name: Multiple producers have independent state
15
+ description: Different producer IDs should have separate epoch/seq tracking
16
+ setup:
17
+ - action: create
18
+ as: streamPath
19
+ contentType: text/plain
20
+ operations:
21
+ # Producer A: seq=0
22
+ - action: server-append
23
+ path: ${streamPath}
24
+ data: "A0"
25
+ producerId: producer-A
26
+ producerEpoch: 0
27
+ producerSeq: 0
28
+ expect:
29
+ status: 200
30
+ # Producer B: seq=0 (independent)
31
+ - action: server-append
32
+ path: ${streamPath}
33
+ data: "B0"
34
+ producerId: producer-B
35
+ producerEpoch: 0
36
+ producerSeq: 0
37
+ expect:
38
+ status: 200
39
+ # Producer A: seq=1
40
+ - action: server-append
41
+ path: ${streamPath}
42
+ data: "A1"
43
+ producerId: producer-A
44
+ producerEpoch: 0
45
+ producerSeq: 1
46
+ expect:
47
+ status: 200
48
+ # Producer B: seq=1
49
+ - action: server-append
50
+ path: ${streamPath}
51
+ data: "B1"
52
+ producerId: producer-B
53
+ producerEpoch: 0
54
+ producerSeq: 1
55
+ expect:
56
+ status: 200
57
+
58
+ - id: producer-scope-per-stream
59
+ name: Same producer ID works independently on different streams
60
+ description: |
61
+ Producer state must be scoped per (stream, producerId), not globally
62
+ by producerId alone. A common implementation bug is to key producer
63
+ state only by producer ID, causing cross-stream interference.
64
+ setup:
65
+ - action: create
66
+ as: streamA
67
+ contentType: text/plain
68
+ - action: create
69
+ as: streamB
70
+ contentType: text/plain
71
+ operations:
72
+ # Same producer, epoch=0, seq=0 on stream A
73
+ - action: server-append
74
+ path: ${streamA}
75
+ data: "streamA-msg"
76
+ producerId: shared-producer
77
+ producerEpoch: 0
78
+ producerSeq: 0
79
+ expect:
80
+ status: 200
81
+ # Same producer, epoch=0, seq=0 on stream B - must be 200, not 204/403
82
+ - action: server-append
83
+ path: ${streamB}
84
+ data: "streamB-msg"
85
+ producerId: shared-producer
86
+ producerEpoch: 0
87
+ producerSeq: 0
88
+ expect:
89
+ status: 200
90
+ # Verify each stream has its own message
91
+ - action: read
92
+ path: ${streamA}
93
+ expect:
94
+ data: "streamA-msg"
95
+ upToDate: true
96
+ - action: read
97
+ path: ${streamB}
98
+ expect:
99
+ data: "streamB-msg"
100
+ upToDate: true
101
+
102
+ - id: producer-epoch-per-stream
103
+ name: Producer epochs are independent per stream
104
+ description: |
105
+ Epoch fencing on one stream must not affect the same producer on
106
+ another stream. Each (stream, producerId) has independent epoch state.
107
+ setup:
108
+ - action: create
109
+ as: streamA
110
+ contentType: text/plain
111
+ - action: create
112
+ as: streamB
113
+ contentType: text/plain
114
+ operations:
115
+ # Establish epoch=5 on stream A
116
+ - action: server-append
117
+ path: ${streamA}
118
+ data: "A-epoch5"
119
+ producerId: shared-producer
120
+ producerEpoch: 5
121
+ producerSeq: 0
122
+ expect:
123
+ status: 200
124
+ # epoch=0 on stream B should still work (different stream)
125
+ - action: server-append
126
+ path: ${streamB}
127
+ data: "B-epoch0"
128
+ producerId: shared-producer
129
+ producerEpoch: 0
130
+ producerSeq: 0
131
+ expect:
132
+ status: 200
133
+ # epoch=0 on stream A should be fenced
134
+ - action: server-append
135
+ path: ${streamA}
136
+ data: "A-epoch0-zombie"
137
+ producerId: shared-producer
138
+ producerEpoch: 0
139
+ producerSeq: 1
140
+ expect:
141
+ status: 403
142
+ producerEpoch: 5
143
+
144
+ - id: interleaved-producers-ordering
145
+ name: Interleaved producers maintain per-producer ordering
146
+ description: |
147
+ When multiple producers write interleaved, each producer's messages
148
+ should appear in their sequence order relative to each other.
149
+ setup:
150
+ - action: create
151
+ as: streamPath
152
+ contentType: text/plain
153
+ operations:
154
+ # Interleave writes from two producers
155
+ - action: server-append
156
+ path: ${streamPath}
157
+ data: "A0"
158
+ producerId: producer-A
159
+ producerEpoch: 0
160
+ producerSeq: 0
161
+ expect:
162
+ status: 200
163
+ - action: server-append
164
+ path: ${streamPath}
165
+ data: "B0"
166
+ producerId: producer-B
167
+ producerEpoch: 0
168
+ producerSeq: 0
169
+ expect:
170
+ status: 200
171
+ - action: server-append
172
+ path: ${streamPath}
173
+ data: "A1"
174
+ producerId: producer-A
175
+ producerEpoch: 0
176
+ producerSeq: 1
177
+ expect:
178
+ status: 200
179
+ - action: server-append
180
+ path: ${streamPath}
181
+ data: "B1"
182
+ producerId: producer-B
183
+ producerEpoch: 0
184
+ producerSeq: 1
185
+ expect:
186
+ status: 200
187
+ - action: server-append
188
+ path: ${streamPath}
189
+ data: "A2"
190
+ producerId: producer-A
191
+ producerEpoch: 0
192
+ producerSeq: 2
193
+ expect:
194
+ status: 200
195
+ # Read and verify A0 comes before A1 comes before A2
196
+ # and B0 comes before B1
197
+ - action: read
198
+ path: ${streamPath}
199
+ expect:
200
+ dataContainsAll:
201
+ - "A0"
202
+ - "A1"
203
+ - "A2"
204
+ - "B0"
205
+ - "B1"
206
+ upToDate: true
207
+
208
+ - id: delete-recreate-resets-producer-state
209
+ name: Deleting and recreating stream resets producer state
210
+ description: |
211
+ When a stream is deleted and recreated, producer state must be reset.
212
+ The new stream is a fresh log, so (epoch=0, seq=0) must be accepted
213
+ even if the same producer previously wrote to the old stream.
214
+ setup:
215
+ - action: create
216
+ as: streamPath
217
+ contentType: text/plain
218
+ operations:
219
+ # Write to original stream
220
+ - action: server-append
221
+ path: ${streamPath}
222
+ data: "original-msg"
223
+ producerId: test-producer
224
+ producerEpoch: 0
225
+ producerSeq: 0
226
+ expect:
227
+ status: 200
228
+ # Advance sequence
229
+ - action: server-append
230
+ path: ${streamPath}
231
+ data: "original-msg2"
232
+ producerId: test-producer
233
+ producerEpoch: 0
234
+ producerSeq: 1
235
+ expect:
236
+ status: 200
237
+ # Delete the stream
238
+ - action: delete
239
+ path: ${streamPath}
240
+ expect:
241
+ status: 200
242
+ # Recreate at same path
243
+ - action: create
244
+ path: ${streamPath}
245
+ contentType: text/plain
246
+ # Same producer, epoch=0, seq=0 must work (new stream)
247
+ - action: server-append
248
+ path: ${streamPath}
249
+ data: "recreated-msg"
250
+ producerId: test-producer
251
+ producerEpoch: 0
252
+ producerSeq: 0
253
+ expect:
254
+ status: 200
255
+ # Verify only the new message exists
256
+ - action: read
257
+ path: ${streamPath}
258
+ expect:
259
+ data: "recreated-msg"
260
+ upToDate: true
261
+
262
+ - id: producer-id-long
263
+ name: Long producer ID works
264
+ description: Very long producer IDs should be accepted
265
+ setup:
266
+ - action: create
267
+ as: streamPath
268
+ contentType: text/plain
269
+ operations:
270
+ - action: server-append
271
+ path: ${streamPath}
272
+ data: "long-id"
273
+ producerId: "very-long-producer-id-that-goes-on-for-quite-a-while-to-test-string-handling-in-the-server-implementation-and-make-sure-it-doesnt-truncate"
274
+ producerEpoch: 0
275
+ producerSeq: 0
276
+ expect:
277
+ status: 200
278
+ # Duplicate detection works with long ID
279
+ - action: server-append
280
+ path: ${streamPath}
281
+ data: "long-id"
282
+ producerId: "very-long-producer-id-that-goes-on-for-quite-a-while-to-test-string-handling-in-the-server-implementation-and-make-sure-it-doesnt-truncate"
283
+ producerEpoch: 0
284
+ producerSeq: 0
285
+ expect:
286
+ status: 204
287
+
288
+ - id: producer-id-with-special-chars
289
+ name: Producer ID with special characters works
290
+ description: Producer IDs can contain colons, slashes, and other special chars
291
+ setup:
292
+ - action: create
293
+ as: streamPath
294
+ contentType: text/plain
295
+ operations:
296
+ # Colon-separated ID (common pattern)
297
+ - action: server-append
298
+ path: ${streamPath}
299
+ data: "msg1"
300
+ producerId: "service:instance:123"
301
+ producerEpoch: 0
302
+ producerSeq: 0
303
+ expect:
304
+ status: 200
305
+ # Slash-separated ID (path-like)
306
+ - action: server-append
307
+ path: ${streamPath}
308
+ data: "msg2"
309
+ producerId: "region/zone/host"
310
+ producerEpoch: 0
311
+ producerSeq: 0
312
+ expect:
313
+ status: 200
314
+ # UUID format
315
+ - action: server-append
316
+ path: ${streamPath}
317
+ data: "msg3"
318
+ producerId: "550e8400-e29b-41d4-a716-446655440000"
319
+ producerEpoch: 0
320
+ producerSeq: 0
321
+ expect:
322
+ status: 200