mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
import { execFile } from 'node:child_process';
|
|||
|
|
import http from 'node:http';
|
||
|
|
import { dirname, resolve as pathResolve } from 'node:path';
|
||
|
|
import { fileURLToPath } from 'node:url';
|
||
|
|
import { promisify } from 'node:util';
|
||
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||
|
|
|
||
|
|
const execFileP = promisify(execFile);
|
||
|
|
const currentDir = dirname(fileURLToPath(import.meta.url));
|
||
|
|
const daemonRoot = pathResolve(currentDir, '..');
|
||
|
|
const repoRoot = pathResolve(currentDir, '../../..');
|
||
|
|
const cliSource = pathResolve(currentDir, '../src/cli.ts');
|
||
|
|
const tsxCli = pathResolve(repoRoot, 'node_modules/tsx/dist/cli.mjs');
|
||
|
|
|
||
|
|
interface CapturedRequest {
|
||
|
|
method: string;
|
||
|
|
url: string;
|
||
|
|
headers: http.IncomingHttpHeaders;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('od plugin exact workspace transport', () => {
|
||
|
|
const requests: CapturedRequest[] = [];
|
||
|
|
let server: http.Server;
|
||
|
|
let baseUrl: string;
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
server = http.createServer((req, res) => {
|
||
|
|
requests.push({
|
||
|
|
method: req.method ?? '',
|
||
|
|
url: req.url ?? '',
|
||
|
|
headers: req.headers,
|
||
|
|
});
|
||
|
|
res.statusCode = 200;
|
||
|
|
if (req.url === '/api/plugins') {
|
||
|
|
res.setHeader('content-type', 'application/json');
|
||
|
|
res.end(JSON.stringify({ plugins: [] }));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
res.setHeader('content-type', 'application/json');
|
||
|
|
res.end(JSON.stringify({
|
||
|
|
ok: true,
|
||
|
|
snapshotId: 'snapshot-a',
|
||
|
|
pluginId: 'plugin-a',
|
||
|
|
capabilitiesGranted: [],
|
||
|
|
issues: [],
|
||
|
|
freshDigest: '1234567890123456',
|
||
|
|
files: [],
|
||
|
|
folder: '/tmp/exported-plugin',
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||
|
|
const address = server.address();
|
||
|
|
if (!address || typeof address === 'string') throw new Error('stub server has no address');
|
||
|
|
baseUrl = `http://127.0.0.1:${address.port}`;
|
||
|
|
});
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
await new Promise<void>((resolve, reject) => {
|
||
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
requests.length = 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
async function runCli(args: string[]) {
|
||
|
|
try {
|
||
|
|
const { stdout, stderr } = await execFileP(
|
||
|
|
process.execPath,
|
||
|
|
[tsxCli, cliSource, ...args],
|
||
|
|
{
|
||
|
|
cwd: daemonRoot,
|
||
|
|
env: { ...process.env, NODE_OPTIONS: undefined },
|
||
|
|
timeout: 15_000,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
return { code: 0, stdout, stderr };
|
||
|
|
} catch (error) {
|
||
|
|
const failed = error as { code?: number; stdout?: string; stderr?: string };
|
||
|
|
return {
|
||
|
|
code: failed.code ?? 1,
|
||
|
|
stdout: failed.stdout ?? '',
|
||
|
|
stderr: failed.stderr ?? '',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
it.each([
|
||
|
|
['list', ['list', '--json']],
|
||
|
|
['info', ['info', 'plugin-a', '--json']],
|
||
|
|
['apply', ['apply', 'plugin-a', '--json']],
|
||
|
|
['trust', ['trust', 'plugin-a', '--capabilities', 'fs:read', '--json']],
|
||
|
|
['uninstall', ['uninstall', 'plugin-a']],
|
||
|
|
['snapshot show', ['snapshots', 'show', 'snapshot-a', '--json']],
|
||
|
|
['canon', ['canon', 'snapshot-a', '--json']],
|
||
|
|
[
|
||
|
|
'export',
|
||
|
|
['export', 'project-a', '--as', 'od', '--out', '/tmp/exported-plugin', '--json'],
|
||
|
|
],
|
||
|
|
])('sends exact workspace headers for %s', async (_label, command) => {
|
||
|
|
const result = await runCli([
|
||
|
|
'plugin',
|
||
|
|
...command,
|
||
|
|
'--workspace',
|
||
|
|
'workspace-a',
|
||
|
|
'--workspace-member',
|
||
|
|
'member-a',
|
||
|
|
'--daemon-url',
|
||
|
|
baseUrl,
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect(result.code).toBe(0);
|
||
|
|
expect(requests).toHaveLength(1);
|
||
|
|
expect(requests[0]?.headers).toMatchObject({
|
||
|
|
'x-od-workspace-id': 'workspace-a',
|
||
|
|
'x-od-workspace-member-id': 'member-a',
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects an incomplete plugin scope before making a request', async () => {
|
||
|
|
const result = await runCli([
|
||
|
|
'plugin',
|
||
|
|
'list',
|
||
|
|
'--workspace',
|
||
|
|
'workspace-a',
|
||
|
|
'--daemon-url',
|
||
|
|
baseUrl,
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect(result.code).not.toBe(0);
|
||
|
|
expect(result.stderr).toContain('--workspace-member');
|
||
|
|
expect(requests).toHaveLength(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('keeps headerless local CLI compatibility when both flags are absent', async () => {
|
||
|
|
const result = await runCli([
|
||
|
|
'plugin',
|
||
|
|
'list',
|
||
|
|
'--json',
|
||
|
|
'--daemon-url',
|
||
|
|
baseUrl,
|
||
|
|
]);
|
||
|
|
|
||
|
|
expect(result.code).toBe(0);
|
||
|
|
expect(requests).toHaveLength(1);
|
||
|
|
expect(requests[0]?.headers['x-od-workspace-id']).toBeUndefined();
|
||
|
|
expect(requests[0]?.headers['x-od-workspace-member-id']).toBeUndefined();
|
||
|
|
});
|
||
|
|
});
|