From c22bb6540cc4384b82f0020e014ce38daad936fe Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Tue, 19 Nov 2024 15:52:09 +0100 Subject: [PATCH] Replace pretenders with pytest-httpserver in tests - Simpliy test_access.py, fixtures, and data. - Update [tests] dependencies in pyproject.toml - Update mypy config --- ...cess.properties => test_access.properties} | 0 .../test_check_single_model_access.properties | 16 -- ixmp/tests/test_access.py | 140 ++++++------------ pyproject.toml | 8 +- 4 files changed, 50 insertions(+), 114 deletions(-) rename ixmp/tests/data/{test_check_multi_model_access.properties => test_access.properties} (100%) delete mode 100644 ixmp/tests/data/test_check_single_model_access.properties diff --git a/ixmp/tests/data/test_check_multi_model_access.properties b/ixmp/tests/data/test_access.properties similarity index 100% rename from ixmp/tests/data/test_check_multi_model_access.properties rename to ixmp/tests/data/test_access.properties diff --git a/ixmp/tests/data/test_check_single_model_access.properties b/ixmp/tests/data/test_check_single_model_access.properties deleted file mode 100644 index 24a595ba8..000000000 --- a/ixmp/tests/data/test_check_single_model_access.properties +++ /dev/null @@ -1,16 +0,0 @@ -# Used by test_access.py - -config.name = unit_test_db@local - -jdbc.driver = org.hsqldb.jdbcDriver -jdbc.url = jdbc:hsqldb:mem:test_access -jdbc.user = ixmp -jdbc.pwd = ixmp - -application.tag = IXSE_SR15 -application.serverURL = http://localhost:8888 - -config.server.url = {auth_url} -config.server.config = DemoDB -config.server.username = service_user_dev -config.server.password = service_user_dev diff --git a/ixmp/tests/test_access.py b/ixmp/tests/test_access.py index 42d6df993..bd24be44d 100644 --- a/ixmp/tests/test_access.py +++ b/ixmp/tests/test_access.py @@ -1,116 +1,64 @@ -import logging -import sys -from subprocess import Popen -from time import sleep +import json import pytest -from pretenders.client.http import HTTPMock -from pretenders.common.constants import FOREVER import ixmp from ixmp.testing import create_test_platform -log = logging.getLogger(__name__) +@pytest.fixture +def mock(httpserver): + """Mock server with responses for both tests.""" + from werkzeug import Request, Response -@pytest.fixture(scope="session") -def server(): - proc = Popen( - [ - sys.executable, - "-m", - "pretenders.server.server", - "--host", - "localhost", - "--port", - "8000", - ] + httpserver.expect_request("/login", method="POST").respond_with_json( + "security-token" ) - log.info(f"Mock server started with pid {proc.pid}") - - # Wait for server to start up - sleep(5) - yield + # Mock the behaviour of the ixmp_source (Java) access API + # - Request data is valid JSON containing a list of dict. + # - Response is a JSON list of bool of equal length. + def handler(r: Request) -> Response: + data = r.get_json() + result = [ + (i["username"], i["entityType"], i["entityId"]) + == ("test_user", "MODEL", "test_model") + for i in data + ] + return Response(json.dumps(result), content_type="application/json") - proc.terminate() - log.info("Mock server terminated") + # Use the same handler for all test requests against the /access/list URL + httpserver.expect_request( + "/access/list", + method="POST", + headers={"Authorization": "Bearer security-token"}, # JSON Web Token header + ).respond_with_handler(handler) + return httpserver -@pytest.fixture(scope="function") -def mock(server): - # Create the mock server - httpmock = HTTPMock("localhost", 8000) - # Common responses for both tests - httpmock.when("POST /login").reply( - '"security-token"', headers={"Content-Type": "application/json"}, times=FOREVER +@pytest.fixture +def test_props(mock, request, tmp_path, test_data_path): + return create_test_platform( + tmp_path, test_data_path, "test_access", auth_url=mock.url_for("") ) - yield httpmock +M = ["test_model", "non_existing_model"] -def test_check_single_model_access(mock, tmp_path, test_data_path, request): - mock.when( - "POST /access/list", - body='.+"test_user".+', - headers={"Authorization": "Bearer security-token"}, - ).reply("[true]", headers={"Content-Type": "application/json"}, times=FOREVER) - mock.when( - "POST /access/list", - body='.+"non_granted_user".+', - headers={"Authorization": "Bearer security-token"}, - ).reply("[false]", headers={"Content-Type": "application/json"}, times=FOREVER) - - test_props = create_test_platform( - tmp_path, - test_data_path, - f"{request.node.name}", - auth_url=mock.pretend_url, - ) +@pytest.mark.parametrize( + "user, models, exp", + ( + ("test_user", "test_model", True), + ("non_granted_user", "test_model", False), + ("non_existing_user", "test_model", False), + ("test_user", M, {"test_model": True, "non_existing_model": False}), + ("non_granted_user", M, {"test_model": False, "non_existing_model": False}), + ("non_existing_user", M, {"test_model": False, "non_existing_model": False}), + ), +) +def test_check_access(test_props, user, models, exp): + """:meth:`.check_access` correctly handles certain arguments and responses.""" mp = ixmp.Platform(backend="jdbc", dbprops=test_props) - - granted = mp.check_access("test_user", "test_model") - assert granted - - granted = mp.check_access("non_granted_user", "test_model") - assert not granted - - granted = mp.check_access("non_existing_user", "test_model") - assert not granted - - -def test_check_multi_model_access(mock, tmp_path, test_data_path, request): - mock.when( - "POST /access/list", - body='.+"test_user".+', - headers={"Authorization": "Bearer security-token"}, - ).reply( - "[true, false]", headers={"Content-Type": "application/json"}, times=FOREVER - ) - mock.when( - "POST /access/list", - body='.+"non_granted_user".+', - headers={"Authorization": "Bearer security-token"}, - ).reply( - "[false, false]", headers={"Content-Type": "application/json"}, times=FOREVER - ) - - test_props = create_test_platform( - tmp_path, test_data_path, f"{request.node.name}", auth_url=mock.pretend_url - ) - - mp = ixmp.Platform(backend="jdbc", dbprops=test_props) - - access = mp.check_access("test_user", ["test_model", "non_existing_model"]) - assert access["test_model"] - assert not access["non_existing_model"] - - access = mp.check_access("non_granted_user", ["test_model", "non_existing_model"]) - assert not access["test_model"] - assert not access["non_existing_model"] - - access = mp.check_access("non_existing_user", ["test_model", "non_existing_model"]) - assert not access["test_model"] - assert not access["non_existing_model"] + assert exp == mp.check_access(user, models) diff --git a/pyproject.toml b/pyproject.toml index 2ecdc7f8c..ede4e147c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,10 +63,10 @@ tests = [ "ixmp[report,tutorial]", "memory_profiler", "nbclient >= 0.5", - "pretenders >= 1.4.4", "pytest >= 5", "pytest-benchmark", "pytest-cov", + "pytest-httpserver", "pytest-rerunfailures", "pytest-xdist", ] @@ -81,12 +81,16 @@ exclude_also = [ ] omit = ["ixmp/util/sphinx_linkcode_github.py"] +[tool.mypy] +exclude = [ + "build/", +] + [[tool.mypy.overrides]] # Packages/modules for which no type hints are available. module = [ "jpype", "memory_profiler", - "pretenders.*", "pyam", ] ignore_missing_imports = true