Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tests/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@
)


@pytest.fixture
def public_dns(monkeypatch):
"""validate_url resolves the host, which would make these tests need a network."""
monkeypatch.setattr("socket.getaddrinfo", lambda *a, **k: [(2, 1, 6, "", ("93.184.216.34", 0))])


# ---------------------------------------------------------------------------
# validate_url
# ---------------------------------------------------------------------------

@pytest.mark.usefixtures("public_dns")
def test_validate_url_accepts_http():
assert validate_url("http://example.com/page") == "http://example.com/page"

@pytest.mark.usefixtures("public_dns")
def test_validate_url_accepts_https():
assert validate_url("https://arxiv.org/abs/1706.03762") == "https://arxiv.org/abs/1706.03762"

Expand Down Expand Up @@ -107,6 +115,7 @@ def test_safe_fetch_rejects_ftp_url():
with pytest.raises(ValueError, match="ftp"):
safe_fetch("ftp://example.com/file.zip")

@pytest.mark.usefixtures("public_dns")
def test_safe_fetch_returns_bytes(tmp_path):
mock_resp = _make_mock_response(b"hello world")
with patch("graphify.security._build_opener") as mock_opener_fn:
Expand All @@ -116,6 +125,7 @@ def test_safe_fetch_returns_bytes(tmp_path):
result = safe_fetch("https://example.com/")
assert result == b"hello world"

@pytest.mark.usefixtures("public_dns")
def test_safe_fetch_raises_on_non_2xx():
mock_resp = _make_mock_response(b"Not Found", status=404)
with patch("graphify.security._build_opener") as mock_opener_fn:
Expand All @@ -125,6 +135,7 @@ def test_safe_fetch_raises_on_non_2xx():
with pytest.raises(urllib.error.HTTPError):
safe_fetch("https://example.com/missing")

@pytest.mark.usefixtures("public_dns")
def test_safe_fetch_raises_on_size_exceeded():
# Build a response larger than max_bytes
big_chunk = b"x" * 65_537
Expand All @@ -148,6 +159,7 @@ def test_safe_fetch_raises_on_size_exceeded():
# safe_fetch_text
# ---------------------------------------------------------------------------

@pytest.mark.usefixtures("public_dns")
def test_safe_fetch_text_decodes_utf8():
content = "héllo wörld".encode("utf-8")
mock_resp = _make_mock_response(content)
Expand All @@ -158,6 +170,7 @@ def test_safe_fetch_text_decodes_utf8():
result = safe_fetch_text("https://example.com/")
assert result == "héllo wörld"

@pytest.mark.usefixtures("public_dns")
def test_safe_fetch_text_replaces_bad_bytes():
bad = b"hello \xff world"
mock_resp = _make_mock_response(bad)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_skillgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@

from tools.skillgen import gen # noqa: E402

# These read blobs from origin/v8, which a fork or tarball checkout may not have.
# CI sets fetch-depth: 0, so they always run there.
needs_origin_v8 = pytest.mark.skipif(
not gen._v8_available(), reason="origin/v8 is not available in this checkout"
)


@needs_origin_v8
def test_audit_coverage_passes():
"""Every v8 heading lands in the lean core or exactly one reference."""
platforms = gen.load_platforms()
Expand Down Expand Up @@ -256,6 +263,7 @@ def test_check_passes_for_codex_and_windows():
assert problems == [], f"[{key}]\n" + "\n".join(problems)


@needs_origin_v8
def test_audit_coverage_passes_for_codex_and_windows():
"""Every v8 heading single-homes for the cli-inline split hosts too."""
platforms = gen.load_platforms()
Expand Down Expand Up @@ -512,6 +520,7 @@ def test_schema_singleton_catches_legacy_enums():
)


@needs_origin_v8
def test_all_progressive_hosts_check_and_audit_clean():
"""check + audit-coverage pass for every rendered progressive host."""
platforms = gen.load_platforms()
Expand Down Expand Up @@ -611,6 +620,7 @@ def test_monoliths_render_inline_single_file_no_references():
assert "references/" not in arts[0].content or "see `references/" not in arts[0].content.lower()


@needs_origin_v8
def test_monolith_roundtrip_passes_for_aider_and_devin():
"""Each monolith is diff-clean vs v8 except the file_type enum unification."""
platforms = gen.load_platforms()
Expand All @@ -619,6 +629,7 @@ def test_monolith_roundtrip_passes_for_aider_and_devin():
assert problems == [], f"[{key}]\n" + "\n".join(problems)


@needs_origin_v8
def test_monoliths_change_only_sanctioned_lines():
"""Every line that differs from pristine v8 is a sanctioned change-class.

Expand Down Expand Up @@ -745,6 +756,7 @@ def test_always_on_included_in_full_render_not_per_platform():
assert "graphify/always_on/claude-md.md" not in claude_only


@needs_origin_v8
def test_always_on_roundtrip_is_byte_faithful():
"""Each always_on/*.md reproduces its former __main__.py constant byte for byte.

Expand Down Expand Up @@ -827,6 +839,7 @@ def test_always_on_files_are_guarded_by_check(tmp_path):
# --- the per-host coverage audit (the systemic guard) --------------------------


@needs_origin_v8
def test_audit_coverage_passes_for_every_split_host():
"""Every split host's render single-homes its own v8 body's headings."""
platforms = gen.load_platforms()
Expand All @@ -847,6 +860,7 @@ def test_audit_reads_each_host_against_its_own_v8_body():
assert gen._v8_baseline_ref("vscode") == "47042beb05d1f6dd2186c0c499ae2840ce604ead:graphify/skill-vscode.md"


@needs_origin_v8
def test_audit_catches_an_induced_per_host_drop():
"""Re-inducing the trae regression (claude-flavored hooks) fails the audit.

Expand All @@ -864,6 +878,7 @@ def test_audit_catches_an_induced_per_host_drop():
assert any("native AGENTS.md integration (Trae)" in p for p in problems), problems


@needs_origin_v8
def test_audit_catches_a_dropped_non_allowlisted_heading():
"""A core fragment that drops a real v8 heading fails the audit.

Expand Down Expand Up @@ -1024,6 +1039,7 @@ def test_amp_has_no_pretooluse_caveat_anywhere():
assert "Trae" not in b2


@needs_origin_v8
def test_amp_audit_coverage_passes_against_its_own_v8():
"""The per-host audit (the guard amp is the exact case for) passes for amp.

Expand Down Expand Up @@ -1085,6 +1101,7 @@ def test_agents_body_matches_amp_modulo_hooks_wording():
assert amp["hooks.md"] != agents["hooks.md"]


@needs_origin_v8
def test_agents_audit_baseline_is_amps_v8_body():
"""`agents` is a post-v8 platform, so its audit baseline is amp's v8 body."""
platforms = gen.load_platforms()
Expand Down
Loading