test(skills): cover install and execute option parsing

This commit is contained in:
William Valentin
2026-02-12 19:38:16 -08:00
parent 8affe8bea9
commit 81d5c4d730
2 changed files with 143 additions and 2 deletions
+133
View File
@@ -2,6 +2,7 @@ import { describe, it, expect, vi } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, existsSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { Command } from 'commander';
import { SkillInstaller } from '../skills/index.js';
import {
toSkillListRows,
@@ -27,6 +28,7 @@ import {
resolveSkillInstallerCommandRunner,
runSkillExecuteAction,
runSkillInstallAction,
registerSkillsCommand,
} from './skills.js';
import type { Skill } from '../skills/index.js';
@@ -46,6 +48,23 @@ function buildSkill(overrides: Partial<Skill>): Skill {
};
}
function writeSkillsCliConfig(configPath: string, opts: { managedDir: string; bundledDir: string; workspaceDir: string }): void {
writeFileSync(
configPath,
[
'models:',
' default:',
' provider: ollama',
' model: test-model',
'skills:',
` managed_dir: ${opts.managedDir}`,
` bundled_dir: ${opts.bundledDir}`,
` workspace_dir: ${opts.workspaceDir}`,
].join('\n'),
'utf-8',
);
}
describe('skills CLI helpers', () => {
it('maps and sorts skill rows', () => {
const rows = toSkillListRows([
@@ -870,4 +889,118 @@ describe('skills CLI helpers', () => {
rmSync(root, { recursive: true, force: true });
});
it('skills install reports invalid runner via CLI option parsing path', async () => {
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
const configPath = join(root, 'config.yaml');
const managedDir = join(root, 'managed');
const bundledDir = join(root, 'bundled');
const workspaceDir = join(root, 'workspace');
mkdirSync(managedDir, { recursive: true });
mkdirSync(bundledDir, { recursive: true });
mkdirSync(workspaceDir, { recursive: true });
writeSkillsCliConfig(configPath, { managedDir, bundledDir, workspaceDir });
const program = new Command();
registerSkillsCommand(program);
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
process.exitCode = undefined;
await program.parseAsync(['skills', 'install', '/tmp/any-skill', '--runner', 'invalid', '-c', configPath], { from: 'user' });
expect(errorSpy).toHaveBeenCalledWith("Invalid runner 'invalid'. Allowed values: noop, shell.");
expect(process.exitCode).toBe(1);
errorSpy.mockRestore();
process.exitCode = undefined;
rmSync(root, { recursive: true, force: true });
});
it('skills install parses execute flags and emits execution-enabled JSON receipt', async () => {
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
const configPath = join(root, 'config.yaml');
const sourceDir = join(root, 'source-skill');
const managedDir = join(root, 'managed');
const bundledDir = join(root, 'bundled');
const workspaceDir = join(root, 'workspace');
mkdirSync(sourceDir, { recursive: true });
mkdirSync(managedDir, { recursive: true });
mkdirSync(bundledDir, { recursive: true });
mkdirSync(workspaceDir, { recursive: true });
writeSkillsCliConfig(configPath, { managedDir, bundledDir, workspaceDir });
writeFileSync(join(sourceDir, 'SKILL.md'), '# Install Skill\nInstructions');
writeFileSync(
join(sourceDir, 'manifest.json'),
JSON.stringify({
name: 'cli-install-skill',
description: 'CLI install parse',
version: '1.0.0',
installers: [{ type: 'download', url: 'https://example.com/cli-install.tgz' }],
}),
'utf-8',
);
const program = new Command();
registerSkillsCommand(program);
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
process.exitCode = undefined;
await program.parseAsync(
['skills', 'install', sourceDir, '--json', '--execute', '--confirm', '--runner', 'noop', '-c', configPath],
{ from: 'user' },
);
const payload = JSON.parse(String(logSpy.mock.calls[logSpy.mock.calls.length - 1]?.[0]));
expect(payload.execution.execution_enabled).toBe(true);
expect(payload.execution.reason).toBe('execution_enabled');
logSpy.mockRestore();
process.exitCode = undefined;
rmSync(root, { recursive: true, force: true });
});
it('skills execute parses execute flags and emits execution-enabled JSON receipt', async () => {
const root = mkdtempSync(join(tmpdir(), 'flynn-skills-cli-'));
const configPath = join(root, 'config.yaml');
const managedDir = join(root, 'managed');
const bundledDir = join(root, 'bundled');
const workspaceDir = join(root, 'workspace');
const skillDir = join(managedDir, 'cli-exec-skill');
mkdirSync(skillDir, { recursive: true });
mkdirSync(bundledDir, { recursive: true });
mkdirSync(workspaceDir, { recursive: true });
writeSkillsCliConfig(configPath, { managedDir, bundledDir, workspaceDir });
writeFileSync(join(skillDir, 'SKILL.md'), '# Execute Skill\nInstructions');
writeFileSync(
join(skillDir, 'manifest.json'),
JSON.stringify({
name: 'cli-exec-skill',
description: 'CLI execute parse',
version: '1.0.0',
installers: [{ type: 'download', url: 'https://example.com/cli-exec.tgz' }],
}),
'utf-8',
);
const program = new Command();
registerSkillsCommand(program);
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
process.exitCode = undefined;
await program.parseAsync(
['skills', 'execute', 'cli-exec-skill', '--json', '--execute', '--confirm', '--runner', 'noop', '-c', configPath],
{ from: 'user' },
);
const payload = JSON.parse(String(logSpy.mock.calls[0]?.[0]));
expect(payload.execution_enabled).toBe(true);
expect(payload.reason).toBe('execution_enabled');
logSpy.mockRestore();
process.exitCode = undefined;
rmSync(root, { recursive: true, force: true });
});
});