vector-store-sqlite.test.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
1 day ago
| 1 | /** |
| 2 | * SQLite vector store tests: ensureCollection, upsert, search, count, close. |
| 3 | * Uses a temp directory for the DB. |
| 4 | */ |
| 5 | import { describe, it, before, after } from 'node:test'; |
| 6 | import assert from 'node:assert'; |
| 7 | import path from 'path'; |
| 8 | import fs from 'fs'; |
| 9 | import { fileURLToPath } from 'url'; |
| 10 | import { createSqliteVectorStore } from '../lib/vector-store-sqlite.mjs'; |
| 11 | |
| 12 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 13 | const testDataDir = path.join(__dirname, 'fixtures', 'tmp-vector-db'); |
| 14 | |
| 15 | describe('vector-store-sqlite', () => { |
| 16 | let store; |
| 17 | |
| 18 | before(() => { |
| 19 | if (fs.existsSync(testDataDir)) { |
| 20 | fs.rmSync(testDataDir, { recursive: true }); |
| 21 | } |
| 22 | fs.mkdirSync(testDataDir, { recursive: true }); |
| 23 | store = createSqliteVectorStore({ data_dir: testDataDir }); |
| 24 | }); |
| 25 | |
| 26 | after(() => { |
| 27 | if (store && typeof store.close === 'function') store.close(); |
| 28 | if (fs.existsSync(testDataDir)) { |
| 29 | try { |
| 30 | fs.rmSync(testDataDir, { recursive: true }); |
| 31 | } catch (_) {} |
| 32 | } |
| 33 | }); |
| 34 | |
| 35 | it('ensureCollection creates table with given dimension', async () => { |
| 36 | await store.ensureCollection(3); |
| 37 | const count = await store.count(); |
| 38 | assert.strictEqual(count, 0); |
| 39 | }); |
| 40 | |
| 41 | it('ensureCollection throws when table exists with different dimension', async () => { |
| 42 | await assert.rejects( |
| 43 | () => store.ensureCollection(5), |
| 44 | /dimension mismatch/ |
| 45 | ); |
| 46 | }); |
| 47 | |
| 48 | it('upsert inserts points and count increases', async () => { |
| 49 | await store.upsert([ |
| 50 | { |
| 51 | id: 'path/to/a_0', |
| 52 | vector: [0.1, 0.2, 0.3], |
| 53 | path: 'path/to/a.md', |
| 54 | project: 'p', |
| 55 | date: '2025-03-01', |
| 56 | tags: ['t1'], |
| 57 | text: 'chunk one', |
| 58 | }, |
| 59 | { |
| 60 | id: 'path/to/b_0', |
| 61 | vector: [0.4, 0.5, 0.6], |
| 62 | path: 'path/to/b.md', |
| 63 | project: 'p', |
| 64 | date: '2025-03-02', |
| 65 | tags: [], |
| 66 | text: 'chunk two', |
| 67 | }, |
| 68 | ]); |
| 69 | const count = await store.count(); |
| 70 | assert.strictEqual(count, 2); |
| 71 | }); |
| 72 | |
| 73 | it('upsert same chunk id again replaces row (vec0 has no INSERT OR REPLACE)', async () => { |
| 74 | await store.upsert([ |
| 75 | { |
| 76 | id: 'path/to/a_0', |
| 77 | vector: [0.11, 0.21, 0.31], |
| 78 | path: 'path/to/a.md', |
| 79 | project: 'p', |
| 80 | date: '2025-03-01', |
| 81 | tags: ['t1'], |
| 82 | text: 'replaced chunk', |
| 83 | }, |
| 84 | ]); |
| 85 | const count = await store.count(); |
| 86 | assert.strictEqual(count, 2); |
| 87 | const hits = await store.search([0.11, 0.21, 0.31], { limit: 5 }); |
| 88 | const a = hits.find((h) => h.path === 'path/to/a.md'); |
| 89 | assert(a && a.text === 'replaced chunk'); |
| 90 | }); |
| 91 | |
| 92 | it('search returns hits with path, score, text', async () => { |
| 93 | const hits = await store.search([0.15, 0.25, 0.35], { limit: 5 }); |
| 94 | assert(Array.isArray(hits)); |
| 95 | assert(hits.length >= 1); |
| 96 | const first = hits[0]; |
| 97 | assert(first.path); |
| 98 | assert(typeof first.score === 'number'); |
| 99 | assert(first.score > 0, 'score must be positive (1/(1+distance)); L2 distances used to map to 0'); |
| 100 | assert(first.text != null); |
| 101 | }); |
| 102 | |
| 103 | it('search filters by vault_id so multi-vault indexes do not leak paths', async () => { |
| 104 | const dim = 3; |
| 105 | const dir = path.join(testDataDir, 'vault-filter-sub'); |
| 106 | fs.mkdirSync(dir, { recursive: true }); |
| 107 | const vStore = createSqliteVectorStore({ data_dir: dir }); |
| 108 | await vStore.ensureCollection(dim); |
| 109 | const vA = [1, 0, 0]; |
| 110 | const vB = [0, 1, 0]; |
| 111 | await vStore.upsert([ |
| 112 | { |
| 113 | id: 'vault-a::same_0', |
| 114 | vector: vA, |
| 115 | path: 'same.md', |
| 116 | vault_id: 'vault-a', |
| 117 | project: null, |
| 118 | date: null, |
| 119 | tags: [], |
| 120 | text: 'alpha', |
| 121 | }, |
| 122 | { |
| 123 | id: 'vault-b::same_0', |
| 124 | vector: vB, |
| 125 | path: 'same.md', |
| 126 | vault_id: 'vault-b', |
| 127 | project: null, |
| 128 | date: null, |
| 129 | tags: [], |
| 130 | text: 'beta', |
| 131 | }, |
| 132 | ]); |
| 133 | const forA = await vStore.search(vA, { limit: 5, vault_id: 'vault-a' }); |
| 134 | const forB = await vStore.search(vB, { limit: 5, vault_id: 'vault-b' }); |
| 135 | assert.strictEqual(forA.length, 1); |
| 136 | assert.strictEqual(forA[0].text, 'alpha'); |
| 137 | assert.strictEqual(forB.length, 1); |
| 138 | assert.strictEqual(forB[0].text, 'beta'); |
| 139 | const noFilter = await vStore.search(vA, { limit: 10 }); |
| 140 | assert.strictEqual(noFilter.length, 2); |
| 141 | vStore.close(); |
| 142 | fs.rmSync(dir, { recursive: true }); |
| 143 | }); |
| 144 | |
| 145 | it('has close() method for cleanup', () => { |
| 146 | assert.strictEqual(typeof store.close, 'function'); |
| 147 | }); |
| 148 | }); |
File History
2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6
docs: accept Calendar Events v0 spec with Phase 0 security …
Human
1 day ago