Files
hermes-agent/tests/tools/test_skills_hub_github_fetch.py
Teknium 51e712b651 fix(skills): review fixes — raw fetch falls back on DNS block, restore never writes through links or over user files
- _fetch_file_bytes: a DNS-poisoned / NXDOMAIN raw.githubusercontent.com raises
  SSRFConnectionBlocked (a ValueError), which escaped the httpx.HTTPError suppress and
  failed the install before the Contents API fallback. Any transport failure on the raw
  attempt now falls back, and latches raw unusable on the source so a dropping firewall
  costs one 15 s timeout, not one per file.
- ensure_bundled_skill: a leftover dest is refused if any entry in it is a symlink or
  junction (copytree with dirs_exist_ok followed them out of skills/), and only missing
  files are copied (a user-edited file was overwritten). The canonical-path "present"
  check also requires the SKILL.md frontmatter name to match; a dir holding another
  skill's SKILL.md is refused, as on main.
- Docstring: a restore over leftover user files reads as user-modified, so update keeps it.
2026-10-09 13:58:16 -07:00

53 lines
2.4 KiB
Python

"""GitHubSource file downloads: which host serves a skill's files."""
from unittest.mock import MagicMock
import httpx
import pytest
from tools.skills_hub_github import GitHubAuth, GitHubSource
@pytest.mark.parametrize("raw_status", [200, 404])
def test_pinned_file_comes_from_raw_and_spends_no_api_call(monkeypatch, raw_status):
"""Every file of a skill folder was one Contents API call: an anonymous user (60/h) could
never install a skill with ~58+ files (remotion-best-practices: 143 calls, rate_limited).
A pinned file now comes from raw.githubusercontent.com; a raw miss (private repo) still
falls back to the API."""
import tools.skills_hub as hub
hosts = []
def fake_get(url, **_kw):
hosts.append(url.split("/")[2])
ok = "raw.githubusercontent.com" not in url or raw_status == 200
return httpx.Response(200 if ok else raw_status, content=b"body" if ok else b"")
monkeypatch.setattr(hub, "_skills_hub_http_get", fake_get)
auth = MagicMock(spec=GitHubAuth)
auth.get_headers.return_value = {}
assert GitHubSource(auth)._fetch_file_bytes("o/r", "s/a b.md", ref="c" * 40) == b"body"
assert hosts == (["raw.githubusercontent.com"] if raw_status == 200
else ["raw.githubusercontent.com", "api.github.com"])
def test_unreachable_raw_host_falls_back_to_the_api_and_is_tried_once(monkeypatch):
"""A DNS-poisoned or NXDOMAIN raw.githubusercontent.com (api.github.com fine) raises
SSRFConnectionBlocked, a ValueError the raw attempt did not catch: every install failed before
the API fallback. A dropped-packet firewall costs a 15 s timeout per file, so stop after one."""
import tools.skills_hub as hub
from tools.url_safety import SSRFConnectionBlocked
hosts = []
def fake_get(url, **_kw):
hosts.append(url.split("/")[2])
if "raw.githubusercontent.com" in url:
raise SSRFConnectionBlocked("DNS resolution failed for: raw.githubusercontent.com")
return httpx.Response(200, content=b"body")
monkeypatch.setattr(hub, "_skills_hub_http_get", fake_get)
auth = MagicMock(spec=GitHubAuth)
auth.get_headers.return_value = {}
source = GitHubSource(auth)
assert [source._fetch_file_bytes("o/r", f"s/{i}.md", ref="c" * 40) for i in range(3)] == [b"body"] * 3
assert hosts == ["raw.githubusercontent.com"] + ["api.github.com"] * 3