summaryrefslogtreecommitdiffhomepage
path: root/src/adapters/idb/index.test.ts
blob: 12bb5ad9845547fda1144495a26572878126d40d (plain)
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import "fake-indexeddb/auto";
import type { StoredChunk } from "@dispatch/wire";
import { describe, expect, it } from "vitest";
import { createIdbChunkStore } from "./index";

function textChunk(text: string): StoredChunk["chunk"] {
	return { type: "text", text };
}

function makeChunk(
	seq: number,
	text: string,
	role: StoredChunk["role"] = "assistant",
): StoredChunk {
	return { seq, role, chunk: textChunk(text) };
}

describe("createIdbChunkStore", () => {
	it("append then load returns chunks seq-ordered", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });
		const chunks = [makeChunk(1, "a"), makeChunk(2, "b"), makeChunk(3, "c")];

		await store.append("conv1", chunks);
		const loaded = await store.load("conv1");

		expect(loaded).toHaveLength(3);
		expect(loaded[0]?.seq).toBe(1);
		expect(loaded[1]?.seq).toBe(2);
		expect(loaded[2]?.seq).toBe(3);
		expect(loaded[0]?.chunk).toEqual(textChunk("a"));
	});

	it("append out-of-order still loads seq-ordered", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });
		const chunks = [makeChunk(3, "c"), makeChunk(1, "a"), makeChunk(2, "b")];

		await store.append("conv1", chunks);
		const loaded = await store.load("conv1");

		expect(loaded).toHaveLength(3);
		expect(loaded.map((c) => c.seq)).toEqual([1, 2, 3]);
	});

	it("append is idempotent on duplicate seq", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });

		await store.append("conv1", [makeChunk(1, "first"), makeChunk(2, "b")]);
		await store.append("conv1", [makeChunk(1, "first"), makeChunk(3, "c")]);

		const loaded = await store.load("conv1");
		expect(loaded).toHaveLength(3);
		expect(loaded.map((c) => c.seq)).toEqual([1, 2, 3]);
		expect(loaded[0]?.chunk).toEqual(textChunk("first"));
	});

	it("load returns [] for an absent conversation", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });

		const loaded = await store.load("nonexistent");
		expect(loaded).toEqual([]);
	});

	it("delete removes a conversation", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });

		await store.append("conv1", [makeChunk(1, "a")]);
		await store.append("conv2", [makeChunk(1, "b")]);

		await store.delete("conv1");

		expect(await store.load("conv1")).toEqual([]);
		const conv2 = await store.load("conv2");
		expect(conv2).toHaveLength(1);
		expect(conv2[0]?.chunk).toEqual(textChunk("b"));
	});

	it("index aggregates chunkCount and maxSeq", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });

		await store.append("conv1", [makeChunk(1, "a"), makeChunk(2, "b"), makeChunk(3, "c")]);
		await store.append("conv2", [makeChunk(1, "x")]);

		const idx = await store.index();
		expect(idx).toHaveLength(2);

		const c1 = idx.find((e) => e.conversationId === "conv1");
		const c2 = idx.find((e) => e.conversationId === "conv2");

		expect(c1?.chunkCount).toBe(3);
		expect(c1?.maxSeq).toBe(3);
		expect(c2?.chunkCount).toBe(1);
		expect(c2?.maxSeq).toBe(1);
	});

	it("index reports lastAccess after load", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });

		await store.append("conv1", [makeChunk(1, "a")]);
		const idx = await store.index();

		const entry = idx.find((e) => e.conversationId === "conv1");
		expect(entry?.lastAccess).toBeTypeOf("number");
		expect(entry?.lastAccess).toBeGreaterThan(0);
	});

	it("separate conversations are isolated", async () => {
		const store = createIdbChunkStore({ indexedDB: new IDBFactory() });

		await store.append("conv1", [makeChunk(1, "a1"), makeChunk(2, "a2")]);
		await store.append("conv2", [makeChunk(1, "b1")]);

		const loaded1 = await store.load("conv1");
		const loaded2 = await store.load("conv2");

		expect(loaded1).toHaveLength(2);
		expect(loaded2).toHaveLength(1);
		expect(loaded1[0]?.chunk).toEqual(textChunk("a1"));
		expect(loaded2[0]?.chunk).toEqual(textChunk("b1"));
	});
});