@durable-streams/client-conformance-tests 0.1.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/README.md +451 -0
- package/dist/adapters/typescript-adapter.d.ts +1 -0
- package/dist/adapters/typescript-adapter.js +586 -0
- package/dist/benchmark-runner-C_Yghc8f.js +1333 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +265 -0
- package/dist/index.d.ts +508 -0
- package/dist/index.js +4 -0
- package/dist/protocol-DyEvTHPF.d.ts +472 -0
- package/dist/protocol-qb83AeUH.js +120 -0
- package/dist/protocol.d.ts +2 -0
- package/dist/protocol.js +3 -0
- package/package.json +53 -0
- package/src/adapters/typescript-adapter.ts +848 -0
- package/src/benchmark-runner.ts +860 -0
- package/src/benchmark-scenarios.ts +311 -0
- package/src/cli.ts +294 -0
- package/src/index.ts +50 -0
- package/src/protocol.ts +656 -0
- package/src/runner.ts +1191 -0
- package/src/test-cases.ts +475 -0
- package/test-cases/consumer/cache-headers.yaml +150 -0
- package/test-cases/consumer/error-handling.yaml +108 -0
- package/test-cases/consumer/message-ordering.yaml +209 -0
- package/test-cases/consumer/offset-handling.yaml +209 -0
- package/test-cases/consumer/offset-resumption.yaml +197 -0
- package/test-cases/consumer/read-catchup.yaml +173 -0
- package/test-cases/consumer/read-longpoll.yaml +132 -0
- package/test-cases/consumer/read-sse.yaml +145 -0
- package/test-cases/consumer/retry-resilience.yaml +160 -0
- package/test-cases/consumer/streaming-equivalence.yaml +226 -0
- package/test-cases/lifecycle/dynamic-headers.yaml +147 -0
- package/test-cases/lifecycle/headers-params.yaml +117 -0
- package/test-cases/lifecycle/stream-lifecycle.yaml +148 -0
- package/test-cases/producer/append-data.yaml +142 -0
- package/test-cases/producer/batching.yaml +112 -0
- package/test-cases/producer/create-stream.yaml +87 -0
- package/test-cases/producer/error-handling.yaml +90 -0
- package/test-cases/producer/sequence-ordering.yaml +148 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
id: consumer-catchup
|
|
2
|
+
name: Catch-up Reads
|
|
3
|
+
description: Tests for reading existing data from streams (catch-up mode)
|
|
4
|
+
category: consumer
|
|
5
|
+
tags:
|
|
6
|
+
- core
|
|
7
|
+
- read
|
|
8
|
+
- catchup
|
|
9
|
+
|
|
10
|
+
tests:
|
|
11
|
+
- id: read-empty-stream
|
|
12
|
+
name: Read empty stream
|
|
13
|
+
description: Client should handle reading from an empty stream
|
|
14
|
+
setup:
|
|
15
|
+
- action: create
|
|
16
|
+
as: streamPath
|
|
17
|
+
operations:
|
|
18
|
+
- action: read
|
|
19
|
+
path: ${streamPath}
|
|
20
|
+
live: false
|
|
21
|
+
expect:
|
|
22
|
+
status: 200
|
|
23
|
+
chunkCount: 0
|
|
24
|
+
upToDate: true
|
|
25
|
+
|
|
26
|
+
- id: read-single-chunk
|
|
27
|
+
name: Read single chunk
|
|
28
|
+
description: Client should read a single chunk of data
|
|
29
|
+
setup:
|
|
30
|
+
- action: create
|
|
31
|
+
as: streamPath
|
|
32
|
+
- action: append
|
|
33
|
+
path: ${streamPath}
|
|
34
|
+
data: "Hello, World!"
|
|
35
|
+
operations:
|
|
36
|
+
- action: read
|
|
37
|
+
path: ${streamPath}
|
|
38
|
+
live: false
|
|
39
|
+
expect:
|
|
40
|
+
status: 200
|
|
41
|
+
data: "Hello, World!"
|
|
42
|
+
upToDate: true
|
|
43
|
+
|
|
44
|
+
- id: read-multiple-chunks
|
|
45
|
+
name: Read multiple chunks
|
|
46
|
+
description: Client should read all chunks concatenated
|
|
47
|
+
setup:
|
|
48
|
+
- action: create
|
|
49
|
+
as: streamPath
|
|
50
|
+
- action: append
|
|
51
|
+
path: ${streamPath}
|
|
52
|
+
data: "Chunk1 "
|
|
53
|
+
- action: append
|
|
54
|
+
path: ${streamPath}
|
|
55
|
+
data: "Chunk2 "
|
|
56
|
+
- action: append
|
|
57
|
+
path: ${streamPath}
|
|
58
|
+
data: "Chunk3"
|
|
59
|
+
operations:
|
|
60
|
+
- action: read
|
|
61
|
+
path: ${streamPath}
|
|
62
|
+
live: false
|
|
63
|
+
expect:
|
|
64
|
+
data: "Chunk1 Chunk2 Chunk3"
|
|
65
|
+
upToDate: true
|
|
66
|
+
|
|
67
|
+
- id: read-from-offset
|
|
68
|
+
name: Read from specific offset
|
|
69
|
+
description: Client should resume reading from a given offset
|
|
70
|
+
setup:
|
|
71
|
+
- action: create
|
|
72
|
+
as: streamPath
|
|
73
|
+
- action: append
|
|
74
|
+
path: ${streamPath}
|
|
75
|
+
data: "First"
|
|
76
|
+
expect:
|
|
77
|
+
storeOffsetAs: firstOffset
|
|
78
|
+
- action: append
|
|
79
|
+
path: ${streamPath}
|
|
80
|
+
data: "Second"
|
|
81
|
+
operations:
|
|
82
|
+
- action: read
|
|
83
|
+
path: ${streamPath}
|
|
84
|
+
offset: ${firstOffset}
|
|
85
|
+
live: false
|
|
86
|
+
expect:
|
|
87
|
+
data: "Second"
|
|
88
|
+
upToDate: true
|
|
89
|
+
|
|
90
|
+
- id: read-from-beginning
|
|
91
|
+
name: Read from beginning after offset
|
|
92
|
+
description: Reading without offset should start from beginning
|
|
93
|
+
setup:
|
|
94
|
+
- action: create
|
|
95
|
+
as: streamPath
|
|
96
|
+
- action: append
|
|
97
|
+
path: ${streamPath}
|
|
98
|
+
data: "Beginning"
|
|
99
|
+
- action: append
|
|
100
|
+
path: ${streamPath}
|
|
101
|
+
data: "End"
|
|
102
|
+
operations:
|
|
103
|
+
- action: read
|
|
104
|
+
path: ${streamPath}
|
|
105
|
+
live: false
|
|
106
|
+
expect:
|
|
107
|
+
data: "BeginningEnd"
|
|
108
|
+
upToDate: true
|
|
109
|
+
|
|
110
|
+
- id: read-preserves-offset
|
|
111
|
+
name: Read returns usable offset
|
|
112
|
+
description: Offset from read result can be used to resume
|
|
113
|
+
setup:
|
|
114
|
+
- action: create
|
|
115
|
+
as: streamPath
|
|
116
|
+
- action: append
|
|
117
|
+
path: ${streamPath}
|
|
118
|
+
data: "Part1"
|
|
119
|
+
- action: append
|
|
120
|
+
path: ${streamPath}
|
|
121
|
+
data: "Part2"
|
|
122
|
+
operations:
|
|
123
|
+
- action: read
|
|
124
|
+
path: ${streamPath}
|
|
125
|
+
maxChunks: 1
|
|
126
|
+
live: false
|
|
127
|
+
expect:
|
|
128
|
+
storeOffsetAs: resumeOffset
|
|
129
|
+
- action: append
|
|
130
|
+
path: ${streamPath}
|
|
131
|
+
data: "Part3"
|
|
132
|
+
- action: read
|
|
133
|
+
path: ${streamPath}
|
|
134
|
+
offset: ${resumeOffset}
|
|
135
|
+
live: false
|
|
136
|
+
expect:
|
|
137
|
+
dataContains: "Part3"
|
|
138
|
+
|
|
139
|
+
- id: read-binary-data
|
|
140
|
+
name: Read binary data
|
|
141
|
+
description: Client should correctly read binary data
|
|
142
|
+
setup:
|
|
143
|
+
- action: create
|
|
144
|
+
as: streamPath
|
|
145
|
+
contentType: application/octet-stream
|
|
146
|
+
- action: append
|
|
147
|
+
path: ${streamPath}
|
|
148
|
+
binaryData: "AQID" # [1, 2, 3] in base64
|
|
149
|
+
operations:
|
|
150
|
+
- action: read
|
|
151
|
+
path: ${streamPath}
|
|
152
|
+
live: false
|
|
153
|
+
expect:
|
|
154
|
+
status: 200
|
|
155
|
+
upToDate: true
|
|
156
|
+
|
|
157
|
+
- id: read-unicode-data
|
|
158
|
+
name: Read unicode data
|
|
159
|
+
description: Client should correctly handle unicode in reads
|
|
160
|
+
setup:
|
|
161
|
+
- action: create
|
|
162
|
+
as: streamPath
|
|
163
|
+
contentType: text/plain; charset=utf-8
|
|
164
|
+
- action: append
|
|
165
|
+
path: ${streamPath}
|
|
166
|
+
data: "Unicode: 日本語 🎉 Ñoño"
|
|
167
|
+
operations:
|
|
168
|
+
- action: read
|
|
169
|
+
path: ${streamPath}
|
|
170
|
+
live: false
|
|
171
|
+
expect:
|
|
172
|
+
data: "Unicode: 日本語 🎉 Ñoño"
|
|
173
|
+
upToDate: true
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
id: consumer-longpoll
|
|
2
|
+
name: Long-Poll Reads
|
|
3
|
+
description: Tests for long-poll live reading from streams
|
|
4
|
+
category: consumer
|
|
5
|
+
tags:
|
|
6
|
+
- core
|
|
7
|
+
- read
|
|
8
|
+
- longpoll
|
|
9
|
+
- live
|
|
10
|
+
requires:
|
|
11
|
+
- long-poll
|
|
12
|
+
|
|
13
|
+
tests:
|
|
14
|
+
- id: longpoll-waits-for-data
|
|
15
|
+
name: Long-poll waits for new data
|
|
16
|
+
description: Client should wait for new data in long-poll mode
|
|
17
|
+
setup:
|
|
18
|
+
- action: create
|
|
19
|
+
as: streamPath
|
|
20
|
+
- action: append
|
|
21
|
+
path: ${streamPath}
|
|
22
|
+
data: "initial"
|
|
23
|
+
expect:
|
|
24
|
+
storeOffsetAs: initialOffset
|
|
25
|
+
operations:
|
|
26
|
+
# Start long-poll read in background (will be waiting for new data)
|
|
27
|
+
- action: read
|
|
28
|
+
path: ${streamPath}
|
|
29
|
+
offset: ${initialOffset}
|
|
30
|
+
live: long-poll
|
|
31
|
+
timeoutMs: 10000
|
|
32
|
+
background: true
|
|
33
|
+
as: readOp
|
|
34
|
+
# Wait for the read to start
|
|
35
|
+
- action: wait
|
|
36
|
+
ms: 200
|
|
37
|
+
# Append data via direct server HTTP (adapter is blocked on read)
|
|
38
|
+
- action: server-append
|
|
39
|
+
path: ${streamPath}
|
|
40
|
+
data: "new-data"
|
|
41
|
+
# Wait for the background read to complete
|
|
42
|
+
- action: await
|
|
43
|
+
ref: readOp
|
|
44
|
+
expect:
|
|
45
|
+
dataContains: "new-data"
|
|
46
|
+
|
|
47
|
+
- id: longpoll-returns-immediately-with-data
|
|
48
|
+
name: Long-poll returns immediately if data exists
|
|
49
|
+
description: Long-poll should not wait if there's already data to return
|
|
50
|
+
setup:
|
|
51
|
+
- action: create
|
|
52
|
+
as: streamPath
|
|
53
|
+
- action: append
|
|
54
|
+
path: ${streamPath}
|
|
55
|
+
data: "existing-data"
|
|
56
|
+
operations:
|
|
57
|
+
- action: read
|
|
58
|
+
path: ${streamPath}
|
|
59
|
+
live: long-poll
|
|
60
|
+
timeoutMs: 1000
|
|
61
|
+
expect:
|
|
62
|
+
data: "existing-data"
|
|
63
|
+
upToDate: true
|
|
64
|
+
|
|
65
|
+
- id: longpoll-timeout
|
|
66
|
+
name: Long-poll times out gracefully
|
|
67
|
+
description: Long-poll should timeout and return up-to-date when no new data arrives
|
|
68
|
+
setup:
|
|
69
|
+
- action: create
|
|
70
|
+
as: streamPath
|
|
71
|
+
- action: append
|
|
72
|
+
path: ${streamPath}
|
|
73
|
+
data: "initial"
|
|
74
|
+
expect:
|
|
75
|
+
storeOffsetAs: offset
|
|
76
|
+
operations:
|
|
77
|
+
- action: read
|
|
78
|
+
path: ${streamPath}
|
|
79
|
+
offset: ${offset}
|
|
80
|
+
live: long-poll
|
|
81
|
+
timeoutMs: 500
|
|
82
|
+
expect:
|
|
83
|
+
chunkCount: 0
|
|
84
|
+
upToDate: true
|
|
85
|
+
|
|
86
|
+
- id: longpoll-multiple-chunks
|
|
87
|
+
name: Long-poll receives multiple chunks
|
|
88
|
+
description: Long-poll should receive all available chunks
|
|
89
|
+
setup:
|
|
90
|
+
- action: create
|
|
91
|
+
as: streamPath
|
|
92
|
+
operations:
|
|
93
|
+
- action: append
|
|
94
|
+
path: ${streamPath}
|
|
95
|
+
data: "chunk1"
|
|
96
|
+
- action: append
|
|
97
|
+
path: ${streamPath}
|
|
98
|
+
data: "chunk2"
|
|
99
|
+
- action: append
|
|
100
|
+
path: ${streamPath}
|
|
101
|
+
data: "chunk3"
|
|
102
|
+
- action: read
|
|
103
|
+
path: ${streamPath}
|
|
104
|
+
live: long-poll
|
|
105
|
+
timeoutMs: 1000
|
|
106
|
+
expect:
|
|
107
|
+
data: "chunk1chunk2chunk3"
|
|
108
|
+
upToDate: true
|
|
109
|
+
|
|
110
|
+
- id: longpoll-resume-from-offset
|
|
111
|
+
name: Long-poll resumes from offset
|
|
112
|
+
description: Long-poll should resume from the provided offset
|
|
113
|
+
setup:
|
|
114
|
+
- action: create
|
|
115
|
+
as: streamPath
|
|
116
|
+
- action: append
|
|
117
|
+
path: ${streamPath}
|
|
118
|
+
data: "old-data"
|
|
119
|
+
expect:
|
|
120
|
+
storeOffsetAs: offset
|
|
121
|
+
- action: append
|
|
122
|
+
path: ${streamPath}
|
|
123
|
+
data: "new-data"
|
|
124
|
+
operations:
|
|
125
|
+
- action: read
|
|
126
|
+
path: ${streamPath}
|
|
127
|
+
offset: ${offset}
|
|
128
|
+
live: long-poll
|
|
129
|
+
timeoutMs: 1000
|
|
130
|
+
expect:
|
|
131
|
+
data: "new-data"
|
|
132
|
+
upToDate: true
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
id: consumer-sse
|
|
2
|
+
name: SSE Reads
|
|
3
|
+
description: Tests for Server-Sent Events live reading from streams
|
|
4
|
+
category: consumer
|
|
5
|
+
tags:
|
|
6
|
+
- core
|
|
7
|
+
- read
|
|
8
|
+
- sse
|
|
9
|
+
- live
|
|
10
|
+
requires:
|
|
11
|
+
- sse
|
|
12
|
+
|
|
13
|
+
tests:
|
|
14
|
+
- id: sse-receives-existing-data
|
|
15
|
+
name: SSE receives existing data
|
|
16
|
+
description: SSE stream should first deliver existing data
|
|
17
|
+
setup:
|
|
18
|
+
- action: create
|
|
19
|
+
as: streamPath
|
|
20
|
+
- action: append
|
|
21
|
+
path: ${streamPath}
|
|
22
|
+
data: "existing1"
|
|
23
|
+
- action: append
|
|
24
|
+
path: ${streamPath}
|
|
25
|
+
data: "existing2"
|
|
26
|
+
operations:
|
|
27
|
+
- action: read
|
|
28
|
+
path: ${streamPath}
|
|
29
|
+
live: sse
|
|
30
|
+
maxChunks: 2
|
|
31
|
+
waitForUpToDate: true
|
|
32
|
+
expect:
|
|
33
|
+
dataContainsAll:
|
|
34
|
+
- "existing1"
|
|
35
|
+
- "existing2"
|
|
36
|
+
minChunks: 1
|
|
37
|
+
|
|
38
|
+
- id: sse-receives-new-data
|
|
39
|
+
name: SSE receives new data
|
|
40
|
+
description: SSE stream should receive data appended after connection
|
|
41
|
+
setup:
|
|
42
|
+
- action: create
|
|
43
|
+
as: streamPath
|
|
44
|
+
- action: append
|
|
45
|
+
path: ${streamPath}
|
|
46
|
+
data: "initial"
|
|
47
|
+
expect:
|
|
48
|
+
storeOffsetAs: offset
|
|
49
|
+
operations:
|
|
50
|
+
# Start SSE read in background (will be waiting for new data)
|
|
51
|
+
- action: read
|
|
52
|
+
path: ${streamPath}
|
|
53
|
+
offset: ${offset}
|
|
54
|
+
live: sse
|
|
55
|
+
maxChunks: 1
|
|
56
|
+
timeoutMs: 10000
|
|
57
|
+
background: true
|
|
58
|
+
as: readOp
|
|
59
|
+
# Wait for the SSE connection to establish
|
|
60
|
+
- action: wait
|
|
61
|
+
ms: 200
|
|
62
|
+
# Append data via direct server HTTP (adapter is blocked on read)
|
|
63
|
+
- action: server-append
|
|
64
|
+
path: ${streamPath}
|
|
65
|
+
data: "new-data"
|
|
66
|
+
# Wait for the background read to complete
|
|
67
|
+
- action: await
|
|
68
|
+
ref: readOp
|
|
69
|
+
expect:
|
|
70
|
+
dataContains: "new-data"
|
|
71
|
+
minChunks: 1
|
|
72
|
+
|
|
73
|
+
- id: sse-resumes-from-offset
|
|
74
|
+
name: SSE resumes from offset
|
|
75
|
+
description: SSE should start from specified offset
|
|
76
|
+
setup:
|
|
77
|
+
- action: create
|
|
78
|
+
as: streamPath
|
|
79
|
+
- action: append
|
|
80
|
+
path: ${streamPath}
|
|
81
|
+
data: "skip-this"
|
|
82
|
+
expect:
|
|
83
|
+
storeOffsetAs: skipOffset
|
|
84
|
+
- action: append
|
|
85
|
+
path: ${streamPath}
|
|
86
|
+
data: "include-this"
|
|
87
|
+
operations:
|
|
88
|
+
- action: read
|
|
89
|
+
path: ${streamPath}
|
|
90
|
+
offset: ${skipOffset}
|
|
91
|
+
live: sse
|
|
92
|
+
waitForUpToDate: true
|
|
93
|
+
expect:
|
|
94
|
+
data: "include-this"
|
|
95
|
+
|
|
96
|
+
- id: sse-up-to-date-signal
|
|
97
|
+
name: SSE signals up-to-date
|
|
98
|
+
description: SSE should indicate when caught up to head
|
|
99
|
+
setup:
|
|
100
|
+
- action: create
|
|
101
|
+
as: streamPath
|
|
102
|
+
- action: append
|
|
103
|
+
path: ${streamPath}
|
|
104
|
+
data: "data"
|
|
105
|
+
operations:
|
|
106
|
+
- action: read
|
|
107
|
+
path: ${streamPath}
|
|
108
|
+
live: sse
|
|
109
|
+
waitForUpToDate: true
|
|
110
|
+
expect:
|
|
111
|
+
upToDate: true
|
|
112
|
+
|
|
113
|
+
- id: sse-empty-stream
|
|
114
|
+
name: SSE on empty stream
|
|
115
|
+
description: SSE should work on empty stream and signal up-to-date
|
|
116
|
+
setup:
|
|
117
|
+
- action: create
|
|
118
|
+
as: streamPath
|
|
119
|
+
operations:
|
|
120
|
+
- action: read
|
|
121
|
+
path: ${streamPath}
|
|
122
|
+
live: sse
|
|
123
|
+
waitForUpToDate: true
|
|
124
|
+
expect:
|
|
125
|
+
upToDate: true
|
|
126
|
+
chunkCount: 0
|
|
127
|
+
|
|
128
|
+
- id: sse-binary-data
|
|
129
|
+
name: SSE with binary data
|
|
130
|
+
description: SSE should correctly transmit binary data
|
|
131
|
+
setup:
|
|
132
|
+
- action: create
|
|
133
|
+
as: streamPath
|
|
134
|
+
contentType: application/octet-stream
|
|
135
|
+
- action: append
|
|
136
|
+
path: ${streamPath}
|
|
137
|
+
binaryData: "/f7/AA==" # some binary bytes
|
|
138
|
+
operations:
|
|
139
|
+
- action: read
|
|
140
|
+
path: ${streamPath}
|
|
141
|
+
live: sse
|
|
142
|
+
waitForUpToDate: true
|
|
143
|
+
expect:
|
|
144
|
+
minChunks: 1
|
|
145
|
+
upToDate: true
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
id: consumer-retry-resilience
|
|
2
|
+
name: Retry and Resilience
|
|
3
|
+
description: Tests for client retry behavior on transient errors
|
|
4
|
+
category: consumer
|
|
5
|
+
tags:
|
|
6
|
+
- core
|
|
7
|
+
- retry
|
|
8
|
+
- resilience
|
|
9
|
+
- error-handling
|
|
10
|
+
|
|
11
|
+
tests:
|
|
12
|
+
- id: retry-on-500
|
|
13
|
+
name: Client retries on 500 Internal Server Error
|
|
14
|
+
description: Client should automatically retry when server returns 500
|
|
15
|
+
setup:
|
|
16
|
+
- action: create
|
|
17
|
+
as: streamPath
|
|
18
|
+
- action: append
|
|
19
|
+
path: ${streamPath}
|
|
20
|
+
data: "test-data"
|
|
21
|
+
operations:
|
|
22
|
+
# Inject a 500 error for the first request
|
|
23
|
+
- action: inject-error
|
|
24
|
+
path: ${streamPath}
|
|
25
|
+
status: 500
|
|
26
|
+
count: 1
|
|
27
|
+
# Client should retry and eventually succeed
|
|
28
|
+
- action: read
|
|
29
|
+
path: ${streamPath}
|
|
30
|
+
expect:
|
|
31
|
+
data: "test-data"
|
|
32
|
+
cleanup:
|
|
33
|
+
- action: clear-errors
|
|
34
|
+
|
|
35
|
+
- id: retry-on-503
|
|
36
|
+
name: Client retries on 503 Service Unavailable
|
|
37
|
+
description: Client should automatically retry when server returns 503
|
|
38
|
+
setup:
|
|
39
|
+
- action: create
|
|
40
|
+
as: streamPath
|
|
41
|
+
- action: append
|
|
42
|
+
path: ${streamPath}
|
|
43
|
+
data: "test-data"
|
|
44
|
+
operations:
|
|
45
|
+
# Inject a 503 error for the first request
|
|
46
|
+
- action: inject-error
|
|
47
|
+
path: ${streamPath}
|
|
48
|
+
status: 503
|
|
49
|
+
count: 1
|
|
50
|
+
# Client should retry and eventually succeed
|
|
51
|
+
- action: read
|
|
52
|
+
path: ${streamPath}
|
|
53
|
+
expect:
|
|
54
|
+
data: "test-data"
|
|
55
|
+
cleanup:
|
|
56
|
+
- action: clear-errors
|
|
57
|
+
|
|
58
|
+
- id: retry-on-429
|
|
59
|
+
name: Client retries on 429 Too Many Requests
|
|
60
|
+
description: Client should retry after rate limiting with backoff
|
|
61
|
+
setup:
|
|
62
|
+
- action: create
|
|
63
|
+
as: streamPath
|
|
64
|
+
- action: append
|
|
65
|
+
path: ${streamPath}
|
|
66
|
+
data: "test-data"
|
|
67
|
+
operations:
|
|
68
|
+
# Inject a 429 error with Retry-After header
|
|
69
|
+
- action: inject-error
|
|
70
|
+
path: ${streamPath}
|
|
71
|
+
status: 429
|
|
72
|
+
count: 1
|
|
73
|
+
retryAfter: 1
|
|
74
|
+
# Client should wait and retry, eventually succeeding
|
|
75
|
+
- action: read
|
|
76
|
+
path: ${streamPath}
|
|
77
|
+
expect:
|
|
78
|
+
data: "test-data"
|
|
79
|
+
cleanup:
|
|
80
|
+
- action: clear-errors
|
|
81
|
+
|
|
82
|
+
- id: retry-multiple-failures
|
|
83
|
+
name: Client retries through multiple transient failures
|
|
84
|
+
description: Client should retry through several consecutive 500 errors
|
|
85
|
+
setup:
|
|
86
|
+
- action: create
|
|
87
|
+
as: streamPath
|
|
88
|
+
- action: append
|
|
89
|
+
path: ${streamPath}
|
|
90
|
+
data: "persisted-data"
|
|
91
|
+
operations:
|
|
92
|
+
# Inject 2 consecutive 500 errors
|
|
93
|
+
- action: inject-error
|
|
94
|
+
path: ${streamPath}
|
|
95
|
+
status: 500
|
|
96
|
+
count: 2
|
|
97
|
+
# Client should retry multiple times and eventually succeed
|
|
98
|
+
- action: read
|
|
99
|
+
path: ${streamPath}
|
|
100
|
+
expect:
|
|
101
|
+
data: "persisted-data"
|
|
102
|
+
cleanup:
|
|
103
|
+
- action: clear-errors
|
|
104
|
+
|
|
105
|
+
- id: no-retry-on-404
|
|
106
|
+
name: Client does not retry on 404 Not Found
|
|
107
|
+
description: 404 is a permanent error and should not be retried
|
|
108
|
+
operations:
|
|
109
|
+
# Try to read from non-existent stream (will get 404)
|
|
110
|
+
- action: read
|
|
111
|
+
path: /non-existent-stream-${randomUUID}
|
|
112
|
+
expect:
|
|
113
|
+
status: 404
|
|
114
|
+
|
|
115
|
+
- id: no-retry-on-400
|
|
116
|
+
name: Client does not retry on 400 Bad Request
|
|
117
|
+
description: 400 is a client error and should not be retried
|
|
118
|
+
setup:
|
|
119
|
+
- action: create
|
|
120
|
+
as: streamPath
|
|
121
|
+
- action: append
|
|
122
|
+
path: ${streamPath}
|
|
123
|
+
data: "data"
|
|
124
|
+
operations:
|
|
125
|
+
# Inject a 400 error
|
|
126
|
+
- action: inject-error
|
|
127
|
+
path: ${streamPath}
|
|
128
|
+
status: 400
|
|
129
|
+
count: 1
|
|
130
|
+
# Client should not retry, should fail immediately
|
|
131
|
+
- action: read
|
|
132
|
+
path: ${streamPath}
|
|
133
|
+
expect:
|
|
134
|
+
status: 400
|
|
135
|
+
cleanup:
|
|
136
|
+
- action: clear-errors
|
|
137
|
+
|
|
138
|
+
- id: append-retry-on-500
|
|
139
|
+
name: Append retries on 500 Internal Server Error
|
|
140
|
+
description: Append should automatically retry when server returns 500
|
|
141
|
+
setup:
|
|
142
|
+
- action: create
|
|
143
|
+
as: streamPath
|
|
144
|
+
operations:
|
|
145
|
+
# Inject a 500 error for the first append
|
|
146
|
+
- action: inject-error
|
|
147
|
+
path: ${streamPath}
|
|
148
|
+
status: 500
|
|
149
|
+
count: 1
|
|
150
|
+
# Client should retry and eventually succeed
|
|
151
|
+
- action: append
|
|
152
|
+
path: ${streamPath}
|
|
153
|
+
data: "retry-data"
|
|
154
|
+
# Verify data was appended
|
|
155
|
+
- action: read
|
|
156
|
+
path: ${streamPath}
|
|
157
|
+
expect:
|
|
158
|
+
data: "retry-data"
|
|
159
|
+
cleanup:
|
|
160
|
+
- action: clear-errors
|