@durable-streams/server-conformance-tests 0.1.4 → 0.1.5
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.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/{src-DK3GDgwo.cjs → src-BJQjRfnf.cjs} +5 -3
- package/dist/{src-DcbQ_SIQ.js → src-BtF2jQ-Q.js} +5 -3
- package/dist/test-runner.cjs +1 -1
- package/dist/test-runner.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +88 -75
package/dist/index.cjs
CHANGED
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -84,6 +84,7 @@ function parseSSEEvents(sseText) {
|
|
|
84
84
|
*/
|
|
85
85
|
function runConformanceTests(options) {
|
|
86
86
|
const getBaseUrl = () => options.baseUrl;
|
|
87
|
+
const getLongPollTestTimeoutMs = () => (options.longPollTimeoutMs ?? 2e4) + 1e3;
|
|
87
88
|
(0, vitest.describe)(`Basic Stream Operations`, () => {
|
|
88
89
|
(0, vitest.test)(`should create a stream`, async () => {
|
|
89
90
|
const streamPath = `/v1/stream/create-test-${Date.now()}`;
|
|
@@ -252,13 +253,14 @@ function runConformanceTests(options) {
|
|
|
252
253
|
const readPromise = (async () => {
|
|
253
254
|
const res = await stream.stream({ live: `long-poll` });
|
|
254
255
|
await new Promise((resolve) => {
|
|
255
|
-
const unsubscribe = res.subscribeBytes(
|
|
256
|
+
const unsubscribe = res.subscribeBytes((chunk) => {
|
|
256
257
|
if (chunk.data.length > 0) receivedData.push(new TextDecoder().decode(chunk.data));
|
|
257
258
|
if (receivedData.length >= 1) {
|
|
258
259
|
unsubscribe();
|
|
259
260
|
res.cancel();
|
|
260
261
|
resolve();
|
|
261
262
|
}
|
|
263
|
+
return Promise.resolve();
|
|
262
264
|
});
|
|
263
265
|
});
|
|
264
266
|
})();
|
|
@@ -266,7 +268,7 @@ function runConformanceTests(options) {
|
|
|
266
268
|
await stream.append(`new data`);
|
|
267
269
|
await readPromise;
|
|
268
270
|
(0, vitest.expect)(receivedData).toContain(`new data`);
|
|
269
|
-
},
|
|
271
|
+
}, getLongPollTestTimeoutMs());
|
|
270
272
|
(0, vitest.test)(`should return immediately if data already exists`, async () => {
|
|
271
273
|
const streamPath = `/v1/stream/longpoll-immediate-test-${Date.now()}`;
|
|
272
274
|
const stream = await __durable_streams_client.DurableStream.create({
|
|
@@ -1059,7 +1061,7 @@ function runConformanceTests(options) {
|
|
|
1059
1061
|
clearTimeout(timeoutId);
|
|
1060
1062
|
if (e instanceof Error && e.name !== `AbortError`) throw e;
|
|
1061
1063
|
}
|
|
1062
|
-
},
|
|
1064
|
+
}, getLongPollTestTimeoutMs());
|
|
1063
1065
|
});
|
|
1064
1066
|
(0, vitest.describe)(`TTL and Expiry Edge Cases`, () => {
|
|
1065
1067
|
(0, vitest.test)(`should reject TTL with leading zeros`, async () => {
|
|
@@ -82,6 +82,7 @@ function parseSSEEvents(sseText) {
|
|
|
82
82
|
*/
|
|
83
83
|
function runConformanceTests(options) {
|
|
84
84
|
const getBaseUrl = () => options.baseUrl;
|
|
85
|
+
const getLongPollTestTimeoutMs = () => (options.longPollTimeoutMs ?? 2e4) + 1e3;
|
|
85
86
|
describe(`Basic Stream Operations`, () => {
|
|
86
87
|
test(`should create a stream`, async () => {
|
|
87
88
|
const streamPath = `/v1/stream/create-test-${Date.now()}`;
|
|
@@ -250,13 +251,14 @@ function runConformanceTests(options) {
|
|
|
250
251
|
const readPromise = (async () => {
|
|
251
252
|
const res = await stream.stream({ live: `long-poll` });
|
|
252
253
|
await new Promise((resolve) => {
|
|
253
|
-
const unsubscribe = res.subscribeBytes(
|
|
254
|
+
const unsubscribe = res.subscribeBytes((chunk) => {
|
|
254
255
|
if (chunk.data.length > 0) receivedData.push(new TextDecoder().decode(chunk.data));
|
|
255
256
|
if (receivedData.length >= 1) {
|
|
256
257
|
unsubscribe();
|
|
257
258
|
res.cancel();
|
|
258
259
|
resolve();
|
|
259
260
|
}
|
|
261
|
+
return Promise.resolve();
|
|
260
262
|
});
|
|
261
263
|
});
|
|
262
264
|
})();
|
|
@@ -264,7 +266,7 @@ function runConformanceTests(options) {
|
|
|
264
266
|
await stream.append(`new data`);
|
|
265
267
|
await readPromise;
|
|
266
268
|
expect(receivedData).toContain(`new data`);
|
|
267
|
-
},
|
|
269
|
+
}, getLongPollTestTimeoutMs());
|
|
268
270
|
test(`should return immediately if data already exists`, async () => {
|
|
269
271
|
const streamPath = `/v1/stream/longpoll-immediate-test-${Date.now()}`;
|
|
270
272
|
const stream = await DurableStream.create({
|
|
@@ -1057,7 +1059,7 @@ function runConformanceTests(options) {
|
|
|
1057
1059
|
clearTimeout(timeoutId);
|
|
1058
1060
|
if (e instanceof Error && e.name !== `AbortError`) throw e;
|
|
1059
1061
|
}
|
|
1060
|
-
},
|
|
1062
|
+
}, getLongPollTestTimeoutMs());
|
|
1061
1063
|
});
|
|
1062
1064
|
describe(`TTL and Expiry Edge Cases`, () => {
|
|
1063
1065
|
test(`should reject TTL with leading zeros`, async () => {
|
package/dist/test-runner.cjs
CHANGED
package/dist/test-runner.js
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
export interface ConformanceTestOptions {
|
|
18
18
|
/** Base URL of the server to test */
|
|
19
19
|
baseUrl: string
|
|
20
|
+
/** Timeout for long-poll tests in milliseconds (default: 20000) */
|
|
21
|
+
longPollTimeoutMs?: number
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -144,6 +146,8 @@ export function runConformanceTests(options: ConformanceTestOptions): void {
|
|
|
144
146
|
// Access options.baseUrl directly instead of destructuring to support
|
|
145
147
|
// mutable config objects (needed for dynamic port assignment)
|
|
146
148
|
const getBaseUrl = () => options.baseUrl
|
|
149
|
+
const getLongPollTestTimeoutMs = () =>
|
|
150
|
+
(options.longPollTimeoutMs ?? 20_000) + 1_000
|
|
147
151
|
|
|
148
152
|
// ============================================================================
|
|
149
153
|
// Basic Stream Operations
|
|
@@ -380,42 +384,47 @@ export function runConformanceTests(options: ConformanceTestOptions): void {
|
|
|
380
384
|
// ============================================================================
|
|
381
385
|
|
|
382
386
|
describe(`Long-Poll Operations`, () => {
|
|
383
|
-
test(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
test(
|
|
388
|
+
`should wait for new data with long-poll`,
|
|
389
|
+
async () => {
|
|
390
|
+
const streamPath = `/v1/stream/longpoll-test-${Date.now()}`
|
|
391
|
+
const stream = await DurableStream.create({
|
|
392
|
+
url: `${getBaseUrl()}${streamPath}`,
|
|
393
|
+
contentType: `text/plain`,
|
|
394
|
+
})
|
|
389
395
|
|
|
390
|
-
|
|
396
|
+
const receivedData: Array<string> = []
|
|
391
397
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
398
|
+
// Start reading in long-poll mode
|
|
399
|
+
const readPromise = (async () => {
|
|
400
|
+
const res = await stream.stream({ live: `long-poll` })
|
|
401
|
+
await new Promise<void>((resolve) => {
|
|
402
|
+
const unsubscribe = res.subscribeBytes((chunk) => {
|
|
403
|
+
if (chunk.data.length > 0) {
|
|
404
|
+
receivedData.push(new TextDecoder().decode(chunk.data))
|
|
405
|
+
}
|
|
406
|
+
if (receivedData.length >= 1) {
|
|
407
|
+
unsubscribe()
|
|
408
|
+
res.cancel()
|
|
409
|
+
resolve()
|
|
410
|
+
}
|
|
411
|
+
return Promise.resolve()
|
|
412
|
+
})
|
|
405
413
|
})
|
|
406
|
-
})
|
|
407
|
-
})()
|
|
414
|
+
})()
|
|
408
415
|
|
|
409
|
-
|
|
410
|
-
|
|
416
|
+
// Wait a bit for the long-poll to be active
|
|
417
|
+
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
411
418
|
|
|
412
|
-
|
|
413
|
-
|
|
419
|
+
// Append data while long-poll is waiting
|
|
420
|
+
await stream.append(`new data`)
|
|
414
421
|
|
|
415
|
-
|
|
422
|
+
await readPromise
|
|
416
423
|
|
|
417
|
-
|
|
418
|
-
|
|
424
|
+
expect(receivedData).toContain(`new data`)
|
|
425
|
+
},
|
|
426
|
+
getLongPollTestTimeoutMs()
|
|
427
|
+
)
|
|
419
428
|
|
|
420
429
|
test(`should return immediately if data already exists`, async () => {
|
|
421
430
|
const streamPath = `/v1/stream/longpoll-immediate-test-${Date.now()}`
|
|
@@ -1631,58 +1640,62 @@ export function runConformanceTests(options: ConformanceTestOptions): void {
|
|
|
1631
1640
|
expect(parseInt(cursor2!, 10)).toBeGreaterThan(parseInt(cursor1!, 10))
|
|
1632
1641
|
})
|
|
1633
1642
|
|
|
1634
|
-
test(
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
method: `PUT`,
|
|
1639
|
-
headers: { "Content-Type": `text/plain` },
|
|
1640
|
-
})
|
|
1641
|
-
|
|
1642
|
-
// Get the current tail offset
|
|
1643
|
-
const headResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1644
|
-
method: `HEAD`,
|
|
1645
|
-
})
|
|
1646
|
-
const tailOffset = headResponse.headers.get(STREAM_OFFSET_HEADER)
|
|
1647
|
-
expect(tailOffset).toBeDefined()
|
|
1643
|
+
test(
|
|
1644
|
+
`should return Stream-Cursor, Stream-Up-To-Date and Stream-Next-Offset on 204 timeout`,
|
|
1645
|
+
async () => {
|
|
1646
|
+
const streamPath = `/v1/stream/longpoll-204-headers-test-${Date.now()}`
|
|
1648
1647
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1648
|
+
await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1649
|
+
method: `PUT`,
|
|
1650
|
+
headers: { "Content-Type": `text/plain` },
|
|
1651
|
+
})
|
|
1653
1652
|
|
|
1654
|
-
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1653
|
+
// Get the current tail offset
|
|
1654
|
+
const headResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1655
|
+
method: `HEAD`,
|
|
1656
|
+
})
|
|
1657
|
+
const tailOffset = headResponse.headers.get(STREAM_OFFSET_HEADER)
|
|
1658
|
+
expect(tailOffset).toBeDefined()
|
|
1659
|
+
|
|
1660
|
+
// Long-poll at tail offset with a short timeout
|
|
1661
|
+
// We use AbortController to limit wait time on our side
|
|
1662
|
+
const controller = new AbortController()
|
|
1663
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000)
|
|
1664
|
+
|
|
1665
|
+
try {
|
|
1666
|
+
const response = await fetch(
|
|
1667
|
+
`${getBaseUrl()}${streamPath}?offset=${tailOffset}&live=long-poll`,
|
|
1668
|
+
{
|
|
1669
|
+
method: `GET`,
|
|
1670
|
+
signal: controller.signal,
|
|
1671
|
+
}
|
|
1672
|
+
)
|
|
1662
1673
|
|
|
1663
|
-
|
|
1674
|
+
clearTimeout(timeoutId)
|
|
1664
1675
|
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1676
|
+
// If we get a 204, verify headers
|
|
1677
|
+
if (response.status === 204) {
|
|
1678
|
+
expect(response.headers.get(STREAM_OFFSET_HEADER)).toBeDefined()
|
|
1679
|
+
expect(response.headers.get(STREAM_UP_TO_DATE_HEADER)).toBe(`true`)
|
|
1669
1680
|
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1681
|
+
// Server MUST return Stream-Cursor even on 204 timeout
|
|
1682
|
+
const cursor = response.headers.get(`Stream-Cursor`)
|
|
1683
|
+
expect(cursor).toBeDefined()
|
|
1684
|
+
expect(/^\d+$/.test(cursor!)).toBe(true)
|
|
1685
|
+
}
|
|
1686
|
+
// If we get a 200 (data arrived somehow), that's also valid
|
|
1687
|
+
expect([200, 204]).toContain(response.status)
|
|
1688
|
+
} catch (e) {
|
|
1689
|
+
clearTimeout(timeoutId)
|
|
1690
|
+
// AbortError is expected if server timeout is longer than our 5s
|
|
1691
|
+
if (e instanceof Error && e.name !== `AbortError`) {
|
|
1692
|
+
throw e
|
|
1693
|
+
}
|
|
1694
|
+
// Test passes - server just has a longer timeout than our abort
|
|
1682
1695
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1696
|
+
},
|
|
1697
|
+
getLongPollTestTimeoutMs()
|
|
1698
|
+
)
|
|
1686
1699
|
})
|
|
1687
1700
|
|
|
1688
1701
|
// ============================================================================
|