Skip to content

Commit

Permalink
tests: Add minimal tests for githost.GitHubApi
Browse files Browse the repository at this point in the history
  • Loading branch information
dbaty committed May 22, 2024
1 parent f40c3be commit f792408
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 0 deletions.
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pytest_plugins = ("tests.requests_mocker",)
107 changes: 107 additions & 0 deletions tests/requests_mocker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""A mocker for ``urllib.request.urlopen``."""

import collections
import dataclasses
import json as jsonlib
import typing
import unittest.mock
import urllib.parse
import urllib.request

import pytest


@pytest.fixture(name="requests_mocker")
def get_mock():
"""Return an instance of ``Mock`` to be used as a fixture.
Example::
with get_mock() as mock:
mock.register("GET", "https://example.com", content="OK")
# call code that would make an HTTP request
"""
m = Mock()
with unittest.mock.patch("urllib.request.urlopen", m.urlopen):
yield m


@dataclasses.dataclass
class Request:
url: str
headers: typing.Dict[str, str]
data: bytes
params: dict


@dataclasses.dataclass
class Response:
content: bytes
status: int

def read(self):
return self.content


@dataclasses.dataclass
class Call:
request: Request
response: Response


class Mock():
"""Intercept HTTP requests and mock their responses.
An instance of ``Mock`` can be configured via its two methods:
- ``get(url: str, json: object, status=200)`` allows you to mock
the response of a ``GET`` request to particular URL.
- ``register(method: str, url: str, content: bytes, status=200)``
is a more generic method.
"""
def __init__(self):
self.mocks = collections.defaultdict(dict)
self.calls = []

def register(self, method: str, url: str, content: bytes, status: int = 200):
method = method.lower()
self.mocks[url][method] = Response(content=content, status=status)

def get(self, url: str, json: object, status: int = 200):
content = jsonlib.dumps(json)
self.register("get", url, content=content.encode("utf-8"), status=status)

def urlopen(self, request: urllib.request.Request, **kwargs):
method = request.get_method().lower()
url = _strip_query_string(request.full_url)
response = self.mocks.get(url, {}).get(method)
if not response:
raise ValueError(f"No mock for method={method} and url={url}")
call = Call(
request=Request(
url=url,
headers=dict(request.headers), # MutableMapping -> dict
data=request.data or b'', # type: ignore [arg-type]
params=_extract_params(request),
),
response=response,
)
self.calls.append(call)
return response


def _strip_query_string(url: str) -> str:
parsed = urllib.parse.urlparse(url)
return parsed._replace(query="").geturl()


def _extract_params(request: urllib.request.Request) -> dict:
query = urllib.parse.urlparse(request.full_url).query
if not query:
return {}
params = urllib.parse.parse_qs(query)
for key, values in params.items():
if len(values) == 1:
params[key] = values[0] # type: ignore [assignment]
return params
28 changes: 28 additions & 0 deletions tests/test_githost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
from unittest import mock

from check_oldies import branches
from check_oldies import githost


FAKE_GITHUB_API_RESPONSE = [
{
"number": 1234,
"state": "open",
"html_url": "https://github.com/polyconseil/check-oldies/pull/1234",
},
]


@mock.patch.dict(os.environ, {"TOKEN": "secret"}, clear=True)
def test_github_api(requests_mocker):
requests_mocker.get(
"https://api.github.com/repos/polyconseil/check-oldies/pulls",
json=FAKE_GITHUB_API_RESPONSE,
)
api_access = branches.GitHostApiAccessInfo(auth_token_env_var="TOKEN")
api = githost.GitHubApi("polyconseil", api_access)
pull_request = api.get_pull_request("check-oldies", "my-branch")
assert pull_request.number == 1234
assert pull_request.state == "open"
assert pull_request.url == "https://github.com/polyconseil/check-oldies/pull/1234"
16 changes: 16 additions & 0 deletions tests/test_requests_mocker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import urllib.request

from . import requests_mocker


def test_extract_params():
url = "https://example.com/?single=1&multiple=2&multiple=3&empty="
request = urllib.request.Request(url)
params = requests_mocker._extract_params(request)
assert params == {"single": "1", "multiple": ["2", "3"]}


def test_strip_query_string():
url = "https://example.com/path?foo=1"
stripped = requests_mocker._strip_query_string(url)
assert stripped == "https://example.com/path"

0 comments on commit f792408

Please sign in to comment.