test: lock secrets capability contract
This commit is contained in:
parent
b11178d8fd
commit
f4f79cf1d0
|
|
@ -106,6 +106,7 @@ export interface VerstakPluginAPI {
|
||||||
readText(relativePath: string): Promise<string>;
|
readText(relativePath: string): Promise<string>;
|
||||||
readBytes(relativePath: string): Promise<FileBytes>;
|
readBytes(relativePath: string): Promise<FileBytes>;
|
||||||
writeText(relativePath: string, content: string, options?: WriteTextOptions): Promise<void>;
|
writeText(relativePath: string, content: string, options?: WriteTextOptions): Promise<void>;
|
||||||
|
writeBytes(relativePath: string, dataBase64: string, options?: WriteTextOptions): Promise<void>;
|
||||||
createFolder(relativePath: string): Promise<void>;
|
createFolder(relativePath: string): Promise<void>;
|
||||||
move(fromRelativePath: string, toRelativePath: string, options?: MovePathOptions): Promise<void>;
|
move(fromRelativePath: string, toRelativePath: string, options?: MovePathOptions): Promise<void>;
|
||||||
trash(relativePath: string): Promise<TrashResult>;
|
trash(relativePath: string): Promise<TrashResult>;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"plugin-api.js","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gDAAgD;AA0KhD,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;AACtF,CAAC"}
|
{"version":3,"file":"plugin-api.js","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,8EAA8E;AAC9E,gFAAgF;AAChF,8EAA8E;AAC9E,gDAAgD;AA2KhD,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;AACtF,CAAC"}
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,KAAK,EAAwB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQ3E,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,4BAA4B,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAetF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CASnF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,SAAgB,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CA6TlH;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCxF;AAGD,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,CAAC"}
|
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,KAAK,EAAwB,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQ3E,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,4BAA4B,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAetF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CASnF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,SAAgB,EAAE,OAAO,GAAE,oBAAyB,GAAG,gBAAgB,CA2VlH;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCxF;AAGD,OAAO,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,EAAE,EAAE,CAAC"}
|
||||||
|
|
@ -88,6 +88,28 @@ export function createMockPluginAPI(pluginId = 'test.plugin', options = {}) {
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
function stringFromBase64(value) {
|
||||||
|
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
const clean = String(value || '').replace(/\s+/g, '');
|
||||||
|
if (clean.length % 4 === 1)
|
||||||
|
throw new Error('invalid-base64');
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < clean.length; i += 4) {
|
||||||
|
const a = alphabet.indexOf(clean[i]);
|
||||||
|
const b = alphabet.indexOf(clean[i + 1]);
|
||||||
|
const c = clean[i + 2] === '=' ? -1 : alphabet.indexOf(clean[i + 2]);
|
||||||
|
const d = clean[i + 3] === '=' ? -1 : alphabet.indexOf(clean[i + 3]);
|
||||||
|
if (a < 0 || b < 0 || (clean[i + 2] !== '=' && c < 0) || (clean[i + 3] !== '=' && d < 0)) {
|
||||||
|
throw new Error('invalid-base64');
|
||||||
|
}
|
||||||
|
result += String.fromCharCode((a << 2) | (b >> 4));
|
||||||
|
if (c >= 0)
|
||||||
|
result += String.fromCharCode(((b & 15) << 4) | (c >> 2));
|
||||||
|
if (d >= 0)
|
||||||
|
result += String.fromCharCode(((c & 3) << 6) | d);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
function entry(path, node) {
|
function entry(path, node) {
|
||||||
const name = baseName(path);
|
const name = baseName(path);
|
||||||
const dot = name.lastIndexOf('.');
|
const dot = name.lastIndexOf('.');
|
||||||
|
|
@ -248,6 +270,20 @@ export function createMockPluginAPI(pluginId = 'test.plugin', options = {}) {
|
||||||
throw new Error(`parent-not-found: ${parent}`);
|
throw new Error(`parent-not-found: ${parent}`);
|
||||||
files.set(path, { type: 'file', content, modifiedAt: new Date().toISOString() });
|
files.set(path, { type: 'file', content, modifiedAt: new Date().toISOString() });
|
||||||
}),
|
}),
|
||||||
|
writeBytes: vi.fn(async (relativePath, dataBase64, options = {}) => {
|
||||||
|
const path = normalizePath(relativePath);
|
||||||
|
const node = files.get(path);
|
||||||
|
if (node && node.type !== 'file')
|
||||||
|
throw new Error(`not-regular-file: ${path}`);
|
||||||
|
if (node && !options.overwrite)
|
||||||
|
throw new Error(`conflict: ${path}`);
|
||||||
|
if (!node && !options.createIfMissing)
|
||||||
|
throw new Error(`not-found: ${path}`);
|
||||||
|
const parent = parentPath(path);
|
||||||
|
if (!files.get(parent) || files.get(parent)?.type !== 'folder')
|
||||||
|
throw new Error(`parent-not-found: ${parent}`);
|
||||||
|
files.set(path, { type: 'file', content: stringFromBase64(dataBase64), modifiedAt: new Date().toISOString() });
|
||||||
|
}),
|
||||||
createFolder: vi.fn(async (relativePath) => {
|
createFolder: vi.fn(async (relativePath) => {
|
||||||
const path = normalizePath(relativePath);
|
const path = normalizePath(relativePath);
|
||||||
if (files.has(path))
|
if (files.has(path))
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,8 @@
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
||||||
|
import capabilitiesSchema from '../schemas/capabilities.json';
|
||||||
import manifestSchema from '../schemas/manifest.json';
|
import manifestSchema from '../schemas/manifest.json';
|
||||||
|
import permissionsSchema from '../schemas/permissions.json';
|
||||||
import vaultEventsSchema from '../schemas/events/vault.json';
|
import vaultEventsSchema from '../schemas/events/vault.json';
|
||||||
import type { OpenResourceRequest, PluginManifest } from './types';
|
import type { OpenResourceRequest, PluginManifest } from './types';
|
||||||
import { createMockPluginAPI } from './test-utils';
|
import { createMockPluginAPI } from './test-utils';
|
||||||
|
|
@ -53,6 +55,19 @@ describe('VerstakPluginAPI contract', () => {
|
||||||
expect(permissionEnum).toContain('workbench.open');
|
expect(permissionEnum).toContain('workbench.open');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('secrets capability and permissions are declared as dangerous platform contract', () => {
|
||||||
|
const capabilities = ((capabilitiesSchema as any).capabilities || []) as Array<{ name: string; status: string }>;
|
||||||
|
const permissions = ((permissionsSchema as any).permissions || []) as Array<{ name: string; dangerous: boolean }>;
|
||||||
|
const permissionEnum = ((manifestSchema as any).properties.permissions.items.enum || []) as string[];
|
||||||
|
|
||||||
|
expect(capabilities).toContainEqual(expect.objectContaining({ name: 'secret-store', status: 'draft' }));
|
||||||
|
expect(capabilities).toContainEqual(expect.objectContaining({ name: 'secrets.read-ui', status: 'draft' }));
|
||||||
|
expect(capabilities).toContainEqual(expect.objectContaining({ name: 'secrets.write-ui', status: 'draft' }));
|
||||||
|
expect(permissions).toContainEqual(expect.objectContaining({ name: 'secrets.read', dangerous: true }));
|
||||||
|
expect(permissions).toContainEqual(expect.objectContaining({ name: 'secrets.write', dangerous: true }));
|
||||||
|
expect(permissionEnum).toEqual(expect.arrayContaining(['secrets.read', 'secrets.write']));
|
||||||
|
});
|
||||||
|
|
||||||
test('file.changed schema documents watcher refresh payload', () => {
|
test('file.changed schema documents watcher refresh payload', () => {
|
||||||
const fileChanged = (vaultEventsSchema as any).events.find((event: any) => event.name === 'file.changed');
|
const fileChanged = (vaultEventsSchema as any).events.find((event: any) => event.name === 'file.changed');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue