@durable-streams/server-conformance-tests 0.2.0 → 0.2.2
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/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-Pd7PHJOG.js → src-D-K9opVc.js} +960 -18
- package/dist/{src-CMFxsR_X.cjs → src-DZatkb9d.cjs} +960 -18
- package/dist/test-runner.cjs +1 -1
- package/dist/test-runner.js +1 -1
- package/package.json +2 -2
- package/src/index.ts +1407 -23
|
@@ -1934,19 +1934,6 @@ function runConformanceTests(options) {
|
|
|
1934
1934
|
const response = await fetch(`${getBaseUrl()}${streamPath}?live=sse`, { method: `GET` });
|
|
1935
1935
|
(0, vitest.expect)(response.status).toBe(400);
|
|
1936
1936
|
});
|
|
1937
|
-
(0, vitest.test)(`client should reject SSE mode for incompatible content types`, async () => {
|
|
1938
|
-
const streamPath = `/v1/stream/sse-binary-test-${Date.now()}`;
|
|
1939
|
-
const stream = await __durable_streams_client.DurableStream.create({
|
|
1940
|
-
url: `${getBaseUrl()}${streamPath}`,
|
|
1941
|
-
contentType: `application/octet-stream`
|
|
1942
|
-
});
|
|
1943
|
-
await stream.append(new Uint8Array([
|
|
1944
|
-
1,
|
|
1945
|
-
2,
|
|
1946
|
-
3
|
|
1947
|
-
]));
|
|
1948
|
-
await (0, vitest.expect)(stream.stream({ live: `sse` })).rejects.toThrow();
|
|
1949
|
-
});
|
|
1950
1937
|
(0, vitest.test)(`should stream data events via SSE`, async () => {
|
|
1951
1938
|
const streamPath = `/v1/stream/sse-data-stream-test-${Date.now()}`;
|
|
1952
1939
|
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
@@ -2209,10 +2196,11 @@ function runConformanceTests(options) {
|
|
|
2209
2196
|
body: `message 2`
|
|
2210
2197
|
});
|
|
2211
2198
|
let lastOffset = null;
|
|
2212
|
-
const { response: response1, received: received1 } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `
|
|
2199
|
+
const { response: response1, received: received1 } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `upToDate` });
|
|
2213
2200
|
(0, vitest.expect)(response1.status).toBe(200);
|
|
2214
|
-
const
|
|
2215
|
-
const
|
|
2201
|
+
const controlLines = received1.split(`\n`).filter((l) => l.startsWith(`data:`) && l.includes(`streamNextOffset`));
|
|
2202
|
+
const lastControlLine = controlLines[controlLines.length - 1];
|
|
2203
|
+
const controlPayload = lastControlLine.slice(`data:`.length);
|
|
2216
2204
|
lastOffset = JSON.parse(controlPayload)[`streamNextOffset`];
|
|
2217
2205
|
(0, vitest.expect)(lastOffset).toBeDefined();
|
|
2218
2206
|
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
@@ -2226,6 +2214,242 @@ function runConformanceTests(options) {
|
|
|
2226
2214
|
(0, vitest.expect)(received2).not.toContain(`message 1`);
|
|
2227
2215
|
(0, vitest.expect)(received2).not.toContain(`message 2`);
|
|
2228
2216
|
});
|
|
2217
|
+
(0, vitest.test)(`should auto-detect binary streams and return base64 encoded data in SSE mode`, async () => {
|
|
2218
|
+
const streamPath = `/v1/stream/sse-binary-base64-${Date.now()}`;
|
|
2219
|
+
const binaryData = new Uint8Array([
|
|
2220
|
+
72,
|
|
2221
|
+
101,
|
|
2222
|
+
108,
|
|
2223
|
+
108,
|
|
2224
|
+
111
|
|
2225
|
+
]);
|
|
2226
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2227
|
+
method: `PUT`,
|
|
2228
|
+
headers: { "Content-Type": `application/octet-stream` },
|
|
2229
|
+
body: binaryData
|
|
2230
|
+
});
|
|
2231
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2232
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2233
|
+
(0, vitest.expect)(response.headers.get(`content-type`)).toBe(`text/event-stream`);
|
|
2234
|
+
const events = parseSSEEvents(received);
|
|
2235
|
+
const dataEvents = events.filter((e) => e.type === `data`);
|
|
2236
|
+
const controlEvents = events.filter((e) => e.type === `control`);
|
|
2237
|
+
(0, vitest.expect)(dataEvents.length).toBe(1);
|
|
2238
|
+
(0, vitest.expect)(controlEvents.length).toBe(1);
|
|
2239
|
+
const base64Data = dataEvents[0].data.replace(/[\n\r\s]/g, ``);
|
|
2240
|
+
(0, vitest.expect)(base64Data).toBe(`SGVsbG8=`);
|
|
2241
|
+
const controlData = JSON.parse(controlEvents[0].data);
|
|
2242
|
+
(0, vitest.expect)(controlData.streamNextOffset).toBeDefined();
|
|
2243
|
+
});
|
|
2244
|
+
(0, vitest.test)(`should include Stream-SSE-Data-Encoding header for binary streams`, async () => {
|
|
2245
|
+
const streamPath = `/v1/stream/sse-encoding-header-${Date.now()}`;
|
|
2246
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2247
|
+
method: `PUT`,
|
|
2248
|
+
headers: { "Content-Type": `application/octet-stream` },
|
|
2249
|
+
body: new Uint8Array([
|
|
2250
|
+
1,
|
|
2251
|
+
2,
|
|
2252
|
+
3
|
|
2253
|
+
])
|
|
2254
|
+
});
|
|
2255
|
+
const { response } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2256
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2257
|
+
const encodingHeader = response.headers.get(`stream-sse-data-encoding`);
|
|
2258
|
+
(0, vitest.expect)(encodingHeader).toBe(`base64`);
|
|
2259
|
+
});
|
|
2260
|
+
(0, vitest.test)(`should NOT include Stream-SSE-Data-Encoding header for text/plain streams`, async () => {
|
|
2261
|
+
const streamPath = `/v1/stream/sse-text-no-encoding-${Date.now()}`;
|
|
2262
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2263
|
+
method: `PUT`,
|
|
2264
|
+
headers: { "Content-Type": `text/plain` },
|
|
2265
|
+
body: `hello world`
|
|
2266
|
+
});
|
|
2267
|
+
const { response } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2268
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2269
|
+
const encodingHeader = response.headers.get(`stream-sse-data-encoding`);
|
|
2270
|
+
(0, vitest.expect)(encodingHeader).toBeNull();
|
|
2271
|
+
});
|
|
2272
|
+
(0, vitest.test)(`should NOT include Stream-SSE-Data-Encoding header for application/json streams`, async () => {
|
|
2273
|
+
const streamPath = `/v1/stream/sse-json-no-encoding-${Date.now()}`;
|
|
2274
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2275
|
+
method: `PUT`,
|
|
2276
|
+
headers: { "Content-Type": `application/json` },
|
|
2277
|
+
body: JSON.stringify({ message: `hello` })
|
|
2278
|
+
});
|
|
2279
|
+
const { response } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2280
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2281
|
+
const encodingHeader = response.headers.get(`stream-sse-data-encoding`);
|
|
2282
|
+
(0, vitest.expect)(encodingHeader).toBeNull();
|
|
2283
|
+
});
|
|
2284
|
+
(0, vitest.test)(`should base64 encode data events only, control events remain JSON`, async () => {
|
|
2285
|
+
const streamPath = `/v1/stream/sse-base64-data-only-${Date.now()}`;
|
|
2286
|
+
const binaryData = new Uint8Array([
|
|
2287
|
+
255,
|
|
2288
|
+
254,
|
|
2289
|
+
253
|
|
2290
|
+
]);
|
|
2291
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2292
|
+
method: `PUT`,
|
|
2293
|
+
headers: { "Content-Type": `application/octet-stream` },
|
|
2294
|
+
body: binaryData
|
|
2295
|
+
});
|
|
2296
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2297
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2298
|
+
const events = parseSSEEvents(received);
|
|
2299
|
+
const controlEvents = events.filter((e) => e.type === `control`);
|
|
2300
|
+
(0, vitest.expect)(controlEvents.length).toBe(1);
|
|
2301
|
+
const controlData = JSON.parse(controlEvents[0].data);
|
|
2302
|
+
(0, vitest.expect)(controlData.streamNextOffset).toBeDefined();
|
|
2303
|
+
(0, vitest.expect)(typeof controlData.streamNextOffset).toBe(`string`);
|
|
2304
|
+
(0, vitest.expect)(controlData.streamCursor).toBeDefined();
|
|
2305
|
+
});
|
|
2306
|
+
(0, vitest.test)(`should handle empty binary payload with auto-detected base64 encoding`, async () => {
|
|
2307
|
+
const streamPath = `/v1/stream/sse-base64-empty-${Date.now()}`;
|
|
2308
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2309
|
+
method: `PUT`,
|
|
2310
|
+
headers: { "Content-Type": `application/octet-stream` }
|
|
2311
|
+
});
|
|
2312
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2313
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2314
|
+
const events = parseSSEEvents(received);
|
|
2315
|
+
const controlEvents = events.filter((e) => e.type === `control`);
|
|
2316
|
+
(0, vitest.expect)(controlEvents.length).toBeGreaterThanOrEqual(1);
|
|
2317
|
+
const controlData = JSON.parse(controlEvents[0].data);
|
|
2318
|
+
(0, vitest.expect)(controlData.upToDate).toBe(true);
|
|
2319
|
+
});
|
|
2320
|
+
(0, vitest.test)(`should handle large binary payload with auto-detected base64 encoding`, async () => {
|
|
2321
|
+
const streamPath = `/v1/stream/sse-base64-large-${Date.now()}`;
|
|
2322
|
+
const binaryData = new Uint8Array(1024);
|
|
2323
|
+
for (let i = 0; i < 1024; i++) binaryData[i] = i % 256;
|
|
2324
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2325
|
+
method: `PUT`,
|
|
2326
|
+
headers: { "Content-Type": `application/octet-stream` },
|
|
2327
|
+
body: binaryData
|
|
2328
|
+
});
|
|
2329
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, {
|
|
2330
|
+
untilContent: `event: control`,
|
|
2331
|
+
timeoutMs: 5e3
|
|
2332
|
+
});
|
|
2333
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2334
|
+
const events = parseSSEEvents(received);
|
|
2335
|
+
const dataEvents = events.filter((e) => e.type === `data`);
|
|
2336
|
+
(0, vitest.expect)(dataEvents.length).toBe(1);
|
|
2337
|
+
const base64Data = dataEvents[0].data.replace(/[\n\r\s]/g, ``);
|
|
2338
|
+
const decoded = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0));
|
|
2339
|
+
(0, vitest.expect)(decoded.length).toBe(1024);
|
|
2340
|
+
for (let i = 0; i < 1024; i++) (0, vitest.expect)(decoded[i]).toBe(i % 256);
|
|
2341
|
+
});
|
|
2342
|
+
(0, vitest.test)(`should handle binary data with special bytes using auto-detected base64 encoding`, async () => {
|
|
2343
|
+
const streamPath = `/v1/stream/sse-base64-special-bytes-${Date.now()}`;
|
|
2344
|
+
const binaryData = new Uint8Array([
|
|
2345
|
+
0,
|
|
2346
|
+
10,
|
|
2347
|
+
13,
|
|
2348
|
+
255,
|
|
2349
|
+
254,
|
|
2350
|
+
0,
|
|
2351
|
+
10,
|
|
2352
|
+
13
|
|
2353
|
+
]);
|
|
2354
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2355
|
+
method: `PUT`,
|
|
2356
|
+
headers: { "Content-Type": `application/octet-stream` },
|
|
2357
|
+
body: binaryData
|
|
2358
|
+
});
|
|
2359
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2360
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2361
|
+
const events = parseSSEEvents(received);
|
|
2362
|
+
const dataEvents = events.filter((e) => e.type === `data`);
|
|
2363
|
+
(0, vitest.expect)(dataEvents.length).toBe(1);
|
|
2364
|
+
const base64Data = dataEvents[0].data.replace(/[\n\r\s]/g, ``);
|
|
2365
|
+
const decoded = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0));
|
|
2366
|
+
(0, vitest.expect)(decoded.length).toBe(8);
|
|
2367
|
+
(0, vitest.expect)(decoded[0]).toBe(0);
|
|
2368
|
+
(0, vitest.expect)(decoded[1]).toBe(10);
|
|
2369
|
+
(0, vitest.expect)(decoded[2]).toBe(13);
|
|
2370
|
+
(0, vitest.expect)(decoded[3]).toBe(255);
|
|
2371
|
+
(0, vitest.expect)(decoded[4]).toBe(254);
|
|
2372
|
+
});
|
|
2373
|
+
(0, vitest.test)(`should auto-detect base64 encoding for application/x-protobuf streams`, async () => {
|
|
2374
|
+
const streamPath = `/v1/stream/sse-base64-protobuf-${Date.now()}`;
|
|
2375
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2376
|
+
method: `PUT`,
|
|
2377
|
+
headers: { "Content-Type": `application/x-protobuf` },
|
|
2378
|
+
body: new Uint8Array([
|
|
2379
|
+
8,
|
|
2380
|
+
6,
|
|
2381
|
+
18,
|
|
2382
|
+
4,
|
|
2383
|
+
110,
|
|
2384
|
+
97,
|
|
2385
|
+
109,
|
|
2386
|
+
101
|
|
2387
|
+
])
|
|
2388
|
+
});
|
|
2389
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2390
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2391
|
+
const events = parseSSEEvents(received);
|
|
2392
|
+
const dataEvents = events.filter((e) => e.type === `data`);
|
|
2393
|
+
(0, vitest.expect)(dataEvents.length).toBe(1);
|
|
2394
|
+
const base64Data = dataEvents[0].data.replace(/[\n\r\s]/g, ``);
|
|
2395
|
+
const decoded = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0));
|
|
2396
|
+
(0, vitest.expect)(decoded.length).toBe(8);
|
|
2397
|
+
(0, vitest.expect)(decoded[0]).toBe(8);
|
|
2398
|
+
(0, vitest.expect)(decoded[7]).toBe(101);
|
|
2399
|
+
});
|
|
2400
|
+
(0, vitest.test)(`should auto-detect base64 encoding for image/png streams`, async () => {
|
|
2401
|
+
const streamPath = `/v1/stream/sse-base64-image-${Date.now()}`;
|
|
2402
|
+
const pngHeader = new Uint8Array([
|
|
2403
|
+
137,
|
|
2404
|
+
80,
|
|
2405
|
+
78,
|
|
2406
|
+
71,
|
|
2407
|
+
13,
|
|
2408
|
+
10,
|
|
2409
|
+
26,
|
|
2410
|
+
10
|
|
2411
|
+
]);
|
|
2412
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2413
|
+
method: `PUT`,
|
|
2414
|
+
headers: { "Content-Type": `image/png` },
|
|
2415
|
+
body: pngHeader
|
|
2416
|
+
});
|
|
2417
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { untilContent: `event: control` });
|
|
2418
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2419
|
+
const events = parseSSEEvents(received);
|
|
2420
|
+
const dataEvents = events.filter((e) => e.type === `data`);
|
|
2421
|
+
(0, vitest.expect)(dataEvents.length).toBe(1);
|
|
2422
|
+
const base64Data = dataEvents[0].data.replace(/[\n\r\s]/g, ``);
|
|
2423
|
+
const decoded = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0));
|
|
2424
|
+
(0, vitest.expect)(decoded.length).toBe(8);
|
|
2425
|
+
(0, vitest.expect)(decoded[0]).toBe(137);
|
|
2426
|
+
(0, vitest.expect)(decoded[1]).toBe(80);
|
|
2427
|
+
(0, vitest.expect)(decoded[2]).toBe(78);
|
|
2428
|
+
(0, vitest.expect)(decoded[3]).toBe(71);
|
|
2429
|
+
});
|
|
2430
|
+
(0, vitest.test)(`should handle offset=now with auto-detected base64 encoding for binary streams`, async () => {
|
|
2431
|
+
const streamPath = `/v1/stream/sse-base64-offset-now-${Date.now()}`;
|
|
2432
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2433
|
+
method: `PUT`,
|
|
2434
|
+
headers: { "Content-Type": `application/octet-stream` },
|
|
2435
|
+
body: new Uint8Array([
|
|
2436
|
+
1,
|
|
2437
|
+
2,
|
|
2438
|
+
3
|
|
2439
|
+
])
|
|
2440
|
+
});
|
|
2441
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=now&live=sse`, { untilContent: `"upToDate"` });
|
|
2442
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
2443
|
+
const controlMatch = received.match(/event: control\s*\n\s*data:({[^}]+})/);
|
|
2444
|
+
(0, vitest.expect)(controlMatch).toBeDefined();
|
|
2445
|
+
if (controlMatch && controlMatch[1]) {
|
|
2446
|
+
const controlData = JSON.parse(controlMatch[1]);
|
|
2447
|
+
(0, vitest.expect)(controlData[`upToDate`]).toBe(true);
|
|
2448
|
+
}
|
|
2449
|
+
const events = parseSSEEvents(received);
|
|
2450
|
+
const dataEvents = events.filter((e) => e.type === `data`);
|
|
2451
|
+
(0, vitest.expect)(dataEvents.length).toBe(0);
|
|
2452
|
+
});
|
|
2229
2453
|
});
|
|
2230
2454
|
(0, vitest.describe)(`JSON Mode`, () => {
|
|
2231
2455
|
(0, vitest.test)(`should allow PUT with empty array body (creates empty stream)`, async () => {
|
|
@@ -2747,7 +2971,7 @@ function runConformanceTests(options) {
|
|
|
2747
2971
|
return true;
|
|
2748
2972
|
}
|
|
2749
2973
|
), { numRuns: 25 });
|
|
2750
|
-
}
|
|
2974
|
+
});
|
|
2751
2975
|
(0, vitest.test)(`read-your-writes: data is immediately visible after append`, async () => {
|
|
2752
2976
|
await fast_check.assert(fast_check.asyncProperty(fast_check.uint8Array({
|
|
2753
2977
|
minLength: 1,
|
|
@@ -3087,7 +3311,7 @@ function runConformanceTests(options) {
|
|
|
3087
3311
|
return true;
|
|
3088
3312
|
}
|
|
3089
3313
|
), { numRuns: 15 });
|
|
3090
|
-
}
|
|
3314
|
+
});
|
|
3091
3315
|
(0, vitest.test)(`content hash changes with each append`, async () => {
|
|
3092
3316
|
const streamPath = `/v1/stream/hash-changes-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3093
3317
|
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
@@ -4080,6 +4304,724 @@ function runConformanceTests(options) {
|
|
|
4080
4304
|
(0, vitest.expect)(r.status).toBe(400);
|
|
4081
4305
|
});
|
|
4082
4306
|
});
|
|
4307
|
+
(0, vitest.describe)(`Stream Closure`, () => {
|
|
4308
|
+
const STREAM_CLOSED_HEADER = `Stream-Closed`;
|
|
4309
|
+
(0, vitest.describe)(`Create with Stream-Closed`, () => {
|
|
4310
|
+
(0, vitest.test)(`create-closed-stream: PUT with Stream-Closed: true creates closed stream`, async () => {
|
|
4311
|
+
const streamPath = `/v1/stream/create-closed-${Date.now()}`;
|
|
4312
|
+
const response = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4313
|
+
method: `PUT`,
|
|
4314
|
+
headers: {
|
|
4315
|
+
"Content-Type": `text/plain`,
|
|
4316
|
+
[STREAM_CLOSED_HEADER]: `true`
|
|
4317
|
+
}
|
|
4318
|
+
});
|
|
4319
|
+
(0, vitest.expect)(response.status).toBe(201);
|
|
4320
|
+
(0, vitest.expect)(response.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4321
|
+
(0, vitest.expect)(response.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER)).toBeTruthy();
|
|
4322
|
+
});
|
|
4323
|
+
(0, vitest.test)(`create-closed-stream-with-body: PUT with body + Stream-Closed: true`, async () => {
|
|
4324
|
+
const streamPath = `/v1/stream/create-closed-body-${Date.now()}`;
|
|
4325
|
+
const response = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4326
|
+
method: `PUT`,
|
|
4327
|
+
headers: {
|
|
4328
|
+
"Content-Type": `text/plain`,
|
|
4329
|
+
[STREAM_CLOSED_HEADER]: `true`
|
|
4330
|
+
},
|
|
4331
|
+
body: `initial content`
|
|
4332
|
+
});
|
|
4333
|
+
(0, vitest.expect)(response.status).toBe(201);
|
|
4334
|
+
(0, vitest.expect)(response.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4335
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}`);
|
|
4336
|
+
const content = await readResponse.text();
|
|
4337
|
+
(0, vitest.expect)(content).toBe(`initial content`);
|
|
4338
|
+
(0, vitest.expect)(readResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4339
|
+
});
|
|
4340
|
+
(0, vitest.test)(`create-closed-returns-header: Response includes Stream-Closed: true`, async () => {
|
|
4341
|
+
const streamPath = `/v1/stream/create-closed-header-${Date.now()}`;
|
|
4342
|
+
const response = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4343
|
+
method: `PUT`,
|
|
4344
|
+
headers: {
|
|
4345
|
+
"Content-Type": `text/plain`,
|
|
4346
|
+
[STREAM_CLOSED_HEADER]: `true`
|
|
4347
|
+
}
|
|
4348
|
+
});
|
|
4349
|
+
(0, vitest.expect)(response.status).toBe(201);
|
|
4350
|
+
(0, vitest.expect)(response.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4351
|
+
});
|
|
4352
|
+
});
|
|
4353
|
+
(0, vitest.describe)(`Close Operations`, () => {
|
|
4354
|
+
(0, vitest.test)(`close-stream-empty-post: POST with Stream-Closed: true, empty body closes stream`, async () => {
|
|
4355
|
+
const streamPath = `/v1/stream/close-empty-${Date.now()}`;
|
|
4356
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4357
|
+
method: `PUT`,
|
|
4358
|
+
headers: { "Content-Type": `text/plain` }
|
|
4359
|
+
});
|
|
4360
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4361
|
+
method: `POST`,
|
|
4362
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4363
|
+
});
|
|
4364
|
+
(0, vitest.expect)(closeResponse.status).toBe(204);
|
|
4365
|
+
(0, vitest.expect)(closeResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4366
|
+
});
|
|
4367
|
+
(0, vitest.test)(`close-with-final-append: POST with body + Stream-Closed: true`, async () => {
|
|
4368
|
+
const streamPath = `/v1/stream/close-final-${Date.now()}`;
|
|
4369
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4370
|
+
method: `PUT`,
|
|
4371
|
+
headers: { "Content-Type": `text/plain` }
|
|
4372
|
+
});
|
|
4373
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4374
|
+
method: `POST`,
|
|
4375
|
+
headers: { "Content-Type": `text/plain` },
|
|
4376
|
+
body: `first message`
|
|
4377
|
+
});
|
|
4378
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4379
|
+
method: `POST`,
|
|
4380
|
+
headers: {
|
|
4381
|
+
"Content-Type": `text/plain`,
|
|
4382
|
+
[STREAM_CLOSED_HEADER]: `true`
|
|
4383
|
+
},
|
|
4384
|
+
body: `final message`
|
|
4385
|
+
});
|
|
4386
|
+
(0, vitest.expect)(closeResponse.status).toBe(204);
|
|
4387
|
+
(0, vitest.expect)(closeResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4388
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}`);
|
|
4389
|
+
const content = await readResponse.text();
|
|
4390
|
+
(0, vitest.expect)(content).toBe(`first messagefinal message`);
|
|
4391
|
+
});
|
|
4392
|
+
(0, vitest.test)(`close-returns-offset-and-header: Response includes Stream-Next-Offset and Stream-Closed: true`, async () => {
|
|
4393
|
+
const streamPath = `/v1/stream/close-returns-${Date.now()}`;
|
|
4394
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4395
|
+
method: `PUT`,
|
|
4396
|
+
headers: { "Content-Type": `text/plain` },
|
|
4397
|
+
body: `content`
|
|
4398
|
+
});
|
|
4399
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4400
|
+
method: `POST`,
|
|
4401
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4402
|
+
});
|
|
4403
|
+
(0, vitest.expect)(closeResponse.status).toBe(204);
|
|
4404
|
+
(0, vitest.expect)(closeResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER)).toBeTruthy();
|
|
4405
|
+
(0, vitest.expect)(closeResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4406
|
+
});
|
|
4407
|
+
(0, vitest.test)(`close-idempotent: Closing already-closed stream (empty body) returns 204`, async () => {
|
|
4408
|
+
const streamPath = `/v1/stream/close-idempotent-${Date.now()}`;
|
|
4409
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4410
|
+
method: `PUT`,
|
|
4411
|
+
headers: { "Content-Type": `text/plain` }
|
|
4412
|
+
});
|
|
4413
|
+
const firstClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4414
|
+
method: `POST`,
|
|
4415
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4416
|
+
});
|
|
4417
|
+
(0, vitest.expect)(firstClose.status).toBe(204);
|
|
4418
|
+
const secondClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4419
|
+
method: `POST`,
|
|
4420
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4421
|
+
});
|
|
4422
|
+
(0, vitest.expect)(secondClose.status).toBe(204);
|
|
4423
|
+
(0, vitest.expect)(secondClose.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4424
|
+
});
|
|
4425
|
+
(0, vitest.test)(`close-only-ignores-content-type: Close-only with mismatched Content-Type still succeeds`, async () => {
|
|
4426
|
+
const streamPath = `/v1/stream/close-ignores-ct-${Date.now()}`;
|
|
4427
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4428
|
+
method: `PUT`,
|
|
4429
|
+
headers: { "Content-Type": `application/json` }
|
|
4430
|
+
});
|
|
4431
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4432
|
+
method: `POST`,
|
|
4433
|
+
headers: {
|
|
4434
|
+
"Content-Type": `text/plain`,
|
|
4435
|
+
[STREAM_CLOSED_HEADER]: `true`
|
|
4436
|
+
}
|
|
4437
|
+
});
|
|
4438
|
+
(0, vitest.expect)(closeResponse.status).toBe(204);
|
|
4439
|
+
(0, vitest.expect)(closeResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4440
|
+
});
|
|
4441
|
+
(0, vitest.test)(`append-to-closed-stream-409: Append to closed stream returns 409 with Stream-Closed: true header`, async () => {
|
|
4442
|
+
const streamPath = `/v1/stream/append-closed-${Date.now()}`;
|
|
4443
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4444
|
+
method: `PUT`,
|
|
4445
|
+
headers: { "Content-Type": `text/plain` }
|
|
4446
|
+
});
|
|
4447
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4448
|
+
method: `POST`,
|
|
4449
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4450
|
+
});
|
|
4451
|
+
const appendResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4452
|
+
method: `POST`,
|
|
4453
|
+
headers: { "Content-Type": `text/plain` },
|
|
4454
|
+
body: `should fail`
|
|
4455
|
+
});
|
|
4456
|
+
(0, vitest.expect)(appendResponse.status).toBe(409);
|
|
4457
|
+
(0, vitest.expect)(appendResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4458
|
+
});
|
|
4459
|
+
(0, vitest.test)(`append-and-close-to-closed-stream-409: POST with body + Stream-Closed: true to already-closed stream returns 409`, async () => {
|
|
4460
|
+
const streamPath = `/v1/stream/append-close-closed-${Date.now()}`;
|
|
4461
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4462
|
+
method: `PUT`,
|
|
4463
|
+
headers: { "Content-Type": `text/plain` }
|
|
4464
|
+
});
|
|
4465
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4466
|
+
method: `POST`,
|
|
4467
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4468
|
+
});
|
|
4469
|
+
const response = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4470
|
+
method: `POST`,
|
|
4471
|
+
headers: {
|
|
4472
|
+
"Content-Type": `text/plain`,
|
|
4473
|
+
[STREAM_CLOSED_HEADER]: `true`
|
|
4474
|
+
},
|
|
4475
|
+
body: `should fail`
|
|
4476
|
+
});
|
|
4477
|
+
(0, vitest.expect)(response.status).toBe(409);
|
|
4478
|
+
(0, vitest.expect)(response.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4479
|
+
});
|
|
4480
|
+
});
|
|
4481
|
+
(0, vitest.describe)(`HEAD with Stream Closure`, () => {
|
|
4482
|
+
(0, vitest.test)(`head-closed-stream: HEAD returns Stream-Closed: true header`, async () => {
|
|
4483
|
+
const streamPath = `/v1/stream/head-closed-${Date.now()}`;
|
|
4484
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4485
|
+
method: `PUT`,
|
|
4486
|
+
headers: { "Content-Type": `text/plain` }
|
|
4487
|
+
});
|
|
4488
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4489
|
+
method: `POST`,
|
|
4490
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4491
|
+
});
|
|
4492
|
+
const headResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
4493
|
+
(0, vitest.expect)(headResponse.status).toBe(200);
|
|
4494
|
+
(0, vitest.expect)(headResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4495
|
+
});
|
|
4496
|
+
(0, vitest.test)(`head-open-stream-no-closed-header: HEAD on open stream does NOT have Stream-Closed header`, async () => {
|
|
4497
|
+
const streamPath = `/v1/stream/head-open-${Date.now()}`;
|
|
4498
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4499
|
+
method: `PUT`,
|
|
4500
|
+
headers: { "Content-Type": `text/plain` }
|
|
4501
|
+
});
|
|
4502
|
+
const headResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
4503
|
+
(0, vitest.expect)(headResponse.status).toBe(200);
|
|
4504
|
+
(0, vitest.expect)(headResponse.headers.get(STREAM_CLOSED_HEADER)).toBeNull();
|
|
4505
|
+
});
|
|
4506
|
+
});
|
|
4507
|
+
(0, vitest.describe)(`Read Closed Streams (Catch-up)`, () => {
|
|
4508
|
+
(0, vitest.test)(`read-closed-stream-at-tail: Returns Stream-Closed: true at tail of closed stream`, async () => {
|
|
4509
|
+
const streamPath = `/v1/stream/read-closed-tail-${Date.now()}`;
|
|
4510
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4511
|
+
method: `PUT`,
|
|
4512
|
+
headers: { "Content-Type": `text/plain` },
|
|
4513
|
+
body: `content`
|
|
4514
|
+
});
|
|
4515
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4516
|
+
method: `POST`,
|
|
4517
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4518
|
+
});
|
|
4519
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}`);
|
|
4520
|
+
(0, vitest.expect)(readResponse.status).toBe(200);
|
|
4521
|
+
(0, vitest.expect)(await readResponse.text()).toBe(`content`);
|
|
4522
|
+
(0, vitest.expect)(readResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4523
|
+
(0, vitest.expect)(readResponse.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
4524
|
+
});
|
|
4525
|
+
(0, vitest.test)(`read-closed-stream-partial-no-closed: Partial read of closed stream does NOT include Stream-Closed`, async () => {
|
|
4526
|
+
const streamPath = `/v1/stream/read-closed-partial-${Date.now()}`;
|
|
4527
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4528
|
+
method: `PUT`,
|
|
4529
|
+
headers: { "Content-Type": `text/plain` },
|
|
4530
|
+
body: `first`
|
|
4531
|
+
});
|
|
4532
|
+
const appendResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4533
|
+
method: `POST`,
|
|
4534
|
+
headers: { "Content-Type": `text/plain` },
|
|
4535
|
+
body: `second`
|
|
4536
|
+
});
|
|
4537
|
+
const secondOffset = appendResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4538
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4539
|
+
method: `POST`,
|
|
4540
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4541
|
+
});
|
|
4542
|
+
const partialRead = await fetch(`${getBaseUrl()}${streamPath}?offset=-1`);
|
|
4543
|
+
const partialContent = await partialRead.text();
|
|
4544
|
+
const nextOffset = partialRead.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4545
|
+
if (nextOffset === secondOffset) (0, vitest.expect)(partialRead.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4546
|
+
else if (partialContent.length < `firstsecond`.length && partialContent.length > 0) (0, vitest.expect)(partialRead.headers.get(STREAM_CLOSED_HEADER)).toBeNull();
|
|
4547
|
+
if (partialContent === `firstsecond`) (0, vitest.expect)(partialRead.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4548
|
+
});
|
|
4549
|
+
(0, vitest.test)(`read-closed-stream-empty-body-eof: At tail of closed stream: 200 OK, empty body, Stream-Closed: true`, async () => {
|
|
4550
|
+
const streamPath = `/v1/stream/read-closed-eof-${Date.now()}`;
|
|
4551
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4552
|
+
method: `PUT`,
|
|
4553
|
+
headers: { "Content-Type": `text/plain` },
|
|
4554
|
+
body: `content`
|
|
4555
|
+
});
|
|
4556
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4557
|
+
method: `POST`,
|
|
4558
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4559
|
+
});
|
|
4560
|
+
const tailOffset = closeResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4561
|
+
const eofRead = await fetch(`${getBaseUrl()}${streamPath}?offset=${tailOffset}`);
|
|
4562
|
+
(0, vitest.expect)(eofRead.status).toBe(200);
|
|
4563
|
+
(0, vitest.expect)(await eofRead.text()).toBe(``);
|
|
4564
|
+
(0, vitest.expect)(eofRead.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4565
|
+
(0, vitest.expect)(eofRead.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
4566
|
+
});
|
|
4567
|
+
});
|
|
4568
|
+
(0, vitest.describe)(`Long-poll with Stream Closure`, () => {
|
|
4569
|
+
(0, vitest.test)(`longpoll-closed-stream-immediate: No wait when closed stream at tail, returns immediately`, async () => {
|
|
4570
|
+
const streamPath = `/v1/stream/longpoll-closed-${Date.now()}`;
|
|
4571
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4572
|
+
method: `PUT`,
|
|
4573
|
+
headers: { "Content-Type": `text/plain` }
|
|
4574
|
+
});
|
|
4575
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4576
|
+
method: `POST`,
|
|
4577
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4578
|
+
});
|
|
4579
|
+
const tailOffset = closeResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4580
|
+
const startTime = Date.now();
|
|
4581
|
+
const longpollResponse = await fetch(`${getBaseUrl()}${streamPath}?offset=${tailOffset}&live=long-poll`);
|
|
4582
|
+
const elapsed = Date.now() - startTime;
|
|
4583
|
+
(0, vitest.expect)(longpollResponse.status).toBe(204);
|
|
4584
|
+
(0, vitest.expect)(longpollResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4585
|
+
(0, vitest.expect)(elapsed).toBeLessThan(5e3);
|
|
4586
|
+
}, getLongPollTestTimeoutMs());
|
|
4587
|
+
(0, vitest.test)(`longpoll-closed-returns-204-with-header: Returns 204 with Stream-Closed: true`, async () => {
|
|
4588
|
+
const streamPath = `/v1/stream/longpoll-closed-204-${Date.now()}`;
|
|
4589
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4590
|
+
method: `PUT`,
|
|
4591
|
+
headers: { "Content-Type": `text/plain` },
|
|
4592
|
+
body: `data`
|
|
4593
|
+
});
|
|
4594
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4595
|
+
method: `POST`,
|
|
4596
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4597
|
+
});
|
|
4598
|
+
const tailOffset = closeResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4599
|
+
const response = await fetch(`${getBaseUrl()}${streamPath}?offset=${tailOffset}&live=long-poll`);
|
|
4600
|
+
(0, vitest.expect)(response.status).toBe(204);
|
|
4601
|
+
(0, vitest.expect)(response.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4602
|
+
(0, vitest.expect)(response.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
4603
|
+
(0, vitest.expect)(response.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER)).toBeTruthy();
|
|
4604
|
+
}, getLongPollTestTimeoutMs());
|
|
4605
|
+
});
|
|
4606
|
+
(0, vitest.describe)(`SSE with Stream Closure`, () => {
|
|
4607
|
+
(0, vitest.test)(`sse-closed-stream-control-event: Final control event has streamClosed: true`, async () => {
|
|
4608
|
+
const streamPath = `/v1/stream/sse-closed-control-${Date.now()}`;
|
|
4609
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4610
|
+
method: `PUT`,
|
|
4611
|
+
headers: { "Content-Type": `text/plain` },
|
|
4612
|
+
body: `content`
|
|
4613
|
+
});
|
|
4614
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4615
|
+
method: `POST`,
|
|
4616
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4617
|
+
});
|
|
4618
|
+
const tailOffset = closeResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4619
|
+
const { received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=${tailOffset}&live=sse`, {
|
|
4620
|
+
timeoutMs: 5e3,
|
|
4621
|
+
untilContent: `streamClosed`
|
|
4622
|
+
});
|
|
4623
|
+
const events = parseSSEEvents(received);
|
|
4624
|
+
const controlEvents = events.filter((e) => e.type === `control`);
|
|
4625
|
+
(0, vitest.expect)(controlEvents.length).toBeGreaterThan(0);
|
|
4626
|
+
const lastControl = controlEvents[controlEvents.length - 1];
|
|
4627
|
+
const controlData = JSON.parse(lastControl.data);
|
|
4628
|
+
(0, vitest.expect)(controlData.streamClosed).toBe(true);
|
|
4629
|
+
});
|
|
4630
|
+
(0, vitest.test)(`sse-closed-stream-no-cursor: streamCursor omitted when streamClosed is true`, async () => {
|
|
4631
|
+
const streamPath = `/v1/stream/sse-closed-no-cursor-${Date.now()}`;
|
|
4632
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4633
|
+
method: `PUT`,
|
|
4634
|
+
headers: { "Content-Type": `text/plain` }
|
|
4635
|
+
});
|
|
4636
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4637
|
+
method: `POST`,
|
|
4638
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4639
|
+
});
|
|
4640
|
+
const tailOffset = closeResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4641
|
+
const { received } = await fetchSSE(`${getBaseUrl()}${streamPath}?offset=${tailOffset}&live=sse`, {
|
|
4642
|
+
timeoutMs: 5e3,
|
|
4643
|
+
untilContent: `streamClosed`
|
|
4644
|
+
});
|
|
4645
|
+
const events = parseSSEEvents(received);
|
|
4646
|
+
const controlEvents = events.filter((e) => e.type === `control`);
|
|
4647
|
+
(0, vitest.expect)(controlEvents.length).toBeGreaterThan(0);
|
|
4648
|
+
const lastControl = controlEvents[controlEvents.length - 1];
|
|
4649
|
+
const controlData = JSON.parse(lastControl.data);
|
|
4650
|
+
(0, vitest.expect)(controlData.streamClosed).toBe(true);
|
|
4651
|
+
(0, vitest.expect)(controlData.streamCursor).toBeUndefined();
|
|
4652
|
+
});
|
|
4653
|
+
(0, vitest.test)(`sse-closed-stream-connection-closes: Connection closes after final event`, async () => {
|
|
4654
|
+
const streamPath = `/v1/stream/sse-closed-conn-${Date.now()}`;
|
|
4655
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4656
|
+
method: `PUT`,
|
|
4657
|
+
headers: { "Content-Type": `text/plain` },
|
|
4658
|
+
body: `data`
|
|
4659
|
+
});
|
|
4660
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4661
|
+
method: `POST`,
|
|
4662
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4663
|
+
});
|
|
4664
|
+
const controller = new AbortController();
|
|
4665
|
+
const startTime = Date.now();
|
|
4666
|
+
try {
|
|
4667
|
+
const response = await fetch(`${getBaseUrl()}${streamPath}?offset=-1&live=sse`, { signal: controller.signal });
|
|
4668
|
+
if (response.body) {
|
|
4669
|
+
const reader = response.body.getReader();
|
|
4670
|
+
let received = ``;
|
|
4671
|
+
let chunkCount = 0;
|
|
4672
|
+
while (chunkCount < 20) {
|
|
4673
|
+
const { done, value } = await reader.read();
|
|
4674
|
+
if (done) break;
|
|
4675
|
+
received += new TextDecoder().decode(value);
|
|
4676
|
+
chunkCount++;
|
|
4677
|
+
}
|
|
4678
|
+
(0, vitest.expect)(received).toContain(`streamClosed`);
|
|
4679
|
+
}
|
|
4680
|
+
} catch {} finally {
|
|
4681
|
+
controller.abort();
|
|
4682
|
+
}
|
|
4683
|
+
const elapsed = Date.now() - startTime;
|
|
4684
|
+
(0, vitest.expect)(elapsed).toBeLessThan(1e4);
|
|
4685
|
+
});
|
|
4686
|
+
});
|
|
4687
|
+
(0, vitest.describe)(`Idempotent Producers with Stream Closure`, () => {
|
|
4688
|
+
const PRODUCER_ID_HEADER = `Producer-Id`;
|
|
4689
|
+
const PRODUCER_EPOCH_HEADER = `Producer-Epoch`;
|
|
4690
|
+
const PRODUCER_SEQ_HEADER = `Producer-Seq`;
|
|
4691
|
+
(0, vitest.test)(`idempotent-close-with-append: Close with final append using producer headers`, async () => {
|
|
4692
|
+
const streamPath = `/v1/stream/idempotent-close-append-${Date.now()}`;
|
|
4693
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4694
|
+
method: `PUT`,
|
|
4695
|
+
headers: { "Content-Type": `text/plain` }
|
|
4696
|
+
});
|
|
4697
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4698
|
+
method: `POST`,
|
|
4699
|
+
headers: {
|
|
4700
|
+
"Content-Type": `text/plain`,
|
|
4701
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4702
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4703
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4704
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4705
|
+
},
|
|
4706
|
+
body: `final message`
|
|
4707
|
+
});
|
|
4708
|
+
(0, vitest.expect)(closeResponse.status).toBe(200);
|
|
4709
|
+
(0, vitest.expect)(closeResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4710
|
+
(0, vitest.expect)(closeResponse.headers.get(PRODUCER_EPOCH_HEADER)).toBe(`0`);
|
|
4711
|
+
(0, vitest.expect)(closeResponse.headers.get(PRODUCER_SEQ_HEADER)).toBe(`0`);
|
|
4712
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}`);
|
|
4713
|
+
(0, vitest.expect)(await readResponse.text()).toBe(`final message`);
|
|
4714
|
+
});
|
|
4715
|
+
(0, vitest.test)(`idempotent-close-only-with-producer-headers: Close-only with producer headers updates state`, async () => {
|
|
4716
|
+
const streamPath = `/v1/stream/idempotent-close-only-${Date.now()}`;
|
|
4717
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4718
|
+
method: `PUT`,
|
|
4719
|
+
headers: { "Content-Type": `text/plain` }
|
|
4720
|
+
});
|
|
4721
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4722
|
+
method: `POST`,
|
|
4723
|
+
headers: {
|
|
4724
|
+
"Content-Type": `text/plain`,
|
|
4725
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4726
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4727
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4728
|
+
},
|
|
4729
|
+
body: `message`
|
|
4730
|
+
});
|
|
4731
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4732
|
+
method: `POST`,
|
|
4733
|
+
headers: {
|
|
4734
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4735
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4736
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4737
|
+
[PRODUCER_SEQ_HEADER]: `1`
|
|
4738
|
+
}
|
|
4739
|
+
});
|
|
4740
|
+
(0, vitest.expect)(closeResponse.status).toBe(204);
|
|
4741
|
+
(0, vitest.expect)(closeResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4742
|
+
(0, vitest.expect)(closeResponse.headers.get(PRODUCER_EPOCH_HEADER)).toBe(`0`);
|
|
4743
|
+
(0, vitest.expect)(closeResponse.headers.get(PRODUCER_SEQ_HEADER)).toBe(`1`);
|
|
4744
|
+
});
|
|
4745
|
+
(0, vitest.test)(`idempotent-close-duplicate-returns-204: Duplicate close (same tuple) returns 204`, async () => {
|
|
4746
|
+
const streamPath = `/v1/stream/idempotent-close-dup-${Date.now()}`;
|
|
4747
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4748
|
+
method: `PUT`,
|
|
4749
|
+
headers: { "Content-Type": `text/plain` }
|
|
4750
|
+
});
|
|
4751
|
+
const firstClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4752
|
+
method: `POST`,
|
|
4753
|
+
headers: {
|
|
4754
|
+
"Content-Type": `text/plain`,
|
|
4755
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4756
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4757
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4758
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4759
|
+
},
|
|
4760
|
+
body: `final`
|
|
4761
|
+
});
|
|
4762
|
+
(0, vitest.expect)(firstClose.status).toBe(200);
|
|
4763
|
+
const duplicateClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4764
|
+
method: `POST`,
|
|
4765
|
+
headers: {
|
|
4766
|
+
"Content-Type": `text/plain`,
|
|
4767
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4768
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4769
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4770
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4771
|
+
},
|
|
4772
|
+
body: `final`
|
|
4773
|
+
});
|
|
4774
|
+
(0, vitest.expect)(duplicateClose.status).toBe(204);
|
|
4775
|
+
(0, vitest.expect)(duplicateClose.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4776
|
+
});
|
|
4777
|
+
(0, vitest.test)(`idempotent-close-different-tuple-returns-409: Different producer/seq gets 409`, async () => {
|
|
4778
|
+
const streamPath = `/v1/stream/idempotent-close-diff-${Date.now()}`;
|
|
4779
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4780
|
+
method: `PUT`,
|
|
4781
|
+
headers: { "Content-Type": `text/plain` }
|
|
4782
|
+
});
|
|
4783
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4784
|
+
method: `POST`,
|
|
4785
|
+
headers: {
|
|
4786
|
+
"Content-Type": `text/plain`,
|
|
4787
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4788
|
+
[PRODUCER_ID_HEADER]: `producer-A`,
|
|
4789
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4790
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4791
|
+
},
|
|
4792
|
+
body: `final`
|
|
4793
|
+
});
|
|
4794
|
+
const differentProducer = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4795
|
+
method: `POST`,
|
|
4796
|
+
headers: {
|
|
4797
|
+
"Content-Type": `text/plain`,
|
|
4798
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4799
|
+
[PRODUCER_ID_HEADER]: `producer-B`,
|
|
4800
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4801
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4802
|
+
},
|
|
4803
|
+
body: `should fail`
|
|
4804
|
+
});
|
|
4805
|
+
(0, vitest.expect)(differentProducer.status).toBe(409);
|
|
4806
|
+
(0, vitest.expect)(differentProducer.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4807
|
+
});
|
|
4808
|
+
(0, vitest.test)(`idempotent-close-different-seq-returns-409: Same producer, different seq gets 409`, async () => {
|
|
4809
|
+
const streamPath = `/v1/stream/idempotent-close-diff-seq-${Date.now()}`;
|
|
4810
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4811
|
+
method: `PUT`,
|
|
4812
|
+
headers: { "Content-Type": `text/plain` }
|
|
4813
|
+
});
|
|
4814
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4815
|
+
method: `POST`,
|
|
4816
|
+
headers: {
|
|
4817
|
+
"Content-Type": `text/plain`,
|
|
4818
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4819
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4820
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4821
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4822
|
+
},
|
|
4823
|
+
body: `final`
|
|
4824
|
+
});
|
|
4825
|
+
const differentSeq = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4826
|
+
method: `POST`,
|
|
4827
|
+
headers: {
|
|
4828
|
+
"Content-Type": `text/plain`,
|
|
4829
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4830
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4831
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4832
|
+
[PRODUCER_SEQ_HEADER]: `1`
|
|
4833
|
+
},
|
|
4834
|
+
body: `should fail`
|
|
4835
|
+
});
|
|
4836
|
+
(0, vitest.expect)(differentSeq.status).toBe(409);
|
|
4837
|
+
(0, vitest.expect)(differentSeq.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4838
|
+
});
|
|
4839
|
+
(0, vitest.test)(`idempotent-close-only-duplicate-returns-204: Duplicate close-only (no body) returns 204`, async () => {
|
|
4840
|
+
const streamPath = `/v1/stream/idempotent-close-only-dup-${Date.now()}`;
|
|
4841
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4842
|
+
method: `PUT`,
|
|
4843
|
+
headers: { "Content-Type": `text/plain` }
|
|
4844
|
+
});
|
|
4845
|
+
const firstClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4846
|
+
method: `POST`,
|
|
4847
|
+
headers: {
|
|
4848
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4849
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4850
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4851
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4852
|
+
}
|
|
4853
|
+
});
|
|
4854
|
+
(0, vitest.expect)(firstClose.status).toBe(204);
|
|
4855
|
+
const duplicateClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4856
|
+
method: `POST`,
|
|
4857
|
+
headers: {
|
|
4858
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4859
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4860
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4861
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4862
|
+
}
|
|
4863
|
+
});
|
|
4864
|
+
(0, vitest.expect)(duplicateClose.status).toBe(204);
|
|
4865
|
+
(0, vitest.expect)(duplicateClose.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4866
|
+
});
|
|
4867
|
+
});
|
|
4868
|
+
(0, vitest.describe)(`Edge Cases`, () => {
|
|
4869
|
+
const PRODUCER_ID_HEADER = `Producer-Id`;
|
|
4870
|
+
const PRODUCER_EPOCH_HEADER = `Producer-Epoch`;
|
|
4871
|
+
const PRODUCER_SEQ_HEADER = `Producer-Seq`;
|
|
4872
|
+
(0, vitest.test)(`409-includes-stream-offset: 409 for closed stream includes Stream-Next-Offset header`, async () => {
|
|
4873
|
+
const streamPath = `/v1/stream/409-offset-${Date.now()}`;
|
|
4874
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4875
|
+
method: `PUT`,
|
|
4876
|
+
headers: { "Content-Type": `text/plain` },
|
|
4877
|
+
body: `some content`
|
|
4878
|
+
});
|
|
4879
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4880
|
+
method: `POST`,
|
|
4881
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4882
|
+
});
|
|
4883
|
+
const finalOffset = closeResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
4884
|
+
(0, vitest.expect)(finalOffset).toBeTruthy();
|
|
4885
|
+
const appendResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4886
|
+
method: `POST`,
|
|
4887
|
+
headers: { "Content-Type": `text/plain` },
|
|
4888
|
+
body: `should fail`
|
|
4889
|
+
});
|
|
4890
|
+
(0, vitest.expect)(appendResponse.status).toBe(409);
|
|
4891
|
+
(0, vitest.expect)(appendResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4892
|
+
(0, vitest.expect)(appendResponse.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER)).toBe(finalOffset);
|
|
4893
|
+
});
|
|
4894
|
+
(0, vitest.test)(`close-nonexistent-stream-404: POST with Stream-Closed to nonexistent stream returns 404`, async () => {
|
|
4895
|
+
const streamPath = `/v1/stream/nonexistent-close-${Date.now()}`;
|
|
4896
|
+
const closeResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4897
|
+
method: `POST`,
|
|
4898
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4899
|
+
});
|
|
4900
|
+
(0, vitest.expect)(closeResponse.status).toBe(404);
|
|
4901
|
+
});
|
|
4902
|
+
(0, vitest.test)(`offset-now-on-closed-stream: offset=now on closed stream returns Stream-Closed: true`, async () => {
|
|
4903
|
+
const streamPath = `/v1/stream/offset-now-closed-${Date.now()}`;
|
|
4904
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4905
|
+
method: `PUT`,
|
|
4906
|
+
headers: { "Content-Type": `text/plain` },
|
|
4907
|
+
body: `content`
|
|
4908
|
+
});
|
|
4909
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4910
|
+
method: `POST`,
|
|
4911
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
4912
|
+
});
|
|
4913
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}?offset=now`);
|
|
4914
|
+
(0, vitest.expect)(readResponse.status).toBe(200);
|
|
4915
|
+
(0, vitest.expect)(readResponse.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
4916
|
+
(0, vitest.expect)(readResponse.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
4917
|
+
});
|
|
4918
|
+
(0, vitest.test)(`producer-state-survives-close: Stale-epoch producer gets 403, not 409 STREAM_CLOSED`, async () => {
|
|
4919
|
+
const streamPath = `/v1/stream/producer-state-close-${Date.now()}`;
|
|
4920
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4921
|
+
method: `PUT`,
|
|
4922
|
+
headers: { "Content-Type": `text/plain` }
|
|
4923
|
+
});
|
|
4924
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4925
|
+
method: `POST`,
|
|
4926
|
+
headers: {
|
|
4927
|
+
"Content-Type": `text/plain`,
|
|
4928
|
+
[PRODUCER_ID_HEADER]: `producer-A`,
|
|
4929
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4930
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4931
|
+
},
|
|
4932
|
+
body: `first`
|
|
4933
|
+
});
|
|
4934
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4935
|
+
method: `POST`,
|
|
4936
|
+
headers: {
|
|
4937
|
+
"Content-Type": `text/plain`,
|
|
4938
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4939
|
+
[PRODUCER_ID_HEADER]: `producer-A`,
|
|
4940
|
+
[PRODUCER_EPOCH_HEADER]: `1`,
|
|
4941
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4942
|
+
},
|
|
4943
|
+
body: `final`
|
|
4944
|
+
});
|
|
4945
|
+
const staleResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4946
|
+
method: `POST`,
|
|
4947
|
+
headers: {
|
|
4948
|
+
"Content-Type": `text/plain`,
|
|
4949
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4950
|
+
[PRODUCER_ID_HEADER]: `producer-A`,
|
|
4951
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4952
|
+
[PRODUCER_SEQ_HEADER]: `1`
|
|
4953
|
+
},
|
|
4954
|
+
body: `stale attempt`
|
|
4955
|
+
});
|
|
4956
|
+
(0, vitest.expect)([403, 409]).toContain(staleResponse.status);
|
|
4957
|
+
});
|
|
4958
|
+
(0, vitest.test)(`close-with-different-body-dedup: Retry close with different body deduplicates to original`, async () => {
|
|
4959
|
+
const streamPath = `/v1/stream/close-dedup-body-${Date.now()}`;
|
|
4960
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4961
|
+
method: `PUT`,
|
|
4962
|
+
headers: { "Content-Type": `text/plain` }
|
|
4963
|
+
});
|
|
4964
|
+
const firstClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4965
|
+
method: `POST`,
|
|
4966
|
+
headers: {
|
|
4967
|
+
"Content-Type": `text/plain`,
|
|
4968
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4969
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4970
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4971
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4972
|
+
},
|
|
4973
|
+
body: `body-A`
|
|
4974
|
+
});
|
|
4975
|
+
(0, vitest.expect)(firstClose.status).toBe(200);
|
|
4976
|
+
const retryClose = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4977
|
+
method: `POST`,
|
|
4978
|
+
headers: {
|
|
4979
|
+
"Content-Type": `text/plain`,
|
|
4980
|
+
[STREAM_CLOSED_HEADER]: `true`,
|
|
4981
|
+
[PRODUCER_ID_HEADER]: `test-producer`,
|
|
4982
|
+
[PRODUCER_EPOCH_HEADER]: `0`,
|
|
4983
|
+
[PRODUCER_SEQ_HEADER]: `0`
|
|
4984
|
+
},
|
|
4985
|
+
body: `body-B`
|
|
4986
|
+
});
|
|
4987
|
+
(0, vitest.expect)(retryClose.status).toBe(204);
|
|
4988
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}`);
|
|
4989
|
+
const content = await readResponse.text();
|
|
4990
|
+
(0, vitest.expect)(content).toBe(`body-A`);
|
|
4991
|
+
});
|
|
4992
|
+
(0, vitest.test)(`empty-post-without-stream-closed-400: POST with empty body but no Stream-Closed returns 400`, async () => {
|
|
4993
|
+
const streamPath = `/v1/stream/empty-no-closed-${Date.now()}`;
|
|
4994
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4995
|
+
method: `PUT`,
|
|
4996
|
+
headers: { "Content-Type": `text/plain` }
|
|
4997
|
+
});
|
|
4998
|
+
const response = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
4999
|
+
method: `POST`,
|
|
5000
|
+
headers: { "Content-Type": `text/plain` },
|
|
5001
|
+
body: ``
|
|
5002
|
+
});
|
|
5003
|
+
(0, vitest.expect)(response.status).toBe(400);
|
|
5004
|
+
});
|
|
5005
|
+
(0, vitest.test)(`delete-closed-stream: Deleting a closed stream removes it (returns 404 after)`, async () => {
|
|
5006
|
+
const streamPath = `/v1/stream/delete-closed-${Date.now()}`;
|
|
5007
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
5008
|
+
method: `PUT`,
|
|
5009
|
+
headers: { "Content-Type": `text/plain` },
|
|
5010
|
+
body: `content`
|
|
5011
|
+
});
|
|
5012
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
5013
|
+
method: `POST`,
|
|
5014
|
+
headers: { [STREAM_CLOSED_HEADER]: `true` }
|
|
5015
|
+
});
|
|
5016
|
+
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
5017
|
+
(0, vitest.expect)(headBefore.headers.get(STREAM_CLOSED_HEADER)).toBe(`true`);
|
|
5018
|
+
const deleteResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `DELETE` });
|
|
5019
|
+
(0, vitest.expect)([200, 204]).toContain(deleteResponse.status);
|
|
5020
|
+
const headAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
5021
|
+
(0, vitest.expect)(headAfter.status).toBe(404);
|
|
5022
|
+
});
|
|
5023
|
+
});
|
|
5024
|
+
});
|
|
4083
5025
|
}
|
|
4084
5026
|
|
|
4085
5027
|
//#endregion
|