Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
67 lines
2.4 KiB
Python
67 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Regression tests for dispatch() in demo.py.
|
|
|
|
Bug: the agentic loop parses tool-call arguments with a JSONDecodeError
|
|
fallback to {} and then calls dispatch() with no try/except. dispatch()
|
|
used to do args["name"] / args["payload"] etc., so any malformed or
|
|
incomplete LLM tool call crashed the whole run with KeyError. Fixed to
|
|
return "[error] ..." strings that the agent can recover from.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from demo import dispatch, scan_skill_catalog
|
|
|
|
OUT = Path("/tmp/test_dispatch_out.pptx")
|
|
|
|
|
|
def test_missing_name_returns_error_not_keyerror():
|
|
catalog = scan_skill_catalog()
|
|
# {} is exactly what the JSONDecodeError fallback in run_agent produces
|
|
result = dispatch(catalog, "read_skill", {}, OUT)
|
|
assert result.startswith("[error]")
|
|
assert "name" in result
|
|
|
|
|
|
def test_missing_payload_returns_error_not_keyerror():
|
|
catalog = scan_skill_catalog()
|
|
result = dispatch(catalog, "run_skill_script",
|
|
{"name": "pptx", "script": "generate_pptx.py"}, OUT)
|
|
assert result.startswith("[error]")
|
|
assert "payload" in result
|
|
|
|
|
|
def test_unknown_tool_still_returns_error():
|
|
catalog = scan_skill_catalog()
|
|
result = dispatch(catalog, "no_such_tool", {}, OUT)
|
|
assert result.startswith("[error]")
|
|
|
|
|
|
def test_valid_read_skill_still_works():
|
|
catalog = scan_skill_catalog()
|
|
result = dispatch(catalog, "read_skill", {"name": "pptx"}, OUT)
|
|
assert not result.startswith("[error]")
|
|
assert len(result) > 0
|
|
|
|
|
|
def test_run_skill_script_rejects_absolute_path(tmp_path):
|
|
"""run_skill_script executes the file, so it must stay inside scripts/."""
|
|
outside = tmp_path / "evil.py"
|
|
outside.write_text("raise AssertionError('executed out-of-tree script')")
|
|
catalog = scan_skill_catalog()
|
|
result = dispatch(catalog, "run_skill_script",
|
|
{"name": "pptx", "script": str(outside), "payload": "{}"}, OUT)
|
|
assert result.startswith("[error]")
|
|
|
|
|
|
def test_run_skill_script_rejects_parent_traversal(tmp_path):
|
|
outside = tmp_path / "evil.py"
|
|
outside.write_text("raise AssertionError('executed out-of-tree script')")
|
|
catalog = scan_skill_catalog()
|
|
scripts_dir = (catalog["pptx"]["dir"] / "scripts").resolve()
|
|
rel = os.path.relpath(outside, scripts_dir)
|
|
result = dispatch(catalog, "run_skill_script",
|
|
{"name": "pptx", "script": rel, "payload": "{}"}, OUT)
|
|
assert result.startswith("[error]")
|