diff --git a/pyproject.toml b/pyproject.toml index 2ec93951..9bf16967 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ dependencies = [ ] optional-dependencies."test" = [ "anyio >= 4.3.0", - "aresponses >= 3", "coverage[toml] >= 7.4.3", "pytest >= 8", "pytest-xdist >= 3.5.0", diff --git a/tests/conftest.py b/tests/conftest.py index 17ea0168..8652d063 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,11 +3,8 @@ from pathlib import Path from typing import Any -import aiohttp -import aiohttp.web import pytest -from aresponses import ResponsesMockServer -from aresponses.errors import NoRouteFoundError +from _pytest.fixtures import SubRequest from yarl import URL import instawow._logging @@ -17,7 +14,7 @@ from instawow.shared_ctx import ConfigBoundCtx from instawow.wow_installations import _DELECTABLE_DIR_NAMES, Flavour -from .fixtures.http import ROUTES +from .fixtures.http import ROUTES, ResponsesMockServer def pytest_addoption(parser: pytest.Parser): @@ -42,17 +39,13 @@ def caplog(caplog: pytest.LogCaptureFixture): instawow._logging.logger.remove(handler_id) -class _StrictResponsesMockServer(ResponsesMockServer): - async def _find_response(self, request: aiohttp.web.Request): - response = await super()._find_response(request) - if response == (None, None): - raise NoRouteFoundError(f'No match found for <{request.method} {request.url}>') - return response - - @pytest.fixture -async def iw_aresponses(): - async with _StrictResponsesMockServer() as server: +async def iw_aresponses( + monkeypatch: pytest.MonkeyPatch, +): + async with ResponsesMockServer() as server: + monkeypatch.setattr('aiohttp.TCPConnector', server.tcp_connector_class) + monkeypatch.setattr('aiohttp.ClientRequest.is_ssl', lambda _: False) yield server @@ -115,7 +108,9 @@ def _iw_global_config_defaults( @pytest.fixture -async def iw_web_client(iw_global_config: instawow.config.GlobalConfig): +async def iw_web_client( + iw_global_config: instawow.config.GlobalConfig, +): async with instawow.http.init_web_client(iw_global_config.http_cache_dir) as web_client: yield web_client @@ -132,9 +127,7 @@ def iw_config_ctx(iw_profile_config: instawow.config.ProfileConfig): @pytest.fixture(autouse=True, params=['all']) -async def _iw_mock_aiohttp_requests( - request: pytest.FixtureRequest, iw_aresponses: _StrictResponsesMockServer -): +async def _iw_mock_aiohttp_requests(request: SubRequest, iw_aresponses: ResponsesMockServer): if request.config.getoption('--iw-no-mock-http') or any( m.name == 'iw_no_mock_http' for m in request.node.iter_markers() ): @@ -150,5 +143,4 @@ async def _iw_mock_aiohttp_requests( routes = (ROUTES[k] for k in ROUTES.keys() & urls) - for route in routes: - iw_aresponses.add(**route.to_aresponses_add_args()) + iw_aresponses.add(*routes) diff --git a/tests/fixtures/http/__init__.py b/tests/fixtures/http/__init__.py index 43e4b188..f3a4f348 100644 --- a/tests/fixtures/http/__init__.py +++ b/tests/fixtures/http/__init__.py @@ -1,23 +1,18 @@ -# pyright: strict - from __future__ import annotations import importlib.resources import json import re -from collections.abc import Awaitable, Callable from functools import cache from io import BytesIO -from typing import Any from zipfile import ZipFile -import attrs -from aiohttp.web import Request, Response from yarl import URL from instawow._version import get_version -_match_any = re.compile(r'.*') +from ._mock_server import Response, Route +from ._mock_server import ResponsesMockServer as ResponsesMockServer def _load_fixture(filename: str): @@ -38,199 +33,145 @@ def _make_addon_zip(*folders: str): return buffer.getvalue() -@attrs.frozen -class Route: - url: URL = attrs.field(converter=URL) - response: ( - Response - | Callable[[Request], Response] - | Callable[[Request], Awaitable[Response]] - | dict[str, Any] - | str - ) - path_pattern: re.Pattern[str] | None = None - method: str = 'GET' - body_pattern: re.Pattern[str] | None = None - match_querystring: bool = False - repeat: float = float('inf') - case_insensitive: bool = False - - def _make_path_pattern(self): - if self.path_pattern is not None: - return self.path_pattern - - if self.case_insensitive: - return re.compile(rf'^{re.escape(self.url.path_qs)}$', re.IGNORECASE) - - return self.url.path_qs - - def to_aresponses_add_args(self) -> dict[str, Any]: - return { - 'host_pattern': self.url.host, - 'path_pattern': self._make_path_pattern(), - 'method_pattern': self.method, - 'body_pattern': _match_any if self.body_pattern is None else self.body_pattern, - 'match_querystring': self.match_querystring, - 'repeat': self.repeat, - 'response': self.response, - } - - ROUTES = { r.url: r for r in ( Route( - '//pypi.org/pypi/instawow/json', + r'//pypi\.org/pypi/instawow/json', {'info': {'version': get_version()}}, ), Route( - '//raw.githubusercontent.com/layday/instawow-data/data/base-catalogue-v7.compact.json', + r'//raw\.githubusercontent\.com/layday/instawow-data/data/base-catalogue-v7\.compact\.json', _load_json_fixture('base-catalogue-v7.compact.json'), ), Route( - '//api.curseforge.com/v1/mods', + r'//api\.curseforge\.com/v1/mods', _load_json_fixture('curse-addon--all.json'), method='POST', ), Route( - '//api.curseforge.com/v1/mods/search?gameId=1&slug=molinari', + r'//api\.curseforge\.com/v1/mods/search\?gameId=1&slug=molinari', _load_json_fixture('curse-addon-slug-search.json'), - match_querystring=True, ), Route( - '//api.curseforge.com/v1/mods/20338/files', + r'//api\.curseforge\.com/v1/mods/20338/files', _load_json_fixture('curse-addon-files.json'), ), Route( - '//api.curseforge.com/v1/mods/20338/files/4419396', + r'//api\.curseforge\.com/v1/mods/20338/files/4419396', _load_json_fixture('curse-addon-file-4419396.json'), ), Route( - '//api.curseforge.com/v1/mods/20338/files/5090686', + r'//api\.curseforge\.com/v1/mods/20338/files/5090686', _load_json_fixture('curse-addon-file-5090686.json'), ), Route( - '//api.curseforge.com/v1/mods/20338/files/{id}/changelog', + r'//api\.curseforge\.com/v1/mods/20338/files/(\d+)/changelog', _load_json_fixture('curse-addon-changelog.json'), - path_pattern=re.compile(r'^/v1/mods/20338/files/(\d+)/changelog$'), ), Route( - '//edge.forgecdn.net', + r'//edge\.forgecdn\.net/.*', lambda _: Response(body=_make_addon_zip('Molinari')), - path_pattern=_match_any, ), Route( - '//api.mmoui.com/v3/game/WOW/filelist.json', + r'//api\.mmoui\.com/v3/game/WOW/filelist\.json', _load_json_fixture('wowi-filelist.json'), ), Route( - '//api.mmoui.com/v3/game/WOW/filedetails/{id}.json', + r'//api\.mmoui\.com/v3/game/WOW/filedetails/(\d*)\.json', _load_json_fixture('wowi-filedetails.json'), - path_pattern=re.compile(r'^/v3/game/WOW/filedetails/(\d*)\.json$'), ), Route( - '//cdn.wowinterface.com', + r'//cdn\.wowinterface\.com/.*', lambda _: Response(body=_make_addon_zip('Molinari')), - path_pattern=_match_any, ), Route( - '//api.tukui.org/v1/addon/tukui', + r'//api\.tukui\.org/v1/addon/tukui', _load_json_fixture('tukui-ui--tukui.json'), ), Route( - '//api.tukui.org/v1/addon/elvui', + r'//api\.tukui\.org/v1/addon/elvui', _load_json_fixture('tukui-ui--elvui.json'), ), Route( - '//api.tukui.org/v1/download/', + r'//api\.tukui\.org/v1/download/.*', lambda _: Response(body=_make_addon_zip('Tukui')), - path_pattern=re.compile(r'^/v1/download/'), ), Route( - '//api.github.com/repos/nebularg/PackagerTest', + r'//api\.github\.com/repos/nebularg/PackagerTest', _load_json_fixture('github-repo-release-json.json'), ), Route( - '//api.github.com/repos/nebularg/PackagerTest/releases?per_page=10', + r'//api\.github\.com/repos/nebularg/PackagerTest/releases\?per_page=10', _load_json_fixture('github-release-release-json.json'), - match_querystring=True, ), Route( - '//api.github.com/repos/nebularg/PackagerTest/releases/assets/37156458', + r'//api\.github\.com/repos/nebularg/PackagerTest/releases/assets/37156458', _load_json_fixture('github-release-release-json-release-json.json'), ), Route( - '//api.github.com/repositories/388670', + r'//api\.github\.com/repositories/388670', _load_json_fixture('github-repo-molinari.json'), ), Route( - '//api.github.com/repos/p3lim-wow/Molinari', + r'//api\.github\.com/repos/p3lim-wow/Molinari', _load_json_fixture('github-repo-molinari.json'), - case_insensitive=True, ), Route( - '//api.github.com/repositories/388670/releases?per_page=10', + r'//api\.github\.com/repositories/388670/releases\?per_page=10', _load_json_fixture('github-release-molinari.json'), - case_insensitive=True, - match_querystring=True, ), Route( - '//api.github.com/repos/p3lim-wow/Molinari/releases?per_page=10', + r'//api\.github\.com/repos/p3lim-wow/Molinari/releases\?per_page=10', _load_json_fixture('github-release-molinari.json'), - case_insensitive=True, - match_querystring=True, ), Route( - URL( + re.escape( next( a['url'] for a in _load_json_fixture('github-release-molinari.json')[0]['assets'] if a['name'] == 'release.json' ) - ).with_scheme(''), + ), _load_json_fixture('github-release-molinari-release-json.json'), - case_insensitive=True, ), Route( - '//api.github.com/repos/AdiAddons/AdiBags', + r'//api\.github\.com/repos/AdiAddons/AdiBags', _load_json_fixture('github-repo-no-releases.json'), ), Route( - '//api.github.com/repos/AdiAddons/AdiBags/releases?per_page=10', + r'//api\.github\.com/repos/AdiAddons/AdiBags/releases\?per_page=10', lambda _: Response(body=b'', status=404), - match_querystring=True, ), Route( - '//api.github.com/repos/AdiAddons/AdiButtonAuras/releases/tags/2.0.19', + r'//api\.github\.com/repos/AdiAddons/AdiButtonAuras/releases/tags/2\.0\.19', _load_json_fixture('github-release-no-assets.json'), ), Route( - '//api.github.com/repos/layday/foobar', + r'//api\.github\.com/repos/layday/foobar', lambda _: Response(body=b'', status=404), ), Route( - '//api.github.com/repos/{x}/{y}/releases/asssets/{z}', + r'//api\.github\.com/repos(/[^/]*){2}/releases/assets/.*', lambda _: Response(body=_make_addon_zip('Molinari')), - path_pattern=re.compile(r'^/repos(/[^/]*){2}/releases/assets/'), ), Route( - '//github.com/login/device/code', + r'//github\.com/login/device/code', _load_json_fixture('github-oauth-login-device-code.json'), method='POST', ), Route( - '//github.com/login/oauth/access_token', + r'//github\.com/login/oauth/access_token', _load_json_fixture('github-oauth-login-access-token.json'), method='POST', ), Route( - '//api.github.com/repos/28/NoteworthyII', + r'//api\.github\.com/repos/28/NoteworthyII', _load_json_fixture('github-repo-no-release-json.json'), ), Route( - '//api.github.com/repos/28/NoteworthyII/releases?per_page=10', + r'//api\.github\.com/repos/28/NoteworthyII/releases\?per_page=10', _load_json_fixture('github-release-no-release-json.json'), - match_querystring=True, ), ) } diff --git a/tests/fixtures/http/_mock_server.py b/tests/fixtures/http/_mock_server.py new file mode 100644 index 00000000..c94b27e1 --- /dev/null +++ b/tests/fixtures/http/_mock_server.py @@ -0,0 +1,102 @@ +""" +Trimmed down version of aresponses. +""" + +from __future__ import annotations + +import asyncio +import re +from collections.abc import Awaitable, Callable, Sequence +from copy import copy +from typing import Any + +import attrs +from aiohttp.connector import TCPConnector +from aiohttp.test_utils import RawTestServer +from aiohttp.tracing import Trace +from aiohttp.web import Response +from aiohttp.web_request import BaseRequest +from aiohttp.web_response import json_response +from yarl import URL + +_Response = ( + Response + | Callable[[BaseRequest], Response] + | Callable[[BaseRequest], Awaitable[Response]] + | dict[str, Any] + | list[Any] + | str +) + + +class NoRouteFoundError(AssertionError): + pass + + +@attrs.frozen +class Route: + url: URL = attrs.field(converter=URL) + response: _Response + method: str = attrs.field(converter=str.upper, default='GET') + single_use: bool = False + path_qs_pattern: re.Pattern[str] = attrs.field(init=False) + + def __attrs_post_init__(self): + object.__setattr__(self, 'path_qs_pattern', re.compile(self.url.path_qs, re.IGNORECASE)) + + def matches(self, request: BaseRequest): + if self.method != request.method: + return False + elif self.url.host != re.escape(request.host): + return False + elif not self.path_qs_pattern.fullmatch(request.path_qs): + return False + return True + + +class ResponsesMockServer(RawTestServer): + def __init__(self, **kwargs: Any): + super().__init__(handler=self.__find_response, **kwargs) + self.__routes = list[Route]() + + async def __find_response(self, request: BaseRequest) -> Response: + for i, route in enumerate(self.__routes): + if not route.matches(request): + continue + + if route.single_use: + del self.__routes[i] + + match route.response: + case Callable() as fn: + return await fn(request) if asyncio.iscoroutinefunction(fn) else fn(request) # pyright: ignore[reportReturnType] + case str() as text: + return Response(body=text) + case (dict() | list()) as json: + return json_response(data=json) + case prepared_response: + return copy(prepared_response) + else: + raise NoRouteFoundError(f'No match found for <{request.method} {request.url}>') + + def add(self, *routes: Route) -> None: + self.__routes.extend(routes) + + @property + def tcp_connector_class(self) -> type[TCPConnector]: + class _TCPConnector(TCPConnector): + async def _resolve_host( + _self: TCPConnector, host: str, port: int, traces: Sequence[Trace] | None = None + ): + return [ + { + 'hostname': host, + 'host': '127.0.0.1', + 'port': self.port, + 'family': _self._family, + 'proto': 0, + 'flags': 0, + } + ] + + return _TCPConnector diff --git a/tests/test_config.py b/tests/test_config.py index 45a739de..42eac438 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -49,7 +49,7 @@ def test_init_with_nonexistent_addon_dir_raises( del values['_installation_dir'] with pytest.raises(ValueError, match='not a writable directory'): - ProfileConfig(**{'global_config': global_config, **values, 'addon_dir': '#@$foo'}) + ProfileConfig(**{**values, 'global_config': global_config, 'addon_dir': '#@$foo'}) def test_default_config_dir( diff --git a/tests/test_pkg_management.py b/tests/test_pkg_management.py index 66e5837b..f616f52d 100644 --- a/tests/test_pkg_management.py +++ b/tests/test_pkg_management.py @@ -3,7 +3,7 @@ from pathlib import Path import aiohttp -import aresponses +import aiohttp.web import attrs import pytest @@ -26,7 +26,7 @@ ) from instawow.shared_ctx import ConfigBoundCtx -from .fixtures.http import Route +from .fixtures.http import ResponsesMockServer, Route @pytest.mark.usefixtures('_iw_web_client_ctx') @@ -141,14 +141,14 @@ async def test_install_cannot_replace_reconciled_folders(iw_config_ctx: ConfigBo @pytest.mark.usefixtures('_iw_web_client_ctx') async def test_install_recognises_renamed_pkg_from_id( monkeypatch: pytest.MonkeyPatch, - iw_aresponses: aresponses.ResponsesMockServer, + iw_aresponses: ResponsesMockServer, iw_config_ctx: ConfigBoundCtx, ): iw_aresponses.add( - **Route( - '//api.github.com/repos/p3lim-wow/molinarifico', - iw_aresponses.Response(status=404), - ).to_aresponses_add_args() + Route( + r'//api\.github\.com/repos/p3lim-wow/molinarifico/?.*', + aiohttp.web.Response(status=404), + ) ) old_defn = Defn('github', 'p3lim-wow/molinari') diff --git a/tests/test_source__github.py b/tests/test_source__github.py index aa350e02..d165946a 100644 --- a/tests/test_source__github.py +++ b/tests/test_source__github.py @@ -1,14 +1,12 @@ from __future__ import annotations import logging -import re from io import BytesIO from typing import Literal from zipfile import ZipFile import aiohttp.hdrs import aiohttp.web -import aresponses import pytest from yarl import URL @@ -19,7 +17,7 @@ from instawow.shared_ctx import ConfigBoundCtx from instawow.wow_installations import Flavour, FlavourVersionRange -from .fixtures.http import Route +from .fixtures.http import ResponsesMockServer, Route @pytest.fixture @@ -60,7 +58,7 @@ def github_resolver( 'toc_files': { '': b'', }, - 'flavours': set(), + 'flavours': set[Flavour](), }, 'unflavoured-toc-only-with-interface-version': { 'toc_files': { @@ -88,8 +86,8 @@ def package_json_less_addon( '_iw_mock_aiohttp_requests', [ { - f'//api.github.com/repos/{zip_defn.alias}', - f'//api.github.com/repos/{zip_defn.alias}/releases?per_page=10', + rf'//api\.github\.com/repos/{zip_defn.alias}', + rf'//api\.github\.com/repos/{zip_defn.alias}/releases\?per_page=10', } ], indirect=True, @@ -101,12 +99,12 @@ def package_json_less_addon( ) @pytest.mark.usefixtures('_iw_web_client_ctx') async def test_extracting_flavour_from_zip_contents( - iw_aresponses: aresponses.ResponsesMockServer, + iw_aresponses: ResponsesMockServer, iw_config_ctx: ConfigBoundCtx, github_resolver: GithubResolver, package_json_less_addon: tuple[bytes, set[Flavour]], ): - async def handle_request(request: aiohttp.web.Request): + async def handle_request(request: aiohttp.web.BaseRequest): if aiohttp.hdrs.RANGE in request.headers: raise aiohttp.web.HTTPRequestRangeNotSatisfiable @@ -115,11 +113,10 @@ async def handle_request(request: aiohttp.web.Request): return response iw_aresponses.add( - **Route( - '//api.github.com', - path_pattern=re.compile(r'^/repos(/[^/]*){2}/releases/assets/'), - response=handle_request, - ).to_aresponses_add_args() + Route( + r'//api\.github\.com/repos(/[^/]*){2}/releases/assets/.*', + handle_request, + ) ) addon, flavours = package_json_less_addon @@ -167,8 +164,8 @@ async def test_nonexistent_repo( '_iw_mock_aiohttp_requests', [ { - f'//api.github.com/repos/{packager_test_defn.alias}', - f'//api.github.com/repos/{packager_test_defn.alias}/releases?per_page=10', + rf'//api\.github\.com/repos/{packager_test_defn.alias}', + rf'//api\.github\.com/repos/{packager_test_defn.alias}/releases\?per_page=10', } ], indirect=True, @@ -176,7 +173,7 @@ async def test_nonexistent_repo( @pytest.mark.parametrize('any_flavour', [True, None]) @pytest.mark.usefixtures('_iw_web_client_ctx') async def test_any_flavour_strategy( - iw_aresponses: aresponses.ResponsesMockServer, + iw_aresponses: ResponsesMockServer, iw_config_ctx: ConfigBoundCtx, github_resolver: GithubResolver, any_flavour: Literal[True, None], @@ -187,10 +184,9 @@ async def test_any_flavour_strategy( ) iw_aresponses.add( - **Route( - '//api.github.com', - path_pattern=re.compile(r'^/repos/nebularg/PackagerTest/releases/assets/'), - response={ + Route( + r'//api\.github\.com/repos/nebularg/PackagerTest/releases/assets/.*', + { 'releases': [ { 'filename': 'TestGit-v1.9.7.zip', @@ -204,7 +200,7 @@ async def test_any_flavour_strategy( } ] }, - ).to_aresponses_add_args() + ) ) defn = Defn( 'github', @@ -239,8 +235,8 @@ async def test_changelog_is_data_url( '_iw_mock_aiohttp_requests', [ { - f'//api.github.com/repos/{packager_test_defn.alias}', - f'//api.github.com/repos/{packager_test_defn.alias}/releases?per_page=10', + rf'//api\.github\.com/repos/{packager_test_defn.alias}', + rf'//api\.github\.com/repos/{packager_test_defn.alias}/releases\?per_page=10', } ], indirect=True, @@ -248,17 +244,16 @@ async def test_changelog_is_data_url( @pytest.mark.usefixtures('_iw_web_client_ctx') async def test_mismatched_release_is_skipped_and_logged( caplog: pytest.LogCaptureFixture, - iw_aresponses: aresponses.ResponsesMockServer, + iw_aresponses: ResponsesMockServer, iw_config_ctx: ConfigBoundCtx, github_resolver: GithubResolver, flavor: str, interface: int, ): iw_aresponses.add( - **Route( - '//api.github.com', - path_pattern=re.compile(r'^/repos/nebularg/PackagerTest/releases/assets/'), - response={ + Route( + r'//api\.github\.com/repos/nebularg/PackagerTest/releases/assets/.*', + { 'releases': [ { 'filename': 'TestGit-v1.9.7.zip', @@ -267,7 +262,7 @@ async def test_mismatched_release_is_skipped_and_logged( } ] }, - ).to_aresponses_add_args() + ) ) with pytest.raises(PkgFilesNotMatching): diff --git a/tests/test_version.py b/tests/test_version.py index e6688760..939186f2 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,13 +1,13 @@ from __future__ import annotations +import aiohttp.web import attrs import pytest -from aresponses import ResponsesMockServer from instawow import _version from instawow.config import GlobalConfig -from .fixtures.http import Route +from .fixtures.http import ResponsesMockServer, Route @pytest.mark.parametrize( @@ -37,11 +37,9 @@ async def test_is_outdated_works_in_variety_of_scenarios( with monkeypatch.context() as patcher: patcher.setattr(_version, 'get_version', lambda: '0.1.0') iw_aresponses.add( - **Route( - '//pypi.org/simple/instawow', - iw_aresponses.Response(status=500), - repeat=1, - ).to_aresponses_add_args() + Route( + r'//pypi\.org/simple/instawow', aiohttp.web.Response(status=500), single_use=True + ) ) assert await _version.is_outdated(global_config) == (False, '0.1.0') @@ -49,11 +47,7 @@ async def test_is_outdated_works_in_variety_of_scenarios( with monkeypatch.context() as patcher: patcher.setattr(_version, 'get_version', lambda: '0.1.0') iw_aresponses.add( - **Route( - '//pypi.org/simple/instawow', - {'versions': ['1.0.0']}, - repeat=1, - ).to_aresponses_add_args() + Route(r'//pypi\.org/simple/instawow', {'versions': ['1.0.0']}, single_use=True) ) assert await _version.is_outdated(global_config) == (True, '1.0.0') @@ -72,11 +66,9 @@ async def test_is_outdated_works_in_variety_of_scenarios( with monkeypatch.context() as patcher: patcher.setattr(_version, 'get_version', lambda: '0.1.0') iw_aresponses.add( - **Route( - '//pypi.org/simple/instawow', - iw_aresponses.Response(status=500), - repeat=1, - ).to_aresponses_add_args() + Route( + r'//pypi\.org/simple/instawow', aiohttp.web.Response(status=500), single_use=True + ) ) assert await _version.is_outdated(global_config) == (True, '1.0.0') @@ -84,11 +76,7 @@ async def test_is_outdated_works_in_variety_of_scenarios( with monkeypatch.context() as patcher: patcher.setattr(_version, 'get_version', lambda: '0.1.0') iw_aresponses.add( - **Route( - '//pypi.org/simple/instawow', - {'versions': ['1.0.0']}, - repeat=1, - ).to_aresponses_add_args() + Route(r'//pypi\.org/simple/instawow', {'versions': ['1.0.0']}, single_use=True) ) assert await _version.is_outdated(global_config) == (True, '1.0.0') @@ -96,10 +84,6 @@ async def test_is_outdated_works_in_variety_of_scenarios( with monkeypatch.context() as patcher: patcher.setattr(_version, 'get_version', lambda: '1.0.0') iw_aresponses.add( - **Route( - '//pypi.org/simple/instawow', - {'versions': ['1.0.0']}, - repeat=1, - ).to_aresponses_add_args() + Route(r'//pypi\.org/simple/instawow', {'versions': ['1.0.0']}, single_use=True) ) assert await _version.is_outdated(global_config) == (False, '1.0.0') diff --git a/tests/test_wow_installations.py b/tests/test_wow_installations.py index c4959698..c0a7e508 100644 --- a/tests/test_wow_installations.py +++ b/tests/test_wow_installations.py @@ -3,6 +3,7 @@ import sys from enum import Enum from pathlib import Path +from unittest import mock import pytest @@ -105,12 +106,7 @@ def test_can_find_mac_installations( monkeypatch: pytest.MonkeyPatch, ): with monkeypatch.context() as patcher: - - def check_output_no_installation(*args, **kwargs): - return '' - - patcher.setattr('subprocess.check_output', check_output_no_installation) - + patcher.setattr('subprocess.check_output', mock.Mock(return_value='')) assert not list(find_installations()) with monkeypatch.context() as patcher: @@ -125,11 +121,10 @@ def check_output_no_installation(*args, **kwargs): }, } - def check_output_has_installations(*args, **kwargs): - return '\n'.join(map(str, app_bundle_paths)) - - patcher.setattr('subprocess.check_output', check_output_has_installations) - + patcher.setattr( + 'subprocess.check_output', + mock.Mock(return_value='\n'.join(map(str, app_bundle_paths))), + ) assert list(find_installations()) == [(p.parent, f) for p, f in app_bundle_paths.items()]