diff --git a/api/src/pcapi/core/bookings/api.py b/api/src/pcapi/core/bookings/api.py index 2f78f9f1139..8383a02cd27 100644 --- a/api/src/pcapi/core/bookings/api.py +++ b/api/src/pcapi/core/bookings/api.py @@ -412,9 +412,11 @@ def _book_cinema_external_ticket(booking: Booking, stock: Stock, beneficiary: Us booking=booking, beneficiary=beneficiary, ) - except external_bookings_exceptions.ExternalBookingSoldOutError as exc: + except external_bookings_exceptions.ExternalBookingSoldOutError: logger.exception("Could not book this offer as it's sold out.") - raise exc + raise + except external_bookings_exceptions.ExternalBookingTimeoutException: + raise except Exception as exc: logger.exception("Could not book external ticket: %s", exc) raise external_bookings_exceptions.ExternalBookingException diff --git a/api/src/pcapi/core/external_bookings/boost/client.py b/api/src/pcapi/core/external_bookings/boost/client.py index 19ac3294652..a114d258708 100644 --- a/api/src/pcapi/core/external_bookings/boost/client.py +++ b/api/src/pcapi/core/external_bookings/boost/client.py @@ -9,6 +9,7 @@ from pcapi.connectors.serialization import boost_serializers import pcapi.core.bookings.constants as bookings_constants import pcapi.core.bookings.models as bookings_models +from pcapi.core.external_bookings.decorators import catch_cinema_provider_request_timeout import pcapi.core.external_bookings.models as external_bookings_models import pcapi.core.users.models as users_models from pcapi.utils.queue import add_to_queue @@ -73,6 +74,7 @@ def cancel_booking(self, barcodes: list[str]) -> None: request_timeout=self.request_timeout, ) + @catch_cinema_provider_request_timeout def book_ticket( self, show_id: int, booking: bookings_models.Booking, beneficiary: users_models.User ) -> list[external_bookings_models.Ticket]: diff --git a/api/src/pcapi/core/external_bookings/cds/client.py b/api/src/pcapi/core/external_bookings/cds/client.py index 5c620060677..91dc05b661b 100644 --- a/api/src/pcapi/core/external_bookings/cds/client.py +++ b/api/src/pcapi/core/external_bookings/cds/client.py @@ -19,6 +19,7 @@ import pcapi.core.bookings.models as bookings_models import pcapi.core.external_bookings.cds.constants as cds_constants import pcapi.core.external_bookings.cds.exceptions as cds_exceptions +from pcapi.core.external_bookings.decorators import catch_cinema_provider_request_timeout import pcapi.core.external_bookings.models as external_bookings_models from pcapi.core.external_bookings.models import Ticket import pcapi.core.users.models as users_models @@ -289,6 +290,7 @@ def cancel_booking(self, barcodes: list[str]) -> None: f"Error while canceling bookings :{sep}{sep.join([f'{barcode} : {error_msg}' for barcode, error_msg in cancel_errors.__root__.items()])}" ) + @catch_cinema_provider_request_timeout def book_ticket( self, show_id: int, booking: bookings_models.Booking, beneficiary: users_models.User ) -> list[Ticket]: diff --git a/api/src/pcapi/core/external_bookings/ems/client.py b/api/src/pcapi/core/external_bookings/ems/client.py index 7ef41d3db3f..8b3589d1a4a 100644 --- a/api/src/pcapi/core/external_bookings/ems/client.py +++ b/api/src/pcapi/core/external_bookings/ems/client.py @@ -9,6 +9,7 @@ from pcapi.core.bookings import models as booking_models from pcapi.core.bookings import repository as bookings_repository from pcapi.core.external_bookings import models as external_bookings_models +from pcapi.core.external_bookings.decorators import catch_cinema_provider_request_timeout from pcapi.core.external_bookings.exceptions import ExternalBookingSoldOutError from pcapi.core.users import models as users_models from pcapi.models.feature import FeatureToggle @@ -46,6 +47,7 @@ def get_ticket(self, token: str) -> list[external_bookings_models.Ticket]: for ticket in content.billets ] + @catch_cinema_provider_request_timeout def book_ticket( self, show_id: int, booking: booking_models.Booking, beneficiary: users_models.User ) -> list[external_bookings_models.Ticket]: diff --git a/api/src/pcapi/core/offers/api.py b/api/src/pcapi/core/offers/api.py index a31eb2e5bf8..fe0a4c76668 100644 --- a/api/src/pcapi/core/offers/api.py +++ b/api/src/pcapi/core/offers/api.py @@ -1401,7 +1401,7 @@ def get_shows_remaining_places_from_provider(provider_class: str | None, offer: def _should_try_to_update_offer_stock_quantity(offer: models.Offer) -> bool: # The offer is to update only if it is a cinema offer, and if the venue has a cinema provider - if not offer.subcategory.id == subcategories.SEANCE_CINE.id: + if offer.subcategory.id != subcategories.SEANCE_CINE.id: return False if not offer.lastProviderId: # Manual offer diff --git a/api/src/pcapi/routes/native/v1/bookings.py b/api/src/pcapi/routes/native/v1/bookings.py index 364a7fc7d5f..55fe4c5f53e 100644 --- a/api/src/pcapi/routes/native/v1/bookings.py +++ b/api/src/pcapi/routes/native/v1/bookings.py @@ -80,6 +80,8 @@ def book_offer(user: User, body: BookOfferRequest) -> BookOfferResponse: extra={"offer_id": stock.offer.id, "provider_id": stock.offer.lastProviderId}, ) raise ApiErrors({"code": "CINEMA_PROVIDER_INACTIVE"}) + except external_bookings_exceptions.ExternalBookingTimeoutException: + raise ApiErrors({"code": "PROVIDER_BOOKING_TIMEOUT"}) except external_bookings_exceptions.ExternalBookingException as error: if stock.offer.lastProvider.hasProviderEnableCharlie: logger.info( diff --git a/api/tests/core/bookings/test_api.py b/api/tests/core/bookings/test_api.py index 29b4b5bbfff..8d3d3d236bd 100644 --- a/api/tests/core/bookings/test_api.py +++ b/api/tests/core/bookings/test_api.py @@ -589,7 +589,7 @@ def test_timeout_during_ems_external_booking_trigger_a_cancel_call( url = EMSBookingConnector()._build_url("VENTE", payload) booking_adapter = requests_mock.post(url=re.compile(rf"{url}"), exc=exception) - with pytest.raises(external_bookings_exceptions.ExternalBookingException): + with pytest.raises(external_bookings_exceptions.ExternalBookingTimeoutException): api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) assert not Booking.query.all() @@ -700,7 +700,7 @@ def test_we_dont_cancel_too_early_failing_booking( url = EMSBookingConnector()._build_url("VENTE", payload) booking_adapter = requests_mock.post(url=re.compile(rf"{url}"), exc=exception) - with pytest.raises(external_bookings_exceptions.ExternalBookingException): + with pytest.raises(external_bookings_exceptions.ExternalBookingTimeoutException): api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) assert not Booking.query.all() @@ -781,7 +781,7 @@ def test_timeout_during_ems_external_booking_dont_do_anything_if_ff_deactivated( url = EMSBookingConnector()._build_url("VENTE", payload) booking_adapter = requests_mock.post(url=re.compile(rf"{url}"), exc=exception) - with pytest.raises(external_bookings_exceptions.ExternalBookingException): + with pytest.raises(external_bookings_exceptions.ExternalBookingTimeoutException): api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) assert not Booking.query.all() diff --git a/api/tests/routes/native/v1/bookings_test.py b/api/tests/routes/native/v1/bookings_test.py index 847ab67db1f..0bc56285f40 100644 --- a/api/tests/routes/native/v1/bookings_test.py +++ b/api/tests/routes/native/v1/bookings_test.py @@ -16,6 +16,7 @@ from pcapi.core.bookings.models import BookingCancellationReasons from pcapi.core.bookings.models import BookingStatus from pcapi.core.categories import subcategories_v2 as subcategories +from pcapi.core.external_bookings.exceptions import ExternalBookingTimeoutException from pcapi.core.external_bookings.factories import ExternalBookingFactory from pcapi.core.finance import utils as finance_utils from pcapi.core.geography.factories import AddressFactory @@ -124,6 +125,18 @@ def test_inactive_provider(self, mocked_book_offer, client): assert response.status_code == 400 assert response.json["code"] == "CINEMA_PROVIDER_INACTIVE" + @patch("pcapi.core.bookings.api.book_offer") + def test_provider_timeout(self, mocked_book_offer, client): + users_factories.BeneficiaryGrant18Factory(email=self.identifier) + stock = offers_factories.EventStockFactory() + mocked_book_offer.side_effect = ExternalBookingTimeoutException() + + client = client.with_token(self.identifier) + response = client.post("/native/v1/bookings", json={"stockId": stock.id, "quantity": 1}) + + assert response.status_code == 400 + assert response.json["code"] == "PROVIDER_BOOKING_TIMEOUT" + @pytest.mark.parametrize( "subcategoryId,price", [(subcategoryId, 0) for subcategoryId in offer_models.Stock.AUTOMATICALLY_USED_SUBCATEGORIES],