-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathcheckpoint.test.ts
More file actions
102 lines (88 loc) · 3.73 KB
/
Copy pathcheckpoint.test.ts
File metadata and controls
102 lines (88 loc) · 3.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* CheckpointStore tests against a real DO `SqlStorage` (via the FsHarnessDO
* harness, same pattern as the FileSystem contract tests).
*
* The checkpoint is the durability layer for UNCOMMITTED workspace writes: it
* must survive a DO reset even when nothing was ever pushed to Artifacts
* (the "files missing until first deploy" bug).
*/
import { describe, it, expect } from 'vitest';
import { env, runInDurableObject } from 'cloudflare:test';
import { CheckpointStore } from '../src/space/checkpoint';
import type {} from './test-env';
function uniqueStub(name: string) {
const id = env.FsHarnessDO.idFromName(`ckpt-${name}-${Date.now()}-${Math.random()}`);
return env.FsHarnessDO.get(id);
}
const encoder = new TextEncoder();
describe('CheckpointStore', () => {
it('saves and loads file bytes', async () => {
await runInDurableObject(uniqueStub('roundtrip'), async (_instance, state) => {
const store = new CheckpointStore(state.storage);
store.save('/a.ts', encoder.encode('aaa'));
store.save('/dir/b.ts', encoder.encode('bbb'));
const { files, tombstones } = store.load();
expect(tombstones).toEqual([]);
const map = new Map(files.map(([p, b]) => [p, new TextDecoder().decode(b)]));
expect(map.get('/a.ts')).toBe('aaa');
expect(map.get('/dir/b.ts')).toBe('bbb');
});
});
it('overwrites a path with the latest bytes', async () => {
await runInDurableObject(uniqueStub('overwrite'), async (_instance, state) => {
const store = new CheckpointStore(state.storage);
store.save('/a.ts', encoder.encode('v1'));
store.save('/a.ts', encoder.encode('v2'));
const { files } = store.load();
expect(files).toHaveLength(1);
expect(new TextDecoder().decode(files[0][1])).toBe('v2');
});
});
it('tombstones a deletion and drops rows beneath it', async () => {
await runInDurableObject(uniqueStub('tombstone'), async (_instance, state) => {
const store = new CheckpointStore(state.storage);
store.save('/keep.ts', encoder.encode('k'));
store.save('/dir/a.ts', encoder.encode('a'));
store.save('/dir/nested/b.ts', encoder.encode('b'));
store.save('/dir', null);
const { files, tombstones } = store.load();
expect(files.map(([p]) => p)).toEqual(['/keep.ts']);
expect(tombstones).toEqual(['/dir']);
});
});
it('a re-save after a tombstone replaces the tombstone for that path', async () => {
await runInDurableObject(uniqueStub('resave'), async (_instance, state) => {
const store = new CheckpointStore(state.storage);
store.save('/f.ts', encoder.encode('v1'));
store.save('/f.ts', null);
store.save('/f.ts', encoder.encode('v2'));
const { files, tombstones } = store.load();
expect(tombstones).toEqual([]);
expect(files).toHaveLength(1);
expect(new TextDecoder().decode(files[0][1])).toBe('v2');
});
});
it('clear drops every row (post-push reset)', async () => {
await runInDurableObject(uniqueStub('clear'), async (_instance, state) => {
const store = new CheckpointStore(state.storage);
store.save('/a.ts', encoder.encode('a'));
store.save('/b.ts', null);
store.clear();
const { files, tombstones } = store.load();
expect(files).toEqual([]);
expect(tombstones).toEqual([]);
});
});
it('persists across store instances bound to the same storage (cold start)', async () => {
const stub = uniqueStub('persist');
await runInDurableObject(stub, async (_instance, state) => {
new CheckpointStore(state.storage).save('/survives.ts', encoder.encode('yes'));
});
// A "cold-started" DO constructs a fresh store over the same storage.
await runInDurableObject(stub, async (_instance, state) => {
const { files } = new CheckpointStore(state.storage).load();
expect(files).toHaveLength(1);
expect(files[0][0]).toBe('/survives.ts');
});
});
});