forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinsightStore.test.ts
More file actions
137 lines (125 loc) · 4.65 KB
/
Copy pathinsightStore.test.ts
File metadata and controls
137 lines (125 loc) · 4.65 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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Proactive Insights history CRUD, proven against a REAL SQLite database via
// node:sqlite. Like dbTasks.test.ts, this imports the SAME symbols production
// runs — the DDL (INSIGHTS_SCHEMA, the exact string db.ts execs) and the exported
// *On(db, …) functions — so the schema and SQL can't drift from prod (the
// SQL-drift trap this program has hit before). db.ts itself still can't import
// here (better-sqlite3/electron), but its logic lives in insightStore.ts.
import { DatabaseSync } from 'node:sqlite'
import { beforeEach, describe, expect, it } from 'vitest'
import {
INSIGHTS_SCHEMA,
INSIGHT_HISTORY_CAP,
insertInsightOn,
recentInsightsOn,
dismissInsightOn,
dismissAllInsightsOn,
clearInsightsOn,
type InsightStoreDb
} from './insightStore'
import type { InsightPayload } from '../../shared/types'
function makeDb(): InsightStoreDb {
const db = new DatabaseSync(':memory:')
db.exec(INSIGHTS_SCHEMA)
return db as unknown as InsightStoreDb
}
let db: InsightStoreDb
beforeEach(() => {
db = makeDb()
})
function payload(overrides: Partial<InsightPayload> & { headline: string }): InsightPayload {
return {
advice: 'do the thing',
reasoning: 'because it helps',
category: 'productivity',
sourceApp: 'Slack',
confidence: 0.8,
...overrides
}
}
describe('schema', () => {
it('creates the insights table', () => {
const names = (
db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").all() as {
name: string
}[]
).map((r) => r.name)
expect(names).toContain('insights')
})
})
describe('insert + recent round-trip', () => {
it('round-trips every mapped column with camelCase aliases', () => {
const id = insertInsightOn(
db,
payload({
headline: 'Take a break',
advice: 'You have been heads-down 2h',
reasoning: 'long focus block detected',
category: 'health',
sourceApp: 'VS Code',
confidence: 0.95
}),
1000
)
expect(id).toBeGreaterThan(0)
const list = recentInsightsOn(db, 100)
expect(list).toHaveLength(1)
const r = list[0]
expect(r).toMatchObject({
id,
ts: 1000,
headline: 'Take a break',
advice: 'You have been heads-down 2h',
reasoning: 'long focus block detected',
category: 'health',
sourceApp: 'VS Code',
confidence: 0.95,
dismissed: 0
})
})
it('orders newest-first (ts DESC) and honors the limit', () => {
insertInsightOn(db, payload({ headline: 'oldest' }), 1000)
insertInsightOn(db, payload({ headline: 'newest' }), 3000)
insertInsightOn(db, payload({ headline: 'middle' }), 2000)
expect(recentInsightsOn(db, 100).map((r) => r.headline)).toEqual(['newest', 'middle', 'oldest'])
expect(recentInsightsOn(db, 2).map((r) => r.headline)).toEqual(['newest', 'middle'])
})
it('caps history to the newest INSIGHT_HISTORY_CAP rows', () => {
for (let i = 0; i < INSIGHT_HISTORY_CAP + 15; i++) {
insertInsightOn(db, payload({ headline: `h${i}` }), 1000 + i)
}
const all = recentInsightsOn(db, 1000)
expect(all).toHaveLength(INSIGHT_HISTORY_CAP)
// The newest row survives; the very first (oldest) was pruned.
expect(all[0].headline).toBe(`h${INSIGHT_HISTORY_CAP + 14}`)
expect(all.some((r) => r.headline === 'h0')).toBe(false)
})
})
describe('dismiss', () => {
it('dismissInsight flags exactly one row and returns true; false when id is absent', () => {
const keep = insertInsightOn(db, payload({ headline: 'keep' }), 1000)
const target = insertInsightOn(db, payload({ headline: 'target' }), 2000)
expect(dismissInsightOn(db, target)).toBe(true)
expect(dismissInsightOn(db, 99999)).toBe(false)
const byId = Object.fromEntries(recentInsightsOn(db, 100).map((r) => [r.id, r.dismissed]))
expect(byId[target]).toBe(1)
expect(byId[keep]).toBe(0)
})
it('dismissAll marks every unread row read and returns the count changed', () => {
insertInsightOn(db, payload({ headline: 'a' }), 1000)
insertInsightOn(db, payload({ headline: 'b' }), 2000)
const first = dismissAllInsightsOn(db)
expect(first).toBe(2)
// Idempotent: a second call changes nothing (already all dismissed).
expect(dismissAllInsightsOn(db)).toBe(0)
expect(recentInsightsOn(db, 100).every((r) => r.dismissed === 1)).toBe(true)
})
})
describe('clear', () => {
it('clearInsights deletes all rows and returns the count', () => {
insertInsightOn(db, payload({ headline: 'a' }), 1000)
insertInsightOn(db, payload({ headline: 'b' }), 2000)
expect(clearInsightsOn(db)).toBe(2)
expect(recentInsightsOn(db, 100)).toEqual([])
expect(clearInsightsOn(db)).toBe(0)
})
})