mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-23 15:19:37 +00:00
Some checks are pending
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (24) (push) Waiting to run
Tests / test (26) (push) Waiting to run
Tests / test-platforms (macos-latest) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Tests / test-platforms (windows-latest) (push) Waiting to run
Tests / desktop (push) Waiting to run
* Add regressions for redirected editor storage roots * Honor redirected data roots for Copilot and Cursor * Verify redirected Copilot OTel discovery on Linux * Reproduce Cursor cache collisions across storage roots * Bind Cursor result caches to their database and backfill redirected history * Verify redirected discovery against the latest daily cache * Update env guard and document editor storage overrides
141 lines
8 KiB
TypeScript
141 lines
8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
import { join, posix, win32 } from 'node:path'
|
|
import { tmpdir } from 'node:os'
|
|
import * as os from 'os'
|
|
import { createRequire } from 'node:module'
|
|
import { createCopilotProvider, getVSCodeGlobalStorageDirs, getVSCodeWorkspaceStorageDirs } from '../src/providers/copilot.js'
|
|
import { clearCursorWorkspaceMapCache, createCursorProvider } from '../src/providers/cursor.js'
|
|
import { computeEnvFingerprint } from '../src/session-cache.js'
|
|
import { isSqliteAvailable } from '../src/sqlite.js'
|
|
|
|
const requireForTest = createRequire(import.meta.url)
|
|
vi.mock('os', async importOriginal => {
|
|
const actual = await importOriginal<typeof import('os')>()
|
|
return { ...actual, platform: vi.fn(actual.platform) }
|
|
})
|
|
let root: string
|
|
|
|
beforeEach(async () => {
|
|
root = await mkdtemp(join(tmpdir(), 'codeburn-editor-roots-'))
|
|
vi.stubEnv('APPDATA', root)
|
|
vi.stubEnv('XDG_CONFIG_HOME', root)
|
|
vi.stubEnv('CODEBURN_COPILOT_DISABLE_OTEL', '1')
|
|
clearCursorWorkspaceMapCache()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
vi.restoreAllMocks()
|
|
vi.unstubAllEnvs()
|
|
clearCursorWorkspaceMapCache()
|
|
await rm(root, { recursive: true, force: true })
|
|
})
|
|
|
|
describe('redirected editor data paths', () => {
|
|
it.each([
|
|
['win32', 'C:\\Users\\fixture', 'D:\\redirected-roaming', win32.join],
|
|
['linux', '/home/fixture', '/mnt/redirected-config', posix.join],
|
|
] as const)('uses the %s application-data override for every VS Code variant', (os, home, redirected, j) => {
|
|
vi.stubEnv(os === 'win32' ? 'APPDATA' : 'XDG_CONFIG_HOME', redirected)
|
|
for (const [getDirs, storage] of [
|
|
[getVSCodeWorkspaceStorageDirs, 'workspaceStorage'],
|
|
[getVSCodeGlobalStorageDirs, 'globalStorage'],
|
|
] as const) {
|
|
expect(getDirs(home, os)).toEqual(['Code', 'Code - Insiders', 'VSCodium'].map(editor => j(redirected, editor, 'User', storage)))
|
|
}
|
|
})
|
|
|
|
it.each([undefined, ''])('keeps platform defaults for absent or empty overrides (%s)', value => {
|
|
vi.stubEnv('APPDATA', value)
|
|
vi.stubEnv('XDG_CONFIG_HOME', value)
|
|
expect(getVSCodeWorkspaceStorageDirs('C:\\Users\\fixture', 'win32')[0]).toBe(win32.join('C:\\Users\\fixture', 'AppData', 'Roaming', 'Code', 'User', 'workspaceStorage'))
|
|
expect(getVSCodeGlobalStorageDirs('/home/fixture', 'linux')[0]).toBe('/home/fixture/.config/Code/User/globalStorage')
|
|
})
|
|
|
|
it('keeps macOS paths independent of APPDATA and XDG_CONFIG_HOME', () => {
|
|
expect(getVSCodeGlobalStorageDirs('/Users/fixture', 'darwin')).toEqual(['Code', 'Code - Insiders', 'VSCodium'].map(editor => posix.join('/Users/fixture', 'Library', 'Application Support', editor, 'User', 'globalStorage')))
|
|
})
|
|
|
|
it.skipIf(process.platform === 'darwin')('discovers Copilot journals and watches both redirected storage folders', async () => {
|
|
const workspace = join(root, 'Code', 'User', 'workspaceStorage')
|
|
const global = join(root, 'Code', 'User', 'globalStorage')
|
|
const wsChats = join(workspace, 'hash', 'chatSessions')
|
|
const globalChats = join(global, 'emptyWindowChatSessions')
|
|
await mkdir(wsChats, { recursive: true })
|
|
await mkdir(globalChats, { recursive: true })
|
|
await writeFile(join(workspace, 'hash', 'workspace.json'), JSON.stringify({ folder: 'file:///work/redirected' }))
|
|
const journal = JSON.stringify({ kind: 0, v: { sessionId: 'redirected', requests: [] } }) + '\n'
|
|
const wsPath = join(wsChats, 'workspace.jsonl')
|
|
const globalPath = join(globalChats, 'empty.jsonl')
|
|
await writeFile(wsPath, journal)
|
|
await writeFile(globalPath, journal)
|
|
const provider = createCopilotProvider(join(root, 'no-cli'), undefined, undefined, join(root, 'no-jb'), join(root, 'no-store'))
|
|
expect((await provider.discoverSessions()).map(s => s.path).sort()).toEqual([wsPath, globalPath].sort())
|
|
expect((await provider.probeRoots!()).map(r => r.path)).toEqual(expect.arrayContaining([workspace, global]))
|
|
})
|
|
|
|
it.skipIf(process.platform === 'darwin' || !isSqliteAvailable())('discovers the redirected Cursor database and its sibling workspace mapping', async () => {
|
|
const { DatabaseSync } = requireForTest('node:sqlite') as typeof import('node:sqlite')
|
|
const global = join(root, 'Cursor', 'User', 'globalStorage')
|
|
const workspace = join(root, 'Cursor', 'User', 'workspaceStorage', 'hash')
|
|
await mkdir(global, { recursive: true })
|
|
await mkdir(workspace, { recursive: true })
|
|
const dbPath = join(global, 'state.vscdb')
|
|
const globalDb = new DatabaseSync(dbPath)
|
|
globalDb.exec('CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value TEXT)')
|
|
globalDb.close()
|
|
const workspaceDb = new DatabaseSync(join(workspace, 'state.vscdb'))
|
|
workspaceDb.exec('CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value TEXT)')
|
|
workspaceDb.prepare('INSERT INTO ItemTable VALUES (?, ?)').run('composer.composerData', JSON.stringify({ allComposers: [{ composerId: 'redirected-composer' }] }))
|
|
workspaceDb.close()
|
|
await writeFile(join(workspace, 'workspace.json'), JSON.stringify({ folder: 'file:///work/redirected' }))
|
|
|
|
const provider = createCursorProvider()
|
|
expect(await provider.probeRoots!()).toEqual([{ path: dbPath, label: 'db' }])
|
|
expect((await provider.discoverSessions()).map(s => s.project)).toContain('-work-redirected')
|
|
})
|
|
|
|
it('keeps constructor storage overrides ahead of application-data defaults', async () => {
|
|
const workspace = join(root, 'explicit-ws')
|
|
const global = join(root, 'explicit-global')
|
|
const provider = createCopilotProvider(join(root, 'no-cli'), workspace, global, join(root, 'no-jb'), join(root, 'no-store'))
|
|
const roots = (await provider.probeRoots!()).map(r => r.path)
|
|
expect(roots).toEqual(expect.arrayContaining([workspace, global]))
|
|
expect(roots).not.toContain(join(root, 'Code', 'User', 'workspaceStorage'))
|
|
expect(await createCursorProvider(join(root, 'explicit.vscdb')).probeRoots!()).toEqual([{ path: join(root, 'explicit.vscdb'), label: 'db' }])
|
|
})
|
|
|
|
it.skipIf(!isSqliteAvailable())('discovers OTel in a redirected Linux global-storage root', async () => {
|
|
vi.stubEnv('APPDATA', join(root, 'unused-windows-root'))
|
|
const { DatabaseSync } = requireForTest('node:sqlite') as typeof import('node:sqlite')
|
|
// posix.join can operate on this temporary Windows path too: Node's fs
|
|
// accepts its forward slashes, so Linux discovery is exercised on any host.
|
|
const global = getVSCodeGlobalStorageDirs(root, 'linux')[0]!
|
|
const otelDir = posix.join(global, 'github.copilot-chat')
|
|
const dbPath = posix.join(otelDir, 'agent-traces.db')
|
|
await mkdir(otelDir, { recursive: true })
|
|
const db = new DatabaseSync(dbPath)
|
|
db.exec(`
|
|
CREATE TABLE spans (span_id TEXT PRIMARY KEY, trace_id TEXT, operation_name TEXT, start_time_ms INTEGER, response_model TEXT);
|
|
CREATE TABLE span_attributes (id INTEGER PRIMARY KEY, span_id TEXT, key TEXT, value TEXT);
|
|
INSERT INTO spans VALUES ('span', 'trace', 'chat', 1780157113020, 'gpt-4o');
|
|
INSERT INTO span_attributes VALUES (1, 'span', 'gen_ai.conversation.id', 'redirected-otel');
|
|
INSERT INTO span_attributes VALUES (2, 'span', 'gen_ai.usage.input_tokens', '100');
|
|
INSERT INTO span_attributes VALUES (3, 'span', 'gen_ai.usage.output_tokens', '20');
|
|
`)
|
|
db.close()
|
|
vi.spyOn(os, 'platform').mockReturnValue('linux')
|
|
vi.stubEnv('CODEBURN_COPILOT_DISABLE_OTEL', '')
|
|
const provider = createCopilotProvider(join(root, 'no-cli'), undefined, undefined, join(root, 'no-jb'), join(root, 'no-store'))
|
|
expect((await provider.discoverSessions()).map(s => s.path)).toContain(join(dbPath))
|
|
expect((await provider.probeRoots!()).map(r => r.path)).toContain(join(otelDir))
|
|
})
|
|
|
|
it.each(['APPDATA', 'XDG_CONFIG_HOME'])('invalidates Cursor caches on %s changes without discarding durable Copilot history', variable => {
|
|
const cursorBefore = computeEnvFingerprint('cursor')
|
|
const copilotBefore = computeEnvFingerprint('copilot')
|
|
vi.stubEnv(variable, join(root, 'moved'))
|
|
expect(computeEnvFingerprint('cursor')).not.toBe(cursorBefore)
|
|
expect(computeEnvFingerprint('copilot')).toBe(copilotBefore)
|
|
})
|
|
})
|