@durable-streams/server-conformance-tests 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-DZatkb9d.cjs → src-C8zcHWaE.cjs} +1778 -45
- package/dist/{src-D-K9opVc.js → src-HGMeYG8a.js} +1779 -46
- package/dist/test-runner.cjs +1 -1
- package/dist/test-runner.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +2564 -90
|
@@ -993,17 +993,23 @@ function runConformanceTests(options) {
|
|
|
993
993
|
body: `historical`
|
|
994
994
|
});
|
|
995
995
|
const longPollPromise = fetch(`${getBaseUrl()}${streamPath}?offset=now&live=long-poll`, { method: `GET` });
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
996
|
+
const interval = setInterval(() => {
|
|
997
|
+
fetch(`${getBaseUrl()}${streamPath}`, {
|
|
998
|
+
method: `POST`,
|
|
999
|
+
headers: { "Content-Type": `text/plain` },
|
|
1000
|
+
body: `new data`
|
|
1001
|
+
});
|
|
1002
|
+
}, 50);
|
|
1003
|
+
try {
|
|
1004
|
+
const response = await longPollPromise;
|
|
1005
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
1006
|
+
const text = await response.text();
|
|
1007
|
+
(0, vitest.expect)(text).toContain(`new data`);
|
|
1008
|
+
(0, vitest.expect)(text).not.toContain(`historical`);
|
|
1009
|
+
(0, vitest.expect)(response.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
1010
|
+
} finally {
|
|
1011
|
+
clearInterval(interval);
|
|
1012
|
+
}
|
|
1007
1013
|
});
|
|
1008
1014
|
(0, vitest.test)(`should support offset=now with SSE mode`, async () => {
|
|
1009
1015
|
const streamPath = `/v1/stream/offset-now-sse-test-${Date.now()}`;
|
|
@@ -1538,10 +1544,7 @@ function runConformanceTests(options) {
|
|
|
1538
1544
|
});
|
|
1539
1545
|
const response = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1540
1546
|
const ttl = response.headers.get(`Stream-TTL`);
|
|
1541
|
-
|
|
1542
|
-
(0, vitest.expect)(parseInt(ttl)).toBeGreaterThan(0);
|
|
1543
|
-
(0, vitest.expect)(parseInt(ttl)).toBeLessThanOrEqual(3600);
|
|
1544
|
-
}
|
|
1547
|
+
(0, vitest.expect)(ttl).toBe(`3600`);
|
|
1545
1548
|
});
|
|
1546
1549
|
(0, vitest.test)(`should return Expires-At metadata if configured`, async () => {
|
|
1547
1550
|
const streamPath = `/v1/stream/head-expires-metadata-test-${Date.now()}`;
|
|
@@ -1561,6 +1564,16 @@ function runConformanceTests(options) {
|
|
|
1561
1564
|
(0, vitest.describe)(`TTL Expiration Behavior`, () => {
|
|
1562
1565
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1563
1566
|
const uniquePath = (prefix) => `/v1/stream/${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1567
|
+
const waitForDeletion = async (url, initialSleepMs, expectedStatuses = [404], timeoutMs = 5e3) => {
|
|
1568
|
+
await sleep(initialSleepMs);
|
|
1569
|
+
await vitest.vi.waitFor(async () => {
|
|
1570
|
+
const head = await fetch(url, { method: `HEAD` });
|
|
1571
|
+
(0, vitest.expect)(expectedStatuses).toContain(head.status);
|
|
1572
|
+
}, {
|
|
1573
|
+
timeout: timeoutMs,
|
|
1574
|
+
interval: 200
|
|
1575
|
+
});
|
|
1576
|
+
};
|
|
1564
1577
|
vitest.test.concurrent(`should return 404 on HEAD after TTL expires`, async () => {
|
|
1565
1578
|
const streamPath = uniquePath(`ttl-expire-head`);
|
|
1566
1579
|
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
@@ -1573,11 +1586,11 @@ function runConformanceTests(options) {
|
|
|
1573
1586
|
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1574
1587
|
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1575
1588
|
(0, vitest.expect)(headBefore.status).toBe(200);
|
|
1576
|
-
await
|
|
1577
|
-
const
|
|
1578
|
-
(0, vitest.expect)(
|
|
1589
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 1e3);
|
|
1590
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1591
|
+
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1579
1592
|
});
|
|
1580
|
-
vitest.test.concurrent(`should return 404 on GET after TTL expires`, async () => {
|
|
1593
|
+
vitest.test.concurrent(`should return 404 on GET after TTL expires (idle)`, async () => {
|
|
1581
1594
|
const streamPath = uniquePath(`ttl-expire-get`);
|
|
1582
1595
|
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1583
1596
|
method: `PUT`,
|
|
@@ -1588,13 +1601,11 @@ function runConformanceTests(options) {
|
|
|
1588
1601
|
body: `test data`
|
|
1589
1602
|
});
|
|
1590
1603
|
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1591
|
-
|
|
1592
|
-
(0, vitest.expect)(getBefore.status).toBe(200);
|
|
1593
|
-
await sleep(1500);
|
|
1604
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 1e3);
|
|
1594
1605
|
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1595
1606
|
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1596
1607
|
});
|
|
1597
|
-
vitest.test.concurrent(`should return 404 on POST append after TTL expires`, async () => {
|
|
1608
|
+
vitest.test.concurrent(`should return 404 on POST append after TTL expires (idle)`, async () => {
|
|
1598
1609
|
const streamPath = uniquePath(`ttl-expire-post`);
|
|
1599
1610
|
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1600
1611
|
method: `PUT`,
|
|
@@ -1604,13 +1615,7 @@ function runConformanceTests(options) {
|
|
|
1604
1615
|
}
|
|
1605
1616
|
});
|
|
1606
1617
|
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1607
|
-
|
|
1608
|
-
method: `POST`,
|
|
1609
|
-
headers: { "Content-Type": `text/plain` },
|
|
1610
|
-
body: `appended data`
|
|
1611
|
-
});
|
|
1612
|
-
(0, vitest.expect)(postBefore.status).toBe(204);
|
|
1613
|
-
await sleep(1500);
|
|
1618
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 1e3);
|
|
1614
1619
|
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1615
1620
|
method: `POST`,
|
|
1616
1621
|
headers: { "Content-Type": `text/plain` },
|
|
@@ -1620,7 +1625,7 @@ function runConformanceTests(options) {
|
|
|
1620
1625
|
});
|
|
1621
1626
|
vitest.test.concurrent(`should return 404 on HEAD after Expires-At passes`, async () => {
|
|
1622
1627
|
const streamPath = uniquePath(`expires-at-head`);
|
|
1623
|
-
const expiresAt = new Date(Date.now() +
|
|
1628
|
+
const expiresAt = new Date(Date.now() + 3e3).toISOString();
|
|
1624
1629
|
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1625
1630
|
method: `PUT`,
|
|
1626
1631
|
headers: {
|
|
@@ -1631,13 +1636,13 @@ function runConformanceTests(options) {
|
|
|
1631
1636
|
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1632
1637
|
const headBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1633
1638
|
(0, vitest.expect)(headBefore.status).toBe(200);
|
|
1634
|
-
await
|
|
1635
|
-
const
|
|
1636
|
-
(0, vitest.expect)(
|
|
1639
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 3e3);
|
|
1640
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1641
|
+
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1637
1642
|
});
|
|
1638
1643
|
vitest.test.concurrent(`should return 404 on GET after Expires-At passes`, async () => {
|
|
1639
1644
|
const streamPath = uniquePath(`expires-at-get`);
|
|
1640
|
-
const expiresAt = new Date(Date.now() +
|
|
1645
|
+
const expiresAt = new Date(Date.now() + 3e3).toISOString();
|
|
1641
1646
|
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1642
1647
|
method: `PUT`,
|
|
1643
1648
|
headers: {
|
|
@@ -1649,13 +1654,13 @@ function runConformanceTests(options) {
|
|
|
1649
1654
|
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1650
1655
|
const getBefore = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1651
1656
|
(0, vitest.expect)(getBefore.status).toBe(200);
|
|
1652
|
-
await
|
|
1657
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 3e3);
|
|
1653
1658
|
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1654
1659
|
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1655
1660
|
});
|
|
1656
1661
|
vitest.test.concurrent(`should return 404 on POST append after Expires-At passes`, async () => {
|
|
1657
1662
|
const streamPath = uniquePath(`expires-at-post`);
|
|
1658
|
-
const expiresAt = new Date(Date.now() +
|
|
1663
|
+
const expiresAt = new Date(Date.now() + 3e3).toISOString();
|
|
1659
1664
|
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1660
1665
|
method: `PUT`,
|
|
1661
1666
|
headers: {
|
|
@@ -1670,7 +1675,7 @@ function runConformanceTests(options) {
|
|
|
1670
1675
|
body: `appended data`
|
|
1671
1676
|
});
|
|
1672
1677
|
(0, vitest.expect)(postBefore.status).toBe(204);
|
|
1673
|
-
await
|
|
1678
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 3e3);
|
|
1674
1679
|
const postAfter = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1675
1680
|
method: `POST`,
|
|
1676
1681
|
headers: { "Content-Type": `text/plain` },
|
|
@@ -1689,7 +1694,7 @@ function runConformanceTests(options) {
|
|
|
1689
1694
|
body: `original data`
|
|
1690
1695
|
});
|
|
1691
1696
|
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1692
|
-
await
|
|
1697
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 1e3);
|
|
1693
1698
|
const recreateResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1694
1699
|
method: `PUT`,
|
|
1695
1700
|
headers: {
|
|
@@ -1704,6 +1709,81 @@ function runConformanceTests(options) {
|
|
|
1704
1709
|
const body = await getResponse.text();
|
|
1705
1710
|
(0, vitest.expect)(body).toContain(`new data`);
|
|
1706
1711
|
});
|
|
1712
|
+
vitest.test.concurrent(`should extend TTL on write (sliding window)`, async () => {
|
|
1713
|
+
const streamPath = uniquePath(`ttl-renew-write`);
|
|
1714
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1715
|
+
method: `PUT`,
|
|
1716
|
+
headers: {
|
|
1717
|
+
"Content-Type": `text/plain`,
|
|
1718
|
+
"Stream-TTL": `2`
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1722
|
+
await sleep(1500);
|
|
1723
|
+
const appendResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1724
|
+
method: `POST`,
|
|
1725
|
+
headers: { "Content-Type": `text/plain` },
|
|
1726
|
+
body: `keep alive`
|
|
1727
|
+
});
|
|
1728
|
+
(0, vitest.expect)(appendResponse.status).toBe(204);
|
|
1729
|
+
await sleep(1500);
|
|
1730
|
+
const headResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1731
|
+
(0, vitest.expect)(headResponse.status).toBe(200);
|
|
1732
|
+
});
|
|
1733
|
+
vitest.test.concurrent(`should extend TTL on read (sliding window)`, async () => {
|
|
1734
|
+
const streamPath = uniquePath(`ttl-renew-read`);
|
|
1735
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1736
|
+
method: `PUT`,
|
|
1737
|
+
headers: {
|
|
1738
|
+
"Content-Type": `text/plain`,
|
|
1739
|
+
"Stream-TTL": `2`
|
|
1740
|
+
},
|
|
1741
|
+
body: `test data`
|
|
1742
|
+
});
|
|
1743
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1744
|
+
await sleep(1500);
|
|
1745
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1746
|
+
(0, vitest.expect)(readResponse.status).toBe(200);
|
|
1747
|
+
await sleep(1500);
|
|
1748
|
+
const headResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1749
|
+
(0, vitest.expect)(headResponse.status).toBe(200);
|
|
1750
|
+
});
|
|
1751
|
+
vitest.test.concurrent(`should NOT extend TTL on HEAD`, async () => {
|
|
1752
|
+
const streamPath = uniquePath(`ttl-no-renew-head`);
|
|
1753
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1754
|
+
method: `PUT`,
|
|
1755
|
+
headers: {
|
|
1756
|
+
"Content-Type": `text/plain`,
|
|
1757
|
+
"Stream-TTL": `2`
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1761
|
+
await sleep(1500);
|
|
1762
|
+
const headMid = await fetch(`${getBaseUrl()}${streamPath}`, { method: `HEAD` });
|
|
1763
|
+
(0, vitest.expect)(headMid.status).toBe(200);
|
|
1764
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 500);
|
|
1765
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1766
|
+
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1767
|
+
});
|
|
1768
|
+
vitest.test.concurrent(`should NOT extend Expires-At on read or write`, async () => {
|
|
1769
|
+
const streamPath = uniquePath(`expires-at-no-renew`);
|
|
1770
|
+
const expiresAt = new Date(Date.now() + 4e3).toISOString();
|
|
1771
|
+
const createResponse = await fetch(`${getBaseUrl()}${streamPath}`, {
|
|
1772
|
+
method: `PUT`,
|
|
1773
|
+
headers: {
|
|
1774
|
+
"Content-Type": `text/plain`,
|
|
1775
|
+
"Stream-Expires-At": expiresAt
|
|
1776
|
+
},
|
|
1777
|
+
body: `test data`
|
|
1778
|
+
});
|
|
1779
|
+
(0, vitest.expect)(createResponse.status).toBe(201);
|
|
1780
|
+
await sleep(2e3);
|
|
1781
|
+
const readResponse = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1782
|
+
(0, vitest.expect)(readResponse.status).toBe(200);
|
|
1783
|
+
await waitForDeletion(`${getBaseUrl()}${streamPath}`, 2e3);
|
|
1784
|
+
const getAfter = await fetch(`${getBaseUrl()}${streamPath}`, { method: `GET` });
|
|
1785
|
+
(0, vitest.expect)(getAfter.status).toBe(404);
|
|
1786
|
+
});
|
|
1707
1787
|
});
|
|
1708
1788
|
(0, vitest.describe)(`Caching and ETag`, () => {
|
|
1709
1789
|
(0, vitest.test)(`should generate ETag on GET responses`, async () => {
|
|
@@ -2826,8 +2906,11 @@ function runConformanceTests(options) {
|
|
|
2826
2906
|
const finalResult = await readEntireStream(streamPath);
|
|
2827
2907
|
(0, vitest.expect)(finalResult).toEqual(expected);
|
|
2828
2908
|
}
|
|
2829
|
-
), {
|
|
2830
|
-
|
|
2909
|
+
), {
|
|
2910
|
+
numRuns: 20,
|
|
2911
|
+
interruptAfterTimeLimit: 1e4
|
|
2912
|
+
});
|
|
2913
|
+
}, 3e4);
|
|
2831
2914
|
(0, vitest.test)(`single byte values cover full range (0-255) with concurrent readers during write`, async () => {
|
|
2832
2915
|
await fast_check.assert(fast_check.asyncProperty(
|
|
2833
2916
|
// Generate a byte value from 0-255
|
|
@@ -2860,8 +2943,11 @@ function runConformanceTests(options) {
|
|
|
2860
2943
|
const finalResult = await readEntireStream(streamPath);
|
|
2861
2944
|
(0, vitest.expect)(finalResult).toEqual(expected);
|
|
2862
2945
|
}
|
|
2863
|
-
), {
|
|
2864
|
-
|
|
2946
|
+
), {
|
|
2947
|
+
numRuns: 50,
|
|
2948
|
+
interruptAfterTimeLimit: 1e4
|
|
2949
|
+
});
|
|
2950
|
+
}, 3e4);
|
|
2865
2951
|
});
|
|
2866
2952
|
(0, vitest.describe)(`Operation Sequence Properties`, () => {
|
|
2867
2953
|
(0, vitest.test)(`random operation sequences maintain stream invariants`, async () => {
|
|
@@ -2938,8 +3024,11 @@ function runConformanceTests(options) {
|
|
|
2938
3024
|
}
|
|
2939
3025
|
return true;
|
|
2940
3026
|
}
|
|
2941
|
-
), {
|
|
2942
|
-
|
|
3027
|
+
), {
|
|
3028
|
+
numRuns: 15,
|
|
3029
|
+
interruptAfterTimeLimit: 3e4
|
|
3030
|
+
});
|
|
3031
|
+
}, 6e4);
|
|
2943
3032
|
(0, vitest.test)(`offsets are always monotonically increasing`, async () => {
|
|
2944
3033
|
await fast_check.assert(fast_check.asyncProperty(
|
|
2945
3034
|
// Generate multiple chunks to append
|
|
@@ -5022,6 +5111,1650 @@ function runConformanceTests(options) {
|
|
|
5022
5111
|
});
|
|
5023
5112
|
});
|
|
5024
5113
|
});
|
|
5114
|
+
(0, vitest.describe)(`Fork - Creation`, () => {
|
|
5115
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
5116
|
+
const STREAM_FORK_OFFSET_HEADER = `Stream-Fork-Offset`;
|
|
5117
|
+
const STREAM_CLOSED_HEADER_FORK = `Stream-Closed`;
|
|
5118
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5119
|
+
(0, vitest.test)(`should fork at current head (default)`, async () => {
|
|
5120
|
+
const id = uniqueId();
|
|
5121
|
+
const sourcePath = `/v1/stream/fork-create-head-src-${id}`;
|
|
5122
|
+
const forkPath = `/v1/stream/fork-create-head-fork-${id}`;
|
|
5123
|
+
const createRes = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5124
|
+
method: `PUT`,
|
|
5125
|
+
headers: { "Content-Type": `text/plain` },
|
|
5126
|
+
body: `source data`
|
|
5127
|
+
});
|
|
5128
|
+
(0, vitest.expect)(createRes.status).toBe(201);
|
|
5129
|
+
const sourceOffset = createRes.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
5130
|
+
(0, vitest.expect)(sourceOffset).toBeDefined();
|
|
5131
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5132
|
+
method: `PUT`,
|
|
5133
|
+
headers: {
|
|
5134
|
+
"Content-Type": `text/plain`,
|
|
5135
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5136
|
+
}
|
|
5137
|
+
});
|
|
5138
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
5139
|
+
});
|
|
5140
|
+
(0, vitest.test)(`should fork at a specific offset`, async () => {
|
|
5141
|
+
const id = uniqueId();
|
|
5142
|
+
const sourcePath = `/v1/stream/fork-create-offset-src-${id}`;
|
|
5143
|
+
const forkPath = `/v1/stream/fork-create-offset-fork-${id}`;
|
|
5144
|
+
const createRes = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5145
|
+
method: `PUT`,
|
|
5146
|
+
headers: { "Content-Type": `text/plain` },
|
|
5147
|
+
body: `first`
|
|
5148
|
+
});
|
|
5149
|
+
(0, vitest.expect)(createRes.status).toBe(201);
|
|
5150
|
+
const midOffset = createRes.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
5151
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5152
|
+
method: `POST`,
|
|
5153
|
+
headers: { "Content-Type": `text/plain` },
|
|
5154
|
+
body: `second`
|
|
5155
|
+
});
|
|
5156
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5157
|
+
method: `PUT`,
|
|
5158
|
+
headers: {
|
|
5159
|
+
"Content-Type": `text/plain`,
|
|
5160
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
5161
|
+
[STREAM_FORK_OFFSET_HEADER]: midOffset
|
|
5162
|
+
}
|
|
5163
|
+
});
|
|
5164
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
5165
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5166
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5167
|
+
const body = await readRes.text();
|
|
5168
|
+
(0, vitest.expect)(body).toBe(`first`);
|
|
5169
|
+
});
|
|
5170
|
+
(0, vitest.test)(`should fork at zero offset (empty inherited data)`, async () => {
|
|
5171
|
+
const id = uniqueId();
|
|
5172
|
+
const sourcePath = `/v1/stream/fork-create-zero-src-${id}`;
|
|
5173
|
+
const forkPath = `/v1/stream/fork-create-zero-fork-${id}`;
|
|
5174
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5175
|
+
method: `PUT`,
|
|
5176
|
+
headers: { "Content-Type": `text/plain` },
|
|
5177
|
+
body: `source data`
|
|
5178
|
+
});
|
|
5179
|
+
const zeroOffset = `0000000000000000_0000000000000000`;
|
|
5180
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5181
|
+
method: `PUT`,
|
|
5182
|
+
headers: {
|
|
5183
|
+
"Content-Type": `text/plain`,
|
|
5184
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
5185
|
+
[STREAM_FORK_OFFSET_HEADER]: zeroOffset
|
|
5186
|
+
}
|
|
5187
|
+
});
|
|
5188
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
5189
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5190
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5191
|
+
const body = await readRes.text();
|
|
5192
|
+
(0, vitest.expect)(body).toBe(``);
|
|
5193
|
+
(0, vitest.expect)(readRes.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
5194
|
+
});
|
|
5195
|
+
(0, vitest.test)(`should fork at head offset (all source data inherited)`, async () => {
|
|
5196
|
+
const id = uniqueId();
|
|
5197
|
+
const sourcePath = `/v1/stream/fork-create-all-src-${id}`;
|
|
5198
|
+
const forkPath = `/v1/stream/fork-create-all-fork-${id}`;
|
|
5199
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5200
|
+
method: `PUT`,
|
|
5201
|
+
headers: { "Content-Type": `text/plain` },
|
|
5202
|
+
body: `chunk1`
|
|
5203
|
+
});
|
|
5204
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5205
|
+
method: `POST`,
|
|
5206
|
+
headers: { "Content-Type": `text/plain` },
|
|
5207
|
+
body: `chunk2`
|
|
5208
|
+
});
|
|
5209
|
+
const headRes = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `HEAD` });
|
|
5210
|
+
const headOffset = headRes.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
5211
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5212
|
+
method: `PUT`,
|
|
5213
|
+
headers: {
|
|
5214
|
+
"Content-Type": `text/plain`,
|
|
5215
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
5216
|
+
[STREAM_FORK_OFFSET_HEADER]: headOffset
|
|
5217
|
+
}
|
|
5218
|
+
});
|
|
5219
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
5220
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5221
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5222
|
+
const body = await readRes.text();
|
|
5223
|
+
(0, vitest.expect)(body).toBe(`chunk1chunk2`);
|
|
5224
|
+
});
|
|
5225
|
+
(0, vitest.test)(`should return 404 when forking a nonexistent stream`, async () => {
|
|
5226
|
+
const id = uniqueId();
|
|
5227
|
+
const forkPath = `/v1/stream/fork-create-404-fork-${id}`;
|
|
5228
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5229
|
+
method: `PUT`,
|
|
5230
|
+
headers: {
|
|
5231
|
+
"Content-Type": `text/plain`,
|
|
5232
|
+
[STREAM_FORKED_FROM_HEADER]: `/v1/stream/nonexistent-${id}`
|
|
5233
|
+
}
|
|
5234
|
+
});
|
|
5235
|
+
(0, vitest.expect)(forkRes.status).toBe(404);
|
|
5236
|
+
});
|
|
5237
|
+
(0, vitest.test)(`should return 400 when forking at offset beyond stream length`, async () => {
|
|
5238
|
+
const id = uniqueId();
|
|
5239
|
+
const sourcePath = `/v1/stream/fork-create-beyond-src-${id}`;
|
|
5240
|
+
const forkPath = `/v1/stream/fork-create-beyond-fork-${id}`;
|
|
5241
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5242
|
+
method: `PUT`,
|
|
5243
|
+
headers: { "Content-Type": `text/plain` },
|
|
5244
|
+
body: `small data`
|
|
5245
|
+
});
|
|
5246
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5247
|
+
method: `PUT`,
|
|
5248
|
+
headers: {
|
|
5249
|
+
"Content-Type": `text/plain`,
|
|
5250
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
5251
|
+
[STREAM_FORK_OFFSET_HEADER]: `9999999999999999_9999999999999999`
|
|
5252
|
+
}
|
|
5253
|
+
});
|
|
5254
|
+
(0, vitest.expect)(forkRes.status).toBe(400);
|
|
5255
|
+
});
|
|
5256
|
+
(0, vitest.test)(`should return 409 when forking to path already in use with different config`, async () => {
|
|
5257
|
+
const id = uniqueId();
|
|
5258
|
+
const sourcePath = `/v1/stream/fork-create-conflict-src-${id}`;
|
|
5259
|
+
const forkPath = `/v1/stream/fork-create-conflict-fork-${id}`;
|
|
5260
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5261
|
+
method: `PUT`,
|
|
5262
|
+
headers: { "Content-Type": `text/plain` },
|
|
5263
|
+
body: `source`
|
|
5264
|
+
});
|
|
5265
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5266
|
+
method: `PUT`,
|
|
5267
|
+
headers: { "Content-Type": `application/json` }
|
|
5268
|
+
});
|
|
5269
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5270
|
+
method: `PUT`,
|
|
5271
|
+
headers: {
|
|
5272
|
+
"Content-Type": `text/plain`,
|
|
5273
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5274
|
+
}
|
|
5275
|
+
});
|
|
5276
|
+
(0, vitest.expect)(forkRes.status).toBe(409);
|
|
5277
|
+
});
|
|
5278
|
+
(0, vitest.test)(`should fork a closed stream — fork starts open`, async () => {
|
|
5279
|
+
const id = uniqueId();
|
|
5280
|
+
const sourcePath = `/v1/stream/fork-create-closed-src-${id}`;
|
|
5281
|
+
const forkPath = `/v1/stream/fork-create-closed-fork-${id}`;
|
|
5282
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5283
|
+
method: `PUT`,
|
|
5284
|
+
headers: { "Content-Type": `text/plain` },
|
|
5285
|
+
body: `closed data`
|
|
5286
|
+
});
|
|
5287
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5288
|
+
method: `POST`,
|
|
5289
|
+
headers: { [STREAM_CLOSED_HEADER_FORK]: `true` }
|
|
5290
|
+
});
|
|
5291
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5292
|
+
method: `PUT`,
|
|
5293
|
+
headers: {
|
|
5294
|
+
"Content-Type": `text/plain`,
|
|
5295
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5296
|
+
}
|
|
5297
|
+
});
|
|
5298
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
5299
|
+
(0, vitest.expect)(forkRes.headers.get(STREAM_CLOSED_HEADER_FORK)).toBeNull();
|
|
5300
|
+
const appendRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5301
|
+
method: `POST`,
|
|
5302
|
+
headers: { "Content-Type": `text/plain` },
|
|
5303
|
+
body: ` fork data`
|
|
5304
|
+
});
|
|
5305
|
+
(0, vitest.expect)(appendRes.status).toBe(204);
|
|
5306
|
+
});
|
|
5307
|
+
(0, vitest.test)(`should fork an empty stream`, async () => {
|
|
5308
|
+
const id = uniqueId();
|
|
5309
|
+
const sourcePath = `/v1/stream/fork-create-empty-src-${id}`;
|
|
5310
|
+
const forkPath = `/v1/stream/fork-create-empty-fork-${id}`;
|
|
5311
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5312
|
+
method: `PUT`,
|
|
5313
|
+
headers: { "Content-Type": `text/plain` }
|
|
5314
|
+
});
|
|
5315
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5316
|
+
method: `PUT`,
|
|
5317
|
+
headers: {
|
|
5318
|
+
"Content-Type": `text/plain`,
|
|
5319
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5320
|
+
}
|
|
5321
|
+
});
|
|
5322
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
5323
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5324
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5325
|
+
const body = await readRes.text();
|
|
5326
|
+
(0, vitest.expect)(body).toBe(``);
|
|
5327
|
+
(0, vitest.expect)(readRes.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
5328
|
+
});
|
|
5329
|
+
(0, vitest.test)(`should fork preserving content-type when specified`, async () => {
|
|
5330
|
+
const id = uniqueId();
|
|
5331
|
+
const sourcePath = `/v1/stream/fork-create-ct-src-${id}`;
|
|
5332
|
+
const forkPath = `/v1/stream/fork-create-ct-fork-${id}`;
|
|
5333
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5334
|
+
method: `PUT`,
|
|
5335
|
+
headers: { "Content-Type": `application/json` },
|
|
5336
|
+
body: `[{"key":"value"}]`
|
|
5337
|
+
});
|
|
5338
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5339
|
+
method: `PUT`,
|
|
5340
|
+
headers: {
|
|
5341
|
+
"Content-Type": `application/json`,
|
|
5342
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5343
|
+
}
|
|
5344
|
+
});
|
|
5345
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
5346
|
+
(0, vitest.expect)(forkRes.headers.get(`content-type`)).toBe(`application/json`);
|
|
5347
|
+
const headRes = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
5348
|
+
(0, vitest.expect)(headRes.headers.get(`content-type`)).toBe(`application/json`);
|
|
5349
|
+
});
|
|
5350
|
+
});
|
|
5351
|
+
(0, vitest.describe)(`Fork - Reading`, () => {
|
|
5352
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
5353
|
+
const STREAM_FORK_OFFSET_HEADER = `Stream-Fork-Offset`;
|
|
5354
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5355
|
+
(0, vitest.test)(`should read entire fork (source + fork data)`, async () => {
|
|
5356
|
+
const id = uniqueId();
|
|
5357
|
+
const sourcePath = `/v1/stream/fork-read-entire-src-${id}`;
|
|
5358
|
+
const forkPath = `/v1/stream/fork-read-entire-fork-${id}`;
|
|
5359
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5360
|
+
method: `PUT`,
|
|
5361
|
+
headers: { "Content-Type": `text/plain` },
|
|
5362
|
+
body: `source`
|
|
5363
|
+
});
|
|
5364
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5365
|
+
method: `PUT`,
|
|
5366
|
+
headers: {
|
|
5367
|
+
"Content-Type": `text/plain`,
|
|
5368
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5369
|
+
}
|
|
5370
|
+
});
|
|
5371
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5372
|
+
method: `POST`,
|
|
5373
|
+
headers: { "Content-Type": `text/plain` },
|
|
5374
|
+
body: ` fork`
|
|
5375
|
+
});
|
|
5376
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5377
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5378
|
+
const body = await readRes.text();
|
|
5379
|
+
(0, vitest.expect)(body).toBe(`source fork`);
|
|
5380
|
+
});
|
|
5381
|
+
(0, vitest.test)(`should read only inherited portion`, async () => {
|
|
5382
|
+
const id = uniqueId();
|
|
5383
|
+
const sourcePath = `/v1/stream/fork-read-inherited-src-${id}`;
|
|
5384
|
+
const forkPath = `/v1/stream/fork-read-inherited-fork-${id}`;
|
|
5385
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5386
|
+
method: `PUT`,
|
|
5387
|
+
headers: { "Content-Type": `text/plain` },
|
|
5388
|
+
body: `inherited data`
|
|
5389
|
+
});
|
|
5390
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5391
|
+
method: `PUT`,
|
|
5392
|
+
headers: {
|
|
5393
|
+
"Content-Type": `text/plain`,
|
|
5394
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5395
|
+
}
|
|
5396
|
+
});
|
|
5397
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5398
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5399
|
+
const body = await readRes.text();
|
|
5400
|
+
(0, vitest.expect)(body).toBe(`inherited data`);
|
|
5401
|
+
(0, vitest.expect)(readRes.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
5402
|
+
});
|
|
5403
|
+
(0, vitest.test)(`should read only fork's own data (starting past fork offset)`, async () => {
|
|
5404
|
+
const id = uniqueId();
|
|
5405
|
+
const sourcePath = `/v1/stream/fork-read-own-src-${id}`;
|
|
5406
|
+
const forkPath = `/v1/stream/fork-read-own-fork-${id}`;
|
|
5407
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5408
|
+
method: `PUT`,
|
|
5409
|
+
headers: { "Content-Type": `text/plain` },
|
|
5410
|
+
body: `source`
|
|
5411
|
+
});
|
|
5412
|
+
const sourceHead = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `HEAD` });
|
|
5413
|
+
const forkOffset = sourceHead.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
5414
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5415
|
+
method: `PUT`,
|
|
5416
|
+
headers: {
|
|
5417
|
+
"Content-Type": `text/plain`,
|
|
5418
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5419
|
+
}
|
|
5420
|
+
});
|
|
5421
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5422
|
+
method: `POST`,
|
|
5423
|
+
headers: { "Content-Type": `text/plain` },
|
|
5424
|
+
body: `fork only`
|
|
5425
|
+
});
|
|
5426
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=${forkOffset}`);
|
|
5427
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5428
|
+
const body = await readRes.text();
|
|
5429
|
+
(0, vitest.expect)(body).toBe(`fork only`);
|
|
5430
|
+
});
|
|
5431
|
+
(0, vitest.test)(`should read across fork boundary`, async () => {
|
|
5432
|
+
const id = uniqueId();
|
|
5433
|
+
const sourcePath = `/v1/stream/fork-read-boundary-src-${id}`;
|
|
5434
|
+
const forkPath = `/v1/stream/fork-read-boundary-fork-${id}`;
|
|
5435
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5436
|
+
method: `PUT`,
|
|
5437
|
+
headers: { "Content-Type": `text/plain` },
|
|
5438
|
+
body: `A`
|
|
5439
|
+
});
|
|
5440
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5441
|
+
method: `POST`,
|
|
5442
|
+
headers: { "Content-Type": `text/plain` },
|
|
5443
|
+
body: `B`
|
|
5444
|
+
});
|
|
5445
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5446
|
+
method: `PUT`,
|
|
5447
|
+
headers: {
|
|
5448
|
+
"Content-Type": `text/plain`,
|
|
5449
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5450
|
+
}
|
|
5451
|
+
});
|
|
5452
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5453
|
+
method: `POST`,
|
|
5454
|
+
headers: { "Content-Type": `text/plain` },
|
|
5455
|
+
body: `C`
|
|
5456
|
+
});
|
|
5457
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5458
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5459
|
+
const body = await readRes.text();
|
|
5460
|
+
(0, vitest.expect)(body).toBe(`ABC`);
|
|
5461
|
+
});
|
|
5462
|
+
(0, vitest.test)(`should not show source appends after fork`, async () => {
|
|
5463
|
+
const id = uniqueId();
|
|
5464
|
+
const sourcePath = `/v1/stream/fork-read-isolation-src-${id}`;
|
|
5465
|
+
const forkPath = `/v1/stream/fork-read-isolation-fork-${id}`;
|
|
5466
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5467
|
+
method: `PUT`,
|
|
5468
|
+
headers: { "Content-Type": `text/plain` },
|
|
5469
|
+
body: `before`
|
|
5470
|
+
});
|
|
5471
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5472
|
+
method: `PUT`,
|
|
5473
|
+
headers: {
|
|
5474
|
+
"Content-Type": `text/plain`,
|
|
5475
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5476
|
+
}
|
|
5477
|
+
});
|
|
5478
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5479
|
+
method: `POST`,
|
|
5480
|
+
headers: { "Content-Type": `text/plain` },
|
|
5481
|
+
body: ` after`
|
|
5482
|
+
});
|
|
5483
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5484
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5485
|
+
const body = await readRes.text();
|
|
5486
|
+
(0, vitest.expect)(body).toBe(`before`);
|
|
5487
|
+
});
|
|
5488
|
+
(0, vitest.test)(`should NOT include fork headers on HEAD/GET/PUT responses (forks are transparent)`, async () => {
|
|
5489
|
+
const id = uniqueId();
|
|
5490
|
+
const sourcePath = `/v1/stream/fork-read-headers-src-${id}`;
|
|
5491
|
+
const forkPath = `/v1/stream/fork-read-headers-fork-${id}`;
|
|
5492
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5493
|
+
method: `PUT`,
|
|
5494
|
+
headers: { "Content-Type": `text/plain` },
|
|
5495
|
+
body: `data`
|
|
5496
|
+
});
|
|
5497
|
+
const putRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5498
|
+
method: `PUT`,
|
|
5499
|
+
headers: {
|
|
5500
|
+
"Content-Type": `text/plain`,
|
|
5501
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5502
|
+
}
|
|
5503
|
+
});
|
|
5504
|
+
(0, vitest.expect)(putRes.headers.get(STREAM_FORKED_FROM_HEADER)).toBeNull();
|
|
5505
|
+
(0, vitest.expect)(putRes.headers.get(STREAM_FORK_OFFSET_HEADER)).toBeNull();
|
|
5506
|
+
const headRes = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
5507
|
+
(0, vitest.expect)(headRes.headers.get(STREAM_FORKED_FROM_HEADER)).toBeNull();
|
|
5508
|
+
(0, vitest.expect)(headRes.headers.get(STREAM_FORK_OFFSET_HEADER)).toBeNull();
|
|
5509
|
+
const getRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5510
|
+
await getRes.text();
|
|
5511
|
+
(0, vitest.expect)(getRes.headers.get(STREAM_FORKED_FROM_HEADER)).toBeNull();
|
|
5512
|
+
(0, vitest.expect)(getRes.headers.get(STREAM_FORK_OFFSET_HEADER)).toBeNull();
|
|
5513
|
+
});
|
|
5514
|
+
});
|
|
5515
|
+
(0, vitest.describe)(`Fork - Appending`, () => {
|
|
5516
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
5517
|
+
const STREAM_CLOSED_HEADER_FORK = `Stream-Closed`;
|
|
5518
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5519
|
+
(0, vitest.test)(`should append to a fork`, async () => {
|
|
5520
|
+
const id = uniqueId();
|
|
5521
|
+
const sourcePath = `/v1/stream/fork-append-src-${id}`;
|
|
5522
|
+
const forkPath = `/v1/stream/fork-append-fork-${id}`;
|
|
5523
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5524
|
+
method: `PUT`,
|
|
5525
|
+
headers: { "Content-Type": `text/plain` },
|
|
5526
|
+
body: `source`
|
|
5527
|
+
});
|
|
5528
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5529
|
+
method: `PUT`,
|
|
5530
|
+
headers: {
|
|
5531
|
+
"Content-Type": `text/plain`,
|
|
5532
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5533
|
+
}
|
|
5534
|
+
});
|
|
5535
|
+
const appendRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5536
|
+
method: `POST`,
|
|
5537
|
+
headers: { "Content-Type": `text/plain` },
|
|
5538
|
+
body: ` appended`
|
|
5539
|
+
});
|
|
5540
|
+
(0, vitest.expect)(appendRes.status).toBe(204);
|
|
5541
|
+
(0, vitest.expect)(appendRes.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER)).toBeDefined();
|
|
5542
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5543
|
+
const body = await readRes.text();
|
|
5544
|
+
(0, vitest.expect)(body).toBe(`source appended`);
|
|
5545
|
+
});
|
|
5546
|
+
(0, vitest.test)(`should support idempotent producer on fork`, async () => {
|
|
5547
|
+
const id = uniqueId();
|
|
5548
|
+
const sourcePath = `/v1/stream/fork-append-idempotent-src-${id}`;
|
|
5549
|
+
const forkPath = `/v1/stream/fork-append-idempotent-fork-${id}`;
|
|
5550
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5551
|
+
method: `PUT`,
|
|
5552
|
+
headers: { "Content-Type": `text/plain` },
|
|
5553
|
+
body: `source`
|
|
5554
|
+
});
|
|
5555
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5556
|
+
method: `PUT`,
|
|
5557
|
+
headers: {
|
|
5558
|
+
"Content-Type": `text/plain`,
|
|
5559
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5560
|
+
}
|
|
5561
|
+
});
|
|
5562
|
+
const append1 = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5563
|
+
method: `POST`,
|
|
5564
|
+
headers: {
|
|
5565
|
+
"Content-Type": `text/plain`,
|
|
5566
|
+
"Producer-Id": `fork-producer-${id}`,
|
|
5567
|
+
"Producer-Epoch": `0`,
|
|
5568
|
+
"Producer-Seq": `0`
|
|
5569
|
+
},
|
|
5570
|
+
body: `msg1`
|
|
5571
|
+
});
|
|
5572
|
+
(0, vitest.expect)(append1.status).toBe(200);
|
|
5573
|
+
const append1Retry = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5574
|
+
method: `POST`,
|
|
5575
|
+
headers: {
|
|
5576
|
+
"Content-Type": `text/plain`,
|
|
5577
|
+
"Producer-Id": `fork-producer-${id}`,
|
|
5578
|
+
"Producer-Epoch": `0`,
|
|
5579
|
+
"Producer-Seq": `0`
|
|
5580
|
+
},
|
|
5581
|
+
body: `msg1`
|
|
5582
|
+
});
|
|
5583
|
+
(0, vitest.expect)(append1Retry.status).toBe(204);
|
|
5584
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5585
|
+
const body = await readRes.text();
|
|
5586
|
+
(0, vitest.expect)(body).toBe(`sourcemsg1`);
|
|
5587
|
+
});
|
|
5588
|
+
(0, vitest.test)(`should close forked stream independently`, async () => {
|
|
5589
|
+
const id = uniqueId();
|
|
5590
|
+
const sourcePath = `/v1/stream/fork-append-close-src-${id}`;
|
|
5591
|
+
const forkPath = `/v1/stream/fork-append-close-fork-${id}`;
|
|
5592
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5593
|
+
method: `PUT`,
|
|
5594
|
+
headers: { "Content-Type": `text/plain` },
|
|
5595
|
+
body: `source`
|
|
5596
|
+
});
|
|
5597
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5598
|
+
method: `PUT`,
|
|
5599
|
+
headers: {
|
|
5600
|
+
"Content-Type": `text/plain`,
|
|
5601
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5602
|
+
}
|
|
5603
|
+
});
|
|
5604
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5605
|
+
method: `POST`,
|
|
5606
|
+
headers: { "Content-Type": `text/plain` },
|
|
5607
|
+
body: ` final`
|
|
5608
|
+
});
|
|
5609
|
+
const closeRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5610
|
+
method: `POST`,
|
|
5611
|
+
headers: { [STREAM_CLOSED_HEADER_FORK]: `true` }
|
|
5612
|
+
});
|
|
5613
|
+
(0, vitest.expect)([200, 204]).toContain(closeRes.status);
|
|
5614
|
+
(0, vitest.expect)(closeRes.headers.get(STREAM_CLOSED_HEADER_FORK)).toBe(`true`);
|
|
5615
|
+
const sourceHead = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `HEAD` });
|
|
5616
|
+
(0, vitest.expect)(sourceHead.headers.get(STREAM_CLOSED_HEADER_FORK)).toBeNull();
|
|
5617
|
+
});
|
|
5618
|
+
(0, vitest.test)(`should not affect fork when source is closed`, async () => {
|
|
5619
|
+
const id = uniqueId();
|
|
5620
|
+
const sourcePath = `/v1/stream/fork-append-src-close-src-${id}`;
|
|
5621
|
+
const forkPath = `/v1/stream/fork-append-src-close-fork-${id}`;
|
|
5622
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5623
|
+
method: `PUT`,
|
|
5624
|
+
headers: { "Content-Type": `text/plain` },
|
|
5625
|
+
body: `source`
|
|
5626
|
+
});
|
|
5627
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5628
|
+
method: `PUT`,
|
|
5629
|
+
headers: {
|
|
5630
|
+
"Content-Type": `text/plain`,
|
|
5631
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5632
|
+
}
|
|
5633
|
+
});
|
|
5634
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5635
|
+
method: `POST`,
|
|
5636
|
+
headers: { [STREAM_CLOSED_HEADER_FORK]: `true` }
|
|
5637
|
+
});
|
|
5638
|
+
const appendRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5639
|
+
method: `POST`,
|
|
5640
|
+
headers: { "Content-Type": `text/plain` },
|
|
5641
|
+
body: ` fork data`
|
|
5642
|
+
});
|
|
5643
|
+
(0, vitest.expect)(appendRes.status).toBe(204);
|
|
5644
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
5645
|
+
(0, vitest.expect)(forkHead.headers.get(STREAM_CLOSED_HEADER_FORK)).toBeNull();
|
|
5646
|
+
});
|
|
5647
|
+
(0, vitest.test)(`should append to source after fork — source independent`, async () => {
|
|
5648
|
+
const id = uniqueId();
|
|
5649
|
+
const sourcePath = `/v1/stream/fork-append-src-indep-src-${id}`;
|
|
5650
|
+
const forkPath = `/v1/stream/fork-append-src-indep-fork-${id}`;
|
|
5651
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5652
|
+
method: `PUT`,
|
|
5653
|
+
headers: { "Content-Type": `text/plain` },
|
|
5654
|
+
body: `initial`
|
|
5655
|
+
});
|
|
5656
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5657
|
+
method: `PUT`,
|
|
5658
|
+
headers: {
|
|
5659
|
+
"Content-Type": `text/plain`,
|
|
5660
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5661
|
+
}
|
|
5662
|
+
});
|
|
5663
|
+
const appendRes = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5664
|
+
method: `POST`,
|
|
5665
|
+
headers: { "Content-Type": `text/plain` },
|
|
5666
|
+
body: ` extra`
|
|
5667
|
+
});
|
|
5668
|
+
(0, vitest.expect)(appendRes.status).toBe(204);
|
|
5669
|
+
const sourceRead = await fetch(`${getBaseUrl()}${sourcePath}?offset=-1`);
|
|
5670
|
+
const sourceBody = await sourceRead.text();
|
|
5671
|
+
(0, vitest.expect)(sourceBody).toBe(`initial extra`);
|
|
5672
|
+
const forkRead = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
5673
|
+
const forkBody = await forkRead.text();
|
|
5674
|
+
(0, vitest.expect)(forkBody).toBe(`initial`);
|
|
5675
|
+
});
|
|
5676
|
+
});
|
|
5677
|
+
(0, vitest.describe)(`Fork - Recursive`, () => {
|
|
5678
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
5679
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5680
|
+
(0, vitest.test)(`should create a three-level fork chain`, async () => {
|
|
5681
|
+
const id = uniqueId();
|
|
5682
|
+
const level0 = `/v1/stream/fork-recursive-l0-${id}`;
|
|
5683
|
+
const level1 = `/v1/stream/fork-recursive-l1-${id}`;
|
|
5684
|
+
const level2 = `/v1/stream/fork-recursive-l2-${id}`;
|
|
5685
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
5686
|
+
method: `PUT`,
|
|
5687
|
+
headers: { "Content-Type": `text/plain` },
|
|
5688
|
+
body: `L0`
|
|
5689
|
+
});
|
|
5690
|
+
const fork1Res = await fetch(`${getBaseUrl()}${level1}`, {
|
|
5691
|
+
method: `PUT`,
|
|
5692
|
+
headers: {
|
|
5693
|
+
"Content-Type": `text/plain`,
|
|
5694
|
+
[STREAM_FORKED_FROM_HEADER]: level0
|
|
5695
|
+
}
|
|
5696
|
+
});
|
|
5697
|
+
(0, vitest.expect)(fork1Res.status).toBe(201);
|
|
5698
|
+
const fork2Res = await fetch(`${getBaseUrl()}${level2}`, {
|
|
5699
|
+
method: `PUT`,
|
|
5700
|
+
headers: {
|
|
5701
|
+
"Content-Type": `text/plain`,
|
|
5702
|
+
[STREAM_FORKED_FROM_HEADER]: level1
|
|
5703
|
+
}
|
|
5704
|
+
});
|
|
5705
|
+
(0, vitest.expect)(fork2Res.status).toBe(201);
|
|
5706
|
+
});
|
|
5707
|
+
(0, vitest.test)(`should fork at mid-point of inherited data`, async () => {
|
|
5708
|
+
const id = uniqueId();
|
|
5709
|
+
const level0 = `/v1/stream/fork-recursive-mid-l0-${id}`;
|
|
5710
|
+
const level1 = `/v1/stream/fork-recursive-mid-l1-${id}`;
|
|
5711
|
+
const level2 = `/v1/stream/fork-recursive-mid-l2-${id}`;
|
|
5712
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
5713
|
+
method: `PUT`,
|
|
5714
|
+
headers: { "Content-Type": `text/plain` },
|
|
5715
|
+
body: `A`
|
|
5716
|
+
});
|
|
5717
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
5718
|
+
method: `POST`,
|
|
5719
|
+
headers: { "Content-Type": `text/plain` },
|
|
5720
|
+
body: `B`
|
|
5721
|
+
});
|
|
5722
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
5723
|
+
method: `PUT`,
|
|
5724
|
+
headers: {
|
|
5725
|
+
"Content-Type": `text/plain`,
|
|
5726
|
+
[STREAM_FORKED_FROM_HEADER]: level0
|
|
5727
|
+
}
|
|
5728
|
+
});
|
|
5729
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
5730
|
+
method: `POST`,
|
|
5731
|
+
headers: { "Content-Type": `text/plain` },
|
|
5732
|
+
body: `C`
|
|
5733
|
+
});
|
|
5734
|
+
const l1Head = await fetch(`${getBaseUrl()}${level1}`, { method: `HEAD` });
|
|
5735
|
+
(0, vitest.expect)(l1Head.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER)).toBeDefined();
|
|
5736
|
+
const fork2Res = await fetch(`${getBaseUrl()}${level2}`, {
|
|
5737
|
+
method: `PUT`,
|
|
5738
|
+
headers: {
|
|
5739
|
+
"Content-Type": `text/plain`,
|
|
5740
|
+
[STREAM_FORKED_FROM_HEADER]: level1
|
|
5741
|
+
}
|
|
5742
|
+
});
|
|
5743
|
+
(0, vitest.expect)(fork2Res.status).toBe(201);
|
|
5744
|
+
const readRes = await fetch(`${getBaseUrl()}${level2}?offset=-1`);
|
|
5745
|
+
const body = await readRes.text();
|
|
5746
|
+
(0, vitest.expect)(body).toBe(`ABC`);
|
|
5747
|
+
});
|
|
5748
|
+
(0, vitest.test)(`should read correctly across three levels`, async () => {
|
|
5749
|
+
const id = uniqueId();
|
|
5750
|
+
const level0 = `/v1/stream/fork-recursive-read-l0-${id}`;
|
|
5751
|
+
const level1 = `/v1/stream/fork-recursive-read-l1-${id}`;
|
|
5752
|
+
const level2 = `/v1/stream/fork-recursive-read-l2-${id}`;
|
|
5753
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
5754
|
+
method: `PUT`,
|
|
5755
|
+
headers: { "Content-Type": `text/plain` },
|
|
5756
|
+
body: `A`
|
|
5757
|
+
});
|
|
5758
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
5759
|
+
method: `PUT`,
|
|
5760
|
+
headers: {
|
|
5761
|
+
"Content-Type": `text/plain`,
|
|
5762
|
+
[STREAM_FORKED_FROM_HEADER]: level0
|
|
5763
|
+
}
|
|
5764
|
+
});
|
|
5765
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
5766
|
+
method: `POST`,
|
|
5767
|
+
headers: { "Content-Type": `text/plain` },
|
|
5768
|
+
body: `B`
|
|
5769
|
+
});
|
|
5770
|
+
await fetch(`${getBaseUrl()}${level2}`, {
|
|
5771
|
+
method: `PUT`,
|
|
5772
|
+
headers: {
|
|
5773
|
+
"Content-Type": `text/plain`,
|
|
5774
|
+
[STREAM_FORKED_FROM_HEADER]: level1
|
|
5775
|
+
}
|
|
5776
|
+
});
|
|
5777
|
+
await fetch(`${getBaseUrl()}${level2}`, {
|
|
5778
|
+
method: `POST`,
|
|
5779
|
+
headers: { "Content-Type": `text/plain` },
|
|
5780
|
+
body: `C`
|
|
5781
|
+
});
|
|
5782
|
+
const r0 = await (await fetch(`${getBaseUrl()}${level0}?offset=-1`)).text();
|
|
5783
|
+
(0, vitest.expect)(r0).toBe(`A`);
|
|
5784
|
+
const r1 = await (await fetch(`${getBaseUrl()}${level1}?offset=-1`)).text();
|
|
5785
|
+
(0, vitest.expect)(r1).toBe(`AB`);
|
|
5786
|
+
const r2 = await (await fetch(`${getBaseUrl()}${level2}?offset=-1`)).text();
|
|
5787
|
+
(0, vitest.expect)(r2).toBe(`ABC`);
|
|
5788
|
+
});
|
|
5789
|
+
(0, vitest.test)(`should append at each level independently`, async () => {
|
|
5790
|
+
const id = uniqueId();
|
|
5791
|
+
const level0 = `/v1/stream/fork-recursive-indep-l0-${id}`;
|
|
5792
|
+
const level1 = `/v1/stream/fork-recursive-indep-l1-${id}`;
|
|
5793
|
+
const level2 = `/v1/stream/fork-recursive-indep-l2-${id}`;
|
|
5794
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
5795
|
+
method: `PUT`,
|
|
5796
|
+
headers: { "Content-Type": `text/plain` },
|
|
5797
|
+
body: `X`
|
|
5798
|
+
});
|
|
5799
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
5800
|
+
method: `PUT`,
|
|
5801
|
+
headers: {
|
|
5802
|
+
"Content-Type": `text/plain`,
|
|
5803
|
+
[STREAM_FORKED_FROM_HEADER]: level0
|
|
5804
|
+
}
|
|
5805
|
+
});
|
|
5806
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
5807
|
+
method: `POST`,
|
|
5808
|
+
headers: { "Content-Type": `text/plain` },
|
|
5809
|
+
body: `Y`
|
|
5810
|
+
});
|
|
5811
|
+
await fetch(`${getBaseUrl()}${level2}`, {
|
|
5812
|
+
method: `PUT`,
|
|
5813
|
+
headers: {
|
|
5814
|
+
"Content-Type": `text/plain`,
|
|
5815
|
+
[STREAM_FORKED_FROM_HEADER]: level1
|
|
5816
|
+
}
|
|
5817
|
+
});
|
|
5818
|
+
await fetch(`${getBaseUrl()}${level2}`, {
|
|
5819
|
+
method: `POST`,
|
|
5820
|
+
headers: { "Content-Type": `text/plain` },
|
|
5821
|
+
body: `Z`
|
|
5822
|
+
});
|
|
5823
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
5824
|
+
method: `POST`,
|
|
5825
|
+
headers: { "Content-Type": `text/plain` },
|
|
5826
|
+
body: `0`
|
|
5827
|
+
});
|
|
5828
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
5829
|
+
method: `POST`,
|
|
5830
|
+
headers: { "Content-Type": `text/plain` },
|
|
5831
|
+
body: `1`
|
|
5832
|
+
});
|
|
5833
|
+
const r0 = await (await fetch(`${getBaseUrl()}${level0}?offset=-1`)).text();
|
|
5834
|
+
(0, vitest.expect)(r0).toBe(`X0`);
|
|
5835
|
+
const r1 = await (await fetch(`${getBaseUrl()}${level1}?offset=-1`)).text();
|
|
5836
|
+
(0, vitest.expect)(r1).toBe(`XY1`);
|
|
5837
|
+
const r2 = await (await fetch(`${getBaseUrl()}${level2}?offset=-1`)).text();
|
|
5838
|
+
(0, vitest.expect)(r2).toBe(`XYZ`);
|
|
5839
|
+
});
|
|
5840
|
+
});
|
|
5841
|
+
(0, vitest.describe)(`Fork - Live Modes`, () => {
|
|
5842
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
5843
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5844
|
+
(0, vitest.test)(`should return inherited data immediately on long-poll`, async () => {
|
|
5845
|
+
const id = uniqueId();
|
|
5846
|
+
const sourcePath = `/v1/stream/fork-live-inherited-src-${id}`;
|
|
5847
|
+
const forkPath = `/v1/stream/fork-live-inherited-fork-${id}`;
|
|
5848
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5849
|
+
method: `PUT`,
|
|
5850
|
+
headers: { "Content-Type": `text/plain` },
|
|
5851
|
+
body: `inherited data`
|
|
5852
|
+
});
|
|
5853
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5854
|
+
method: `PUT`,
|
|
5855
|
+
headers: {
|
|
5856
|
+
"Content-Type": `text/plain`,
|
|
5857
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5858
|
+
}
|
|
5859
|
+
});
|
|
5860
|
+
const controller = new AbortController();
|
|
5861
|
+
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
5862
|
+
try {
|
|
5863
|
+
const response = await fetch(`${getBaseUrl()}${forkPath}?offset=-1&live=long-poll`, {
|
|
5864
|
+
method: `GET`,
|
|
5865
|
+
signal: controller.signal
|
|
5866
|
+
});
|
|
5867
|
+
clearTimeout(timeoutId);
|
|
5868
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
5869
|
+
const body = await response.text();
|
|
5870
|
+
(0, vitest.expect)(body).toBe(`inherited data`);
|
|
5871
|
+
(0, vitest.expect)(response.headers.get(__durable_streams_client.STREAM_UP_TO_DATE_HEADER)).toBe(`true`);
|
|
5872
|
+
} catch (e) {
|
|
5873
|
+
clearTimeout(timeoutId);
|
|
5874
|
+
if (!(e instanceof Error && e.name === `AbortError`)) throw e;
|
|
5875
|
+
(0, vitest.expect)(true).toBe(false);
|
|
5876
|
+
}
|
|
5877
|
+
});
|
|
5878
|
+
(0, vitest.test)(`should wait for fork appends, not source appends, on long-poll at tail`, async () => {
|
|
5879
|
+
const id = uniqueId();
|
|
5880
|
+
const sourcePath = `/v1/stream/fork-live-tail-src-${id}`;
|
|
5881
|
+
const forkPath = `/v1/stream/fork-live-tail-fork-${id}`;
|
|
5882
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5883
|
+
method: `PUT`,
|
|
5884
|
+
headers: { "Content-Type": `text/plain` },
|
|
5885
|
+
body: `source`
|
|
5886
|
+
});
|
|
5887
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5888
|
+
method: `PUT`,
|
|
5889
|
+
headers: {
|
|
5890
|
+
"Content-Type": `text/plain`,
|
|
5891
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5892
|
+
}
|
|
5893
|
+
});
|
|
5894
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `GET` });
|
|
5895
|
+
await forkHead.text();
|
|
5896
|
+
const forkOffset = forkHead.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
5897
|
+
const longPollPromise = fetch(`${getBaseUrl()}${forkPath}?offset=${forkOffset}&live=long-poll`, { method: `GET` });
|
|
5898
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
5899
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5900
|
+
method: `POST`,
|
|
5901
|
+
headers: { "Content-Type": `text/plain` },
|
|
5902
|
+
body: ` source extra`
|
|
5903
|
+
});
|
|
5904
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5905
|
+
method: `POST`,
|
|
5906
|
+
headers: { "Content-Type": `text/plain` },
|
|
5907
|
+
body: ` fork new`
|
|
5908
|
+
});
|
|
5909
|
+
const response = await longPollPromise;
|
|
5910
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
5911
|
+
const body = await response.text();
|
|
5912
|
+
(0, vitest.expect)(body).toBe(` fork new`);
|
|
5913
|
+
}, getLongPollTestTimeoutMs());
|
|
5914
|
+
(0, vitest.test)(`should stream fork data via SSE`, async () => {
|
|
5915
|
+
const id = uniqueId();
|
|
5916
|
+
const sourcePath = `/v1/stream/fork-live-sse-src-${id}`;
|
|
5917
|
+
const forkPath = `/v1/stream/fork-live-sse-fork-${id}`;
|
|
5918
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5919
|
+
method: `PUT`,
|
|
5920
|
+
headers: { "Content-Type": `text/plain` },
|
|
5921
|
+
body: `inherited`
|
|
5922
|
+
});
|
|
5923
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5924
|
+
method: `PUT`,
|
|
5925
|
+
headers: {
|
|
5926
|
+
"Content-Type": `text/plain`,
|
|
5927
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5928
|
+
}
|
|
5929
|
+
});
|
|
5930
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5931
|
+
method: `POST`,
|
|
5932
|
+
headers: { "Content-Type": `text/plain` },
|
|
5933
|
+
body: ` forked`
|
|
5934
|
+
});
|
|
5935
|
+
const { response, received } = await fetchSSE(`${getBaseUrl()}${forkPath}?offset=-1&live=sse`, {
|
|
5936
|
+
untilContent: `forked`,
|
|
5937
|
+
timeoutMs: 5e3,
|
|
5938
|
+
maxChunks: 20
|
|
5939
|
+
});
|
|
5940
|
+
(0, vitest.expect)(response.status).toBe(200);
|
|
5941
|
+
(0, vitest.expect)(received).toContain(`inherited`);
|
|
5942
|
+
(0, vitest.expect)(received).toContain(`forked`);
|
|
5943
|
+
});
|
|
5944
|
+
(0, vitest.test)(`should handle long-poll handover at fork offset`, async () => {
|
|
5945
|
+
const id = uniqueId();
|
|
5946
|
+
const sourcePath = `/v1/stream/fork-live-handover-src-${id}`;
|
|
5947
|
+
const forkPath = `/v1/stream/fork-live-handover-fork-${id}`;
|
|
5948
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5949
|
+
method: `PUT`,
|
|
5950
|
+
headers: { "Content-Type": `text/plain` },
|
|
5951
|
+
body: `source data`
|
|
5952
|
+
});
|
|
5953
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5954
|
+
method: `PUT`,
|
|
5955
|
+
headers: {
|
|
5956
|
+
"Content-Type": `text/plain`,
|
|
5957
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
5958
|
+
}
|
|
5959
|
+
});
|
|
5960
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1&live=long-poll`);
|
|
5961
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
5962
|
+
const firstBody = await readRes.text();
|
|
5963
|
+
(0, vitest.expect)(firstBody).toBe(`source data`);
|
|
5964
|
+
const nextOffset = readRes.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
5965
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5966
|
+
method: `POST`,
|
|
5967
|
+
headers: { "Content-Type": `text/plain` },
|
|
5968
|
+
body: ` fork append`
|
|
5969
|
+
});
|
|
5970
|
+
const readRes2 = await fetch(`${getBaseUrl()}${forkPath}?offset=${nextOffset}`);
|
|
5971
|
+
(0, vitest.expect)(readRes2.status).toBe(200);
|
|
5972
|
+
const secondBody = await readRes2.text();
|
|
5973
|
+
(0, vitest.expect)(secondBody).toBe(` fork append`);
|
|
5974
|
+
});
|
|
5975
|
+
});
|
|
5976
|
+
(0, vitest.describe)(`Fork - Deletion and Lifecycle`, () => {
|
|
5977
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
5978
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5979
|
+
const waitForStatus = async (url, expectedStatus, timeoutMs = 5e3) => {
|
|
5980
|
+
await vitest.vi.waitFor(async () => {
|
|
5981
|
+
const res = await fetch(url, { method: `HEAD` });
|
|
5982
|
+
(0, vitest.expect)(res.status).toBe(expectedStatus);
|
|
5983
|
+
}, {
|
|
5984
|
+
timeout: timeoutMs,
|
|
5985
|
+
interval: 200
|
|
5986
|
+
});
|
|
5987
|
+
};
|
|
5988
|
+
(0, vitest.test)(`should delete fork without affecting source`, async () => {
|
|
5989
|
+
const id = uniqueId();
|
|
5990
|
+
const sourcePath = `/v1/stream/fork-del-src-unaffected-src-${id}`;
|
|
5991
|
+
const forkPath = `/v1/stream/fork-del-src-unaffected-fork-${id}`;
|
|
5992
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
5993
|
+
method: `PUT`,
|
|
5994
|
+
headers: { "Content-Type": `text/plain` },
|
|
5995
|
+
body: `source data`
|
|
5996
|
+
});
|
|
5997
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
5998
|
+
method: `PUT`,
|
|
5999
|
+
headers: {
|
|
6000
|
+
"Content-Type": `text/plain`,
|
|
6001
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6002
|
+
}
|
|
6003
|
+
});
|
|
6004
|
+
const deleteRes = await fetch(`${getBaseUrl()}${forkPath}`, { method: `DELETE` });
|
|
6005
|
+
(0, vitest.expect)(deleteRes.status).toBe(204);
|
|
6006
|
+
const forkRead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `GET` });
|
|
6007
|
+
(0, vitest.expect)(forkRead.status).toBe(404);
|
|
6008
|
+
const sourceRead = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `GET` });
|
|
6009
|
+
(0, vitest.expect)(sourceRead.status).toBe(200);
|
|
6010
|
+
const body = await sourceRead.text();
|
|
6011
|
+
(0, vitest.expect)(body).toBe(`source data`);
|
|
6012
|
+
});
|
|
6013
|
+
(0, vitest.test)(`should soft-delete source while fork exists — fork still reads`, async () => {
|
|
6014
|
+
const id = uniqueId();
|
|
6015
|
+
const sourcePath = `/v1/stream/fork-del-soft-src-${id}`;
|
|
6016
|
+
const forkPath = `/v1/stream/fork-del-soft-fork-${id}`;
|
|
6017
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6018
|
+
method: `PUT`,
|
|
6019
|
+
headers: { "Content-Type": `text/plain` },
|
|
6020
|
+
body: `preserved data`
|
|
6021
|
+
});
|
|
6022
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6023
|
+
method: `PUT`,
|
|
6024
|
+
headers: {
|
|
6025
|
+
"Content-Type": `text/plain`,
|
|
6026
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6027
|
+
}
|
|
6028
|
+
});
|
|
6029
|
+
const deleteRes = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6030
|
+
(0, vitest.expect)(deleteRes.status).toBe(204);
|
|
6031
|
+
const sourceHead = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `HEAD` });
|
|
6032
|
+
(0, vitest.expect)(sourceHead.status).toBe(410);
|
|
6033
|
+
const forkRead = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
6034
|
+
(0, vitest.expect)(forkRead.status).toBe(200);
|
|
6035
|
+
const body = await forkRead.text();
|
|
6036
|
+
(0, vitest.expect)(body).toBe(`preserved data`);
|
|
6037
|
+
});
|
|
6038
|
+
(0, vitest.test)(`should block re-creation of soft-deleted source (PUT returns 409)`, async () => {
|
|
6039
|
+
const id = uniqueId();
|
|
6040
|
+
const sourcePath = `/v1/stream/fork-del-block-recreate-src-${id}`;
|
|
6041
|
+
const forkPath = `/v1/stream/fork-del-block-recreate-fork-${id}`;
|
|
6042
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6043
|
+
method: `PUT`,
|
|
6044
|
+
headers: { "Content-Type": `text/plain` },
|
|
6045
|
+
body: `original`
|
|
6046
|
+
});
|
|
6047
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6048
|
+
method: `PUT`,
|
|
6049
|
+
headers: {
|
|
6050
|
+
"Content-Type": `text/plain`,
|
|
6051
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6052
|
+
}
|
|
6053
|
+
});
|
|
6054
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6055
|
+
const recreateRes = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6056
|
+
method: `PUT`,
|
|
6057
|
+
headers: { "Content-Type": `text/plain` }
|
|
6058
|
+
});
|
|
6059
|
+
(0, vitest.expect)(recreateRes.status).toBe(409);
|
|
6060
|
+
});
|
|
6061
|
+
(0, vitest.test)(`should return 410 for GET on soft-deleted source`, async () => {
|
|
6062
|
+
const id = uniqueId();
|
|
6063
|
+
const sourcePath = `/v1/stream/fork-del-soft-get-${id}`;
|
|
6064
|
+
const forkPath = `/v1/stream/fork-del-soft-get-fork-${id}`;
|
|
6065
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6066
|
+
method: `PUT`,
|
|
6067
|
+
headers: { "Content-Type": `text/plain` },
|
|
6068
|
+
body: `data`
|
|
6069
|
+
});
|
|
6070
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6071
|
+
method: `PUT`,
|
|
6072
|
+
headers: {
|
|
6073
|
+
"Content-Type": `text/plain`,
|
|
6074
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6075
|
+
}
|
|
6076
|
+
});
|
|
6077
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6078
|
+
const getRes = await fetch(`${getBaseUrl()}${sourcePath}?offset=-1`);
|
|
6079
|
+
(0, vitest.expect)(getRes.status).toBe(410);
|
|
6080
|
+
});
|
|
6081
|
+
(0, vitest.test)(`should return 410 for POST on soft-deleted source`, async () => {
|
|
6082
|
+
const id = uniqueId();
|
|
6083
|
+
const sourcePath = `/v1/stream/fork-del-soft-post-${id}`;
|
|
6084
|
+
const forkPath = `/v1/stream/fork-del-soft-post-fork-${id}`;
|
|
6085
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6086
|
+
method: `PUT`,
|
|
6087
|
+
headers: { "Content-Type": `text/plain` },
|
|
6088
|
+
body: `data`
|
|
6089
|
+
});
|
|
6090
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6091
|
+
method: `PUT`,
|
|
6092
|
+
headers: {
|
|
6093
|
+
"Content-Type": `text/plain`,
|
|
6094
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6095
|
+
}
|
|
6096
|
+
});
|
|
6097
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6098
|
+
const postRes = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6099
|
+
method: `POST`,
|
|
6100
|
+
headers: { "Content-Type": `text/plain` },
|
|
6101
|
+
body: `more data`
|
|
6102
|
+
});
|
|
6103
|
+
(0, vitest.expect)(postRes.status).toBe(410);
|
|
6104
|
+
});
|
|
6105
|
+
(0, vitest.test)(`should return 410 for DELETE on soft-deleted source`, async () => {
|
|
6106
|
+
const id = uniqueId();
|
|
6107
|
+
const sourcePath = `/v1/stream/fork-del-soft-del-${id}`;
|
|
6108
|
+
const forkPath = `/v1/stream/fork-del-soft-del-fork-${id}`;
|
|
6109
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6110
|
+
method: `PUT`,
|
|
6111
|
+
headers: { "Content-Type": `text/plain` },
|
|
6112
|
+
body: `data`
|
|
6113
|
+
});
|
|
6114
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6115
|
+
method: `PUT`,
|
|
6116
|
+
headers: {
|
|
6117
|
+
"Content-Type": `text/plain`,
|
|
6118
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6119
|
+
}
|
|
6120
|
+
});
|
|
6121
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6122
|
+
const deleteRes = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6123
|
+
(0, vitest.expect)(deleteRes.status).toBe(410);
|
|
6124
|
+
});
|
|
6125
|
+
(0, vitest.test)(`should return 409 for fork from soft-deleted source`, async () => {
|
|
6126
|
+
const id = uniqueId();
|
|
6127
|
+
const sourcePath = `/v1/stream/fork-del-soft-refork-${id}`;
|
|
6128
|
+
const forkPath = `/v1/stream/fork-del-soft-refork-fork1-${id}`;
|
|
6129
|
+
const fork2Path = `/v1/stream/fork-del-soft-refork-fork2-${id}`;
|
|
6130
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6131
|
+
method: `PUT`,
|
|
6132
|
+
headers: { "Content-Type": `text/plain` },
|
|
6133
|
+
body: `data`
|
|
6134
|
+
});
|
|
6135
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6136
|
+
method: `PUT`,
|
|
6137
|
+
headers: {
|
|
6138
|
+
"Content-Type": `text/plain`,
|
|
6139
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6140
|
+
}
|
|
6141
|
+
});
|
|
6142
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6143
|
+
const fork2Res = await fetch(`${getBaseUrl()}${fork2Path}`, {
|
|
6144
|
+
method: `PUT`,
|
|
6145
|
+
headers: {
|
|
6146
|
+
"Content-Type": `text/plain`,
|
|
6147
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6148
|
+
}
|
|
6149
|
+
});
|
|
6150
|
+
(0, vitest.expect)(fork2Res.status).toBe(409);
|
|
6151
|
+
});
|
|
6152
|
+
(0, vitest.test)(`should return 409 for fork with content-type mismatch`, async () => {
|
|
6153
|
+
const id = uniqueId();
|
|
6154
|
+
const sourcePath = `/v1/stream/fork-ct-mismatch-src-${id}`;
|
|
6155
|
+
const forkPath = `/v1/stream/fork-ct-mismatch-fork-${id}`;
|
|
6156
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6157
|
+
method: `PUT`,
|
|
6158
|
+
headers: { "Content-Type": `text/plain` },
|
|
6159
|
+
body: `data`
|
|
6160
|
+
});
|
|
6161
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6162
|
+
method: `PUT`,
|
|
6163
|
+
headers: {
|
|
6164
|
+
"Content-Type": `application/json`,
|
|
6165
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6166
|
+
}
|
|
6167
|
+
});
|
|
6168
|
+
(0, vitest.expect)(forkRes.status).toBe(409);
|
|
6169
|
+
});
|
|
6170
|
+
(0, vitest.test)(`should cascade GC when last fork is deleted`, async () => {
|
|
6171
|
+
const id = uniqueId();
|
|
6172
|
+
const sourcePath = `/v1/stream/fork-del-cascade-src-${id}`;
|
|
6173
|
+
const forkPath = `/v1/stream/fork-del-cascade-fork-${id}`;
|
|
6174
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6175
|
+
method: `PUT`,
|
|
6176
|
+
headers: { "Content-Type": `text/plain` },
|
|
6177
|
+
body: `cascade data`
|
|
6178
|
+
});
|
|
6179
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6180
|
+
method: `PUT`,
|
|
6181
|
+
headers: {
|
|
6182
|
+
"Content-Type": `text/plain`,
|
|
6183
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6184
|
+
}
|
|
6185
|
+
});
|
|
6186
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6187
|
+
const sourceHead1 = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `HEAD` });
|
|
6188
|
+
(0, vitest.expect)(sourceHead1.status).toBe(410);
|
|
6189
|
+
const deleteFork = await fetch(`${getBaseUrl()}${forkPath}`, { method: `DELETE` });
|
|
6190
|
+
(0, vitest.expect)(deleteFork.status).toBe(204);
|
|
6191
|
+
await waitForStatus(`${getBaseUrl()}${sourcePath}`, 404);
|
|
6192
|
+
});
|
|
6193
|
+
(0, vitest.test)(`should cascade GC through three levels`, async () => {
|
|
6194
|
+
const id = uniqueId();
|
|
6195
|
+
const level0 = `/v1/stream/fork-del-cascade3-l0-${id}`;
|
|
6196
|
+
const level1 = `/v1/stream/fork-del-cascade3-l1-${id}`;
|
|
6197
|
+
const level2 = `/v1/stream/fork-del-cascade3-l2-${id}`;
|
|
6198
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
6199
|
+
method: `PUT`,
|
|
6200
|
+
headers: { "Content-Type": `text/plain` },
|
|
6201
|
+
body: `root`
|
|
6202
|
+
});
|
|
6203
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
6204
|
+
method: `PUT`,
|
|
6205
|
+
headers: {
|
|
6206
|
+
"Content-Type": `text/plain`,
|
|
6207
|
+
[STREAM_FORKED_FROM_HEADER]: level0
|
|
6208
|
+
}
|
|
6209
|
+
});
|
|
6210
|
+
await fetch(`${getBaseUrl()}${level2}`, {
|
|
6211
|
+
method: `PUT`,
|
|
6212
|
+
headers: {
|
|
6213
|
+
"Content-Type": `text/plain`,
|
|
6214
|
+
[STREAM_FORKED_FROM_HEADER]: level1
|
|
6215
|
+
}
|
|
6216
|
+
});
|
|
6217
|
+
await fetch(`${getBaseUrl()}${level0}`, { method: `DELETE` });
|
|
6218
|
+
await fetch(`${getBaseUrl()}${level1}`, { method: `DELETE` });
|
|
6219
|
+
(0, vitest.expect)((await fetch(`${getBaseUrl()}${level0}`, { method: `HEAD` })).status).toBe(410);
|
|
6220
|
+
(0, vitest.expect)((await fetch(`${getBaseUrl()}${level1}`, { method: `HEAD` })).status).toBe(410);
|
|
6221
|
+
const deleteLevel2 = await fetch(`${getBaseUrl()}${level2}`, { method: `DELETE` });
|
|
6222
|
+
(0, vitest.expect)(deleteLevel2.status).toBe(204);
|
|
6223
|
+
(0, vitest.expect)((await fetch(`${getBaseUrl()}${level2}`, { method: `HEAD` })).status).toBe(404);
|
|
6224
|
+
await waitForStatus(`${getBaseUrl()}${level1}`, 404);
|
|
6225
|
+
await waitForStatus(`${getBaseUrl()}${level0}`, 404);
|
|
6226
|
+
});
|
|
6227
|
+
(0, vitest.test)(`should preserve data when deleting middle of chain`, async () => {
|
|
6228
|
+
const id = uniqueId();
|
|
6229
|
+
const level0 = `/v1/stream/fork-del-middle-l0-${id}`;
|
|
6230
|
+
const level1 = `/v1/stream/fork-del-middle-l1-${id}`;
|
|
6231
|
+
const level2 = `/v1/stream/fork-del-middle-l2-${id}`;
|
|
6232
|
+
await fetch(`${getBaseUrl()}${level0}`, {
|
|
6233
|
+
method: `PUT`,
|
|
6234
|
+
headers: { "Content-Type": `text/plain` },
|
|
6235
|
+
body: `A`
|
|
6236
|
+
});
|
|
6237
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
6238
|
+
method: `PUT`,
|
|
6239
|
+
headers: {
|
|
6240
|
+
"Content-Type": `text/plain`,
|
|
6241
|
+
[STREAM_FORKED_FROM_HEADER]: level0
|
|
6242
|
+
}
|
|
6243
|
+
});
|
|
6244
|
+
await fetch(`${getBaseUrl()}${level1}`, {
|
|
6245
|
+
method: `POST`,
|
|
6246
|
+
headers: { "Content-Type": `text/plain` },
|
|
6247
|
+
body: `B`
|
|
6248
|
+
});
|
|
6249
|
+
await fetch(`${getBaseUrl()}${level2}`, {
|
|
6250
|
+
method: `PUT`,
|
|
6251
|
+
headers: {
|
|
6252
|
+
"Content-Type": `text/plain`,
|
|
6253
|
+
[STREAM_FORKED_FROM_HEADER]: level1
|
|
6254
|
+
}
|
|
6255
|
+
});
|
|
6256
|
+
await fetch(`${getBaseUrl()}${level2}`, {
|
|
6257
|
+
method: `POST`,
|
|
6258
|
+
headers: { "Content-Type": `text/plain` },
|
|
6259
|
+
body: `C`
|
|
6260
|
+
});
|
|
6261
|
+
await fetch(`${getBaseUrl()}${level1}`, { method: `DELETE` });
|
|
6262
|
+
(0, vitest.expect)((await fetch(`${getBaseUrl()}${level1}`, { method: `HEAD` })).status).toBe(410);
|
|
6263
|
+
const readRes = await fetch(`${getBaseUrl()}${level2}?offset=-1`);
|
|
6264
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
6265
|
+
const body = await readRes.text();
|
|
6266
|
+
(0, vitest.expect)(body).toBe(`ABC`);
|
|
6267
|
+
const l0Read = await fetch(`${getBaseUrl()}${level0}?offset=-1`);
|
|
6268
|
+
(0, vitest.expect)(l0Read.status).toBe(200);
|
|
6269
|
+
(0, vitest.expect)(await l0Read.text()).toBe(`A`);
|
|
6270
|
+
});
|
|
6271
|
+
(0, vitest.test)(`should keep source alive when all forks are deleted`, async () => {
|
|
6272
|
+
const id = uniqueId();
|
|
6273
|
+
const sourcePath = `/v1/stream/fork-del-allgone-src-${id}`;
|
|
6274
|
+
const fork1Path = `/v1/stream/fork-del-allgone-f1-${id}`;
|
|
6275
|
+
const fork2Path = `/v1/stream/fork-del-allgone-f2-${id}`;
|
|
6276
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6277
|
+
method: `PUT`,
|
|
6278
|
+
headers: { "Content-Type": `text/plain` },
|
|
6279
|
+
body: `alive`
|
|
6280
|
+
});
|
|
6281
|
+
await fetch(`${getBaseUrl()}${fork1Path}`, {
|
|
6282
|
+
method: `PUT`,
|
|
6283
|
+
headers: {
|
|
6284
|
+
"Content-Type": `text/plain`,
|
|
6285
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6286
|
+
}
|
|
6287
|
+
});
|
|
6288
|
+
await fetch(`${getBaseUrl()}${fork2Path}`, {
|
|
6289
|
+
method: `PUT`,
|
|
6290
|
+
headers: {
|
|
6291
|
+
"Content-Type": `text/plain`,
|
|
6292
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6293
|
+
}
|
|
6294
|
+
});
|
|
6295
|
+
await fetch(`${getBaseUrl()}${fork1Path}`, { method: `DELETE` });
|
|
6296
|
+
await fetch(`${getBaseUrl()}${fork2Path}`, { method: `DELETE` });
|
|
6297
|
+
const sourceRead = await fetch(`${getBaseUrl()}${sourcePath}?offset=-1`);
|
|
6298
|
+
(0, vitest.expect)(sourceRead.status).toBe(200);
|
|
6299
|
+
const body = await sourceRead.text();
|
|
6300
|
+
(0, vitest.expect)(body).toBe(`alive`);
|
|
6301
|
+
});
|
|
6302
|
+
});
|
|
6303
|
+
(0, vitest.describe)(`Fork - TTL and Expiry`, () => {
|
|
6304
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
6305
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6306
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6307
|
+
const waitForDeletion = async (url, initialSleepMs, expectedStatuses = [404], timeoutMs = 5e3) => {
|
|
6308
|
+
await sleep(initialSleepMs);
|
|
6309
|
+
await vitest.vi.waitFor(async () => {
|
|
6310
|
+
const head = await fetch(url, { method: `HEAD` });
|
|
6311
|
+
(0, vitest.expect)(expectedStatuses).toContain(head.status);
|
|
6312
|
+
}, {
|
|
6313
|
+
timeout: timeoutMs,
|
|
6314
|
+
interval: 200
|
|
6315
|
+
});
|
|
6316
|
+
};
|
|
6317
|
+
(0, vitest.test)(`should inherit source expiry when none specified`, async () => {
|
|
6318
|
+
const id = uniqueId();
|
|
6319
|
+
const sourcePath = `/v1/stream/fork-ttl-inherit-src-${id}`;
|
|
6320
|
+
const forkPath = `/v1/stream/fork-ttl-inherit-fork-${id}`;
|
|
6321
|
+
const expiresAt = new Date(Date.now() + 36e5).toISOString();
|
|
6322
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6323
|
+
method: `PUT`,
|
|
6324
|
+
headers: {
|
|
6325
|
+
"Content-Type": `text/plain`,
|
|
6326
|
+
"Stream-Expires-At": expiresAt
|
|
6327
|
+
},
|
|
6328
|
+
body: `data`
|
|
6329
|
+
});
|
|
6330
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6331
|
+
method: `PUT`,
|
|
6332
|
+
headers: {
|
|
6333
|
+
"Content-Type": `text/plain`,
|
|
6334
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6335
|
+
}
|
|
6336
|
+
});
|
|
6337
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
6338
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
6339
|
+
(0, vitest.expect)(forkHead.status).toBe(200);
|
|
6340
|
+
const forkExpires = forkHead.headers.get(`Stream-Expires-At`);
|
|
6341
|
+
if (forkExpires) (0, vitest.expect)(new Date(forkExpires).getTime()).toBeLessThanOrEqual(new Date(expiresAt).getTime());
|
|
6342
|
+
});
|
|
6343
|
+
(0, vitest.test)(`should allow fork with shorter TTL`, async () => {
|
|
6344
|
+
const id = uniqueId();
|
|
6345
|
+
const sourcePath = `/v1/stream/fork-ttl-shorter-src-${id}`;
|
|
6346
|
+
const forkPath = `/v1/stream/fork-ttl-shorter-fork-${id}`;
|
|
6347
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6348
|
+
method: `PUT`,
|
|
6349
|
+
headers: {
|
|
6350
|
+
"Content-Type": `text/plain`,
|
|
6351
|
+
"Stream-TTL": `3600`
|
|
6352
|
+
},
|
|
6353
|
+
body: `data`
|
|
6354
|
+
});
|
|
6355
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6356
|
+
method: `PUT`,
|
|
6357
|
+
headers: {
|
|
6358
|
+
"Content-Type": `text/plain`,
|
|
6359
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6360
|
+
"Stream-TTL": `1800`
|
|
6361
|
+
}
|
|
6362
|
+
});
|
|
6363
|
+
(0, vitest.expect)([200, 201]).toContain(forkRes.status);
|
|
6364
|
+
});
|
|
6365
|
+
vitest.test.concurrent(`should expire fork based on TTL (releases refcount)`, async () => {
|
|
6366
|
+
const id = uniqueId();
|
|
6367
|
+
const sourcePath = `/v1/stream/fork-ttl-expire-src-${id}`;
|
|
6368
|
+
const forkPath = `/v1/stream/fork-ttl-expire-fork-${id}`;
|
|
6369
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6370
|
+
method: `PUT`,
|
|
6371
|
+
headers: {
|
|
6372
|
+
"Content-Type": `text/plain`,
|
|
6373
|
+
"Stream-TTL": `60`
|
|
6374
|
+
},
|
|
6375
|
+
body: `data`
|
|
6376
|
+
});
|
|
6377
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6378
|
+
method: `PUT`,
|
|
6379
|
+
headers: {
|
|
6380
|
+
"Content-Type": `text/plain`,
|
|
6381
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6382
|
+
"Stream-TTL": `1`
|
|
6383
|
+
}
|
|
6384
|
+
});
|
|
6385
|
+
const forkHeadBefore = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
6386
|
+
(0, vitest.expect)(forkHeadBefore.status).toBe(200);
|
|
6387
|
+
await waitForDeletion(`${getBaseUrl()}${forkPath}`, 1e3);
|
|
6388
|
+
const forkGetAfter = await fetch(`${getBaseUrl()}${forkPath}`, { method: `GET` });
|
|
6389
|
+
(0, vitest.expect)(forkGetAfter.status).toBe(404);
|
|
6390
|
+
});
|
|
6391
|
+
vitest.test.concurrent(`should expire source with living forks (source goes 410)`, async () => {
|
|
6392
|
+
const id = uniqueId();
|
|
6393
|
+
const sourcePath = `/v1/stream/fork-ttl-src-expire-src-${id}`;
|
|
6394
|
+
const forkPath = `/v1/stream/fork-ttl-src-expire-fork-${id}`;
|
|
6395
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6396
|
+
method: `PUT`,
|
|
6397
|
+
headers: {
|
|
6398
|
+
"Content-Type": `text/plain`,
|
|
6399
|
+
"Stream-TTL": `1`
|
|
6400
|
+
},
|
|
6401
|
+
body: `data`
|
|
6402
|
+
});
|
|
6403
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6404
|
+
method: `PUT`,
|
|
6405
|
+
headers: {
|
|
6406
|
+
"Content-Type": `text/plain`,
|
|
6407
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6408
|
+
}
|
|
6409
|
+
});
|
|
6410
|
+
await waitForDeletion(`${getBaseUrl()}${sourcePath}`, 1e3, [404, 410]);
|
|
6411
|
+
const sourceGet = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `GET` });
|
|
6412
|
+
(0, vitest.expect)([404, 410]).toContain(sourceGet.status);
|
|
6413
|
+
await waitForDeletion(`${getBaseUrl()}${forkPath}`, 0, [404, 410]);
|
|
6414
|
+
const forkGet = await fetch(`${getBaseUrl()}${forkPath}`, { method: `GET` });
|
|
6415
|
+
(0, vitest.expect)([404, 410]).toContain(forkGet.status);
|
|
6416
|
+
});
|
|
6417
|
+
(0, vitest.test)(`should inherit source TTL value when none specified`, async () => {
|
|
6418
|
+
const id = uniqueId();
|
|
6419
|
+
const sourcePath = `/v1/stream/fork-ttl-inherit-ttl-src-${id}`;
|
|
6420
|
+
const forkPath = `/v1/stream/fork-ttl-inherit-ttl-fork-${id}`;
|
|
6421
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6422
|
+
method: `PUT`,
|
|
6423
|
+
headers: {
|
|
6424
|
+
"Content-Type": `text/plain`,
|
|
6425
|
+
"Stream-TTL": `3600`
|
|
6426
|
+
},
|
|
6427
|
+
body: `data`
|
|
6428
|
+
});
|
|
6429
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6430
|
+
method: `PUT`,
|
|
6431
|
+
headers: {
|
|
6432
|
+
"Content-Type": `text/plain`,
|
|
6433
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6434
|
+
}
|
|
6435
|
+
});
|
|
6436
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
6437
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
6438
|
+
(0, vitest.expect)(forkHead.status).toBe(200);
|
|
6439
|
+
const forkTTL = forkHead.headers.get(`Stream-TTL`);
|
|
6440
|
+
(0, vitest.expect)(forkTTL).toBe(`3600`);
|
|
6441
|
+
});
|
|
6442
|
+
(0, vitest.test)(`should use fork's own TTL when specified`, async () => {
|
|
6443
|
+
const id = uniqueId();
|
|
6444
|
+
const sourcePath = `/v1/stream/fork-own-ttl-src-${id}`;
|
|
6445
|
+
const forkPath = `/v1/stream/fork-own-ttl-fork-${id}`;
|
|
6446
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6447
|
+
method: `PUT`,
|
|
6448
|
+
headers: {
|
|
6449
|
+
"Content-Type": `text/plain`,
|
|
6450
|
+
"Stream-TTL": `3600`
|
|
6451
|
+
},
|
|
6452
|
+
body: `data`
|
|
6453
|
+
});
|
|
6454
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6455
|
+
method: `PUT`,
|
|
6456
|
+
headers: {
|
|
6457
|
+
"Content-Type": `text/plain`,
|
|
6458
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6459
|
+
"Stream-TTL": `7200`
|
|
6460
|
+
}
|
|
6461
|
+
});
|
|
6462
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
6463
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
6464
|
+
(0, vitest.expect)(forkHead.status).toBe(200);
|
|
6465
|
+
const forkTTL = forkHead.headers.get(`Stream-TTL`);
|
|
6466
|
+
(0, vitest.expect)(forkTTL).toBe(`7200`);
|
|
6467
|
+
});
|
|
6468
|
+
vitest.test.concurrent(`should allow fork to outlive source via TTL renewal`, async () => {
|
|
6469
|
+
const id = uniqueId();
|
|
6470
|
+
const sourcePath = `/v1/stream/fork-outlive-src-${id}`;
|
|
6471
|
+
const forkPath = `/v1/stream/fork-outlive-fork-${id}`;
|
|
6472
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6473
|
+
method: `PUT`,
|
|
6474
|
+
headers: {
|
|
6475
|
+
"Content-Type": `text/plain`,
|
|
6476
|
+
"Stream-TTL": `2`
|
|
6477
|
+
},
|
|
6478
|
+
body: `source data`
|
|
6479
|
+
});
|
|
6480
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6481
|
+
method: `PUT`,
|
|
6482
|
+
headers: {
|
|
6483
|
+
"Content-Type": `text/plain`,
|
|
6484
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6485
|
+
"Stream-TTL": `2`
|
|
6486
|
+
}
|
|
6487
|
+
});
|
|
6488
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
6489
|
+
await sleep(1500);
|
|
6490
|
+
const forkRead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `GET` });
|
|
6491
|
+
(0, vitest.expect)(forkRead.status).toBe(200);
|
|
6492
|
+
await waitForDeletion(`${getBaseUrl()}${sourcePath}`, 500, [404, 410]);
|
|
6493
|
+
const sourceGet = await fetch(`${getBaseUrl()}${sourcePath}`, { method: `GET` });
|
|
6494
|
+
(0, vitest.expect)([404, 410]).toContain(sourceGet.status);
|
|
6495
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
6496
|
+
(0, vitest.expect)(forkHead.status).toBe(200);
|
|
6497
|
+
});
|
|
6498
|
+
(0, vitest.test)(`should allow fork Expires-At beyond source TTL expiry`, async () => {
|
|
6499
|
+
const id = uniqueId();
|
|
6500
|
+
const sourcePath = `/v1/stream/fork-expires-beyond-src-${id}`;
|
|
6501
|
+
const forkPath = `/v1/stream/fork-expires-beyond-fork-${id}`;
|
|
6502
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6503
|
+
method: `PUT`,
|
|
6504
|
+
headers: {
|
|
6505
|
+
"Content-Type": `text/plain`,
|
|
6506
|
+
"Stream-TTL": `10`
|
|
6507
|
+
},
|
|
6508
|
+
body: `data`
|
|
6509
|
+
});
|
|
6510
|
+
const farFuture = new Date(Date.now() + 36e5).toISOString();
|
|
6511
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6512
|
+
method: `PUT`,
|
|
6513
|
+
headers: {
|
|
6514
|
+
"Content-Type": `text/plain`,
|
|
6515
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6516
|
+
"Stream-Expires-At": farFuture
|
|
6517
|
+
}
|
|
6518
|
+
});
|
|
6519
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
6520
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
6521
|
+
(0, vitest.expect)(forkHead.status).toBe(200);
|
|
6522
|
+
const forkExpiresAt = forkHead.headers.get(`Stream-Expires-At`);
|
|
6523
|
+
if (forkExpiresAt) (0, vitest.expect)(new Date(forkExpiresAt).getTime()).toBeGreaterThan(Date.now() + 35e5);
|
|
6524
|
+
});
|
|
6525
|
+
(0, vitest.test)(`should allow fork TTL longer than source TTL (no capping)`, async () => {
|
|
6526
|
+
const id = uniqueId();
|
|
6527
|
+
const sourcePath = `/v1/stream/fork-ttl-nocap-src-${id}`;
|
|
6528
|
+
const forkPath = `/v1/stream/fork-ttl-nocap-fork-${id}`;
|
|
6529
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6530
|
+
method: `PUT`,
|
|
6531
|
+
headers: {
|
|
6532
|
+
"Content-Type": `text/plain`,
|
|
6533
|
+
"Stream-TTL": `10`
|
|
6534
|
+
},
|
|
6535
|
+
body: `data`
|
|
6536
|
+
});
|
|
6537
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6538
|
+
method: `PUT`,
|
|
6539
|
+
headers: {
|
|
6540
|
+
"Content-Type": `text/plain`,
|
|
6541
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6542
|
+
"Stream-TTL": `99999`
|
|
6543
|
+
}
|
|
6544
|
+
});
|
|
6545
|
+
(0, vitest.expect)([200, 201]).toContain(forkRes.status);
|
|
6546
|
+
const forkHead = await fetch(`${getBaseUrl()}${forkPath}`, { method: `HEAD` });
|
|
6547
|
+
(0, vitest.expect)(forkHead.status).toBe(200);
|
|
6548
|
+
const forkTTL = forkHead.headers.get(`Stream-TTL`);
|
|
6549
|
+
(0, vitest.expect)(forkTTL).toBe(`99999`);
|
|
6550
|
+
});
|
|
6551
|
+
});
|
|
6552
|
+
(0, vitest.describe)(`Fork - JSON Mode`, () => {
|
|
6553
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
6554
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6555
|
+
(0, vitest.test)(`should fork a JSON stream`, async () => {
|
|
6556
|
+
const id = uniqueId();
|
|
6557
|
+
const sourcePath = `/v1/stream/fork-json-src-${id}`;
|
|
6558
|
+
const forkPath = `/v1/stream/fork-json-fork-${id}`;
|
|
6559
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6560
|
+
method: `PUT`,
|
|
6561
|
+
headers: { "Content-Type": `application/json` },
|
|
6562
|
+
body: `[{"event":"one"}]`
|
|
6563
|
+
});
|
|
6564
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6565
|
+
method: `PUT`,
|
|
6566
|
+
headers: {
|
|
6567
|
+
"Content-Type": `application/json`,
|
|
6568
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6569
|
+
}
|
|
6570
|
+
});
|
|
6571
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
6572
|
+
(0, vitest.expect)(forkRes.headers.get(`content-type`)).toBe(`application/json`);
|
|
6573
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
6574
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
6575
|
+
(0, vitest.expect)(readRes.headers.get(`content-type`)).toBe(`application/json`);
|
|
6576
|
+
const body = JSON.parse(await readRes.text());
|
|
6577
|
+
(0, vitest.expect)(Array.isArray(body)).toBe(true);
|
|
6578
|
+
(0, vitest.expect)(body).toEqual([{ event: `one` }]);
|
|
6579
|
+
});
|
|
6580
|
+
(0, vitest.test)(`should read forked JSON across boundary`, async () => {
|
|
6581
|
+
const id = uniqueId();
|
|
6582
|
+
const sourcePath = `/v1/stream/fork-json-boundary-src-${id}`;
|
|
6583
|
+
const forkPath = `/v1/stream/fork-json-boundary-fork-${id}`;
|
|
6584
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6585
|
+
method: `PUT`,
|
|
6586
|
+
headers: { "Content-Type": `application/json` },
|
|
6587
|
+
body: `[{"from":"source"}]`
|
|
6588
|
+
});
|
|
6589
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6590
|
+
method: `PUT`,
|
|
6591
|
+
headers: {
|
|
6592
|
+
"Content-Type": `application/json`,
|
|
6593
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6594
|
+
}
|
|
6595
|
+
});
|
|
6596
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6597
|
+
method: `POST`,
|
|
6598
|
+
headers: { "Content-Type": `application/json` },
|
|
6599
|
+
body: `[{"from":"fork"}]`
|
|
6600
|
+
});
|
|
6601
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
6602
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
6603
|
+
const body = JSON.parse(await readRes.text());
|
|
6604
|
+
(0, vitest.expect)(Array.isArray(body)).toBe(true);
|
|
6605
|
+
(0, vitest.expect)(body).toEqual([{ from: `source` }, { from: `fork` }]);
|
|
6606
|
+
});
|
|
6607
|
+
});
|
|
6608
|
+
(0, vitest.describe)(`Fork - Edge Cases`, () => {
|
|
6609
|
+
const STREAM_FORKED_FROM_HEADER = `Stream-Forked-From`;
|
|
6610
|
+
const STREAM_FORK_OFFSET_HEADER = `Stream-Fork-Offset`;
|
|
6611
|
+
const uniqueId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6612
|
+
(0, vitest.test)(`should handle fork then immediately delete source`, async () => {
|
|
6613
|
+
const id = uniqueId();
|
|
6614
|
+
const sourcePath = `/v1/stream/fork-edge-imm-del-src-${id}`;
|
|
6615
|
+
const forkPath = `/v1/stream/fork-edge-imm-del-fork-${id}`;
|
|
6616
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6617
|
+
method: `PUT`,
|
|
6618
|
+
headers: { "Content-Type": `text/plain` },
|
|
6619
|
+
body: `ephemeral`
|
|
6620
|
+
});
|
|
6621
|
+
await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6622
|
+
method: `PUT`,
|
|
6623
|
+
headers: {
|
|
6624
|
+
"Content-Type": `text/plain`,
|
|
6625
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6626
|
+
}
|
|
6627
|
+
});
|
|
6628
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, { method: `DELETE` });
|
|
6629
|
+
const readRes = await fetch(`${getBaseUrl()}${forkPath}?offset=-1`);
|
|
6630
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
6631
|
+
const body = await readRes.text();
|
|
6632
|
+
(0, vitest.expect)(body).toBe(`ephemeral`);
|
|
6633
|
+
});
|
|
6634
|
+
(0, vitest.test)(`should handle many forks of same stream (10 forks)`, async () => {
|
|
6635
|
+
const id = uniqueId();
|
|
6636
|
+
const sourcePath = `/v1/stream/fork-edge-many-src-${id}`;
|
|
6637
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6638
|
+
method: `PUT`,
|
|
6639
|
+
headers: { "Content-Type": `text/plain` },
|
|
6640
|
+
body: `shared data`
|
|
6641
|
+
});
|
|
6642
|
+
const forkPaths = [];
|
|
6643
|
+
for (let i = 0; i < 10; i++) {
|
|
6644
|
+
const forkPath = `/v1/stream/fork-edge-many-f${i}-${id}`;
|
|
6645
|
+
const forkRes = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6646
|
+
method: `PUT`,
|
|
6647
|
+
headers: {
|
|
6648
|
+
"Content-Type": `text/plain`,
|
|
6649
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6650
|
+
}
|
|
6651
|
+
});
|
|
6652
|
+
(0, vitest.expect)(forkRes.status).toBe(201);
|
|
6653
|
+
forkPaths.push(forkPath);
|
|
6654
|
+
}
|
|
6655
|
+
for (const fp of forkPaths) {
|
|
6656
|
+
const readRes = await fetch(`${getBaseUrl()}${fp}?offset=-1`);
|
|
6657
|
+
(0, vitest.expect)(readRes.status).toBe(200);
|
|
6658
|
+
const body = await readRes.text();
|
|
6659
|
+
(0, vitest.expect)(body).toBe(`shared data`);
|
|
6660
|
+
}
|
|
6661
|
+
for (const fp of forkPaths) await fetch(`${getBaseUrl()}${fp}`, { method: `DELETE` });
|
|
6662
|
+
});
|
|
6663
|
+
(0, vitest.test)(`should fork at every offset position`, async () => {
|
|
6664
|
+
const id = uniqueId();
|
|
6665
|
+
const sourcePath = `/v1/stream/fork-edge-every-offset-src-${id}`;
|
|
6666
|
+
const createRes = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6667
|
+
method: `PUT`,
|
|
6668
|
+
headers: { "Content-Type": `text/plain` },
|
|
6669
|
+
body: `A`
|
|
6670
|
+
});
|
|
6671
|
+
const offset0 = `0000000000000000_0000000000000000`;
|
|
6672
|
+
const offset1 = createRes.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
6673
|
+
const append1 = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6674
|
+
method: `POST`,
|
|
6675
|
+
headers: { "Content-Type": `text/plain` },
|
|
6676
|
+
body: `B`
|
|
6677
|
+
});
|
|
6678
|
+
const offset2 = append1.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
6679
|
+
const append2 = await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6680
|
+
method: `POST`,
|
|
6681
|
+
headers: { "Content-Type": `text/plain` },
|
|
6682
|
+
body: `C`
|
|
6683
|
+
});
|
|
6684
|
+
const offset3 = append2.headers.get(__durable_streams_client.STREAM_OFFSET_HEADER);
|
|
6685
|
+
const f0 = `/v1/stream/fork-edge-every-f0-${id}`;
|
|
6686
|
+
const f0Res = await fetch(`${getBaseUrl()}${f0}`, {
|
|
6687
|
+
method: `PUT`,
|
|
6688
|
+
headers: {
|
|
6689
|
+
"Content-Type": `text/plain`,
|
|
6690
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6691
|
+
[STREAM_FORK_OFFSET_HEADER]: offset0
|
|
6692
|
+
}
|
|
6693
|
+
});
|
|
6694
|
+
(0, vitest.expect)(f0Res.status).toBe(201);
|
|
6695
|
+
const f0Body = await (await fetch(`${getBaseUrl()}${f0}?offset=-1`)).text();
|
|
6696
|
+
(0, vitest.expect)(f0Body).toBe(``);
|
|
6697
|
+
const f1 = `/v1/stream/fork-edge-every-f1-${id}`;
|
|
6698
|
+
await fetch(`${getBaseUrl()}${f1}`, {
|
|
6699
|
+
method: `PUT`,
|
|
6700
|
+
headers: {
|
|
6701
|
+
"Content-Type": `text/plain`,
|
|
6702
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6703
|
+
[STREAM_FORK_OFFSET_HEADER]: offset1
|
|
6704
|
+
}
|
|
6705
|
+
});
|
|
6706
|
+
const f1Body = await (await fetch(`${getBaseUrl()}${f1}?offset=-1`)).text();
|
|
6707
|
+
(0, vitest.expect)(f1Body).toBe(`A`);
|
|
6708
|
+
const f2 = `/v1/stream/fork-edge-every-f2-${id}`;
|
|
6709
|
+
await fetch(`${getBaseUrl()}${f2}`, {
|
|
6710
|
+
method: `PUT`,
|
|
6711
|
+
headers: {
|
|
6712
|
+
"Content-Type": `text/plain`,
|
|
6713
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6714
|
+
[STREAM_FORK_OFFSET_HEADER]: offset2
|
|
6715
|
+
}
|
|
6716
|
+
});
|
|
6717
|
+
const f2Body = await (await fetch(`${getBaseUrl()}${f2}?offset=-1`)).text();
|
|
6718
|
+
(0, vitest.expect)(f2Body).toBe(`AB`);
|
|
6719
|
+
const f3 = `/v1/stream/fork-edge-every-f3-${id}`;
|
|
6720
|
+
await fetch(`${getBaseUrl()}${f3}`, {
|
|
6721
|
+
method: `PUT`,
|
|
6722
|
+
headers: {
|
|
6723
|
+
"Content-Type": `text/plain`,
|
|
6724
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath,
|
|
6725
|
+
[STREAM_FORK_OFFSET_HEADER]: offset3
|
|
6726
|
+
}
|
|
6727
|
+
});
|
|
6728
|
+
const f3Body = await (await fetch(`${getBaseUrl()}${f3}?offset=-1`)).text();
|
|
6729
|
+
(0, vitest.expect)(f3Body).toBe(`ABC`);
|
|
6730
|
+
});
|
|
6731
|
+
(0, vitest.test)(`should handle idempotent fork creation (PUT twice)`, async () => {
|
|
6732
|
+
const id = uniqueId();
|
|
6733
|
+
const sourcePath = `/v1/stream/fork-edge-idempotent-src-${id}`;
|
|
6734
|
+
const forkPath = `/v1/stream/fork-edge-idempotent-fork-${id}`;
|
|
6735
|
+
await fetch(`${getBaseUrl()}${sourcePath}`, {
|
|
6736
|
+
method: `PUT`,
|
|
6737
|
+
headers: { "Content-Type": `text/plain` },
|
|
6738
|
+
body: `data`
|
|
6739
|
+
});
|
|
6740
|
+
const fork1 = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6741
|
+
method: `PUT`,
|
|
6742
|
+
headers: {
|
|
6743
|
+
"Content-Type": `text/plain`,
|
|
6744
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6745
|
+
}
|
|
6746
|
+
});
|
|
6747
|
+
(0, vitest.expect)(fork1.status).toBe(201);
|
|
6748
|
+
const fork2 = await fetch(`${getBaseUrl()}${forkPath}`, {
|
|
6749
|
+
method: `PUT`,
|
|
6750
|
+
headers: {
|
|
6751
|
+
"Content-Type": `text/plain`,
|
|
6752
|
+
[STREAM_FORKED_FROM_HEADER]: sourcePath
|
|
6753
|
+
}
|
|
6754
|
+
});
|
|
6755
|
+
(0, vitest.expect)(fork2.status).toBe(200);
|
|
6756
|
+
});
|
|
6757
|
+
});
|
|
5025
6758
|
}
|
|
5026
6759
|
|
|
5027
6760
|
//#endregion
|