forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathliveNotesStore.test.ts
More file actions
171 lines (158 loc) · 4.9 KB
/
Copy pathliveNotesStore.test.ts
File metadata and controls
171 lines (158 loc) · 4.9 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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// PR8 LiveNotes persistence — proven against a REAL SQLite database via
// node:sqlite. db.ts's better-sqlite3 is built for Electron's ABI and can't load
// under plain-node vitest, so the tests exercise the SAME schema + CRUD symbols
// db.ts imports (LIVE_NOTES_SCHEMA + the *On functions) — never a re-declared copy
// (that drift hid two real bugs in this program). This asserts:
// - the schema materializes both tables,
// - AI and manual notes are SEPARATE rows (auto-gen never overwrites a typed note),
// - is_ai maps to a real boolean, notes list oldest-first,
// - update touches text + updated_at only, delete removes one row.
import { DatabaseSync } from 'node:sqlite'
import { beforeEach, describe, expect, it } from 'vitest'
import {
LIVE_NOTES_SCHEMA,
createTranscriptionSessionOn,
createLiveNoteOn,
updateLiveNoteOn,
deleteLiveNoteOn,
listLiveNotesOn,
type LiveNotesDb
} from './liveNotesStore'
const SESSION = 'session-1'
function makeDb(): DatabaseSync {
const db = new DatabaseSync(':memory:')
db.exec(LIVE_NOTES_SCHEMA)
return db
}
function asDb(db: DatabaseSync): LiveNotesDb {
return db as unknown as LiveNotesDb
}
function seedSession(db: DatabaseSync, id = SESSION): void {
createTranscriptionSessionOn(asDb(db), { id, startedAt: 1000, createdAt: 1000 })
}
describe('LiveNotes schema', () => {
it('creates transcription_sessions and live_notes', () => {
const db = makeDb()
const names = (
db
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
.all() as { name: string }[]
).map((r) => r.name)
expect(names).toContain('transcription_sessions')
expect(names).toContain('live_notes')
})
it('declares the cascading FK on live_notes.session_id', () => {
const db = makeDb()
const fks = db.prepare('PRAGMA foreign_key_list(live_notes)').all() as {
table: string
from: string
to: string
on_delete: string
}[]
expect(fks).toHaveLength(1)
expect(fks[0].table).toBe('transcription_sessions')
expect(fks[0].from).toBe('session_id')
expect(fks[0].on_delete).toBe('CASCADE')
})
})
describe('LiveNotes CRUD', () => {
let db: DatabaseSync
beforeEach(() => {
db = makeDb()
seedSession(db)
})
it('stores AI and manual notes as separate rows (never overwrites a typed note)', () => {
createLiveNoteOn(asDb(db), {
id: 'n1',
sessionId: SESSION,
text: 'typed by user',
isAi: false,
createdAt: 2000,
updatedAt: 2000
})
createLiveNoteOn(asDb(db), {
id: 'n2',
sessionId: SESSION,
text: 'ai bullet',
isAi: true,
segStart: 3,
segEnd: 6,
createdAt: 2500,
updatedAt: 2500
})
const notes = listLiveNotesOn(asDb(db), SESSION)
expect(notes).toHaveLength(2)
// Oldest-first: the typed note precedes the later AI note.
expect(notes.map((n) => n.id)).toEqual(['n1', 'n2'])
const [manual, ai] = notes
expect(manual.isAi).toBe(false)
expect(manual.text).toBe('typed by user')
expect(ai.isAi).toBe(true)
expect(ai.segStart).toBe(3)
expect(ai.segEnd).toBe(6)
})
it('updates only text + updated_at on an explicit edit', () => {
createLiveNoteOn(asDb(db), {
id: 'n1',
sessionId: SESSION,
text: 'original',
isAi: false,
createdAt: 2000,
updatedAt: 2000
})
updateLiveNoteOn(asDb(db), 'n1', 'edited', 9000)
const [note] = listLiveNotesOn(asDb(db), SESSION)
expect(note.text).toBe('edited')
expect(note.updatedAt).toBe(9000)
expect(note.createdAt).toBe(2000)
})
it('deletes a single note', () => {
createLiveNoteOn(asDb(db), {
id: 'n1',
sessionId: SESSION,
text: 'a',
isAi: true,
createdAt: 2000,
updatedAt: 2000
})
createLiveNoteOn(asDb(db), {
id: 'n2',
sessionId: SESSION,
text: 'b',
isAi: true,
createdAt: 2100,
updatedAt: 2100
})
deleteLiveNoteOn(asDb(db), 'n1')
const notes = listLiveNotesOn(asDb(db), SESSION)
expect(notes.map((n) => n.id)).toEqual(['n2'])
})
it('scopes notes to their session', () => {
seedSession(db, 'session-2')
createLiveNoteOn(asDb(db), {
id: 'n1',
sessionId: SESSION,
text: 'a',
isAi: true,
createdAt: 2000,
updatedAt: 2000
})
createLiveNoteOn(asDb(db), {
id: 'n2',
sessionId: 'session-2',
text: 'b',
isAi: true,
createdAt: 2100,
updatedAt: 2100
})
expect(listLiveNotesOn(asDb(db), SESSION).map((n) => n.id)).toEqual(['n1'])
expect(listLiveNotesOn(asDb(db), 'session-2').map((n) => n.id)).toEqual(['n2'])
})
it('is idempotent on a repeated session id (StrictMode / reconnect safe)', () => {
seedSession(db) // same id again
const count = (
db.prepare('SELECT COUNT(*) AS n FROM transcription_sessions').get() as { n: number }
).n
expect(count).toBe(1)
})
})