Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions server/src/computer/snapshot-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ export function createInMemorySnapshotStore(): SnapshotStore {
const snapshots = new Map<string, StoredSnapshot>();
return {
save: async (computerId, snapshot) => {
// Only ever forward, the same rule the table's `setWhere` applies, because the two stores have
// to agree about when a save wins. Two snapshots of one computer can complete out of order in a
// single process as easily as across two, and a test that reaches for this store because it has
// no database would otherwise be told a different story about what the gateway resolves
// against: the older page here, the newer one in a deployment.
const held = snapshots.get(computerId);
if (held && held.snapshotId >= snapshot.snapshotId) return;
snapshots.set(computerId, snapshot);
},
load: async (computerId) => snapshots.get(computerId),
Expand Down
29 changes: 29 additions & 0 deletions server/tests/computer-snapshot-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,35 @@ describe("the in-memory snapshot store", () => {
expect(loaded?.elements.get("e9")?.name).toBe("Cancel");
});

test("an older snapshot arriving late does not overwrite the newer one", async () => {
// The property #46 established, asked of this store rather than of the table. A test that reaches
// for the in-memory store because it has no database must not be told a different story about
// when a save wins: two snapshots of one computer can complete out of order here too, and the
// generation is what decides between them in both implementations.
const store = createInMemorySnapshotStore();
await store.save(
"default",
snapshot(8, [{ ref: "e9", role: "button", name: "Cancel" }]),
);

await store.save(
"default",
snapshot(7, [{ ref: "e9", role: "button", name: "Submit order" }]),
);

// A save of the generation already held loses too, the way `setWhere`'s `lt` refuses it: the
// stored snapshot is the one that generation named, and a second delivery of it carries nothing
// newer to say.
await store.save(
"default",
snapshot(8, [{ ref: "e9", role: "button", name: "Submit order" }]),
);

const loaded = await store.load("default");
expect(loaded?.snapshotId).toBe(8);
expect(loaded?.elements.get("e9")?.name).toBe("Cancel");
});

test("clearing forgets the snapshot, so nothing resolves against a wiped computer", async () => {
const store = createInMemorySnapshotStore();
await store.save(
Expand Down