Skip to content

Commit 8275beb

Browse files
committed
Add unit tests and Codecov coverage upload
Relates: RHELWF-14238 Assisted-by: Claude Code (claude-opus-4-6)
1 parent bc34dca commit 8275beb

5 files changed

Lines changed: 302 additions & 2 deletions

File tree

.github/workflows/resultsdb_frontend.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,38 @@ name: ResultsDB_Frontend
44
"on":
55
pull_request:
66
push:
7+
branches:
8+
- develop
79
workflow_dispatch:
810
inputs: {}
911

1012
jobs:
13+
tests:
14+
name: Unit tests
15+
runs-on: ubuntu-latest
16+
permissions:
17+
id-token: write
18+
contents: read
19+
20+
steps:
21+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
22+
23+
- name: Install uv
24+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
25+
with:
26+
python-version: "3.13"
27+
28+
- name: Test with tox
29+
run: uvx --with tox-uv tox -e py3
30+
31+
- name: Upload coverage to Codecov
32+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7
33+
with:
34+
fail_ci_if_error: true
35+
verbose: true
36+
use_oidc: true
37+
flags: unit-tests
38+
1139
hadolint:
1240
name: Hadolint
1341
runs-on: ubuntu-latest
@@ -17,9 +45,9 @@ jobs:
1745
- Dockerfile
1846

1947
steps:
20-
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
48+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2149

22-
- uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
50+
- uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
2351
with:
2452
dockerfile: ${{ matrix.dockerfile }}
2553
failure-threshold: warning

tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import os
2+
3+
os.environ["TEST"] = "true"
4+
5+
import pytest # noqa: E402
6+
7+
from resultsdb_frontend import app # noqa: E402
8+
9+
10+
@pytest.fixture
11+
def client():
12+
with app.test_client() as client:
13+
yield client

tests/test_routes.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from unittest.mock import patch
2+
3+
4+
MOCK_RESULTS = {
5+
"data": [
6+
{
7+
"id": 1,
8+
"testcase": {"name": "test.case.1"},
9+
"outcome": "PASSED",
10+
"groups": ["group1"],
11+
"data": {},
12+
"note": "",
13+
"submit_time": "2024-01-01T00:00:00",
14+
"href": "",
15+
"ref_url": "",
16+
}
17+
],
18+
"prev": None,
19+
"next": None,
20+
}
21+
22+
MOCK_RESULT = {
23+
"id": 1,
24+
"testcase": {"name": "test.case.1", "url": ""},
25+
"outcome": "PASSED",
26+
"groups": ["group1"],
27+
"note": "",
28+
"submit_time": "2024-01-01T00:00:00",
29+
"data": {},
30+
"href": "",
31+
"ref_url": "",
32+
}
33+
34+
MOCK_GROUPS = {
35+
"data": [{"uuid": "group1", "results_count": 1}],
36+
"prev": None,
37+
"next": None,
38+
}
39+
40+
MOCK_GROUP = {"uuid": "group1", "results_count": 1}
41+
42+
MOCK_TESTCASES = {
43+
"data": [{"name": "test.case.1"}],
44+
"prev": None,
45+
"next": None,
46+
}
47+
48+
MOCK_TESTCASE = {"name": "test.case.1"}
49+
50+
51+
def test_index_redirects(client):
52+
response = client.get("/")
53+
assert response.status_code == 302
54+
assert "/results" in response.headers["Location"]
55+
56+
57+
@patch("resultsdb_frontend.controllers.main.RDB_API")
58+
def test_results(mock_api, client):
59+
mock_api.get_results.return_value = MOCK_RESULTS
60+
response = client.get("/results")
61+
assert response.status_code == 200
62+
63+
64+
@patch("resultsdb_frontend.controllers.main.RDB_API")
65+
def test_result_detail(mock_api, client):
66+
mock_api.get_result.return_value = MOCK_RESULT
67+
response = client.get("/results/1")
68+
assert response.status_code == 200
69+
70+
71+
@patch("resultsdb_frontend.controllers.main.RDB_API")
72+
def test_result_detail_no_groups(mock_api, client):
73+
mock_api.get_result.return_value = {
74+
"id": 1,
75+
"testcase": {"name": "test.case.1", "url": ""},
76+
"outcome": "PASSED",
77+
"note": "",
78+
"submit_time": "2024-01-01T00:00:00",
79+
"data": {},
80+
"href": "",
81+
"ref_url": "",
82+
}
83+
response = client.get("/results/1")
84+
assert response.status_code == 200
85+
86+
87+
@patch("resultsdb_frontend.controllers.main.RDB_API")
88+
def test_groups(mock_api, client):
89+
mock_api.get_groups.return_value = MOCK_GROUPS
90+
response = client.get("/groups")
91+
assert response.status_code == 200
92+
93+
94+
@patch("resultsdb_frontend.controllers.main.RDB_API")
95+
def test_group_detail(mock_api, client):
96+
mock_api.get_group.return_value = MOCK_GROUP
97+
response = client.get("/groups/group1")
98+
assert response.status_code == 200
99+
100+
101+
@patch("resultsdb_frontend.controllers.main.RDB_API")
102+
def test_testcases(mock_api, client):
103+
mock_api.get_testcases.return_value = MOCK_TESTCASES
104+
response = client.get("/testcases")
105+
assert response.status_code == 200
106+
107+
108+
@patch("resultsdb_frontend.controllers.main.RDB_API")
109+
def test_testcase_detail(mock_api, client):
110+
mock_api.get_testcase.return_value = MOCK_TESTCASE
111+
response = client.get("/testcases/test.case.1")
112+
assert response.status_code == 200
113+
114+
115+
@patch("resultsdb_frontend.controllers.main.RDB_API")
116+
def test_testcase_tokenizer(mock_api, client):
117+
mock_api.get_testcases.side_effect = [
118+
{"data": [{"name": "b.test"}, {"name": "a.test"}]},
119+
{"data": []},
120+
]
121+
response = client.get("/testcase_tokenizer")
122+
assert response.status_code == 200
123+
assert response.content_type == "application/json"
124+
data = response.get_json()
125+
assert data == ["a.test", "b.test"]

tests/test_utils.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import json
2+
3+
from resultsdb_frontend.proxy import ReverseProxied
4+
from resultsdb_frontend.requests_session import ErrorResponse
5+
from resultsdb_frontend.resultsdb_api import _prepare_params
6+
7+
8+
class TestPrepareParams:
9+
def test_string_values(self):
10+
assert _prepare_params(foo="bar") == {"foo": "bar"}
11+
12+
def test_list_values(self):
13+
assert _prepare_params(items=["a", "b"]) == {"items": "a,b"}
14+
15+
def test_none_values_excluded(self):
16+
assert _prepare_params(foo="bar", empty=None) == {"foo": "bar"}
17+
18+
def test_empty(self):
19+
assert _prepare_params() == {}
20+
21+
def test_integer_values(self):
22+
assert _prepare_params(page=1, limit=10) == {"page": "1", "limit": "10"}
23+
24+
25+
class TestErrorResponse:
26+
def test_status_code(self):
27+
resp = ErrorResponse(504, "timeout", "http://example.com")
28+
assert resp.status_code == 504
29+
30+
def test_content(self):
31+
resp = ErrorResponse(502, "connection error", "http://example.com")
32+
data = json.loads(resp.content)
33+
assert data == {"message": "connection error"}
34+
35+
def test_url(self):
36+
resp = ErrorResponse(504, "timeout", "http://example.com/api")
37+
assert resp.url == "http://example.com/api"
38+
39+
40+
class TestReverseProxied:
41+
def _make_environ(self, **kwargs):
42+
environ = {
43+
"PATH_INFO": "/test",
44+
"REQUEST_METHOD": "GET",
45+
"SERVER_NAME": "localhost",
46+
"SERVER_PORT": "5000",
47+
}
48+
environ.update(kwargs)
49+
return environ
50+
51+
def test_script_name(self):
52+
app_calls = []
53+
54+
def mock_app(environ, start_response):
55+
app_calls.append(environ.copy())
56+
return []
57+
58+
proxied = ReverseProxied(mock_app)
59+
environ = self._make_environ(
60+
HTTP_X_SCRIPT_NAME="/prefix",
61+
PATH_INFO="/prefix/test",
62+
)
63+
proxied(environ, None)
64+
assert app_calls[0]["SCRIPT_NAME"] == "/prefix"
65+
assert app_calls[0]["PATH_INFO"] == "/test"
66+
67+
def test_forwarded_host(self):
68+
app_calls = []
69+
70+
def mock_app(environ, start_response):
71+
app_calls.append(environ.copy())
72+
return []
73+
74+
proxied = ReverseProxied(mock_app)
75+
environ = self._make_environ(HTTP_X_FORWARDED_HOST="example.com")
76+
proxied(environ, None)
77+
assert app_calls[0]["HTTP_HOST"] == "example.com"
78+
79+
def test_scheme(self):
80+
app_calls = []
81+
82+
def mock_app(environ, start_response):
83+
app_calls.append(environ.copy())
84+
return []
85+
86+
proxied = ReverseProxied(mock_app)
87+
environ = self._make_environ(HTTP_X_SCHEME="https")
88+
proxied(environ, None)
89+
assert app_calls[0]["wsgi.url_scheme"] == "https"
90+
91+
def test_no_proxy_headers(self):
92+
app_calls = []
93+
94+
def mock_app(environ, start_response):
95+
app_calls.append(environ.copy())
96+
return []
97+
98+
proxied = ReverseProxied(mock_app)
99+
environ = self._make_environ()
100+
proxied(environ, None)
101+
assert "SCRIPT_NAME" not in app_calls[0]
102+
assert app_calls[0]["PATH_INFO"] == "/test"

tox.ini

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[tox]
2+
envlist = py3
3+
requires =
4+
tox-uv
5+
6+
[pytest]
7+
addopts =
8+
--cov=resultsdb_frontend
9+
--cov-report=term-missing
10+
--cov-report=xml
11+
12+
[coverage:run]
13+
branch = true
14+
source_pkgs =
15+
resultsdb_frontend
16+
17+
[coverage:paths]
18+
source =
19+
resultsdb_frontend/
20+
.tox/py3/lib/python*/site-packages/resultsdb_frontend/
21+
22+
[testenv]
23+
basepython = python3.13
24+
runner = uv-venv-runner
25+
deps =
26+
pytest
27+
pytest-cov
28+
-r requirements.txt
29+
commands =
30+
pytest {posargs}
31+
setenv =
32+
TEST = true

0 commit comments

Comments
 (0)