From ffb0b797c0fed2266c1d85c9d8bc9260324b2f99 Mon Sep 17 00:00:00 2001 From: ghinks <ghinks@yahoo.com> Date: Thu, 26 Dec 2024 08:50:58 -0500 Subject: [PATCH 1/7] feat: async testing - interim checkin --- poetry.lock | 17 ++++++- pyproject.toml | 1 + pytest.ini | 3 ++ tests/async_test_notes.md | 50 +++++++++++++++++++++ tests/fixtures/fixtures_reviews_response.py | 14 ++++++ tests/fixtures/reviews_response.json | 41 +++++++++++++++++ tests/test_get_reviewers_rest.py | 40 +++++++++++++++++ 7 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 pytest.ini create mode 100644 tests/async_test_notes.md create mode 100644 tests/fixtures/fixtures_reviews_response.py create mode 100644 tests/fixtures/reviews_response.json create mode 100644 tests/test_get_reviewers_rest.py diff --git a/poetry.lock b/poetry.lock index 9cd6923..aaa8246 100644 --- a/poetry.lock +++ b/poetry.lock @@ -108,6 +108,21 @@ yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +[[package]] +name = "aioresponses" +version = "0.7.7" +description = "Mock out requests made by ClientSession from aiohttp package" +optional = false +python-versions = "*" +files = [ + {file = "aioresponses-0.7.7-py2.py3-none-any.whl", hash = "sha256:6975f31fe5e7f2113a41bd387221f31854f285ecbc05527272cd8ba4c50764a3"}, + {file = "aioresponses-0.7.7.tar.gz", hash = "sha256:66292f1d5c94a3cb984f3336d806446042adb17347d3089f2d3962dd6e5ba55a"}, +] + +[package.dependencies] +aiohttp = ">=3.3.0,<4.0.0" +packaging = ">=22.0" + [[package]] name = "aiosignal" version = "1.3.1" @@ -862,4 +877,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "02a148172248ce5f797eba063b5a641faa47b6b4eb763ad2146b871a01c824fd" +content-hash = "16f6a4fb59c3414cda51c9e86aed644dbf6c9c4825f6d917b095496b59ea56bf" diff --git a/pyproject.toml b/pyproject.toml index 89e9a45..6d1558d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ aiohttp = "^3.11.2" requests = "^2.32.3" tabulate = "^0.9.0" asyncio = "^3.4.3" +aioresponses = "^0.7.7" [tool.poetry.group.dev.dependencies] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..c47d933 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +markers = + asyncio: mark a test as requiring asyncio \ No newline at end of file diff --git a/tests/async_test_notes.md b/tests/async_test_notes.md new file mode 100644 index 0000000..d1ecf8f --- /dev/null +++ b/tests/async_test_notes.md @@ -0,0 +1,50 @@ +# Asynch testing notes +## +## references +- [reviewers github api docs](https://docs.github.com/en/rest/pulls/reviews?apiVersion=2022-11-28) +- [github api docs](https://docs.github.com/en/rest/reference/pulls#reviews) + +### sample response +```json +[ + { + "id": 80, + "node_id": "MDE3OlB1bGxSZXF1ZXN0UmV2aWV3ODA=", + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "Here is the body for the review.", + "state": "APPROVED", + "html_url": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80", + "pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/12", + "_links": { + "html": { + "href": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80" + }, + "pull_request": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/12" + } + }, + "submitted_at": "2019-11-17T17:43:43Z", + "commit_id": "ecdd80bb57125d7ba9641ffaa4d7d2c19d3f3091", + "author_association": "COLLABORATOR" + } +] +``` \ No newline at end of file diff --git a/tests/fixtures/fixtures_reviews_response.py b/tests/fixtures/fixtures_reviews_response.py new file mode 100644 index 0000000..85da6f1 --- /dev/null +++ b/tests/fixtures/fixtures_reviews_response.py @@ -0,0 +1,14 @@ +import json +from typing import Any + +import pytest + + +def read_reviews_file() -> dict[str, Any]: + # assume the reviews_response.json file is in the tests/fixtures directory + with open("tests/fixtures/reviews_response.json") as file: + return json.load(file) + +def get_reviews_url(owner: str, repo: str, pull_number: int) -> str: + URL = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews" + return URL diff --git a/tests/fixtures/reviews_response.json b/tests/fixtures/reviews_response.json new file mode 100644 index 0000000..710bbd5 --- /dev/null +++ b/tests/fixtures/reviews_response.json @@ -0,0 +1,41 @@ +[ + { + "id": 80, + "node_id": "MDE3OlB1bGxSZXF1ZXN0UmV2aWV3ODA=", + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "Here is the body for the review.", + "state": "APPROVED", + "html_url": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80", + "pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/12", + "_links": { + "html": { + "href": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80" + }, + "pull_request": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/12" + } + }, + "submitted_at": "2019-11-17T17:43:43Z", + "commit_id": "ecdd80bb57125d7ba9641ffaa4d7d2c19d3f3091", + "author_association": "COLLABORATOR" + } +] diff --git a/tests/test_get_reviewers_rest.py b/tests/test_get_reviewers_rest.py new file mode 100644 index 0000000..8163703 --- /dev/null +++ b/tests/test_get_reviewers_rest.py @@ -0,0 +1,40 @@ +# Python +import json +import pytest +import unittest +from unittest.mock import AsyncMock, patch +import aiohttp +import asyncio +import os + +from aioresponses import aioresponses + +from pr_reviews.queries.get_reviewers_rest import (fetch, fetch_batch, + get_reviewers_for_pull_requests) +from tests.fixtures.fixtures_reviews_response import read_reviews_file, \ + get_reviews_url + +import asyncio +import os + + +class TestGetReviewersRest(unittest.TestCase): + def __init__(self, *args, **kwargs): + super(TestGetReviewersRest, self).__init__(*args, **kwargs) + self.REVIEWS_RESPONSE = read_reviews_file() + + @pytest.mark.asyncio + @patch.dict(os.environ, {"GITHUB_TOKEN": "test_token"}) + async def test_fetch(self): + with aioresponses() as mock_get: + url = get_reviews_url("expressjs", + "express", + 1) + mock_get.get(url, status=200, payload=self.REVIEWS_RESPONSE) + response = await fetch(url) + assert response == self.REVIEWS_RESPONSE + + + +if __name__ == "__main__": + unittest.main() From 59a37969bb8b3250818a9d7a79d03c1b2eb5940d Mon Sep 17 00:00:00 2001 From: ghinks <ghinks@yahoo.com> Date: Thu, 26 Dec 2024 17:25:15 -0500 Subject: [PATCH 2/7] feat: async testing - asyncio/aiohttp testing with aioresponses module --- poetry.lock | 39 ++++++++++- pr_reviews/queries/get_reviewers_rest.py | 10 +-- pyproject.toml | 8 ++- pytest.ini | 3 - tests/fixtures/fixtures_reviews_response.py | 14 ---- tests/test_get_reviewers_rest.py | 73 +++++++++++---------- 6 files changed, 87 insertions(+), 60 deletions(-) delete mode 100644 pytest.ini delete mode 100644 tests/fixtures/fixtures_reviews_response.py diff --git a/poetry.lock b/poetry.lock index aaa8246..2695ac0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -699,6 +699,43 @@ pluggy = ">=1.5,<2" [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-aiohttp" +version = "1.0.5" +description = "Pytest plugin for aiohttp support" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, + {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, +] + +[package.dependencies] +aiohttp = ">=3.8.1" +pytest = ">=6.1.0" +pytest-asyncio = ">=0.17.2" + +[package.extras] +testing = ["coverage (==6.2)", "mypy (==0.931)"] + +[[package]] +name = "pytest-asyncio" +version = "0.25.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"}, + {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "requests" version = "2.32.3" @@ -877,4 +914,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "16f6a4fb59c3414cda51c9e86aed644dbf6c9c4825f6d917b095496b59ea56bf" +content-hash = "506b7f02e3c09662096712ee841ae877d9a1481572168e2a340e1f7441490d8b" diff --git a/pr_reviews/queries/get_reviewers_rest.py b/pr_reviews/queries/get_reviewers_rest.py index ab86bd9..9aefdf9 100644 --- a/pr_reviews/queries/get_reviewers_rest.py +++ b/pr_reviews/queries/get_reviewers_rest.py @@ -12,18 +12,18 @@ HTTPS_PROXY = os.getenv("https_proxy") -async def fetch(session: aiohttp.ClientSession, url: str) -> dict[str, str]: +async def fetch(client: aiohttp.ClientSession, url: str) -> dict[str, str]: headers = { "Authorization": f"Bearer {GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json", } if HTTPS_PROXY: - async with session.get(url, - headers=headers, - proxy=HTTPS_PROXY) as response: + async with client.get(url, + headers=headers, + proxy=HTTPS_PROXY) as response: return await response.json() else: - async with session.get(url, headers=headers) as response: + async with client.get(url, headers=headers) as response: return await response.json() diff --git a/pyproject.toml b/pyproject.toml index 6d1558d..9e5fb2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,8 @@ aioresponses = "^0.7.7" pytest = "^8.3.4" ruff = "^0.8.4" isort = "^5.13.2" +pytest-aiohttp = "^1.0.5" +pytest-asyncio = "^0.25.0" [build-system] requires = ["poetry-core"] @@ -53,4 +55,8 @@ per-file-ignores = { "tests/**/test*.py" = ["S101"] } #PT: Pytest-specific rules #Q: Quotes (single vs. double) #S: Security issues -#T: Type annotations \ No newline at end of file +#T: Type annotations + +[tool.pytest.ini_options] +asyncio_mode = "auto" # or "strict" +required_plugins = ["pytest-asyncio"] \ No newline at end of file diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index c47d933..0000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -markers = - asyncio: mark a test as requiring asyncio \ No newline at end of file diff --git a/tests/fixtures/fixtures_reviews_response.py b/tests/fixtures/fixtures_reviews_response.py deleted file mode 100644 index 85da6f1..0000000 --- a/tests/fixtures/fixtures_reviews_response.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from typing import Any - -import pytest - - -def read_reviews_file() -> dict[str, Any]: - # assume the reviews_response.json file is in the tests/fixtures directory - with open("tests/fixtures/reviews_response.json") as file: - return json.load(file) - -def get_reviews_url(owner: str, repo: str, pull_number: int) -> str: - URL = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews" - return URL diff --git a/tests/test_get_reviewers_rest.py b/tests/test_get_reviewers_rest.py index 8163703..3c9adff 100644 --- a/tests/test_get_reviewers_rest.py +++ b/tests/test_get_reviewers_rest.py @@ -1,40 +1,41 @@ -# Python import json -import pytest -import unittest -from unittest.mock import AsyncMock, patch -import aiohttp -import asyncio import os +from typing import Callable, Any +from unittest.mock import patch +import aiohttp +import pytest from aioresponses import aioresponses - -from pr_reviews.queries.get_reviewers_rest import (fetch, fetch_batch, - get_reviewers_for_pull_requests) -from tests.fixtures.fixtures_reviews_response import read_reviews_file, \ - get_reviews_url - -import asyncio -import os - - -class TestGetReviewersRest(unittest.TestCase): - def __init__(self, *args, **kwargs): - super(TestGetReviewersRest, self).__init__(*args, **kwargs) - self.REVIEWS_RESPONSE = read_reviews_file() - - @pytest.mark.asyncio - @patch.dict(os.environ, {"GITHUB_TOKEN": "test_token"}) - async def test_fetch(self): - with aioresponses() as mock_get: - url = get_reviews_url("expressjs", - "express", - 1) - mock_get.get(url, status=200, payload=self.REVIEWS_RESPONSE) - response = await fetch(url) - assert response == self.REVIEWS_RESPONSE - - - -if __name__ == "__main__": - unittest.main() +from pr_reviews.queries.get_reviewers_rest import fetch + +@pytest.fixture +def read_reviews_file(): + # assume the reviews_response.json file is in the tests/fixtures directory + with open("tests/fixtures/reviews_response.json") as file: + return json.load(file) + +@pytest.fixture +def get_reviews_url(): + def _get_reviews_url(owner: str, repo: str, pull_number: int) -> str: + url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews" + return url + return _get_reviews_url + + +@pytest.fixture +def mock_aioresponse(): + with aioresponses() as m: + yield m + +@pytest.mark.asyncio +@patch.dict(os.environ, {"GITHUB_TOKEN": "test_token"}) +async def test_get_reviewers_success(read_reviews_file, get_reviews_url, mock_aioresponse): + url = get_reviews_url("expressjs", "express", 1) + mock_aioresponse.get( + url, + status=200, + payload=read_reviews_file + ) + async with aiohttp.ClientSession() as client: + response = await fetch(client, url) + assert response == read_reviews_file From 88f423f6c9411219a92abd3ebafc5929eef7d6f0 Mon Sep 17 00:00:00 2001 From: ghinks <ghinks@yahoo.com> Date: Thu, 26 Dec 2024 17:54:50 -0500 Subject: [PATCH 3/7] feat: async testing - ruff lint, mostly annotations --- tests/test_get_reviewers_rest.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/test_get_reviewers_rest.py b/tests/test_get_reviewers_rest.py index 3c9adff..1019e8f 100644 --- a/tests/test_get_reviewers_rest.py +++ b/tests/test_get_reviewers_rest.py @@ -1,40 +1,44 @@ import json import os -from typing import Callable, Any +from pathlib import Path +from typing import Callable from unittest.mock import patch import aiohttp import pytest from aioresponses import aioresponses + from pr_reviews.queries.get_reviewers_rest import fetch + @pytest.fixture -def read_reviews_file(): +def read_reviews_file() -> str: # assume the reviews_response.json file is in the tests/fixtures directory - with open("tests/fixtures/reviews_response.json") as file: - return json.load(file) + with Path("tests/fixtures/reviews_response.json").open("r") as file: + return json.dumps(json.load(file)) @pytest.fixture -def get_reviews_url(): +def get_reviews_url() -> callable: def _get_reviews_url(owner: str, repo: str, pull_number: int) -> str: - url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews" - return url + return f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews" return _get_reviews_url @pytest.fixture -def mock_aioresponse(): +def mock_aioresponse() -> aioresponses: with aioresponses() as m: yield m @pytest.mark.asyncio @patch.dict(os.environ, {"GITHUB_TOKEN": "test_token"}) -async def test_get_reviewers_success(read_reviews_file, get_reviews_url, mock_aioresponse): +async def test_get_reviewers_success(read_reviews_file: str, + get_reviews_url: Callable[[],str], + mock_aioresponse: aioresponses) -> None: url = get_reviews_url("expressjs", "express", 1) mock_aioresponse.get( url, status=200, - payload=read_reviews_file + payload=read_reviews_file, ) async with aiohttp.ClientSession() as client: response = await fetch(client, url) From 81876f47424bf4f165a59f15a31ab28202396c1a Mon Sep 17 00:00:00 2001 From: ghinks <ghinks@yahoo.com> Date: Mon, 6 Jan 2025 14:35:08 -0500 Subject: [PATCH 4/7] feat: async testing - add a class based test for aioresponses that has the same functionality as the non class based example. --- tests/test_get_reviewers_rest_cls.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/test_get_reviewers_rest_cls.py diff --git a/tests/test_get_reviewers_rest_cls.py b/tests/test_get_reviewers_rest_cls.py new file mode 100644 index 0000000..ee8d7ec --- /dev/null +++ b/tests/test_get_reviewers_rest_cls.py @@ -0,0 +1,34 @@ +import asyncio +import json +import unittest +from pathlib import Path + +import aiohttp +from aioresponses import aioresponses + +from pr_reviews.queries.get_reviewers_rest import fetch + + +class TestFetchJson(unittest.TestCase): + def read_reviews_file(self) -> str: + # assume the reviews_response.json file is in + # the tests/fixtures directory + with Path("tests/fixtures/reviews_response.json").open("r") as file: + return json.dumps(json.load(file)) + + def get_reviews_url(self, owner: str, repo: str, pull_number: int) -> str: + return ("https://api.github.com/repos/" + f"{owner}/{repo}/pulls/{pull_number}/reviews") + + @aioresponses() + def test_fetch_json(self, mocked: aioresponses) -> None: + url = self.get_reviews_url("expressjs", "express", 1) + payload = self.read_reviews_file() + mocked.get(url, status=200, payload=payload) + + async def run_test() -> None: + async with aiohttp.ClientSession() as session: + result = await fetch(session,url) + assert result == payload + + asyncio.run(run_test()) From 7c2bdb1a536d204f8d3864662870b4bc08d753e5 Mon Sep 17 00:00:00 2001 From: ghinks <ghinks@yahoo.com> Date: Mon, 6 Jan 2025 14:52:40 -0500 Subject: [PATCH 5/7] feat: async testing - file renaming - batch fetch testing --- ...ls.py => test_get_reviewers_rest_fetch.py} | 2 +- tests/test_get_reviewers_rest_fetch_batch.py | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) rename tests/{test_get_reviewers_rest_cls.py => test_get_reviewers_rest_fetch.py} (96%) create mode 100644 tests/test_get_reviewers_rest_fetch_batch.py diff --git a/tests/test_get_reviewers_rest_cls.py b/tests/test_get_reviewers_rest_fetch.py similarity index 96% rename from tests/test_get_reviewers_rest_cls.py rename to tests/test_get_reviewers_rest_fetch.py index ee8d7ec..d865a0f 100644 --- a/tests/test_get_reviewers_rest_cls.py +++ b/tests/test_get_reviewers_rest_fetch.py @@ -9,7 +9,7 @@ from pr_reviews.queries.get_reviewers_rest import fetch -class TestFetchJson(unittest.TestCase): +class TestFetch(unittest.TestCase): def read_reviews_file(self) -> str: # assume the reviews_response.json file is in # the tests/fixtures directory diff --git a/tests/test_get_reviewers_rest_fetch_batch.py b/tests/test_get_reviewers_rest_fetch_batch.py new file mode 100644 index 0000000..33e2921 --- /dev/null +++ b/tests/test_get_reviewers_rest_fetch_batch.py @@ -0,0 +1,37 @@ +import asyncio +import json +import unittest +from pathlib import Path + +from aioresponses import aioresponses + +from pr_reviews.queries.get_reviewers_rest import fetch_batch + + +class TestFetchBatch(unittest.TestCase): + def read_reviews_file(self) -> str: + # assume the reviews_response.json file is in + # the tests/fixtures directory + with Path("tests/fixtures/reviews_response.json").open("r") as file: + return json.dumps(json.load(file)) + + def get_reviews_url(self, owner: str, repo: str, pull_number: int) -> str: + return ("https://api.github.com/repos/" + f"{owner}/{repo}/pulls/{pull_number}/reviews") + + @aioresponses() + def test_fetch_json(self, mocked: aioresponses) -> None: + payload = self.read_reviews_file() + urls =[] + for pull_number in range(2): + url = self.get_reviews_url("expressjs", + "express", + pull_number) + urls.append(url) + mocked.get(url, status=200, payload=payload) + + async def run_test() -> None: + result = await fetch_batch(urls) + assert result == [payload, payload] + + asyncio.run(run_test()) From a82308ff0879865ffcdd8cd2eebc050b5ab4bcf9 Mon Sep 17 00:00:00 2001 From: ghinks <ghinks@yahoo.com> Date: Tue, 7 Jan 2025 10:13:03 -0500 Subject: [PATCH 6/7] feat: async testing - batch fetch testing --- pr_reviews/queries/get_reviewers_rest.py | 1 + tests/get_reviewers/__init__.py | 0 .../test_get_reviewers_rest.py | 0 .../test_get_reviewers_rest_fetch.py | 15 ++-------- .../test_get_reviewers_rest_fetch_batch.py | 18 ++---------- .../test_get_reviewers_rest_pull_requests.py | 29 +++++++++++++++++++ tests/utils.py | 11 +++++++ 7 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 tests/get_reviewers/__init__.py rename tests/{ => get_reviewers}/test_get_reviewers_rest.py (100%) rename tests/{ => get_reviewers}/test_get_reviewers_rest_fetch.py (50%) rename tests/{ => get_reviewers}/test_get_reviewers_rest_fetch_batch.py (53%) create mode 100644 tests/get_reviewers/test_get_reviewers_rest_pull_requests.py create mode 100644 tests/utils.py diff --git a/pr_reviews/queries/get_reviewers_rest.py b/pr_reviews/queries/get_reviewers_rest.py index 9aefdf9..f83fa82 100644 --- a/pr_reviews/queries/get_reviewers_rest.py +++ b/pr_reviews/queries/get_reviewers_rest.py @@ -42,4 +42,5 @@ def get_reviewers_for_pull_requests( for pull_number in pull_numbers ] reviewers = asyncio.run(fetch_batch(urls)) + # print(reviewers) return [item["user"] for sublist in reviewers for item in sublist] diff --git a/tests/get_reviewers/__init__.py b/tests/get_reviewers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_get_reviewers_rest.py b/tests/get_reviewers/test_get_reviewers_rest.py similarity index 100% rename from tests/test_get_reviewers_rest.py rename to tests/get_reviewers/test_get_reviewers_rest.py diff --git a/tests/test_get_reviewers_rest_fetch.py b/tests/get_reviewers/test_get_reviewers_rest_fetch.py similarity index 50% rename from tests/test_get_reviewers_rest_fetch.py rename to tests/get_reviewers/test_get_reviewers_rest_fetch.py index d865a0f..e030a1f 100644 --- a/tests/test_get_reviewers_rest_fetch.py +++ b/tests/get_reviewers/test_get_reviewers_rest_fetch.py @@ -7,23 +7,14 @@ from aioresponses import aioresponses from pr_reviews.queries.get_reviewers_rest import fetch +from tests.utils import read_reviews_file, get_reviews_url class TestFetch(unittest.TestCase): - def read_reviews_file(self) -> str: - # assume the reviews_response.json file is in - # the tests/fixtures directory - with Path("tests/fixtures/reviews_response.json").open("r") as file: - return json.dumps(json.load(file)) - - def get_reviews_url(self, owner: str, repo: str, pull_number: int) -> str: - return ("https://api.github.com/repos/" - f"{owner}/{repo}/pulls/{pull_number}/reviews") - @aioresponses() def test_fetch_json(self, mocked: aioresponses) -> None: - url = self.get_reviews_url("expressjs", "express", 1) - payload = self.read_reviews_file() + url = get_reviews_url("expressjs", "express", 1) + payload = read_reviews_file() mocked.get(url, status=200, payload=payload) async def run_test() -> None: diff --git a/tests/test_get_reviewers_rest_fetch_batch.py b/tests/get_reviewers/test_get_reviewers_rest_fetch_batch.py similarity index 53% rename from tests/test_get_reviewers_rest_fetch_batch.py rename to tests/get_reviewers/test_get_reviewers_rest_fetch_batch.py index 33e2921..e801cf7 100644 --- a/tests/test_get_reviewers_rest_fetch_batch.py +++ b/tests/get_reviewers/test_get_reviewers_rest_fetch_batch.py @@ -1,30 +1,18 @@ import asyncio -import json import unittest -from pathlib import Path from aioresponses import aioresponses from pr_reviews.queries.get_reviewers_rest import fetch_batch - +from tests.utils import read_reviews_file, get_reviews_url class TestFetchBatch(unittest.TestCase): - def read_reviews_file(self) -> str: - # assume the reviews_response.json file is in - # the tests/fixtures directory - with Path("tests/fixtures/reviews_response.json").open("r") as file: - return json.dumps(json.load(file)) - - def get_reviews_url(self, owner: str, repo: str, pull_number: int) -> str: - return ("https://api.github.com/repos/" - f"{owner}/{repo}/pulls/{pull_number}/reviews") - @aioresponses() def test_fetch_json(self, mocked: aioresponses) -> None: - payload = self.read_reviews_file() + payload = read_reviews_file() urls =[] for pull_number in range(2): - url = self.get_reviews_url("expressjs", + url = get_reviews_url("expressjs", "express", pull_number) urls.append(url) diff --git a/tests/get_reviewers/test_get_reviewers_rest_pull_requests.py b/tests/get_reviewers/test_get_reviewers_rest_pull_requests.py new file mode 100644 index 0000000..891af43 --- /dev/null +++ b/tests/get_reviewers/test_get_reviewers_rest_pull_requests.py @@ -0,0 +1,29 @@ +import asyncio +import unittest + +from aioresponses import aioresponses +from pr_reviews.queries.get_reviewers_rest import get_reviewers_for_pull_requests +from tests.utils import read_reviews_file, get_reviews_url + +class TestGetReviewers(unittest.TestCase): + OWNER = "expressjs" + REPO = "express" + PULL_REQUESTS = [1, 2] + @aioresponses() + def test_get_reviewers(self, mocked: aioresponses) -> None: + payload = read_reviews_file() + urls =[] + for pull_number in self.PULL_REQUESTS: + url = get_reviews_url(self.OWNER, + self.REPO, + pull_number) + urls.append(url) + mocked.get(url, status=200, payload=payload) + + results = get_reviewers_for_pull_requests(self.OWNER, + self.REPO, + self.PULL_REQUESTS) + print(results[0]) + assert results[0]['id'] == 1 + + diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..6bdc3f3 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,11 @@ +# Python +from pathlib import Path +import json + +def read_reviews_file() -> str: + # assume the reviews_response.json file is in the tests/fixtures directory + with Path("tests/fixtures/reviews_response.json").open("r") as file: + return json.load(file) + +def get_reviews_url(owner: str, repo: str, pull_number: int) -> str: + return f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews" \ No newline at end of file From 4a914686c3c90d2fbbc73ce934887bb7691faba1 Mon Sep 17 00:00:00 2001 From: ghinks <ghinks@yahoo.com> Date: Tue, 7 Jan 2025 10:57:49 -0500 Subject: [PATCH 7/7] feat: async testing - linting , ruff --- pr_reviews/queries/get_reviewers_rest.py | 1 - pyproject.toml | 1 + .../get_reviewers/test_get_reviewers_rest_fetch.py | 4 +--- .../test_get_reviewers_rest_fetch_batch.py | 3 ++- .../test_get_reviewers_rest_pull_requests.py | 14 ++++++++------ tests/utils.py | 5 +++-- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/pr_reviews/queries/get_reviewers_rest.py b/pr_reviews/queries/get_reviewers_rest.py index f83fa82..9aefdf9 100644 --- a/pr_reviews/queries/get_reviewers_rest.py +++ b/pr_reviews/queries/get_reviewers_rest.py @@ -42,5 +42,4 @@ def get_reviewers_for_pull_requests( for pull_number in pull_numbers ] reviewers = asyncio.run(fetch_batch(urls)) - # print(reviewers) return [item["user"] for sublist in reviewers for item in sublist] diff --git a/pyproject.toml b/pyproject.toml index 9e5fb2f..8d15be8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ exclude = [ "venv", "pr_reviews/**/__init__.py", "tests/__init__.py", + "tests/get_reviewers/__init__.py" ] [tool.ruff.lint] diff --git a/tests/get_reviewers/test_get_reviewers_rest_fetch.py b/tests/get_reviewers/test_get_reviewers_rest_fetch.py index e030a1f..c496068 100644 --- a/tests/get_reviewers/test_get_reviewers_rest_fetch.py +++ b/tests/get_reviewers/test_get_reviewers_rest_fetch.py @@ -1,13 +1,11 @@ import asyncio -import json import unittest -from pathlib import Path import aiohttp from aioresponses import aioresponses from pr_reviews.queries.get_reviewers_rest import fetch -from tests.utils import read_reviews_file, get_reviews_url +from tests.utils import get_reviews_url, read_reviews_file class TestFetch(unittest.TestCase): diff --git a/tests/get_reviewers/test_get_reviewers_rest_fetch_batch.py b/tests/get_reviewers/test_get_reviewers_rest_fetch_batch.py index e801cf7..04c66ac 100644 --- a/tests/get_reviewers/test_get_reviewers_rest_fetch_batch.py +++ b/tests/get_reviewers/test_get_reviewers_rest_fetch_batch.py @@ -4,7 +4,8 @@ from aioresponses import aioresponses from pr_reviews.queries.get_reviewers_rest import fetch_batch -from tests.utils import read_reviews_file, get_reviews_url +from tests.utils import get_reviews_url, read_reviews_file + class TestFetchBatch(unittest.TestCase): @aioresponses() diff --git a/tests/get_reviewers/test_get_reviewers_rest_pull_requests.py b/tests/get_reviewers/test_get_reviewers_rest_pull_requests.py index 891af43..c995f56 100644 --- a/tests/get_reviewers/test_get_reviewers_rest_pull_requests.py +++ b/tests/get_reviewers/test_get_reviewers_rest_pull_requests.py @@ -1,14 +1,17 @@ -import asyncio import unittest from aioresponses import aioresponses -from pr_reviews.queries.get_reviewers_rest import get_reviewers_for_pull_requests -from tests.utils import read_reviews_file, get_reviews_url + +from pr_reviews.queries.get_reviewers_rest import ( + get_reviewers_for_pull_requests, +) +from tests.utils import get_reviews_url, read_reviews_file + class TestGetReviewers(unittest.TestCase): OWNER = "expressjs" REPO = "express" - PULL_REQUESTS = [1, 2] + PULL_REQUESTS: tuple[int, ...] = (1, 2) @aioresponses() def test_get_reviewers(self, mocked: aioresponses) -> None: payload = read_reviews_file() @@ -23,7 +26,6 @@ def test_get_reviewers(self, mocked: aioresponses) -> None: results = get_reviewers_for_pull_requests(self.OWNER, self.REPO, self.PULL_REQUESTS) - print(results[0]) - assert results[0]['id'] == 1 + assert results[0]["id"] == 1 diff --git a/tests/utils.py b/tests/utils.py index 6bdc3f3..271690f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,6 +1,7 @@ # Python -from pathlib import Path import json +from pathlib import Path + def read_reviews_file() -> str: # assume the reviews_response.json file is in the tests/fixtures directory @@ -8,4 +9,4 @@ def read_reviews_file() -> str: return json.load(file) def get_reviews_url(owner: str, repo: str, pull_number: int) -> str: - return f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews" \ No newline at end of file + return f"https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews"