feat(coding): add plugin capability policy registry
This commit is contained in:
153
tests/unit/coding-capability-registry.test.ts
Normal file
153
tests/unit/coding-capability-registry.test.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { PluginPolicyClientState } from '../../electron/services/plugin-policy-client';
|
||||
import {
|
||||
CodingCapabilityRegistryImpl,
|
||||
} from '../../electron/coding-plugins/registry';
|
||||
import type { CodingPluginAdapter } from '../../electron/coding-plugins/registry';
|
||||
|
||||
const policy: PluginPolicyClientState = {
|
||||
status: 'current',
|
||||
revision: 7,
|
||||
lastVerifiedAt: 1,
|
||||
catalog: {
|
||||
schema_version: 1,
|
||||
catalog_version: 'catalog-1',
|
||||
pricing_version: null,
|
||||
plugins: [{
|
||||
plugin_id: 'makelore.data-service',
|
||||
supported_contract_versions: [1],
|
||||
status: 'active',
|
||||
capabilities: [
|
||||
{
|
||||
capability_id: 'data-service.control',
|
||||
operations: ['configure', 'inspect', 'list_projects', 'remove_collection', 'reset', 'remove_project']
|
||||
.map((operation) => ({
|
||||
operation,
|
||||
billing: { mode: 'included' as const, entitlement_scope: null, notice: 'Included' },
|
||||
})),
|
||||
},
|
||||
{
|
||||
capability_id: 'data-service.documents',
|
||||
operations: ['get_document', 'list_documents', 'put_document', 'delete_document']
|
||||
.map((operation) => ({
|
||||
operation,
|
||||
billing: { mode: 'included' as const, entitlement_scope: null, notice: 'Included' },
|
||||
})),
|
||||
},
|
||||
{
|
||||
capability_id: 'data-service.preview',
|
||||
operations: ['get', 'list', 'put', 'delete'].map((operation) => ({
|
||||
operation,
|
||||
billing: { mode: 'included' as const, entitlement_scope: null, notice: 'Included' },
|
||||
})),
|
||||
},
|
||||
],
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
const context = {
|
||||
conversationId: 'conversation-a',
|
||||
runId: 'run-a',
|
||||
resourceId: 'resource-a',
|
||||
projectId: 'local-project-a',
|
||||
projectPath: 'C:\\projects\\demo',
|
||||
skillIds: ['data-service'],
|
||||
};
|
||||
|
||||
function adapter(): CodingPluginAdapter {
|
||||
return {
|
||||
pluginId: 'makelore.data-service',
|
||||
async inspect() { return { status: 'ready' }; },
|
||||
async invoke() {
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
code: null,
|
||||
error: null,
|
||||
retryable: false,
|
||||
payload_schema: 'data-service.v1',
|
||||
data: { id: 'one', data: {}, revision: 1 },
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function registry(overrides: Partial<ConstructorParameters<typeof CodingCapabilityRegistryImpl>[0]> = {}) {
|
||||
return new CodingCapabilityRegistryImpl({
|
||||
policyClient: { getState: () => policy },
|
||||
getEnabledPluginIds: async () => ['makelore.data-service'],
|
||||
adapters: [adapter()],
|
||||
...overrides,
|
||||
});
|
||||
}
|
||||
|
||||
describe('CodingCapabilityRegistry', () => {
|
||||
it('joins package, selection, grant, role, and server policy for parent resources', async () => {
|
||||
const resources = await registry().resolveWorkerResources({
|
||||
projectPath: context.projectPath,
|
||||
assignedSkillIds: context.skillIds,
|
||||
role: 'parent',
|
||||
});
|
||||
expect(resources.catalogRevision).toBe(7);
|
||||
expect(resources.pluginIds).toEqual(['makelore.data-service']);
|
||||
expect(resources.effectiveSkillIds).toEqual(['data-service']);
|
||||
expect(resources.tools.map(({ name }) => name)).toHaveLength(10);
|
||||
});
|
||||
|
||||
it('never exposes plugin resources to child workers or before the first catalog', async () => {
|
||||
const child = await registry().resolveWorkerResources({
|
||||
projectPath: context.projectPath, assignedSkillIds: context.skillIds, role: 'child',
|
||||
});
|
||||
expect(child.pluginIds).toEqual([]);
|
||||
expect(child.tools).toEqual([]);
|
||||
const unavailable = await registry({
|
||||
policyClient: { getState: () => ({ ...policy, catalog: null, status: 'unavailable', revision: 0 }) },
|
||||
}).resolveWorkerResources({
|
||||
projectPath: context.projectPath, assignedSkillIds: context.skillIds, role: 'parent',
|
||||
});
|
||||
expect(unavailable.tools).toEqual([]);
|
||||
});
|
||||
|
||||
it('revalidates invocation, derives the stable Pi request id, and preserves domain faults', async () => {
|
||||
const result = await registry().invoke({
|
||||
toolName: 'data_service_get_document',
|
||||
context,
|
||||
workerRole: 'parent',
|
||||
effectiveSkillIds: context.skillIds,
|
||||
value: { collection: 'todos', document_id: 'one' },
|
||||
});
|
||||
expect(result.details).toMatchObject({
|
||||
schema: 'makelore-capability.v1',
|
||||
plugin_id: 'makelore.data-service',
|
||||
capability_id: 'data-service.documents',
|
||||
operation: 'get_document',
|
||||
request_id: 'pi:run-a:resource-a',
|
||||
billing: { mode: 'included', status: 'included' },
|
||||
payload_schema: 'data-service.v1',
|
||||
});
|
||||
const invalid = await registry().invoke({
|
||||
toolName: 'data_service_get_document', context, workerRole: 'parent',
|
||||
effectiveSkillIds: context.skillIds, value: { collection: 'todos', projectPath: 'forbidden' },
|
||||
});
|
||||
expect(invalid.details).toMatchObject({ success: false, code: 'plugin_input_invalid' });
|
||||
const disabled = await registry({ getEnabledPluginIds: async () => [] }).invoke({
|
||||
toolName: 'data_service_get_document', context, workerRole: 'parent',
|
||||
effectiveSkillIds: context.skillIds, value: { collection: 'todos', document_id: 'one' },
|
||||
});
|
||||
expect(disabled.details).toMatchObject({ success: false, code: 'plugin_not_enabled' });
|
||||
const invalidIdentity = await registry().invoke({
|
||||
toolName: 'data_service_get_document',
|
||||
context: { ...context, runId: '' },
|
||||
workerRole: 'parent',
|
||||
effectiveSkillIds: context.skillIds,
|
||||
value: { collection: 'todos', document_id: 'one' },
|
||||
});
|
||||
expect(invalidIdentity.details).toMatchObject({
|
||||
success: false, code: 'plugin_input_invalid', request_id: 'invalid-request-id',
|
||||
});
|
||||
expect(JSON.stringify(invalidIdentity)).not.toMatch(/[0-9a-f]{8}-[0-9a-f]{4}/i);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user