diff --git a/lib/__tests__/plugin-assistant-c-features.test.ts b/lib/__tests__/plugin-assistant-c-features.test.ts new file mode 100644 index 000000000..61d637de6 --- /dev/null +++ b/lib/__tests__/plugin-assistant-c-features.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { InstalledPlugin } from '../plugin-types'; + +vi.mock('@/stores/toast-store', () => ({ + toast: { success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() }, +})); + +const { snapshotHostFeatures } = await import('../plugin-sandbox/host-features'); + +// A plugin sees answerInvitation and changeSettings only once it was granted +// the optional permission behind them: one enabled before the update that +// added them doesn't have it until the user turns it off and on. + +function plugin(granted: string[]): InstalledPlugin { + return { + id: 'ai-assistant', name: 'AI', version: '2.2.0', author: 'test', description: '', type: 'hook', + permissions: ['calendar:respond', 'settings:change'], grantedPermissions: granted, + entrypoint: 'index.js', enabled: true, status: 'running', settings: {}, + } as InstalledPlugin; +} + +describe('the area c flags for one plugin', () => { + it('follow what the plugin was granted', () => { + expect(snapshotHostFeatures(plugin([]))).toMatchObject({ answerInvitation: false, changeSettings: false, batchMail: true }); + expect(snapshotHostFeatures(plugin(['calendar:respond']))).toMatchObject({ answerInvitation: true, changeSettings: false }); + expect(snapshotHostFeatures(plugin(['calendar:respond', 'settings:change']))).toMatchObject({ answerInvitation: true, changeSettings: true }); + }); + + it('say what the webmail offers when no plugin is named', () => { + expect(snapshotHostFeatures()).toMatchObject({ answerInvitation: true, changeSettings: true }); + }); +}); diff --git a/lib/plugin-sandbox/assistant/c.ts b/lib/plugin-sandbox/assistant/c.ts index e4b4b6e65..410222978 100644 --- a/lib/plugin-sandbox/assistant/c.ts +++ b/lib/plugin-sandbox/assistant/c.ts @@ -14,6 +14,7 @@ import { useVacationStore } from '@/stores/vacation-store'; import { doVacationGet, doVacationSet } from '../host-api-settings'; import { isPlainRecord, sessionClient } from '../host-api-input'; import { NOT_MINE } from './not-mine'; +import { pluginHasPermission } from '../permissions'; import { doCalendarDeleteWithUndo, doCalendarRespond, doCalendarUndoDelete } from './c.calendar'; import { doFiltersCreate, doFiltersList, doFiltersUndo } from './c.filters'; import { doEmailBatch, doEmailUndoBatch } from './c.batch'; @@ -94,13 +95,18 @@ export async function dispatch(plugin: InstalledPlugin, method: string, args: un } } -/** This area's flags in api.host.features, right now. */ -export function features(): AreaHostFeaturesC { +/** + * This area's flags in api.host.features, right now. For one plugin, the + * calls behind a new optional permission count only once it was granted (a + * plugin enabled before an update lacks them until turned off and on). + */ +export function features(plugin?: InstalledPlugin): AreaHostFeaturesC { const client = useAuthStore.getState().client; + const granted = (permission: Permission) => !plugin || pluginHasPermission(plugin, permission); return { - answerInvitation: true, + answerInvitation: granted('calendar:respond'), filterRules: client?.supportsSieve?.() === true, batchMail: true, - changeSettings: true, + changeSettings: granted('settings:change'), }; }