Files
orca/src/main/persistence-worktree-visibility.test.ts
T
OrcaWinandm4air 8416e8de10 refactor(persistence): retire ordinary JSON profile writes (#23202)
* refactor(persistence): retire ordinary JSON profile writes

Require SQLite for writable profiles and keep import, compatibility export, and recovery in a documented legacy-json boundary.

* fix(cli): preserve dynamic profile imports in release output

* test(persistence): exercise SQL races and verify packaged CLI imports

* test(persistence): consolidate shared fixture imports

* test(persistence): close SQLite fixtures before cleanup and await launcher output

* test(automations): use SQLite fixtures for dispatch fencing and skip coalescing

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-09-26 12:56:32 -07:00

392 lines
12 KiB
TypeScript

import {
closeTestStores,
testState,
createStore,
writeDataFile,
makeRepo
} from './persistence-test-harness'
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { rmSync, mkdtempSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import type { GlobalSettings } from '../shared/global-settings-types'
import type { ExternalWorktreeVisibility } from '../shared/repo-types'
import { getDefaultPersistedState } from '../shared/constants'
// Stub the ~/.ssh/config parser so the SSH-import test drives the real Store with deterministic hosts, not the operator's actual ~/.ssh/config.
const { loadUserSshConfigMock, sshConfigHostsToTargetsMock } = vi.hoisted(() => ({
loadUserSshConfigMock: vi.fn(),
sshConfigHostsToTargetsMock: vi.fn()
}))
vi.mock('./ssh/ssh-config-parser', () => ({
loadUserSshConfig: loadUserSshConfigMock,
sshConfigHostsToTargets: sshConfigHostsToTargetsMock
}))
const { trackMock, getCohortAtEmitMock } = vi.hoisted(() => ({
trackMock: vi.fn(),
getCohortAtEmitMock: vi.fn()
}))
vi.mock('electron', () => ({
app: {
getPath: () => testState.dir
},
safeStorage: {
isEncryptionAvailable: () => true,
encryptString: (plaintext: string) => Buffer.from(`encrypted:${plaintext}`, 'utf-8'),
decryptString: (ciphertext: Buffer) => {
const decoded = ciphertext.toString('utf-8')
if (!decoded.startsWith('encrypted:')) {
throw new Error('invalid ciphertext')
}
return decoded.slice('encrypted:'.length)
}
}
}))
vi.mock('./telemetry/client', () => ({
track: trackMock
}))
vi.mock('./telemetry/cohort-classifier', () => ({
getCohortAtEmit: getCohortAtEmitMock
}))
describe('Store', () => {
beforeEach(() => {
testState.dir = mkdtempSync(join(tmpdir(), 'orca-test-'))
trackMock.mockReset()
getCohortAtEmitMock.mockReset()
getCohortAtEmitMock.mockReturnValue({ nth_repo_added: 2 })
})
afterEach(async () => {
await closeTestStores()
rmSync(testState.dir, { recursive: true, force: true })
})
it('updateRepo stamps legacy external-worktree visibility before changing old repos', async () => {
const store = await createStore()
store.addRepo(
makeRepo({
addedAt: Date.UTC(2026, 4, 24),
externalWorktreeVisibility: undefined,
externalWorktreeVisibilityLegacy: undefined
})
)
const updated = store.updateRepo('r1', { externalWorktreeVisibility: 'hide' })
expect(updated!.externalWorktreeVisibility).toBe('hide')
expect(updated!.externalWorktreeVisibilityLegacy).toBe(true)
})
it('migrates implicit legacy visibility to an explicit override before defaulting global hide', async () => {
const persisted = getDefaultPersistedState(testState.dir)
delete persisted.settings.worktreeVisibilityDefaults
persisted.repos = [
makeRepo({
id: 'legacy',
kind: 'git',
externalWorktreeVisibility: undefined,
externalWorktreeVisibilityLegacy: undefined
}),
makeRepo({ id: 'explicit', externalWorktreeVisibility: 'hide' }),
makeRepo({
id: 'inherited',
externalWorktreeVisibility: undefined,
externalWorktreeVisibilityLegacy: false
})
]
writeDataFile(persisted)
const store = await createStore()
expect(store.getSettings().worktreeVisibilityDefaults).toEqual({ external: 'hide' })
expect(store.getRepo('legacy')).toMatchObject({
externalWorktreeVisibility: 'show',
externalWorktreeVisibilityLegacy: true
})
expect(store.getRepo('explicit')?.externalWorktreeVisibility).toBe('hide')
expect(store.getRepo('inherited')?.externalWorktreeVisibility).toBeUndefined()
store.flush()
const reloaded = await createStore()
expect(reloaded.getRepo('legacy')).toMatchObject({
externalWorktreeVisibility: 'show',
externalWorktreeVisibilityLegacy: true
})
expect(reloaded.getRepo('inherited')?.externalWorktreeVisibility).toBeUndefined()
})
it('merges visibility-default patches so future source defaults survive older controls', async () => {
const store = await createStore()
store.updateSettings({
worktreeVisibilityDefaults: {
external: 'show',
futureSource: 'hide'
} as GlobalSettings['worktreeVisibilityDefaults']
})
store.updateSettings({ worktreeVisibilityDefaults: { external: 'hide' } })
expect(store.getSettings().worktreeVisibilityDefaults).toEqual({
external: 'hide',
futureSource: 'hide'
})
})
it('normalizes and persists global worktree source defaults', async () => {
const store = await createStore()
store.updateSettings({
worktreeVisibilityDefaults: {
external: 'show',
customSources: [
{ id: 'team', rootPath: ' /srv/team-worktrees ' },
{ id: 'invalid', rootPath: '../relative' }
],
sourcePreferences: {
builtIn: { claude: 'show', gsd: 'hide' },
custom: { team: 'show' }
}
}
})
expect(store.getSettings().worktreeVisibilityDefaults).toEqual({
external: 'show',
customSources: [{ id: 'team', rootPath: '/srv/team-worktrees' }],
sourcePreferences: {
builtIn: { claude: 'show', gsd: 'hide' },
custom: { team: 'show' }
}
})
store.flush()
const reloaded = await createStore()
expect(reloaded.getSettings().worktreeVisibilityDefaults).toEqual(
store.getSettings().worktreeVisibilityDefaults
)
})
it('persists agent worktree visibility independently from external visibility', async () => {
const store = await createStore()
store.addRepo(makeRepo({ externalWorktreeVisibility: 'hide' }))
const updated = store.updateRepo('r1', { agentWorktreeVisibility: 'show' })
expect(updated).toMatchObject({
externalWorktreeVisibility: 'hide',
agentWorktreeVisibility: 'show',
worktreeVisibilitySourcePreferences: {
builtIn: { claude: 'show', gsd: 'show' }
}
})
store.flush()
const reloaded = await createStore()
expect(reloaded.getRepo('r1')).toMatchObject({
externalWorktreeVisibility: 'hide',
agentWorktreeVisibility: 'show',
worktreeVisibilitySourcePreferences: {
builtIn: { claude: 'show', gsd: 'show' }
}
})
})
it('persists bounded custom worktree sources separately from their preferences', async () => {
const store = await createStore()
store.addRepo(makeRepo())
const updated = store.updateRepo('r1', {
customWorktreeVisibilitySources: [
{ id: 'team', rootPath: ' /srv/team-worktrees ' },
{ id: 'invalid', rootPath: '../relative' }
],
worktreeVisibilitySourcePreferences: {
builtIn: { claude: 'show', gsd: 'hide' },
custom: { team: 'show' }
}
})
expect(updated).toMatchObject({
customWorktreeVisibilitySources: [{ id: 'team', rootPath: '/srv/team-worktrees' }],
worktreeVisibilitySourcePreferences: {
builtIn: { claude: 'show', gsd: 'hide' },
custom: { team: 'show' }
}
})
store.flush()
const reloaded = await createStore()
expect(reloaded.getRepo('r1')?.customWorktreeVisibilitySources).toEqual([
{ id: 'team', rootPath: '/srv/team-worktrees' }
])
})
it('clears an explicit external-worktree override without stamping an inherited default', async () => {
const store = await createStore()
store.addRepo(
makeRepo({
externalWorktreeVisibility: 'show',
externalWorktreeVisibilityLegacy: true
})
)
const updated = store.updateRepo('r1', { externalWorktreeVisibility: null })
expect(updated?.externalWorktreeVisibility).toBeUndefined()
expect(updated?.externalWorktreeVisibilityLegacy).toBe(false)
store.flush()
const reloaded = await createStore()
expect(reloaded.getRepo('r1')?.externalWorktreeVisibility).toBeUndefined()
expect(reloaded.getRepo('r1')?.externalWorktreeVisibilityLegacy).toBe(false)
})
it('sanitizes raw custom worktree visibility sources on the read path', async () => {
const persisted = getDefaultPersistedState(testState.dir)
persisted.repos = [
makeRepo({
id: 'r1',
customWorktreeVisibilitySources: [
{ id: 'team', rootPath: ' /srv/team-worktrees ' },
{ id: 'invalid', rootPath: '../relative' }
],
worktreeVisibilitySourcePreferences: {
builtIn: { claude: 'show', gsd: 'show' },
custom: { team: 'show', missing: 'bogus' as unknown as ExternalWorktreeVisibility }
}
})
]
writeDataFile(persisted)
const store = await createStore()
expect(store.getRepo('r1')).toMatchObject({
customWorktreeVisibilitySources: [{ id: 'team', rootPath: '/srv/team-worktrees' }],
worktreeVisibilitySourcePreferences: {
builtIn: { claude: 'show', gsd: 'show' },
custom: { team: 'show' }
}
})
})
it('updateRepo clears source-control AI overrides independently from other clearable fields', async () => {
const store = await createStore()
store.addRepo(
makeRepo({
issueSourcePreference: 'origin',
sourceControlAi: {
instructionsByOperation: { commitMessage: 'Repo style' },
prCreationDefaults: { draft: true }
}
})
)
store.updateRepo('r1', {
issueSourcePreference: undefined,
sourceControlAi: undefined
})
expect(store.getRepo('r1')!.issueSourcePreference).toBeUndefined()
expect(store.getRepo('r1')!.sourceControlAi).toBeUndefined()
store.flush()
const reloaded = await createStore()
expect(reloaded.getRepo('r1')!.issueSourcePreference).toBeUndefined()
expect(reloaded.getRepo('r1')!.sourceControlAi).toBeUndefined()
})
it('updateRepo treats source-control AI null as a transport clear sentinel', async () => {
const store = await createStore()
store.addRepo(
makeRepo({
sourceControlAi: {
enabled: true,
customAgentCommand: 'repo-agent {prompt}'
}
})
)
store.updateRepo('r1', {
sourceControlAi: null
})
expect(store.getRepo('r1')!.sourceControlAi).toBeUndefined()
store.flush()
const reloaded = await createStore()
expect(reloaded.getRepo('r1')!.sourceControlAi).toBeUndefined()
})
it('updateRepo normalizes source-control AI overrides before storing', async () => {
const store = await createStore()
store.addRepo(makeRepo())
const updated = store.updateRepo('r1', {
sourceControlAi: {
instructionsByOperation: {
commitMessage: 'Repo style',
pullRequest: 42,
unknown: 'ignored'
},
prCreationDefaults: {
draft: true,
useTemplate: null,
openAfterCreate: 'yes'
},
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: { codex: 'gpt-5.4', claude: false },
selectedThinkingByModel: { 'gpt-5.4': 'high', bad: true }
},
unknown: {
selectedModelByAgent: { codex: 'ignored' }
}
}
} as never
})
expect(updated!.sourceControlAi).toEqual({
instructionsByOperation: {
commitMessage: 'Repo style'
},
actionOverrides: {
commitMessage: {
commandInputTemplate: '{basePrompt}\n\nRepo style'
}
},
prCreationDefaults: {
draft: true,
useTemplate: null
},
modelOverridesByOperation: {
commitMessage: {
selectedModelByAgent: { codex: 'gpt-5.4' },
selectedThinkingByModel: { 'gpt-5.4': 'high' }
}
}
})
})
it('updateRepo ignores malformed source-control AI overrides without clearing existing overrides', async () => {
const store = await createStore()
store.addRepo(
makeRepo({
sourceControlAi: {
instructionsByOperation: { commitMessage: 'Keep me' }
}
})
)
const updated = store.updateRepo('r1', { sourceControlAi: 'bad' as never })
expect(updated!.sourceControlAi).toEqual({
instructionsByOperation: { commitMessage: 'Keep me' },
actionOverrides: {
commitMessage: {
commandInputTemplate: '{basePrompt}\n\nKeep me'
}
}
})
})
})