1646 lines
60 KiB
TypeScript
1646 lines
60 KiB
TypeScript
/* eslint-disable max-lines -- test suite covers snapshot, migration, auth materialization, and error-resilience scenarios */
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import {
|
|
chmodSync,
|
|
existsSync,
|
|
mkdtempSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
rmSync,
|
|
statSync,
|
|
writeFileSync
|
|
} from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import type { GlobalSettings } from '../../shared/types'
|
|
|
|
const testState = { userDataDir: '', fakeHomeDir: '' }
|
|
|
|
vi.mock('electron', () => ({
|
|
app: {
|
|
getPath: () => testState.userDataDir
|
|
}
|
|
}))
|
|
|
|
vi.mock('node:os', async () => {
|
|
const actual = await vi.importActual<typeof import('node:os')>('node:os') // eslint-disable-line @typescript-eslint/consistent-type-imports -- vi.importActual requires inline import()
|
|
return {
|
|
...actual,
|
|
homedir: () => testState.fakeHomeDir
|
|
}
|
|
})
|
|
|
|
function createSettings(overrides: Partial<GlobalSettings> = {}): GlobalSettings {
|
|
const appFontFamily = overrides.appFontFamily ?? 'Geist'
|
|
return {
|
|
workspaceDir: testState.fakeHomeDir,
|
|
nestWorkspaces: false,
|
|
refreshLocalBaseRefOnWorktreeCreate: false,
|
|
branchPrefix: 'git-username',
|
|
branchPrefixCustom: '',
|
|
theme: 'system',
|
|
editorAutoSave: false,
|
|
editorAutoSaveDelayMs: 1000,
|
|
editorMinimapEnabled: false,
|
|
markdownReviewToolsEnabled: true,
|
|
terminalFontSize: 14,
|
|
terminalFontFamily: 'JetBrains Mono',
|
|
terminalFontWeight: 500,
|
|
terminalLineHeight: 1,
|
|
terminalGpuAcceleration: 'auto',
|
|
terminalLigatures: 'auto',
|
|
terminalCursorStyle: 'block',
|
|
terminalCursorBlink: false,
|
|
terminalThemeDark: 'orca-dark',
|
|
terminalDividerColorDark: '#000000',
|
|
terminalUseSeparateLightTheme: false,
|
|
terminalThemeLight: 'orca-light',
|
|
terminalDividerColorLight: '#ffffff',
|
|
terminalInactivePaneOpacity: 0.5,
|
|
terminalActivePaneOpacity: 1,
|
|
terminalPaneOpacityTransitionMs: 150,
|
|
terminalDividerThicknessPx: 1,
|
|
terminalRightClickToPaste: false,
|
|
terminalFocusFollowsMouse: false,
|
|
terminalClipboardOnSelect: false,
|
|
terminalAllowOsc52Clipboard: false,
|
|
setupScriptLaunchMode: 'split-vertical',
|
|
terminalScrollbackBytes: 10_000_000,
|
|
openLinksInApp: false,
|
|
rightSidebarOpenByDefault: true,
|
|
sourceControlViewMode: 'list',
|
|
showTitlebarAppName: true,
|
|
showTasksButton: true,
|
|
floatingTerminalEnabled: false,
|
|
floatingTerminalCwd: '~',
|
|
floatingTerminalTriggerLocation: 'floating-button',
|
|
diffDefaultView: 'inline',
|
|
combinedDiffFileTreeVisibleByDefault: false,
|
|
notifications: {
|
|
enabled: true,
|
|
agentTaskComplete: true,
|
|
terminalBell: false,
|
|
suppressWhenFocused: true,
|
|
customSoundPath: null,
|
|
customSoundVolume: 100
|
|
},
|
|
promptCacheTimerEnabled: false,
|
|
promptCacheTtlMs: 300_000,
|
|
codexManagedAccounts: [],
|
|
activeCodexManagedAccountId: null,
|
|
claudeManagedAccounts: [],
|
|
activeClaudeManagedAccountId: null,
|
|
terminalScopeHistoryByWorktree: true,
|
|
defaultTuiAgent: null,
|
|
skipDeleteWorktreeConfirm: false,
|
|
skipDeleteAutomationConfirm: false,
|
|
defaultTaskViewPreset: 'all',
|
|
defaultTaskSource: 'github',
|
|
visibleTaskProviders: ['github', 'gitlab', 'linear'],
|
|
defaultRepoSelection: null,
|
|
defaultLinearTeamSelection: null,
|
|
opencodeSessionCookie: '',
|
|
opencodeWorkspaceId: '',
|
|
geminiCliOAuthEnabled: false,
|
|
agentCmdOverrides: {},
|
|
keepComputerAwakeWhileAgentsRun: false,
|
|
terminalMacOptionAsAlt: 'false',
|
|
terminalMacOptionAsAltMigrated: true,
|
|
experimentalMobile: false,
|
|
mobileAutoRestoreFitMs: null,
|
|
experimentalPet: false,
|
|
experimentalActivity: true,
|
|
experimentalWorktreeSymlinks: false,
|
|
terminalWindowsShell: 'powershell.exe',
|
|
terminalWindowsPowerShellImplementation: 'powershell.exe',
|
|
enableGitHubAttribution: true,
|
|
...overrides,
|
|
appFontFamily
|
|
}
|
|
}
|
|
|
|
function createStore(settings: GlobalSettings) {
|
|
return {
|
|
getSettings: vi.fn(() => settings),
|
|
updateSettings: vi.fn((updates: Partial<GlobalSettings>) => {
|
|
settings = {
|
|
...settings,
|
|
...updates,
|
|
notifications: {
|
|
...settings.notifications,
|
|
...updates.notifications
|
|
}
|
|
}
|
|
return settings
|
|
})
|
|
}
|
|
}
|
|
|
|
function createManagedAuth(rootDir: string, accountId: string, auth: string): string {
|
|
const managedHomePath = join(rootDir, 'codex-accounts', accountId, 'home')
|
|
mkdirSync(managedHomePath, { recursive: true })
|
|
writeFileSync(join(managedHomePath, '.orca-managed-home'), `${accountId}\n`, 'utf-8')
|
|
writeFileSync(join(managedHomePath, 'auth.json'), auth, 'utf-8')
|
|
return managedHomePath
|
|
}
|
|
|
|
function encodeJwtPart(value: Record<string, unknown>): string {
|
|
return Buffer.from(JSON.stringify(value)).toString('base64url')
|
|
}
|
|
|
|
function createCodexAuthJson(
|
|
email: string,
|
|
accountId: string,
|
|
refreshToken: string,
|
|
expiresAt?: number
|
|
): string {
|
|
const idToken = [
|
|
encodeJwtPart({ alg: 'none', typ: 'JWT' }),
|
|
encodeJwtPart({
|
|
email,
|
|
...(expiresAt === undefined ? {} : { exp: expiresAt }),
|
|
'https://api.openai.com/auth': {
|
|
chatgpt_account_id: accountId,
|
|
workspace_account_id: accountId
|
|
}
|
|
}),
|
|
''
|
|
].join('.')
|
|
|
|
return `${JSON.stringify({
|
|
tokens: {
|
|
id_token: idToken,
|
|
account_id: accountId,
|
|
...(expiresAt === undefined ? {} : { expires_at: expiresAt }),
|
|
refresh_token: refreshToken
|
|
}
|
|
})}\n`
|
|
}
|
|
|
|
describe('CodexRuntimeHomeService', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules()
|
|
vi.clearAllMocks()
|
|
testState.userDataDir = mkdtempSync(join(tmpdir(), 'orca-runtime-home-'))
|
|
testState.fakeHomeDir = mkdtempSync(join(tmpdir(), 'orca-codex-home-'))
|
|
mkdirSync(join(testState.fakeHomeDir, '.codex'), { recursive: true })
|
|
})
|
|
|
|
afterEach(() => {
|
|
rmSync(testState.userDataDir, { recursive: true, force: true })
|
|
rmSync(testState.fakeHomeDir, { recursive: true, force: true })
|
|
})
|
|
|
|
it('captures the existing ~/.codex auth as the system-default snapshot', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const store = createStore(createSettings())
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(
|
|
existsSync(join(testState.userDataDir, 'codex-runtime-home', 'system-default-auth.json'))
|
|
).toBe(true)
|
|
if (process.platform !== 'win32') {
|
|
expect(
|
|
statSync(join(testState.userDataDir, 'codex-runtime-home', 'system-default-auth.json'))
|
|
.mode & 0o777
|
|
).toBe(0o600)
|
|
}
|
|
})
|
|
|
|
it('materializes the active managed account auth into ~/.codex on startup', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const store = createStore(
|
|
createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"managed"}\n')
|
|
expect(
|
|
existsSync(join(testState.userDataDir, 'codex-runtime-home', 'system-default-auth.json'))
|
|
).toBe(false)
|
|
})
|
|
|
|
it('restores the system-default snapshot when no managed account is selected', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: null
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
settings.activeCodexManagedAccountId = 'account-1'
|
|
service.syncForCurrentSelection()
|
|
settings.activeCodexManagedAccountId = null
|
|
writeFileSync(runtimeAuthPath, '{"account":"managed"}\n', 'utf-8')
|
|
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"system"}\n')
|
|
})
|
|
|
|
it('removes runtime auth when restoring a no-login system default', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"managed"}\n')
|
|
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(existsSync(runtimeAuthPath)).toBe(false)
|
|
})
|
|
|
|
it('removes runtime auth when deselecting with a missing system-default snapshot', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
const managedAuth = createCodexAuthJson('user@example.com', 'acct-1', 'managed')
|
|
writeFileSync(runtimeAuthPath, managedAuth, 'utf-8')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', managedAuth)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(existsSync(runtimeAuthPath)).toBe(false)
|
|
})
|
|
|
|
it('removes runtime auth when deselecting with a corrupt system-default snapshot', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedAuth = createCodexAuthJson('user@example.com', 'acct-1', 'managed')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', managedAuth)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: null
|
|
})
|
|
const store = createStore(settings)
|
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
settings.activeCodexManagedAccountId = 'account-1'
|
|
service.syncForCurrentSelection()
|
|
|
|
const snapshotPath = join(
|
|
testState.userDataDir,
|
|
'codex-runtime-home',
|
|
'system-default-auth.json'
|
|
)
|
|
writeFileSync(snapshotPath, '{not valid json', 'utf-8')
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(existsSync(runtimeAuthPath)).toBe(false)
|
|
expect(existsSync(snapshotPath)).toBe(false)
|
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
'[codex-runtime-home] Ignoring invalid system-default auth snapshot'
|
|
)
|
|
})
|
|
|
|
it('clears an invalid active account selection and removes untrusted runtime auth', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const missingManagedHomePath = join(
|
|
testState.userDataDir,
|
|
'codex-accounts',
|
|
'account-1',
|
|
'home'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath: missingManagedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(store.updateSettings).toHaveBeenCalledWith({ activeCodexManagedAccountId: null })
|
|
expect(existsSync(runtimeAuthPath)).toBe(false)
|
|
expect(warnSpy).toHaveBeenCalled()
|
|
})
|
|
|
|
it('clears an unknown active account id and removes untrusted runtime auth', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"stale-managed"}\n', 'utf-8')
|
|
const settings = createSettings({
|
|
activeCodexManagedAccountId: 'missing-account'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(store.updateSettings).toHaveBeenCalledWith({ activeCodexManagedAccountId: null })
|
|
expect(existsSync(runtimeAuthPath)).toBe(false)
|
|
})
|
|
|
|
it('returns ~/.codex for Codex launch and rate-limit preparation', async () => {
|
|
const store = createStore(createSettings())
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(service.prepareForCodexLaunch()).toBe(join(testState.fakeHomeDir, '.codex'))
|
|
expect(service.prepareForRateLimitFetch()).toBe(join(testState.fakeHomeDir, '.codex'))
|
|
expect(existsSync(join(testState.fakeHomeDir, '.codex'))).toBe(true)
|
|
})
|
|
|
|
it('does not overwrite auth.json when no managed account was ever active', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"original"}\n', 'utf-8')
|
|
const store = createStore(createSettings())
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, '{"account":"external-switch"}\n', 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"external-switch"}\n')
|
|
})
|
|
|
|
it('does not overwrite auth.json after deselection + external change', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
settings.activeCodexManagedAccountId = 'account-1'
|
|
service.syncForCurrentSelection()
|
|
|
|
// Deselect managed account — should restore system default once
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"system"}\n')
|
|
|
|
// External tool changes auth — subsequent syncs must not overwrite
|
|
writeFileSync(runtimeAuthPath, '{"account":"external-tool"}\n', 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"external-tool"}\n')
|
|
})
|
|
|
|
it('removes untrusted runtime auth on restart when persisted active account is invalid', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath: join(testState.userDataDir, 'codex-accounts', 'account-1', 'home'),
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(store.updateSettings).toHaveBeenCalledWith({ activeCodexManagedAccountId: null })
|
|
expect(existsSync(runtimeAuthPath)).toBe(false)
|
|
expect(warnSpy).toHaveBeenCalled()
|
|
})
|
|
|
|
it('imports legacy managed-home history into the shared runtime history', async () => {
|
|
const runtimeHomePath = join(testState.fakeHomeDir, '.codex')
|
|
const runtimeHistoryPath = join(runtimeHomePath, 'history.jsonl')
|
|
writeFileSync(runtimeHistoryPath, '{"id":"shared-1"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
writeFileSync(
|
|
join(managedHomePath, 'history.jsonl'),
|
|
'{"id":"shared-1"}\n{"id":"managed-2"}\n',
|
|
'utf-8'
|
|
)
|
|
const store = createStore(createSettings())
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(runtimeHistoryPath, 'utf-8')).toBe(
|
|
'{"id":"shared-1"}\n{"id":"managed-2"}\n'
|
|
)
|
|
expect(existsSync(join(testState.userDataDir, 'codex-runtime-home', 'migration-v1.json'))).toBe(
|
|
true
|
|
)
|
|
})
|
|
|
|
it('writes auth.json with restrictive permissions', async () => {
|
|
if (process.platform === 'win32') {
|
|
return
|
|
}
|
|
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const store = createStore(
|
|
createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
const mode = statSync(runtimeAuthPath).mode & 0o777
|
|
expect(mode).toBe(0o600)
|
|
})
|
|
|
|
it('tightens auth.json permissions when unchanged content is already present', async () => {
|
|
if (process.platform === 'win32') {
|
|
return
|
|
}
|
|
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
chmodSync(runtimeAuthPath, 0o644)
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(statSync(runtimeAuthPath).mode & 0o777).toBe(0o600)
|
|
})
|
|
|
|
it('does not throw when syncForCurrentSelection encounters an error', async () => {
|
|
const store = createStore(
|
|
createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath: '/nonexistent/path/home',
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
)
|
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
expect(() => new CodexRuntimeHomeService(store as never)).not.toThrow()
|
|
expect(warnSpy).toHaveBeenCalled()
|
|
})
|
|
|
|
it('does not re-run migration when marker already exists', async () => {
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
writeFileSync(join(managedHomePath, 'history.jsonl'), '{"id":"legacy-1"}\n', 'utf-8')
|
|
const store = createStore(createSettings())
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
const runtimeHistoryPath = join(testState.fakeHomeDir, '.codex', 'history.jsonl')
|
|
expect(readFileSync(runtimeHistoryPath, 'utf-8')).toContain('legacy-1')
|
|
|
|
writeFileSync(
|
|
join(managedHomePath, 'history.jsonl'),
|
|
'{"id":"legacy-1"}\n{"id":"legacy-2"}\n',
|
|
'utf-8'
|
|
)
|
|
|
|
vi.resetModules()
|
|
const mod2 = await import('./runtime-home-service')
|
|
new mod2.CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(runtimeHistoryPath, 'utf-8')).not.toContain('legacy-2')
|
|
})
|
|
|
|
it('clears system-default snapshot via clearSystemDefaultSnapshot', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const store = createStore(createSettings())
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
const snapshotPath = join(
|
|
testState.userDataDir,
|
|
'codex-runtime-home',
|
|
'system-default-auth.json'
|
|
)
|
|
expect(existsSync(snapshotPath)).toBe(true)
|
|
|
|
service.clearSystemDefaultSnapshot()
|
|
expect(existsSync(snapshotPath)).toBe(false)
|
|
})
|
|
|
|
it('reads back CLI-refreshed tokens into managed storage on subsequent sync', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const originalAuth = createCodexAuthJson('user@example.com', 'acct-1', 'original')
|
|
const refreshedAuth = createCodexAuthJson('user@example.com', 'acct-1', 'refreshed')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', originalAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
// Simulate CLI refreshing the token in ~/.codex/auth.json
|
|
writeFileSync(runtimeAuthPath, refreshedAuth, 'utf-8')
|
|
|
|
// Next sync should read back the refreshed token to managed storage
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
})
|
|
|
|
it('rejects runtime read-back from a different Codex identity', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const selectedAuth = createCodexAuthJson('selected@example.com', 'acct-selected', 'selected')
|
|
const staleLivePtyAuth = createCodexAuthJson('stale@example.com', 'acct-stale', 'stale')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', selectedAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'selected@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-selected',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-selected',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
// Simulate an old live Codex PTY from another account refreshing the
|
|
// shared runtime auth after Orca has already selected account-1.
|
|
writeFileSync(runtimeAuthPath, staleLivePtyAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(selectedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(selectedAuth)
|
|
})
|
|
|
|
it('routes runtime read-back from a different Codex identity to its matching account', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const account1Auth = createCodexAuthJson('one@example.com', 'acct-one', 'one')
|
|
const account1RefreshedAuth = createCodexAuthJson(
|
|
'one@example.com',
|
|
'acct-one',
|
|
'one-refreshed'
|
|
)
|
|
const account2Auth = createCodexAuthJson('two@example.com', 'acct-two', 'two')
|
|
const managedHomePath1 = createManagedAuth(testState.userDataDir, 'account-1', account1Auth)
|
|
const managedHomePath2 = createManagedAuth(testState.userDataDir, 'account-2', account2Auth)
|
|
const managedAuthPath1 = join(managedHomePath1, 'auth.json')
|
|
const managedAuthPath2 = join(managedHomePath2, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'one@example.com',
|
|
managedHomePath: managedHomePath1,
|
|
providerAccountId: 'acct-one',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-one',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
},
|
|
{
|
|
id: 'account-2',
|
|
email: 'two@example.com',
|
|
managedHomePath: managedHomePath2,
|
|
providerAccountId: 'acct-two',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-two',
|
|
createdAt: 2,
|
|
updatedAt: 2,
|
|
lastAuthenticatedAt: 2
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-2'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
// An older account-1 Codex process refreshed the shared runtime file after
|
|
// Orca selected account-2. Persist the refresh to account-1, then restore
|
|
// the selected account in ~/.codex.
|
|
writeFileSync(runtimeAuthPath, account1RefreshedAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath1, 'utf-8')).toBe(account1RefreshedAuth)
|
|
expect(readFileSync(managedAuthPath2, 'utf-8')).toBe(account2Auth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(account2Auth)
|
|
})
|
|
|
|
it('rejects ambiguous Codex read-back instead of choosing a managed account', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const originalAuth = createCodexAuthJson('same@example.com', 'acct-same', 'original')
|
|
const refreshedAuth = createCodexAuthJson('same@example.com', 'acct-same', 'refreshed')
|
|
const activeAuth = createCodexAuthJson('active@example.com', 'acct-active', 'active')
|
|
const managedHomePath1 = createManagedAuth(testState.userDataDir, 'account-1', originalAuth)
|
|
const managedHomePath2 = createManagedAuth(testState.userDataDir, 'account-2', originalAuth)
|
|
const managedHomePath3 = createManagedAuth(testState.userDataDir, 'account-3', activeAuth)
|
|
const managedAuthPath1 = join(managedHomePath1, 'auth.json')
|
|
const managedAuthPath2 = join(managedHomePath2, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'same@example.com',
|
|
managedHomePath: managedHomePath1,
|
|
providerAccountId: 'acct-same',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-same',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
},
|
|
{
|
|
id: 'account-2',
|
|
email: 'same@example.com',
|
|
managedHomePath: managedHomePath2,
|
|
providerAccountId: 'acct-same',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-same',
|
|
createdAt: 2,
|
|
updatedAt: 2,
|
|
lastAuthenticatedAt: 2
|
|
},
|
|
{
|
|
id: 'account-3',
|
|
email: 'active@example.com',
|
|
managedHomePath: managedHomePath3,
|
|
providerAccountId: 'acct-active',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-active',
|
|
createdAt: 3,
|
|
updatedAt: 3,
|
|
lastAuthenticatedAt: 3
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-3'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, refreshedAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath1, 'utf-8')).toBe(originalAuth)
|
|
expect(readFileSync(managedAuthPath2, 'utf-8')).toBe(originalAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(activeAuth)
|
|
})
|
|
|
|
it('rejects runtime read-back without a positive selected-account identity match', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const selectedAuth = createCodexAuthJson('selected@example.com', 'acct-selected', 'selected')
|
|
const accountOnlyAuth = `${JSON.stringify({
|
|
tokens: {
|
|
account_id: 'acct-stale',
|
|
refresh_token: 'stale'
|
|
}
|
|
})}\n`
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', selectedAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'selected@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, accountOnlyAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(selectedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(selectedAuth)
|
|
})
|
|
|
|
it('rejects same-email runtime read-back when account ids differ from sparse managed metadata', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const selectedAuth = createCodexAuthJson('user@example.com', 'acct-selected', 'selected')
|
|
const staleLivePtyAuth = createCodexAuthJson('user@example.com', 'acct-stale', 'stale')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', selectedAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, staleLivePtyAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(selectedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(selectedAuth)
|
|
})
|
|
|
|
it('reads back same-account refreshes for sparse managed metadata using stored auth identity', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const originalAuth = createCodexAuthJson('user@example.com', 'acct-selected', 'original')
|
|
const refreshedAuth = createCodexAuthJson('user@example.com', 'acct-selected', 'refreshed')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', originalAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, refreshedAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
})
|
|
|
|
it('reads back strong account-id refreshes when the runtime auth has no email', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const originalAuth = createCodexAuthJson('user@example.com', 'acct-1', 'original')
|
|
const refreshedAuth = `${JSON.stringify({
|
|
tokens: {
|
|
account_id: 'acct-1',
|
|
refresh_token: 'refreshed'
|
|
}
|
|
})}\n`
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', originalAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, refreshedAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
})
|
|
|
|
it('rejects unverifiable Codex read-back on first sync after restart', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"tokens":"refreshed-while-down"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"tokens":"original"}\n'
|
|
)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const store = createStore(
|
|
createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe('{"tokens":"original"}\n')
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"tokens":"original"}\n')
|
|
})
|
|
|
|
it('reads back verified same-account refreshes on first sync after restart', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
const originalAuth = createCodexAuthJson('user@example.com', 'acct-1', 'original', 1_000)
|
|
const refreshedAuth = createCodexAuthJson('user@example.com', 'acct-1', 'refreshed', 2_000)
|
|
writeFileSync(runtimeAuthPath, refreshedAuth, 'utf-8')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', originalAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const store = createStore(
|
|
createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(refreshedAuth)
|
|
})
|
|
|
|
it('rejects older same-account Codex auth on first sync after restart', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
const staleRuntimeAuth = createCodexAuthJson('user@example.com', 'acct-1', 'stale', 1_000)
|
|
const managedAuth = createCodexAuthJson('user@example.com', 'acct-1', 'managed-newer', 2_000)
|
|
writeFileSync(runtimeAuthPath, staleRuntimeAuth, 'utf-8')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', managedAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const store = createStore(
|
|
createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(managedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(managedAuth)
|
|
})
|
|
|
|
it('does not contaminate the incoming Codex account during account switch', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedHomePath1 = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"tokens":"account1"}\n'
|
|
)
|
|
const managedHomePath2 = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-2',
|
|
'{"tokens":"account2"}\n'
|
|
)
|
|
const managedAuthPath2 = join(managedHomePath2, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user1@example.com',
|
|
managedHomePath: managedHomePath1,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
},
|
|
{
|
|
id: 'account-2',
|
|
email: 'user2@example.com',
|
|
managedHomePath: managedHomePath2,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
settings.activeCodexManagedAccountId = 'account-2'
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath2, 'utf-8')).toBe('{"tokens":"account2"}\n')
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"tokens":"account2"}\n')
|
|
})
|
|
|
|
it('does not carry the reauth read-back skip across Codex account switches', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const account1Auth = createCodexAuthJson('one@example.com', 'acct-one', 'one')
|
|
const account2Auth = createCodexAuthJson('two@example.com', 'acct-two', 'two')
|
|
const account2RefreshedAuth = createCodexAuthJson(
|
|
'two@example.com',
|
|
'acct-two',
|
|
'two-refreshed'
|
|
)
|
|
const managedHomePath1 = createManagedAuth(testState.userDataDir, 'account-1', account1Auth)
|
|
const managedHomePath2 = createManagedAuth(testState.userDataDir, 'account-2', account2Auth)
|
|
const managedAuthPath2 = join(managedHomePath2, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'one@example.com',
|
|
managedHomePath: managedHomePath1,
|
|
providerAccountId: 'acct-one',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-one',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
},
|
|
{
|
|
id: 'account-2',
|
|
email: 'two@example.com',
|
|
managedHomePath: managedHomePath2,
|
|
providerAccountId: 'acct-two',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-two',
|
|
createdAt: 2,
|
|
updatedAt: 2,
|
|
lastAuthenticatedAt: 2
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
service.clearLastWrittenAuthJson()
|
|
settings.activeCodexManagedAccountId = 'account-2'
|
|
service.syncForCurrentSelection()
|
|
|
|
writeFileSync(runtimeAuthPath, account2RefreshedAuth, 'utf-8')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath2, 'utf-8')).toBe(account2RefreshedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(account2RefreshedAuth)
|
|
})
|
|
|
|
it('does not apply inactive-account Codex reauth skip to the active account', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const account1Auth = createCodexAuthJson('one@example.com', 'acct-one', 'one')
|
|
const account1RefreshedAuth = createCodexAuthJson(
|
|
'one@example.com',
|
|
'acct-one',
|
|
'one-refreshed'
|
|
)
|
|
const account2Auth = createCodexAuthJson('two@example.com', 'acct-two', 'two')
|
|
const managedHomePath1 = createManagedAuth(testState.userDataDir, 'account-1', account1Auth)
|
|
const managedHomePath2 = createManagedAuth(testState.userDataDir, 'account-2', account2Auth)
|
|
const managedAuthPath1 = join(managedHomePath1, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'one@example.com',
|
|
managedHomePath: managedHomePath1,
|
|
providerAccountId: 'acct-one',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-one',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
},
|
|
{
|
|
id: 'account-2',
|
|
email: 'two@example.com',
|
|
managedHomePath: managedHomePath2,
|
|
providerAccountId: 'acct-two',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-two',
|
|
createdAt: 2,
|
|
updatedAt: 2,
|
|
lastAuthenticatedAt: 2
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, account1RefreshedAuth, 'utf-8')
|
|
service.clearLastWrittenAuthJson('account-2')
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath1, 'utf-8')).toBe(account1RefreshedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(account1RefreshedAuth)
|
|
})
|
|
|
|
it('restores system default when unverified runtime auth appears before deselect', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
settings.activeCodexManagedAccountId = 'account-1'
|
|
service.syncForCurrentSelection()
|
|
|
|
// A stale or external process overwrites runtime with auth Orca cannot
|
|
// verify against the outgoing managed account.
|
|
writeFileSync(runtimeAuthPath, '{"account":"external-login"}\n', 'utf-8')
|
|
|
|
// Deselect managed account
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"system"}\n')
|
|
expect(
|
|
existsSync(join(testState.userDataDir, 'codex-runtime-home', 'system-default-auth.json'))
|
|
).toBe(true)
|
|
})
|
|
|
|
it('restores system default after same-identity managed Codex refresh on deselect', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system-old"}\n', 'utf-8')
|
|
const managedAuth = createCodexAuthJson('user@example.com', 'acct-1', 'managed')
|
|
const externalAuth = createCodexAuthJson('user@example.com', 'acct-1', 'external')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', managedAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
settings.activeCodexManagedAccountId = 'account-1'
|
|
service.syncForCurrentSelection()
|
|
|
|
writeFileSync(runtimeAuthPath, externalAuth, 'utf-8')
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(externalAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"system-old"}\n')
|
|
})
|
|
|
|
it('restores system default when stale Codex credentials are rejected on deselect', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system-old"}\n', 'utf-8')
|
|
const selectedAuth = createCodexAuthJson('selected@example.com', 'acct-selected', 'selected')
|
|
const staleLivePtyAuth = createCodexAuthJson('stale@example.com', 'acct-stale', 'stale')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', selectedAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'selected@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-selected',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-selected',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
settings.activeCodexManagedAccountId = 'account-1'
|
|
service.syncForCurrentSelection()
|
|
|
|
writeFileSync(runtimeAuthPath, staleLivePtyAuth, 'utf-8')
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(selectedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"system-old"}\n')
|
|
})
|
|
|
|
it('keeps external Codex logout when deselecting managed account', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system-old"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
rmSync(runtimeAuthPath, { force: true })
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(existsSync(runtimeAuthPath)).toBe(false)
|
|
})
|
|
|
|
it('captures a fresh system-default snapshot when re-entering managed mode', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system-1"}\n', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: null,
|
|
workspaceLabel: null,
|
|
workspaceAccountId: null,
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
writeFileSync(runtimeAuthPath, '{"account":"system-2"}\n', 'utf-8')
|
|
|
|
settings.activeCodexManagedAccountId = 'account-1'
|
|
service.syncForCurrentSelection()
|
|
settings.activeCodexManagedAccountId = null
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe('{"account":"system-2"}\n')
|
|
})
|
|
|
|
it('reads back refreshed tokens for the outgoing Codex account before switching', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const account1Original = createCodexAuthJson('one@example.com', 'acct-1', 'one-original')
|
|
const account1Refreshed = createCodexAuthJson('one@example.com', 'acct-1', 'one-refreshed')
|
|
const account2Auth = createCodexAuthJson('two@example.com', 'acct-2', 'two')
|
|
const managedHomePath1 = createManagedAuth(testState.userDataDir, 'account-1', account1Original)
|
|
const managedHomePath2 = createManagedAuth(testState.userDataDir, 'account-2', account2Auth)
|
|
const managedAuthPath1 = join(managedHomePath1, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'one@example.com',
|
|
managedHomePath: managedHomePath1,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
},
|
|
{
|
|
id: 'account-2',
|
|
email: 'two@example.com',
|
|
managedHomePath: managedHomePath2,
|
|
providerAccountId: 'acct-2',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-2',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
writeFileSync(runtimeAuthPath, account1Refreshed, 'utf-8')
|
|
settings.activeCodexManagedAccountId = 'account-2'
|
|
service.syncForCurrentSelection()
|
|
|
|
expect(readFileSync(managedAuthPath1, 'utf-8')).toBe(account1Refreshed)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(account2Auth)
|
|
})
|
|
|
|
it('does not clobber fresh tokens after clearLastWrittenAuthJson', async () => {
|
|
const runtimeAuthPath = join(testState.fakeHomeDir, '.codex', 'auth.json')
|
|
writeFileSync(runtimeAuthPath, '{"account":"system"}\n', 'utf-8')
|
|
const originalAuth = createCodexAuthJson('user@example.com', 'acct-1', 'original')
|
|
const reauthedAuth = createCodexAuthJson('user@example.com', 'acct-1', 'reauthed')
|
|
const managedHomePath = createManagedAuth(testState.userDataDir, 'account-1', originalAuth)
|
|
const managedAuthPath = join(managedHomePath, 'auth.json')
|
|
const settings = createSettings({
|
|
codexManagedAccounts: [
|
|
{
|
|
id: 'account-1',
|
|
email: 'user@example.com',
|
|
managedHomePath,
|
|
providerAccountId: 'acct-1',
|
|
workspaceLabel: null,
|
|
workspaceAccountId: 'acct-1',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
lastAuthenticatedAt: 1
|
|
}
|
|
],
|
|
activeCodexManagedAccountId: 'account-1'
|
|
})
|
|
const store = createStore(settings)
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
const service = new CodexRuntimeHomeService(store as never)
|
|
|
|
// Simulate re-auth: managed storage gets fresh tokens
|
|
writeFileSync(managedAuthPath, reauthedAuth, 'utf-8')
|
|
|
|
// Clear tracking before sync (as CodexAccountService would)
|
|
service.clearLastWrittenAuthJson()
|
|
service.syncForCurrentSelection()
|
|
|
|
// Fresh re-auth tokens should survive — not be clobbered by stale runtime read-back
|
|
expect(readFileSync(managedAuthPath, 'utf-8')).toBe(reauthedAuth)
|
|
expect(readFileSync(runtimeAuthPath, 'utf-8')).toBe(reauthedAuth)
|
|
})
|
|
|
|
it('preserves conflicting legacy session files under deterministic names', async () => {
|
|
const runtimeSessionsDir = join(testState.fakeHomeDir, '.codex', 'sessions')
|
|
mkdirSync(runtimeSessionsDir, { recursive: true })
|
|
writeFileSync(join(runtimeSessionsDir, 'session.json'), '{"turns":[1]}', 'utf-8')
|
|
const managedHomePath = createManagedAuth(
|
|
testState.userDataDir,
|
|
'account-1',
|
|
'{"account":"managed"}\n'
|
|
)
|
|
const legacySessionsDir = join(managedHomePath, 'sessions')
|
|
mkdirSync(legacySessionsDir, { recursive: true })
|
|
writeFileSync(join(legacySessionsDir, 'session.json'), '{"turns":[1,2]}', 'utf-8')
|
|
const store = createStore(createSettings())
|
|
|
|
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
|
new CodexRuntimeHomeService(store as never)
|
|
|
|
expect(readFileSync(join(runtimeSessionsDir, 'session.json'), 'utf-8')).toBe('{"turns":[1]}')
|
|
expect(
|
|
readFileSync(join(runtimeSessionsDir, 'session.orca-legacy-account-1.json'), 'utf-8')
|
|
).toBe('{"turns":[1,2]}')
|
|
expect(
|
|
readFileSync(
|
|
join(testState.userDataDir, 'codex-runtime-home', 'migration-diagnostics.jsonl'),
|
|
'utf-8'
|
|
)
|
|
).toContain('"type":"session-conflict"')
|
|
})
|
|
})
|