Files
orca/src/main/plugins/plugin-host-methods.test.ts
T
Neil 1b2be7dd08 feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental)
Adds Orca's experimental plugin system behind a settings flag: a
supervised kernel, declarative content packs (VM recipes, commands and
keybindings, language packs), sandboxed iframe panels, forked worker
hosts, and a Git-backed marketplace v0 with consent, provenance and
kill-list enforcement.

Theme, icon-theme and terminal-theme contributions are deferred to a
follow-up pass.
2026-07-27 00:27:26 -07:00

233 lines
8.3 KiB
TypeScript

import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import { PLUGIN_WORKSPACE_TERMINAL_LIMIT } from '../../shared/plugins/plugin-host-api'
import { bindPluginHostServices, type PluginRuntimeDelegate } from './plugin-host-service-bindings'
import { executePluginHostCall, type PluginHostServices } from './plugin-host-methods'
function createServices(storageSet: PluginHostServices['storage']['set']): PluginHostServices {
return {
resolveActiveWorktreeContext: vi.fn().mockResolvedValue(null),
listWorktreeTerminals: vi.fn().mockResolvedValue([]),
sendTerminalText: vi.fn().mockResolvedValue({ accepted: true }),
dispatchPluginNotification: vi.fn().mockResolvedValue({ delivered: true }),
storage: {
get: vi.fn(),
set: storageSet,
delete: vi.fn(),
keys: vi.fn().mockReturnValue([])
},
secrets: {
get: vi.fn().mockReturnValue({ ok: true, value: null }),
set: vi.fn().mockReturnValue({ ok: true }),
delete: vi.fn()
},
settings: {
getAll: vi.fn().mockReturnValue({}),
set: vi.fn().mockReturnValue({ ok: true })
},
subscribeEvents: vi.fn().mockReturnValue([])
}
}
describe('executePluginHostCall mutation auditing', () => {
it('rejects prototype-sensitive storage keys before any host service call', async () => {
const storageSet = vi.fn().mockReturnValue({ ok: true })
const outcome = await executePluginHostCall({
pluginId: 'orca-samples.demo',
method: 'storage.set',
params: { key: '__proto__', value: 42 },
viaPanel: false,
grantedCapabilities: ['storage'],
services: createServices(storageSet),
audit: { record: vi.fn().mockResolvedValue(undefined) }
})
expect(outcome).toMatchObject({ ok: false, code: 'invalid_params' })
expect(storageSet).not.toHaveBeenCalled()
})
it('rejects non-JSON storage values before any host service call', async () => {
const storageSet = vi.fn().mockReturnValue({ ok: true })
const outcome = await executePluginHostCall({
pluginId: 'orca-samples.demo',
method: 'storage.set',
params: { key: 'created', value: new Date() },
viaPanel: false,
grantedCapabilities: ['storage'],
services: createServices(storageSet),
audit: { record: vi.fn().mockResolvedValue(undefined) }
})
expect(outcome).toMatchObject({ ok: false, code: 'invalid_params' })
expect(storageSet).not.toHaveBeenCalled()
})
it('fails closed before a mutation when the audit intent cannot be recorded', async () => {
const storageSet = vi.fn().mockReturnValue({ ok: true })
const outcome = await executePluginHostCall({
pluginId: 'orca-samples.demo',
method: 'storage.set',
params: { key: 'answer', value: 42 },
viaPanel: false,
grantedCapabilities: ['storage'],
services: createServices(storageSet),
audit: { record: vi.fn().mockRejectedValue(new Error('disk full')) }
})
expect(outcome).toMatchObject({ ok: false, code: 'action_failed' })
expect(storageSet).not.toHaveBeenCalled()
})
it('records an intent before the mutation and its outcome afterward', async () => {
const order: string[] = []
const storageSet = vi.fn(() => {
order.push('mutation')
return { ok: true as const }
})
const record = vi.fn(async (entry: { outcome: string }) => {
order.push(`audit:${entry.outcome}`)
})
const outcome = await executePluginHostCall({
pluginId: 'orca-samples.demo',
method: 'storage.set',
params: { key: 'answer', value: 42 },
viaPanel: false,
grantedCapabilities: ['storage'],
services: createServices(storageSet),
audit: { record }
})
expect(outcome).toEqual({ ok: true, value: { ok: true } })
expect(order).toEqual(['audit:attempt', 'mutation', 'audit:ok'])
})
it('refuses mutations when no audit writer is configured', async () => {
const storageSet = vi.fn().mockReturnValue({ ok: true })
const outcome = await executePluginHostCall({
pluginId: 'orca-samples.demo',
method: 'storage.set',
params: { key: 'answer', value: 42 },
viaPanel: false,
grantedCapabilities: ['storage'],
services: createServices(storageSet)
})
expect(outcome).toMatchObject({ ok: false, code: 'unavailable' })
expect(storageSet).not.toHaveBeenCalled()
})
})
function createTerminalHarness(terminalHandles: string[]): {
delegate: PluginRuntimeDelegate
services: PluginHostServices
} {
const delegate: PluginRuntimeDelegate = {
resolveActiveWorktreeContext: vi.fn().mockResolvedValue({
worktreeId: 'worktree-1',
path: '/Users/private/repo',
branch: 'main',
displayName: 'Repo'
}),
listTerminals: vi.fn().mockResolvedValue({
terminals: terminalHandles.map((handle) => ({ handle, title: null }))
}),
sendTerminal: vi.fn().mockResolvedValue({ accepted: true }),
dispatchPluginNotification: vi.fn().mockResolvedValue({ delivered: true })
}
return {
delegate,
services: bindPluginHostServices({
delegate,
pluginsDataDir: join(tmpdir(), 'plugin-host-methods-test'),
subscribeEvents: vi.fn().mockReturnValue([])
})
}
}
async function sendTerminalText(
services: PluginHostServices,
terminalId: string
): ReturnType<typeof executePluginHostCall> {
return executePluginHostCall({
pluginId: 'orca-samples.demo',
method: 'terminal.sendText',
params: { terminalId, text: 'echo hi', enter: true },
viaPanel: true,
grantedCapabilities: ['terminal:send'],
services,
audit: { record: vi.fn().mockResolvedValue(undefined) }
})
}
describe('terminal.sendText explicit worktree routing', () => {
it('performs one bounded list and zero sends when the terminal is outside the worktree', async () => {
const { delegate, services } = createTerminalHarness(['terminal:local:other'])
const outcome = await sendTerminalText(services, 'terminal:ssh:requested')
expect(outcome).toMatchObject({ ok: false, code: 'action_failed' })
expect(delegate.resolveActiveWorktreeContext).toHaveBeenCalledTimes(1)
expect(delegate.listTerminals).toHaveBeenCalledTimes(1)
expect(delegate.listTerminals).toHaveBeenCalledWith(
'id:worktree-1',
PLUGIN_WORKSPACE_TERMINAL_LIMIT
)
expect(delegate.sendTerminal).not.toHaveBeenCalled()
})
it.each(['terminal:local:one', 'terminal:ssh:opaque-provider-id'])(
'performs one bounded list and one send for provider-agnostic id %s',
async (terminalId) => {
const { delegate, services } = createTerminalHarness([terminalId])
const outcome = await sendTerminalText(services, terminalId)
expect(outcome).toEqual({ ok: true, value: { accepted: true } })
expect(delegate.resolveActiveWorktreeContext).toHaveBeenCalledTimes(1)
expect(delegate.listTerminals).toHaveBeenCalledTimes(1)
expect(delegate.listTerminals).toHaveBeenCalledWith(
'id:worktree-1',
PLUGIN_WORKSPACE_TERMINAL_LIMIT
)
expect(delegate.sendTerminal).toHaveBeenCalledTimes(1)
expect(delegate.sendTerminal).toHaveBeenCalledWith(terminalId, {
text: 'echo hi',
enter: true
})
expect(vi.mocked(delegate.listTerminals).mock.invocationCallOrder[0]!).toBeLessThan(
vi.mocked(delegate.sendTerminal).mock.invocationCallOrder[0]!
)
}
)
it('bounds workspace.readContext and omits the provider path', async () => {
const handles = Array.from(
{ length: PLUGIN_WORKSPACE_TERMINAL_LIMIT + 10 },
(_, index) => `terminal:local:${index}`
)
const { delegate, services } = createTerminalHarness(handles)
const outcome = await executePluginHostCall({
pluginId: 'orca-samples.demo',
method: 'workspace.readContext',
params: {},
viaPanel: true,
grantedCapabilities: ['workspace:read'],
services
})
expect(outcome).toMatchObject({
ok: true,
value: { branch: 'main', displayName: 'Repo' }
})
expect(outcome).not.toHaveProperty('value.path')
expect(outcome).not.toHaveProperty('value.worktreeId')
expect(outcome.ok && (outcome.value as { terminals: unknown[] }).terminals).toHaveLength(
PLUGIN_WORKSPACE_TERMINAL_LIMIT
)
expect(delegate.listTerminals).toHaveBeenCalledTimes(1)
})
})