Files
openswarm/openswarm-runner/tests/test_seed_skills.py
T

84 lines
3.5 KiB
Python

"""The user's know-how travelling up, and their app credentials not travelling at all."""
import os
import pytest
from pydantic import ValidationError
from runner.run_spec import McpServerNote, RunSpec, SkillPayload
from runner.seed.data_root import settings_for_run, unavailable_apps_note
from runner.seed.skills import skills_dir, write_skills
COFFEE = {
"id": "coffee-ratio",
"files": [
{"path": "SKILL.md", "text": "---\nname: coffee-ratio\ndescription: house ratio\n---\n\n1:16.5\n"},
{"path": "scripts/brew.py", "text": "print('brew')\n"},
],
}
def p_spec(**overrides) -> RunSpec:
body = {
"run_id": "cr-1",
"workflow": {"id": "wf-1", "title": "Test", "steps": [{"id": "s1", "text": "go"}]},
"credentials": [{"provider": "anthropic", "auth_type": "api_key", "api_key": "sk-test"}],
}
body.update(overrides)
return RunSpec.model_validate(body)
def test_a_skill_folder_lands_where_the_backend_looks_for_it(tmp_path) -> None:
written = write_skills(str(tmp_path), [SkillPayload.model_validate(COFFEE)])
assert written == 1
root = skills_dir(str(tmp_path))
assert root.endswith(os.path.join(".claude", "skills"))
with open(os.path.join(root, "coffee-ratio", "SKILL.md"), encoding="utf-8") as handle:
assert "house ratio" in handle.read()
assert os.path.isfile(os.path.join(root, "coffee-ratio", "scripts", "brew.py"))
def test_no_skills_is_an_empty_directory_not_a_failure(tmp_path) -> None:
assert write_skills(str(tmp_path), []) == 0
assert os.path.isdir(skills_dir(str(tmp_path)))
def test_a_skill_with_no_skill_md_is_refused_before_a_machine_boots() -> None:
with pytest.raises(ValidationError, match="no SKILL.md"):
SkillPayload.model_validate({"id": "x", "files": [{"path": "README.md", "text": "hi"}]})
@pytest.mark.parametrize("bad", ["../escape.md", "/etc/passwd", "a/../../b.md", "a\\b.md", ""])
def test_a_skill_path_that_could_climb_out_is_refused(bad: str) -> None:
with pytest.raises(ValidationError):
SkillPayload.model_validate({"id": "x", "files": [{"path": bad, "text": "hi"}]})
@pytest.mark.parametrize("bad", ["../evil", "a/b", ".hidden", "has space", ""])
def test_a_skill_id_that_is_not_a_plain_folder_name_is_refused(bad: str) -> None:
with pytest.raises(ValidationError):
SkillPayload.model_validate({"id": bad, "files": [{"path": "SKILL.md", "text": "hi"}]})
def test_the_spec_cannot_carry_an_mcp_secret_at_all() -> None:
# extra="forbid" is the wall: there is no field for a token, so a payload with one dies here
# rather than landing in a container that runs the user's own prose with Bash.
with pytest.raises(ValidationError):
McpServerNote.model_validate({"name": "Notion", "access_token": "secret_abc"})
with pytest.raises(ValidationError):
McpServerNote.model_validate({"name": "Slack", "env": {"SLACK_MCP_XOXC_TOKEN": "xoxc-1"}})
def test_unreachable_apps_are_named_in_the_prompt_so_silence_is_not_mistaken_for_absence(tmp_path) -> None:
spec = p_spec(unavailable_mcp_servers=[{"name": "Notion"}, {"name": "Google Workspace"}])
note = unavailable_apps_note(spec)
assert "Notion" in note and "Google Workspace" in note
assert "cannot be done from a cloud run" in note
assert note in (settings_for_run(spec, str(tmp_path)).default_system_prompt or "")
def test_with_no_connected_apps_nothing_is_added_to_the_prompt(tmp_path) -> None:
assert unavailable_apps_note(p_spec()) == ""