Harden agent-friendly discovery and one-shot execution
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Tests for CLI commands."""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
@@ -67,6 +68,159 @@ class TestListCommand:
|
||||
assert 'test-tool' in captured.out
|
||||
assert 'another-tool' in captured.out
|
||||
|
||||
def test_json_is_compact_catalog_unless_full_requested(
|
||||
self, temp_tools_dir, capsys
|
||||
):
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
save_tool(Tool(
|
||||
name="catalog-tool",
|
||||
description="Catalog entry",
|
||||
input_schema={"type": "string"},
|
||||
output_schema={"type": "string"},
|
||||
steps=[PromptStep(
|
||||
prompt="Private implementation {input}",
|
||||
provider="mock",
|
||||
output_var="result",
|
||||
plain_text=True,
|
||||
)],
|
||||
output="{result}",
|
||||
))
|
||||
|
||||
with patch('sys.argv', ['cmdforge', 'list', '--json']):
|
||||
assert main() == 0
|
||||
compact = json.loads(capsys.readouterr().out)
|
||||
assert compact[0]["name"] == "catalog-tool"
|
||||
assert compact[0]["input_schema"] == {"type": "string"}
|
||||
assert "steps" not in compact[0]
|
||||
|
||||
with patch('sys.argv', ['cmdforge', 'list', '--json', '--full']):
|
||||
assert main() == 0
|
||||
full = json.loads(capsys.readouterr().out)
|
||||
assert full[0]["steps"][0]["prompt"] == "Private implementation {input}"
|
||||
|
||||
def test_filter_reports_filtered_count_and_empty_result(
|
||||
self, temp_tools_dir, capsys
|
||||
):
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
save_tool(Tool(name="alpha", description="First"))
|
||||
save_tool(Tool(name="beta", description="Second"))
|
||||
|
||||
with patch('sys.argv', ['cmdforge', 'list', '--filter', 'alpha']):
|
||||
assert main() == 0
|
||||
output = capsys.readouterr().out
|
||||
assert "Available tools (1)" in output
|
||||
assert "alpha" in output
|
||||
assert "beta" not in output
|
||||
|
||||
with patch('sys.argv', ['cmdforge', 'list', '--filter', 'missing']):
|
||||
assert main() == 0
|
||||
assert "No tools match filter 'missing'" in capsys.readouterr().out
|
||||
|
||||
def test_json_limit_and_invalid_limit(self, temp_tools_dir, capsys):
|
||||
from cmdforge.tool import save_tool
|
||||
|
||||
save_tool(Tool(name="alpha"))
|
||||
save_tool(Tool(name="beta"))
|
||||
with patch('sys.argv', ['cmdforge', 'list', '--json', '--limit', '1']):
|
||||
assert main() == 0
|
||||
assert len(json.loads(capsys.readouterr().out)) == 1
|
||||
|
||||
with patch('sys.argv', ['cmdforge', 'list', '--limit', '0']):
|
||||
assert main() == 1
|
||||
assert "at least 1" in capsys.readouterr().err
|
||||
|
||||
with patch('sys.argv', ['cmdforge', 'list', '--full']):
|
||||
assert main() == 1
|
||||
assert "--full requires --json" in capsys.readouterr().err
|
||||
|
||||
|
||||
class TestRunOnceCommand:
|
||||
def test_combines_instruction_with_piped_input(self, capsys):
|
||||
result = SimpleNamespace(success=True, text="summary", error=None)
|
||||
with (
|
||||
patch('sys.argv', [
|
||||
'cmdforge', 'run-once', 'Summarize:\n{input}',
|
||||
'--provider', 'mock',
|
||||
]),
|
||||
patch('sys.stdin', StringIO('document body')),
|
||||
patch('cmdforge.providers.call_provider', return_value=result) as call_provider,
|
||||
):
|
||||
assert main() == 0
|
||||
|
||||
assert call_provider.call_args.args[:2] == (
|
||||
'mock', 'Summarize:\ndocument body'
|
||||
)
|
||||
assert capsys.readouterr().out == "summary\n"
|
||||
|
||||
def test_appends_piped_input_when_template_has_no_placeholder(self):
|
||||
result = SimpleNamespace(success=True, text="ok", error=None)
|
||||
with (
|
||||
patch('sys.argv', [
|
||||
'cmdforge', 'run-once', 'Summarize this', '--provider', 'mock',
|
||||
]),
|
||||
patch('sys.stdin', StringIO('document body')),
|
||||
patch('cmdforge.providers.call_provider', return_value=result) as call_provider,
|
||||
):
|
||||
assert main() == 0
|
||||
|
||||
assert call_provider.call_args.args[1] == "Summarize this\n\ndocument body"
|
||||
|
||||
def test_uses_configured_default_provider(self):
|
||||
result = SimpleNamespace(success=True, text="ok", error=None)
|
||||
config = SimpleNamespace(default_provider="configured-provider")
|
||||
stdin = MagicMock()
|
||||
stdin.isatty.return_value = True
|
||||
with (
|
||||
patch('sys.argv', ['cmdforge', 'run-once', 'hello']),
|
||||
patch('sys.stdin', stdin),
|
||||
patch('cmdforge.config.load_config', return_value=config),
|
||||
patch('cmdforge.providers.call_provider', return_value=result) as call_provider,
|
||||
):
|
||||
assert main() == 0
|
||||
assert call_provider.call_args.args[0] == "configured-provider"
|
||||
|
||||
def test_selects_first_available_balanced_provider(self):
|
||||
from cmdforge.cli.tool_commands import _resolve_one_shot_provider
|
||||
from cmdforge.providers import Provider
|
||||
|
||||
config = SimpleNamespace(default_provider=None)
|
||||
providers = [
|
||||
Provider("opencode-pickle", "missing-opencode"),
|
||||
Provider("codex", "codex exec -"),
|
||||
]
|
||||
with (
|
||||
patch('cmdforge.config.load_config', return_value=config),
|
||||
patch('cmdforge.providers.load_providers', return_value=providers),
|
||||
patch(
|
||||
'cmdforge.cli.tool_commands._provider_is_available',
|
||||
side_effect=lambda provider: provider.name == "codex",
|
||||
),
|
||||
):
|
||||
assert _resolve_one_shot_provider() == "codex"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"arguments,message",
|
||||
[
|
||||
(["--timeout", "0"], "--timeout"),
|
||||
(["--max-tokens", "-1"], "--max-tokens"),
|
||||
],
|
||||
)
|
||||
def test_rejects_invalid_limits_before_provider_call(
|
||||
self, arguments, message, capsys
|
||||
):
|
||||
with (
|
||||
patch('sys.argv', [
|
||||
'cmdforge', 'run-once', 'hello', '--provider', 'mock', *arguments,
|
||||
]),
|
||||
patch('sys.stdin', StringIO()),
|
||||
patch('cmdforge.providers.call_provider') as call_provider,
|
||||
):
|
||||
assert main() == 1
|
||||
call_provider.assert_not_called()
|
||||
assert message in capsys.readouterr().err
|
||||
|
||||
|
||||
class TestCreateCommand:
|
||||
"""Tests for 'cmdforge create' command."""
|
||||
|
||||
Reference in New Issue
Block a user