Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create an access code when approving a reservation without one #1513

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions tests/test_graphql_api/test_reservation/test_approve.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
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.exceptions import PindoraAPIError
from utils.date_utils import DEFAULT_TIMEZONE, local_datetime

from tests.factories import ReservationFactory, ReservationUnitFactory
from tests.helpers import patch_method
Expand Down Expand Up @@ -109,15 +112,14 @@ def test_reservation__approve__succeeds_with_empty_handling_details(graphql):


@patch_method(PindoraClient.activate_reservation_access_code)
@patch_method(PindoraClient.create_reservation)
def test_reservation__approve__succeeds__pindora_api__call_succeeds(graphql):
reservation_unit = ReservationUnitFactory.create(
access_type=AccessType.ACCESS_CODE,
)
reservation = ReservationFactory.create(
reservation_units__access_type=AccessType.ACCESS_CODE,
state=ReservationStateChoice.REQUIRES_HANDLING,
reservation_units=[reservation_unit],
access_type=AccessType.ACCESS_CODE,
access_code_is_active=False,
access_code_generated_at=local_datetime(),
)

graphql.login_with_superuser()
Expand All @@ -130,19 +132,19 @@ def test_reservation__approve__succeeds__pindora_api__call_succeeds(graphql):
assert reservation.state == ReservationStateChoice.CONFIRMED
assert reservation.access_code_is_active is True

assert PindoraClient.activate_reservation_access_code.call_count == 1
assert PindoraClient.activate_reservation_access_code.called is True
assert PindoraClient.create_reservation.called is False


@patch_method(PindoraClient.activate_reservation_access_code, side_effect=PindoraAPIError("Error"))
@patch_method(PindoraClient.create_reservation)
def test_reservation__approve__succeeds__pindora_api__call_fails(graphql):
reservation_unit = ReservationUnitFactory.create(
access_type=AccessType.ACCESS_CODE,
)
reservation = ReservationFactory.create(
reservation_units__access_type=AccessType.ACCESS_CODE,
state=ReservationStateChoice.REQUIRES_HANDLING,
reservation_units=[reservation_unit],
access_type=AccessType.ACCESS_CODE,
access_code_is_active=False,
access_code_generated_at=local_datetime(),
)

graphql.login_with_superuser()
Expand All @@ -156,4 +158,37 @@ 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.call_count == 1
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_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
26 changes: 26 additions & 0 deletions tests/test_graphql_api/test_reservation/test_requires_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from tilavarauspalvelu.enums import AccessType, ReservationNotification, ReservationStateChoice
from tilavarauspalvelu.integrations.keyless_entry import PindoraClient
from tilavarauspalvelu.integrations.keyless_entry.exceptions import PindoraAPIError, PindoraNotFoundError
from utils.date_utils import local_datetime

from tests.factories import ReservationFactory, UserFactory
from tests.helpers import patch_method
Expand Down Expand Up @@ -85,6 +86,7 @@ def test_reservation__requires_handling__pindora_api__call_succeeds(graphql):
state=ReservationStateChoice.CONFIRMED,
access_type=AccessType.ACCESS_CODE,
access_code_is_active=True,
access_code_generated_at=local_datetime(),
)

graphql.login_with_superuser()
Expand All @@ -106,6 +108,7 @@ def test_reservation__requires_handling__pindora_api__call_fails(graphql):
state=ReservationStateChoice.CONFIRMED,
access_type=AccessType.ACCESS_CODE,
access_code_is_active=True,
access_code_generated_at=local_datetime(),
)

graphql.login_with_superuser()
Expand All @@ -123,6 +126,7 @@ def test_reservation__requires_handling__pindora_api__call_fails__404(graphql):
state=ReservationStateChoice.CONFIRMED,
access_type=AccessType.ACCESS_CODE,
access_code_is_active=True,
access_code_generated_at=local_datetime(),
)

graphql.login_with_superuser()
Expand All @@ -137,3 +141,25 @@ def test_reservation__requires_handling__pindora_api__call_fails__404(graphql):
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
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,21 @@ def validate(self, data: ReservationApproveData) -> ReservationApproveData:
return data

def update(self, instance: Reservation, validated_data: ReservationApproveData) -> Reservation:
instance = super().update(instance=instance, validated_data=validated_data)

if self.instance.access_type == AccessType.ACCESS_CODE:
# Allow activation in Pindora to fail, will be handled by a background task.
with suppress(Exception):
PindoraClient.activate_reservation_access_code(reservation=instance)
instance.access_code_is_active = True
instance.save(update_fields=["access_code_is_active"])
# 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)

EmailService.send_reservation_approved_email(reservation=instance)
EmailService.send_staff_notification_reservation_made_email(reservation=instance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ def validate(self, data: ReservationHandlingData) -> ReservationHandlingData:
return data

def update(self, instance: Reservation, validated_data: dict[str, Any]) -> Reservation:
if self.instance.access_type == AccessType.ACCESS_CODE:
# 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.access_code_generated_at is not None:
# Allow reservation modification to succeed if reservation doesn't exist in Pindora.
with suppress(PindoraNotFoundError):
PindoraClient.deactivate_reservation_access_code(reservation=instance)
Expand Down
2 changes: 2 additions & 0 deletions tilavarauspalvelu/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ class ReservationApproveData(TypedDict):

state: NotRequired[ReservationStateChoice]
handled_at: NotRequired[datetime.datetime]
access_code_generated_at: NotRequired[datetime.datetime | None]
access_code_is_active: NotRequired[bool]


class ReservationCancellationData(TypedDict):
Expand Down