Skip to content

Commit e7ec905

Browse files
Copilotmedley56
andauthored
Fix Codecov validator request handling and add unit tests
Co-authored-by: medley56 <7018964+medley56@users.noreply.github.com>
1 parent 3817ec5 commit e7ec905

2 files changed

Lines changed: 112 additions & 18 deletions

File tree

scripts/check_codecov_yaml.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,36 @@
33
from __future__ import annotations
44

55
import argparse
6-
import http.client
76
import sys
87
from pathlib import Path
8+
from urllib import error, request
99

10-
CODECOV_VALIDATE_HOST = "codecov.io"
11-
CODECOV_VALIDATE_PATH = "/validate"
10+
CODECOV_VALIDATE_URL = "https://codecov.io/validate"
11+
NETWORK_TIMEOUT_SECONDS = 10
1212
REPO_ROOT = Path(__file__).parent.parent
1313

1414

1515
def validate_codecov_yaml(file_path: Path) -> int:
1616
"""Validate a Codecov YAML file against Codecov's endpoint."""
1717
try:
18-
conn = http.client.HTTPSConnection(CODECOV_VALIDATE_HOST)
19-
conn.request(
20-
method="POST",
21-
url=CODECOV_VALIDATE_PATH,
22-
body=file_path.read_bytes(),
23-
headers={"Content-Type": "text/yaml"},
24-
)
25-
response = conn.getresponse()
26-
body = response.read().decode("utf-8", errors="replace").strip()
18+
request_body = file_path.read_bytes()
2719
except OSError as exc:
28-
print(f"Unable to reach Codecov validation endpoint: {exc}", file=sys.stderr)
20+
print(f"Unable to read {file_path}: {exc}", file=sys.stderr)
2921
return 1
30-
finally:
31-
if "conn" in locals():
32-
conn.close()
3322

34-
if response.status >= 400:
35-
print(f"{file_path} validation failed with HTTP {response.status}.", file=sys.stderr)
23+
validate_request = request.Request(CODECOV_VALIDATE_URL, data=request_body, method="POST")
24+
try:
25+
with request.urlopen(validate_request, timeout=NETWORK_TIMEOUT_SECONDS) as response: # noqa: S310
26+
body = response.read().decode("utf-8", errors="replace").strip()
27+
except error.HTTPError as exc:
28+
body = exc.read().decode("utf-8", errors="replace").strip()
29+
print(f"{file_path} validation failed with HTTP {exc.code}.", file=sys.stderr)
3630
if body:
3731
print(body, file=sys.stderr)
3832
return 1
33+
except (error.URLError, TimeoutError, OSError) as exc:
34+
print(f"Unable to reach Codecov validation endpoint: {exc}", file=sys.stderr)
35+
return 1
3936

4037
if body:
4138
print(body)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Tests for scripts/check_codecov_yaml.py."""
2+
3+
import importlib.util
4+
import io
5+
from pathlib import Path
6+
from urllib import error
7+
8+
SCRIPT_PATH = Path(__file__).resolve().parents[2] / "scripts" / "check_codecov_yaml.py"
9+
SPEC = importlib.util.spec_from_file_location("check_codecov_yaml", SCRIPT_PATH)
10+
MODULE = importlib.util.module_from_spec(SPEC)
11+
assert SPEC.loader is not None
12+
SPEC.loader.exec_module(MODULE)
13+
14+
15+
class _FakeResponse:
16+
"""Minimal context manager for urllib responses."""
17+
18+
def __init__(self, body: bytes):
19+
self._body = body
20+
21+
def __enter__(self):
22+
return self
23+
24+
def __exit__(self, exc_type, exc, tb):
25+
return False
26+
27+
def read(self):
28+
return self._body
29+
30+
31+
def test_validate_codecov_yaml_posts_to_expected_endpoint(tmp_path, monkeypatch, capsys):
32+
file_path = tmp_path / "codecov.yml"
33+
file_path.write_text("coverage:\n status: off\n")
34+
captured: dict[str, object] = {}
35+
36+
def _fake_urlopen(req, timeout):
37+
captured["url"] = req.full_url
38+
captured["method"] = req.get_method()
39+
captured["data"] = req.data
40+
captured["timeout"] = timeout
41+
return _FakeResponse(b"Valid!")
42+
43+
monkeypatch.setattr(MODULE.request, "urlopen", _fake_urlopen)
44+
45+
assert MODULE.validate_codecov_yaml(file_path) == 0
46+
assert captured == {
47+
"url": "https://codecov.io/validate",
48+
"method": "POST",
49+
"data": file_path.read_bytes(),
50+
"timeout": MODULE.NETWORK_TIMEOUT_SECONDS,
51+
}
52+
output = capsys.readouterr()
53+
assert "is valid." in output.out
54+
assert output.err == ""
55+
56+
57+
def test_validate_codecov_yaml_http_error(tmp_path, monkeypatch, capsys):
58+
file_path = tmp_path / "codecov.yml"
59+
file_path.write_text("bad: yaml\n")
60+
61+
def _raise_http_error(*_args, **_kwargs):
62+
raise error.HTTPError(
63+
url=MODULE.CODECOV_VALIDATE_URL,
64+
code=400,
65+
msg="Bad Request",
66+
hdrs=None,
67+
fp=io.BytesIO(b"Invalid YAML"),
68+
)
69+
70+
monkeypatch.setattr(MODULE.request, "urlopen", _raise_http_error)
71+
72+
assert MODULE.validate_codecov_yaml(file_path) == 1
73+
output = capsys.readouterr()
74+
assert "validation failed with HTTP 400" in output.err
75+
assert "Invalid YAML" in output.err
76+
77+
78+
def test_validate_codecov_yaml_network_error(tmp_path, monkeypatch, capsys):
79+
file_path = tmp_path / "codecov.yml"
80+
file_path.write_text("coverage: {}\n")
81+
82+
def _raise_url_error(*_args, **_kwargs):
83+
raise error.URLError("network down")
84+
85+
monkeypatch.setattr(MODULE.request, "urlopen", _raise_url_error)
86+
87+
assert MODULE.validate_codecov_yaml(file_path) == 1
88+
output = capsys.readouterr()
89+
assert "Unable to reach Codecov validation endpoint" in output.err
90+
91+
92+
def test_validate_codecov_yaml_file_read_error(tmp_path, capsys):
93+
missing_path = tmp_path / "missing-codecov.yml"
94+
95+
assert MODULE.validate_codecov_yaml(missing_path) == 1
96+
output = capsys.readouterr()
97+
assert f"Unable to read {missing_path}" in output.err

0 commit comments

Comments
 (0)