codeburn/tests/cursor-range-cache.test.ts
Resham Joshi ed3c3f466b
fix(cursor): parse the full lookback regardless of the requested range (#1718)
The session cache keys the Cursor parse on the DB fingerprint alone, but the
parse stopped at the requested range start, so a narrow run (7 days) was served
to every wider range afterwards (30 days showed 2 calls instead of 1,680). The
parse now always reads the full 6 month lookback; the cursor parse version is
bumped so cached narrow entries re-parse.
2026-10-10 07:05:31 +02:00

74 lines
2.7 KiB
TypeScript

import { afterAll, beforeEach, describe, expect, it } from 'vitest'
import { mkdir, mkdtemp, rm } from 'fs/promises'
import { createRequire } from 'module'
import { join } from 'path'
import { tmpdir } from 'os'
import { getEditorDataDir } from '../src/editor-data-dir.js'
import { clearSessionCache, parseAllSessions } from '../src/parser.js'
import { isSqliteAvailable } from '../src/sqlite.js'
import type { DateRange, ProjectSummary } from '../src/types.js'
const DAY = 86_400_000
const daysAgo = (n: number) => new Date(Date.now() - n * DAY).toISOString()
const lastDays = (n: number): DateRange => ({ start: new Date(Date.now() - n * DAY), end: new Date() })
let root: string
async function createCursorDb(createdAt: string[]): Promise<void> {
const dir = join(getEditorDataDir('Cursor', root, process.platform), 'User', 'globalStorage')
await mkdir(dir, { recursive: true })
const { DatabaseSync } = createRequire(import.meta.url)('node:sqlite') as typeof import('node:sqlite')
const db = new DatabaseSync(join(dir, 'state.vscdb'))
db.exec('CREATE TABLE cursorDiskKV (key TEXT PRIMARY KEY, value TEXT)')
const stmt = db.prepare('INSERT INTO cursorDiskKV (key, value) VALUES (?, ?)')
createdAt.forEach((ts, i) => stmt.run(`bubbleId:conv-${i}:bubble-${i}`, JSON.stringify({
type: 2,
conversationId: `conv-${i}`,
text: 'def hello(): pass',
tokenCount: { inputTokens: 100, outputTokens: 100 },
createdAt: ts,
modelInfo: { modelName: 'gpt-5' },
})))
db.close()
}
function totals(projects: ProjectSummary[]): { calls: number; cost: number } {
let calls = 0
let cost = 0
for (const p of projects) for (const s of p.sessions) for (const t of s.turns) for (const c of t.assistantCalls) {
calls++
cost += c.costUSD
}
return { calls, cost }
}
async function parse(range: DateRange, cacheDir: string) {
process.env['CODEBURN_CACHE_DIR'] = cacheDir
clearSessionCache()
return totals(await parseAllSessions(range, 'cursor'))
}
beforeEach(async () => {
root = await mkdtemp(join(tmpdir(), 'cursor-range-cache-'))
process.env['HOME'] = root
process.env['USERPROFILE'] = root
process.env['APPDATA'] = join(root, 'AppData', 'Roaming')
delete process.env['XDG_CONFIG_HOME']
})
afterAll(async () => {
await rm(root, { recursive: true, force: true })
})
describe.skipIf(!isSqliteAvailable())('cursor parse cached across ranges', () => {
it('a wider range after a narrow one matches a cold wide parse', async () => {
await createCursorDb([daysAgo(1), daysAgo(10), daysAgo(20)])
const cold = await parse(lastDays(30), join(root, 'cold'))
expect(cold.calls).toBe(3)
const warm = join(root, 'warm')
expect((await parse(lastDays(7), warm)).calls).toBe(1)
expect(await parse(lastDays(30), warm)).toEqual(cold)
})
})