mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-22 14:49:39 +00:00
os.homedir() reads USERPROFILE on Windows and never HOME, so the isolation setup and every test that moved home were still resolving the developer's real home directory. Fixtures came back empty, real sessions leaked into counts, and the code under test wrote plans, receipts and a sync ledger into the real ~/.config/codeburn during a run. USERPROFILE now joins the redirected variables and a shared setHome() helper moves HOME and USERPROFILE together; every spawn env carries both. The remaining Windows-only failures were POSIX-shaped fixtures: chmod tricks that only toggle a read-only attribute on NTFS, pid 1 as an always-alive lock holder, splitting paths on '/', bare tar, mode-bit expectations, and per-file timeouts too short for the cost of a tsx spawn. Each gets a platform-aware equivalent so nothing is skipped on Windows and the POSIX branches stay byte-for-byte what they were. Full run on Windows: 267 files passed, 2 skipped, 3592 tests passed.
128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'fs/promises'
|
|
import { tmpdir } from 'os'
|
|
import { join } from 'path'
|
|
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
|
|
import { clearSessionCache, parseAllSessions } from '../src/parser.js'
|
|
import { CACHE_VERSION, computeEnvFingerprint, type SessionCache } from '../src/session-cache.js'
|
|
import { readCacheOnDisk, writeCacheOnDisk } from './fixtures/session-cache-io.js'
|
|
import type { DateRange } from '../src/types.js'
|
|
import { setHome } from './setup/home.js'
|
|
|
|
let home: string
|
|
let cacheDir: string
|
|
|
|
beforeEach(async () => {
|
|
home = await mkdtemp(join(tmpdir(), 'codeburn-gemini-home-'))
|
|
cacheDir = await mkdtemp(join(tmpdir(), 'codeburn-gemini-cache-'))
|
|
setHome(home)
|
|
process.env['CODEBURN_CACHE_DIR'] = cacheDir
|
|
})
|
|
|
|
afterEach(async () => {
|
|
clearSessionCache()
|
|
await rm(home, { recursive: true, force: true })
|
|
await rm(cacheDir, { recursive: true, force: true })
|
|
})
|
|
|
|
describe('Gemini session cache migration', () => {
|
|
it('reparses cached legacy aggregate Gemini entries into granular calls', async () => {
|
|
const chatsDir = join(home, '.gemini', 'tmp', 'project-a', 'chats')
|
|
await mkdir(chatsDir, { recursive: true })
|
|
const sessionPath = join(chatsDir, 'session-2026-05-16.json')
|
|
await writeFile(sessionPath, JSON.stringify({
|
|
sessionId: 'gemini-session-1',
|
|
startTime: '2026-05-16T10:00:00.000Z',
|
|
messages: [
|
|
{ id: 'u1', timestamp: '2026-05-16T10:00:00.000Z', type: 'user', content: 'work' },
|
|
{
|
|
id: 'g1',
|
|
timestamp: '2026-05-16T10:00:05.000Z',
|
|
type: 'gemini',
|
|
content: 'first',
|
|
model: 'gemini-3.1-pro-preview',
|
|
tokens: { input: 10, output: 5 },
|
|
},
|
|
{
|
|
id: 'g2',
|
|
timestamp: '2026-05-16T10:00:10.000Z',
|
|
type: 'gemini',
|
|
content: 'second',
|
|
model: 'gemini-3.1-pro-preview',
|
|
tokens: { input: 12, output: 6 },
|
|
},
|
|
],
|
|
}))
|
|
|
|
const fileStat = await stat(sessionPath)
|
|
await writeCacheOnDisk({
|
|
version: CACHE_VERSION,
|
|
providers: {
|
|
gemini: {
|
|
envFingerprint: computeEnvFingerprint('gemini'),
|
|
files: {
|
|
[sessionPath]: {
|
|
fingerprint: {
|
|
dev: fileStat.dev,
|
|
ino: fileStat.ino,
|
|
mtimeMs: fileStat.mtimeMs,
|
|
sizeBytes: fileStat.size,
|
|
},
|
|
mcpInventory: [],
|
|
turns: [{
|
|
timestamp: '2026-05-16T10:00:00.000Z',
|
|
sessionId: 'gemini-session-1',
|
|
userMessage: 'work',
|
|
calls: [{
|
|
provider: 'gemini',
|
|
model: 'gemini-3.1-pro-preview',
|
|
usage: {
|
|
inputTokens: 22,
|
|
outputTokens: 11,
|
|
cacheCreationInputTokens: 0,
|
|
cacheReadInputTokens: 0,
|
|
cachedInputTokens: 0,
|
|
reasoningTokens: 0,
|
|
webSearchRequests: 0,
|
|
cacheCreationOneHourTokens: 0,
|
|
},
|
|
speed: 'standard',
|
|
timestamp: '2026-05-16T10:00:00.000Z',
|
|
tools: [],
|
|
bashCommands: [],
|
|
skills: [],
|
|
deduplicationKey: 'gemini:gemini-session-1',
|
|
}],
|
|
}],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as SessionCache)
|
|
|
|
const range: DateRange = {
|
|
start: new Date('2026-05-16T00:00:00.000Z'),
|
|
end: new Date('2026-05-16T23:59:59.999Z'),
|
|
}
|
|
|
|
const projects = await parseAllSessions(range, 'gemini')
|
|
const keys = projects.flatMap(project =>
|
|
project.sessions.flatMap(session =>
|
|
session.turns.flatMap(turn => turn.assistantCalls.map(call => call.deduplicationKey)),
|
|
),
|
|
)
|
|
|
|
expect(projects[0]!.totalApiCalls).toBe(2)
|
|
expect(keys).toEqual([
|
|
'gemini:gemini-session-1:g1',
|
|
'gemini:gemini-session-1:g2',
|
|
])
|
|
|
|
const savedCache = await readCacheOnDisk() as any
|
|
const savedKeys = savedCache.providers.gemini.files[sessionPath].turns.flatMap((turn: { calls: Array<{ deduplicationKey: string }> }) =>
|
|
turn.calls.map(call => call.deduplicationKey),
|
|
)
|
|
expect(savedKeys).toEqual(keys)
|
|
})
|
|
})
|