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 } { let settings = getDefaultSettings(tmpdir()) const updateSettings = vi.fn((updates: Partial) => { settings = { ...settings, ...updates } }) return { store: { getSettings: () => settings, updateSettings } as unknown as Store, getSettings: () => settings, updateSettings } } function createPluginService( getFingerprint: () => string, overrides: Partial = {} ): 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() }) })