mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission auto-review source
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import type { AutoReviewCommandController } from '../src/command.js'
|
||||
import type { AutoReviewConfigFileSystem } from '../src/config-store.js'
|
||||
import type { LoadConfigResult } from '../src/config.js'
|
||||
import type { ExtensionAPI, ExtensionCommandContext, RegisteredCommand } from '@earendil-works/pi-coding-agent'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { registerAutoReviewCommand } from '../src/command.js'
|
||||
import { AutoReviewConfigStore } from '../src/config-store.js'
|
||||
|
||||
function createFileSystem(initial: Record<string, string> = {}) {
|
||||
const files = new Map(Object.entries(initial))
|
||||
const readFile = vi.fn((path: string) => files.get(path))
|
||||
const writeFile = vi.fn((path: string, source: string) => {
|
||||
files.set(path, source)
|
||||
})
|
||||
const rename = vi.fn((sourcePath: string, destinationPath: string) => {
|
||||
const source = files.get(sourcePath)
|
||||
if (source === undefined) {
|
||||
throw new Error(`missing ${sourcePath}`)
|
||||
}
|
||||
files.set(destinationPath, source)
|
||||
files.delete(sourcePath)
|
||||
})
|
||||
const mkdir = vi.fn((_path: string) => {})
|
||||
const unlink = vi.fn((path: string) => {
|
||||
files.delete(path)
|
||||
})
|
||||
const fileSystem: AutoReviewConfigFileSystem = {
|
||||
readFile,
|
||||
writeFile,
|
||||
rename,
|
||||
mkdir,
|
||||
unlink,
|
||||
}
|
||||
return { files, fileSystem }
|
||||
}
|
||||
|
||||
function createCommandHarness(initial: Record<string, string> = {}) {
|
||||
const { files, fileSystem } = createFileSystem(initial)
|
||||
const configStore = new AutoReviewConfigStore({ agentDir: '/agent', fileSystem })
|
||||
let activeConfig = configStore.load('/project').config
|
||||
const applyConfig = vi.fn((result: LoadConfigResult) => {
|
||||
activeConfig = result.config
|
||||
return { kind: 'active' as const }
|
||||
})
|
||||
const controller: AutoReviewCommandController = {
|
||||
configStore,
|
||||
getActiveConfig: () => activeConfig,
|
||||
applyConfig,
|
||||
}
|
||||
|
||||
let command: Omit<RegisteredCommand, 'name' | 'sourceInfo'> | undefined
|
||||
const registerCommand = vi.fn((_name: string, options: Omit<RegisteredCommand, 'name' | 'sourceInfo'>) => {
|
||||
command = options
|
||||
})
|
||||
const pi = {
|
||||
registerCommand,
|
||||
} as unknown as ExtensionAPI
|
||||
registerAutoReviewCommand(pi, controller)
|
||||
|
||||
const ui = {
|
||||
select: vi.fn(),
|
||||
input: vi.fn(),
|
||||
editor: vi.fn(),
|
||||
confirm: vi.fn(),
|
||||
notify: vi.fn(),
|
||||
}
|
||||
const reload = vi.fn()
|
||||
const waitForIdle = vi.fn(async () => {})
|
||||
const context = {
|
||||
cwd: '/project',
|
||||
mode: 'tui',
|
||||
hasUI: true,
|
||||
modelRegistry: {
|
||||
getAll: () => [],
|
||||
},
|
||||
ui,
|
||||
waitForIdle,
|
||||
reload,
|
||||
} as unknown as ExtensionCommandContext
|
||||
|
||||
return {
|
||||
activeConfig: () => activeConfig,
|
||||
applyConfig,
|
||||
command: () => {
|
||||
if (command === undefined) {
|
||||
throw new Error('command not registered')
|
||||
}
|
||||
return command
|
||||
},
|
||||
context,
|
||||
files,
|
||||
pi,
|
||||
registerCommand,
|
||||
reload,
|
||||
ui,
|
||||
waitForIdle,
|
||||
}
|
||||
}
|
||||
|
||||
const globalPath = '/agent/extensions/pi-permission-auto-review/config.json'
|
||||
const projectPath = '/project/.pi/extensions/pi-permission-auto-review/config.json'
|
||||
|
||||
describe('/permission-auto-review', () => {
|
||||
it('registers the command and completes subcommands and reset scopes', async () => {
|
||||
const harness = createCommandHarness()
|
||||
expect(harness.registerCommand).toHaveBeenCalledWith('permission-auto-review', expect.any(Object))
|
||||
|
||||
const command = harness.command()
|
||||
expect(await command.getArgumentCompletions?.('sh')).toEqual([expect.objectContaining({ value: 'show' })])
|
||||
expect(await command.getArgumentCompletions?.('reset p')).toEqual([
|
||||
expect.objectContaining({ value: 'reset project' }),
|
||||
])
|
||||
})
|
||||
|
||||
it('edits a staged global draft, saves it, and applies it without ctx.reload', async () => {
|
||||
const harness = createCommandHarness()
|
||||
let menuVisits = 0
|
||||
const menuOptions: string[][] = []
|
||||
harness.ui.select.mockImplementation(async (title: string, options: string[]) => {
|
||||
if (title === 'Select configuration scope') {
|
||||
return 'Global configuration'
|
||||
}
|
||||
if (title === 'Configure Provider') {
|
||||
return 'Enter custom value...'
|
||||
}
|
||||
if (title.startsWith('Permission auto-review settings')) {
|
||||
menuOptions.push(options)
|
||||
menuVisits += 1
|
||||
return menuVisits === 1 ? options.find(option => option.startsWith('Provider:')) : 'Save changes'
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
harness.ui.input.mockResolvedValue('review-proxy')
|
||||
|
||||
await harness.command().handler('', harness.context)
|
||||
|
||||
expect(harness.waitForIdle).toHaveBeenCalledOnce()
|
||||
expect(harness.applyConfig).toHaveBeenCalledOnce()
|
||||
expect(harness.activeConfig()).toMatchObject({ provider: 'review-proxy' })
|
||||
expect(JSON.parse(harness.files.get(globalPath) ?? '')).toMatchObject({
|
||||
provider: 'review-proxy',
|
||||
})
|
||||
expect(menuOptions[0]).toContain('Provider: openai-codex (source: default; global: inherit)')
|
||||
expect(menuOptions[1]).toContain('Provider: review-proxy (source: global; global: override)')
|
||||
expect(harness.reload).not.toHaveBeenCalled()
|
||||
expect(harness.ui.notify).toHaveBeenCalledWith('Config saved and applied without reloading the Pi session.', 'info')
|
||||
})
|
||||
|
||||
it('cancels the settings menu without writing or applying', async () => {
|
||||
const harness = createCommandHarness()
|
||||
harness.ui.select.mockResolvedValueOnce('Project configuration').mockResolvedValueOnce('Cancel')
|
||||
|
||||
await harness.command().handler('', harness.context)
|
||||
|
||||
expect(harness.files.has(projectPath)).toBe(false)
|
||||
expect(harness.applyConfig).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows active values without exposing the additional policy body', async () => {
|
||||
const harness = createCommandHarness({
|
||||
[globalPath]: JSON.stringify({
|
||||
reasoning: 'high',
|
||||
additionalPolicy: 'Private policy contents',
|
||||
}),
|
||||
})
|
||||
|
||||
await harness.command().handler('show', harness.context)
|
||||
|
||||
const message = harness.ui.notify.mock.calls[0]?.[0] as string
|
||||
expect(message).toContain('reasoning=high (global)')
|
||||
expect(message).toContain('additionalPolicy=configured (global)')
|
||||
expect(message).not.toContain('Private policy contents')
|
||||
})
|
||||
|
||||
it('reports both config paths and command help', async () => {
|
||||
const harness = createCommandHarness()
|
||||
|
||||
await harness.command().handler('path', harness.context)
|
||||
await harness.command().handler('help', harness.context)
|
||||
|
||||
expect(harness.ui.notify).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.stringContaining(`global=${globalPath}\nproject=${projectPath}`),
|
||||
'info',
|
||||
)
|
||||
expect(harness.ui.notify).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'Usage: /permission-auto-review [show|path|reset [global|project]|help]',
|
||||
'info',
|
||||
)
|
||||
})
|
||||
|
||||
it('resets an invalid scope and hot-applies the inherited config', async () => {
|
||||
const harness = createCommandHarness({
|
||||
[projectPath]: JSON.stringify({ apiKey: 'invalid' }),
|
||||
})
|
||||
harness.ui.confirm.mockResolvedValue(true)
|
||||
|
||||
await harness.command().handler('reset project', harness.context)
|
||||
|
||||
expect(harness.waitForIdle).toHaveBeenCalledOnce()
|
||||
expect(harness.files.has(projectPath)).toBe(false)
|
||||
expect(harness.applyConfig).toHaveBeenCalledOnce()
|
||||
expect(harness.activeConfig()).toMatchObject({
|
||||
provider: 'openai-codex',
|
||||
model: 'codex-auto-review',
|
||||
})
|
||||
expect(harness.reload).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps the interactive editor disabled outside TUI mode', async () => {
|
||||
const harness = createCommandHarness()
|
||||
const context = {
|
||||
...harness.context,
|
||||
mode: 'rpc',
|
||||
} as ExtensionCommandContext
|
||||
|
||||
await harness.command().handler('', context)
|
||||
|
||||
expect(harness.ui.select).not.toHaveBeenCalled()
|
||||
expect(harness.ui.notify).toHaveBeenCalledWith('/permission-auto-review requires interactive TUI mode.', 'warning')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user