feat(coding): add plugin capability policy registry
This commit is contained in:
123
tests/unit/plugin-policy-client.test.ts
Normal file
123
tests/unit/plugin-policy-client.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
parsePluginCatalog,
|
||||
PluginPolicyClient,
|
||||
} from '../../electron/services/plugin-policy-client';
|
||||
|
||||
const 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.documents',
|
||||
operations: [
|
||||
{
|
||||
operation: 'get_document',
|
||||
billing: {
|
||||
mode: 'included',
|
||||
entitlement_scope: null,
|
||||
notice: 'Included in development data service',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}],
|
||||
};
|
||||
|
||||
describe('PluginPolicyClient', () => {
|
||||
it('parses the exact catalog and rejects unknown or malformed fields', () => {
|
||||
expect(parsePluginCatalog(catalog)).toMatchObject({
|
||||
schema_version: 1,
|
||||
catalog_version: 'catalog-1',
|
||||
plugins: [{ plugin_id: 'makelore.data-service' }],
|
||||
});
|
||||
expect(() => parsePluginCatalog({ ...catalog, extra: true })).toThrow('unexpected fields');
|
||||
expect(() => parsePluginCatalog({
|
||||
...catalog,
|
||||
plugins: [{
|
||||
...catalog.plugins[0],
|
||||
capabilities: [{
|
||||
...catalog.plugins[0].capabilities[0],
|
||||
operations: [{
|
||||
...catalog.plugins[0].capabilities[0].operations[0],
|
||||
billing: { mode: 'included', entitlement_scope: 'wrong', notice: 'nope' },
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
})).toThrow();
|
||||
});
|
||||
|
||||
it('accepts an unavailable platform-metered policy without exposing a price', () => {
|
||||
const parsed = parsePluginCatalog({
|
||||
...catalog,
|
||||
plugins: [{
|
||||
...catalog.plugins[0],
|
||||
capabilities: [{
|
||||
...catalog.plugins[0].capabilities[0],
|
||||
operations: [{
|
||||
...catalog.plugins[0].capabilities[0].operations[0],
|
||||
billing: {
|
||||
mode: 'platform_metered',
|
||||
status: 'billing_unavailable',
|
||||
entitlement_scope: 'data-service.documents',
|
||||
notice: 'Pricing is unavailable',
|
||||
},
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
});
|
||||
expect(parsed.plugins[0]?.capabilities[0]?.operations[0]?.billing).toEqual({
|
||||
mode: 'platform_metered',
|
||||
status: 'billing_unavailable',
|
||||
entitlement_scope: 'data-service.documents',
|
||||
notice: 'Pricing is unavailable',
|
||||
});
|
||||
});
|
||||
|
||||
it('coalesces concurrent refreshes and marks the verified catalog stale after failure', async () => {
|
||||
let resolveRequest: ((response: Response) => void) | undefined;
|
||||
const fetchImpl = vi.fn(() => new Promise<Response>((resolve) => {
|
||||
resolveRequest = resolve;
|
||||
}));
|
||||
const client = new PluginPolicyClient({
|
||||
fetchImpl,
|
||||
apiBaseUrl: 'https://works.example',
|
||||
now: () => 123,
|
||||
});
|
||||
const first = client.refresh();
|
||||
const second = client.refresh();
|
||||
expect(first).toBe(second);
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
||||
resolveRequest?.(new Response(JSON.stringify(catalog), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}));
|
||||
await expect(first).resolves.toMatchObject({ status: 'current', revision: 1 });
|
||||
|
||||
fetchImpl.mockRejectedValueOnce(new Error('offline'));
|
||||
await expect(client.refresh()).resolves.toMatchObject({
|
||||
status: 'stale',
|
||||
revision: 1,
|
||||
errorCode: 'plugin_backend_unavailable',
|
||||
});
|
||||
expect(client.getState().catalog).toEqual(expect.objectContaining({ catalog_version: 'catalog-1' }));
|
||||
});
|
||||
|
||||
it('keeps an unavailable first state when no catalog was verified', async () => {
|
||||
const client = new PluginPolicyClient({ fetchImpl: vi.fn().mockRejectedValue(new Error('offline')) });
|
||||
await expect(client.refresh()).resolves.toMatchObject({
|
||||
status: 'unavailable',
|
||||
catalog: null,
|
||||
revision: 0,
|
||||
errorCode: 'plugin_backend_unavailable',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user