open-design/apps/daemon/tests/plugins-connector-gate.test.ts
pftom 89be57b2c4 feat(genui): introduce GenUI surface management and event handling
- Added a new GenUI module for managing user interface surfaces, including creation, response handling, and state synchronization.
- Implemented API endpoints for listing and responding to GenUI surfaces associated with runs and projects.
- Introduced event types and payload helpers for GenUI surface events, enhancing the interaction model for headless operations.
- Established a persistent state writer for GenUI surfaces, ensuring reliable data management and retrieval.
- Enhanced the plugin system to support auto-derived OAuth prompts for required connectors, improving user experience during plugin application.
2026-05-09 18:44:04 +08:00

150 lines
5.9 KiB
TypeScript

// Connector-gate unit tests (spec §9 / §11.3).
//
// Three behaviours under test:
// 1. resolveConnectorBindings returns 'unavailable' for unknown ids and
// 'connected' / 'pending' for known ones (apply path).
// 2. deriveAutoOAuthPrompts auto-creates an oauth-prompt for every
// not-yet-connected required connector, with the spec-locked
// __auto_connector_<id> id and persist='project'.
// 3. checkConnectorTokenIssuance allows trusted plugins implicitly and
// restricts restricted plugins to their explicit grants.
import { describe, expect, it } from 'vitest';
import {
checkConnectorTokenIssuance,
deriveAutoOAuthPrompts,
mergeAutoOAuthPrompts,
resolveConnectorBindings,
validateConnectorRefs,
type ConnectorCatalogEntry,
type ConnectorProbe,
} from '../src/plugins/connector-gate.js';
import type { PluginManifest } from '@open-design/contracts';
const buildProbe = (entries: ConnectorCatalogEntry[]): ConnectorProbe => ({
get(id) { return entries.find((e) => e.id === id); },
});
const baseManifest = (): PluginManifest => ({
name: 'sample-plugin',
version: '1.0.0',
od: {
connectors: {
required: [
{ id: 'figma', tools: ['figma_get_file'] },
{ id: 'notion', tools: [] },
],
optional: [
{ id: 'slack', tools: [] },
],
},
capabilities: ['connector:figma', 'connector:notion'],
},
});
describe('resolveConnectorBindings', () => {
it('marks unknown connector ids as unavailable', () => {
const probe = buildProbe([
{ id: 'figma', status: 'connected', allowedToolNames: ['figma_get_file'] },
]);
const { resolved } = resolveConnectorBindings(baseManifest(), probe);
const figma = resolved.find((r) => r.id === 'figma');
const notion = resolved.find((r) => r.id === 'notion');
expect(figma?.status).toBe('connected');
expect(notion?.status).toBe('unavailable');
});
it('flips required:false on optional[] entries', () => {
const probe = buildProbe([
{ id: 'figma', status: 'connected', allowedToolNames: [] },
{ id: 'notion', status: 'connected', allowedToolNames: [] },
{ id: 'slack', status: 'connected', allowedToolNames: [] },
]);
const { resolved } = resolveConnectorBindings(baseManifest(), probe);
expect(resolved.find((r) => r.id === 'slack')?.required).toBe(false);
expect(resolved.find((r) => r.id === 'figma')?.required).toBe(true);
});
it('without a probe leaves all bindings pending', () => {
const { resolved } = resolveConnectorBindings(baseManifest(), undefined);
for (const r of resolved) expect(r.status).toBe('pending');
});
});
describe('deriveAutoOAuthPrompts', () => {
it('only fires for required + not-connected bindings', () => {
const auto = deriveAutoOAuthPrompts([
{ id: 'figma', required: true, status: 'pending', tools: [] },
{ id: 'notion', required: true, status: 'connected', tools: [] },
{ id: 'slack', required: false, status: 'unavailable', tools: [] },
]);
expect(auto).toHaveLength(1);
const surface = auto[0]!;
expect(surface.id).toBe('__auto_connector_figma');
expect(surface.kind).toBe('oauth-prompt');
expect(surface.persist).toBe('project');
expect(surface.oauth?.route).toBe('connector');
expect(surface.oauth?.connectorId).toBe('figma');
});
});
describe('mergeAutoOAuthPrompts', () => {
it('lets plugin-declared surfaces win over auto-derived ones with the same id', () => {
const declared = [{
id: '__auto_connector_figma',
kind: 'oauth-prompt' as const,
persist: 'project' as const,
prompt: 'Custom Figma copy',
}];
const auto = deriveAutoOAuthPrompts([
{ id: 'figma', required: true, status: 'pending', tools: [] },
{ id: 'notion', required: true, status: 'pending', tools: [] },
]);
const merged = mergeAutoOAuthPrompts(declared, auto);
const figma = merged.find((s) => s.id === '__auto_connector_figma');
expect(figma?.prompt).toBe('Custom Figma copy');
expect(merged.find((s) => s.id === '__auto_connector_notion')).toBeDefined();
});
});
describe('validateConnectorRefs', () => {
it('flags unknown connector ids as errors and missing capabilities as warnings', () => {
const probe = buildProbe([
{ id: 'figma', status: 'connected', allowedToolNames: ['figma_get_file', 'figma_get_node'] },
// notion missing on purpose
]);
const manifest = baseManifest();
delete manifest.od!.capabilities;
const issues = validateConnectorRefs(manifest, probe);
const codes = issues.map((i) => i.code).sort();
expect(codes).toContain('unknown-connector');
expect(codes).toContain('missing-capability');
});
it('flags tools not in allowedToolNames', () => {
const probe = buildProbe([
{ id: 'figma', status: 'connected', allowedToolNames: ['figma_get_node'] },
{ id: 'notion', status: 'connected', allowedToolNames: [] },
{ id: 'slack', status: 'connected', allowedToolNames: [] },
]);
const manifest = baseManifest();
const issues = validateConnectorRefs(manifest, probe);
const unknownTool = issues.find((i) => i.code === 'unknown-tool');
expect(unknownTool?.connectorId).toBe('figma');
expect(unknownTool?.tools).toContain('figma_get_file');
});
});
describe('checkConnectorTokenIssuance', () => {
const snap = { capabilitiesGranted: ['connector:figma'] };
it('trusted plugins implicitly carry connector:*', () => {
expect(checkConnectorTokenIssuance({ snapshot: { capabilitiesGranted: [] }, trust: 'trusted', connectorId: 'figma' }))
.toEqual({ ok: true });
});
it('restricted plugins must list each id', () => {
expect(checkConnectorTokenIssuance({ snapshot: snap, trust: 'restricted', connectorId: 'figma' }))
.toEqual({ ok: true });
const reject = checkConnectorTokenIssuance({ snapshot: snap, trust: 'restricted', connectorId: 'notion' });
expect(reject.ok).toBe(false);
});
});