84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||
|
|
import { writeFileSync, readFileSync, mkdtempSync, rmSync, mkdirSync, existsSync } from 'node:fs';
|
||
|
|
import { join } from 'node:path';
|
||
|
|
import { tmpdir } from 'node:os';
|
||
|
|
import { createConfigCommand } from '../../src/commands/config.js';
|
||
|
|
import type { ApiClient } from '../../src/api-client.js';
|
||
|
|
|
||
|
|
function mockClient(visibleSkills: unknown[] = []): ApiClient {
|
||
|
|
return {
|
||
|
|
get: vi.fn(async (path: string) => {
|
||
|
|
if (path.endsWith('/skills/visible')) return visibleSkills;
|
||
|
|
if (path.startsWith('/api/v1/skills/')) return {
|
||
|
|
id: 's1', name: 'demo', description: 'd', semver: '1.0.0',
|
||
|
|
content: '# demo\n\nSkill body\n', files: {}, metadata: {}, projectId: null, agentId: null,
|
||
|
|
};
|
||
|
|
return {};
|
||
|
|
}),
|
||
|
|
post: vi.fn(async () => ({ token: 't', user: { email: 'x' } })),
|
||
|
|
put: vi.fn(async () => ({})),
|
||
|
|
delete: vi.fn(async () => {}),
|
||
|
|
} as unknown as ApiClient;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('config pi', () => {
|
||
|
|
let client: ReturnType<typeof mockClient>;
|
||
|
|
let output: string[];
|
||
|
|
let tmpDir: string;
|
||
|
|
const log = (...args: string[]) => output.push(args.join(' '));
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
client = mockClient();
|
||
|
|
output = [];
|
||
|
|
tmpDir = mkdtempSync(join(tmpdir(), 'mcpctl-config-pi-'));
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('installs the extension + registers with a custom pi dir (skips skills)', async () => {
|
||
|
|
// Stage a src/pi-ext source tree to install from.
|
||
|
|
const srcPiExt = join(tmpDir, 'src-pi-ext');
|
||
|
|
mkdirSync(srcPiExt, { recursive: true });
|
||
|
|
writeFileSync(join(srcPiExt, 'mcpctl-pi.ts'), '// a\n');
|
||
|
|
writeFileSync(join(srcPiExt, 'mcp-http.ts'), '// b\n');
|
||
|
|
|
||
|
|
const piDir = join(tmpDir, 'pi-agent');
|
||
|
|
const cmd = createConfigCommand(
|
||
|
|
{ configDeps: { configDir: tmpDir }, log },
|
||
|
|
{ client, credentialsDeps: { configDir: tmpDir }, log },
|
||
|
|
);
|
||
|
|
|
||
|
|
await cmd.parseAsync(
|
||
|
|
['pi', '--project', 'docmost', '--extension-dir', srcPiExt, '--pi-dir', piDir, '--skip-skills'],
|
||
|
|
{ from: 'user' },
|
||
|
|
);
|
||
|
|
|
||
|
|
// Extension files landed in the pi dir.
|
||
|
|
expect(existsSync(join(piDir, 'extensions', 'mcpctl', 'mcpctl-pi.ts'))).toBe(true);
|
||
|
|
expect(existsSync(join(piDir, 'extensions', 'mcpctl', 'mcp-http.ts'))).toBe(true);
|
||
|
|
|
||
|
|
// settings.json updated.
|
||
|
|
const settings = JSON.parse(readFileSync(join(piDir, 'settings.json'), 'utf-8'));
|
||
|
|
expect(settings.extensions).toContain(join(piDir, 'extensions', 'mcpctl', 'mcpctl-pi.ts'));
|
||
|
|
expect(settings.skills).toContain(join(piDir, 'skills'));
|
||
|
|
|
||
|
|
// Active project persisted under the pi dir (fully isolated).
|
||
|
|
const state = JSON.parse(readFileSync(join(piDir, 'pi-state.json'), 'utf-8'));
|
||
|
|
expect(state.project).toBe('docmost');
|
||
|
|
|
||
|
|
// Output mentions the write.
|
||
|
|
expect(output.join('\n')).toContain('Wrote active project');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('requires a project', async () => {
|
||
|
|
const cmd = createConfigCommand(
|
||
|
|
{ configDeps: { configDir: tmpDir }, log },
|
||
|
|
{ client, credentialsDeps: { configDir: tmpDir }, log },
|
||
|
|
);
|
||
|
|
await cmd.parseAsync(['pi', '--pi-dir', join(tmpDir, 'x'), '--skip-skills'], { from: 'user' });
|
||
|
|
expect(output.join('\n')).toContain('--project is required');
|
||
|
|
});
|
||
|
|
});
|