200 lines
5.6 KiB
Python
200 lines
5.6 KiB
Python
"""Response and postcondition contracts for Atlas draft creation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import copy
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from testing.tests.test_hermes_gitea_support import (
|
|
HEAD_SHA,
|
|
Response,
|
|
_draft_response,
|
|
_load,
|
|
)
|
|
|
|
@pytest.mark.parametrize("status", [200, 202, 204, 206])
|
|
def test_create_accepts_only_http_201(status: int):
|
|
client = _load()
|
|
|
|
with pytest.raises(client.PolicyError, match="unexpected HTTP status"):
|
|
client.create_draft(
|
|
"cassandra",
|
|
base="main",
|
|
head="hermes/fix",
|
|
head_sha=HEAD_SHA,
|
|
title="Focused fix",
|
|
body="Review evidence",
|
|
token="runtime",
|
|
opener=lambda *_a, **_k: Response(_draft_response(), status=status),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("status", [201, 202, 204, 206])
|
|
def test_read_accepts_only_http_200(status: int):
|
|
client = _load()
|
|
|
|
with pytest.raises(client.PolicyError, match="unexpected HTTP status"):
|
|
client.read(
|
|
"/api/v1/repos/titan/cassandra",
|
|
token="runtime",
|
|
opener=lambda *_a, **_k: Response(b"{}", status=status),
|
|
)
|
|
|
|
|
|
def test_successful_create_validates_each_ref_once(monkeypatch):
|
|
client = _load()
|
|
calls = []
|
|
|
|
def git_run(command, **_kwargs):
|
|
calls.append(command)
|
|
return type("Result", (), {"returncode": 0})()
|
|
|
|
monkeypatch.setattr(client._validate_ref.__globals__["subprocess"], "run", git_run)
|
|
client.create_draft(
|
|
"cassandra",
|
|
base="main",
|
|
head="hermes/fix",
|
|
head_sha=HEAD_SHA,
|
|
title="Focused fix",
|
|
body="Review evidence",
|
|
token="runtime",
|
|
opener=lambda *_a, **_k: Response(_draft_response()),
|
|
)
|
|
|
|
assert [command[-1] for command in calls] == [
|
|
"refs/heads/main",
|
|
"refs/heads/hermes/fix",
|
|
]
|
|
|
|
|
|
def test_create_verifies_every_server_postcondition():
|
|
client = _load()
|
|
calls = []
|
|
|
|
def opener(request, timeout):
|
|
calls.append((request, timeout))
|
|
return Response(_draft_response())
|
|
|
|
result = client.create_draft(
|
|
"cassandra",
|
|
base="main",
|
|
head="hermes/fix",
|
|
head_sha=HEAD_SHA,
|
|
title="Focused fix",
|
|
body="Review evidence",
|
|
token="runtime",
|
|
opener=opener,
|
|
)
|
|
|
|
assert json.loads(result) == _draft_response()
|
|
payload = json.loads(calls[0][0].data)
|
|
assert payload == {
|
|
"base": "main",
|
|
"body": "Review evidence",
|
|
"head": "hermes/fix",
|
|
"title": "WIP: Focused fix",
|
|
}
|
|
assert calls[0][1] == 30
|
|
|
|
|
|
def test_create_postcondition_rejects_every_material_mismatch():
|
|
client = _load()
|
|
mutations = [
|
|
("number", 0),
|
|
("number", 2_147_483_648),
|
|
("state", "closed"),
|
|
("draft", False),
|
|
("merged", True),
|
|
("html_url", "https://evil.example/pulls/3"),
|
|
("url", "https://evil.example/api/pulls/3"),
|
|
("title", "Focused fix"),
|
|
("body", "different"),
|
|
]
|
|
documents = []
|
|
for key, value in mutations:
|
|
document = _draft_response()
|
|
document[key] = value
|
|
documents.append(document)
|
|
for path, value in [
|
|
(("base", "ref"), "master"),
|
|
(("base", "repo", "full_name"), "evil/cassandra"),
|
|
(("head", "ref"), "other"),
|
|
(("head", "sha"), "0" * 40),
|
|
(("head", "repo", "full_name"), "evil/cassandra"),
|
|
]:
|
|
document = copy.deepcopy(_draft_response())
|
|
target = document
|
|
for key in path[:-1]:
|
|
target = target[key]
|
|
target[path[-1]] = value
|
|
documents.append(document)
|
|
|
|
for document in documents:
|
|
with pytest.raises(client.PolicyError):
|
|
client._require_create_response(
|
|
json.dumps(document).encode(),
|
|
repo="cassandra",
|
|
base="main",
|
|
head="hermes/fix",
|
|
head_sha=HEAD_SHA,
|
|
title="WIP: Focused fix",
|
|
body="Review evidence",
|
|
)
|
|
|
|
|
|
def test_read_response_is_bounded():
|
|
client = _load()
|
|
|
|
with pytest.raises(client.PolicyError, match="safe size limit"):
|
|
client.read(
|
|
"/api/v1/repos/titan/cassandra",
|
|
token="runtime",
|
|
opener=lambda *_a, **_k: Response(b"x" * (client.MAX_RESPONSE_BYTES + 1)),
|
|
)
|
|
|
|
|
|
def test_direct_api_rejects_unexpected_success_content_type():
|
|
client = _load()
|
|
response = Response(b"{}")
|
|
response.headers.replace_header("Content-Type", "text/html")
|
|
|
|
with pytest.raises(client.PolicyError, match="unexpected response type"):
|
|
client.read(
|
|
"/api/v1/repos/titan/cassandra",
|
|
token="runtime",
|
|
opener=lambda *_a, **_k: response,
|
|
)
|
|
|
|
|
|
def test_output_redaction_covers_exact_token_and_authorization_header():
|
|
client = _load()
|
|
raw = b'{"message":"do-not-leak","debug":"Authorization: token do-not-leak"}'
|
|
redacted = client.redact_bytes(raw, "do-not-leak")
|
|
|
|
assert b"do-not-leak" not in redacted
|
|
assert redacted.count(b"[REDACTED]") >= 1
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"reflected",
|
|
[
|
|
b"runtime-sentinel",
|
|
base64.b64encode(b"runtime-sentinel"),
|
|
base64.b64encode(b"hermes-automation:runtime-sentinel"),
|
|
],
|
|
)
|
|
def test_direct_api_rejects_credential_reflection(reflected: bytes):
|
|
client = _load()
|
|
|
|
with pytest.raises(client.PolicyError, match="credential material"):
|
|
client.read(
|
|
"/api/v1/repos/titan/cassandra",
|
|
token="runtime-sentinel",
|
|
opener=lambda *_a, **_k: Response(
|
|
b'{"unexpected":"' + reflected + b'"}'
|
|
),
|
|
)
|