Skip to content

Commit 2877df3

Browse files
committed
tests: Add minimal tests for githost.GitHubApi
1 parent f40c3be commit 2877df3

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest_plugins = ("tests.requests_mocker",)

tests/requests_mocker.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""A mocker for ``urllib.request.urlopen``."""
2+
3+
import collections
4+
import dataclasses
5+
import json as jsonlib
6+
import typing
7+
import unittest.mock
8+
import urllib.request
9+
10+
import pytest
11+
12+
13+
@pytest.fixture(name="requests_mocker")
14+
def get_mock():
15+
"""Return an instance of ``Mock`` to be used as a fixture.
16+
17+
Example::
18+
19+
with get_mock() as mock:
20+
mock.register("GET", "https://example.com", content="OK")
21+
# call code that would make an HTTP request
22+
"""
23+
m = Mock()
24+
with unittest.mock.patch("urllib.request.urlopen", m.urlopen):
25+
yield m
26+
27+
28+
@dataclasses.dataclass
29+
class Request:
30+
url: str
31+
headers: typing.Dict[str, str]
32+
data: bytes
33+
params: dict
34+
35+
36+
@dataclasses.dataclass
37+
class Response:
38+
content: bytes
39+
status: int
40+
41+
def read(self):
42+
return self.content
43+
44+
45+
@dataclasses.dataclass
46+
class Call:
47+
request: Request
48+
response: Response
49+
50+
51+
class Mock():
52+
"""Intercept HTTP requests and mock their responses.
53+
54+
An instance of ``Mock`` can be configured via its two methods:
55+
56+
- ``get(url: str, json: object, status=200)`` allows you to mock
57+
the response of a ``GET`` request to particular URL.
58+
59+
- ``register(method: str, url: str, content: bytes, status=200)``
60+
is a more generic method.
61+
"""
62+
def __init__(self):
63+
self.mocks = collections.defaultdict(dict)
64+
self.calls = []
65+
66+
def register(self, method: str, url: str, content: bytes, status: int = 200):
67+
method = method.lower()
68+
self.mocks[url][method] = Response(content=content, status=status)
69+
70+
def get(self, url: str, json: object, status: int = 200):
71+
content = jsonlib.dumps(json)
72+
self.register("get", url, content=content, status=status)
73+
74+
def urlopen(self, request: urllib.request.Request, **kwargs):
75+
method = request.get_method().lower()
76+
url = _strip_query_string(request.full_url)
77+
response = self.mocks.get(url, {}).get(method)
78+
if not response:
79+
raise ValueError(f"No mock for method={method} and url={url}")
80+
call = Call(
81+
request=Request(
82+
url=url,
83+
headers=request.headers, # type: ignore[arg-type]
84+
data=request.data or b'', # type: ignore[arg-type]
85+
params=_extract_params(request),
86+
),
87+
response=response,
88+
)
89+
self.calls.append(call)
90+
return response
91+
92+
93+
def _strip_query_string(url: str) -> str:
94+
parsed = urllib.parse.urlparse(url)
95+
return parsed._replace(query="").geturl()
96+
97+
98+
def _extract_params(request: urllib.request.Request) -> dict:
99+
query = urllib.parse.urlparse(request.full_url).query
100+
if not query:
101+
return {}
102+
params = urllib.parse.parse_qs(query)
103+
for key, values in params.items():
104+
if len(values) == 1:
105+
params[key] = values[0]
106+
return params

tests/test_githost.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
from unittest import mock
3+
4+
from check_oldies import branches
5+
from check_oldies import githost
6+
7+
8+
FAKE_GITHUB_API_RESPONSE = [
9+
{
10+
"number": 1234,
11+
"state": "open",
12+
"html_url": "https://github.com/polyconseil/check-oldies/pull/1234",
13+
},
14+
]
15+
16+
17+
@mock.patch.dict(os.environ, {"TOKEN": "secret"}, clear=True)
18+
def test_github_api(requests_mocker):
19+
requests_mocker.get(
20+
"https://api.github.com/repos/polyconseil/check-oldies/pulls",
21+
json=FAKE_GITHUB_API_RESPONSE,
22+
)
23+
api_access = branches.GitHostApiAccessInfo(auth_token_env_var="TOKEN")
24+
api = githost.GitHubApi("polyconseil", api_access)
25+
pull_request = api.get_pull_request("check-oldies", "my-branch")
26+
assert pull_request.number == 1234
27+
assert pull_request.state == "open"
28+
assert pull_request.url == "https://github.com/polyconseil/check-oldies/pull/1234"

tests/test_requests_mocker.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import urllib.request
2+
3+
from . import requests_mocker
4+
5+
6+
def test_extract_params():
7+
url = "https://example.com/?single=1&multiple=2&multiple=3&empty="
8+
request = urllib.request.Request(url)
9+
params = requests_mocker._extract_params(request)
10+
assert params == {"single": "1", "multiple": ["2", "3"]}
11+
12+
13+
def test_strip_query_string():
14+
url = "https://example.com/path?foo=1"
15+
stripped = requests_mocker._strip_query_string(url)
16+
assert stripped == "https://example.com/path"

0 commit comments

Comments
 (0)