atlas-iac/testing/tests/test_hermes_gitea_internal_coverage.py
2026-09-01 20:43:50 -03:00

381 lines
13 KiB
Python

"""Behavioral coverage for internal safe-Gitea client and policy paths."""
from __future__ import annotations
import json
import subprocess
import sys
import types
import urllib.error
import urllib.parse
from email.message import Message
from pathlib import Path
import pytest
from testing.tests.test_hermes_gitea_support import CLIENT_PATH, Response, _load
from testing.tests.test_hermes_scm_broker_support import _load_path
def _policy(name: str = "gitea_policy_coverage"):
return _load_path(name, CLIENT_PATH.parent / "gitea_api_policy.py")
def test_runtime_token_reader_and_fixed_origin(tmp_path: Path, monkeypatch):
module = _load()
token = tmp_path / "token"
token.write_text(" synthetic-runtime-value \n", encoding="utf-8")
assert module.read_token(token) == "synthetic-runtime-value"
token.write_text(" \n", encoding="utf-8")
with pytest.raises(ValueError, match="empty"):
module.read_token(token)
monkeypatch.setenv("GITEA_BASE_URL", module.CANONICAL_BASE_URL)
assert module.configured_base_url() == module.CANONICAL_BASE_URL
monkeypatch.setenv("GITEA_BASE_URL", "https://example.invalid")
with pytest.raises(module.PolicyError, match="fixed"):
module.configured_base_url()
def test_safe_urlopen_delegates_only_to_deadline_bounded_exchange(monkeypatch):
module = _load()
sentinel = object()
monkeypatch.setattr(
module.deadline_http,
"open_bounded",
lambda request, *, maximum, timeout: (request, maximum, timeout, sentinel),
)
request = object()
assert module._safe_urlopen(request, 7) == (
request,
module.MAX_RESPONSE_BYTES,
7,
sentinel,
)
def test_api_target_and_request_body_edge_paths(monkeypatch):
module = _load()
with pytest.raises(module.PolicyError, match="path exceeds"):
module._split_api_path("/api/v1/" + "x" * 510)
with pytest.raises(module.PolicyError, match="request body"):
module.authorize_request(
"GET", "/api/v1/repos/titan/cassandra", {"unexpected": True}
)
with pytest.raises(module.PolicyError, match="query"):
module.authorize_request(
"POST",
"/api/v1/repos/titan/cassandra/pulls?page=1",
{},
)
with pytest.raises(module.PolicyError, match="only read"):
module.authorize_request("TRACE", "/api/v1/repos/titan/cassandra/pulls", None)
with pytest.raises(module.PolicyError, match="accepts only"):
module.authorize_request("POST", "/api/v1/repos/titan/cassandra/pulls", [])
def test_response_status_fallback_and_nested_fail_closed():
module = _load()
class GetCodeResponse(Response):
def __init__(self):
super().__init__({"name": "cassandra"}, status=200)
del self.status
def getcode(self):
return 200
assert (
module.read(
"/api/v1/repos/titan/cassandra",
token="synthetic",
opener=lambda *_a, **_k: GetCodeResponse(),
)
== b'{"name": "cassandra"}'
)
with pytest.raises(module.PolicyError, match="omitted required"):
module._nested({"base": None}, "base", "ref")
@pytest.mark.parametrize("body", [b"not-json", b"[]"])
def test_create_response_requires_json_object(body: bytes):
module = _load()
with pytest.raises(module.PolicyError, match="invalid pull-request metadata"):
module._require_create_response(
body,
repo="cassandra",
base="main",
head="feature/test",
head_sha="a" * 40,
title="WIP: Test",
body="Evidence",
)
def test_write_body_handles_empty_newline_and_missing_newline(monkeypatch):
module = _load()
class Buffer:
value = bytearray()
@classmethod
def write(cls, value):
cls.value.extend(value)
monkeypatch.setattr(module.sys, "stdout", types.SimpleNamespace(buffer=Buffer))
module._write_body(b"")
module._write_body(b"one\n")
module._write_body(b"two")
assert bytes(Buffer.value) == b"one\ntwo\n"
def test_main_executes_broker_read_and_create_paths(monkeypatch):
module = _load()
outputs: list[bytes] = []
client = types.ModuleType("scm_broker_client")
client.read = lambda path: json.dumps({"path": path}).encode()
client.create_draft = lambda repo, **data: json.dumps(
{"repo": repo, **data}, sort_keys=True
).encode()
monkeypatch.setitem(sys.modules, "scm_broker_client", client)
monkeypatch.setattr(module, "_write_body", outputs.append)
assert module.main(["read", "/api/v1/repos/titan/cassandra"]) == 0
assert json.loads(outputs.pop()) == {"path": "/api/v1/repos/titan/cassandra"}
assert (
module.main(
[
"create-draft",
"cassandra",
"--base",
"main",
"--head",
"feature/coverage",
"--head-sha",
"a" * 40,
"--title",
"Coverage repair",
"--body",
"Review the focused tests",
]
)
== 0
)
assert json.loads(outputs.pop())["repo"] == "cassandra"
def test_main_handles_http_and_policy_failures(monkeypatch, capsys):
module = _load()
client = types.ModuleType("scm_broker_client")
def http_failure(_path):
headers = Message()
raise urllib.error.HTTPError("url", 503, "unavailable", headers, None)
client.read = http_failure
client.create_draft = lambda *_a, **_k: b"{}"
monkeypatch.setitem(sys.modules, "scm_broker_client", client)
assert module.main(["read", "/api/v1/repos/titan/cassandra"]) == 1
assert "HTTP 503" in capsys.readouterr().err
client.read = lambda _path: (_ for _ in ()).throw(module.PolicyError("rejected"))
assert module.main(["read", "/not-allowed"]) == 1
assert "no credential" in capsys.readouterr().err
@pytest.mark.parametrize(
("function", "value"),
[
("_validate_repo", "."),
("_validate_repo", ".."),
("_validate_sha", "short"),
("_validate_pr_number", True),
("_validate_pr_number", 0),
("_validate_pr_number", 2_147_483_648),
],
)
def test_policy_scalar_validators_reject_ambiguous_values(function, value):
module = _policy("gitea_policy_scalar")
with pytest.raises(module.PolicyError):
getattr(module, function)(value)
def test_ref_bounds_cover_utf8_controls_git_failure_and_success(monkeypatch):
module = _policy("gitea_policy_refs")
with pytest.raises(module.PolicyError, match="valid UTF-8"):
module._validate_ref_bounds("\ud800", "head")
with pytest.raises(module.PolicyError, match="UTF-8"):
module._validate_ref_bounds("🧪" * 64, "head")
with pytest.raises(module.PolicyError, match="safe same"):
module._validate_ref("bad\nref", "head")
monkeypatch.setattr(
module.subprocess,
"run",
lambda *_a, **_k: subprocess.CompletedProcess([], 1),
)
with pytest.raises(module.PolicyError, match="same-repository"):
module._validate_ref("invalid-ref", "head")
monkeypatch.setattr(
module.subprocess,
"run",
lambda *_a, **_k: subprocess.CompletedProcess([], 0),
)
assert module._validate_ref("feature/valid", "head") == "feature/valid"
@pytest.mark.parametrize(
("value", "required", "match"),
[
(None, False, "must be text"),
(" ", True, "must not be empty"),
("\ud800", False, "valid UTF-8"),
("x\x00y", False, "safe request limit"),
],
)
def test_text_validation_rejects_nontext_empty_invalid_and_controls(
value, required, match
):
module = _policy("gitea_policy_text")
with pytest.raises(module.PolicyError, match=match):
module._validate_text(value, "field", 20, 20, required=required)
@pytest.mark.parametrize(
("key", "expected"),
[
("password", True),
("accountKey", True),
("registry-key", True),
("clientEmail", True),
("clientId", True),
("accessId", True),
("connectionString", True),
("dockerConfigJson", True),
("release_note", False),
],
)
def test_sensitive_key_semantics_cover_generic_forms(key, expected):
module = _policy("gitea_policy_keys")
assert module._is_sensitive_key(key) is expected
@pytest.mark.parametrize(
("value", "expected"),
[
("", False),
('"synthetic"', True),
("{encoded}", True),
("Bearer value-12345678", True),
("ghp_" + "a" * 24, True),
("https://example.invalid/value", True),
("name@example.invalid", True),
("${RUNTIME_VALUE}", True),
("AbCdEf0123456789", True),
("reject empty values", False),
("ordinary", True),
],
)
def test_assignment_value_shape_distinguishes_prose_from_credentials(value, expected):
module = _policy("gitea_policy_assignment_values")
assert module._looks_sensitive_assignment_value(value) is expected
@pytest.mark.parametrize(
("value", "expected"),
[
("single", False),
("add regression coverage", True),
("add regression_coverage", False),
("ordinary engineering prose", False),
],
)
def test_prose_classifier_covers_length_punctuation_and_lead_words(value, expected):
module = _policy("gitea_policy_prose")
assert module._looks_like_prose(value) is expected
def test_structured_json_walk_covers_lists_nonstring_keys_and_limits():
module = _policy("gitea_policy_json_walk")
assert module._json_value_has_sensitive_assignment({1: "ignored"}) is False
assert module._json_value_has_sensitive_assignment([{"release": "safe"}]) is False
assert (
module._json_value_has_sensitive_assignment({"client_secret": "value"}) is True
)
assert (
module._json_value_has_sensitive_assignment(
{"outer": {"client_secret": "value"}}
)
is True
)
assert (
module._json_value_has_sensitive_assignment({"type": "service-account"}) is True
)
with pytest.raises(module.PolicyError, match="scan limit"):
module._json_value_has_sensitive_assignment([], depth=33)
with pytest.raises(module.PolicyError, match="scan limit"):
module._json_value_has_sensitive_assignment([], nodes=[2048])
def test_json_key_decoder_and_embedded_document_scanner_cover_failures():
module = _policy("gitea_policy_json_decoder")
assert module._decode_json_key("client\\u005fsecret") == ("client_secret", True)
decoded, valid = module._decode_json_key("client\\qsecret")
assert valid is False and "client" in decoded
assert list(module._decoded_json_documents("prose only")) == []
assert list(module._decoded_json_documents("x {bad y [1, 2]")) == [[1, 2]]
with pytest.raises(module.PolicyError, match="scan limit"):
list(module._decoded_json_documents("{" * 33))
assert (
module._has_structured_sensitive_assignment('"client\\qsecret": value') is True
)
assert (
module._has_structured_sensitive_assignment("type:\n service-account") is True
)
def test_high_entropy_detector_covers_long_mixed_and_low_entropy_values():
module = _policy("gitea_policy_entropy")
assert module._has_high_entropy_token("A1_" * 100) is True
assert (
module._has_high_entropy_token(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/_="
)
is True
)
assert module._has_high_entropy_token("a" * 64) is False
assert module._has_high_entropy_token("short prose") is False
@pytest.mark.parametrize(
"query",
[
"bad",
"a=1&b=2&c=3&d=4",
"page=1&page=2",
"=value",
"unexpected=1",
"page=abc",
"page=01",
"page=10001",
"limit=51",
"state=merged",
],
)
def test_query_validator_covers_each_rejection_class(query):
module = _policy("gitea_policy_query")
target = urllib.parse.urlsplit("/api/v1/repos/titan/cassandra/pulls?" + query)
with pytest.raises(module.PolicyError):
module._validate_query(target, {"page", "limit", "state"})
def test_draft_title_rejects_prefix_without_content():
module = _policy("gitea_policy_empty_draft")
with pytest.raises(module.PolicyError, match="after the draft prefix"):
module._draft_title("WIP:")
def test_query_validator_rejects_noncanonical_raw_form():
module = _policy("gitea_policy_raw_query")
target = urllib.parse.SplitResult("", "", "/api/v1/repos/titan/cassandra", "%", "")
with pytest.raises(module.PolicyError, match="canonical ASCII"):
module._validate_query(target, set())