@durable-streams/server-conformance-tests 0.1.2 → 0.1.3
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-mPjxiipV.cjs → src-DK3GDgwo.cjs} +147 -0
- package/dist/{src-DRIMnUPk.js → src-DcbQ_SIQ.js} +147 -0
- package/dist/test-runner.cjs +1 -1
- package/dist/test-runner.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +254 -0
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -1212,6 +1212,153 @@ function runConformanceTests(options) {
|
|
|
1212
1212
|
if (expiresHeader) (0, vitest.expect)(expiresHeader).toBeDefined();
|
|
1213
1213
|
});
|
|
1214
1214
|
});
|
|
1215
|
+
(0, vitest.describe)(`TTL Expiration Behavior`, () => {
|
|
1216
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1217
|
+
const uniquePath = (prefix) => `/v1/stream/${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1218
|
+
vitest.test.concurrent(`should return 404 on HEAD after TTL expires`, async () => {
|
|
1219
|
+
const streamPath = uniquePath(`ttl-expire-head`);
|
|
1220
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1221
|
+
method: `PUT`,
|
|
1222
|
+
headers: {
|
|
1223
|
+
"Content-Type": `text/plain`,
|
|
1224
|
+
"Stream-TTL": `1`
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1228
|
+
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1229
|
+
(0, vitest.expect)(headBefore.status).toBe(200);
|
|
1230
|
+
await sleep(1500);
|
|
1231
|
+
const headAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1232
|
+
(0, vitest.expect)(headAfter.status).toBe(404);
|
|
1233
|
+
});
|
|
1234
|
+
vitest.test.concurrent(`should return 404 on GET after TTL expires`, async () => {
|
|
1235
|
+
const streamPath = uniquePath(`ttl-expire-get`);
|
|
1236
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1237
|
+
method: `PUT`,
|
|
1238
|
+
headers: {
|
|
1239
|
+
"Content-Type": `text/plain`,
|
|
1240
|
+
"Stream-TTL": `1`
|
|
1241
|
+
},
|
|
1242
|
+
body: `test data`
|
|
1243
|
+
});
|
|
1244
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1245
|
+
const getBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1246
|
+
(0, vitest.expect)(getBefore.status).toBe(200);
|
|
1247
|
+
await sleep(1500);
|
|
1248
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1249
|
+
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1250
|
+
});
|
|
1251
|
+
vitest.test.concurrent(`should return 404 on POST append after TTL expires`, async () => {
|
|
1252
|
+
const streamPath = uniquePath(`ttl-expire-post`);
|
|
1253
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1254
|
+
method: `PUT`,
|
|
1255
|
+
headers: {
|
|
1256
|
+
"Content-Type": `text/plain`,
|
|
1257
|
+
"Stream-TTL": `1`
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1261
|
+
const postBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1262
|
+
method: `POST`,
|
|
1263
|
+
headers: { "Content-Type": `text/plain` },
|
|
1264
|
+
body: `appended data`
|
|
1265
|
+
});
|
|
1266
|
+
(0, vitest.expect)([200, 204]).toContain(postBefore.status);
|
|
1267
|
+
await sleep(1500);
|
|
1268
|
+
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1269
|
+
method: `POST`,
|
|
1270
|
+
headers: { "Content-Type": `text/plain` },
|
|
1271
|
+
body: `more data`
|
|
1272
|
+
});
|
|
1273
|
+
(0, vitest.expect)(postAfter.status).toBe(404);
|
|
1274
|
+
});
|
|
1275
|
+
vitest.test.concurrent(`should return 404 on HEAD after Expires-At passes`, async () => {
|
|
1276
|
+
const streamPath = uniquePath(`expires-at-head`);
|
|
1277
|
+
const expiresAt = new Date(Date.now() + 1e3).toISOString();
|
|
1278
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1279
|
+
method: `PUT`,
|
|
1280
|
+
headers: {
|
|
1281
|
+
"Content-Type": `text/plain`,
|
|
1282
|
+
"Stream-Expires-At": expiresAt
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1286
|
+
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1287
|
+
(0, vitest.expect)(headBefore.status).toBe(200);
|
|
1288
|
+
await sleep(1500);
|
|
1289
|
+
const headAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1290
|
+
(0, vitest.expect)(headAfter.status).toBe(404);
|
|
1291
|
+
});
|
|
1292
|
+
vitest.test.concurrent(`should return 404 on GET after Expires-At passes`, async () => {
|
|
1293
|
+
const streamPath = uniquePath(`expires-at-get`);
|
|
1294
|
+
const expiresAt = new Date(Date.now() + 1e3).toISOString();
|
|
1295
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1296
|
+
method: `PUT`,
|
|
1297
|
+
headers: {
|
|
1298
|
+
"Content-Type": `text/plain`,
|
|
1299
|
+
"Stream-Expires-At": expiresAt
|
|
1300
|
+
},
|
|
1301
|
+
body: `test data`
|
|
1302
|
+
});
|
|
1303
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1304
|
+
const getBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1305
|
+
(0, vitest.expect)(getBefore.status).toBe(200);
|
|
1306
|
+
await sleep(1500);
|
|
1307
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1308
|
+
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1309
|
+
});
|
|
1310
|
+
vitest.test.concurrent(`should return 404 on POST append after Expires-At passes`, async () => {
|
|
1311
|
+
const streamPath = uniquePath(`expires-at-post`);
|
|
1312
|
+
const expiresAt = new Date(Date.now() + 1e3).toISOString();
|
|
1313
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1314
|
+
method: `PUT`,
|
|
1315
|
+
headers: {
|
|
1316
|
+
"Content-Type": `text/plain`,
|
|
1317
|
+
"Stream-Expires-At": expiresAt
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1321
|
+
const postBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1322
|
+
method: `POST`,
|
|
1323
|
+
headers: { "Content-Type": `text/plain` },
|
|
1324
|
+
body: `appended data`
|
|
1325
|
+
});
|
|
1326
|
+
(0, vitest.expect)([200, 204]).toContain(postBefore.status);
|
|
1327
|
+
await sleep(1500);
|
|
1328
|
+
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1329
|
+
method: `POST`,
|
|
1330
|
+
headers: { "Content-Type": `text/plain` },
|
|
1331
|
+
body: `more data`
|
|
1332
|
+
});
|
|
1333
|
+
(0, vitest.expect)(postAfter.status).toBe(404);
|
|
1334
|
+
});
|
|
1335
|
+
vitest.test.concurrent(`should allow recreating stream after TTL expires`, async () => {
|
|
1336
|
+
const streamPath = uniquePath(`ttl-recreate`);
|
|
1337
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1338
|
+
method: `PUT`,
|
|
1339
|
+
headers: {
|
|
1340
|
+
"Content-Type": `text/plain`,
|
|
1341
|
+
"Stream-TTL": `1`
|
|
1342
|
+
},
|
|
1343
|
+
body: `original data`
|
|
1344
|
+
});
|
|
1345
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1346
|
+
await sleep(1500);
|
|
1347
|
+
const recreateResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1348
|
+
method: `PUT`,
|
|
1349
|
+
headers: {
|
|
1350
|
+
"Content-Type": `application/json`,
|
|
1351
|
+
"Stream-TTL": `3600`
|
|
1352
|
+
},
|
|
1353
|
+
body: `["new data"]`
|
|
1354
|
+
});
|
|
1355
|
+
(0, vitest.expect)(recreateResponse.status).toBe(201);
|
|
1356
|
+
const getResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1357
|
+
(0, vitest.expect)(getResponse.status).toBe(200);
|
|
1358
|
+
const body = await getResponse.text();
|
|
1359
|
+
(0, vitest.expect)(body).toContain(`new data`);
|
|
1360
|
+
});
|
|
1361
|
+
});
|
|
1215
1362
|
(0, vitest.describe)(`Caching and ETag`, () => {
|
|
1216
1363
|
(0, vitest.test)(`should generate ETag on GET responses`, async () => {
|
|
1217
1364
|
const streamPath = `/v1/stream/etag-generate-test-${Date.now()}`;
|
|
@@ -1210,6 +1210,153 @@ function runConformanceTests(options) {
|
|
|
1210
1210
|
if (expiresHeader) expect(expiresHeader).toBeDefined();
|
|
1211
1211
|
});
|
|
1212
1212
|
});
|
|
1213
|
+
describe(`TTL Expiration Behavior`, () => {
|
|
1214
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1215
|
+
const uniquePath = (prefix) => `/v1/stream/${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1216
|
+
test.concurrent(`should return 404 on HEAD after TTL expires`, async () => {
|
|
1217
|
+
const streamPath = uniquePath(`ttl-expire-head`);
|
|
1218
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1219
|
+
method: `PUT`,
|
|
1220
|
+
headers: {
|
|
1221
|
+
"Content-Type": `text/plain`,
|
|
1222
|
+
"Stream-TTL": `1`
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
expect(createResponse.status).toBe(201);
|
|
1226
|
+
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1227
|
+
expect(headBefore.status).toBe(200);
|
|
1228
|
+
await sleep(1500);
|
|
1229
|
+
const headAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1230
|
+
expect(headAfter.status).toBe(404);
|
|
1231
|
+
});
|
|
1232
|
+
test.concurrent(`should return 404 on GET after TTL expires`, async () => {
|
|
1233
|
+
const streamPath = uniquePath(`ttl-expire-get`);
|
|
1234
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1235
|
+
method: `PUT`,
|
|
1236
|
+
headers: {
|
|
1237
|
+
"Content-Type": `text/plain`,
|
|
1238
|
+
"Stream-TTL": `1`
|
|
1239
|
+
},
|
|
1240
|
+
body: `test data`
|
|
1241
|
+
});
|
|
1242
|
+
expect(createResponse.status).toBe(201);
|
|
1243
|
+
const getBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1244
|
+
expect(getBefore.status).toBe(200);
|
|
1245
|
+
await sleep(1500);
|
|
1246
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1247
|
+
expect(getAfter.status).toBe(404);
|
|
1248
|
+
});
|
|
1249
|
+
test.concurrent(`should return 404 on POST append after TTL expires`, async () => {
|
|
1250
|
+
const streamPath = uniquePath(`ttl-expire-post`);
|
|
1251
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1252
|
+
method: `PUT`,
|
|
1253
|
+
headers: {
|
|
1254
|
+
"Content-Type": `text/plain`,
|
|
1255
|
+
"Stream-TTL": `1`
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
expect(createResponse.status).toBe(201);
|
|
1259
|
+
const postBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1260
|
+
method: `POST`,
|
|
1261
|
+
headers: { "Content-Type": `text/plain` },
|
|
1262
|
+
body: `appended data`
|
|
1263
|
+
});
|
|
1264
|
+
expect([200, 204]).toContain(postBefore.status);
|
|
1265
|
+
await sleep(1500);
|
|
1266
|
+
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1267
|
+
method: `POST`,
|
|
1268
|
+
headers: { "Content-Type": `text/plain` },
|
|
1269
|
+
body: `more data`
|
|
1270
|
+
});
|
|
1271
|
+
expect(postAfter.status).toBe(404);
|
|
1272
|
+
});
|
|
1273
|
+
test.concurrent(`should return 404 on HEAD after Expires-At passes`, async () => {
|
|
1274
|
+
const streamPath = uniquePath(`expires-at-head`);
|
|
1275
|
+
const expiresAt = new Date(Date.now() + 1e3).toISOString();
|
|
1276
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1277
|
+
method: `PUT`,
|
|
1278
|
+
headers: {
|
|
1279
|
+
"Content-Type": `text/plain`,
|
|
1280
|
+
"Stream-Expires-At": expiresAt
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
expect(createResponse.status).toBe(201);
|
|
1284
|
+
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1285
|
+
expect(headBefore.status).toBe(200);
|
|
1286
|
+
await sleep(1500);
|
|
1287
|
+
const headAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1288
|
+
expect(headAfter.status).toBe(404);
|
|
1289
|
+
});
|
|
1290
|
+
test.concurrent(`should return 404 on GET after Expires-At passes`, async () => {
|
|
1291
|
+
const streamPath = uniquePath(`expires-at-get`);
|
|
1292
|
+
const expiresAt = new Date(Date.now() + 1e3).toISOString();
|
|
1293
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1294
|
+
method: `PUT`,
|
|
1295
|
+
headers: {
|
|
1296
|
+
"Content-Type": `text/plain`,
|
|
1297
|
+
"Stream-Expires-At": expiresAt
|
|
1298
|
+
},
|
|
1299
|
+
body: `test data`
|
|
1300
|
+
});
|
|
1301
|
+
expect(createResponse.status).toBe(201);
|
|
1302
|
+
const getBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1303
|
+
expect(getBefore.status).toBe(200);
|
|
1304
|
+
await sleep(1500);
|
|
1305
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1306
|
+
expect(getAfter.status).toBe(404);
|
|
1307
|
+
});
|
|
1308
|
+
test.concurrent(`should return 404 on POST append after Expires-At passes`, async () => {
|
|
1309
|
+
const streamPath = uniquePath(`expires-at-post`);
|
|
1310
|
+
const expiresAt = new Date(Date.now() + 1e3).toISOString();
|
|
1311
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1312
|
+
method: `PUT`,
|
|
1313
|
+
headers: {
|
|
1314
|
+
"Content-Type": `text/plain`,
|
|
1315
|
+
"Stream-Expires-At": expiresAt
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
expect(createResponse.status).toBe(201);
|
|
1319
|
+
const postBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1320
|
+
method: `POST`,
|
|
1321
|
+
headers: { "Content-Type": `text/plain` },
|
|
1322
|
+
body: `appended data`
|
|
1323
|
+
});
|
|
1324
|
+
expect([200, 204]).toContain(postBefore.status);
|
|
1325
|
+
await sleep(1500);
|
|
1326
|
+
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1327
|
+
method: `POST`,
|
|
1328
|
+
headers: { "Content-Type": `text/plain` },
|
|
1329
|
+
body: `more data`
|
|
1330
|
+
});
|
|
1331
|
+
expect(postAfter.status).toBe(404);
|
|
1332
|
+
});
|
|
1333
|
+
test.concurrent(`should allow recreating stream after TTL expires`, async () => {
|
|
1334
|
+
const streamPath = uniquePath(`ttl-recreate`);
|
|
1335
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1336
|
+
method: `PUT`,
|
|
1337
|
+
headers: {
|
|
1338
|
+
"Content-Type": `text/plain`,
|
|
1339
|
+
"Stream-TTL": `1`
|
|
1340
|
+
},
|
|
1341
|
+
body: `original data`
|
|
1342
|
+
});
|
|
1343
|
+
expect(createResponse.status).toBe(201);
|
|
1344
|
+
await sleep(1500);
|
|
1345
|
+
const recreateResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1346
|
+
method: `PUT`,
|
|
1347
|
+
headers: {
|
|
1348
|
+
"Content-Type": `application/json`,
|
|
1349
|
+
"Stream-TTL": `3600`
|
|
1350
|
+
},
|
|
1351
|
+
body: `["new data"]`
|
|
1352
|
+
});
|
|
1353
|
+
expect(recreateResponse.status).toBe(201);
|
|
1354
|
+
const getResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1355
|
+
expect(getResponse.status).toBe(200);
|
|
1356
|
+
const body = await getResponse.text();
|
|
1357
|
+
expect(body).toContain(`new data`);
|
|
1358
|
+
});
|
|
1359
|
+
});
|
|
1213
1360
|
describe(`Caching and ETag`, () => {
|
|
1214
1361
|
test(`should generate ETag on GET responses`, async () => {
|
|
1215
1362
|
const streamPath = `/v1/stream/etag-generate-test-${Date.now()}`;
|
package/dist/test-runner.cjs
CHANGED
package/dist/test-runner.js
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1898,6 +1898,260 @@ export function runConformanceTests(options: ConformanceTestOptions): void {
|
|
|
1898
1898
|
})
|
|
1899
1899
|
})
|
|
1900
1900
|
|
|
1901
|
+
// ============================================================================
|
|
1902
|
+
// TTL Expiration Behavior Tests
|
|
1903
|
+
// ============================================================================
|
|
1904
|
+
|
|
1905
|
+
describe(`TTL Expiration Behavior`, () => {
|
|
1906
|
+
// Helper function to wait for a specified duration
|
|
1907
|
+
const sleep = (ms: number) =>
|
|
1908
|
+
new Promise((resolve) => setTimeout(resolve, ms))
|
|
1909
|
+
|
|
1910
|
+
// Helper to generate unique stream paths for concurrent tests
|
|
1911
|
+
const uniquePath = (prefix: string) =>
|
|
1912
|
+
`/v1/stream/${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
1913
|
+
|
|
1914
|
+
// Run tests concurrently to avoid 6x 1.5s wait time
|
|
1915
|
+
test.concurrent(`should return 404 on HEAD after TTL expires`, async () => {
|
|
1916
|
+
const streamPath = uniquePath(`ttl-expire-head`)
|
|
1917
|
+
|
|
1918
|
+
// Create stream with 1 second TTL
|
|
1919
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1920
|
+
method: `PUT`,
|
|
1921
|
+
headers: {
|
|
1922
|
+
"Content-Type": `text/plain`,
|
|
1923
|
+
"Stream-TTL": `1`,
|
|
1924
|
+
},
|
|
1925
|
+
})
|
|
1926
|
+
expect(createResponse.status).toBe(201)
|
|
1927
|
+
|
|
1928
|
+
// Verify stream exists immediately
|
|
1929
|
+
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1930
|
+
method: `HEAD`,
|
|
1931
|
+
})
|
|
1932
|
+
expect(headBefore.status).toBe(200)
|
|
1933
|
+
|
|
1934
|
+
// Wait for TTL to expire (1 second + buffer)
|
|
1935
|
+
await sleep(1500)
|
|
1936
|
+
|
|
1937
|
+
// Stream should no longer exist
|
|
1938
|
+
const headAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1939
|
+
method: `HEAD`,
|
|
1940
|
+
})
|
|
1941
|
+
expect(headAfter.status).toBe(404)
|
|
1942
|
+
})
|
|
1943
|
+
|
|
1944
|
+
test.concurrent(`should return 404 on GET after TTL expires`, async () => {
|
|
1945
|
+
const streamPath = uniquePath(`ttl-expire-get`)
|
|
1946
|
+
|
|
1947
|
+
// Create stream with 1 second TTL and some data
|
|
1948
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1949
|
+
method: `PUT`,
|
|
1950
|
+
headers: {
|
|
1951
|
+
"Content-Type": `text/plain`,
|
|
1952
|
+
"Stream-TTL": `1`,
|
|
1953
|
+
},
|
|
1954
|
+
body: `test data`,
|
|
1955
|
+
})
|
|
1956
|
+
expect(createResponse.status).toBe(201)
|
|
1957
|
+
|
|
1958
|
+
// Verify stream is readable immediately
|
|
1959
|
+
const getBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1960
|
+
method: `GET`,
|
|
1961
|
+
})
|
|
1962
|
+
expect(getBefore.status).toBe(200)
|
|
1963
|
+
|
|
1964
|
+
// Wait for TTL to expire
|
|
1965
|
+
await sleep(1500)
|
|
1966
|
+
|
|
1967
|
+
// Stream should no longer exist
|
|
1968
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1969
|
+
method: `GET`,
|
|
1970
|
+
})
|
|
1971
|
+
expect(getAfter.status).toBe(404)
|
|
1972
|
+
})
|
|
1973
|
+
|
|
1974
|
+
test.concurrent(
|
|
1975
|
+
`should return 404 on POST append after TTL expires`,
|
|
1976
|
+
async () => {
|
|
1977
|
+
const streamPath = uniquePath(`ttl-expire-post`)
|
|
1978
|
+
|
|
1979
|
+
// Create stream with 1 second TTL
|
|
1980
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1981
|
+
method: `PUT`,
|
|
1982
|
+
headers: {
|
|
1983
|
+
"Content-Type": `text/plain`,
|
|
1984
|
+
"Stream-TTL": `1`,
|
|
1985
|
+
},
|
|
1986
|
+
})
|
|
1987
|
+
expect(createResponse.status).toBe(201)
|
|
1988
|
+
|
|
1989
|
+
// Verify append works immediately
|
|
1990
|
+
const postBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1991
|
+
method: `POST`,
|
|
1992
|
+
headers: { "Content-Type": `text/plain` },
|
|
1993
|
+
body: `appended data`,
|
|
1994
|
+
})
|
|
1995
|
+
expect([200, 204]).toContain(postBefore.status)
|
|
1996
|
+
|
|
1997
|
+
// Wait for TTL to expire
|
|
1998
|
+
await sleep(1500)
|
|
1999
|
+
|
|
2000
|
+
// Append should fail - stream no longer exists
|
|
2001
|
+
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2002
|
+
method: `POST`,
|
|
2003
|
+
headers: { "Content-Type": `text/plain` },
|
|
2004
|
+
body: `more data`,
|
|
2005
|
+
})
|
|
2006
|
+
expect(postAfter.status).toBe(404)
|
|
2007
|
+
}
|
|
2008
|
+
)
|
|
2009
|
+
|
|
2010
|
+
test.concurrent(
|
|
2011
|
+
`should return 404 on HEAD after Expires-At passes`,
|
|
2012
|
+
async () => {
|
|
2013
|
+
const streamPath = uniquePath(`expires-at-head`)
|
|
2014
|
+
|
|
2015
|
+
// Create stream that expires in 1 second
|
|
2016
|
+
const expiresAt = new Date(Date.now() + 1000).toISOString()
|
|
2017
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2018
|
+
method: `PUT`,
|
|
2019
|
+
headers: {
|
|
2020
|
+
"Content-Type": `text/plain`,
|
|
2021
|
+
"Stream-Expires-At": expiresAt,
|
|
2022
|
+
},
|
|
2023
|
+
})
|
|
2024
|
+
expect(createResponse.status).toBe(201)
|
|
2025
|
+
|
|
2026
|
+
// Verify stream exists immediately
|
|
2027
|
+
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2028
|
+
method: `HEAD`,
|
|
2029
|
+
})
|
|
2030
|
+
expect(headBefore.status).toBe(200)
|
|
2031
|
+
|
|
2032
|
+
// Wait for expiry time to pass
|
|
2033
|
+
await sleep(1500)
|
|
2034
|
+
|
|
2035
|
+
// Stream should no longer exist
|
|
2036
|
+
const headAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2037
|
+
method: `HEAD`,
|
|
2038
|
+
})
|
|
2039
|
+
expect(headAfter.status).toBe(404)
|
|
2040
|
+
}
|
|
2041
|
+
)
|
|
2042
|
+
|
|
2043
|
+
test.concurrent(
|
|
2044
|
+
`should return 404 on GET after Expires-At passes`,
|
|
2045
|
+
async () => {
|
|
2046
|
+
const streamPath = uniquePath(`expires-at-get`)
|
|
2047
|
+
|
|
2048
|
+
// Create stream that expires in 1 second
|
|
2049
|
+
const expiresAt = new Date(Date.now() + 1000).toISOString()
|
|
2050
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2051
|
+
method: `PUT`,
|
|
2052
|
+
headers: {
|
|
2053
|
+
"Content-Type": `text/plain`,
|
|
2054
|
+
"Stream-Expires-At": expiresAt,
|
|
2055
|
+
},
|
|
2056
|
+
body: `test data`,
|
|
2057
|
+
})
|
|
2058
|
+
expect(createResponse.status).toBe(201)
|
|
2059
|
+
|
|
2060
|
+
// Verify stream is readable immediately
|
|
2061
|
+
const getBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2062
|
+
method: `GET`,
|
|
2063
|
+
})
|
|
2064
|
+
expect(getBefore.status).toBe(200)
|
|
2065
|
+
|
|
2066
|
+
// Wait for expiry time to pass
|
|
2067
|
+
await sleep(1500)
|
|
2068
|
+
|
|
2069
|
+
// Stream should no longer exist
|
|
2070
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2071
|
+
method: `GET`,
|
|
2072
|
+
})
|
|
2073
|
+
expect(getAfter.status).toBe(404)
|
|
2074
|
+
}
|
|
2075
|
+
)
|
|
2076
|
+
|
|
2077
|
+
test.concurrent(
|
|
2078
|
+
`should return 404 on POST append after Expires-At passes`,
|
|
2079
|
+
async () => {
|
|
2080
|
+
const streamPath = uniquePath(`expires-at-post`)
|
|
2081
|
+
|
|
2082
|
+
// Create stream that expires in 1 second
|
|
2083
|
+
const expiresAt = new Date(Date.now() + 1000).toISOString()
|
|
2084
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2085
|
+
method: `PUT`,
|
|
2086
|
+
headers: {
|
|
2087
|
+
"Content-Type": `text/plain`,
|
|
2088
|
+
"Stream-Expires-At": expiresAt,
|
|
2089
|
+
},
|
|
2090
|
+
})
|
|
2091
|
+
expect(createResponse.status).toBe(201)
|
|
2092
|
+
|
|
2093
|
+
// Verify append works immediately
|
|
2094
|
+
const postBefore = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2095
|
+
method: `POST`,
|
|
2096
|
+
headers: { "Content-Type": `text/plain` },
|
|
2097
|
+
body: `appended data`,
|
|
2098
|
+
})
|
|
2099
|
+
expect([200, 204]).toContain(postBefore.status)
|
|
2100
|
+
|
|
2101
|
+
// Wait for expiry time to pass
|
|
2102
|
+
await sleep(1500)
|
|
2103
|
+
|
|
2104
|
+
// Append should fail - stream no longer exists
|
|
2105
|
+
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2106
|
+
method: `POST`,
|
|
2107
|
+
headers: { "Content-Type": `text/plain` },
|
|
2108
|
+
body: `more data`,
|
|
2109
|
+
})
|
|
2110
|
+
expect(postAfter.status).toBe(404)
|
|
2111
|
+
}
|
|
2112
|
+
)
|
|
2113
|
+
|
|
2114
|
+
test.concurrent(
|
|
2115
|
+
`should allow recreating stream after TTL expires`,
|
|
2116
|
+
async () => {
|
|
2117
|
+
const streamPath = uniquePath(`ttl-recreate`)
|
|
2118
|
+
|
|
2119
|
+
// Create stream with 1 second TTL
|
|
2120
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2121
|
+
method: `PUT`,
|
|
2122
|
+
headers: {
|
|
2123
|
+
"Content-Type": `text/plain`,
|
|
2124
|
+
"Stream-TTL": `1`,
|
|
2125
|
+
},
|
|
2126
|
+
body: `original data`,
|
|
2127
|
+
})
|
|
2128
|
+
expect(createResponse.status).toBe(201)
|
|
2129
|
+
|
|
2130
|
+
// Wait for TTL to expire
|
|
2131
|
+
await sleep(1500)
|
|
2132
|
+
|
|
2133
|
+
// Recreate stream with different config - should succeed (201)
|
|
2134
|
+
const recreateResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2135
|
+
method: `PUT`,
|
|
2136
|
+
headers: {
|
|
2137
|
+
"Content-Type": `application/json`,
|
|
2138
|
+
"Stream-TTL": `3600`,
|
|
2139
|
+
},
|
|
2140
|
+
body: `["new data"]`,
|
|
2141
|
+
})
|
|
2142
|
+
expect(recreateResponse.status).toBe(201)
|
|
2143
|
+
|
|
2144
|
+
// Verify the new stream is accessible
|
|
2145
|
+
const getResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
2146
|
+
method: `GET`,
|
|
2147
|
+
})
|
|
2148
|
+
expect(getResponse.status).toBe(200)
|
|
2149
|
+
const body = await getResponse.text()
|
|
2150
|
+
expect(body).toContain(`new data`)
|
|
2151
|
+
}
|
|
2152
|
+
)
|
|
2153
|
+
})
|
|
2154
|
+
|
|
1901
2155
|
// ============================================================================
|
|
1902
2156
|
// Caching and ETag Tests
|
|
1903
2157
|
// ============================================================================
|