# retoor import uuid import pytest import requests from starlette.testclient import TestClient import devplacepy.main as m from devplacepy.database import get_table from devplacepy.database.skills import get_skill_row from devplacepy.routers import skills as skills_router from devplacepy.services.skills import limits from devplacepy.services.skills.fetcher import FetchedSkill, SkillFetchError, SkippedFile from devplacepy.services.skills.parser import parse_skill_md from devplacepy.services.skills.prompt import skill_index_lines from tests.conftest import BASE_URL JSON = {"Accept": "application/json"} def _signup_install(): name = f"ins{uuid.uuid4().hex[:10]}" requests.post( f"{BASE_URL}/auth/signup", data={ "username": name, "email": f"{name}@t.dev", "password": "secret123", "confirm_password": "secret123", "birth_date": "1990-01-01", "accept_terms": "1", }, allow_redirects=True, ) return get_table("users").find_one(username=name) def _fetched(name="installed-skill", description="Installed through the route."): parsed = parse_skill_md(f"---\nname: {name}\ndescription: {description}\n---\n# Body\n\n`scripts/run.py`") return FetchedSkill( parsed=parsed, source_url=f"https://skills.example/{name}/SKILL.md", files={"scripts/run.py": "print(1)"}, skipped=[SkippedFile("assets/logo.png", "binary or unsupported file type; only text files are stored")], warnings=["a warning"], ) @pytest.fixture def client(app_server): return TestClient(m.app) def _headers(user): return {"X-API-KEY": user["api_key"], **JSON} def test_install_through_the_route_stores_audits_and_feeds_the_prompt(client, monkeypatch): user = _signup_install() async def fake_fetch(url): return _fetched() monkeypatch.setattr(skills_router, "fetch_skill", fake_fetch) response = client.post("/skills/install", data={"url": "https://skills.example/x"}, headers=_headers(user)) assert response.status_code == 200 body = response.json() assert body["action"] == "install" skill = body["skill"] assert skill["name"] == "installed-skill" assert skill["files"] == {"scripts/run.py": "print(1)"} assert skill["skipped"] == [ {"path": "assets/logo.png", "reason": "binary or unsupported file type; only text files are stored"} ] assert skill["warnings"] == ["a warning"] assert len(skill["sha256"]) == 64 assert get_skill_row(user["uid"], "installed-skill")["enabled"] == 1 assert "installed-skill: Installed through the route." in skill_index_lines(user["uid"]) audit = [e for e in get_table("audit_log").find(target_uid=skill["uid"]) if e["event_key"] == "skill.install"] assert len(audit) == 1 and audit[0]["result"] == "success" def test_reinstalling_updates_in_place_and_keeps_the_uid(client, monkeypatch): user = _signup_install() calls = {"description": "First."} async def fake_fetch(url): return _fetched(description=calls["description"]) monkeypatch.setattr(skills_router, "fetch_skill", fake_fetch) first = client.post("/skills/install", data={"url": "https://skills.example/x"}, headers=_headers(user)).json() calls["description"] = "Second." second = client.post("/skills/install", data={"url": "https://skills.example/x"}, headers=_headers(user)).json() assert first["action"] == "install" and second["action"] == "update" assert first["skill"]["uid"] == second["skill"]["uid"] assert second["skill"]["description"] == "Second." listing = client.get("/skills", headers=_headers(user)).json() assert listing["skill_total"] == 1 keys = [e["event_key"] for e in get_table("audit_log").find(target_uid=first["skill"]["uid"])] assert "skill.install" in keys and "skill.update" in keys def test_a_failed_fetch_is_audited_as_a_failure_and_stores_nothing(client, monkeypatch): user = _signup_install() async def failing_fetch(url): raise SkillFetchError("the server returned 500") monkeypatch.setattr(skills_router, "fetch_skill", failing_fetch) response = client.post("/skills/install", data={"url": "https://skills.example/x"}, headers=_headers(user)) assert response.status_code == 400 assert response.json()["error"]["message"] == "the server returned 500" assert client.get("/skills", headers=_headers(user)).json()["skill_total"] == 0 failures = [ e for e in get_table("audit_log").find(actor_uid=user["uid"]) if e["event_key"] == "skill.install" and e["result"] == "failure" ] assert len(failures) == 1 def test_the_per_user_cap_refuses_a_new_install_but_allows_an_update(client, monkeypatch): user = _signup_install() current = {"name": "cap-0"} async def fake_fetch(url): return _fetched(name=current["name"]) monkeypatch.setattr(skills_router, "fetch_skill", fake_fetch) for index in range(limits.SKILLS_PER_USER_MAX): current["name"] = f"cap-{index}" assert client.post("/skills/install", data={"url": "https://s.example/x"}, headers=_headers(user)).status_code == 200 current["name"] = "one-too-many" refused = client.post("/skills/install", data={"url": "https://s.example/x"}, headers=_headers(user)) assert refused.status_code == 400 assert "at most" in refused.json()["error"]["message"] current["name"] = "cap-0" assert client.post("/skills/install", data={"url": "https://s.example/x"}, headers=_headers(user)).json()["action"] == "update" def test_a_browser_install_redirects_to_the_list(client, monkeypatch): user = _signup_install() async def fake_fetch(url): return _fetched(name="browser-skill") monkeypatch.setattr(skills_router, "fetch_skill", fake_fetch) response = client.post( "/skills/install", data={"url": "https://skills.example/x"}, headers={"X-API-KEY": user["api_key"], "Accept": "text/html"}, follow_redirects=False, ) assert response.status_code == 302 assert response.headers["location"] == "/skills" assert get_skill_row(user["uid"], "browser-skill") is not None