diff --git a/backend/tests/test_graphql_api/test_application_section/test_query.py b/backend/tests/test_graphql_api/test_application_section/test_query.py index 8f15041b1b..4c6f635578 100644 --- a/backend/tests/test_graphql_api/test_application_section/test_query.py +++ b/backend/tests/test_graphql_api/test_application_section/test_query.py @@ -20,13 +20,14 @@ RecurringReservationFactory, ReservationFactory, SuitableTimeRangeFactory, + UserFactory, ) from tests.helpers import patch_method from .helpers import section_query, sections_query if TYPE_CHECKING: - from tilavarauspalvelu.models import ApplicationSection + from tilavarauspalvelu.models import ApplicationSection, ReservationUnit # Applied to all tests pytestmark = [ @@ -180,7 +181,7 @@ def test_application_section__all_statuses(graphql): assert len(response.queries) in {8, 9}, response.query_log -def pindora_response(section: ApplicationSection) -> PindoraSeasonalBookingResponse: +def pindora_response(reservation_unit: ReservationUnit) -> PindoraSeasonalBookingResponse: return PindoraSeasonalBookingResponse( access_code="1234", access_code_generated_at=local_datetime(2022, 1, 1, 12), @@ -191,7 +192,7 @@ def pindora_response(section: ApplicationSection) -> PindoraSeasonalBookingRespo access_code_sms_message="123456789", reservation_unit_code_validity=[ PindoraSeasonalBookingAccessCodeValidity( - reservation_unit_id=section.reservation_unit_options.first().reservation_unit.uuid, + reservation_unit_id=reservation_unit.uuid, access_code_valid_minutes_before=10, access_code_valid_minutes_after=5, begin=local_datetime(2022, 1, 1, 12), @@ -225,7 +226,9 @@ def pindora_query(section: ApplicationSection) -> str: @freeze_time(local_datetime(2022, 1, 1)) def test_application_section__query__pindora_info(graphql): + user = UserFactory.create() section = ApplicationSectionFactory.create_in_status_unallocated( + application__user=user, reservations_begin_date=local_date(2022, 1, 1), reservations_end_date=local_date(2022, 1, 1), ) @@ -233,9 +236,11 @@ def test_application_section__query__pindora_info(graphql): allocated_time_slot__reservation_unit_option__application_section=section, ) reservation = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, begin=local_datetime(2022, 1, 1, 12), end=local_datetime(2022, 1, 1, 13), - recurring_reservation=series, access_type=AccessType.ACCESS_CODE, state=ReservationStateChoice.CONFIRMED, type=ReservationTypeChoice.NORMAL, @@ -245,7 +250,9 @@ def test_application_section__query__pindora_info(graphql): graphql.login_with_superuser() - with patch_method(PindoraClient.get_seasonal_booking, return_value=pindora_response(section)): + data = pindora_response(series.reservation_unit) + + with patch_method(PindoraClient.get_seasonal_booking, return_value=data): response = graphql(query) assert response.has_errors is False, response.errors @@ -272,13 +279,20 @@ def test_application_section__query__pindora_info(graphql): @freeze_time(local_datetime(2022, 1, 1)) @pytest.mark.parametrize("as_reservee", [True, False]) def test_application_section__query__pindora_info__access_code_not_active(graphql, as_reservee): + user = UserFactory.create() section = ApplicationSectionFactory.create_in_status_unallocated( + application__user=user, reservations_begin_date=local_date(2022, 1, 1), reservations_end_date=local_date(2022, 1, 1), ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) ReservationFactory.create( - recurring_reservation__allocated_time_slot__reservation_unit_option__application_section=section, + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, access_type=AccessType.ACCESS_CODE, state=ReservationStateChoice.CONFIRMED, type=ReservationTypeChoice.NORMAL, @@ -291,7 +305,7 @@ def test_application_section__query__pindora_info__access_code_not_active(graphq else: graphql.login_with_superuser() - response = pindora_response(section) + response = pindora_response(series.reservation_unit) response["access_code_is_active"] = False with patch_method(PindoraClient.get_seasonal_booking, return_value=response): @@ -307,13 +321,20 @@ def test_application_section__query__pindora_info__access_code_not_active(graphq @freeze_time(local_datetime(2022, 1, 1)) def test_application_section__query__pindora_info__access_type_not_access_code(graphql): + user = UserFactory.create() section = ApplicationSectionFactory.create_in_status_unallocated( + application__user=user, reservations_begin_date=local_date(2022, 1, 1), reservations_end_date=local_date(2022, 1, 1), ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) ReservationFactory.create( - recurring_reservation__allocated_time_slot__reservation_unit_option__application_section=section, + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, access_type=AccessType.PHYSICAL_KEY, state=ReservationStateChoice.CONFIRMED, type=ReservationTypeChoice.NORMAL, @@ -323,7 +344,9 @@ def test_application_section__query__pindora_info__access_type_not_access_code(g graphql.login_with_superuser() - with patch_method(PindoraClient.get_seasonal_booking, return_value=pindora_response(section)): + data = pindora_response(series.reservation_unit) + + with patch_method(PindoraClient.get_seasonal_booking, return_value=data): response = graphql(query) assert response.has_errors is False, response.errors @@ -333,13 +356,20 @@ def test_application_section__query__pindora_info__access_type_not_access_code(g @freeze_time(local_datetime(2022, 1, 1)) def test_application_section__query__pindora_info__pindora_call_fails(graphql): + user = UserFactory.create() section = ApplicationSectionFactory.create_in_status_unallocated( + application__user=user, reservations_begin_date=local_date(2022, 1, 1), reservations_end_date=local_date(2022, 1, 1), ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) ReservationFactory.create( - recurring_reservation__allocated_time_slot__reservation_unit_option__application_section=section, + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, access_type=AccessType.ACCESS_CODE, state=ReservationStateChoice.CONFIRMED, type=ReservationTypeChoice.NORMAL, @@ -359,13 +389,20 @@ def test_application_section__query__pindora_info__pindora_call_fails(graphql): @freeze_time(local_datetime(2022, 1, 3)) def test_application_section__query__pindora_info__section_past(graphql): + user = UserFactory.create() section = ApplicationSectionFactory.create_in_status_unallocated( + application__user=user, reservations_begin_date=local_date(2022, 1, 1), reservations_end_date=local_date(2022, 1, 1), ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) ReservationFactory.create( - recurring_reservation__allocated_time_slot__reservation_unit_option__application_section=section, + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, access_type=AccessType.ACCESS_CODE, state=ReservationStateChoice.CONFIRMED, type=ReservationTypeChoice.NORMAL, @@ -375,7 +412,9 @@ def test_application_section__query__pindora_info__section_past(graphql): graphql.login_with_superuser() - with patch_method(PindoraClient.get_seasonal_booking, return_value=pindora_response(section)): + data = pindora_response(series.reservation_unit) + + with patch_method(PindoraClient.get_seasonal_booking, return_value=data): response = graphql(query) assert response.has_errors is False, response.errors diff --git a/backend/tests/test_graphql_api/test_reservation/test_adjust_time.py b/backend/tests/test_graphql_api/test_reservation/test_adjust_time.py index 5d4c31723e..09c8e4c644 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_adjust_time.py +++ b/backend/tests/test_graphql_api/test_reservation/test_adjust_time.py @@ -9,7 +9,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStartInterval, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService from tilavarauspalvelu.models import Reservation, ReservationUnitHierarchy from utils.date_utils import DEFAULT_TIMEZONE, local_date, local_datetime @@ -505,8 +505,7 @@ def test_reservation__adjust_time__update_reservation_buffer_on_adjust(graphql): @patch_method(PindoraClient.get_reservation) # Called by email sending -@patch_method(PindoraClient.reschedule_reservation) -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) def test_reservation__adjust_time__same_access_type(graphql): reservation = ReservationFactory.create_for_time_adjustment( access_type=AccessType.ACCESS_CODE, @@ -520,15 +519,13 @@ def test_reservation__adjust_time__same_access_type(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.reschedule_reservation.called is True - assert PindoraClient.deactivate_reservation_access_code.called is False + assert PindoraService.sync_access_code.call_count == 1 reservation.refresh_from_db() assert reservation.access_code_is_active is True -@patch_method(PindoraClient.reschedule_reservation) -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) def test_reservation__adjust_time__same_access_type__requires_handling(graphql): reservation = ReservationFactory.create_for_time_adjustment( access_type=AccessType.ACCESS_CODE, @@ -543,21 +540,11 @@ def test_reservation__adjust_time__same_access_type__requires_handling(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.reschedule_reservation.called is True - assert PindoraClient.deactivate_reservation_access_code.called is True - - reservation.refresh_from_db() - assert reservation.access_code_is_active is False + assert PindoraService.sync_access_code.call_count == 1 @patch_method(PindoraClient.get_reservation) # Called by email sending -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) +@patch_method(PindoraService.sync_access_code) def test_reservation__adjust_time__change_to_access_code(graphql): reservation = ReservationFactory.create_for_time_adjustment( access_type=AccessType.UNRESTRICTED, @@ -571,21 +558,10 @@ def test_reservation__adjust_time__change_to_access_code(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.create_reservation.called is True - assert PindoraClient.create_reservation.call_args.kwargs["is_active"] is True - - reservation.refresh_from_db() - assert reservation.access_code_generated_at == datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is True + assert PindoraService.sync_access_code.call_count == 1 -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": False, - }, -) +@patch_method(PindoraService.sync_access_code) def test_reservation__adjust_time__change_to_access_code__requires_handling(graphql): reservation = ReservationFactory.create_for_time_adjustment( access_type=AccessType.UNRESTRICTED, @@ -600,15 +576,10 @@ def test_reservation__adjust_time__change_to_access_code__requires_handling(grap assert response.has_errors is False, response.errors - assert PindoraClient.create_reservation.called is True - assert PindoraClient.create_reservation.call_args.kwargs["is_active"] is False - - reservation.refresh_from_db() - assert reservation.access_code_generated_at == datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is False + assert PindoraService.sync_access_code.call_count == 1 -@patch_method(PindoraClient.delete_reservation) +@patch_method(PindoraService.sync_access_code) def test_reservation__adjust_time__change_from_access_code(graphql): reservation = ReservationFactory.create_for_time_adjustment( access_type=AccessType.ACCESS_CODE, @@ -623,8 +594,4 @@ def test_reservation__adjust_time__change_from_access_code(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True - - reservation.refresh_from_db() - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False + assert PindoraService.sync_access_code.call_count == 1 diff --git a/backend/tests/test_graphql_api/test_reservation/test_approve.py b/backend/tests/test_graphql_api/test_reservation/test_approve.py index e4faf8d35b..e24b1b031b 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_approve.py +++ b/backend/tests/test_graphql_api/test_reservation/test_approve.py @@ -1,13 +1,11 @@ from __future__ import annotations -import datetime - import pytest from tilavarauspalvelu.enums import AccessType, ReservationStateChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError -from utils.date_utils import DEFAULT_TIMEZONE, local_datetime +from utils.date_utils import local_datetime from tests.factories import ReservationFactory, ReservationUnitFactory from tests.helpers import patch_method @@ -19,7 +17,7 @@ ] -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.activate_access_code) def test_reservation__approve__succeeds(graphql): reservation_unit = ReservationUnitFactory.create() reservation = ReservationFactory.create( @@ -36,7 +34,7 @@ def test_reservation__approve__succeeds(graphql): reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.CONFIRMED - assert PindoraClient.activate_reservation_access_code.call_count == 0 + assert PindoraService.activate_access_code.call_count == 0 def test_reservation__approve__cant_approve_if_status_not_requires_handling(graphql): @@ -111,8 +109,8 @@ def test_reservation__approve__succeeds_with_empty_handling_details(graphql): assert reservation.state == ReservationStateChoice.CONFIRMED -@patch_method(PindoraClient.activate_reservation_access_code) -@patch_method(PindoraClient.create_reservation) +@patch_method(PindoraService.activate_access_code) +@patch_method(PindoraService.create_access_code) def test_reservation__approve__succeeds__pindora_api__call_succeeds(graphql): reservation = ReservationFactory.create( reservation_units__access_types__access_type=AccessType.ACCESS_CODE, @@ -130,14 +128,13 @@ def test_reservation__approve__succeeds__pindora_api__call_succeeds(graphql): reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.CONFIRMED - assert reservation.access_code_is_active is True - assert PindoraClient.activate_reservation_access_code.called is True - assert PindoraClient.create_reservation.called is False + assert PindoraService.activate_access_code.called is True + assert PindoraService.create_access_code.called is False -@patch_method(PindoraClient.activate_reservation_access_code, side_effect=PindoraAPIError("Error")) -@patch_method(PindoraClient.create_reservation) +@patch_method(PindoraService.activate_access_code, side_effect=PindoraAPIError("Error")) +@patch_method(PindoraService.create_access_code) def test_reservation__approve__succeeds__pindora_api__call_fails(graphql): reservation = ReservationFactory.create( reservation_units__access_types__access_type=AccessType.ACCESS_CODE, @@ -158,37 +155,5 @@ def test_reservation__approve__succeeds__pindora_api__call_fails(graphql): assert reservation.state == ReservationStateChoice.CONFIRMED assert reservation.access_code_is_active is False - assert PindoraClient.activate_reservation_access_code.called is True - assert PindoraClient.create_reservation.called is False - - -@patch_method(PindoraClient.activate_reservation_access_code) -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) -def test_reservation__approve__succeeds__pindora_api__create_if_not_generated(graphql): - reservation = ReservationFactory.create( - reservation_units__access_types__access_type=AccessType.ACCESS_CODE, - state=ReservationStateChoice.REQUIRES_HANDLING, - access_type=AccessType.ACCESS_CODE, - access_code_is_active=False, - access_code_generated_at=None, - ) - - graphql.login_with_superuser() - data = get_approve_data(reservation) - response = graphql(APPROVE_MUTATION, input_data=data) - - assert response.has_errors is False, response.errors - - reservation.refresh_from_db() - assert reservation.state == ReservationStateChoice.CONFIRMED - assert reservation.access_code_generated_at == datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is True - - assert PindoraClient.activate_reservation_access_code.called is False - assert PindoraClient.create_reservation.called is True + assert PindoraService.activate_access_code.called is True + assert PindoraService.create_access_code.called is False diff --git a/backend/tests/test_graphql_api/test_reservation/test_cancel.py b/backend/tests/test_graphql_api/test_reservation/test_cancel.py index dbca06490f..d34765bf33 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_cancel.py +++ b/backend/tests/test_graphql_api/test_reservation/test_cancel.py @@ -10,7 +10,7 @@ from tilavarauspalvelu.enums import AccessType, OrderStatus, PaymentType, ReservationStateChoice, ReservationTypeChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError, PindoraNotFoundError from tilavarauspalvelu.integrations.verkkokauppa.verkkokauppa_api_client import VerkkokauppaAPIClient from tilavarauspalvelu.models import ReservationCancelReason @@ -247,7 +247,7 @@ def test_reservation__cancel__starts_refund_process_for_paid_reservation(graphql assert payment_order.refund_id == refund_id -@patch_method(PindoraClient.delete_reservation) +@patch_method(PindoraService.delete_access_code) def test_reservation__cancel__delete_from_pindora__call_success(graphql): reservation = ReservationFactory.create_for_cancellation( access_type=AccessType.ACCESS_CODE, @@ -261,15 +261,13 @@ def test_reservation__cancel__delete_from_pindora__call_success(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.CANCELLED - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False -@patch_method(PindoraClient.delete_reservation, side_effect=PindoraAPIError("Pindora API error")) +@patch_method(PindoraService.delete_access_code, side_effect=PindoraAPIError("Pindora API error")) def test_reservation__cancel__delete_from_pindora__call_fails(graphql): reservation = ReservationFactory.create_for_cancellation( access_type=AccessType.ACCESS_CODE, @@ -283,15 +281,13 @@ def test_reservation__cancel__delete_from_pindora__call_fails(graphql): assert response.error_message() == "Pindora API error" - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.CONFIRMED - assert reservation.access_code_generated_at is not None - assert reservation.access_code_is_active is True -@patch_method(PindoraClient.delete_reservation, side_effect=PindoraNotFoundError("Error")) +@patch_method(PindoraService.delete_access_code, side_effect=PindoraNotFoundError("Error")) def test_reservation__cancel__delete_from_pindora__call_fails__404(graphql): reservation = ReservationFactory.create_for_cancellation( access_type=AccessType.ACCESS_CODE, @@ -306,7 +302,7 @@ def test_reservation__cancel__delete_from_pindora__call_fails__404(graphql): # Request is still successful if Pindora fails with 404 assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.CANCELLED diff --git a/backend/tests/test_graphql_api/test_reservation/test_confirm.py b/backend/tests/test_graphql_api/test_reservation/test_confirm.py index 1dbe922a67..748de78db1 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_confirm.py +++ b/backend/tests/test_graphql_api/test_reservation/test_confirm.py @@ -14,7 +14,7 @@ ReservationNotification, ReservationStateChoice, ) -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError from tilavarauspalvelu.integrations.keyless_entry.typing import PindoraReservationResponse from tilavarauspalvelu.integrations.sentry import SentryLogger @@ -40,7 +40,7 @@ @override_settings(SEND_EMAILS=True) -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.activate_access_code) def test_reservation__confirm__changes_state__confirmed(graphql, outbox): reservation = ReservationFactory.create_for_confirmation() @@ -65,7 +65,7 @@ def test_reservation__confirm__changes_state__confirmed(graphql, outbox): unit_name = reservation.reservation_units.first().unit.name assert outbox[1].subject == f"New booking {reservation.id} has been made for {unit_name}" - assert PindoraClient.activate_reservation_access_code.call_count == 0 + assert PindoraService.activate_access_code.call_count == 0 @override_settings(SEND_EMAILS=True) @@ -476,7 +476,7 @@ def test_reservation__confirm__without_price_and_with_free_pricing_does_not_requ end=datetime.datetime(2024, 1, 1, 15), ), ) -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.activate_access_code) def test_reservation__confirm__pindora_api__call_succeeds(graphql): reservation = ReservationFactory.create_for_confirmation( access_type=AccessType.ACCESS_CODE, @@ -491,9 +491,8 @@ def test_reservation__confirm__pindora_api__call_succeeds(graphql): reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.CONFIRMED - assert reservation.access_code_is_active is True - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 @patch_method( @@ -504,7 +503,7 @@ def test_reservation__confirm__pindora_api__call_succeeds(graphql): end=datetime.datetime(2024, 1, 1, 15), ), ) -@patch_method(PindoraClient.activate_reservation_access_code, side_effect=PindoraAPIError("Error")) +@patch_method(PindoraService.activate_access_code, side_effect=PindoraAPIError("Error")) def test_reservation__confirm__pindora_api__call_fails(graphql): reservation = ReservationFactory.create_for_confirmation( access_type=AccessType.ACCESS_CODE, @@ -522,4 +521,4 @@ def test_reservation__confirm__pindora_api__call_fails(graphql): assert reservation.state == ReservationStateChoice.CONFIRMED assert reservation.access_code_is_active is False - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 diff --git a/backend/tests/test_graphql_api/test_reservation/test_create.py b/backend/tests/test_graphql_api/test_reservation/test_create.py index 928d62cfe1..c9d9e47612 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_create.py +++ b/backend/tests/test_graphql_api/test_reservation/test_create.py @@ -20,7 +20,7 @@ ReservationTypeChoice, ) from tilavarauspalvelu.integrations.helsinki_profile.clients import HelsinkiProfileClient -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError from tilavarauspalvelu.integrations.sentry import SentryLogger from tilavarauspalvelu.models import Reservation, ReservationUnitHierarchy @@ -1109,13 +1109,7 @@ def test_reservation__create__require_adult_reservee__no_id_token(graphql): assert reservation is not None -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": False, - }, -) +@patch_method(PindoraService.create_access_code) def test_reservation__create__access_type__access_code(graphql): reservation_unit = ReservationUnitFactory.create_reservable_now( access_types__access_type=AccessType.ACCESS_CODE, @@ -1131,13 +1125,11 @@ def test_reservation__create__access_type__access_code(graphql): reservation: Reservation = Reservation.objects.get(pk=response.first_query_object["pk"]) assert reservation.access_type == AccessType.ACCESS_CODE - assert reservation.access_code_generated_at == datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is False - PindoraClient.create_reservation.assert_called_with(reservation=reservation) + assert PindoraService.create_access_code.called is True -@patch_method(PindoraClient.create_reservation) +@patch_method(PindoraService.create_access_code) def test_reservation__create__access_type__changes_to_access_code_in_the_future(graphql): today = local_date() @@ -1156,13 +1148,11 @@ def test_reservation__create__access_type__changes_to_access_code_in_the_future( reservation: Reservation = Reservation.objects.get(pk=response.first_query_object["pk"]) assert reservation.access_type == AccessType.UNRESTRICTED - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False - assert PindoraClient.create_reservation.call_count == 0 + assert PindoraService.create_access_code.call_count == 0 -@patch_method(PindoraClient.create_reservation, side_effect=PindoraAPIError()) +@patch_method(PindoraService.create_access_code, side_effect=PindoraAPIError()) def test_reservation__create__access_type__access_code__no_reservation_on_pindora_failure(graphql): reservation_unit = ReservationUnitFactory.create_reservable_now( access_types__access_type=AccessType.ACCESS_CODE, diff --git a/backend/tests/test_graphql_api/test_reservation/test_delete.py b/backend/tests/test_graphql_api/test_reservation/test_delete.py index d7df5a6151..69268aa76d 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_delete.py +++ b/backend/tests/test_graphql_api/test_reservation/test_delete.py @@ -8,7 +8,7 @@ from tilavarauspalvelu.enums import AccessType, OrderStatus, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError from tilavarauspalvelu.integrations.sentry import SentryLogger from tilavarauspalvelu.integrations.verkkokauppa.order.exceptions import CancelOrderError @@ -179,7 +179,7 @@ def test_reservation__delete__mock_verkkokauppa(graphql): assert payment_order.status == OrderStatus.CANCELLED -@patch_method(PindoraClient.delete_reservation) +@patch_method(PindoraService.delete_access_code) def test_reservation__delete__delete_from_pindora__call_succeeds(graphql): reservation = ReservationFactory.create_for_delete( state=ReservationStateChoice.WAITING_FOR_PAYMENT, @@ -192,10 +192,10 @@ def test_reservation__delete__delete_from_pindora__call_succeeds(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True -@patch_method(PindoraClient.delete_reservation, side_effect=PindoraAPIError()) +@patch_method(PindoraService.delete_access_code, side_effect=PindoraAPIError()) def test_reservation__delete__delete_from_pindora__call_fails_runs_task(graphql): reservation = ReservationFactory.create_for_delete( state=ReservationStateChoice.WAITING_FOR_PAYMENT, @@ -212,5 +212,5 @@ def test_reservation__delete__delete_from_pindora__call_fails_runs_task(graphql) assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True assert task.called is True diff --git a/backend/tests/test_graphql_api/test_reservation/test_deny.py b/backend/tests/test_graphql_api/test_reservation/test_deny.py index 7028b1a0b0..9fb533f8ca 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_deny.py +++ b/backend/tests/test_graphql_api/test_reservation/test_deny.py @@ -7,7 +7,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError, PindoraNotFoundError from utils.date_utils import local_datetime @@ -207,7 +207,7 @@ def test_reservation__deny__dont_send_notification_if_reservation_already_ended( assert len(outbox) == 0 -@patch_method(PindoraClient.delete_reservation) +@patch_method(PindoraService.delete_access_code) def test_reservation__deny__delete_from_pindora__call_success(graphql): reservation = ReservationFactory.create_for_deny( access_type=AccessType.ACCESS_CODE, @@ -221,15 +221,13 @@ def test_reservation__deny__delete_from_pindora__call_success(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.DENIED - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False -@patch_method(PindoraClient.delete_reservation, side_effect=PindoraAPIError("Pindora API error")) +@patch_method(PindoraService.delete_access_code, side_effect=PindoraAPIError("Pindora API error")) def test_reservation__deny__delete_from_pindora__call_fails(graphql): reservation = ReservationFactory.create_for_deny( access_type=AccessType.ACCESS_CODE, @@ -243,15 +241,13 @@ def test_reservation__deny__delete_from_pindora__call_fails(graphql): assert response.error_message() == "Pindora API error" - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.REQUIRES_HANDLING - assert reservation.access_code_generated_at is not None - assert reservation.access_code_is_active is True -@patch_method(PindoraClient.delete_reservation, side_effect=PindoraNotFoundError("Error")) +@patch_method(PindoraService.delete_access_code, side_effect=PindoraNotFoundError("Error")) def test_reservation__deny__delete_from_pindora__call_fails__404(graphql): reservation = ReservationFactory.create_for_deny( access_type=AccessType.ACCESS_CODE, @@ -266,9 +262,7 @@ def test_reservation__deny__delete_from_pindora__call_fails__404(graphql): # Request is still successful if Pindora fails with 404 assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True + assert PindoraService.delete_access_code.called is True reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.DENIED - assert reservation.access_code_generated_at is not None - assert reservation.access_code_is_active is True diff --git a/backend/tests/test_graphql_api/test_reservation/test_query.py b/backend/tests/test_graphql_api/test_reservation/test_query.py index f827dd0c06..bf66eb10ed 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_query.py +++ b/backend/tests/test_graphql_api/test_reservation/test_query.py @@ -716,15 +716,22 @@ def test_reservation__query__pindora_info__in_recurring_reservation(graphql): @freeze_time(local_datetime(2022, 1, 1)) def test_reservation__query__pindora_info__in_application_section(graphql): + user = UserFactory.create() section = ApplicationSectionFactory.create( + application__user=user, application__application_round__sent_date=local_datetime(2022, 1, 1), ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) reservation = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, access_type=AccessType.ACCESS_CODE, state=ReservationStateChoice.CONFIRMED, begin=local_datetime(2022, 1, 1, 12), end=local_datetime(2022, 1, 1, 13), - recurring_reservation__allocated_time_slot__reservation_unit_option__application_section=section, ) query = pindora_query(reservation) @@ -741,11 +748,11 @@ def test_reservation__query__pindora_info__in_application_section(graphql): access_code_is_active=True, reservation_unit_code_validity=[ PindoraSeasonalBookingAccessCodeValidity( - reservation_unit_id=uuid.uuid4(), + reservation_unit_id=series.reservation_unit.uuid, access_code_valid_minutes_before=10, access_code_valid_minutes_after=5, - begin=local_datetime(2022, 1, 1, 12), - end=local_datetime(2022, 1, 1, 13), + begin=reservation.begin, + end=reservation.end, ), ], ) @@ -770,13 +777,22 @@ def test_reservation__query__pindora_info__in_application_section(graphql): @freeze_time(local_datetime(2022, 1, 1)) def test_reservation__query__pindora_info__in_application_section__not_sent(graphql): - section = ApplicationSectionFactory.create(application__application_round__sent_date=None) + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + application__application_round__sent_date=None, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) reservation = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, access_type=AccessType.ACCESS_CODE, state=ReservationStateChoice.CONFIRMED, begin=local_datetime(2022, 1, 1, 12), end=local_datetime(2022, 1, 1, 13), - recurring_reservation__allocated_time_slot__reservation_unit_option__application_section=section, ) query = pindora_query(reservation) diff --git a/backend/tests/test_graphql_api/test_reservation/test_requires_handling.py b/backend/tests/test_graphql_api/test_reservation/test_requires_handling.py index ec770bb460..933a617021 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_requires_handling.py +++ b/backend/tests/test_graphql_api/test_reservation/test_requires_handling.py @@ -4,7 +4,7 @@ from django.test import override_settings from tilavarauspalvelu.enums import AccessType, ReservationNotification, ReservationStateChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError, PindoraNotFoundError from utils.date_utils import local_datetime @@ -26,7 +26,7 @@ ReservationStateChoice.DENIED, ], ) -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.deactivate_access_code) def test_reservation__requires_handling__allowed_states(graphql, outbox, state): reservation = ReservationFactory.create_for_requires_handling(state=state) @@ -48,7 +48,7 @@ def test_reservation__requires_handling__allowed_states(graphql, outbox, state): assert len(outbox) == 1 assert outbox[0].subject == "Your booking is waiting for processing" - assert PindoraClient.deactivate_reservation_access_code.call_count == 0 + assert PindoraService.deactivate_access_code.call_count == 0 @pytest.mark.parametrize( @@ -80,7 +80,7 @@ def test_reservation__requires_handling__disallowed_states(graphql, state): assert reservation.state == state -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.deactivate_access_code) def test_reservation__requires_handling__pindora_api__call_succeeds(graphql): reservation = ReservationFactory.create_for_requires_handling( state=ReservationStateChoice.CONFIRMED, @@ -97,12 +97,11 @@ def test_reservation__requires_handling__pindora_api__call_succeeds(graphql): reservation.refresh_from_db() assert reservation.state == ReservationStateChoice.REQUIRES_HANDLING - assert reservation.access_code_is_active is False - assert PindoraClient.deactivate_reservation_access_code.call_count == 1 + assert PindoraService.deactivate_access_code.call_count == 1 -@patch_method(PindoraClient.deactivate_reservation_access_code, side_effect=PindoraAPIError("Pindora API error")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraAPIError("Pindora API error")) def test_reservation__requires_handling__pindora_api__call_fails(graphql): reservation = ReservationFactory.create_for_requires_handling( state=ReservationStateChoice.CONFIRMED, @@ -117,10 +116,10 @@ def test_reservation__requires_handling__pindora_api__call_fails(graphql): assert response.error_message() == "Pindora API error" - assert PindoraClient.deactivate_reservation_access_code.call_count == 1 + assert PindoraService.deactivate_access_code.call_count == 1 -@patch_method(PindoraClient.deactivate_reservation_access_code, side_effect=PindoraNotFoundError("Error")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraNotFoundError("Error")) def test_reservation__requires_handling__pindora_api__call_fails__404(graphql): reservation = ReservationFactory.create_for_requires_handling( state=ReservationStateChoice.CONFIRMED, @@ -140,26 +139,4 @@ def test_reservation__requires_handling__pindora_api__call_fails__404(graphql): assert reservation.state == ReservationStateChoice.REQUIRES_HANDLING assert reservation.access_code_is_active is True - assert PindoraClient.deactivate_reservation_access_code.call_count == 1 - - -@patch_method(PindoraClient.deactivate_reservation_access_code) -def test_reservation__requires_handling__pindora_api__not_called_if_not_generated(graphql): - reservation = ReservationFactory.create_for_requires_handling( - state=ReservationStateChoice.DENIED, - access_type=AccessType.ACCESS_CODE, - access_code_is_active=False, - access_code_generated_at=None, - ) - - graphql.login_with_superuser() - input_data = get_require_handling_data(reservation) - response = graphql(REQUIRE_HANDLING_MUTATION, input_data=input_data) - - assert response.has_errors is False, response.errors - - reservation.refresh_from_db() - assert reservation.state == ReservationStateChoice.REQUIRES_HANDLING - assert reservation.access_code_is_active is False - - assert PindoraClient.deactivate_reservation_access_code.call_count == 0 + assert PindoraService.deactivate_access_code.call_count == 1 diff --git a/backend/tests/test_graphql_api/test_reservation/test_staff_adjust_time.py b/backend/tests/test_graphql_api/test_reservation/test_staff_adjust_time.py index d0bec0e71f..a1491e6a4a 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_staff_adjust_time.py +++ b/backend/tests/test_graphql_api/test_reservation/test_staff_adjust_time.py @@ -7,7 +7,7 @@ from django.test import override_settings from tilavarauspalvelu.enums import AccessType, ReservationStartInterval, ReservationStateChoice, ReservationTypeChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import Reservation, ReservationUnitHierarchy from utils.date_utils import DEFAULT_TIMEZONE, local_datetime, next_hour @@ -535,8 +535,7 @@ def test_reservation__staff_adjust_time__reservation_block_whole_day__ignore_giv assert reservation.buffer_time_after == datetime.timedelta(hours=11) -@patch_method(PindoraClient.reschedule_reservation) -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) def test_reservation__staff_adjust_time__same_access_type(graphql): reservation = ReservationFactory.create_for_time_adjustment( type=ReservationTypeChoice.STAFF, @@ -551,15 +550,10 @@ def test_reservation__staff_adjust_time__same_access_type(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.reschedule_reservation.called is True - assert PindoraClient.deactivate_reservation_access_code.called is False + assert PindoraService.sync_access_code.called is True - reservation.refresh_from_db() - assert reservation.access_code_is_active is True - -@patch_method(PindoraClient.reschedule_reservation) -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) def test_reservation__staff_adjust_time__same_access_type__requires_handling(graphql): reservation = ReservationFactory.create_for_time_adjustment( type=ReservationTypeChoice.STAFF, @@ -575,20 +569,13 @@ def test_reservation__staff_adjust_time__same_access_type__requires_handling(gra assert response.has_errors is False, response.errors - assert PindoraClient.reschedule_reservation.called is True - assert PindoraClient.deactivate_reservation_access_code.called is False + assert PindoraService.sync_access_code.called is True reservation.refresh_from_db() assert reservation.access_code_is_active is True -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) +@patch_method(PindoraService.sync_access_code) def test_reservation__staff_adjust_time__change_to_access_code(graphql): reservation = ReservationFactory.create_for_time_adjustment( type=ReservationTypeChoice.STAFF, @@ -603,21 +590,10 @@ def test_reservation__staff_adjust_time__change_to_access_code(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.create_reservation.called is True - assert PindoraClient.create_reservation.call_args.kwargs["is_active"] is True - - reservation.refresh_from_db() - assert reservation.access_code_generated_at == datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is True + assert PindoraService.sync_access_code.called is True -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) +@patch_method(PindoraService.sync_access_code) def test_reservation__staff_adjust_time__change_to_access_code__requires_handling(graphql): reservation = ReservationFactory.create_for_time_adjustment( type=ReservationTypeChoice.STAFF, @@ -633,15 +609,10 @@ def test_reservation__staff_adjust_time__change_to_access_code__requires_handlin assert response.has_errors is False, response.errors - assert PindoraClient.create_reservation.called is True - assert PindoraClient.create_reservation.call_args.kwargs["is_active"] is True - - reservation.refresh_from_db() - assert reservation.access_code_generated_at == datetime.datetime(2025, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is True + assert PindoraService.sync_access_code.called is True -@patch_method(PindoraClient.delete_reservation) +@patch_method(PindoraService.sync_access_code) def test_reservation__staff_adjust_time__change_from_access_code(graphql): reservation = ReservationFactory.create_for_time_adjustment( type=ReservationTypeChoice.STAFF, @@ -657,8 +628,4 @@ def test_reservation__staff_adjust_time__change_from_access_code(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.delete_reservation.called is True - - reservation.refresh_from_db() - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False + assert PindoraService.sync_access_code.called is True diff --git a/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code.py b/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code.py index 9859304a39..c54332f4c7 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code.py +++ b/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code.py @@ -6,7 +6,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError, PindoraNotFoundError from utils.date_utils import DEFAULT_TIMEZONE, local_datetime @@ -20,8 +20,8 @@ ] -@patch_method(PindoraClient.change_reservation_access_code) -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_change_access_code(graphql): reservation = ReservationFactory.create( @@ -43,13 +43,13 @@ def test_staff_change_access_code(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.change_reservation_access_code.call_count == 1 - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.change_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 -@patch_method(PindoraClient.change_reservation_access_code) -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_change_access_code__not_active(graphql): reservation = ReservationFactory.create( @@ -70,17 +70,14 @@ def test_staff_change_access_code__not_active(graphql): response = graphql(CHANGE_ACCESS_CODE_STAFF_MUTATION, input_data=data) assert response.has_errors is False, response.errors - assert response.first_query_object["accessCodeIsActive"] is True - reservation.refresh_from_db() - assert reservation.access_code_is_active is True - - assert PindoraClient.change_reservation_access_code.call_count == 1 - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.change_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 -@patch_method(PindoraClient.change_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__not_generated(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -103,7 +100,8 @@ def test_staff_change_access_code__not_generated(graphql): assert response.field_error_messages() == ["Reservation must have an access code to change it."] -@patch_method(PindoraClient.change_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__not_access_type_access_code(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -126,7 +124,8 @@ def test_staff_change_access_code__not_access_type_access_code(graphql): assert response.field_error_messages() == ["Reservation access type does not use access codes."] -@patch_method(PindoraClient.change_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__in_series(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -150,7 +149,8 @@ def test_staff_change_access_code__in_series(graphql): assert response.field_error_messages() == ["Reservation cannot be in a reservation series."] -@patch_method(PindoraClient.change_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__state_not_confirmed(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.WAITING_FOR_PAYMENT, @@ -173,7 +173,8 @@ def test_staff_change_access_code__state_not_confirmed(graphql): assert response.field_error_messages() == ["Reservation access code cannot be changed based on its state."] -@patch_method(PindoraClient.change_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__type_is_blocked(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -196,8 +197,8 @@ def test_staff_change_access_code__type_is_blocked(graphql): assert response.field_error_messages() == ["Reservation access code cannot be changed based on its type."] -@patch_method(PindoraClient.change_reservation_access_code) -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_change_access_code__ongoing(graphql): reservation = ReservationFactory.create( @@ -219,12 +220,13 @@ def test_staff_change_access_code__ongoing(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.change_reservation_access_code.call_count == 1 - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.change_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 -@patch_method(PindoraClient.change_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__already_ended(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -247,7 +249,8 @@ def test_staff_change_access_code__already_ended(graphql): assert response.field_error_messages() == ["Reservation has already ended."] -@patch_method(PindoraClient.change_reservation_access_code, side_effect=PindoraAPIError()) +@patch_method(PindoraService.change_access_code, side_effect=PindoraAPIError()) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__pindora_error(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -269,7 +272,8 @@ def test_staff_change_access_code__pindora_error(graphql): assert response.error_message() == "Pindora client error" -@patch_method(PindoraClient.change_reservation_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.change_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__pindora_error__404(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, diff --git a/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code_permissions.py b/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code_permissions.py index 05768bd556..3eac6e8585 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code_permissions.py +++ b/backend/tests/test_graphql_api/test_reservation/test_staff_change_access_code_permissions.py @@ -6,7 +6,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice, UserRoleChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from utils.date_utils import local_datetime from tests.factories import ReservationFactory, ReservationUnitFactory, UserFactory @@ -18,7 +18,8 @@ ] -@patch_method(PindoraClient.change_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) def test_staff_change_access_code__regular_user(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -39,11 +40,12 @@ def test_staff_change_access_code__regular_user(graphql): assert response.error_message() == "No permission to update." - assert PindoraClient.change_reservation_access_code.call_count == 0 + assert PindoraService.change_access_code.call_count == 0 + assert PindoraService.activate_access_code.call_count == 0 -@patch_method(PindoraClient.change_reservation_access_code) -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_change_access_code__unit_handler(graphql): reservation_unit = ReservationUnitFactory.create() @@ -69,13 +71,13 @@ def test_staff_change_access_code__unit_handler(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.change_reservation_access_code.call_count == 1 - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.change_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 -@patch_method(PindoraClient.change_reservation_access_code) -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.change_access_code) +@patch_method(PindoraService.activate_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_change_access_code__general_handler(graphql): reservation = ReservationFactory.create( @@ -99,6 +101,6 @@ def test_staff_change_access_code__general_handler(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.change_reservation_access_code.call_count == 1 - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.change_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 diff --git a/backend/tests/test_graphql_api/test_reservation/test_staff_create.py b/backend/tests/test_graphql_api/test_reservation/test_staff_create.py index d94eea6641..1cc4320a22 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_staff_create.py +++ b/backend/tests/test_graphql_api/test_reservation/test_staff_create.py @@ -6,7 +6,7 @@ import pytest from tilavarauspalvelu.enums import AccessType, CustomerTypeChoice, ReservationStateChoice, ReservationTypeChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError from tilavarauspalvelu.models import Reservation, ReservationUnitHierarchy from utils.date_utils import DEFAULT_TIMEZONE, local_date, local_datetime, next_hour @@ -486,13 +486,7 @@ def test_reservation__staff_create__reservee_used_ad_login(graphql, amr, expecte assert reservation.reservee_used_ad_login is expected -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) +@patch_method(PindoraService.create_access_code) def test_reservation__staff_create__access_type__access_code(graphql): reservation_unit = ReservationUnitFactory.create(access_types__access_type=AccessType.ACCESS_CODE) @@ -503,15 +497,13 @@ def test_reservation__staff_create__access_type__access_code(graphql): assert response.has_errors is False, response.errors reservation = Reservation.objects.get(pk=response.first_query_object["pk"]) - assert reservation.access_type == AccessType.ACCESS_CODE - assert reservation.access_code_generated_at == datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is True - PindoraClient.create_reservation.assert_called_with(reservation=reservation, is_active=True) + assert PindoraService.create_access_code.call_count == 1 + assert PindoraService.create_access_code.call_args.kwargs["is_active"] is True -@patch_method(PindoraClient.create_reservation) +@patch_method(PindoraService.create_access_code) def test_reservation__staff_create__access_type__changed_to_access_code_in_the_future(graphql): today = local_date() @@ -527,15 +519,12 @@ def test_reservation__staff_create__access_type__changed_to_access_code_in_the_f assert response.has_errors is False, response.errors reservation = Reservation.objects.get(pk=response.first_query_object["pk"]) - assert reservation.access_type == AccessType.UNRESTRICTED - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False - assert PindoraClient.create_reservation.call_count == 0 + assert PindoraService.create_access_code.call_count == 0 -@patch_method(PindoraClient.create_reservation) +@patch_method(PindoraService.create_access_code) def test_reservation__staff_create__access_type__access_code_has_ended(graphql): today = local_date() @@ -558,21 +547,12 @@ def test_reservation__staff_create__access_type__access_code_has_ended(graphql): assert response.has_errors is False, response.errors reservation = Reservation.objects.get(pk=response.first_query_object["pk"]) - assert reservation.access_type == AccessType.UNRESTRICTED - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False - assert PindoraClient.create_reservation.call_count == 0 + assert PindoraService.create_access_code.call_count == 0 -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) +@patch_method(PindoraService.create_access_code) def test_reservation__staff_create__access_type__access_code__blocked(graphql): reservation_unit = ReservationUnitFactory.create( access_types__access_type=AccessType.ACCESS_CODE, @@ -585,15 +565,13 @@ def test_reservation__staff_create__access_type__access_code__blocked(graphql): assert response.has_errors is False, response.errors reservation = Reservation.objects.get(pk=response.first_query_object["pk"]) - assert reservation.access_type == AccessType.ACCESS_CODE - assert reservation.access_code_generated_at == datetime.datetime(2023, 1, 1, tzinfo=DEFAULT_TIMEZONE) - assert reservation.access_code_is_active is True - PindoraClient.create_reservation.assert_called_with(reservation=reservation, is_active=False) + assert PindoraService.create_access_code.call_count == 1 + assert PindoraService.create_access_code.call_args.kwargs["is_active"] is False -@patch_method(PindoraClient.create_reservation, side_effect=PindoraAPIError()) +@patch_method(PindoraService.create_access_code, side_effect=PindoraAPIError()) def test_reservation__staff_create__access_type__access_code__create_reservation_on_pindora_failure(graphql): reservation_unit = ReservationUnitFactory.create( access_types__access_type=AccessType.ACCESS_CODE, @@ -609,7 +587,4 @@ def test_reservation__staff_create__access_type__access_code__create_reservation # Reservation is still created, but it doesn't know an access code was generated. reservation: Reservation | None = Reservation.objects.first() assert reservation is not None - assert reservation.access_type == AccessType.ACCESS_CODE - assert reservation.access_code_generated_at is None - assert reservation.access_code_is_active is False diff --git a/backend/tests/test_graphql_api/test_reservation/test_staff_modify.py b/backend/tests/test_graphql_api/test_reservation/test_staff_modify.py index 1d7305657c..c0f0ae8a3a 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_staff_modify.py +++ b/backend/tests/test_graphql_api/test_reservation/test_staff_modify.py @@ -5,7 +5,7 @@ import pytest from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError, PindoraNotFoundError from utils.date_utils import next_hour @@ -129,7 +129,7 @@ def test_reservation__staff_modify__blocked_reservation_to_normal(graphql): ] -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.activate_access_code) def test_reservation__staff_modify__blocked_reservation_to_staff(graphql): reservation = ReservationFactory.create_for_staff_update(type=ReservationTypeChoice.BLOCKED) @@ -142,10 +142,10 @@ def test_reservation__staff_modify__blocked_reservation_to_staff(graphql): reservation.refresh_from_db() assert reservation.type == ReservationTypeChoice.STAFF - assert PindoraClient.activate_reservation_access_code.call_count == 0 + assert PindoraService.activate_access_code.call_count == 0 -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.activate_access_code) def test_reservation__staff_modify__blocked_reservation_to_staff__pindora_api__call_succeeds(graphql): reservation = ReservationFactory.create_for_staff_update( type=ReservationTypeChoice.BLOCKED, @@ -161,12 +161,11 @@ def test_reservation__staff_modify__blocked_reservation_to_staff__pindora_api__c reservation.refresh_from_db() assert reservation.type == ReservationTypeChoice.STAFF - assert reservation.access_code_is_active is True - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 -@patch_method(PindoraClient.activate_reservation_access_code, side_effect=PindoraAPIError("Pindora API error")) +@patch_method(PindoraService.activate_access_code, side_effect=PindoraAPIError("Pindora API error")) def test_reservation__staff_modify__blocked_reservation_to_staff__pindora_api__call_fails(graphql): reservation = ReservationFactory.create_for_staff_update( type=ReservationTypeChoice.BLOCKED, @@ -180,10 +179,10 @@ def test_reservation__staff_modify__blocked_reservation_to_staff__pindora_api__c assert response.error_message() == "Pindora API error" - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 -@patch_method(PindoraClient.activate_reservation_access_code, side_effect=PindoraNotFoundError("Error")) +@patch_method(PindoraService.activate_access_code, side_effect=PindoraNotFoundError("Error")) def test_reservation__staff_modify__blocked_reservation_to_staff__pindora_api__call_fails__404(graphql): reservation = ReservationFactory.create_for_staff_update( type=ReservationTypeChoice.BLOCKED, @@ -202,10 +201,10 @@ def test_reservation__staff_modify__blocked_reservation_to_staff__pindora_api__c assert reservation.type == ReservationTypeChoice.STAFF assert reservation.access_code_is_active is False - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.activate_access_code.call_count == 1 -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.deactivate_access_code) def test_reservation__staff_modify__staff_reservation_to_blocked(graphql): reservation = ReservationFactory.create_for_staff_update(type=ReservationTypeChoice.STAFF) @@ -219,10 +218,10 @@ def test_reservation__staff_modify__staff_reservation_to_blocked(graphql): assert reservation.type == ReservationTypeChoice.BLOCKED assert reservation.access_code_is_active is False - assert PindoraClient.deactivate_reservation_access_code.call_count == 0 + assert PindoraService.deactivate_access_code.call_count == 0 -@patch_method(PindoraClient.deactivate_reservation_access_code) +@patch_method(PindoraService.deactivate_access_code) def test_reservation__staff_modify__staff_reservation_to_blocked__pindora_api__call_succeeds(graphql): reservation = ReservationFactory.create_for_staff_update( type=ReservationTypeChoice.STAFF, @@ -238,12 +237,11 @@ def test_reservation__staff_modify__staff_reservation_to_blocked__pindora_api__c reservation.refresh_from_db() assert reservation.type == ReservationTypeChoice.BLOCKED - assert reservation.access_code_is_active is False - assert PindoraClient.deactivate_reservation_access_code.call_count == 1 + assert PindoraService.deactivate_access_code.call_count == 1 -@patch_method(PindoraClient.deactivate_reservation_access_code, side_effect=PindoraAPIError("Pindora API error")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraAPIError("Pindora API error")) def test_reservation__staff_modify__staff_reservation_to_blocked__pindora_api__call_fails(graphql): reservation = ReservationFactory.create_for_staff_update( type=ReservationTypeChoice.STAFF, @@ -257,10 +255,10 @@ def test_reservation__staff_modify__staff_reservation_to_blocked__pindora_api__c assert response.error_message() == "Pindora API error" - assert PindoraClient.deactivate_reservation_access_code.call_count == 1 + assert PindoraService.deactivate_access_code.call_count == 1 -@patch_method(PindoraClient.deactivate_reservation_access_code, side_effect=PindoraNotFoundError("Error")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraNotFoundError("Error")) def test_reservation__staff_modify__staff_reservation_to_blocked__pindora_api__call_succeeds__404(graphql): reservation = ReservationFactory.create_for_staff_update( type=ReservationTypeChoice.STAFF, @@ -279,4 +277,4 @@ def test_reservation__staff_modify__staff_reservation_to_blocked__pindora_api__c assert reservation.type == ReservationTypeChoice.BLOCKED assert reservation.access_code_is_active is True - assert PindoraClient.deactivate_reservation_access_code.call_count == 1 + assert PindoraService.deactivate_access_code.call_count == 1 diff --git a/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code.py b/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code.py index 47dd94ad3a..f79c52eef1 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code.py +++ b/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code.py @@ -6,9 +6,8 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient -from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraConflictError, PindoraNotFoundError -from utils.date_utils import DEFAULT_TIMEZONE, local_datetime +from tilavarauspalvelu.integrations.keyless_entry import PindoraService +from utils.date_utils import local_datetime from tests.factories import RecurringReservationFactory, ReservationFactory from tests.helpers import patch_method @@ -20,12 +19,9 @@ ] -@patch_method(PindoraClient.activate_reservation_access_code) -@patch_method(PindoraClient.deactivate_reservation_access_code) -@patch_method(PindoraClient.create_reservation) -@patch_method(PindoraClient.get_reservation) +@patch_method(PindoraService.sync_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) -def test_staff_repair_access_code__should_be_active(graphql): +def test_staff_repair_access_code(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, type=ReservationTypeChoice.NORMAL, @@ -49,117 +45,10 @@ def test_staff_repair_access_code__should_be_active(graphql): assert reservation.access_code_is_active is True assert reservation.access_code_generated_at is not None - assert PindoraClient.activate_reservation_access_code.call_count == 1 - assert PindoraClient.deactivate_reservation_access_code.call_count == 0 - assert PindoraClient.create_reservation.call_count == 0 - assert PindoraClient.get_reservation.call_count == 0 + assert PindoraService.sync_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 -@patch_method(PindoraClient.activate_reservation_access_code) -@patch_method(PindoraClient.deactivate_reservation_access_code) -@patch_method(PindoraClient.create_reservation) -@patch_method(PindoraClient.get_reservation) -def test_staff_repair_access_code__should_not_be_active(graphql): - reservation = ReservationFactory.create( - state=ReservationStateChoice.CONFIRMED, - type=ReservationTypeChoice.BLOCKED, - access_type=AccessType.ACCESS_CODE, - access_code_generated_at=local_datetime(), - access_code_is_active=True, - begin=local_datetime() + datetime.timedelta(hours=1), - end=local_datetime() + datetime.timedelta(hours=2), - ) - - data = { - "pk": reservation.pk, - } - - graphql.login_with_superuser() - response = graphql(REPAIR_ACCESS_CODE_STAFF_MUTATION, input_data=data) - - assert response.has_errors is False, response.errors - - reservation.refresh_from_db() - assert reservation.access_code_is_active is False - assert reservation.access_code_generated_at is not None - - assert PindoraClient.activate_reservation_access_code.call_count == 0 - assert PindoraClient.deactivate_reservation_access_code.call_count == 1 - assert PindoraClient.create_reservation.call_count == 0 - assert PindoraClient.get_reservation.call_count == 0 - - -@patch_method(EmailService.send_reservation_modified_access_code_email) -@patch_method(PindoraClient.activate_reservation_access_code, side_effect=PindoraNotFoundError("Not found")) -@patch_method( - PindoraClient.create_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2024, 1, 2, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) -def test_staff_repair_access_code__create_new_access_code(graphql): - reservation = ReservationFactory.create( - state=ReservationStateChoice.CONFIRMED, - type=ReservationTypeChoice.NORMAL, - access_type=AccessType.ACCESS_CODE, - access_code_generated_at=datetime.datetime(2024, 1, 1, tzinfo=DEFAULT_TIMEZONE), - access_code_is_active=True, - begin=local_datetime() + datetime.timedelta(hours=1), - end=local_datetime() + datetime.timedelta(hours=2), - ) - - data = { - "pk": reservation.pk, - } - - graphql.login_with_superuser() - response = graphql(REPAIR_ACCESS_CODE_STAFF_MUTATION, input_data=data) - - assert response.has_errors is False, response.errors - - reservation.refresh_from_db() - assert reservation.access_code_is_active is True - assert reservation.access_code_generated_at == datetime.datetime(2024, 1, 2, tzinfo=DEFAULT_TIMEZONE) - - assert EmailService.send_reservation_modified_access_code_email.call_count == 1 - - -@patch_method(PindoraClient.activate_reservation_access_code, side_effect=PindoraNotFoundError("Not found")) -@patch_method(PindoraClient.create_reservation, side_effect=PindoraConflictError("Conflict")) -@patch_method( - PindoraClient.get_reservation, - return_value={ - "access_code_generated_at": datetime.datetime(2024, 1, 2, tzinfo=DEFAULT_TIMEZONE), - "access_code_is_active": True, - }, -) -def test_staff_repair_access_code__get_access_code_info(graphql): - reservation = ReservationFactory.create( - state=ReservationStateChoice.CONFIRMED, - type=ReservationTypeChoice.NORMAL, - access_type=AccessType.ACCESS_CODE, - access_code_generated_at=datetime.datetime(2024, 1, 1, tzinfo=DEFAULT_TIMEZONE), - access_code_is_active=True, - begin=local_datetime() + datetime.timedelta(hours=1), - end=local_datetime() + datetime.timedelta(hours=2), - ) - - data = { - "pk": reservation.pk, - } - - graphql.login_with_superuser() - response = graphql(REPAIR_ACCESS_CODE_STAFF_MUTATION, input_data=data) - - assert response.has_errors is False, response.errors - - reservation.refresh_from_db() - assert reservation.access_code_is_active is True - assert reservation.access_code_generated_at == datetime.datetime(2024, 1, 2, tzinfo=DEFAULT_TIMEZONE) - - def test_staff_repair_access_code__access_type_not_access_code(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -205,7 +94,7 @@ def test_staff_repair_access_code__in_series(graphql): assert response.field_error_messages() == ["Reservation cannot be in a reservation series."] -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_repair_access_code__ongoing(graphql): reservation = ReservationFactory.create( @@ -229,7 +118,7 @@ def test_staff_repair_access_code__ongoing(graphql): assert EmailService.send_reservation_modified_access_code_email.call_count == 1 -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) def test_staff_repair_access_code__has_ended(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, diff --git a/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code_permissions.py b/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code_permissions.py index e84ec06cbc..c50fdf7003 100644 --- a/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code_permissions.py +++ b/backend/tests/test_graphql_api/test_reservation/test_staff_repair_access_code_permissions.py @@ -6,7 +6,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice, UserRoleChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from utils.date_utils import local_datetime from tests.factories import ReservationFactory, ReservationUnitFactory, UserFactory @@ -18,7 +18,7 @@ ] -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) def test_staff_repair_access_code__regular_user(graphql): reservation = ReservationFactory.create( state=ReservationStateChoice.CONFIRMED, @@ -39,10 +39,10 @@ def test_staff_repair_access_code__regular_user(graphql): assert response.error_message() == "No permission to update." - assert PindoraClient.activate_reservation_access_code.call_count == 0 + assert PindoraService.sync_access_code.call_count == 0 -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_repair_access_code__unit_handler(graphql): reservation_unit = ReservationUnitFactory.create() @@ -68,11 +68,11 @@ def test_staff_repair_access_code__unit_handler(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.sync_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 -@patch_method(PindoraClient.activate_reservation_access_code) +@patch_method(PindoraService.sync_access_code) @patch_method(EmailService.send_reservation_modified_access_code_email) def test_staff_repair_access_code__general_handler(graphql): reservation = ReservationFactory.create( @@ -96,5 +96,5 @@ def test_staff_repair_access_code__general_handler(graphql): assert response.has_errors is False, response.errors - assert PindoraClient.activate_reservation_access_code.call_count == 1 + assert PindoraService.sync_access_code.call_count == 1 assert EmailService.send_reservation_modified_access_code_email.call_count == 1 diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/conftest.py b/backend/tests/test_integrations/test_keyless_entry/conftest.py similarity index 100% rename from backend/tests/test_integrations/test_keyless_entry/test_pindora_client/conftest.py rename to backend/tests/test_integrations/test_keyless_entry/conftest.py diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/helpers.py b/backend/tests/test_integrations/test_keyless_entry/helpers.py similarity index 58% rename from backend/tests/test_integrations/test_keyless_entry/test_pindora_client/helpers.py rename to backend/tests/test_integrations/test_keyless_entry/helpers.py index d15f13b307..a43b7a86f5 100644 --- a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/helpers.py +++ b/backend/tests/test_integrations/test_keyless_entry/helpers.py @@ -1,27 +1,25 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, NamedTuple +import uuid +from typing import Any, NamedTuple -from utils.date_utils import DEFAULT_TIMEZONE, local_datetime +from utils.date_utils import local_datetime -if TYPE_CHECKING: - from tilavarauspalvelu.models import Reservation, ReservationUnit - -def default_reservation_unit_response(reservation_unit: ReservationUnit, **overrides: Any) -> dict[str, Any]: +def default_reservation_unit_response(**overrides: Any) -> dict[str, Any]: # This is the json response form Pindora API, which is processed to `PindoraReservationUnitResponse`. return { - "reservation_unit_id": str(reservation_unit.uuid), - "name": reservation_unit.name, + "reservation_unit_id": str(uuid.uuid4()), + "name": "foo", "keypad_url": "https://example.com", **overrides, } -def default_reservation_response(reservation: Reservation, **overrides: Any) -> dict[str, Any]: +def default_reservation_response(**overrides: Any) -> dict[str, Any]: # This is the json response form Pindora API, which is processed to `PindoraReservationResponse`. return { - "reservation_unit_id": str(reservation.ext_uuid), + "reservation_unit_id": str(uuid.uuid4()), "access_code": "13245#", "access_code_keypad_url": "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", "access_code_phone_number": "+358407089833", @@ -29,54 +27,54 @@ def default_reservation_response(reservation: Reservation, **overrides: Any) -> "access_code_sms_message": "a13245", "access_code_valid_minutes_before": 0, "access_code_valid_minutes_after": 0, - "access_code_generated_at": reservation.created_at.astimezone(DEFAULT_TIMEZONE).isoformat(), + "access_code_generated_at": local_datetime(2022, 1, 1).isoformat(), "access_code_is_active": True, - "begin": reservation.begin.astimezone(DEFAULT_TIMEZONE).isoformat(), - "end": reservation.end.astimezone(DEFAULT_TIMEZONE).isoformat(), + "begin": local_datetime(2022, 1, 1, 12).isoformat(), + "end": local_datetime(2022, 1, 1, 14).isoformat(), **overrides, } -def default_seasonal_booking_response(reservation: Reservation, **overrides: Any) -> dict[str, Any]: - # This is the json response form Pindora API, which is processed to `PindoraSeasonalBookingResponse`. +def default_reservation_series_response(**overrides: Any) -> dict[str, Any]: + # This is the json response form Pindora API, which is processed to `PindoraReservationSeriesResponse`. return { + "reservation_unit_id": str(uuid.uuid4()), "access_code": "13245#", "access_code_keypad_url": "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", "access_code_phone_number": "+358407089833", "access_code_sms_number": "+358407089834", "access_code_sms_message": "a13245", - "access_code_generated_at": reservation.created_at.astimezone(DEFAULT_TIMEZONE).isoformat(), + "access_code_generated_at": local_datetime(2022, 1, 1).isoformat(), "access_code_is_active": True, "reservation_unit_code_validity": [ { - "reservation_unit_id": str(reservation.ext_uuid), "access_code_valid_minutes_before": 0, "access_code_valid_minutes_after": 0, - "begin": reservation.begin.astimezone(DEFAULT_TIMEZONE).isoformat(), - "end": reservation.end.astimezone(DEFAULT_TIMEZONE).isoformat(), + "begin": local_datetime(2022, 1, 1, 12).isoformat(), + "end": local_datetime(2022, 1, 1, 14).isoformat(), }, ], **overrides, } -def default_reservation_series_response(reservation: Reservation, **overrides: Any) -> dict[str, Any]: - # This is the json response form Pindora API, which is processed to `PindoraReservationSeriesResponse`. +def default_seasonal_booking_response(**overrides: Any) -> dict[str, Any]: + # This is the json response form Pindora API, which is processed to `PindoraSeasonalBookingResponse`. return { - "reservation_unit_id": str(reservation.ext_uuid), "access_code": "13245#", "access_code_keypad_url": "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", "access_code_phone_number": "+358407089833", "access_code_sms_number": "+358407089834", "access_code_sms_message": "a13245", - "access_code_generated_at": reservation.created_at.astimezone(DEFAULT_TIMEZONE).isoformat(), + "access_code_generated_at": local_datetime(2022, 1, 1).isoformat(), "access_code_is_active": True, "reservation_unit_code_validity": [ { + "reservation_unit_id": str(uuid.uuid4()), "access_code_valid_minutes_before": 0, "access_code_valid_minutes_after": 0, - "begin": reservation.begin.astimezone(DEFAULT_TIMEZONE).isoformat(), - "end": reservation.end.astimezone(DEFAULT_TIMEZONE).isoformat(), + "begin": local_datetime(2022, 1, 1, 12).isoformat(), + "end": local_datetime(2022, 1, 1, 14).isoformat(), }, ], **overrides, @@ -86,7 +84,7 @@ def default_reservation_series_response(reservation: Reservation, **overrides: A def default_access_code_modify_response(**overrides: Any) -> dict[str, Any]: # This is the json response form Pindora API, which is processed to `PindoraAccessCodeModifyResponse`. return { - "access_code_generated_at": local_datetime().isoformat(), + "access_code_generated_at": local_datetime(2022, 1, 1).isoformat(), "access_code_is_active": True, **overrides, } diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation.py index 2d6f3ef29a..49bddab1ca 100644 --- a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation.py +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation.py @@ -22,35 +22,27 @@ PindoraPermissionError, PindoraUnexpectedResponseError, ) -from utils.date_utils import DEFAULT_TIMEZONE, local_datetime +from utils.date_utils import local_datetime from utils.external_service.errors import ExternalServiceRequestError from tests.factories import ReservationFactory from tests.helpers import ResponseMock, exact, patch_method, use_retries - -from .helpers import ErrorParams, default_access_code_modify_response, default_reservation_response +from tests.test_integrations.test_keyless_entry.helpers import ( + ErrorParams, + default_access_code_modify_response, + default_reservation_response, +) def test_pindora_client__get_reservation(): reservation = ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_response(reservation) + data = default_reservation_response() with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): response = PindoraClient.get_reservation(reservation) - assert response["reservation_unit_id"] == reservation.ext_uuid assert response["access_code"] == "13245#" - assert response["access_code_keypad_url"] == "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto" - assert response["access_code_phone_number"] == "+358407089833" - assert response["access_code_sms_number"] == "+358407089834" - assert response["access_code_sms_message"] == "a13245" - assert response["access_code_valid_minutes_before"] == 0 - assert response["access_code_valid_minutes_after"] == 0 - assert response["access_code_generated_at"] == reservation.created_at.astimezone(DEFAULT_TIMEZONE) - assert response["access_code_is_active"] is True - assert response["begin"] == reservation.begin.astimezone(DEFAULT_TIMEZONE) - assert response["end"] == reservation.end.astimezone(DEFAULT_TIMEZONE) @pytest.mark.parametrize( @@ -85,7 +77,7 @@ def test_pindora_client__get_reservation__errors(status_code, exception, error_m def test_pindora_client__get_reservation__missing_key(): reservation = ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_response(reservation) + data = default_reservation_response() data.pop("reservation_unit_id") patch = patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)) @@ -98,7 +90,7 @@ def test_pindora_client__get_reservation__missing_key(): def test_pindora_client__get_reservation__invalid_data(): reservation = ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_response(reservation) + data = default_reservation_response() data["reservation_unit_id"] = str(reservation.id) patch = patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)) @@ -136,7 +128,7 @@ def test_pindora_client__get_reservation__retry__retry_on_500(): def test_pindora_client__get_reservation__retry__succeeds_after_retry(): reservation = ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_response(reservation) + data = default_reservation_response() patch = patch_method( PindoraClient.request, @@ -158,24 +150,13 @@ def test_pindora_client__get_reservation__retry__succeeds_after_retry(): def test_pindora_client__create_reservation(is_active): reservation = ReservationFactory.create(created_at=local_datetime(), reservation_units__name="foo") - data = default_reservation_response(reservation) + data = default_reservation_response() data["access_code_is_active"] = is_active with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): response = PindoraClient.create_reservation(reservation, is_active=is_active) - assert response["reservation_unit_id"] == reservation.ext_uuid assert response["access_code"] == "13245#" - assert response["access_code_keypad_url"] == "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto" - assert response["access_code_phone_number"] == "+358407089833" - assert response["access_code_sms_number"] == "+358407089834" - assert response["access_code_sms_message"] == "a13245" - assert response["access_code_valid_minutes_before"] == 0 - assert response["access_code_valid_minutes_after"] == 0 - assert response["access_code_generated_at"] == reservation.created_at.astimezone(DEFAULT_TIMEZONE) - assert response["access_code_is_active"] is is_active - assert response["begin"] == reservation.begin.astimezone(DEFAULT_TIMEZONE) - assert response["end"] == reservation.end.astimezone(DEFAULT_TIMEZONE) @pytest.mark.parametrize( diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_series.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_series.py index 35eadea10e..2685a9141a 100644 --- a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_series.py +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_series.py @@ -25,41 +25,28 @@ PindoraPermissionError, PindoraUnexpectedResponseError, ) -from utils.date_utils import DEFAULT_TIMEZONE, local_datetime +from utils.date_utils import local_datetime from utils.external_service.errors import ExternalServiceRequestError from tests.factories import RecurringReservationFactory, ReservationFactory from tests.helpers import ResponseMock, exact, patch_method, use_retries - -from .helpers import ErrorParams, default_access_code_modify_response, default_reservation_series_response +from tests.test_integrations.test_keyless_entry.helpers import ( + ErrorParams, + default_access_code_modify_response, + default_reservation_series_response, +) def test_pindora_client__get_reservation_series(): series = RecurringReservationFactory.build() - reservation = ReservationFactory.build(created_at=local_datetime()) + ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_series_response(reservation) + data = default_reservation_series_response() with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): response = PindoraClient.get_reservation_series(series) - assert response["reservation_unit_id"] == reservation.ext_uuid assert response["access_code"] == "13245#" - assert response["access_code_keypad_url"] == "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto" - assert response["access_code_phone_number"] == "+358407089833" - assert response["access_code_sms_number"] == "+358407089834" - assert response["access_code_sms_message"] == "a13245" - assert response["access_code_generated_at"] == reservation.created_at.astimezone(DEFAULT_TIMEZONE) - assert response["access_code_is_active"] is True - - assert response["reservation_unit_code_validity"] == [ - { - "access_code_valid_minutes_before": 0, - "access_code_valid_minutes_after": 0, - "begin": reservation.begin.astimezone(DEFAULT_TIMEZONE), - "end": reservation.end.astimezone(DEFAULT_TIMEZONE), - } - ] @pytest.mark.parametrize( @@ -93,9 +80,9 @@ def test_pindora_client__get_reservation_series__errors(status_code, exception, def test_pindora_client__get_reservation_series__missing_key(): series = RecurringReservationFactory.build() - reservation = ReservationFactory.build(created_at=local_datetime()) + ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_series_response(reservation) + data = default_reservation_series_response() data.pop("reservation_unit_id") patch = patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)) @@ -109,7 +96,7 @@ def test_pindora_client__get_reservation_series__invalid_data(): series = RecurringReservationFactory.build() reservation = ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_series_response(reservation) + data = default_reservation_series_response() data["reservation_unit_id"] = str(reservation.id) patch = patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)) @@ -146,9 +133,9 @@ def test_pindora_client__get_reservation_series__retry_on_500(): @use_retries(attempts=3) def test_pindora_client__get_reservation_series__succeeds_after_retry(): series = RecurringReservationFactory.build() - reservation = ReservationFactory.build(created_at=local_datetime()) + ReservationFactory.build(created_at=local_datetime()) - data = default_reservation_series_response(reservation) + data = default_reservation_series_response() patch = patch_method( PindoraClient.request, @@ -169,7 +156,7 @@ def test_pindora_client__get_reservation_series__succeeds_after_retry(): @pytest.mark.parametrize("is_active", [True, False]) def test_pindora_client__create_reservation_series(is_active: bool): series = RecurringReservationFactory.create() - reservation = ReservationFactory.create( + ReservationFactory.create( recurring_reservation=series, created_at=local_datetime(), state=ReservationStateChoice.CONFIRMED, @@ -177,28 +164,12 @@ def test_pindora_client__create_reservation_series(is_active: bool): access_type=AccessType.ACCESS_CODE, ) - data = default_reservation_series_response(reservation, access_code_is_active=is_active) + data = default_reservation_series_response(access_code_is_active=is_active) with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): response = PindoraClient.create_reservation_series(series, is_active=is_active) - assert response["reservation_unit_id"] == reservation.ext_uuid assert response["access_code"] == "13245#" - assert response["access_code_keypad_url"] == "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto" - assert response["access_code_phone_number"] == "+358407089833" - assert response["access_code_sms_number"] == "+358407089834" - assert response["access_code_sms_message"] == "a13245" - assert response["access_code_generated_at"] == reservation.created_at.astimezone(DEFAULT_TIMEZONE) - assert response["access_code_is_active"] is is_active - - assert response["reservation_unit_code_validity"] == [ - { - "access_code_valid_minutes_before": 0, - "access_code_valid_minutes_after": 0, - "begin": reservation.begin.astimezone(DEFAULT_TIMEZONE), - "end": reservation.end.astimezone(DEFAULT_TIMEZONE), - } - ] @pytest.mark.parametrize( diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_unit.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_unit.py index c972dcfaf2..970694e621 100644 --- a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_unit.py +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_reservation_unit.py @@ -24,21 +24,18 @@ from tests.factories import ReservationUnitFactory from tests.helpers import ResponseMock, exact, patch_method, use_retries - -from .helpers import ErrorParams, default_reservation_unit_response +from tests.test_integrations.test_keyless_entry.helpers import ErrorParams, default_reservation_unit_response def test_pindora_client__get_reservation_unit(): reservation_unit = ReservationUnitFactory.build() - data = default_reservation_unit_response(reservation_unit) + data = default_reservation_unit_response() with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): response = PindoraClient.get_reservation_unit(reservation_unit) - assert response["reservation_unit_id"] == reservation_unit.uuid - assert response["name"] == reservation_unit.name - assert response["keypad_url"] == "https://example.com" + assert response["name"] == "foo" def test_pindora_client__get_reservation_unit__missing_api_key(settings): @@ -89,7 +86,7 @@ def test_pindora_client__get_reservation_unit__retry_on_500(): def test_pindora_client__get_reservation_unit__succeeds_after_retry(): reservation_unit = ReservationUnitFactory.build() - data = default_reservation_unit_response(reservation_unit) + data = default_reservation_unit_response() patch = patch_method( PindoraClient.request, @@ -138,7 +135,7 @@ def test_pindora_client__get_reservation_unit__errors(status_code, exception, er def test_pindora_client__get_reservation_unit__missing_key(): reservation_unit = ReservationUnitFactory.build() - data = default_reservation_unit_response(reservation_unit) + data = default_reservation_unit_response() data.pop("reservation_unit_id") patch = patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)) @@ -151,7 +148,7 @@ def test_pindora_client__get_reservation_unit__missing_key(): def test_pindora_client__get_reservation_unit__invalid_data(): reservation_unit = ReservationUnitFactory.build() - data = default_reservation_unit_response(reservation_unit) + data = default_reservation_unit_response() data["reservation_unit_id"] = str(reservation_unit.id) patch = patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)) diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_seasonal_booking.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_seasonal_booking.py index 5c983def45..d6344be6aa 100644 --- a/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_seasonal_booking.py +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_client/test_seasonal_booking.py @@ -24,7 +24,7 @@ PindoraPermissionError, PindoraUnexpectedResponseError, ) -from utils.date_utils import DEFAULT_TIMEZONE, local_datetime +from utils.date_utils import local_datetime from utils.external_service.errors import ExternalServiceRequestError from tests.factories import ( @@ -35,36 +35,23 @@ ReservationUnitOptionFactory, ) from tests.helpers import ResponseMock, exact, patch_method, use_retries - -from .helpers import ErrorParams, default_access_code_modify_response, default_seasonal_booking_response +from tests.test_integrations.test_keyless_entry.helpers import ( + ErrorParams, + default_access_code_modify_response, + default_seasonal_booking_response, +) def test_pindora_client__get_seasonal_booking(): application_section = ApplicationSectionFactory.build() - reservation = ReservationFactory.build(created_at=local_datetime()) + ReservationFactory.build(created_at=local_datetime()) - data = default_seasonal_booking_response(reservation) + data = default_seasonal_booking_response() with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): response = PindoraClient.get_seasonal_booking(application_section) assert response["access_code"] == "13245#" - assert response["access_code_keypad_url"] == "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto" - assert response["access_code_phone_number"] == "+358407089833" - assert response["access_code_sms_number"] == "+358407089834" - assert response["access_code_sms_message"] == "a13245" - assert response["access_code_generated_at"] == reservation.created_at.astimezone(DEFAULT_TIMEZONE) - assert response["access_code_is_active"] is True - - assert response["reservation_unit_code_validity"] == [ - { - "reservation_unit_id": reservation.ext_uuid, - "access_code_valid_minutes_before": 0, - "access_code_valid_minutes_after": 0, - "begin": reservation.begin.astimezone(DEFAULT_TIMEZONE), - "end": reservation.end.astimezone(DEFAULT_TIMEZONE), - } - ] @pytest.mark.parametrize( @@ -123,9 +110,9 @@ def test_pindora_client__get_seasonal_booking__retry_on_500(): @use_retries(attempts=3) def test_pindora_client__get_seasonal_booking__succeeds_after_retry(): application_section = ApplicationSectionFactory.build() - reservation = ReservationFactory.build(created_at=local_datetime()) + ReservationFactory.build(created_at=local_datetime()) - data = default_seasonal_booking_response(reservation) + data = default_seasonal_booking_response() patch = patch_method( PindoraClient.request, @@ -150,7 +137,7 @@ def test_pindora_client__create_seasonal_booking(is_active: bool): allocation = AllocatedTimeSlotFactory.create(reservation_unit_option=reservation_unit_option) recurring_reservation = RecurringReservationFactory.create(allocated_time_slot=allocation) - reservation = ReservationFactory.create( + ReservationFactory.create( recurring_reservation=recurring_reservation, created_at=local_datetime(), user=application_section.application.user, @@ -159,28 +146,12 @@ def test_pindora_client__create_seasonal_booking(is_active: bool): access_type=AccessType.ACCESS_CODE, ) - data = default_seasonal_booking_response(reservation, access_code_is_active=is_active) + data = default_seasonal_booking_response(access_code_is_active=is_active) with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): response = PindoraClient.create_seasonal_booking(application_section, is_active=is_active) assert response["access_code"] == "13245#" - assert response["access_code_keypad_url"] == "https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto" - assert response["access_code_phone_number"] == "+358407089833" - assert response["access_code_sms_number"] == "+358407089834" - assert response["access_code_sms_message"] == "a13245" - assert response["access_code_generated_at"] == reservation.created_at.astimezone(DEFAULT_TIMEZONE) - assert response["access_code_is_active"] is is_active - - assert response["reservation_unit_code_validity"] == [ - { - "reservation_unit_id": reservation.ext_uuid, - "access_code_valid_minutes_before": 0, - "access_code_valid_minutes_after": 0, - "begin": reservation.begin.astimezone(DEFAULT_TIMEZONE), - "end": reservation.end.astimezone(DEFAULT_TIMEZONE), - } - ] @pytest.mark.parametrize( diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/__init__.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_activate_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_activate_access_code.py new file mode 100644 index 0000000000..a0e2444b58 --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_activate_access_code.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +import uuid + +import pytest +from rest_framework.status import HTTP_204_NO_CONTENT + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory, UserFactory +from tests.helpers import ResponseMock, patch_method + +pytestmark = [ + pytest.mark.django_db, +] + + +def test_activate_access_code__reservation(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.activate_access_code(obj=reservation) + + reservation.refresh_from_db() + assert reservation.access_code_is_active is True + + +def test_activate_access_code__reservation__in_series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.activate_access_code(obj=reservation_1) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is True + + +def test_activate_access_code__reservation__in_series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.activate_access_code(obj=reservation_1) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is True + + +def test_activate_access_code__series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.activate_access_code(obj=series) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is True + + +def test_activate_access_code__series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.activate_access_code(obj=series) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is True + + +def test_activate_access_code__seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=False, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.activate_access_code(obj=section) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is True diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_change_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_change_access_code.py new file mode 100644 index 0000000000..3a6fc94997 --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_change_access_code.py @@ -0,0 +1,290 @@ +from __future__ import annotations + +import uuid + +import pytest + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from tilavarauspalvelu.integrations.keyless_entry.typing import PindoraAccessCodeModifyResponse +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory, UserFactory +from tests.helpers import ResponseMock, patch_method +from tests.test_integrations.test_keyless_entry.helpers import default_access_code_modify_response + +pytestmark = [ + pytest.mark.django_db, +] + + +def test_change_access_code__reservation(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.change_access_code(obj=reservation) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation.access_code_is_active is True + + +def test_change_access_code__reservation__in_series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.change_access_code(obj=reservation_1) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_change_access_code__reservation__in_series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.change_access_code(obj=reservation_1) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_change_access_code__series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.change_access_code(obj=series) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_change_access_code__series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.change_access_code(obj=series) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_change_access_code__seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.change_access_code(obj=section) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_create_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_create_access_code.py new file mode 100644 index 0000000000..81cbf7bc1a --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_create_access_code.py @@ -0,0 +1,274 @@ +from __future__ import annotations + +import uuid + +import pytest + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from tilavarauspalvelu.integrations.keyless_entry.typing import PindoraAccessCodeModifyResponse +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory, UserFactory +from tests.helpers import ResponseMock, patch_method +from tests.test_integrations.test_keyless_entry.helpers import ( + default_reservation_response, + default_reservation_series_response, + default_seasonal_booking_response, +) + +pytestmark = [ + pytest.mark.django_db, +] + + +def test_create_access_code__reservation(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_reservation_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.create_access_code(obj=reservation, is_active=True) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation.access_code_is_active is True + + +def test_create_access_code__reservation__in_series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_reservation_series_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.create_access_code(obj=reservation_1, is_active=True) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_create_access_code__reservation__in_series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_seasonal_booking_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.create_access_code(obj=reservation_1, is_active=True) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_create_access_code__series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_reservation_series_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.create_access_code(obj=series, is_active=True) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_create_access_code__series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_seasonal_booking_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.create_access_code(obj=series, is_active=True) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_create_access_code__seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_seasonal_booking_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.create_access_code(obj=section, is_active=True) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_deactivate_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_deactivate_access_code.py new file mode 100644 index 0000000000..612e8b3b6d --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_deactivate_access_code.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +import uuid + +import pytest +from rest_framework.status import HTTP_204_NO_CONTENT + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory, UserFactory +from tests.helpers import ResponseMock, patch_method + +pytestmark = [ + pytest.mark.django_db, +] + + +def test_deactivate_access_code__reservation(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.deactivate_access_code(obj=reservation) + + reservation.refresh_from_db() + assert reservation.access_code_is_active is False + + +def test_deactivate_access_code__reservation__in_series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.deactivate_access_code(obj=reservation_1) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is False + + +def test_deactivate_access_code__reservation__in_series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.deactivate_access_code(obj=reservation_1) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is False + + +def test_deactivate_access_code__series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.deactivate_access_code(obj=series) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is False + + +def test_deactivate_access_code__series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.deactivate_access_code(obj=series) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is False + + +def test_deactivate_access_code__seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.deactivate_access_code(obj=section) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_is_active is False diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_delete_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_delete_access_code.py new file mode 100644 index 0000000000..5e24a25129 --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_delete_access_code.py @@ -0,0 +1,247 @@ +from __future__ import annotations + +import uuid + +import pytest +from rest_framework.status import HTTP_204_NO_CONTENT + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory, UserFactory +from tests.helpers import ResponseMock, patch_method + +pytestmark = [ + pytest.mark.django_db, +] + + +def test_delete_access_code__reservation(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.delete_access_code(obj=reservation) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at is None + assert reservation.access_code_is_active is False + + +def test_delete_access_code__reservation__in_series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.delete_access_code(obj=reservation_1) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at is None + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at is None + assert reservation_2.access_code_is_active is False + + +def test_delete_access_code__reservation__in_series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.delete_access_code(obj=reservation_1) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at is None + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at is None + assert reservation_2.access_code_is_active is False + + +def test_delete_access_code__series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.delete_access_code(obj=series) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at is None + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at is None + assert reservation_2.access_code_is_active is False + + +def test_delete_access_code__series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.delete_access_code(obj=series) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at is None + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at is None + assert reservation_2.access_code_is_active is False + + +def test_delete_access_code__seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(), + access_code_is_active=True, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(status_code=HTTP_204_NO_CONTENT)): + PindoraService.delete_access_code(obj=section) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at is None + assert reservation_1.access_code_is_active is False + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at is None + assert reservation_2.access_code_is_active is False diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_get_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_get_access_code.py new file mode 100644 index 0000000000..1bea623e70 --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_get_access_code.py @@ -0,0 +1,403 @@ +from __future__ import annotations + +import datetime + +import pytest + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from tilavarauspalvelu.typing import ( + PindoraReservationInfoData, + PindoraSectionInfoData, + PindoraSeriesInfoData, + PindoraValidityInfoData, +) +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory +from tests.helpers import ResponseMock, patch_method +from tests.test_integrations.test_keyless_entry.helpers import ( + default_reservation_response, + default_reservation_series_response, + default_seasonal_booking_response, +) + +pytestmark = [ + pytest.mark.django_db, +] + + +def test_get_access_code__reservation(): + reservation = ReservationFactory.create( + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_reservation_response( + begin=reservation.begin.isoformat(), + end=reservation.end.isoformat(), + access_code_valid_minutes_before=10, + access_code_valid_minutes_after=5, + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=reservation) + + assert response == PindoraReservationInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_begins_at=reservation.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation.end + datetime.timedelta(minutes=5), + ) + + +def test_get_access_code__reservation__in_series(): + series = RecurringReservationFactory.create() + reservation = ReservationFactory.create( + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_reservation_series_response( + reservation_unit_id=str(series.reservation_unit.uuid), + reservation_unit_code_validity=[ + { + "begin": reservation.begin.isoformat(), + "end": reservation.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + ], + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=reservation) + + assert response == PindoraReservationInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_begins_at=reservation.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation.end + datetime.timedelta(minutes=5), + ) + + +def test_get_access_code__reservation__in_series__pick_correct_reservation(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + reservation_2 = ReservationFactory.create( + recurring_reservation=series, + begin=local_datetime(2024, 1, 2, 12), + end=local_datetime(2024, 1, 2, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_reservation_series_response( + reservation_unit_id=str(series.reservation_unit.uuid), + reservation_unit_code_validity=[ + { + "begin": reservation_1.begin.isoformat(), + "end": reservation_1.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + { + "begin": reservation_2.begin.isoformat(), + "end": reservation_2.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + ], + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=reservation_1) + + assert response == PindoraReservationInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_begins_at=reservation_1.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation_1.end + datetime.timedelta(minutes=5), + ) + + +def test_get_access_code__reservation__in_series__in_seasonal_booking(): + section = ApplicationSectionFactory.create() + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation = ReservationFactory.create( + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_seasonal_booking_response( + reservation_unit_code_validity=[ + { + "reservation_unit_id": str(series.reservation_unit.uuid), + "begin": reservation.begin.isoformat(), + "end": reservation.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + ], + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=reservation) + + assert response == PindoraReservationInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_begins_at=reservation.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation.end + datetime.timedelta(minutes=5), + ) + + +def test_get_access_code__series(): + series = RecurringReservationFactory.create() + reservation = ReservationFactory.create( + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_reservation_series_response( + reservation_unit_id=str(series.reservation_unit.uuid), + reservation_unit_code_validity=[ + { + "begin": reservation.begin.isoformat(), + "end": reservation.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + ], + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=series) + + assert response == PindoraSeriesInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_validity=[ + PindoraValidityInfoData( + reservation_id=reservation.pk, + reservation_series_id=series.pk, + access_code_begins_at=reservation.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation.end + datetime.timedelta(minutes=5), + ) + ], + ) + + +def test_get_access_code__series__in_seasonal_booking(): + section = ApplicationSectionFactory.create() + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation = ReservationFactory.create( + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_seasonal_booking_response( + reservation_unit_code_validity=[ + { + "reservation_unit_id": str(series.reservation_unit.uuid), + "begin": reservation.begin.isoformat(), + "end": reservation.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + ], + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=series) + + assert response == PindoraSeriesInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_validity=[ + PindoraValidityInfoData( + reservation_id=reservation.pk, + reservation_series_id=series.pk, + access_code_begins_at=reservation.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation.end + datetime.timedelta(minutes=5), + ) + ], + ) + + +def test_get_access_code__series__in_seasonal_booking__remove_other_series_values(): + section = ApplicationSectionFactory.create() + + series_1 = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + recurring_reservation=series_1, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + series_2 = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_2 = ReservationFactory.create( + recurring_reservation=series_2, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_seasonal_booking_response( + reservation_unit_code_validity=[ + { + "reservation_unit_id": str(series_1.reservation_unit.uuid), + "begin": reservation_1.begin.isoformat(), + "end": reservation_1.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + { + "reservation_unit_id": str(series_2.reservation_unit.uuid), + "begin": reservation_2.begin.isoformat(), + "end": reservation_2.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + ], + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=series_1) + + assert response == PindoraSeriesInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_validity=[ + PindoraValidityInfoData( + reservation_id=reservation_1.pk, + reservation_series_id=series_1.pk, + access_code_begins_at=reservation_1.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation_1.end + datetime.timedelta(minutes=5), + ) + ], + ) + + +def test_get_access_code__seasonal_booking(): + section = ApplicationSectionFactory.create() + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation = ReservationFactory.create( + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + ) + + data = default_seasonal_booking_response( + reservation_unit_code_validity=[ + { + "reservation_unit_id": str(series.reservation_unit.uuid), + "begin": reservation.begin.isoformat(), + "end": reservation.end.isoformat(), + "access_code_valid_minutes_before": 10, + "access_code_valid_minutes_after": 5, + }, + ], + ) + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.get_access_code(obj=section) + + assert response == PindoraSectionInfoData( + access_code="13245#", + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + access_code_keypad_url="https://keypad.test.ovaa.fi/hel/list/kannelmaen_leikkipuisto", + access_code_phone_number="+358407089833", + access_code_sms_number="+358407089834", + access_code_sms_message="a13245", + access_code_validity=[ + PindoraValidityInfoData( + reservation_id=reservation.pk, + reservation_series_id=series.pk, + access_code_begins_at=reservation.begin - datetime.timedelta(minutes=10), + access_code_ends_at=reservation.end + datetime.timedelta(minutes=5), + ) + ], + ) diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_reschedule_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_reschedule_access_code.py new file mode 100644 index 0000000000..711e505c95 --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_reschedule_access_code.py @@ -0,0 +1,290 @@ +from __future__ import annotations + +import uuid + +import pytest + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from tilavarauspalvelu.integrations.keyless_entry.typing import PindoraAccessCodeModifyResponse +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory, UserFactory +from tests.helpers import ResponseMock, patch_method +from tests.test_integrations.test_keyless_entry.helpers import default_access_code_modify_response + +pytestmark = [ + pytest.mark.django_db, +] + + +def test_reschedule_access_code__reservation(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.reschedule_access_code(obj=reservation) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation.access_code_is_active is True + + +def test_reschedule_access_code__reservation__in_series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.reschedule_access_code(obj=reservation_1) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_reschedule_access_code__reservation__in_series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.reschedule_access_code(obj=reservation_1) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_reschedule_access_code__series(): + series = RecurringReservationFactory.create() + reservation_1 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.reschedule_access_code(obj=series) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_reschedule_access_code__series__in_seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.reschedule_access_code(obj=series) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True + + +def test_reschedule_access_code__seasonal_booking(): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation_1 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + reservation_2 = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 14), + end=local_datetime(2024, 1, 1, 15), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=None, + access_code_is_active=False, + ) + + data = default_access_code_modify_response() + + with patch_method(PindoraClient.request, return_value=ResponseMock(json_data=data)): + response = PindoraService.reschedule_access_code(obj=section) + + assert response == PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + reservation_1.refresh_from_db() + assert reservation_1.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_1.access_code_is_active is True + + reservation_2.refresh_from_db() + assert reservation_2.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation_2.access_code_is_active is True diff --git a/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_sync_access_code.py b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_sync_access_code.py new file mode 100644 index 0000000000..7e83fe18e4 --- /dev/null +++ b/backend/tests/test_integrations/test_keyless_entry/test_pindora_service/test_sync_access_code.py @@ -0,0 +1,501 @@ +from __future__ import annotations + +import uuid +from typing import Any + +import pytest + +from tilavarauspalvelu.enums import AccessType, ReservationStateChoice, ReservationTypeChoice +from tilavarauspalvelu.integrations.keyless_entry import PindoraClient, PindoraService +from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraConflictError, PindoraNotFoundError +from tilavarauspalvelu.integrations.keyless_entry.typing import PindoraAccessCodeModifyResponse +from utils.date_utils import local_datetime + +from tests.factories import ApplicationSectionFactory, RecurringReservationFactory, ReservationFactory, UserFactory +from tests.helpers import patch_method + +pytestmark = [ + pytest.mark.django_db, +] + + +def access_code_response(**kwargs: Any) -> PindoraAccessCodeModifyResponse: + return PindoraAccessCodeModifyResponse( + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=kwargs["is_active"], + ) + + +@patch_method(PindoraService.delete_access_code) +@patch_method(PindoraClient.create_reservation, side_effect=access_code_response) +@patch_method(PindoraClient.reschedule_reservation, side_effect=access_code_response) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__generated(is_active): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation.access_code_is_active is is_active + + assert PindoraService.delete_access_code.call_count == 0 + assert PindoraClient.create_reservation.call_count == 0 + assert PindoraClient.reschedule_reservation.call_count == 1 + + +@patch_method(PindoraService.delete_access_code) +@patch_method(PindoraClient.create_reservation, side_effect=access_code_response) +@patch_method(PindoraClient.reschedule_reservation, side_effect=PindoraNotFoundError("Not found")) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__generated__doesnt_exist(is_active): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation.access_code_is_active is is_active + + assert PindoraService.delete_access_code.call_count == 0 + assert PindoraClient.create_reservation.call_count == 1 + assert PindoraClient.reschedule_reservation.call_count == 1 + + +@patch_method(PindoraService.delete_access_code) +@patch_method(PindoraClient.create_reservation, side_effect=access_code_response) +@patch_method(PindoraClient.reschedule_reservation, side_effect=access_code_response) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__not_generated(is_active): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=None, + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation.access_code_is_active is is_active + + assert PindoraService.delete_access_code.call_count == 0 + assert PindoraClient.create_reservation.call_count == 1 + assert PindoraClient.reschedule_reservation.call_count == 0 + + +@patch_method(PindoraService.delete_access_code) +@patch_method(PindoraClient.create_reservation, side_effect=PindoraConflictError("Conflict")) +@patch_method(PindoraClient.reschedule_reservation, side_effect=access_code_response) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__not_generated__already_exists(is_active): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=None, + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + reservation.refresh_from_db() + assert reservation.access_code_generated_at == local_datetime(2022, 1, 1) + assert reservation.access_code_is_active is is_active + + assert PindoraService.delete_access_code.call_count == 0 + assert PindoraClient.create_reservation.call_count == 1 + assert PindoraClient.reschedule_reservation.call_count == 1 + + +@patch_method(PindoraService.delete_access_code) +@patch_method(PindoraClient.create_reservation, side_effect=access_code_response) +@patch_method(PindoraClient.reschedule_reservation, side_effect=access_code_response) +def test_sync_access_code__reservation__not_access_code(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.UNRESTRICTED, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + PindoraService.sync_access_code(obj=reservation) + + assert PindoraService.delete_access_code.call_count == 1 + assert PindoraClient.create_reservation.call_count == 0 + assert PindoraClient.reschedule_reservation.call_count == 0 + + +@patch_method(PindoraService.delete_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraClient.create_reservation, side_effect=access_code_response) +@patch_method(PindoraClient.reschedule_reservation, side_effect=access_code_response) +def test_sync_access_code__reservation__not_access_code__not_found(): + reservation = ReservationFactory.create( + reservation_units__uuid=uuid.uuid4(), + recurring_reservation=None, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.UNRESTRICTED, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=True, + ) + + PindoraService.sync_access_code(obj=reservation) + + assert PindoraService.delete_access_code.call_count == 1 + assert PindoraClient.create_reservation.call_count == 0 + assert PindoraClient.reschedule_reservation.call_count == 0 + + +@patch_method(PindoraService.activate_access_code) +@patch_method(PindoraService.deactivate_access_code) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__in_series(is_active): + series = RecurringReservationFactory.create() + reservation = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 0 + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__in_series__not_found(is_active): + series = RecurringReservationFactory.create() + reservation = ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 1 + assert PindoraService.create_access_code.call_args.kwargs["is_active"] is is_active + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code) +@patch_method(PindoraService.deactivate_access_code) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__in_series__in_seasonal_booking(is_active): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 0 + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__reservation__in_series__in_seasonal_booking__not_found(is_active): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + reservation = ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=reservation) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 1 + assert PindoraService.create_access_code.call_args.kwargs["is_active"] is is_active + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code) +@patch_method(PindoraService.deactivate_access_code) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__series(is_active): + series = RecurringReservationFactory.create() + ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=series) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 0 + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__series__not_found(is_active): + series = RecurringReservationFactory.create() + ReservationFactory.create( + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=series) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 1 + assert PindoraService.create_access_code.call_args.kwargs["is_active"] is is_active + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code) +@patch_method(PindoraService.deactivate_access_code) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__series__in_seasonal_booking(is_active): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=series) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 0 + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__series__in_seasonal_booking__not_found(is_active): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=series) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 1 + assert PindoraService.create_access_code.call_args.kwargs["is_active"] is is_active + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code) +@patch_method(PindoraService.deactivate_access_code) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__seasonal_booking(is_active): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=section) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 0 + assert PindoraService.reschedule_access_code.call_count == 1 + + +@patch_method(PindoraService.activate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.deactivate_access_code, side_effect=PindoraNotFoundError("Not found")) +@patch_method(PindoraService.create_access_code) +@patch_method(PindoraService.reschedule_access_code) +@pytest.mark.parametrize("is_active", [True, False]) +def test_sync_access_code__seasonal_booking__not_found(is_active): + user = UserFactory.create() + section = ApplicationSectionFactory.create( + application__user=user, + ) + series = RecurringReservationFactory.create( + allocated_time_slot__reservation_unit_option__application_section=section, + ) + ReservationFactory.create( + user=user, + reservation_units=[series.reservation_unit], + recurring_reservation=series, + begin=local_datetime(2024, 1, 1, 12), + end=local_datetime(2024, 1, 1, 13), + access_type=AccessType.ACCESS_CODE, + state=ReservationStateChoice.CONFIRMED, + type=ReservationTypeChoice.NORMAL if is_active else ReservationTypeChoice.BLOCKED, + access_code_generated_at=local_datetime(2022, 1, 1), + access_code_is_active=False, + ) + + PindoraService.sync_access_code(obj=section) + + assert PindoraService.activate_access_code.call_count == (1 if is_active else 0) + assert PindoraService.deactivate_access_code.call_count == (0 if is_active else 1) + assert PindoraService.create_access_code.call_count == 1 + assert PindoraService.create_access_code.call_args.kwargs["is_active"] is is_active + assert PindoraService.reschedule_access_code.call_count == 1 diff --git a/backend/tilavarauspalvelu/admin/application_section/form.py b/backend/tilavarauspalvelu/admin/application_section/form.py index fd14670739..682ac3bd78 100644 --- a/backend/tilavarauspalvelu/admin/application_section/form.py +++ b/backend/tilavarauspalvelu/admin/application_section/form.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _ from tilavarauspalvelu.enums import ApplicationSectionStatusChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import ( Application, ApplicationSection, @@ -15,6 +15,7 @@ ReservationUnitOption, SuitableTimeRange, ) +from utils.external_service.errors import ExternalServiceError from utils.fields.forms import disabled_widget @@ -124,24 +125,6 @@ class ApplicationSectionAdminForm(forms.ModelForm): help_text=_("Response from Pindora API"), ) - def __init__(self, *args: Any, **kwargs: Any) -> None: - instance: ApplicationSection | None = kwargs.get("instance") - if instance: - kwargs.setdefault("initial", {}) - kwargs["initial"]["status"] = ApplicationSectionStatusChoice(instance.status).label - - self.base_fields["application"].queryset = Application.objects.select_related("user") - - super().__init__(*args, **kwargs) - - if getattr(self.instance, "pk", None) and self.instance.should_have_active_access_code: - pindora_field = self.fields["pindora_response"] - pindora_field.widget.attrs.update({"cols": "100", "rows": "20"}) - - response = PindoraClient.get_seasonal_booking(section=self.instance) - - pindora_field.initial = json.dumps(response, default=str, indent=2) - class Meta: model = ApplicationSection fields = [] # Use fields from ModelAdmin @@ -174,3 +157,28 @@ class Meta: "purpose": _("Purpose for this section."), "should_have_active_access_code": _("Should this application section have an active access code?"), } + + def __init__(self, *args: Any, **kwargs: Any) -> None: + instance: ApplicationSection | None = kwargs.get("instance") + if instance: + kwargs.setdefault("initial", {}) + kwargs["initial"]["status"] = ApplicationSectionStatusChoice(instance.status).label + + self.base_fields["application"].queryset = Application.objects.select_related("user") + + super().__init__(*args, **kwargs) + + editing = getattr(self.instance, "pk", None) is not None + + if editing and self.instance.should_have_active_access_code: + pindora_field = self.fields["pindora_response"] + pindora_field.widget.attrs.update({"cols": "100", "rows": "20"}) + pindora_field.initial = self.get_pindora_response() + + def get_pindora_response(self) -> str | None: + try: + response = PindoraService.get_access_code(obj=self.instance) + except ExternalServiceError as error: + return str(error) + + return json.dumps(response, default=str, indent=2) diff --git a/backend/tilavarauspalvelu/admin/recurring_reservation/form.py b/backend/tilavarauspalvelu/admin/recurring_reservation/form.py index 589a3bbe46..25c9b8434a 100644 --- a/backend/tilavarauspalvelu/admin/recurring_reservation/form.py +++ b/backend/tilavarauspalvelu/admin/recurring_reservation/form.py @@ -6,8 +6,9 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import RecurringReservation +from utils.external_service.errors import ExternalServiceError class ReservationSeriesAdminForm(forms.ModelForm): @@ -76,24 +77,17 @@ class Meta: def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - if getattr(self.instance, "pk", None) and self.instance.should_have_active_access_code: + editing = getattr(self.instance, "pk", None) is not None + + if editing and self.instance.should_have_active_access_code: pindora_field = self.fields["pindora_response"] pindora_field.widget.attrs.update({"cols": "100", "rows": "20"}) + pindora_field.initial = self.get_pindora_response() - if self.instance.allocated_time_slot is None: - response = PindoraClient.get_reservation_series(series=self.instance) - - pindora_field.initial = json.dumps(response, default=str, indent=2) - - else: - section = self.instance.allocated_time_slot.reservation_unit_option.application_section - response = PindoraClient.get_seasonal_booking(section=section) - - # Only show validity for this series's reservation unit (might not match one-to-one with the series) - response["reservation_unit_code_validity"] = [ - item - for item in response["reservation_unit_code_validity"] - if item["reservation_unit_id"] == self.instance.reservation_unit.uuid - ] + def get_pindora_response(self) -> str | None: + try: + response = PindoraService.get_access_code(obj=self.instance) + except ExternalServiceError as error: + return str(error) - pindora_field.initial = json.dumps(response, default=str, indent=2) + return json.dumps(response, default=str, indent=2) diff --git a/backend/tilavarauspalvelu/admin/reservation/form.py b/backend/tilavarauspalvelu/admin/reservation/form.py index a27fe511e3..822281cce1 100644 --- a/backend/tilavarauspalvelu/admin/reservation/form.py +++ b/backend/tilavarauspalvelu/admin/reservation/form.py @@ -7,10 +7,9 @@ from django.utils.translation import gettext_lazy as _ from tilavarauspalvelu.enums import AccessType -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient -from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraClientError +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import Reservation -from utils.date_utils import DEFAULT_TIMEZONE +from utils.external_service.errors import ExternalServiceError class ReservationAdminForm(forms.ModelForm): @@ -160,50 +159,17 @@ class Meta: def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - if getattr(self.instance, "pk", None) and self.instance.access_type == AccessType.ACCESS_CODE: + editing = getattr(self.instance, "pk", None) is not None + + if editing and self.instance.access_type == AccessType.ACCESS_CODE: pindora_field = self.fields["pindora_response"] pindora_field.widget.attrs.update({"cols": "100", "rows": "20"}) pindora_field.initial = self.get_pindora_response() def get_pindora_response(self) -> str | None: - if self.instance.recurring_reservation is None: - try: - response = PindoraClient.get_reservation(reservation=self.instance) - except PindoraClientError as error: - return str(error.msg) - return json.dumps(response, default=str, indent=2) - - if self.instance.recurring_reservation.allocated_time_slot is None: - try: - response = PindoraClient.get_reservation_series(series=self.instance.recurring_reservation) - except PindoraClientError as error: - return str(error.msg) - - # Only show the validity for this reservation - response["reservation_unit_code_validity"] = [ - item - for item in response["reservation_unit_code_validity"] - if item["begin"] == self.instance.begin.astimezone(DEFAULT_TIMEZONE) - and item["end"] == self.instance.end.astimezone(DEFAULT_TIMEZONE) - ] - - return json.dumps(response, default=str, indent=2) - - allocation = self.instance.recurring_reservation.allocated_time_slot - section = allocation.reservation_unit_option.application_section - try: - response = PindoraClient.get_seasonal_booking(section=section) - except PindoraClientError as error: - return str(error.msg) - - # Only show the validity for this reservation - response["reservation_unit_code_validity"] = [ - item - for item in response["reservation_unit_code_validity"] - if item["begin"] == self.instance.begin.astimezone(DEFAULT_TIMEZONE) - and item["end"] == self.instance.end.astimezone(DEFAULT_TIMEZONE) - and item["reservation_unit_id"] == self.instance.recurring_reservation.reservation_unit.uuid - ] + response = PindoraService.get_access_code(obj=self.instance) + except ExternalServiceError as error: + return str(error) return json.dumps(response, default=str, indent=2) diff --git a/backend/tilavarauspalvelu/admin/reservation_unit/form.py b/backend/tilavarauspalvelu/admin/reservation_unit/form.py index ac7e3b097c..6ad29fba8c 100644 --- a/backend/tilavarauspalvelu/admin/reservation_unit/form.py +++ b/backend/tilavarauspalvelu/admin/reservation_unit/form.py @@ -1,7 +1,8 @@ from __future__ import annotations +import datetime import json -from typing import TYPE_CHECKING, Any +from typing import Any from django import forms from django.core.exceptions import ValidationError @@ -20,9 +21,6 @@ from utils.external_service.errors import ExternalServiceError from utils.utils import only_django_validation_errors -if TYPE_CHECKING: - import datetime - class ReservationUnitAccessTypeForm(forms.ModelForm): instance: ReservationUnitAccessType @@ -84,7 +82,7 @@ class ReservationUnitAccessTypeFormSet(BaseInlineFormSet): def clean(self) -> None: today = local_date() - only_begun = (True for form in self.forms if form.cleaned_data["begin_date"] <= today) + only_begun = (True for form in self.forms if form.cleaned_data.get("begin_date", datetime.date.max) <= today) has_active = next(only_begun, None) is not None if not has_active: msg = "At least one active access type is required." diff --git a/backend/tilavarauspalvelu/api/graphql/types/application_section/types.py b/backend/tilavarauspalvelu/api/graphql/types/application_section/types.py index b4747eb64a..6cf87ebf17 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/application_section/types.py +++ b/backend/tilavarauspalvelu/api/graphql/types/application_section/types.py @@ -11,9 +11,8 @@ from query_optimizer.optimizer import QueryOptimizer from tilavarauspalvelu.enums import ApplicationSectionStatusChoice, UserRoleChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient -from tilavarauspalvelu.models import Application, ApplicationSection, RecurringReservation, Reservation, User -from tilavarauspalvelu.typing import PindoraSectionInfoData +from tilavarauspalvelu.integrations.keyless_entry import PindoraService +from tilavarauspalvelu.models import Application, ApplicationSection, Reservation, User from utils.date_utils import local_date from .filtersets import ApplicationSectionFilterSet @@ -22,7 +21,7 @@ if TYPE_CHECKING: from tilavarauspalvelu.models.application.queryset import ApplicationQuerySet from tilavarauspalvelu.models.application_section.queryset import ApplicationSectionQuerySet - from tilavarauspalvelu.typing import GQLInfo, PindoraValidityInfoData + from tilavarauspalvelu.typing import GQLInfo, PindoraSectionInfoData __all__ = [ "ApplicationSectionNode", @@ -174,29 +173,12 @@ def resolve_pindora_info(root: ApplicationSection, info: GQLInfo) -> PindoraSect return None try: - response = PindoraClient.get_seasonal_booking(section=root.ext_uuid) + response = PindoraService.get_access_code(obj=root) except Exception: # noqa: BLE001 return None # Don't show Pindora info without permissions if the access code is not active - access_code_is_active = response["access_code_is_active"] - if not has_perms and not access_code_is_active: + if not has_perms and not response.access_code_is_active: return None - qs = RecurringReservation.objects.filter(allocated_time_slot__reservation_unit_option__application_section=root) - - access_code_validity: list[PindoraValidityInfoData] = [] - for series in qs: - validity = series.actions.get_access_code_validity_info(response["reservation_unit_code_validity"]) - access_code_validity.extend(validity) - - return PindoraSectionInfoData( - access_code=response["access_code"], - access_code_generated_at=response["access_code_generated_at"], - access_code_is_active=response["access_code_is_active"], - access_code_keypad_url=response["access_code_keypad_url"], - access_code_phone_number=response["access_code_phone_number"], - access_code_sms_number=response["access_code_sms_number"], - access_code_sms_message=response["access_code_sms_message"], - access_code_validity=access_code_validity, - ) + return response diff --git a/backend/tilavarauspalvelu/api/graphql/types/recurring_reservation/types.py b/backend/tilavarauspalvelu/api/graphql/types/recurring_reservation/types.py index 57b5f60108..1dcb68f721 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/recurring_reservation/types.py +++ b/backend/tilavarauspalvelu/api/graphql/types/recurring_reservation/types.py @@ -9,9 +9,8 @@ from query_optimizer import AnnotatedField, MultiField from tilavarauspalvelu.enums import AccessType -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import RecurringReservation -from tilavarauspalvelu.typing import PindoraSeriesInfoData from utils.date_utils import local_date from .filtersets import RecurringReservationFilterSet @@ -20,7 +19,7 @@ if TYPE_CHECKING: from django.db import models - from tilavarauspalvelu.typing import GQLInfo + from tilavarauspalvelu.typing import GQLInfo, PindoraSeriesInfoData __all__ = [ "RecurringReservationNode", @@ -127,58 +126,20 @@ def resolve_pindora_info(root: RecurringReservation, info: GQLInfo) -> PindoraSe has_perms = info.context.user.permissions.can_view_recurring_reservation(root, reserver_needs_role=True) if root.allocated_time_slot is not None: - return RecurringReservationNode.section_pindora_info(root, has_perms=has_perms) + section = root.allocated_time_slot.reservation_unit_option.application_section + application_round = section.application.application_round - try: - response = PindoraClient.get_reservation_series(series=root.ext_uuid) - except Exception: # noqa: BLE001 - return None - - # Don't allow reserver to view Pindora info without view permissions if the access code is not active - access_code_is_active = response["access_code_is_active"] - if not has_perms and not access_code_is_active: - return None - - access_code_validity = root.actions.get_access_code_validity_info(response["reservation_unit_code_validity"]) - - return PindoraSeriesInfoData( - access_code=response["access_code"], - access_code_generated_at=response["access_code_generated_at"], - access_code_is_active=response["access_code_is_active"], - access_code_keypad_url=response["access_code_keypad_url"], - access_code_phone_number=response["access_code_phone_number"], - access_code_sms_number=response["access_code_sms_number"], - access_code_sms_message=response["access_code_sms_message"], - access_code_validity=access_code_validity, - ) - - @staticmethod - def section_pindora_info(series: RecurringReservation, *, has_perms: bool) -> PindoraSeriesInfoData | None: - section = series.allocated_time_slot.reservation_unit_option.application_section - - # Don't show Pindora info without permissions if the application round results haven't been sent yet - if not has_perms and section.application.application_round.sent_date is None: - return None + # Don't show Pindora info without permissions if the application round results haven't been sent yet + if not has_perms and application_round.sent_date is None: + return None try: - response = PindoraClient.get_seasonal_booking(section=section.ext_uuid) + response = PindoraService.get_access_code(obj=root) except Exception: # noqa: BLE001 return None - # Don't allow reserver to view Pindora info without permissions if the access code is not active - access_code_is_active = response["access_code_is_active"] - if not has_perms and not access_code_is_active: + # Don't allow reserver to view Pindora info without view permissions if the access code is not active + if not has_perms and not response.access_code_is_active: return None - access_code_validity = series.actions.get_access_code_validity_info(response["reservation_unit_code_validity"]) - - return PindoraSeriesInfoData( - access_code=response["access_code"], - access_code_generated_at=response["access_code_generated_at"], - access_code_is_active=access_code_is_active, - access_code_keypad_url=response["access_code_keypad_url"], - access_code_phone_number=response["access_code_phone_number"], - access_code_sms_number=response["access_code_sms_number"], - access_code_sms_message=response["access_code_sms_message"], - access_code_validity=access_code_validity, - ) + return response diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/mutations.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/mutations.py index 02031ba5a7..43cf322885 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/mutations.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/mutations.py @@ -9,7 +9,7 @@ from tilavarauspalvelu.api.graphql.types.merchants.types import PaymentOrderNode from tilavarauspalvelu.enums import AccessType, OrderStatus, ReservationStateChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraNotFoundError from tilavarauspalvelu.integrations.verkkokauppa.order.exceptions import CancelOrderError from tilavarauspalvelu.models import Reservation @@ -159,7 +159,7 @@ def validate_deletion(cls, reservation: Reservation, user: AnyUser) -> None: # Try Pindora delete, but if it fails, retry in background if reservation.access_type == AccessType.ACCESS_CODE: try: - PindoraClient.delete_reservation(reservation=reservation) + PindoraService.delete_access_code(obj=reservation) except PindoraNotFoundError: pass except Exception: # noqa: BLE001 diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/adjust_time_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/adjust_time_serializers.py index 443b30bd28..c6e0c07f23 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/adjust_time_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/adjust_time_serializers.py @@ -9,6 +9,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import Reservation from utils.date_utils import DEFAULT_TIMEZONE @@ -83,12 +84,13 @@ def validate(self, data: ReservationAdjustTimeData) -> ReservationAdjustTimeData return data def update(self, instance: Reservation, validated_data: ReservationAdjustTimeData) -> Reservation: - access_type_before = instance.access_type + was_access_code = instance.access_type == AccessType.ACCESS_CODE with transaction.atomic(): instance = super().update(instance=instance, validated_data=validated_data) - instance.actions.create_or_update_reservation_access_code_if_required(from_access_type=access_type_before) + if was_access_code or instance.access_type == AccessType.ACCESS_CODE: + PindoraService.sync_access_code(obj=instance) EmailService.send_reservation_modified_email(reservation=instance) diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/approve_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/approve_serializers.py index d849acab16..4d54596471 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/approve_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/approve_serializers.py @@ -9,7 +9,8 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService +from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraNotFoundError from tilavarauspalvelu.models import Reservation from utils.date_utils import local_datetime from utils.external_service.errors import ExternalServiceError @@ -57,21 +58,17 @@ def validate(self, data: ReservationApproveData) -> ReservationApproveData: return data def update(self, instance: Reservation, validated_data: ReservationApproveData) -> Reservation: - if self.instance.access_type == AccessType.ACCESS_CODE and instance.recurring_reservation is None: + instance = super().update(instance=instance, validated_data=validated_data) + + if instance.access_type == AccessType.ACCESS_CODE: # Allow activation in Pindora to fail, will be handled by a background task. with suppress(ExternalServiceError): - # If access code has not been generated (e.g. returned to handling after a deny and then approved), - # create a new active access code in Pindora. - if instance.access_code_generated_at is None: - response = PindoraClient.create_reservation(reservation=instance, is_active=True) - validated_data["access_code_generated_at"] = response["access_code_generated_at"] - validated_data["access_code_is_active"] = response["access_code_is_active"] - - else: - PindoraClient.activate_reservation_access_code(reservation=instance) - validated_data["access_code_is_active"] = True - - instance = super().update(instance=instance, validated_data=validated_data) + try: + PindoraService.activate_access_code(instance) + except PindoraNotFoundError: + # If access code has not been generated (e.g. returned to handling after a deny and then approved), + # create a new active access code in Pindora. + PindoraService.create_access_code(instance, is_active=True) EmailService.send_reservation_approved_email(reservation=instance) EmailService.send_staff_notification_reservation_made_email(reservation=instance) diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/cancellation_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/cancellation_serializers.py index b552f03cc0..e37813b8ab 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/cancellation_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/cancellation_serializers.py @@ -3,13 +3,14 @@ from contextlib import suppress from typing import TYPE_CHECKING +from django.db import transaction from graphene_django_extensions import NestingModelSerializer from graphene_django_extensions.fields import EnumFriendlyChoiceField, IntegerPrimaryKeyField from rest_framework.fields import CharField, IntegerField from tilavarauspalvelu.enums import AccessType, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraNotFoundError from tilavarauspalvelu.models import Reservation, ReservationCancelReason from tilavarauspalvelu.tasks import refund_paid_reservation_task @@ -66,13 +67,12 @@ def validate(self, data: ReservationCancellationData) -> ReservationCancellation return data def update(self, instance: Reservation, validated_data: ReservationCancellationData) -> Reservation: - if instance.access_type == AccessType.ACCESS_CODE and instance.recurring_reservation is None: - with suppress(PindoraNotFoundError): - PindoraClient.delete_reservation(reservation=instance) - validated_data["access_code_generated_at"] = None - validated_data["access_code_is_active"] = False + with transaction.atomic(): + instance = super().update(instance=instance, validated_data=validated_data) - instance = super().update(instance=instance, validated_data=validated_data) + if instance.access_type == AccessType.ACCESS_CODE: + with suppress(PindoraNotFoundError): + PindoraService.delete_access_code(obj=instance) if instance.actions.is_refundable and instance.price_net > 0: refund_paid_reservation_task.delay(instance.pk) diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/confirm_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/confirm_serializers.py index ee74c48a71..95ca1f1c84 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/confirm_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/confirm_serializers.py @@ -12,7 +12,7 @@ from tilavarauspalvelu.api.graphql.extensions import error_codes from tilavarauspalvelu.enums import AccessType, OrderStatus, PaymentType, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.sentry import SentryLogger from tilavarauspalvelu.integrations.verkkokauppa.helpers import ( create_mock_verkkokauppa_order, @@ -88,12 +88,10 @@ def update(self, instance: Reservation, validated_data: ReservationConfirmData) instance = super().update(instance=instance, validated_data=validated_data) if instance.state == ReservationStateChoice.CONFIRMED: - if self.instance.access_type == AccessType.ACCESS_CODE and instance.recurring_reservation is None: + if instance.access_type == AccessType.ACCESS_CODE: # Allow activation in Pindora to fail, will be handled by a background task. with suppress(ExternalServiceError): - PindoraClient.activate_reservation_access_code(reservation=instance) - instance.access_code_is_active = True - instance.save(update_fields=["access_code_is_active"]) + PindoraService.activate_access_code(obj=instance) EmailService.send_reservation_confirmed_email(reservation=instance) EmailService.send_staff_notification_reservation_made_email(reservation=instance) diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/create_serializer.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/create_serializer.py index a2b48f32c9..4691c57a90 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/create_serializer.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/create_serializer.py @@ -12,7 +12,7 @@ from tilavarauspalvelu.api.graphql.extensions import error_codes from tilavarauspalvelu.enums import AccessType from tilavarauspalvelu.integrations.helsinki_profile.clients import HelsinkiProfileClient -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.sentry import SentryLogger from tilavarauspalvelu.models import Reservation, ReservationUnit from utils.date_utils import DEFAULT_TIMEZONE @@ -127,10 +127,6 @@ def create(self, validated_data: ReservationCreateData) -> Reservation: # Pindora request must succeed, or the reservation is not created. if reservation.access_type == AccessType.ACCESS_CODE: - response = PindoraClient.create_reservation(reservation=reservation) - - reservation.access_code_generated_at = response["access_code_generated_at"] - reservation.access_code_is_active = response["access_code_is_active"] - reservation.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + PindoraService.create_access_code(obj=reservation) return reservation diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/deny_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/deny_serializers.py index 63e3335518..a72f4c5e62 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/deny_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/deny_serializers.py @@ -3,13 +3,14 @@ from contextlib import suppress from typing import TYPE_CHECKING +from django.db import transaction from graphene_django_extensions import NestingModelSerializer from graphene_django_extensions.fields import EnumFriendlyChoiceField, IntegerPrimaryKeyField from rest_framework.fields import CharField, IntegerField from tilavarauspalvelu.enums import AccessType, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraNotFoundError from tilavarauspalvelu.models import Reservation, ReservationDenyReason from utils.date_utils import local_datetime @@ -58,13 +59,12 @@ def validate(self, data: ReservationDenyData) -> ReservationDenyData: return data def update(self, instance: Reservation, validated_data: ReservationDenyData) -> Reservation: - if instance.access_type == AccessType.ACCESS_CODE and instance.recurring_reservation is None: - with suppress(PindoraNotFoundError): - PindoraClient.delete_reservation(reservation=instance) - validated_data["access_code_generated_at"] = None - validated_data["access_code_is_active"] = False + with transaction.atomic(): + instance = super().update(instance=instance, validated_data=validated_data) - instance = super().update(instance=instance, validated_data=validated_data) + if instance.access_type == AccessType.ACCESS_CODE: + with suppress(PindoraNotFoundError): + PindoraService.delete_access_code(obj=instance) EmailService.send_reservation_rejected_email(reservation=instance) return instance diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/requires_handling_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/requires_handling_serializers.py index 9a57b03591..9a6871fd8a 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/requires_handling_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/requires_handling_serializers.py @@ -9,7 +9,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraNotFoundError from tilavarauspalvelu.models import Reservation @@ -50,18 +50,13 @@ def validate(self, data: ReservationHandlingData) -> ReservationHandlingData: return data def update(self, instance: Reservation, validated_data: dict[str, Any]) -> Reservation: + instance = super().update(instance=instance, validated_data=validated_data) + # Denied reservations shouldn't have an access code. It will be regenerated if the reservation is approved. - if ( - self.instance.access_type == AccessType.ACCESS_CODE - and instance.recurring_reservation is None - and instance.access_code_generated_at is not None - ): + if instance.access_type == AccessType.ACCESS_CODE: # Allow reservation modification to succeed if reservation doesn't exist in Pindora. with suppress(PindoraNotFoundError): - PindoraClient.deactivate_reservation_access_code(reservation=instance) - validated_data["access_code_is_active"] = False - - instance = super().update(instance=instance, validated_data=validated_data) + PindoraService.deactivate_access_code(obj=instance) EmailService.send_reservation_requires_handling_email(reservation=instance) return instance diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_adjust_time_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_adjust_time_serializers.py index 7d1d18b6a9..e012930ef2 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_adjust_time_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_adjust_time_serializers.py @@ -9,6 +9,7 @@ from tilavarauspalvelu.enums import AccessType, ReservationStateChoice from tilavarauspalvelu.integrations.email.main import EmailService +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import Reservation from utils.date_utils import DEFAULT_TIMEZONE @@ -67,12 +68,13 @@ def validate(self, data: dict[str, Any]) -> dict[str, Any]: return data def update(self, instance: Reservation, validated_data: dict[str, Any]) -> Reservation: - access_type_before = instance.access_type + was_access_code = instance.access_type == AccessType.ACCESS_CODE with transaction.atomic(): instance = super().update(instance=instance, validated_data=validated_data) - instance.actions.create_or_update_reservation_access_code_if_required(from_access_type=access_type_before) + if was_access_code or instance.access_type == AccessType.ACCESS_CODE: + PindoraService.sync_access_code(obj=instance) EmailService.send_reservation_modified_email(reservation=instance) EmailService.send_staff_notification_reservation_requires_handling_email(reservation=instance) diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_change_access_code_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_change_access_code_serializers.py index fad9f83246..0b8c0db56b 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_change_access_code_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_change_access_code_serializers.py @@ -7,7 +7,7 @@ from rest_framework.fields import IntegerField from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraNotFoundError from tilavarauspalvelu.models import Reservation @@ -45,17 +45,20 @@ def validate(self, data: dict[str, Any]) -> dict[str, Any]: self.instance.validator.validate_not_in_reservation_series() return data - def update(self, instance: Reservation, validated_data: dict[str, Any]) -> Reservation: + def update(self, instance: Reservation, validated_data: dict[str, Any]) -> Reservation: # noqa: ARG002 try: - PindoraClient.change_reservation_access_code(reservation=instance) + PindoraService.change_access_code(obj=instance) + except PindoraNotFoundError: instance.access_code_generated_at = None instance.access_code_is_active = False - else: - if instance.access_code_should_be_active: - with suppress(ExternalServiceError): - PindoraClient.activate_reservation_access_code(reservation=instance) - instance.access_code_is_active = True - EmailService.send_reservation_modified_access_code_email(reservation=instance) - - return super().update(instance=instance, validated_data=validated_data) + instance.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + return instance + + if instance.access_code_should_be_active: + with suppress(ExternalServiceError): + PindoraService.activate_access_code(obj=instance) + + EmailService.send_reservation_modified_access_code_email(reservation=instance) + + return instance diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_create_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_create_serializers.py index be1a73b957..0deee7facb 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_create_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_create_serializers.py @@ -9,7 +9,7 @@ from rest_framework.fields import IntegerField from tilavarauspalvelu.enums import AccessType, CustomerTypeChoice, ReservationStateChoice, ReservationTypeChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import AgeGroup, City, Reservation, ReservationPurpose, ReservationUnit from utils.date_utils import DEFAULT_TIMEZONE, local_datetime @@ -161,10 +161,6 @@ def create(self, validated_data: StaffCreateReservationData) -> Reservation: # Don't fail reservation creation if Pindora request fails, but return an error in the response. if reservation.access_type == AccessType.ACCESS_CODE: is_active = reservation.type != ReservationTypeChoice.BLOCKED - response = PindoraClient.create_reservation(reservation=reservation, is_active=is_active) - - reservation.access_code_generated_at = response["access_code_generated_at"] - reservation.access_code_is_active = response["access_code_is_active"] - reservation.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + PindoraService.create_access_code(obj=reservation, is_active=is_active) return reservation diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_repair_access_code_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_repair_access_code_serializers.py index e1616e946e..6f83ce0305 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_repair_access_code_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_repair_access_code_serializers.py @@ -5,6 +5,8 @@ from graphene_django_extensions import NestingModelSerializer from rest_framework.fields import IntegerField +from tilavarauspalvelu.integrations.email.main import EmailService +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import Reservation __all__ = [ @@ -41,5 +43,8 @@ def validate(self, data: dict[str, Any]) -> dict[str, Any]: return data def update(self, instance: Reservation, validated_data: dict[str, Any]) -> Reservation: # noqa: ARG002 - instance.actions.repair_reservation_access_code() + PindoraService.sync_access_code(obj=instance) + + if instance.access_code_should_be_active: + EmailService.send_reservation_modified_access_code_email(reservation=instance) return instance diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_reservation_modify_serializers.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_reservation_modify_serializers.py index 5cb7fbb450..0b6a022f65 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_reservation_modify_serializers.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/serializers/staff_reservation_modify_serializers.py @@ -8,7 +8,7 @@ from rest_framework.fields import IntegerField from tilavarauspalvelu.enums import AccessType, CustomerTypeChoice, ReservationStateChoice, ReservationTypeChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraNotFoundError from tilavarauspalvelu.models import AgeGroup, City, Reservation, ReservationPurpose @@ -135,18 +135,14 @@ def update(self, instance: Reservation, validated_data: StaffReservationData) -> # If reservation was changed to or from blocked, change access code active state in Pindora. changed_with_blocked = type_before != type_after and ReservationTypeChoice.BLOCKED in {type_before, type_after} - if ( - instance.access_type == AccessType.ACCESS_CODE - and instance.recurring_reservation is None - and changed_with_blocked - ): + instance = super().update(instance=instance, validated_data=validated_data) + + if instance.access_type == AccessType.ACCESS_CODE and changed_with_blocked: # Allow reservation modification to succeed if reservation doesn't exist in Pindora. with suppress(PindoraNotFoundError): if type_after == ReservationTypeChoice.BLOCKED: - PindoraClient.deactivate_reservation_access_code(reservation=instance) - validated_data["access_code_is_active"] = False + PindoraService.deactivate_access_code(obj=instance) else: - PindoraClient.activate_reservation_access_code(reservation=instance) - validated_data["access_code_is_active"] = True + PindoraService.activate_access_code(obj=instance) - return super().update(instance=instance, validated_data=validated_data) + return instance diff --git a/backend/tilavarauspalvelu/api/graphql/types/reservation/types.py b/backend/tilavarauspalvelu/api/graphql/types/reservation/types.py index 4700298ec1..e91c2690d6 100644 --- a/backend/tilavarauspalvelu/api/graphql/types/reservation/types.py +++ b/backend/tilavarauspalvelu/api/graphql/types/reservation/types.py @@ -14,9 +14,8 @@ from tilavarauspalvelu.api.graphql.types.merchants.types import PaymentOrderNode from tilavarauspalvelu.enums import AccessType, CustomerTypeChoice, ReservationStateChoice, ReservationTypeChoice -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient +from tilavarauspalvelu.integrations.keyless_entry import PindoraService from tilavarauspalvelu.models import Reservation, ReservationUnit, User -from tilavarauspalvelu.typing import PindoraReservationInfoData from utils.date_utils import DEFAULT_TIMEZONE, local_datetime from utils.db import SubqueryArray from utils.utils import ical_hmac_signature @@ -25,13 +24,9 @@ from .permissions import ReservationPermission if TYPE_CHECKING: - from tilavarauspalvelu.integrations.keyless_entry.typing import ( - PindoraReservationSeriesAccessCodeValidity, - PindoraSeasonalBookingAccessCodeValidity, - ) - from tilavarauspalvelu.models import ApplicationSection, PaymentOrder, RecurringReservation + from tilavarauspalvelu.models import PaymentOrder from tilavarauspalvelu.models.reservation.queryset import ReservationQuerySet - from tilavarauspalvelu.typing import AnyUser, GQLInfo + from tilavarauspalvelu.typing import AnyUser, GQLInfo, PindoraReservationInfoData __all__ = [ "ReservationNode", @@ -299,147 +294,21 @@ def resolve_pindora_info(root: Reservation, info: GQLInfo) -> PindoraReservation if not has_perms and root.state != ReservationStateChoice.CONFIRMED: return None - if root.recurring_reservation is not None: - return ReservationNode.series_pindora_info( - root.recurring_reservation, - begin=root.begin.astimezone(DEFAULT_TIMEZONE), - end=root.end.astimezone(DEFAULT_TIMEZONE), - has_perms=has_perms, - ) - - try: - response = PindoraClient.get_reservation(reservation=root.ext_uuid) - except Exception: # noqa: BLE001 - return None - - # Don't allow reserver to view Pindora info without permissions if the access code is not active - access_code_is_active = response["access_code_is_active"] - if not has_perms and not access_code_is_active: - return None - - begin = response["begin"] - end = response["end"] - access_code_valid_minutes_before = response["access_code_valid_minutes_before"] - access_code_valid_minutes_after = response["access_code_valid_minutes_after"] - access_code_begins_at = begin - datetime.timedelta(minutes=access_code_valid_minutes_before) - access_code_ends_at = end + datetime.timedelta(minutes=access_code_valid_minutes_after) - - return PindoraReservationInfoData( - access_code=response["access_code"], - access_code_generated_at=response["access_code_generated_at"], - access_code_is_active=access_code_is_active, - access_code_keypad_url=response["access_code_keypad_url"], - access_code_phone_number=response["access_code_phone_number"], - access_code_sms_number=response["access_code_sms_number"], - access_code_sms_message=response["access_code_sms_message"], - access_code_begins_at=access_code_begins_at, - access_code_ends_at=access_code_ends_at, - ) - - @staticmethod - def series_pindora_info( - series: RecurringReservation, - *, - begin: datetime.datetime, - end: datetime.datetime, - has_perms: bool, - ) -> PindoraReservationInfoData | None: - if series.allocated_time_slot is not None: - return ReservationNode.section_pindora_info( - series.allocated_time_slot.reservation_unit_option.application_section, - begin=begin, - end=end, - has_perms=has_perms, - ) - - try: - response = PindoraClient.get_reservation_series(series=series.ext_uuid) - except Exception: # noqa: BLE001 - return None - - # Don't allow reserver to view Pindora info without permissions if the access code is not active - access_code_is_active = response["access_code_is_active"] - if not has_perms and not access_code_is_active: - return None - - validity: PindoraReservationSeriesAccessCodeValidity | None = next( - ( - validity - for validity in response["reservation_unit_code_validity"] - if validity["begin"] == begin and validity["end"] == end - ), - None, - ) - if validity is None: - return None - - begin = validity["begin"] - end = validity["end"] - access_code_valid_minutes_before = validity["access_code_valid_minutes_before"] - access_code_valid_minutes_after = validity["access_code_valid_minutes_after"] - access_code_begins_at = begin - datetime.timedelta(minutes=access_code_valid_minutes_before) - access_code_ends_at = end + datetime.timedelta(minutes=access_code_valid_minutes_after) - - return PindoraReservationInfoData( - access_code=response["access_code"], - access_code_generated_at=response["access_code_generated_at"], - access_code_is_active=access_code_is_active, - access_code_keypad_url=response["access_code_keypad_url"], - access_code_phone_number=response["access_code_phone_number"], - access_code_sms_number=response["access_code_sms_number"], - access_code_sms_message=response["access_code_sms_message"], - access_code_begins_at=access_code_begins_at, - access_code_ends_at=access_code_ends_at, - ) + if root.recurring_reservation is not None and root.recurring_reservation.allocated_time_slot is not None: + section = root.recurring_reservation.allocated_time_slot.reservation_unit_option.application_section + application_round = section.application.application_round - @staticmethod - def section_pindora_info( - section: ApplicationSection, - *, - begin: datetime.datetime, - end: datetime.datetime, - has_perms: bool, - ) -> PindoraReservationInfoData | None: - # Don't show Pindora info without permissions if the application round results haven't been sent yet - if not has_perms and section.application.application_round.sent_date is None: - return None + # Don't show Pindora info without permissions if the application round results haven't been sent yet + if not has_perms and application_round.sent_date is None: + return None try: - response = PindoraClient.get_seasonal_booking(section=section.ext_uuid) + response = PindoraService.get_access_code(obj=root) except Exception: # noqa: BLE001 return None # Don't allow reserver to view Pindora info without permissions if the access code is not active - access_code_is_active = response["access_code_is_active"] - if not has_perms and not access_code_is_active: - return None - - validity: PindoraSeasonalBookingAccessCodeValidity | None = next( - ( - validity - for validity in response["reservation_unit_code_validity"] - if validity["begin"] == begin and validity["end"] == end - ), - None, - ) - if validity is None: + if not has_perms and not response.access_code_is_active: return None - begin = validity["begin"] - end = validity["end"] - access_code_valid_minutes_before = validity["access_code_valid_minutes_before"] - access_code_valid_minutes_after = validity["access_code_valid_minutes_after"] - access_code_begins_at = begin - datetime.timedelta(minutes=access_code_valid_minutes_before) - access_code_ends_at = end + datetime.timedelta(minutes=access_code_valid_minutes_after) - - return PindoraReservationInfoData( - access_code=response["access_code"], - access_code_generated_at=response["access_code_generated_at"], - access_code_is_active=access_code_is_active, - access_code_keypad_url=response["access_code_keypad_url"], - access_code_phone_number=response["access_code_phone_number"], - access_code_sms_number=response["access_code_sms_number"], - access_code_sms_message=response["access_code_sms_message"], - access_code_begins_at=access_code_begins_at, - access_code_ends_at=access_code_ends_at, - ) + return response diff --git a/backend/tilavarauspalvelu/integrations/keyless_entry/__init__.py b/backend/tilavarauspalvelu/integrations/keyless_entry/__init__.py index 7c3ef71d99..a005bc2c03 100644 --- a/backend/tilavarauspalvelu/integrations/keyless_entry/__init__.py +++ b/backend/tilavarauspalvelu/integrations/keyless_entry/__init__.py @@ -1,7 +1,9 @@ from __future__ import annotations from .client import PindoraClient +from .service import PindoraService __all__ = [ "PindoraClient", + "PindoraService", ] diff --git a/backend/tilavarauspalvelu/integrations/keyless_entry/client.py b/backend/tilavarauspalvelu/integrations/keyless_entry/client.py index 3dde1afc1f..797362ad3b 100644 --- a/backend/tilavarauspalvelu/integrations/keyless_entry/client.py +++ b/backend/tilavarauspalvelu/integrations/keyless_entry/client.py @@ -264,7 +264,9 @@ def create_reservation(cls, reservation: Reservation, *, is_active: bool = False return parsed_data @classmethod - def reschedule_reservation(cls, reservation: Reservation) -> PindoraAccessCodeModifyResponse: + def reschedule_reservation( + cls, reservation: Reservation, *, is_active: bool = ... + ) -> PindoraAccessCodeModifyResponse: """Reschedule a reservation in Pindora.""" url = cls._build_url(f"reservation/reschedule/{reservation.ext_uuid}") @@ -272,6 +274,8 @@ def reschedule_reservation(cls, reservation: Reservation) -> PindoraAccessCodeMo begin=local_iso_format(reservation.begin), end=local_iso_format(reservation.end), ) + if is_active is not ...: + data["is_active"] = is_active response = cls.put(url=url, json=data) cls._validate_reservation_response( @@ -281,10 +285,10 @@ def reschedule_reservation(cls, reservation: Reservation) -> PindoraAccessCodeMo expected_status_code=HTTP_200_OK, ) + cls._clear_cached_reservation_response(ext_uuid=reservation.ext_uuid) + data = cls.response_json(response) - parsed_data = cls._parse_access_code_modify_response(data) - cls._update_cached_reservation_response(parsed_data, ext_uuid=reservation.ext_uuid) - return parsed_data + return cls._parse_access_code_modify_response(data) @classmethod def change_reservation_access_code(cls, reservation: Reservation | uuid.UUID) -> PindoraAccessCodeModifyResponse: @@ -541,10 +545,10 @@ def reschedule_seasonal_booking(cls, section: ApplicationSection) -> PindoraAcce expected_status_code=HTTP_200_OK, ) + cls._clear_cached_seasonal_booking_response(ext_uuid=section.ext_uuid) + data = cls.response_json(response) - parsed_data = cls._parse_access_code_modify_response(data) - cls._update_cached_seasonal_booking_response(parsed_data, ext_uuid=section.ext_uuid) - return parsed_data + return cls._parse_access_code_modify_response(data) @classmethod def change_seasonal_booking_access_code( @@ -800,10 +804,10 @@ def reschedule_reservation_series(cls, series: RecurringReservation) -> PindoraA expected_status_code=HTTP_200_OK, ) + cls._clear_cached_reservation_series_response(ext_uuid=series.ext_uuid) + data = cls.response_json(response) - parsed_data = cls._parse_access_code_modify_response(data) - cls._update_cached_reservation_series_response(parsed_data, ext_uuid=series.ext_uuid) - return parsed_data + return cls._parse_access_code_modify_response(data) @classmethod def change_reservation_series_access_code( diff --git a/backend/tilavarauspalvelu/integrations/keyless_entry/service.py b/backend/tilavarauspalvelu/integrations/keyless_entry/service.py new file mode 100644 index 0000000000..b30a0636a2 --- /dev/null +++ b/backend/tilavarauspalvelu/integrations/keyless_entry/service.py @@ -0,0 +1,504 @@ +from __future__ import annotations + +import datetime +from contextlib import suppress +from typing import TYPE_CHECKING, overload + +from tilavarauspalvelu.enums import AccessType +from tilavarauspalvelu.models import ApplicationSection, RecurringReservation, Reservation +from tilavarauspalvelu.typing import ( + PindoraReservationInfoData, + PindoraSectionInfoData, + PindoraSeriesInfoData, + PindoraValidityInfoData, +) +from utils.date_utils import DEFAULT_TIMEZONE + +from .client import PindoraClient +from .exceptions import PindoraClientError, PindoraConflictError, PindoraInvalidValueError, PindoraNotFoundError +from .typing import PindoraAccessCodeModifyResponse, PindoraAccessCodePeriod + +if TYPE_CHECKING: + import uuid + + from .typing import ( + PindoraAccessCodeValidity, + PindoraReservationSeriesAccessCodeValidity, + PindoraSeasonalBookingAccessCodeValidity, + ) + + +__all__ = [ + "PindoraService", +] + + +class PindoraService: + """Service for Pindora operations.""" + + @classmethod + @overload + def get_access_code(cls, obj: Reservation) -> PindoraReservationInfoData: ... + + @classmethod + @overload + def get_access_code(cls, obj: RecurringReservation) -> PindoraSeriesInfoData: ... + + @classmethod + @overload + def get_access_code(cls, obj: ApplicationSection) -> PindoraSectionInfoData: ... + + @classmethod + def get_access_code(cls, obj): + """Get access code from Pindora through the correct Pindora API endpoint according to the object's type.""" + match obj: + case ApplicationSection(): + response = PindoraClient.get_seasonal_booking(obj) + + series_for_section = RecurringReservation.objects.filter( + allocated_time_slot__reservation_unit_option__application_section=obj + ) + + access_code_validity: list[PindoraValidityInfoData] = [] + for series in series_for_section: + validity = cls._parse_series_validity_info(series, response["reservation_unit_code_validity"]) + access_code_validity.extend(validity) + + return PindoraSectionInfoData( + access_code=response["access_code"], + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + access_code_keypad_url=response["access_code_keypad_url"], + access_code_phone_number=response["access_code_phone_number"], + access_code_sms_number=response["access_code_sms_number"], + access_code_sms_message=response["access_code_sms_message"], + access_code_validity=access_code_validity, + ) + + case RecurringReservation(): + if obj.allocated_time_slot is None: + response = PindoraClient.get_reservation_series(obj) + validity = cls._parse_series_validity_info(obj, response["reservation_unit_code_validity"]) + return PindoraSeriesInfoData( + access_code=response["access_code"], + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + access_code_keypad_url=response["access_code_keypad_url"], + access_code_phone_number=response["access_code_phone_number"], + access_code_sms_number=response["access_code_sms_number"], + access_code_sms_message=response["access_code_sms_message"], + access_code_validity=validity, + ) + + section = obj.allocated_time_slot.reservation_unit_option.application_section + section_data: PindoraSectionInfoData = cls.get_access_code(section) + + validity = [acv for acv in section_data.access_code_validity if acv.reservation_series_id == obj.id] + + return PindoraSeriesInfoData( + access_code=section_data.access_code, + access_code_generated_at=section_data.access_code_generated_at, + access_code_is_active=section_data.access_code_is_active, + access_code_keypad_url=section_data.access_code_keypad_url, + access_code_phone_number=section_data.access_code_phone_number, + access_code_sms_number=section_data.access_code_sms_number, + access_code_sms_message=section_data.access_code_sms_message, + access_code_validity=validity, + ) + + case Reservation(): + if obj.recurring_reservation is None: + response = PindoraClient.get_reservation(obj) + period = cls._parse_code_valid_period(response) + return PindoraReservationInfoData( + access_code=response["access_code"], + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + access_code_keypad_url=response["access_code_keypad_url"], + access_code_phone_number=response["access_code_phone_number"], + access_code_sms_number=response["access_code_sms_number"], + access_code_sms_message=response["access_code_sms_message"], + access_code_begins_at=period["access_code_begins_at"], + access_code_ends_at=period["access_code_ends_at"], + ) + + series = obj.recurring_reservation + series_data: PindoraSeriesInfoData = cls.get_access_code(series) + + next_valid = (acv for acv in series_data.access_code_validity if acv.reservation_id == obj.id) + validity = next(next_valid, None) + + if validity is None: + raise PindoraInvalidValueError(entity="reservation", error="Access code not found") + + return PindoraReservationInfoData( + access_code=series_data.access_code, + access_code_generated_at=series_data.access_code_generated_at, + access_code_is_active=series_data.access_code_is_active, + access_code_keypad_url=series_data.access_code_keypad_url, + access_code_phone_number=series_data.access_code_phone_number, + access_code_sms_number=series_data.access_code_sms_number, + access_code_sms_message=series_data.access_code_sms_message, + access_code_begins_at=validity.access_code_begins_at, + access_code_ends_at=validity.access_code_ends_at, + ) + + case _: + msg = f"Invalid target: {obj}" + raise PindoraClientError(msg) + + @classmethod + def create_access_code( + cls, obj: ApplicationSection | RecurringReservation | Reservation, *, is_active: bool = False + ) -> PindoraAccessCodeModifyResponse: + """Create access code in Pindora through the correct Pindora API endpoint according to the object's type.""" + match obj: + case ApplicationSection(): + response = PindoraClient.create_seasonal_booking(obj, is_active=is_active) + obj.actions.get_reservations().requires_active_access_code().update( + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + ) + + case RecurringReservation(): + if obj.allocated_time_slot is None: + response = PindoraClient.create_reservation_series(obj, is_active=is_active) + obj.reservations.requires_active_access_code().update( + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + ) + else: + section = obj.allocated_time_slot.reservation_unit_option.application_section + response = cls.create_access_code(section, is_active=is_active) + + case Reservation(): + if obj.recurring_reservation is None: + response = PindoraClient.create_reservation(obj, is_active=is_active) + obj.access_code_generated_at = response["access_code_generated_at"] + obj.access_code_is_active = response["access_code_is_active"] + obj.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + else: + series = obj.recurring_reservation + response = cls.create_access_code(series, is_active=is_active) + + case _: + msg = f"Invalid create target: {obj}" + raise PindoraClientError(msg) + + return PindoraAccessCodeModifyResponse( + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + ) + + @classmethod + def reschedule_access_code( + cls, obj: ApplicationSection | RecurringReservation | Reservation + ) -> PindoraAccessCodeModifyResponse: + """Reschedule Pindora access code through the correct Pindora API endpoint according to the object's type.""" + match obj: + case ApplicationSection(): + response = PindoraClient.reschedule_seasonal_booking(obj) + obj.actions.get_reservations().requires_active_access_code().update( + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + ) + return response + + case RecurringReservation(): + if obj.allocated_time_slot is None: + response = PindoraClient.reschedule_reservation_series(obj) + obj.reservations.requires_active_access_code().update( + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + ) + return response + + section = obj.allocated_time_slot.reservation_unit_option.application_section + return cls.reschedule_access_code(section) + + case Reservation(): + if obj.recurring_reservation is None: + response = PindoraClient.reschedule_reservation(obj) + obj.access_code_generated_at = response["access_code_generated_at"] + obj.access_code_is_active = response["access_code_is_active"] + obj.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + return response + + series = obj.recurring_reservation + return cls.reschedule_access_code(series) + + case _: + msg = f"Invalid reschedule target: {obj}" + raise PindoraClientError(msg) + + @classmethod + def change_access_code( + cls, obj: ApplicationSection | RecurringReservation | Reservation + ) -> PindoraAccessCodeModifyResponse: + """Change Pindora access code through the correct Pindora API endpoint according to the object's type.""" + match obj: + case ApplicationSection(): + response = PindoraClient.change_seasonal_booking_access_code(obj) + obj.actions.get_reservations().requires_active_access_code().update( + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + ) + return response + + case RecurringReservation(): + if obj.allocated_time_slot is None: + response = PindoraClient.change_reservation_series_access_code(obj) + obj.reservations.requires_active_access_code().update( + access_code_generated_at=response["access_code_generated_at"], + access_code_is_active=response["access_code_is_active"], + ) + return response + + section = obj.allocated_time_slot.reservation_unit_option.application_section + return cls.change_access_code(section) + + case Reservation(): + if obj.recurring_reservation is None: + response = PindoraClient.change_reservation_access_code(obj) + obj.access_code_generated_at = response["access_code_generated_at"] + obj.access_code_is_active = response["access_code_is_active"] + obj.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + return response + + series = obj.recurring_reservation + return cls.change_access_code(series) + + case _: + msg = f"Invalid change access code target: {obj}" + raise PindoraClientError(msg) + + @classmethod + def activate_access_code(cls, obj: ApplicationSection | RecurringReservation | Reservation) -> None: + """Activate Pindora access code through the correct Pindora API endpoint according to the object's type.""" + match obj: + case ApplicationSection(): + PindoraClient.activate_seasonal_booking_access_code(obj) + obj.actions.get_reservations().requires_active_access_code().update(access_code_is_active=True) + + case RecurringReservation(): + if obj.allocated_time_slot is None: + PindoraClient.activate_reservation_series_access_code(obj) + obj.reservations.requires_active_access_code().update(access_code_is_active=True) + return + + section = obj.allocated_time_slot.reservation_unit_option.application_section + cls.activate_access_code(section) + + case Reservation(): + if obj.recurring_reservation is None: + PindoraClient.activate_reservation_access_code(obj) + obj.access_code_is_active = True + obj.save(update_fields=["access_code_is_active"]) + return + + series = obj.recurring_reservation + cls.activate_access_code(series) + + case _: + msg = f"Invalid activate target: {obj}" + raise PindoraClientError(msg) + + @classmethod + def deactivate_access_code(cls, obj: ApplicationSection | RecurringReservation | Reservation) -> None: + """Deactivate Pindora access code through the correct Pindora API endpoint according to the object's type.""" + match obj: + case ApplicationSection(): + PindoraClient.deactivate_seasonal_booking_access_code(obj) + obj.actions.get_reservations().requires_active_access_code().update(access_code_is_active=False) + + case RecurringReservation(): + if obj.allocated_time_slot is None: + PindoraClient.deactivate_reservation_series_access_code(obj) + obj.reservations.requires_active_access_code().update(access_code_is_active=False) + return + + section = obj.allocated_time_slot.reservation_unit_option.application_section + cls.deactivate_access_code(section) + + case Reservation(): + if obj.recurring_reservation is None: + PindoraClient.deactivate_reservation_access_code(obj) + obj.access_code_is_active = False + obj.save(update_fields=["access_code_is_active"]) + return + + series = obj.recurring_reservation + cls.deactivate_access_code(series) + + case _: + msg = f"Invalid deactivate target: {obj}" + raise PindoraClientError(msg) + + @classmethod + def delete_access_code(cls, obj: ApplicationSection | RecurringReservation | Reservation) -> None: + """Delete Pindora access code through the correct Pindora API endpoint according to the object's type.""" + match obj: + case ApplicationSection(): + PindoraClient.delete_seasonal_booking(obj) + + Reservation.objects.filter( + recurring_reservation__allocated_time_slot__reservation_unit_option__application_section=obj, + ).update(access_code_generated_at=None, access_code_is_active=False) + + case RecurringReservation(): + if obj.allocated_time_slot is None: + PindoraClient.delete_reservation_series(obj) + + Reservation.objects.filter( + recurring_reservation=obj, + ).update(access_code_generated_at=None, access_code_is_active=False) + + return + + section = obj.allocated_time_slot.reservation_unit_option.application_section + cls.delete_access_code(section) + + case Reservation(): + if obj.recurring_reservation is None: + PindoraClient.delete_reservation(obj) + obj.access_code_generated_at = None + obj.access_code_is_active = False + obj.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + return + + series = obj.recurring_reservation + cls.delete_access_code(series) + + case _: + msg = f"Invalid delete target: {obj}" + raise PindoraClientError(msg) + + @classmethod + def sync_access_code(cls, obj: ApplicationSection | RecurringReservation | Reservation) -> None: + """ + Synchronizes the access code through the correct Pindora API endpoints according to the object's type + so that it matches what Varaamo thinks is the correct state. + """ + match obj: + case ApplicationSection(): + cls._sync_series_or_seasonal_booking_access_code(obj) + + case RecurringReservation(): + if obj.allocated_time_slot is None: + cls._sync_series_or_seasonal_booking_access_code(obj) + return + + section = obj.allocated_time_slot.reservation_unit_option.application_section + cls.sync_access_code(obj=section) + + case Reservation(): + if obj.recurring_reservation is None: + cls._sync_reservation_access_code(obj) + return + + series = obj.recurring_reservation + cls.sync_access_code(obj=series) + + case _: + msg = f"Invalid sync target: {obj}" + raise PindoraClientError(msg) + + @classmethod + def _sync_reservation_access_code(cls, reservation: Reservation) -> None: + # Access type is not 'ACCESS_CODE', delete the access code (if it exists) + if reservation.access_type != AccessType.ACCESS_CODE: + with suppress(PindoraNotFoundError): + cls.delete_access_code(obj=reservation) + return + + should_be_active = reservation.access_code_should_be_active + + # Otherwise, reschedule or create the access code in Pindora. + # Do one of the operations first depending on if we think the access code has been generated or not. + # This doubles as a way to ensure that the access code is correctly active or inactive in Pindora. + if reservation.access_code_generated_at is None: + try: + response = PindoraClient.create_reservation(reservation=reservation, is_active=should_be_active) + except PindoraConflictError: + response = PindoraClient.reschedule_reservation(reservation=reservation, is_active=should_be_active) + else: + try: + response = PindoraClient.reschedule_reservation(reservation=reservation, is_active=should_be_active) + except PindoraNotFoundError: + response = PindoraClient.create_reservation(reservation=reservation, is_active=should_be_active) + + reservation.access_code_generated_at = response["access_code_generated_at"] + reservation.access_code_is_active = response["access_code_is_active"] + reservation.save(update_fields=["access_code_generated_at", "access_code_is_active"]) + + @classmethod + def _sync_series_or_seasonal_booking_access_code(cls, obj: RecurringReservation | ApplicationSection) -> None: + should_be_active: bool = obj.should_have_active_access_code # type: ignore[attr-defined] + + try: + if should_be_active: + cls.activate_access_code(obj=obj) + else: + cls.deactivate_access_code(obj=obj) + except PindoraNotFoundError: + # Reservation series and application sections always have an access code, even if it's not active. + cls.create_access_code(obj=obj, is_active=should_be_active) + + cls.reschedule_access_code(obj=obj) + + @classmethod + def _parse_series_validity_info( + cls, + series: RecurringReservation, + validities: list[PindoraReservationSeriesAccessCodeValidity | PindoraSeasonalBookingAccessCodeValidity], + ) -> list[PindoraValidityInfoData]: + """ + Given the list of access code validity info from Pindora (either for a reservation series + or an application section), construct a list of info objects for this reservation series with + the pre-calculated access code validity times as well as the reservation and series ids. + """ + reservations_by_period: dict[tuple[datetime.datetime, datetime.datetime, uuid.UUID], int] = {} + + for reservation in series.reservations.requires_active_access_code(): + begin = reservation.begin.astimezone(DEFAULT_TIMEZONE) + end = reservation.end.astimezone(DEFAULT_TIMEZONE) + reservations_by_period[begin, end, series.reservation_unit.uuid] = reservation.pk + + access_code_validity: list[PindoraValidityInfoData] = [] + for validity in validities: + reservation_unit_uuid = validity.get("reservation_unit_id", series.reservation_unit.uuid) + key = (validity["begin"], validity["end"], reservation_unit_uuid) + reservation_id = reservations_by_period.get(key) + + # This will filter out other series' reservations in case info is from an application section + # (although it might filter out legitimate reservations in this series if dates don't match exactly). + if reservation_id is None: + continue + + period = cls._parse_code_valid_period(validity) + + access_code_validity.append( + PindoraValidityInfoData( + reservation_id=reservation_id, + reservation_series_id=series.pk, + access_code_begins_at=period["access_code_begins_at"], + access_code_ends_at=period["access_code_ends_at"], + ) + ) + + return access_code_validity + + @classmethod + def _parse_code_valid_period(cls, validity: PindoraAccessCodeValidity) -> PindoraAccessCodePeriod: + """Calculate access code validity time based on reservation start time and validity buffers.""" + begin = validity["begin"] + end = validity["end"] + access_code_valid_minutes_before = validity["access_code_valid_minutes_before"] + access_code_valid_minutes_after = validity["access_code_valid_minutes_after"] + access_code_begins_at = begin - datetime.timedelta(minutes=access_code_valid_minutes_before) + access_code_ends_at = end + datetime.timedelta(minutes=access_code_valid_minutes_after) + + return PindoraAccessCodePeriod( + access_code_begins_at=access_code_begins_at, + access_code_ends_at=access_code_ends_at, + ) diff --git a/backend/tilavarauspalvelu/integrations/keyless_entry/typing.py b/backend/tilavarauspalvelu/integrations/keyless_entry/typing.py index ba933207b9..11d5819ad0 100644 --- a/backend/tilavarauspalvelu/integrations/keyless_entry/typing.py +++ b/backend/tilavarauspalvelu/integrations/keyless_entry/typing.py @@ -8,6 +8,7 @@ __all__ = [ "PindoraAccessCodeModifyResponse", + "PindoraAccessCodePeriod", "PindoraReservationCreateData", "PindoraReservationRescheduleData", "PindoraReservationResponse", @@ -63,6 +64,11 @@ class PindoraAccessCodeValidity(TypedDict): end: datetime.datetime +class PindoraAccessCodePeriod(TypedDict): + access_code_begins_at: datetime.datetime + access_code_ends_at: datetime.datetime + + class PindoraSeasonalBookingAccessCodeValidity(PindoraAccessCodeValidity): reservation_unit_id: uuid.UUID @@ -109,6 +115,7 @@ class PindoraReservationCreateData(TypedDict): class PindoraReservationRescheduleData(TypedDict): begin: str # datetime end: str # datetime + is_active: NotRequired[bool] class PindoraSeasonalBookingReservationData(TypedDict): diff --git a/backend/tilavarauspalvelu/models/recurring_reservation/actions.py b/backend/tilavarauspalvelu/models/recurring_reservation/actions.py index d16d2a4ade..59d1659b76 100644 --- a/backend/tilavarauspalvelu/models/recurring_reservation/actions.py +++ b/backend/tilavarauspalvelu/models/recurring_reservation/actions.py @@ -8,7 +8,6 @@ from tilavarauspalvelu.enums import AccessType, RejectionReadinessChoice from tilavarauspalvelu.integrations.opening_hours.time_span_element import TimeSpanElement from tilavarauspalvelu.models import AffectingTimeSpan, ApplicationSection, RejectedOccurrence, Reservation -from tilavarauspalvelu.typing import PindoraValidityInfoData from utils.date_utils import DEFAULT_TIMEZONE, combine, get_periods_between if TYPE_CHECKING: @@ -22,7 +21,6 @@ ReservationTypeChoice, ReservationTypeStaffChoice, ) - from tilavarauspalvelu.integrations.keyless_entry.typing import PindoraAccessCodeValidity from tilavarauspalvelu.models import ( AgeGroup, City, @@ -31,7 +29,6 @@ ReservationPurpose, User, ) - from tilavarauspalvelu.typing import PindoraSeriesValidityInfoData class ReservationPeriod(TypedDict): @@ -346,39 +343,3 @@ def get_application_section(self) -> ApplicationSection | None: return ApplicationSection.objects.filter( reservation_unit_options__allocated_time_slots__recurring_reservation=self.recurring_reservation ).first() - - def get_access_code_validity_info(self, info: list[PindoraAccessCodeValidity]) -> list[PindoraValidityInfoData]: - """ - Given the list of access code validity info from Pindora (either for a reservation series - or an application section), construct a list of info objects for this reservation series with - the pre-calculated access code validity times as well as the reservation and series ids. - """ - reservations_by_period: dict[tuple[datetime.datetime, datetime.datetime], int] = {} - - for reservation in self.recurring_reservation.reservations.requires_active_access_code(): - begin = reservation.begin.astimezone(DEFAULT_TIMEZONE) - end = reservation.end.astimezone(DEFAULT_TIMEZONE) - reservations_by_period[begin, end] = reservation.pk - - access_code_validity: list[PindoraSeriesValidityInfoData] = [] - for validity in info: - reservation_id = reservations_by_period.get((validity["begin"], validity["end"])) - - # This will filter out other series' reservations in case info is from an application section - # (although it might filter out legitimate reservations in this series if dates don't match exactly). - if reservation_id is None: - continue - - begins = validity["begin"] - datetime.timedelta(minutes=validity["access_code_valid_minutes_before"]) - ends = validity["end"] + datetime.timedelta(minutes=validity["access_code_valid_minutes_after"]) - - access_code_validity.append( - PindoraValidityInfoData( - reservation_id=reservation_id, - reservation_series_id=self.recurring_reservation.pk, - access_code_begins_at=begins, - access_code_ends_at=ends, - ) - ) - - return access_code_validity diff --git a/backend/tilavarauspalvelu/models/reservation/actions.py b/backend/tilavarauspalvelu/models/reservation/actions.py index 2c1331c8d5..ef039fef5f 100644 --- a/backend/tilavarauspalvelu/models/reservation/actions.py +++ b/backend/tilavarauspalvelu/models/reservation/actions.py @@ -2,7 +2,6 @@ import datetime import uuid -from contextlib import suppress from typing import TYPE_CHECKING, Literal from django.conf import settings @@ -11,7 +10,6 @@ from icalendar import Calendar, Event, Timezone, TimezoneDaylight, TimezoneStandard from tilavarauspalvelu.enums import ( - AccessType, CalendarProperty, CustomerTypeChoice, EventProperty, @@ -22,9 +20,6 @@ TimezoneRuleProperty, ) from tilavarauspalvelu.exceptions import ReservationPriceCalculationError -from tilavarauspalvelu.integrations.email.main import EmailService -from tilavarauspalvelu.integrations.keyless_entry import PindoraClient -from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraConflictError, PindoraNotFoundError from tilavarauspalvelu.integrations.verkkokauppa.verkkokauppa_api_client import VerkkokauppaAPIClient from tilavarauspalvelu.models import ApplicationSection, ReservationMetadataField, Space from tilavarauspalvelu.translation import get_attr_by_language, get_translated @@ -294,75 +289,3 @@ def refund_paid_reservation(self) -> None: payment_order.status = OrderStatus.REFUNDED payment_order.save(update_fields=["refund_id", "status"]) - - def create_or_update_reservation_access_code_if_required(self, *, from_access_type: AccessType) -> None: - """Notify Pindora about the time of the reservation if required.""" - # Recurring reservations should be handled with separate endpoints - if self.reservation.recurring_reservation is not None: - return - - current_access_type = self.reservation.access_type - - is_active = self.reservation.access_code_should_be_active - - # Access type remains 'ACCESS_CODE', reschedule the reservation - if {from_access_type, current_access_type} == {AccessType.ACCESS_CODE}: - with suppress(PindoraNotFoundError): - if not is_active: - PindoraClient.deactivate_reservation_access_code(reservation=self.reservation) - self.reservation.access_code_is_active = False - self.reservation.save(update_fields=["access_code_is_active"]) - - PindoraClient.reschedule_reservation(reservation=self.reservation) - - # Access type no longer 'ACCESS_CODE', delete the reservation - elif from_access_type == AccessType.ACCESS_CODE: - with suppress(PindoraNotFoundError): - PindoraClient.delete_reservation(reservation=self.reservation) - self.reservation.access_code_generated_at = None - self.reservation.access_code_is_active = False - self.reservation.save(update_fields=["access_code_generated_at", "access_code_is_active"]) - - # Access type now 'ACCESS_CODE', create access code - elif current_access_type == AccessType.ACCESS_CODE: - response = PindoraClient.create_reservation(reservation=self.reservation, is_active=is_active) - self.reservation.access_code_generated_at = response["access_code_generated_at"] - self.reservation.access_code_is_active = response["access_code_is_active"] - self.reservation.save(update_fields=["access_code_generated_at", "access_code_is_active"]) - - def repair_reservation_access_code(self) -> None: - """ - Synchronizes the state of access codes for a reservation between Pindora and Varaamo - so that the access code is generated and activated/deactivated in Pindora according to what - Varaamo thinks is should be its current state. - """ - should_be_active = self.reservation.access_code_should_be_active - - if self.reservation.access_code_generated_at is not None: - try: - if should_be_active: - PindoraClient.activate_reservation_access_code(reservation=self.reservation) - self.reservation.access_code_is_active = True - EmailService.send_reservation_modified_access_code_email(reservation=self.reservation) - else: - PindoraClient.deactivate_reservation_access_code(reservation=self.reservation) - self.reservation.access_code_is_active = False - except PindoraNotFoundError: - self.reservation.access_code_generated_at = None - self.reservation.access_code_is_active = False - - if self.reservation.access_code_generated_at is None: - try: - response = PindoraClient.create_reservation(reservation=self.reservation, is_active=should_be_active) - self.reservation.access_code_generated_at = response["access_code_generated_at"] - self.reservation.access_code_is_active = response["access_code_is_active"] - - if should_be_active: - EmailService.send_reservation_modified_access_code_email(reservation=self.reservation) - - except PindoraConflictError: - response = PindoraClient.get_reservation(reservation=self.reservation) - self.reservation.access_code_generated_at = response["access_code_generated_at"] - self.reservation.access_code_is_active = response["access_code_is_active"] - - self.reservation.save(update_fields=["access_code_generated_at", "access_code_is_active"])