No tests exist yet (listed as TODO in README). The goal is to verify that Claude Code usage is correctly tracked: JSONL parsing, token aggregation, cost calculations, and DB queries all produce correct numbers. We'll use in-memory SQLite (better-sqlite3 supports :memory:) for fast, isolated tests.
- Framework: Vitest (natural fit since Vite is already in the project; handles ESM + TypeScript natively)
- DB strategy: Add a single
setDb()function toschema.tsto allow injecting an in-memory database. All query functions already callgetDb()which returns the singleton, so no other production code changes needed. - Test types: Unit tests for pure functions + integration tests for the parse-to-DB-to-query pipeline
| File | Purpose |
|---|---|
vitest.config.ts |
Separate from client vite.config.ts (which has root: 'src/client' and React plugin) |
src/server/test/setup.ts |
setupTestDb() / teardownTestDb() helpers using :memory: DB |
src/server/test/fixtures.ts |
JSONL test data constants (various models, streaming dedup, subagents, etc.) |
src/server/parser/jsonl.test.ts |
Parser unit + integration tests (~18 tests) |
src/server/db/queries.test.ts |
DB query and cost calculation tests (~20 tests) |
| File | Change |
|---|---|
src/server/db/schema.ts |
Add setDb(database) function (4 lines) |
package.json |
Add vitest devDependency + "test" / "test:watch" scripts |
- Pure functions:
isSubagentFile,extractProjectFromPath,extractSessionExternalIdFromPath,extractParentSessionExternalId - Integration: Basic session parse -> DB verify, streaming message deduplication (same ID appears multiple times, last wins), skipping
file-history-snapshotlines, handling invalid JSON gracefully, custom title extraction, sync state tracking, incremental sync, upsert idempotency, subagent parsing + parent linking
- CRUD:
upsertSessioninsert + update semantics (keeps earliest start, latest end),insertMessagesbulk + upsert on conflict - Cost calculations (all 5 pricing tiers with hand-calculated expected values):
- Sonnet 4: input=$3, cache_write=$3.75, cache_read=$0.30, output=$15
- Sonnet 4.5 (<=200K context): $3/$3.75/$0.30/$15
- Sonnet 4.5 (>200K context): $6/$7.50/$0.60/$22.50
- Opus 4.5: $5/$6.25/$0.50/$25
- Haiku 4.5: $1/$1.25/$0.10/$5
- Unknown model falls through to default pricing
- Aggregation:
getSessionStatswith date/project/customTitle filters, agent-* exclusion, subagentCount;getDailyStatsdate grouping;getSummarytotals + cache savings;getProjects/getCustomTitlesdistinct lists;getSubagentsBySessionId;cleanupOrphanedSubagentSessions
- Install vitest:
npm install --save-dev vitest - Add npm scripts:
"test": "vitest run","test:watch": "vitest" - Create
vitest.config.tsat project root - Modify
schema.ts- addsetDb()function - Create
src/server/test/setup.ts - Create
src/server/test/fixtures.ts - Create
src/server/db/queries.test.ts - Create
src/server/parser/jsonl.test.ts - Run
npm testto verify
npm test # run all tests once
npm run test:watch # watch mode during developmentNo issues encountered during implementation. All 50 tests passed on the first run (170ms total):
queries.test.ts: 28 tests passedjsonl.test.ts: 22 tests passed
The in-memory SQLite approach worked seamlessly -- the single setDb() addition to schema.ts was sufficient to redirect all DB operations to the test database without any other production code changes.