mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +00:00
* 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
138 lines
4.7 KiB
TypeScript
138 lines
4.7 KiB
TypeScript
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()
|
||
})
|
||
})
|