From 178f69cb0c21d2a449f7405610d73835f2fee686 Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Mon, 29 Jan 2024 16:16:31 -0400 Subject: [PATCH] Refactor controller tests to use flask_app_fixture (PP-893) (#1634) * Remove uses of the admin settings controller. * Convert discovery_services tests. * Fix up library settings tests * Fix up catalog services controller tests. * Replace admin controller fixture for TestAdminSearchController * Replace admin controller fixture for DiscoveryServiceLibraryRegistrationsController * Replace fixture for TestAdminPermissionsControllerMixin * Replace fixtures in DeviceTokensController tests. --- api/admin/controller/__init__.py | 2 - api/circulation_manager.py | 2 - .../test_admin_search_controller.py | 63 ++-- tests/api/admin/controller/test_base.py | 86 +++-- .../admin/controller/test_catalog_services.py | 104 +++--- .../controller/test_discovery_services.py | 126 +++---- tests/api/admin/controller/test_library.py | 308 ++++++++++-------- .../controller/test_library_registrations.py | 94 +++--- tests/api/admin/controller/test_settings.py | 14 +- tests/api/test_device_tokens.py | 68 ++-- tests/fixtures/api_admin.py | 6 + tests/fixtures/flask.py | 10 +- 12 files changed, 497 insertions(+), 386 deletions(-) diff --git a/api/admin/controller/__init__.py b/api/admin/controller/__init__.py index 822c133a59..65936c7320 100644 --- a/api/admin/controller/__init__.py +++ b/api/admin/controller/__init__.py @@ -30,7 +30,6 @@ def setup_admin_controllers(manager: CirculationManager): from api.admin.controller.patron import PatronController from api.admin.controller.patron_auth_services import PatronAuthServicesController from api.admin.controller.reset_password import ResetPasswordController - from api.admin.controller.settings import SettingsController from api.admin.controller.sign_in import SignInController from api.admin.controller.sitewide_settings import ( SitewideConfigurationSettingsController, @@ -48,7 +47,6 @@ def setup_admin_controllers(manager: CirculationManager): manager.admin_custom_lists_controller = CustomListsController(manager) manager.admin_lanes_controller = LanesController(manager) manager.admin_dashboard_controller = DashboardController(manager) - manager.admin_settings_controller = SettingsController(manager) manager.admin_patron_controller = PatronController(manager) manager.admin_discovery_services_controller = DiscoveryServicesController(manager) manager.admin_discovery_service_library_registrations_controller = ( diff --git a/api/circulation_manager.py b/api/circulation_manager.py index e66f5f530d..1ed1393916 100644 --- a/api/circulation_manager.py +++ b/api/circulation_manager.py @@ -64,7 +64,6 @@ from api.admin.controller.patron_auth_services import PatronAuthServicesController from api.admin.controller.quicksight import QuickSightController from api.admin.controller.reset_password import ResetPasswordController - from api.admin.controller.settings import SettingsController from api.admin.controller.sign_in import SignInController from api.admin.controller.sitewide_settings import ( SitewideConfigurationSettingsController, @@ -100,7 +99,6 @@ class CirculationManager(LoggerMixin): admin_custom_lists_controller: CustomListsController admin_lanes_controller: LanesController admin_dashboard_controller: DashboardController - admin_settings_controller: SettingsController admin_patron_controller: PatronController admin_discovery_services_controller: DiscoveryServicesController admin_discovery_service_library_registrations_controller: DiscoveryServiceLibraryRegistrationsController diff --git a/tests/api/admin/controller/test_admin_search_controller.py b/tests/api/admin/controller/test_admin_search_controller.py index 6d34f0d19e..cad5d2cbc3 100644 --- a/tests/api/admin/controller/test_admin_search_controller.py +++ b/tests/api/admin/controller/test_admin_search_controller.py @@ -1,19 +1,22 @@ +from unittest.mock import MagicMock + import pytest +from api.admin.controller.admin_search import AdminSearchController from core.model.classification import Subject from core.model.datasource import DataSource from core.model.licensing import LicensePool from core.model.work import Work -from tests.fixtures.api_admin import AdminControllerFixture +from tests.fixtures.database import DatabaseTransactionFixture +from tests.fixtures.flask import FlaskAppFixture class AdminSearchFixture: - def __init__(self, admin_ctrl_fixture: AdminControllerFixture): - self.admin_ctrl_fixture = admin_ctrl_fixture - self.manager = admin_ctrl_fixture.manager - self.db = self.admin_ctrl_fixture.ctrl.db - - db = self.db + def __init__(self, db: DatabaseTransactionFixture): + self.db = db + mock_manager = MagicMock() + mock_manager._db = db.session + self.controller = AdminSearchController(mock_manager) # Setup works with subjects, languages, audiences etc... gutenberg = DataSource.lookup(db.session, DataSource.GUTENBERG) @@ -77,20 +80,23 @@ def __init__(self, admin_ctrl_fixture: AdminControllerFixture): @pytest.fixture(scope="function") def admin_search_fixture( - admin_ctrl_fixture: AdminControllerFixture, + db: DatabaseTransactionFixture, ) -> AdminSearchFixture: - return AdminSearchFixture(admin_ctrl_fixture) + return AdminSearchFixture(db) class TestAdminSearchController: - def test_search_field_values(self, admin_search_fixture: AdminSearchFixture): - with admin_search_fixture.admin_ctrl_fixture.request_context_with_library_and_admin( + def test_search_field_values( + self, + admin_search_fixture: AdminSearchFixture, + flask_app_fixture: FlaskAppFixture, + db: DatabaseTransactionFixture, + ): + with flask_app_fixture.test_request_context( "/", - library=admin_search_fixture.admin_ctrl_fixture.ctrl.db.default_library(), + library=db.default_library(), ): - response = ( - admin_search_fixture.manager.admin_search_controller.search_field_values() - ) + response = admin_search_fixture.controller.search_field_values() assert response["subjects"] == { "subject 1": 1, @@ -104,14 +110,19 @@ def test_search_field_values(self, admin_search_fixture: AdminSearchFixture): assert response["publishers"] == {"Publisher 1": 3, "Publisher 10": 10} assert response["distributors"] == {"Gutenberg": 13} - def test_different_license_types(self, admin_search_fixture: AdminSearchFixture): + def test_different_license_types( + self, + admin_search_fixture: AdminSearchFixture, + flask_app_fixture: FlaskAppFixture, + db: DatabaseTransactionFixture, + ): # Remove the cache - admin_search_fixture.manager.admin_search_controller.__class__._search_field_values_cached.ttls = ( # type: ignore + admin_search_fixture.controller.__class__._search_field_values_cached.ttls = ( # type: ignore 0 ) w = ( - admin_search_fixture.db.session.query(Work) + db.session.query(Work) .filter(Work.presentation_edition.has(title="work3")) .first() ) @@ -122,24 +133,20 @@ def test_different_license_types(self, admin_search_fixture: AdminSearchFixture) # A pool without licenses should not attribute to the count pool.licenses_owned = 0 - with admin_search_fixture.admin_ctrl_fixture.request_context_with_library_and_admin( + with flask_app_fixture.test_request_context( "/", - library=admin_search_fixture.admin_ctrl_fixture.ctrl.db.default_library(), + library=db.default_library(), ): - response = ( - admin_search_fixture.manager.admin_search_controller.search_field_values() - ) + response = admin_search_fixture.controller.search_field_values() assert "Horror" not in response["genres"] assert "Spanish" not in response["languages"] # An open access license should get counted even without owned licenses pool.open_access = True - with admin_search_fixture.admin_ctrl_fixture.request_context_with_library_and_admin( + with flask_app_fixture.test_request_context( "/", - library=admin_search_fixture.admin_ctrl_fixture.ctrl.db.default_library(), + library=db.default_library(), ): - response = ( - admin_search_fixture.manager.admin_search_controller.search_field_values() - ) + response = admin_search_fixture.controller.search_field_values() assert "Horror" in response["genres"] assert "Spanish" in response["languages"] diff --git a/tests/api/admin/controller/test_base.py b/tests/api/admin/controller/test_base.py index 635df2acff..249c117ea0 100644 --- a/tests/api/admin/controller/test_base.py +++ b/tests/api/admin/controller/test_base.py @@ -1,59 +1,83 @@ import pytest +from api.admin.controller.base import AdminPermissionsControllerMixin from api.admin.exceptions import AdminNotAuthorized from core.model import AdminRole -from tests.fixtures.api_admin import AdminControllerFixture +from tests.fixtures.database import DatabaseTransactionFixture +from tests.fixtures.flask import FlaskAppFixture + + +@pytest.fixture() +def controller() -> AdminPermissionsControllerMixin: + return AdminPermissionsControllerMixin() class TestAdminPermissionsControllerMixin: - def test_require_system_admin(self, admin_ctrl_fixture: AdminControllerFixture): - with admin_ctrl_fixture.request_context_with_admin("/admin"): + def test_require_system_admin( + self, + controller: AdminPermissionsControllerMixin, + flask_app_fixture: FlaskAppFixture, + ): + with flask_app_fixture.test_request_context("/admin"): pytest.raises( AdminNotAuthorized, - admin_ctrl_fixture.manager.admin_work_controller.require_system_admin, + controller.require_system_admin, ) - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) - admin_ctrl_fixture.manager.admin_work_controller.require_system_admin() + with flask_app_fixture.test_request_context_system_admin("/admin"): + controller.require_system_admin() def test_require_sitewide_library_manager( - self, admin_ctrl_fixture: AdminControllerFixture + self, + controller: AdminPermissionsControllerMixin, + flask_app_fixture: FlaskAppFixture, ): - with admin_ctrl_fixture.request_context_with_admin("/admin"): + with flask_app_fixture.test_request_context("/admin"): pytest.raises( AdminNotAuthorized, - admin_ctrl_fixture.manager.admin_work_controller.require_sitewide_library_manager, + controller.require_sitewide_library_manager, ) - admin_ctrl_fixture.admin.add_role(AdminRole.SITEWIDE_LIBRARY_MANAGER) - admin_ctrl_fixture.manager.admin_work_controller.require_sitewide_library_manager() + library_manager = flask_app_fixture.admin_user( + role=AdminRole.SITEWIDE_LIBRARY_MANAGER + ) + with flask_app_fixture.test_request_context("/admin", admin=library_manager): + controller.require_sitewide_library_manager() - def test_require_library_manager(self, admin_ctrl_fixture: AdminControllerFixture): - with admin_ctrl_fixture.request_context_with_admin("/admin"): + def test_require_library_manager( + self, + controller: AdminPermissionsControllerMixin, + flask_app_fixture: FlaskAppFixture, + db: DatabaseTransactionFixture, + ): + with flask_app_fixture.test_request_context("/admin"): pytest.raises( AdminNotAuthorized, - admin_ctrl_fixture.manager.admin_work_controller.require_library_manager, - admin_ctrl_fixture.ctrl.db.default_library(), + controller.require_library_manager, + db.default_library(), ) - admin_ctrl_fixture.admin.add_role( - AdminRole.LIBRARY_MANAGER, admin_ctrl_fixture.ctrl.db.default_library() - ) - admin_ctrl_fixture.manager.admin_work_controller.require_library_manager( - admin_ctrl_fixture.ctrl.db.default_library() - ) + library_manager = flask_app_fixture.admin_user( + role=AdminRole.LIBRARY_MANAGER, library=db.default_library() + ) + with flask_app_fixture.test_request_context("/admin", admin=library_manager): + controller.require_library_manager(db.default_library()) - def test_require_librarian(self, admin_ctrl_fixture: AdminControllerFixture): - with admin_ctrl_fixture.request_context_with_admin("/admin"): + def test_require_librarian( + self, + controller: AdminPermissionsControllerMixin, + flask_app_fixture: FlaskAppFixture, + db: DatabaseTransactionFixture, + ): + with flask_app_fixture.test_request_context("/admin"): pytest.raises( AdminNotAuthorized, - admin_ctrl_fixture.manager.admin_work_controller.require_librarian, - admin_ctrl_fixture.ctrl.db.default_library(), + controller.require_librarian, + db.default_library(), ) - admin_ctrl_fixture.admin.add_role( - AdminRole.LIBRARIAN, admin_ctrl_fixture.ctrl.db.default_library() - ) - admin_ctrl_fixture.manager.admin_work_controller.require_librarian( - admin_ctrl_fixture.ctrl.db.default_library() - ) + librarian = flask_app_fixture.admin_user( + role=AdminRole.LIBRARIAN, library=db.default_library() + ) + with flask_app_fixture.test_request_context("/admin", admin=librarian): + controller.require_librarian(db.default_library()) diff --git a/tests/api/admin/controller/test_catalog_services.py b/tests/api/admin/controller/test_catalog_services.py index fa8f5d76d1..5c9e9e1012 100644 --- a/tests/api/admin/controller/test_catalog_services.py +++ b/tests/api/admin/controller/test_catalog_services.py @@ -1,11 +1,13 @@ import json from contextlib import nullcontext +from unittest.mock import MagicMock import flask import pytest from flask import Response from werkzeug.datastructures import ImmutableMultiDict +from api.admin.controller.catalog_services import CatalogServicesController from api.admin.exceptions import AdminNotAuthorized from api.admin.problem_details import ( CANNOT_CHANGE_PROTOCOL, @@ -19,26 +21,31 @@ from api.integration.registry.catalog_services import CatalogServicesRegistry from core.integration.goals import Goals from core.marc import MARCExporter, MarcExporterLibrarySettings -from core.model import AdminRole, IntegrationConfiguration, get_one +from core.model import IntegrationConfiguration, get_one from core.util.problem_detail import ProblemDetail -from tests.fixtures.api_admin import AdminControllerFixture +from tests.fixtures.database import DatabaseTransactionFixture +from tests.fixtures.flask import FlaskAppFixture + + +@pytest.fixture +def controller(db: DatabaseTransactionFixture) -> CatalogServicesController: + mock_manager = MagicMock() + mock_manager._db = db.session + return CatalogServicesController(mock_manager) class TestCatalogServicesController: def test_catalog_services_get_with_no_services( - self, admin_ctrl_fixture: AdminControllerFixture + self, flask_app_fixture: FlaskAppFixture, controller: CatalogServicesController ): - with admin_ctrl_fixture.request_context_with_admin("/"): + with flask_app_fixture.test_request_context("/"): pytest.raises( AdminNotAuthorized, - admin_ctrl_fixture.manager.admin_catalog_services_controller.process_catalog_services, + controller.process_catalog_services, ) - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) - - response = ( - admin_ctrl_fixture.manager.admin_catalog_services_controller.process_catalog_services() - ) + with flask_app_fixture.test_request_context_system_admin("/"): + response = controller.process_catalog_services() assert isinstance(response, Response) assert response.status_code == 200 data = response.json @@ -55,10 +62,11 @@ def test_catalog_services_get_with_no_services( assert "library_settings" in protocols[0] def test_catalog_services_get_with_marc_exporter( - self, admin_ctrl_fixture: AdminControllerFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: CatalogServicesController, + db: DatabaseTransactionFixture, ): - db = admin_ctrl_fixture.ctrl.db - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) library_settings = MarcExporterLibrarySettings( include_summary=True, include_genres=True, organization_code="US-MaBoDPL" ) @@ -77,10 +85,8 @@ def test_catalog_services_get_with_marc_exporter( library_settings_integration, library_settings ) - with admin_ctrl_fixture.request_context_with_admin("/"): - response = ( - admin_ctrl_fixture.manager.admin_catalog_services_controller.process_catalog_services() - ) + with flask_app_fixture.test_request_context_system_admin("/"): + response = controller.process_catalog_services() assert isinstance(response, Response) assert response.status_code == 200 data = response.json @@ -93,10 +99,7 @@ def test_catalog_services_get_with_marc_exporter( assert integration.name == service.get("name") assert integration.protocol == service.get("protocol") [library] = service.get("libraries") - assert ( - admin_ctrl_fixture.ctrl.db.default_library().short_name - == library.get("short_name") - ) + assert db.default_library().short_name == library.get("short_name") assert "US-MaBoDPL" == library.get("organization_code") assert library.get("include_summary") is True assert library.get("include_genres") is True @@ -156,18 +159,21 @@ def test_catalog_services_get_with_marc_exporter( ) def test_catalog_services_post_errors( self, - admin_ctrl_fixture: AdminControllerFixture, + flask_app_fixture: FlaskAppFixture, + controller: CatalogServicesController, + db: DatabaseTransactionFixture, post_data: dict[str, str], expected: ProblemDetail | None, admin: bool, raises: type[Exception] | None, ): if admin: - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) + make_request = flask_app_fixture.test_request_context_system_admin + else: + make_request = flask_app_fixture.test_request_context context_manager = pytest.raises(raises) if raises is not None else nullcontext() - db = admin_ctrl_fixture.ctrl.db service = db.integration_configuration( "fake protocol", Goals.CATALOG_GOAL, @@ -178,12 +184,10 @@ def test_catalog_services_post_errors( if post_data.get("id") == "": post_data["id"] = str(service.id) - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with make_request("/", method="POST"): flask.request.form = ImmutableMultiDict(post_data) with context_manager: - response = ( - admin_ctrl_fixture.manager.admin_catalog_services_controller.process_catalog_services() - ) + response = controller.process_catalog_services() assert isinstance(response, ProblemDetail) assert isinstance(expected, ProblemDetail) assert response.uri == expected.uri @@ -191,14 +195,15 @@ def test_catalog_services_post_errors( assert response.title == expected.title def test_catalog_services_post_create( - self, admin_ctrl_fixture: AdminControllerFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: CatalogServicesController, + db: DatabaseTransactionFixture, ): - db = admin_ctrl_fixture.ctrl.db protocol = CatalogServicesRegistry().get_protocol(MARCExporter) assert protocol is not None - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "exporter name"), @@ -217,9 +222,7 @@ def test_catalog_services_post_create( ), ] ) - response = ( - admin_ctrl_fixture.manager.admin_catalog_services_controller.process_catalog_services() - ) + response = controller.process_catalog_services() assert isinstance(response, Response) assert response.status_code == 201 @@ -240,12 +243,13 @@ def test_catalog_services_post_create( assert settings.include_genres is True def test_catalog_services_post_edit( - self, admin_ctrl_fixture: AdminControllerFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: CatalogServicesController, + db: DatabaseTransactionFixture, ): - db = admin_ctrl_fixture.ctrl.db protocol = CatalogServicesRegistry().get_protocol(MARCExporter) assert protocol is not None - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) service = db.integration_configuration( protocol, @@ -253,7 +257,7 @@ def test_catalog_services_post_edit( name="name", ) - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "exporter name"), @@ -273,9 +277,7 @@ def test_catalog_services_post_edit( ), ] ) - response = ( - admin_ctrl_fixture.manager.admin_catalog_services_controller.process_catalog_services() - ) + response = controller.process_catalog_services() assert isinstance(response, Response) assert response.status_code == 200 @@ -288,8 +290,12 @@ def test_catalog_services_post_edit( assert settings.include_summary is True assert settings.include_genres is False - def test_catalog_services_delete(self, admin_ctrl_fixture: AdminControllerFixture): - db = admin_ctrl_fixture.ctrl.db + def test_catalog_services_delete( + self, + flask_app_fixture: FlaskAppFixture, + controller: CatalogServicesController, + db: DatabaseTransactionFixture, + ): protocol = CatalogServicesRegistry().get_protocol(MARCExporter) assert protocol is not None @@ -299,17 +305,15 @@ def test_catalog_services_delete(self, admin_ctrl_fixture: AdminControllerFixtur name="name", ) - with admin_ctrl_fixture.request_context_with_admin("/", method="DELETE"): + with flask_app_fixture.test_request_context("/", method="DELETE"): pytest.raises( AdminNotAuthorized, - admin_ctrl_fixture.manager.admin_catalog_services_controller.process_delete, + controller.process_delete, service.id, ) - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) - response = admin_ctrl_fixture.manager.admin_catalog_services_controller.process_delete( - service.id - ) + with flask_app_fixture.test_request_context_system_admin("/", method="DELETE"): + response = controller.process_delete(service.id) assert isinstance(response, Response) assert response.status_code == 200 diff --git a/tests/api/admin/controller/test_discovery_services.py b/tests/api/admin/controller/test_discovery_services.py index 5c5ed4d071..d533817dc6 100644 --- a/tests/api/admin/controller/test_discovery_services.py +++ b/tests/api/admin/controller/test_discovery_services.py @@ -1,12 +1,14 @@ from __future__ import annotations from typing import TYPE_CHECKING +from unittest.mock import MagicMock import flask import pytest from flask import Response from werkzeug.datastructures import ImmutableMultiDict +from api.admin.controller.discovery_services import DiscoveryServicesController from api.admin.exceptions import AdminNotAuthorized from api.admin.problem_details import ( INCOMPLETE_CONFIGURATION, @@ -19,12 +21,22 @@ from api.discovery.opds_registration import OpdsRegistrationService from api.integration.registry.discovery import DiscoveryRegistry from core.integration.goals import Goals -from core.model import AdminRole, ExternalIntegration, IntegrationConfiguration, get_one +from core.model import ExternalIntegration, IntegrationConfiguration, get_one from core.util.problem_detail import ProblemDetail +from tests.fixtures.flask import FlaskAppFixture if TYPE_CHECKING: - from tests.fixtures.api_admin import SettingsControllerFixture - from tests.fixtures.database import IntegrationConfigurationFixture + from tests.fixtures.database import ( + DatabaseTransactionFixture, + IntegrationConfigurationFixture, + ) + + +@pytest.fixture +def controller(db: DatabaseTransactionFixture) -> DiscoveryServicesController: + mock_manager = MagicMock() + mock_manager._db = db.session + return DiscoveryServicesController(mock_manager) class TestDiscoveryServices: @@ -39,12 +51,12 @@ def protocol(self): return registry.get_protocol(OpdsRegistrationService) def test_discovery_services_get_with_no_services_creates_default( - self, settings_ctrl_fixture: SettingsControllerFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: DiscoveryServicesController, ): - with settings_ctrl_fixture.request_context_with_admin("/"): - response = ( - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_discovery_services() - ) + with flask_app_fixture.test_request_context_system_admin("/"): + response = controller.process_discovery_services() assert response.status_code == 200 assert isinstance(response, Response) json = response.get_json() @@ -60,25 +72,26 @@ def test_discovery_services_get_with_no_services_creates_default( "name" ) + with flask_app_fixture.test_request_context("/"): # Only system admins can see the discovery services. - settings_ctrl_fixture.admin.remove_role(AdminRole.SYSTEM_ADMIN) - settings_ctrl_fixture.ctrl.db.session.flush() pytest.raises( AdminNotAuthorized, - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_discovery_services, + controller.process_discovery_services, ) def test_discovery_services_get_with_one_service( self, - settings_ctrl_fixture: SettingsControllerFixture, + flask_app_fixture: FlaskAppFixture, + controller: DiscoveryServicesController, + db: DatabaseTransactionFixture, create_integration_configuration: IntegrationConfigurationFixture, ): discovery_service = create_integration_configuration.discovery_service( - url=settings_ctrl_fixture.ctrl.db.fresh_str() + url=db.fresh_str() ) - controller = settings_ctrl_fixture.manager.admin_discovery_services_controller + controller = controller - with settings_ctrl_fixture.request_context_with_admin("/"): + with flask_app_fixture.test_request_context_system_admin("/"): response = controller.process_discovery_services() assert isinstance(response, Response) [service] = response.get_json().get("discovery_services") @@ -91,11 +104,13 @@ def test_discovery_services_get_with_one_service( def test_discovery_services_post_errors( self, - settings_ctrl_fixture: SettingsControllerFixture, + flask_app_fixture: FlaskAppFixture, + controller: DiscoveryServicesController, + db: DatabaseTransactionFixture, create_integration_configuration: IntegrationConfigurationFixture, ): - controller = settings_ctrl_fixture.manager.admin_discovery_services_controller - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + controller = controller + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Name"), @@ -105,7 +120,7 @@ def test_discovery_services_post_errors( response = controller.process_discovery_services() assert response == UNKNOWN_PROTOCOL - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Name"), @@ -114,7 +129,7 @@ def test_discovery_services_post_errors( response = controller.process_discovery_services() assert response == NO_PROTOCOL_FOR_NEW_SERVICE - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Name"), @@ -125,11 +140,11 @@ def test_discovery_services_post_errors( response = controller.process_discovery_services() assert response == MISSING_SERVICE - integration_url = settings_ctrl_fixture.ctrl.db.fresh_url() + integration_url = db.fresh_url() existing_integration = create_integration_configuration.discovery_service( url=integration_url ) - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): assert isinstance(existing_integration.name, str) flask.request.form = ImmutableMultiDict( [ @@ -141,7 +156,7 @@ def test_discovery_services_post_errors( response = controller.process_discovery_services() assert response == INTEGRATION_NAME_ALREADY_IN_USE - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): assert isinstance(existing_integration.protocol, str) flask.request.form = ImmutableMultiDict( [ @@ -153,7 +168,7 @@ def test_discovery_services_post_errors( response = controller.process_discovery_services() assert response == INTEGRATION_URL_ALREADY_IN_USE - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("id", str(existing_integration.id)), @@ -164,8 +179,7 @@ def test_discovery_services_post_errors( assert isinstance(response, ProblemDetail) assert response.uri == INCOMPLETE_CONFIGURATION.uri - settings_ctrl_fixture.admin.remove_role(AdminRole.SYSTEM_ADMIN) - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("protocol", self.protocol), @@ -175,9 +189,12 @@ def test_discovery_services_post_errors( pytest.raises(AdminNotAuthorized, controller.process_discovery_services) def test_discovery_services_post_create( - self, settings_ctrl_fixture: SettingsControllerFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: DiscoveryServicesController, + db: DatabaseTransactionFixture, ): - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Name"), @@ -185,13 +202,11 @@ def test_discovery_services_post_create( (ExternalIntegration.URL, "http://registry.url"), ] ) - response = ( - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_discovery_services() - ) + response = controller.process_discovery_services() assert response.status_code == 201 service = get_one( - settings_ctrl_fixture.ctrl.db.session, + db.session, IntegrationConfiguration, goal=Goals.DISCOVERY_GOAL, ) @@ -205,14 +220,15 @@ def test_discovery_services_post_create( def test_discovery_services_post_edit( self, - settings_ctrl_fixture: SettingsControllerFixture, + flask_app_fixture: FlaskAppFixture, + controller: DiscoveryServicesController, create_integration_configuration: IntegrationConfigurationFixture, ): discovery_service = create_integration_configuration.discovery_service( url="registry url" ) - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Name"), @@ -221,9 +237,7 @@ def test_discovery_services_post_edit( (ExternalIntegration.URL, "http://new_registry_url.com"), ] ) - response = ( - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_discovery_services() - ) + response = controller.process_discovery_services() assert response.status_code == 200 assert isinstance(response, Response) @@ -236,7 +250,8 @@ def test_discovery_services_post_edit( def test_check_name_unique( self, - settings_ctrl_fixture: SettingsControllerFixture, + flask_app_fixture: FlaskAppFixture, + controller: DiscoveryServicesController, create_integration_configuration: IntegrationConfigurationFixture, ): existing_service = create_integration_configuration.discovery_service() @@ -244,7 +259,7 @@ def test_check_name_unique( # Try to change new service so that it has the same name as existing service # -- this is not allowed. - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", str(existing_service.name)), @@ -253,13 +268,11 @@ def test_check_name_unique( ("url", "http://test.com"), ] ) - response = ( - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_discovery_services() - ) + response = controller.process_discovery_services() assert response == INTEGRATION_NAME_ALREADY_IN_USE # Try to edit existing service without changing its name -- this is fine. - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", str(existing_service.name)), @@ -268,14 +281,12 @@ def test_check_name_unique( ("url", "http://test.com"), ] ) - response = ( - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_discovery_services() - ) + response = controller.process_discovery_services() assert isinstance(response, Response) assert response.status_code == 200 # Changing the existing service's name is also fine. - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "New name"), @@ -284,38 +295,37 @@ def test_check_name_unique( ("url", "http://test.com"), ] ) - response = ( - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_discovery_services() - ) + response = controller.process_discovery_services() assert isinstance(response, Response) assert response.status_code == 200 def test_discovery_service_delete( self, - settings_ctrl_fixture: SettingsControllerFixture, + flask_app_fixture: FlaskAppFixture, + controller: DiscoveryServicesController, + db: DatabaseTransactionFixture, create_integration_configuration: IntegrationConfigurationFixture, ): discovery_service = create_integration_configuration.discovery_service( url="registry url" ) - with settings_ctrl_fixture.request_context_with_admin("/", method="DELETE"): - settings_ctrl_fixture.admin.remove_role(AdminRole.SYSTEM_ADMIN) + with flask_app_fixture.test_request_context("/", method="DELETE"): pytest.raises( AdminNotAuthorized, - settings_ctrl_fixture.manager.admin_discovery_services_controller.process_delete, + controller.process_delete, discovery_service.id, ) - settings_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) - response = settings_ctrl_fixture.manager.admin_discovery_services_controller.process_delete( + with flask_app_fixture.test_request_context_system_admin("/", method="DELETE"): + response = controller.process_delete( discovery_service.id # type: ignore[arg-type] ) assert response.status_code == 200 service = get_one( - settings_ctrl_fixture.ctrl.db.session, + db.session, IntegrationConfiguration, id=discovery_service.id, ) - assert None == service + assert service is None diff --git a/tests/api/admin/controller/test_library.py b/tests/api/admin/controller/test_library.py index 6cc8785777..0cf36be507 100644 --- a/tests/api/admin/controller/test_library.py +++ b/tests/api/admin/controller/test_library.py @@ -4,7 +4,8 @@ import datetime import json from io import BytesIO -from unittest.mock import MagicMock +from typing import Any +from unittest.mock import MagicMock, create_autospec import flask import pytest @@ -30,13 +31,21 @@ from core.model.library import LibraryLogo from core.util.problem_detail import ProblemDetail, ProblemError from tests.fixtures.announcements import AnnouncementFixture -from tests.fixtures.api_controller import ControllerFixture +from tests.fixtures.database import DatabaseTransactionFixture +from tests.fixtures.flask import FlaskAppFixture from tests.fixtures.library import LibraryFixture +@pytest.fixture +def controller(db: DatabaseTransactionFixture) -> LibrarySettingsController: + mock_manager = MagicMock() + mock_manager._db = db.session + return LibrarySettingsController(mock_manager) + + class TestLibrarySettings: @pytest.fixture() - def logo_properties(self): + def logo_properties(self) -> dict[str, Any]: image_data_raw = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x01\x03\x00\x00\x00%\xdbV\xca\x00\x00\x00\x06PLTE\xffM\x00\x01\x01\x01\x8e\x1e\xe5\x1b\x00\x00\x00\x01tRNS\xcc\xd24V\xfd\x00\x00\x00\nIDATx\x9cc`\x00\x00\x00\x02\x00\x01H\xaf\xa4q\x00\x00\x00\x00IEND\xaeB`\x82" image_data_b64_bytes = base64.b64encode(image_data_raw) image_data_b64_unicode = image_data_b64_bytes.decode("utf-8") @@ -52,7 +61,7 @@ def logo_properties(self): def library_form( self, library: Library, fields: dict[str, str | list[str]] | None = None - ): + ) -> ImmutableMultiDict[str, str]: fields = fields or {} defaults: dict[str, str | list[str]] = { "uuid": str(library.uuid), @@ -75,39 +84,45 @@ def library_form( form = ImmutableMultiDict(form_data) return form - def test_libraries_get_with_no_libraries(self, settings_ctrl_fixture): + def test_libraries_get_with_no_libraries( + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + ): # Delete any existing library created by the controller test setup. - library = get_one(settings_ctrl_fixture.ctrl.db.session, Library) + library = get_one(db.session, Library) if library: - settings_ctrl_fixture.ctrl.db.session.delete(library) + db.session.delete(library) - with settings_ctrl_fixture.ctrl.app.test_request_context("/"): - response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_get() - ) + with flask_app_fixture.test_request_context_system_admin("/"): + response = controller.process_get() + assert isinstance(response.json, dict) assert response.json.get("libraries") == [] def test_libraries_get_with_announcements( - self, settings_ctrl_fixture, announcement_fixture: AnnouncementFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + announcement_fixture: AnnouncementFixture, ): - db = settings_ctrl_fixture.ctrl.db # Delete any existing library created by the controller test setup. library = get_one(db.session, Library) if library: db.session.delete(library) # Set some announcements for this library. - test_library = settings_ctrl_fixture.ctrl.db.library("Library 1", "L1") + test_library = db.library("Library 1", "L1") a1 = announcement_fixture.active_announcement(db.session, test_library) a2 = announcement_fixture.expired_announcement(db.session, test_library) a3 = announcement_fixture.forthcoming_announcement(db.session, test_library) # When we request information about this library... - with settings_ctrl_fixture.request_context_with_admin("/"): - response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_get() - ) - library_settings = response.json.get("libraries")[0].get("settings") + with flask_app_fixture.test_request_context_system_admin("/"): + response = controller.process_get() + assert isinstance(response.json, dict) + library_settings = response.json.get("libraries", [])[0].get("settings") # We find out about the library's announcements. announcements = library_settings.get(ANNOUNCEMENTS_SETTING_NAME) @@ -130,21 +145,23 @@ def test_libraries_get_with_announcements( datetime.date, ) - def test_libraries_get_with_logo(self, settings_ctrl_fixture, logo_properties): - db = settings_ctrl_fixture.ctrl.db - + def test_libraries_get_with_logo( + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + logo_properties: dict[str, Any], + ): library = db.default_library() # Give the library a logo library.logo = LibraryLogo(content=logo_properties["base64_bytes"]) # When we request information about this library... - with settings_ctrl_fixture.request_context_with_admin("/"): - response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_get() - ) - - libraries = response.json.get("libraries") + with flask_app_fixture.test_request_context_system_admin("/"): + response = controller.process_get() + assert isinstance(response.json, dict) + libraries = response.json.get("libraries", []) assert len(libraries) == 1 library_settings = libraries[0].get("settings") @@ -152,12 +169,16 @@ def test_libraries_get_with_logo(self, settings_ctrl_fixture, logo_properties): assert library_settings["logo"] == logo_properties["data_url"] def test_libraries_get_with_multiple_libraries( - self, settings_ctrl_fixture, library_fixture: LibraryFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + library_fixture: LibraryFixture, ): # Delete any existing library created by the controller test setup. - library = get_one(settings_ctrl_fixture.ctrl.db.session, Library) + library = get_one(db.session, Library) if library: - settings_ctrl_fixture.ctrl.db.session.delete(library) + db.session.delete(library) l1 = library_fixture.library("Library 1", "L1") l2 = library_fixture.library("Library 2", "L2") @@ -175,15 +196,15 @@ def test_libraries_get_with_multiple_libraries( l2.update_settings(settings) # The admin only has access to L1 and L2. - settings_ctrl_fixture.admin.remove_role(AdminRole.SYSTEM_ADMIN) - settings_ctrl_fixture.admin.add_role(AdminRole.LIBRARIAN, l1) - settings_ctrl_fixture.admin.add_role(AdminRole.LIBRARY_MANAGER, l2) - - with settings_ctrl_fixture.request_context_with_admin("/"): - response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_get() - ) - libraries = response.json.get("libraries") + admin = flask_app_fixture.admin_user() + admin.remove_role(AdminRole.SYSTEM_ADMIN) + admin.add_role(AdminRole.LIBRARIAN, l1) + admin.add_role(AdminRole.LIBRARY_MANAGER, l2) + + with flask_app_fixture.test_request_context("/", admin=admin): + response = controller.process_get() + assert isinstance(response.json, dict) + libraries = response.json.get("libraries", []) assert 2 == len(libraries) assert l1.uuid == libraries[0].get("uuid") @@ -211,77 +232,82 @@ def test_libraries_get_with_multiple_libraries( ] == settings_dict.get("facets_enabled_order") assert ["fre"] == settings_dict.get("large_collection_languages") - def test_libraries_post_errors(self, settings_ctrl_fixture): - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + def test_libraries_post_errors( + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + ): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict([]) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INCOMPLETE_CONFIGURATION.uri assert ( "Required field 'Name' is missing." == excinfo.value.problem_detail.detail ) - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Brooklyn Public Library"), ] ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INCOMPLETE_CONFIGURATION.uri assert ( "Required field 'Short name' is missing." == excinfo.value.problem_detail.detail ) - library = settings_ctrl_fixture.ctrl.db.library() - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + library = db.library() + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = self.library_form(library, {"uuid": "1234"}) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == LIBRARY_NOT_FOUND.uri - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Brooklyn Public Library"), - ("short_name", library.short_name), + ("short_name", str(library.short_name)), ] ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail == LIBRARY_SHORT_NAME_ALREADY_IN_USE - bpl = settings_ctrl_fixture.ctrl.db.library(short_name="bpl") - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + bpl = db.library(short_name="bpl") + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ - ("uuid", bpl.uuid), + ("uuid", str(bpl.uuid)), ("name", "Brooklyn Public Library"), - ("short_name", library.short_name), + ("short_name", str(library.short_name)), ] ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail == LIBRARY_SHORT_NAME_ALREADY_IN_USE - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ - ("uuid", library.uuid), + ("uuid", str(library.uuid)), ("name", "The New York Public Library"), - ("short_name", library.short_name), + ("short_name", str(library.short_name)), ] ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INCOMPLETE_CONFIGURATION.uri # Either patron support email or website MUST be present - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "Email or Website Library"), @@ -291,8 +317,9 @@ def test_libraries_post_errors(self, settings_ctrl_fixture): ] ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INCOMPLETE_CONFIGURATION.uri + assert excinfo.value.problem_detail.detail is not None assert ( "'Patron support email address' or 'Patron support website'" in excinfo.value.problem_detail.detail @@ -300,7 +327,7 @@ def test_libraries_post_errors(self, settings_ctrl_fixture): # Test a web primary and secondary color that doesn't contrast # well on white. Here primary will, secondary should not. - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = self.library_form( library, { @@ -309,8 +336,9 @@ def test_libraries_post_errors(self, settings_ctrl_fixture): }, ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INVALID_CONFIGURATION_OPTION.uri + assert excinfo.value.problem_detail.detail is not None assert ( "contrast-ratio.com/#%23e0e0e0-on-%23ffffff" in excinfo.value.problem_detail.detail @@ -322,7 +350,7 @@ def test_libraries_post_errors(self, settings_ctrl_fixture): # Test a list of web header links and a list of labels that # aren't the same length. - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = self.library_form( library, { @@ -334,24 +362,25 @@ def test_libraries_post_errors(self, settings_ctrl_fixture): }, ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INVALID_CONFIGURATION_OPTION.uri # Test bad language code - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = self.library_form( library, {"tiny_collection_languages": "zzz"} ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == UNKNOWN_LANGUAGE.uri + assert excinfo.value.problem_detail.detail is not None assert ( '"zzz" is not a valid language code' in excinfo.value.problem_detail.detail ) # Test uploading a logo that is in the wrong format. - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = self.library_form(library) flask.request.files = ImmutableMultiDict( { @@ -363,15 +392,16 @@ def test_libraries_post_errors(self, settings_ctrl_fixture): } ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INVALID_CONFIGURATION_OPTION.uri + assert excinfo.value.problem_detail.detail is not None assert ( "Image upload must be in GIF, PNG, or JPG format." in excinfo.value.problem_detail.detail ) # Test uploading a logo that we can't open to resize. - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = self.library_form(library) flask.request.files = ImmutableMultiDict( { @@ -383,13 +413,14 @@ def test_libraries_post_errors(self, settings_ctrl_fixture): } ) with pytest.raises(ProblemError) as excinfo: - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() + controller.process_post() assert excinfo.value.problem_detail.uri == INVALID_CONFIGURATION_OPTION.uri + assert excinfo.value.problem_detail.detail is not None assert ( "Unable to open uploaded image" in excinfo.value.problem_detail.detail ) - def test__process_image(self, logo_properties, settings_ctrl_fixture): + def test__process_image(self, logo_properties: dict[str, Any]): image, expected_encoded_image = ( logo_properties[key] for key in ("image", "base64_bytes") ) @@ -408,12 +439,12 @@ def test__process_image(self, logo_properties, settings_ctrl_fixture): def test_libraries_post_create( self, - logo_properties, - settings_ctrl_fixture, + logo_properties: dict[str, Any], + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, announcement_fixture: AnnouncementFixture, ): - db = settings_ctrl_fixture.ctrl.db - # Pull needed properties from logo fixture image_data, expected_logo_data_url, image = ( logo_properties[key] for key in ("raw_bytes", "data_url", "image") @@ -423,7 +454,7 @@ def test_libraries_post_create( # a mismatch between the expected data URL and the one configured. assert max(*image.size) <= Configuration.LOGO_MAX_DIMENSION - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "The New York Public Library"), @@ -477,9 +508,7 @@ def test_libraries_post_create( ) } ) - response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() - ) + response = controller.process_post() assert response.status_code == 201 library = get_one(db.session, Library, short_name="nypl") @@ -532,7 +561,11 @@ def test_libraries_post_create( assert ["ger"] == german.languages def test_libraries_post_edit( - self, settings_ctrl_fixture, library_fixture: LibraryFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + library_fixture: LibraryFixture, ): # A library already exists. settings = library_fixture.mock_settings() @@ -548,7 +581,7 @@ def test_libraries_post_edit( library_to_edit.logo = LibraryLogo(content=b"A tiny image") library_fixture.reset_settings_cache(library_to_edit) - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("uuid", str(library_to_edit.uuid)), @@ -576,15 +609,10 @@ def test_libraries_post_edit( ), ] ) - flask.request.files = ImmutableMultiDict([]) - response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() - ) + response = controller.process_post() assert response.status_code == 200 - library = get_one( - settings_ctrl_fixture.ctrl.db.session, Library, uuid=library_to_edit.uuid - ) + library = get_one(db.session, Library, uuid=library_to_edit.uuid) assert library is not None assert library.uuid == response.get_data(as_text=True) @@ -609,7 +637,11 @@ def test_libraries_post_edit( assert library.logo.content == b"A tiny image" def test_library_post_empty_values_edit( - self, settings_ctrl_fixture, library_fixture: LibraryFixture + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + library_fixture: LibraryFixture, ): settings = library_fixture.mock_settings() settings.library_description = "description" @@ -618,7 +650,7 @@ def test_library_post_empty_values_edit( ) library_fixture.reset_settings_cache(library_to_edit) - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("uuid", str(library_to_edit.uuid)), @@ -629,19 +661,20 @@ def test_library_post_empty_values_edit( ("help_email", "help@example.com"), ] ) - response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() - ) + response = controller.process_post() assert response.status_code == 200 - library = get_one( - settings_ctrl_fixture.ctrl.db.session, Library, uuid=library_to_edit.uuid - ) + library = get_one(db.session, Library, uuid=library_to_edit.uuid) assert library is not None assert library.settings.library_description is None - def test_library_post_empty_values_create(self, settings_ctrl_fixture): - with settings_ctrl_fixture.request_context_with_admin("/", method="POST"): + def test_library_post_empty_values_create( + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + ): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("name", "The New York Public Library"), @@ -651,75 +684,76 @@ def test_library_post_empty_values_create(self, settings_ctrl_fixture): ("help_email", "help@example.com"), ] ) - response: Response = ( - settings_ctrl_fixture.manager.admin_library_settings_controller.process_post() - ) + response: Response = controller.process_post() assert response.status_code == 201 uuid = response.get_data(as_text=True) - library = get_one(settings_ctrl_fixture.ctrl.db.session, Library, uuid=uuid) + library = get_one(db.session, Library, uuid=uuid) + assert library is not None assert library.settings.library_description is None - def test_library_delete(self, settings_ctrl_fixture): - library = settings_ctrl_fixture.ctrl.db.library() + def test_library_delete( + self, + flask_app_fixture: FlaskAppFixture, + controller: LibrarySettingsController, + db: DatabaseTransactionFixture, + ): + library = db.library() - with settings_ctrl_fixture.request_context_with_admin("/", method="DELETE"): - settings_ctrl_fixture.admin.remove_role(AdminRole.SYSTEM_ADMIN) + with flask_app_fixture.test_request_context("/", method="DELETE"): pytest.raises( AdminNotAuthorized, - settings_ctrl_fixture.manager.admin_library_settings_controller.process_delete, + controller.process_delete, library.uuid, ) - settings_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) - response = settings_ctrl_fixture.manager.admin_library_settings_controller.process_delete( - library.uuid - ) + with flask_app_fixture.test_request_context_system_admin("/", method="DELETE"): + response = controller.process_delete(str(library.uuid)) assert response.status_code == 200 - library = get_one( - settings_ctrl_fixture.ctrl.db.session, Library, uuid=library.uuid - ) - assert None == library + queried_library = get_one(db.session, Library, uuid=library.uuid) + assert queried_library is None - def test_process_libraries(self, controller_fixture: ControllerFixture): - manager = MagicMock() - controller = LibrarySettingsController(manager) - controller.process_get = MagicMock() - controller.process_post = MagicMock() + def test_process_libraries( + self, flask_app_fixture: FlaskAppFixture, controller: LibrarySettingsController + ): + mock_process_get = create_autospec(controller.process_get) + controller.process_get = mock_process_get + mock_process_post = create_autospec(controller.process_post) + controller.process_post = mock_process_post # Make sure we call process_get for a get request - with controller_fixture.request_context_with_library("/", method="GET"): + with flask_app_fixture.test_request_context("/", method="GET"): controller.process_libraries() - controller.process_get.assert_called_once() - controller.process_post.assert_not_called() - controller.process_get.reset_mock() - controller.process_post.reset_mock() + mock_process_get.assert_called_once() + mock_process_post.assert_not_called() + mock_process_get.reset_mock() + mock_process_post.reset_mock() # Make sure we call process_post for a post request - with controller_fixture.request_context_with_library("/", method="POST"): + with flask_app_fixture.test_request_context("/", method="POST"): controller.process_libraries() - controller.process_get.assert_not_called() - controller.process_post.assert_called_once() - controller.process_get.reset_mock() - controller.process_post.reset_mock() + mock_process_get.assert_not_called() + mock_process_post.assert_called_once() + mock_process_get.reset_mock() + mock_process_post.reset_mock() # For any other request, make sure we return a ProblemDetail - with controller_fixture.request_context_with_library("/", method="PUT"): + with flask_app_fixture.test_request_context("/", method="PUT"): resp = controller.process_libraries() - controller.process_get.assert_not_called() - controller.process_post.assert_not_called() + mock_process_get.assert_not_called() + mock_process_post.assert_not_called() assert isinstance(resp, ProblemDetail) # Make sure that if process_get or process_post raises a ProblemError, # we return the problem detail. - controller.process_get.side_effect = ProblemError( + mock_process_get.side_effect = ProblemError( problem_detail=INCOMPLETE_CONFIGURATION.detailed("test") ) - with controller_fixture.request_context_with_library("/", method="GET"): + with flask_app_fixture.test_request_context("/", method="GET"): resp = controller.process_libraries() assert isinstance(resp, ProblemDetail) assert resp.detail == "test" diff --git a/tests/api/admin/controller/test_library_registrations.py b/tests/api/admin/controller/test_library_registrations.py index 889b566c92..f5d54bb406 100644 --- a/tests/api/admin/controller/test_library_registrations.py +++ b/tests/api/admin/controller/test_library_registrations.py @@ -6,11 +6,14 @@ from requests_mock import Mocker from werkzeug.datastructures import ImmutableMultiDict +from api.admin.controller.discovery_service_library_registrations import ( + DiscoveryServiceLibraryRegistrationsController, +) from api.admin.exceptions import AdminNotAuthorized from api.admin.problem_details import MISSING_SERVICE, NO_SUCH_LIBRARY from api.discovery.opds_registration import OpdsRegistrationService from api.problem_details import REMOTE_INTEGRATION_FAILED -from core.model import AdminRole, create +from core.model import create from core.model.discovery_service_registration import ( DiscoveryServiceRegistration, RegistrationStage, @@ -18,23 +21,35 @@ ) from core.problem_details import INVALID_INPUT from core.util.problem_detail import ProblemDetail, ProblemError -from tests.fixtures.api_admin import AdminControllerFixture -from tests.fixtures.database import IntegrationConfigurationFixture +from tests.fixtures.database import ( + DatabaseTransactionFixture, + IntegrationConfigurationFixture, +) +from tests.fixtures.flask import FlaskAppFixture from tests.fixtures.library import LibraryFixture +@pytest.fixture +def controller( + db: DatabaseTransactionFixture, +) -> DiscoveryServiceLibraryRegistrationsController: + mock_manager = MagicMock() + mock_manager._db = db.session + return DiscoveryServiceLibraryRegistrationsController(mock_manager) + + class TestLibraryRegistration: """Test the process of registering a library with a OpdsRegistrationService.""" def test_discovery_service_library_registrations_get( self, - admin_ctrl_fixture: AdminControllerFixture, + controller: DiscoveryServiceLibraryRegistrationsController, + flask_app_fixture: FlaskAppFixture, + db: DatabaseTransactionFixture, create_integration_configuration: IntegrationConfigurationFixture, library_fixture: LibraryFixture, requests_mock: Mocker, ) -> None: - db = admin_ctrl_fixture.ctrl.db - # Here's a discovery service. discovery_service = create_integration_configuration.discovery_service( url="http://service-url.com/" @@ -112,20 +127,18 @@ def test_discovery_service_library_registrations_get( headers={"Content-Type": OpdsRegistrationService.OPDS_2_TYPE}, ) - controller = ( - admin_ctrl_fixture.ctrl.manager.admin_discovery_service_library_registrations_controller - ) - m = controller.process_discovery_service_library_registrations - with admin_ctrl_fixture.request_context_with_admin("/", method="GET"): + with flask_app_fixture.test_request_context("/", method="GET"): # When the user lacks the SYSTEM_ADMIN role, the # controller won't even start processing their GET # request. - pytest.raises(AdminNotAuthorized, m) - - # Add the admin role and try again. - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) + pytest.raises( + AdminNotAuthorized, + controller.process_discovery_service_library_registrations, + ) - response = m() + # Request again with system admin role + with flask_app_fixture.test_request_context_system_admin("/", method="GET"): + response = controller.process_discovery_service_library_registrations() # The document we get back from the controller is a # dictionary with useful information on all known # discovery integrations -- just one, in this case. @@ -179,7 +192,7 @@ def test_discovery_service_library_registrations_get( status_code=502, ) - response = m() + response = controller.process_discovery_service_library_registrations() # Everything looks good, except that there's no TOS data # available. @@ -198,7 +211,8 @@ def test_discovery_service_library_registrations_get( def test_discovery_service_library_registrations_post( self, - admin_ctrl_fixture: AdminControllerFixture, + controller: DiscoveryServiceLibraryRegistrationsController, + flask_app_fixture: FlaskAppFixture, create_integration_configuration: IntegrationConfigurationFixture, library_fixture: LibraryFixture, ) -> None: @@ -206,34 +220,30 @@ def test_discovery_service_library_registrations_post( discovery_service_library_registrations. """ - controller = ( - admin_ctrl_fixture.manager.admin_discovery_service_library_registrations_controller - ) - m = controller.process_discovery_service_library_registrations - # Here, the user doesn't have permission to start the # registration process. - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): - pytest.raises(AdminNotAuthorized, m) - - admin_ctrl_fixture.admin.add_role(AdminRole.SYSTEM_ADMIN) + with flask_app_fixture.test_request_context("/", method="POST"): + pytest.raises( + AdminNotAuthorized, + controller.process_discovery_service_library_registrations, + ) # We might not get an integration ID parameter. - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict() - response = m() + response = controller.process_discovery_service_library_registrations() assert isinstance(response, ProblemDetail) assert INVALID_INPUT.uri == response.uri # The integration ID might not correspond to a valid # ExternalIntegration. - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("integration_id", "1234"), ] ) - response = m() + response = controller.process_discovery_service_library_registrations() assert isinstance(response, ProblemDetail) assert MISSING_SERVICE == response @@ -243,44 +253,44 @@ def test_discovery_service_library_registrations_post( ) # We might not get a library short name. - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("integration_id", str(discovery_service.id)), ] ) - response = m() + response = controller.process_discovery_service_library_registrations() assert isinstance(response, ProblemDetail) assert INVALID_INPUT.uri == response.uri # The library name might not correspond to a real library. - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("integration_id", str(discovery_service.id)), ("library_short_name", "not-a-library"), ] ) - response = m() + response = controller.process_discovery_service_library_registrations() assert NO_SUCH_LIBRARY == response # Take care of that problem. library = library_fixture.library() # We might not get a registration stage. - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("integration_id", str(discovery_service.id)), ("library_short_name", str(library.short_name)), ] ) - response = m() + response = controller.process_discovery_service_library_registrations() assert isinstance(response, ProblemDetail) assert INVALID_INPUT.uri == response.uri # The registration stage might not be valid. - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = ImmutableMultiDict( [ ("integration_id", str(discovery_service.id)), @@ -288,7 +298,7 @@ def test_discovery_service_library_registrations_post( ("registration_stage", "not-a-stage"), ] ) - response = m() + response = controller.process_discovery_service_library_registrations() assert isinstance(response, ProblemDetail) assert INVALID_INPUT.uri == response.uri @@ -307,9 +317,9 @@ def test_discovery_service_library_registrations_post( ) controller.look_up_registry = MagicMock(return_value=mock_registry) - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = form - response = m() + response = controller.process_discovery_service_library_registrations() assert REMOTE_INTEGRATION_FAILED == response # But if that doesn't happen, success! @@ -317,7 +327,7 @@ def test_discovery_service_library_registrations_post( mock_registry.register_library.return_value = True controller.look_up_registry = MagicMock(return_value=mock_registry) - with admin_ctrl_fixture.request_context_with_admin("/", method="POST"): + with flask_app_fixture.test_request_context_system_admin("/", method="POST"): flask.request.form = form response = controller.process_discovery_service_library_registrations() assert isinstance(response, Response) diff --git a/tests/api/admin/controller/test_settings.py b/tests/api/admin/controller/test_settings.py index 12f380ab6e..479d721878 100644 --- a/tests/api/admin/controller/test_settings.py +++ b/tests/api/admin/controller/test_settings.py @@ -71,9 +71,7 @@ def test_get_integration_info( self, settings_ctrl_fixture: SettingsControllerFixture ): """Test the _get_integration_info helper method.""" - m = ( - settings_ctrl_fixture.manager.admin_settings_controller._get_integration_info - ) + m = settings_ctrl_fixture.controller._get_integration_info # Test the case where there are integrations in the database # with the given goal, but none of them match the @@ -87,7 +85,7 @@ def test_get_integration_info( def test_create_integration(self, settings_ctrl_fixture: SettingsControllerFixture): """Test the _create_integration helper method.""" - m = settings_ctrl_fixture.manager.admin_settings_controller._create_integration + m = settings_ctrl_fixture.controller._create_integration protocol_definitions = [ dict(name="allow many"), @@ -131,7 +129,7 @@ def test_create_integration(self, settings_ctrl_fixture: SettingsControllerFixtu def test_check_url_unique(self, settings_ctrl_fixture: SettingsControllerFixture): # Verify our ability to catch duplicate integrations for a # given URL. - m = settings_ctrl_fixture.manager.admin_settings_controller.check_url_unique + m = settings_ctrl_fixture.controller.check_url_unique # Here's an ExternalIntegration. original = settings_ctrl_fixture.ctrl.db.external_integration( @@ -215,9 +213,7 @@ def m(url): def test__get_protocol_class( self, settings_ctrl_fixture: SettingsControllerFixture ): - _get_protocol_class = ( - settings_ctrl_fixture.manager.admin_settings_controller._get_settings_class - ) + _get_protocol_class = settings_ctrl_fixture.controller._get_settings_class registry = IntegrationRegistry[Any](Goals.LICENSE_GOAL) class P1Settings(BaseSettings): @@ -261,7 +257,7 @@ def test__set_configuration_library( db = settings_ctrl_fixture.ctrl.db config = db.default_collection().integration_configuration _set_configuration_library = ( - settings_ctrl_fixture.manager.admin_settings_controller._set_configuration_library + settings_ctrl_fixture.controller._set_configuration_library ) library = db.library(short_name="short-name") diff --git a/tests/api/test_device_tokens.py b/tests/api/test_device_tokens.py index f807a1f8ad..24a5c2915e 100644 --- a/tests/api/test_device_tokens.py +++ b/tests/api/test_device_tokens.py @@ -1,25 +1,37 @@ from unittest.mock import MagicMock, patch +import pytest + +from api.controller.device_tokens import DeviceTokensController from api.problem_details import DEVICE_TOKEN_NOT_FOUND, DEVICE_TOKEN_TYPE_INVALID from core.model.devicetokens import DeviceToken, DeviceTokenTypes -from tests.fixtures.api_controller import ControllerFixture +from tests.fixtures.database import DatabaseTransactionFixture + + +@pytest.fixture +def controller(db: DatabaseTransactionFixture) -> DeviceTokensController: + mock_manager = MagicMock() + mock_manager._db = db.session + return DeviceTokensController(mock_manager) @patch("api.controller.device_tokens.flask") class TestDeviceTokens: - def test_create_invalid_type(self, flask, controller_fixture: ControllerFixture): - db = controller_fixture.db + def test_create_invalid_type( + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture + ): request = MagicMock() request.patron = db.patron() request.json = {"device_token": "xx", "token_type": "aninvalidtoken"} flask.request = request - detail = controller_fixture.app.manager.patron_devices.create_patron_device() + detail = controller.create_patron_device() assert detail is DEVICE_TOKEN_TYPE_INVALID assert detail.status_code == 400 - def test_create_token(self, flask, controller_fixture: ControllerFixture): - db = controller_fixture.db + def test_create_token( + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture + ): request = MagicMock() request.patron = db.patron() request.json = { @@ -27,7 +39,7 @@ def test_create_token(self, flask, controller_fixture: ControllerFixture): "token_type": DeviceTokenTypes.FCM_ANDROID, } flask.request = request - response = controller_fixture.app.manager.patron_devices.create_patron_device() + response = controller.create_patron_device() assert response[1] == 201 @@ -42,8 +54,9 @@ def test_create_token(self, flask, controller_fixture: ControllerFixture): assert device.device_token == "xxx" assert device.token_type == DeviceTokenTypes.FCM_ANDROID - def test_get_token(self, flask, controller_fixture: ControllerFixture): - db = controller_fixture.db + def test_get_token( + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture + ): patron = db.patron() device = DeviceToken.create( db.session, DeviceTokenTypes.FCM_ANDROID, "xx", patron @@ -53,14 +66,15 @@ def test_get_token(self, flask, controller_fixture: ControllerFixture): request.patron = patron request.args = {"device_token": "xx"} flask.request = request - response = controller_fixture.app.manager.patron_devices.get_patron_device() + response = controller.get_patron_device() assert response[1] == 200 assert response[0]["token_type"] == DeviceTokenTypes.FCM_ANDROID assert response[0]["device_token"] == "xx" - def test_get_token_not_found(self, flask, controller_fixture: ControllerFixture): - db = controller_fixture.db + def test_get_token_not_found( + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture + ): patron = db.patron() device = DeviceToken.create( db.session, DeviceTokenTypes.FCM_ANDROID, "xx", patron @@ -70,14 +84,13 @@ def test_get_token_not_found(self, flask, controller_fixture: ControllerFixture) request.patron = patron request.args = {"device_token": "xxs"} flask.request = request - detail = controller_fixture.app.manager.patron_devices.get_patron_device() + detail = controller.get_patron_device() assert detail == DEVICE_TOKEN_NOT_FOUND def test_get_token_different_patron( - self, flask, controller_fixture: ControllerFixture + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture ): - db = controller_fixture.db patron = db.patron() device = DeviceToken.create( db.session, DeviceTokenTypes.FCM_ANDROID, "xx", patron @@ -87,12 +100,13 @@ def test_get_token_different_patron( request.patron = db.patron() request.args = {"device_token": "xx"} flask.request = request - detail = controller_fixture.app.manager.patron_devices.get_patron_device() + detail = controller.get_patron_device() assert detail == DEVICE_TOKEN_NOT_FOUND - def test_create_duplicate_token(self, flask, controller_fixture: ControllerFixture): - db = controller_fixture.db + def test_create_duplicate_token( + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture + ): patron = db.patron() device = DeviceToken.create(db.session, DeviceTokenTypes.FCM_IOS, "xxx", patron) @@ -105,7 +119,7 @@ def test_create_duplicate_token(self, flask, controller_fixture: ControllerFixtu } flask.request = request nested = db.session.begin_nested() # rollback only affects device create - response = controller_fixture.app.manager.patron_devices.create_patron_device() + response = controller.create_patron_device() assert response == (dict(exists=True), 200) # different patron same token @@ -117,12 +131,13 @@ def test_create_duplicate_token(self, flask, controller_fixture: ControllerFixtu "token_type": DeviceTokenTypes.FCM_ANDROID, } flask.request = request - response = controller_fixture.app.manager.patron_devices.create_patron_device() + response = controller.create_patron_device() assert response[1] == 201 - def test_delete_token(self, flask, controller_fixture: ControllerFixture): - db = controller_fixture.db + def test_delete_token( + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture + ): patron = db.patron() device = DeviceToken.create(db.session, DeviceTokenTypes.FCM_IOS, "xxx", patron) @@ -134,14 +149,15 @@ def test_delete_token(self, flask, controller_fixture: ControllerFixture): } flask.request = request - response = controller_fixture.app.manager.patron_devices.delete_patron_device() + response = controller.delete_patron_device() db.session.commit() assert response.status_code == 204 assert db.session.query(DeviceToken).get(device.id) == None - def test_delete_no_token(self, flask, controller_fixture: ControllerFixture): - db = controller_fixture.db + def test_delete_no_token( + self, flask, controller: DeviceTokensController, db: DatabaseTransactionFixture + ): patron = db.patron() device = DeviceToken.create(db.session, DeviceTokenTypes.FCM_IOS, "xxx", patron) @@ -153,5 +169,5 @@ def test_delete_no_token(self, flask, controller_fixture: ControllerFixture): } flask.request = request - response = controller_fixture.app.manager.patron_devices.delete_patron_device() + response = controller.delete_patron_device() assert response == DEVICE_TOKEN_NOT_FOUND diff --git a/tests/fixtures/api_admin.py b/tests/fixtures/api_admin.py index 081841cc95..b536219df3 100644 --- a/tests/fixtures/api_admin.py +++ b/tests/fixtures/api_admin.py @@ -1,9 +1,11 @@ from contextlib import contextmanager +from unittest.mock import MagicMock import flask import pytest from api.admin.controller import setup_admin_controllers +from api.admin.controller.settings import SettingsController from api.app import initialize_admin from api.circulation_manager import CirculationManager from api.config import Configuration @@ -100,6 +102,10 @@ def __init__(self, controller_fixture: ControllerFixture): # Make the admin a system admin so they can do everything by default. self.admin.add_role(AdminRole.SYSTEM_ADMIN) + mock_manager = MagicMock() + mock_manager._db = self.ctrl.db.session + self.controller = SettingsController(mock_manager) + def do_request(self, url, *args, **kwargs): """Mock HTTP get/post method to replace HTTP.get_with_timeout or post_with_timeout.""" self.requests.append((url, args, kwargs)) diff --git a/tests/fixtures/flask.py b/tests/fixtures/flask.py index c105472e13..3ef24f8494 100644 --- a/tests/fixtures/flask.py +++ b/tests/fixtures/flask.py @@ -8,6 +8,7 @@ import pytest from flask.ctx import RequestContext from flask_babel import Babel +from werkzeug.datastructures import ImmutableMultiDict from api.util.flask import PalaceFlask from core.model import Admin, AdminRole, Library, get_one_or_create @@ -32,11 +33,18 @@ def admin_user( @contextmanager def test_request_context( - self, *args: Any, admin: Admin | None = None, **kwargs: Any + self, + *args: Any, + admin: Admin | None = None, + library: Library | None = None, + **kwargs: Any, ) -> Generator[RequestContext, None, None]: with self.app.test_request_context(*args, **kwargs) as c: self.db.session.begin_nested() + flask.request.library = library # type: ignore[attr-defined] flask.request.admin = admin # type: ignore[attr-defined] + flask.request.form = ImmutableMultiDict() + flask.request.files = ImmutableMultiDict() yield c # Flush any changes that may have occurred during the request, then