f733998945
- Novos indexadores hermes-indexer.ts e opencode-indexer.ts para unificar sessões Claude, Hermes Agent e OpenCode num único sessions.db - SessionMeta alargado: source (obrigatório), model, input/output_tokens, estimated_cost; project_path/jsonl_path agora nullable - Fix TS: tipos explícitos, guard jsonl_path null, dependências instaladas Security Audit (Regra #47): - npm audit executado — 0 vulnerabilities após fix - vite 7→8.0.16 (breaking upgrade, resolve esbuild CVE GHSA-gv7w-rqvm-qjhr) - vitest 4.0.18→4.1.9 (resolve esbuild interno CVE GHSA-gv7w-rqvm-qjhr) - shell-quote override ^1.8.4 via package.json#overrides (CVE GHSA-w7jw-789q-3m8p) - react-router, joi, qs, form-data, ip-address, js-yaml resolvidos via npm audit fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { mkdtempSync } from 'fs'
|
|
import { tmpdir } from 'os'
|
|
import { join } from 'path'
|
|
import { openSessionsDb } from '../services/sessions/db.js'
|
|
import type { SessionMeta } from '../types/session.js'
|
|
|
|
function sampleMeta(overrides: Partial<SessionMeta> = {}): SessionMeta {
|
|
return {
|
|
session_id: 's1',
|
|
source: 'claude',
|
|
model: null,
|
|
input_tokens: null,
|
|
output_tokens: null,
|
|
estimated_cost: null,
|
|
project_path: '/tmp/project',
|
|
project_slug: 'project',
|
|
jsonl_path: '/tmp/project/s1.jsonl',
|
|
started_at: '2026-04-23T10:00:00Z',
|
|
ended_at: '2026-04-23T10:30:00Z',
|
|
duration_sec: 1800,
|
|
event_count: 50,
|
|
user_messages: 5,
|
|
assistant_msgs: 10,
|
|
tool_calls: 20,
|
|
first_prompt: 'olá',
|
|
tools_used: ['Bash', 'Read'],
|
|
skills_invoked: ['brainstorming'],
|
|
outcome: 'completed',
|
|
permission_mode: 'default',
|
|
file_size: 10000,
|
|
indexed_at: '2026-04-23T10:31:00Z',
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
describe('sessions db', () => {
|
|
let dbPath: string
|
|
beforeEach(() => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'obs-db-'))
|
|
dbPath = join(dir, 'sessions.db')
|
|
})
|
|
|
|
it('cria schema, faz upsert e query', () => {
|
|
const db = openSessionsDb(dbPath)
|
|
db.upsertSession(sampleMeta())
|
|
const rows = db.listSessions({ days: 30 })
|
|
expect(rows).toHaveLength(1)
|
|
expect(rows[0].session_id).toBe('s1')
|
|
expect(rows[0].tools_used).toEqual(['Bash', 'Read'])
|
|
})
|
|
|
|
it('upsert substitui registo existente', () => {
|
|
const db = openSessionsDb(dbPath)
|
|
db.upsertSession(sampleMeta({ event_count: 50 }))
|
|
db.upsertSession(sampleMeta({ event_count: 75 }))
|
|
const rows = db.listSessions({ days: 30 })
|
|
expect(rows).toHaveLength(1)
|
|
expect(rows[0].event_count).toBe(75)
|
|
})
|
|
|
|
it('filtra por projecto e tool', () => {
|
|
const db = openSessionsDb(dbPath)
|
|
db.upsertSession(sampleMeta({ session_id: 'a', jsonl_path: '/tmp/a.jsonl', project_slug: 'alpha', tools_used: ['Bash'] }))
|
|
db.upsertSession(sampleMeta({ session_id: 'b', jsonl_path: '/tmp/b.jsonl', project_slug: 'beta', tools_used: ['Read'] }))
|
|
expect(db.listSessions({ project: 'alpha' })).toHaveLength(1)
|
|
expect(db.listSessions({ tool: 'Read' })).toHaveLength(1)
|
|
})
|
|
|
|
it('devolve contagem total', () => {
|
|
const db = openSessionsDb(dbPath)
|
|
db.upsertSession(sampleMeta({ session_id: 'a', jsonl_path: '/tmp/a.jsonl' }))
|
|
db.upsertSession(sampleMeta({ session_id: 'b', jsonl_path: '/tmp/b.jsonl' }))
|
|
expect(db.countSessions({})).toBe(2)
|
|
})
|
|
|
|
it('upsertMany insere batch em transacção', () => {
|
|
const db = openSessionsDb(dbPath)
|
|
db.upsertMany([
|
|
sampleMeta({ session_id: 'x', jsonl_path: '/tmp/x.jsonl' }),
|
|
sampleMeta({ session_id: 'y', jsonl_path: '/tmp/y.jsonl' }),
|
|
sampleMeta({ session_id: 'z', jsonl_path: '/tmp/z.jsonl' }),
|
|
])
|
|
expect(db.countSessions({})).toBe(3)
|
|
})
|
|
})
|