This commit is contained in:
147
packages/agent/tests/cli-tools.test.ts
Normal file
147
packages/agent/tests/cli-tools.test.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { createCliTools } from '../src/cli-tools.js';
|
||||
|
||||
describe('cli_discover', () => {
|
||||
it('scans PATH and returns available CLIs', async () => {
|
||||
const tools = createCliTools({ allowlist: [] });
|
||||
const discover = tools.find(t => t.name === 'cli_discover')!;
|
||||
const result = JSON.parse(await discover.execute({}));
|
||||
|
||||
// At minimum, node and npm should be found (we're in a Node.js environment)
|
||||
expect(result.found).toBeGreaterThanOrEqual(1);
|
||||
expect(result.programs.some((p: { name: string }) => p.name === 'node')).toBe(true);
|
||||
});
|
||||
|
||||
it('marks allowed programs correctly', async () => {
|
||||
const tools = createCliTools({ allowlist: ['node'] });
|
||||
const discover = tools.find(t => t.name === 'cli_discover')!;
|
||||
const result = JSON.parse(await discover.execute({}));
|
||||
|
||||
const nodeProg = result.programs.find((p: { name: string }) => p.name === 'node');
|
||||
expect(nodeProg?.allowed).toBe(true);
|
||||
|
||||
// git may or may not be present, but if it is, it shouldn't be allowed
|
||||
const gitProg = result.programs.find((p: { name: string }) => p.name === 'git');
|
||||
if (gitProg) {
|
||||
expect(gitProg.allowed).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns version info for found programs', async () => {
|
||||
const tools = createCliTools({ allowlist: [] });
|
||||
const discover = tools.find(t => t.name === 'cli_discover')!;
|
||||
const result = JSON.parse(await discover.execute({}));
|
||||
|
||||
const nodeProg = result.programs.find((p: { name: string }) => p.name === 'node');
|
||||
expect(nodeProg?.version).toBeTruthy();
|
||||
expect(nodeProg?.version.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli_execute', () => {
|
||||
it('executes allowed CLI program', async () => {
|
||||
const tools = createCliTools({ allowlist: ['node'] });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
const result = JSON.parse(await execute.execute({
|
||||
program: 'node',
|
||||
args: ['--version'],
|
||||
}));
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toMatch(/^v\d+/);
|
||||
});
|
||||
|
||||
it('rejects programs not in allowlist', async () => {
|
||||
const tools = createCliTools({ allowlist: ['node'] });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
const result = JSON.parse(await execute.execute({
|
||||
program: 'curl',
|
||||
args: ['--version'],
|
||||
}));
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('not in the CLI allowlist');
|
||||
});
|
||||
|
||||
it('respects wildcard allowlist', async () => {
|
||||
const tools = createCliTools({ allowlist: ['*'] });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
const result = JSON.parse(await execute.execute({
|
||||
program: 'node',
|
||||
args: ['--version'],
|
||||
}));
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('captures stdout and stderr separately', async () => {
|
||||
const tools = createCliTools({ allowlist: ['node'] });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
const result = JSON.parse(await execute.execute({
|
||||
program: 'node',
|
||||
args: ['-e', 'console.log("out"); console.error("err")'],
|
||||
}));
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.stdout).toBe('out');
|
||||
expect(result.stderr).toBe('err');
|
||||
});
|
||||
|
||||
it('returns exit code in result', async () => {
|
||||
const tools = createCliTools({ allowlist: ['node'] });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
const result = JSON.parse(await execute.execute({
|
||||
program: 'node',
|
||||
args: ['-e', 'process.exit(42)'],
|
||||
}));
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
// Node.js will throw on non-zero exit code via execFile
|
||||
expect(result.error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('logs execution to audit trail', async () => {
|
||||
const auditLog = vi.fn();
|
||||
const tools = createCliTools({ allowlist: ['node'], auditLog });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
await execute.execute({ program: 'node', args: ['--version'] });
|
||||
|
||||
expect(auditLog).toHaveBeenCalledWith({
|
||||
actionType: 'cli.execute.node',
|
||||
description: 'CLI: node --version',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles empty allowlist', async () => {
|
||||
const tools = createCliTools({ allowlist: [] });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
const result = JSON.parse(await execute.execute({
|
||||
program: 'node',
|
||||
args: ['--version'],
|
||||
}));
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('not in the CLI allowlist');
|
||||
});
|
||||
|
||||
it('reads an updated allowlist without recreating the tools', async () => {
|
||||
let allowlist: string[] = [];
|
||||
const tools = createCliTools({ allowlist, getAllowlist: () => allowlist });
|
||||
const execute = tools.find(t => t.name === 'cli_execute')!;
|
||||
|
||||
const denied = JSON.parse(await execute.execute({ program: 'node', args: ['--version'] }));
|
||||
expect(denied.success).toBe(false);
|
||||
|
||||
allowlist = ['node'];
|
||||
const allowed = JSON.parse(await execute.execute({ program: 'node', args: ['--version'] }));
|
||||
expect(allowed.success).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user