Files
orca/src/main/plugins/plugin-enablement.test.ts
T
Neil b9a4b5fd2f fix(plugins): re-review any plugin whose catalog moves it to another repository (#36643)
* fix(plugins): drop a plugin's approval when its catalog moves it to another repository

* plugins: revokePluginConsent refuses when the plugin service is not running

* cli: orca plugin install/update ask again when a plugin moves to another repository, for every plugin kind
2026-11-06 14:41:29 -08:00

138 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { tmpdir } from 'node:os'
import { describe, expect, it, vi } from 'vitest'
import { getDefaultSettings } from '../../shared/constants'
import type { GlobalSettings } from '../../shared/global-settings-types'
import { pluginManifestSchema } from '../../shared/plugins/manifest/plugin-manifest'
import type { Store } from '../persistence/loading-store/store'
import { applyPluginConsent, revokePluginConsent } from './plugin-enablement'
import type { ValidDiscoveredPlugin } from './plugin-discovery'
import type { PluginService } from './plugin-service'
const pluginKey = 'samples.demo'
const manifest = pluginManifestSchema.parse({
manifestVersion: 1,
id: 'demo',
publisher: 'samples',
name: 'Demo',
version: '1.0.0',
engines: { orca: '>=1.0.0' },
pluginApi: 1,
contributes: {},
capabilities: []
})
function createStore(): {
store: Store
getSettings: () => GlobalSettings
updateSettings: ReturnType<typeof vi.fn>
} {
let settings = getDefaultSettings(tmpdir())
const updateSettings = vi.fn((updates: Partial<GlobalSettings>) => {
settings = { ...settings, ...updates }
})
return {
store: { getSettings: () => settings, updateSettings } as unknown as Store,
getSettings: () => settings,
updateSettings
}
}
function createPluginService(
getFingerprint: () => string,
overrides: Partial<ValidDiscoveredPlugin> = {}
): PluginService {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the enablement paths read only these members.
return {
options: {},
findValidPlugin: (requestedKey: string) =>
requestedKey === pluginKey
? {
pluginKey,
rootDir: tmpdir(),
manifest,
consentFingerprint: getFingerprint(),
consentContentHash: null,
contentHash: null,
isDev: true,
...overrides
}
: null,
reconcileActivationState: vi.fn().mockResolvedValue(undefined)
} as unknown as PluginService
}
describe('applyPluginConsent', () => {
it('stores approval only for the fingerprint the user reviewed', async () => {
const harness = createStore()
const pluginService = createPluginService(() => 'sha256-reviewed')
await applyPluginConsent({
store: harness.store,
pluginService,
pluginKey,
reviewedFingerprint: 'sha256-reviewed',
decision: 'approve'
})
expect(harness.getSettings().pluginConsents[pluginKey]).toBe('sha256-reviewed')
expect(harness.getSettings().disabledPlugins).not.toContain(pluginKey)
})
it('rejects a stale review after a same-key plugin update without writing settings', async () => {
const harness = createStore()
let currentFingerprint = 'sha256-reviewed-v1'
const pluginService = createPluginService(() => currentFingerprint)
currentFingerprint = 'sha256-current-v2'
await expect(
applyPluginConsent({
store: harness.store,
pluginService,
pluginKey,
reviewedFingerprint: 'sha256-reviewed-v1',
decision: 'approve'
})
).rejects.toThrow('changed since its permissions were reviewed')
expect(harness.updateSettings).not.toHaveBeenCalled()
expect(harness.getSettings().pluginConsents[pluginKey]).toBeUndefined()
})
it('allows a stale dialog to keep the newer plugin disabled', async () => {
const harness = createStore()
const pluginService = createPluginService(() => 'sha256-current-v2')
await applyPluginConsent({
store: harness.store,
pluginService,
pluginKey,
reviewedFingerprint: 'sha256-reviewed-v1',
decision: 'keep-disabled'
})
expect(harness.getSettings().disabledPlugins).toContain(pluginKey)
expect(pluginService.reconcileActivationState).toHaveBeenCalledOnce()
})
})
describe('revokePluginConsent', () => {
it('drops only this plugin’s approval, leaves it enabled, and stops it running', async () => {
const harness = createStore()
harness.store.updateSettings({
pluginConsents: { [pluginKey]: 'sha256-old', 'samples.other': 'sha256-other' }
})
const pluginService = createPluginService(() => 'sha256-old')
await revokePluginConsent({ store: harness.store, pluginService, pluginKey })
expect(harness.getSettings().pluginConsents).toEqual({ 'samples.other': 'sha256-other' })
expect(harness.getSettings().disabledPlugins).not.toContain(pluginKey)
expect(pluginService.reconcileActivationState).toHaveBeenCalledOnce()
})
it('writes nothing when the plugin had no approval', async () => {
const harness = createStore()
const pluginService = createPluginService(() => 'sha256-old')
await revokePluginConsent({ store: harness.store, pluginService, pluginKey })
expect(harness.updateSettings).not.toHaveBeenCalled()
})
})