From e6af3e82dc0e0101b7cfcecb39e8d6fe51d5a364 Mon Sep 17 00:00:00 2001 From: jbaudet-pass <187622442+jbaudet-pass@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:10:21 +0100 Subject: [PATCH] (BSR)[API] feat: public api: add @atomic to products module Add @atomic to routes and some nested functions called by them. --- api/src/pcapi/core/bookings/api.py | 5 +- api/src/pcapi/core/external/batch.py | 5 +- api/src/pcapi/core/external/compliance.py | 3 +- .../bookings/booking_withdrawal_updated.py | 4 +- api/src/pcapi/core/offers/api.py | 80 ++++-------- api/src/pcapi/core/search/__init__.py | 26 ++-- .../public/individual_offers/v1/products.py | 117 +++++++++++------- .../public/individual_offers/v1/venues.py | 2 + .../booking_withdrawal_updated_test.py | 2 +- .../v1/get_product_by_ean_test.py | 14 ++- .../individual_offers/v1/get_product_test.py | 9 +- .../individual_offers/v1/get_products_test.py | 54 +++++--- 12 files changed, 184 insertions(+), 137 deletions(-) diff --git a/api/src/pcapi/core/bookings/api.py b/api/src/pcapi/core/bookings/api.py index 35ac7c40882..5b100edb539 100644 --- a/api/src/pcapi/core/bookings/api.py +++ b/api/src/pcapi/core/bookings/api.py @@ -560,7 +560,10 @@ def _cancel_booking( technical_message_id="booking.cancelled", ) batch.track_booking_cancellation(booking) - external_bookings_api.send_booking_notification_to_external_service(booking, BookingAction.CANCEL) + + on_commit( + partial(external_bookings_api.send_booking_notification_to_external_service, booking, BookingAction.CANCEL) + ) update_external_user(booking.user) update_external_pro(booking.venue.bookingEmail) diff --git a/api/src/pcapi/core/external/batch.py b/api/src/pcapi/core/external/batch.py index ef4df0031cf..bb954fa8e23 100644 --- a/api/src/pcapi/core/external/batch.py +++ b/api/src/pcapi/core/external/batch.py @@ -1,4 +1,5 @@ from datetime import datetime +from functools import partial import logging import textwrap @@ -9,6 +10,7 @@ import pcapi.core.fraud.models as fraud_models import pcapi.core.offers.models as offers_models import pcapi.notifications.push as push_notifications +from pcapi.repository import on_commit from pcapi.tasks import batch_tasks @@ -167,7 +169,8 @@ def track_booking_cancellation(booking: bookings_models.Booking) -> None: "offer_price": booking.total_amount, } payload = batch_tasks.TrackBatchEventRequest(event_name=event_name, event_payload=event_payload, user_id=user.id) - batch_tasks.track_event_task.delay(payload) + + on_commit(partial(batch_tasks.track_event_task.delay, payload)) def format_offer_attributes(offer: offers_models.Offer) -> dict: diff --git a/api/src/pcapi/core/external/compliance.py b/api/src/pcapi/core/external/compliance.py index 95abb87e388..b53a9c9f216 100644 --- a/api/src/pcapi/core/external/compliance.py +++ b/api/src/pcapi/core/external/compliance.py @@ -16,7 +16,8 @@ logger = logging.getLogger(__name__) -def update_offer_compliance_score(offer: offers_models.Offer, is_primary: bool) -> None: +def update_offer_compliance_score(offer_id: int, is_primary: bool) -> None: + offer = offers_models.Offer.query.get(offer_id) payload = _get_payload_for_compliance_api(offer) if is_primary: compliance_tasks.update_offer_compliance_score_primary_task.delay(payload) diff --git a/api/src/pcapi/core/mails/transactional/bookings/booking_withdrawal_updated.py b/api/src/pcapi/core/mails/transactional/bookings/booking_withdrawal_updated.py index f63b2a381d1..033981a1b67 100644 --- a/api/src/pcapi/core/mails/transactional/bookings/booking_withdrawal_updated.py +++ b/api/src/pcapi/core/mails/transactional/bookings/booking_withdrawal_updated.py @@ -16,7 +16,9 @@ from pcapi.utils.date import format_time_in_second_to_human_readable -def send_email_for_each_ongoing_booking(offer: Offer) -> None: +def send_email_for_each_ongoing_booking(offer_id: int) -> None: + offer = Offer.query.get(offer_id) + ongoing_bookings = ( bookings_models.Booking.query.join(bookings_models.Booking.stock) .join(Stock.offer) diff --git a/api/src/pcapi/core/offers/api.py b/api/src/pcapi/core/offers/api.py index 8bc9a2ecadc..edef5e3bae4 100644 --- a/api/src/pcapi/core/offers/api.py +++ b/api/src/pcapi/core/offers/api.py @@ -443,9 +443,10 @@ def update_offer( withdrawal_updated = updates_set & withdrawal_fields oa_updated = "offererAddress" in updates if should_send_mail and (withdrawal_updated or oa_updated): - transactional_mails.send_email_for_each_ongoing_booking(offer) + on_commit(partial(transactional_mails.send_email_for_each_ongoing_booking, offer.id)) reason = search.IndexationReason.OFFER_UPDATE + search.async_index_offer_ids([offer.id], reason=reason, log_extra={"changes": updates_set}) return offer @@ -591,13 +592,10 @@ def batch_update_offers(query: BaseQuery, update_fields: dict, send_email_notifi else: db.session.commit() - on_commit( - partial( - search.async_index_offer_ids, - offer_ids_batch, - reason=search.IndexationReason.OFFER_BATCH_UPDATE, - log_extra={"changes": set(update_fields.keys())}, - ), + search.async_index_offer_ids( + offer_ids_batch, + reason=search.IndexationReason.OFFER_BATCH_UPDATE, + log_extra={"changes": set(update_fields.keys())}, ) withdrawal_updated = {"withdrawalDetails", "withdrawalType", "withdrawalDelay"}.intersection( @@ -608,7 +606,7 @@ def batch_update_offers(query: BaseQuery, update_fields: dict, send_email_notifi on_commit( partial( transactional_mails.send_email_for_each_ongoing_booking, - offer, + offer.id, ), ) @@ -825,10 +823,8 @@ def create_stock( offer.lastValidationPrice = price repository.add_to_session(created_stock, *created_activation_codes, offer) db.session.flush() - search.async_index_offer_ids( - [offer.id], - reason=search.IndexationReason.STOCK_CREATION, - ) + + search.async_index_offer_ids([offer.id], reason=search.IndexationReason.STOCK_CREATION) return created_stock @@ -916,6 +912,7 @@ def edit_stock( finance_api.update_finance_event_pricing_date(stock) repository.add_to_session(stock) + search.async_index_offer_ids( [stock.offerId], reason=search.IndexationReason.STOCK_UPDATE, @@ -976,10 +973,7 @@ def publish_offer( else: if offer.publicationDate: offers_repository.delete_future_offer(offer.id) - search.async_index_offer_ids( - [offer.id], - reason=search.IndexationReason.OFFER_PUBLICATION, - ) + search.async_index_offer_ids([offer.id], reason=search.IndexationReason.OFFER_PUBLICATION) logger.info( "Offer has been published", extra={"offer_id": offer.id, "venue_id": offer.venueId, "offer_status": offer.status}, @@ -1037,6 +1031,7 @@ def _delete_stock(stock: models.Stock, author_id: int | None = None, user_connec if cancelled_bookings: for booking in cancelled_bookings: transactional_mails.send_booking_cancellation_by_pro_to_beneficiary_email(booking) + transactional_mails.send_booking_cancellation_confirmation_by_pro_email(cancelled_bookings) if not FeatureToggle.WIP_DISABLE_CANCEL_BOOKING_NOTIFICATION.is_active(): on_commit( @@ -1046,13 +1041,7 @@ def _delete_stock(stock: models.Stock, author_id: int | None = None, user_connec ) ) - on_commit( - partial( - search.async_index_offer_ids, - [stock.offerId], - reason=search.IndexationReason.STOCK_DELETION, - ) - ) + search.async_index_offer_ids([stock.offerId], reason=search.IndexationReason.STOCK_DELETION) def delete_stock(stock: models.Stock, author_id: int | None = None, user_connect_as: bool | None = None) -> None: @@ -1106,13 +1095,7 @@ def create_mediation( ) _delete_mediations_and_thumbs(previous_mediations) - on_commit( - partial( - search.async_index_offer_ids, - [offer.id], - reason=search.IndexationReason.MEDIATION_CREATION, - ), - ) + search.async_index_offer_ids([offer.id], reason=search.IndexationReason.MEDIATION_CREATION) return mediation @@ -1204,13 +1187,8 @@ def add_criteria_to_offers( db.session.bulk_save_objects(offer_criteria) db.session.flush() - on_commit( - partial( - search.async_index_offer_ids, - offer_ids, - reason=search.IndexationReason.CRITERIA_LINK, - log_extra={"criterion_ids": criterion_ids}, - ), + search.async_index_offer_ids( + offer_ids, reason=search.IndexationReason.CRITERIA_LINK, log_extra={"criterion_ids": criterion_ids} ) return True @@ -1310,13 +1288,10 @@ def reject_inappropriate_products( users_models.Favorite.query.filter(users_models.Favorite.offerId.in_(offer_ids)).delete( synchronize_session=False ) - on_commit( - partial( - search.async_index_offer_ids, - offer_ids, - reason=search.IndexationReason.PRODUCT_REJECTION, - log_extra={"eans": eans}, - ), + search.async_index_offer_ids( + offer_ids, + reason=search.IndexationReason.PRODUCT_REJECTION, + log_extra={"eans": eans}, ) return True @@ -1384,10 +1359,10 @@ def set_offer_status_based_on_fraud_criteria(offer: AnyOffer) -> models.OfferVal status = models.OfferValidationStatus.PENDING offer.flaggingValidationRules = flagging_rules if isinstance(offer, models.Offer): - compliance.update_offer_compliance_score(offer, is_primary=True) + on_commit(partial(compliance.update_offer_compliance_score, offer.id, is_primary=True)) else: if isinstance(offer, models.Offer): - compliance.update_offer_compliance_score(offer, is_primary=False) + on_commit(partial(compliance.update_offer_compliance_score, offer.id, is_primary=False)) logger.info("Computed offer validation", extra={"offer": offer.id, "status": status.value}) return status @@ -2022,17 +1997,12 @@ def move_event_offer( db.session.add(booking) - on_commit( - partial( - search.async_index_offer_ids, - {offer_id}, - reason=search.IndexationReason.OFFER_UPDATE, - log_extra={"changes": {"venueId"}}, - ) + search.async_index_offer_ids( + {offer_id}, reason=search.IndexationReason.OFFER_UPDATE, log_extra={"changes": {"venueId"}} ) if notify_beneficiary: - on_commit(partial(transactional_mails.send_email_for_each_ongoing_booking, offer)) + on_commit(partial(transactional_mails.send_email_for_each_ongoing_booking, offer.id)) def move_collective_offer_venue( diff --git a/api/src/pcapi/core/search/__init__.py b/api/src/pcapi/core/search/__init__.py index de0d6a34319..d0742ebc514 100644 --- a/api/src/pcapi/core/search/__init__.py +++ b/api/src/pcapi/core/search/__init__.py @@ -1,6 +1,7 @@ from collections import abc import datetime import enum +from functools import partial import logging import typing @@ -19,6 +20,7 @@ from pcapi.core.search.backends import base from pcapi.models import db from pcapi.models.feature import FeatureToggle +from pcapi.repository import on_commit from pcapi.utils import requests from pcapi.utils.module_loading import import_string @@ -132,14 +134,22 @@ def async_index_offer_ids( This function returns quickly. The "real" reindexation will be done later through a cron job. """ - _log_async_request("offers", offer_ids, reason, log_extra) - backend = _get_backend() - try: - backend.enqueue_offer_ids(offer_ids) - except Exception: # pylint: disable=broad-except - if not settings.CATCH_INDEXATION_EXCEPTIONS: - raise - logger.exception("Could not enqueue offer ids to index", extra={"offers": offer_ids}) + + def enqueue( + offer_ids: abc.Collection[int], + reason: IndexationReason, + log_extra: dict | None = None, + ) -> None: + _log_async_request("offers", offer_ids, reason, log_extra) + backend = _get_backend() + try: + backend.enqueue_offer_ids(offer_ids) + except Exception: # pylint: disable=broad-except + if not settings.CATCH_INDEXATION_EXCEPTIONS: + raise + logger.exception("Could not enqueue offer ids to index", extra={"offers": offer_ids}) + + on_commit(partial(enqueue, offer_ids, reason, log_extra)) def async_index_collective_offer_template_ids( diff --git a/api/src/pcapi/routes/public/individual_offers/v1/products.py b/api/src/pcapi/routes/public/individual_offers/v1/products.py index 2809f51555a..29631f3e4e2 100644 --- a/api/src/pcapi/routes/public/individual_offers/v1/products.py +++ b/api/src/pcapi/routes/public/individual_offers/v1/products.py @@ -1,5 +1,6 @@ import copy import datetime +from functools import partial import logging from flask import request @@ -26,6 +27,8 @@ from pcapi.models import api_errors from pcapi.models import db from pcapi.models.offer_mixin import OfferValidationType +from pcapi.repository import atomic +from pcapi.repository import on_commit from pcapi.routes.public import blueprints from pcapi.routes.public import spectree_schemas from pcapi.routes.public.documentation_constants import http_responses @@ -62,6 +65,7 @@ ) ), ) +@atomic() def get_show_types() -> serialization.GetShowTypesResponse: """ Get Show Types @@ -93,6 +97,7 @@ def get_show_types() -> serialization.GetShowTypesResponse: ) ), ) +@atomic() def get_music_types() -> serialization.GetMusicTypesResponse: """ [LEGACY] Get music types @@ -124,6 +129,7 @@ def get_music_types() -> serialization.GetMusicTypesResponse: ) ), ) +@atomic() def get_all_titelive_music_types() -> serialization.GetTiteliveMusicTypesResponse: """ Get Music Types @@ -155,6 +161,7 @@ def get_all_titelive_music_types() -> serialization.GetTiteliveMusicTypesRespons ) ), ) +@atomic() def get_event_titelive_music_types() -> serialization.GetTiteliveEventMusicTypesResponse: """ Get Events Music Types @@ -276,6 +283,7 @@ def _create_stock(product: offers_models.Offer, body: serialization.ProductOffer ) ), ) +@atomic() def post_product_offer(body: serialization.ProductOfferCreation) -> serialization.ProductOfferResponse: """ Create Product Offer @@ -333,6 +341,7 @@ def post_product_offer(body: serialization.ProductOfferCreation) -> serializatio ) ), ) +@atomic() def post_product_offer_by_ean(body: serialization.ProductsOfferByEanCreation) -> None: """ Batch Upsert Product Offers by EAN @@ -362,12 +371,15 @@ def post_product_offer_by_ean(body: serialization.ProductsOfferByEanCreation) -> address_label = body.location.address_label serialized_products_stocks = _serialize_products_from_body(body.products) - _create_or_update_ean_offers.delay( - serialized_products_stocks=serialized_products_stocks, - venue_id=venue.id, - provider_id=current_api_key.provider.id, - address_id=address_id, - address_label=address_label, + on_commit( + partial( + _create_or_update_ean_offers.delay, + serialized_products_stocks=serialized_products_stocks, + venue_id=venue.id, + provider_id=current_api_key.provider.id, + address_id=address_id, + address_label=address_label, + ) ) @@ -615,6 +627,7 @@ def _create_offer_from_product( ) ), ) +@atomic() def get_product(product_id: int) -> serialization.ProductOfferResponse: """ Get Product Offer @@ -646,6 +659,7 @@ def get_product(product_id: int) -> serialization.ProductOfferResponse: ) ), ) +@atomic() def get_product_by_ean( query: serialization.GetProductsListByEansQuery, ) -> serialization.ProductOffersByEanResponse: @@ -683,6 +697,7 @@ def get_product_by_ean( ) ), ) +@atomic() def check_eans_availability( query: serialization.GetAvailableEANsListQuery, ) -> serialization.AvailableEANsResponse: @@ -763,6 +778,7 @@ def _retrieve_offer_by_eans_query(eans: list[str], venueId: int) -> sqla.orm.Que ) ), ) +@atomic() def get_products( query: serialization.GetOffersQueryParams, ) -> serialization.ProductOffersResponse: @@ -813,6 +829,7 @@ def _check_offer_can_be_edited(offer: offers_models.Offer) -> None: ) ), ) +@atomic() def edit_product(body: serialization.ProductOfferEdition) -> serialization.ProductOfferResponse: """ Update Product Offer @@ -833,45 +850,52 @@ def edit_product(body: serialization.ProductOfferEdition) -> serialization.Produ _check_offer_can_be_edited(offer) utils.check_offer_subcategory(body, offer.subcategoryId) - venue, offerer_address = utils.extract_venue_and_offerer_address_from_location(body) - try: - with repository.transaction(): - updates = body.dict(by_alias=True, exclude_unset=True) - dc = updates.get("accessibility", {}) - extra_data = copy.deepcopy(offer.extraData) - offer_body = offers_schemas.UpdateOffer( - name=get_field(offer, updates, "name"), - audioDisabilityCompliant=get_field(offer, dc, "audioDisabilityCompliant"), - mentalDisabilityCompliant=get_field(offer, dc, "mentalDisabilityCompliant"), - motorDisabilityCompliant=get_field(offer, dc, "motorDisabilityCompliant"), - visualDisabilityCompliant=get_field(offer, dc, "visualDisabilityCompliant"), - bookingContact=get_field(offer, updates, "bookingContact"), - bookingEmail=get_field(offer, updates, "bookingEmail"), - description=get_field(offer, updates, "description"), - extraData=( - serialization.deserialize_extra_data(body.category_related_fields, extra_data) - if "categoryRelatedFields" in updates - else extra_data - ), - isActive=get_field(offer, updates, "isActive"), - idAtProvider=get_field(offer, updates, "idAtProvider"), - isDuo=get_field(offer, updates, "enableDoubleBookings", col="isDuo"), - withdrawalDetails=get_field(offer, updates, "itemCollectionDetails", col="withdrawalDetails"), - ) # type: ignore[call-arg] - updated_offer = offers_api.update_offer(offer, offer_body, venue=venue, offerer_address=offerer_address) - if body.image: - utils.save_image(body.image, updated_offer) - if "stock" in updates: - _upsert_product_stock(updated_offer, body.stock, current_api_key.provider) + venue, offerer_address = utils.extract_venue_and_offerer_address_from_location(body) + + updates = body.dict(by_alias=True, exclude_unset=True) + dc = updates.get("accessibility", {}) + extra_data = copy.deepcopy(offer.extraData) + offer_body = offers_schemas.UpdateOffer( + name=get_field(offer, updates, "name"), + audioDisabilityCompliant=get_field(offer, dc, "audioDisabilityCompliant"), + mentalDisabilityCompliant=get_field(offer, dc, "mentalDisabilityCompliant"), + motorDisabilityCompliant=get_field(offer, dc, "motorDisabilityCompliant"), + visualDisabilityCompliant=get_field(offer, dc, "visualDisabilityCompliant"), + bookingContact=get_field(offer, updates, "bookingContact"), + bookingEmail=get_field(offer, updates, "bookingEmail"), + description=get_field(offer, updates, "description"), + extraData=( + serialization.deserialize_extra_data(body.category_related_fields, extra_data) + if "categoryRelatedFields" in updates + else extra_data + ), + isActive=get_field(offer, updates, "isActive"), + idAtProvider=get_field(offer, updates, "idAtProvider"), + isDuo=get_field(offer, updates, "enableDoubleBookings", col="isDuo"), + withdrawalDetails=get_field(offer, updates, "itemCollectionDetails", col="withdrawalDetails"), + ) # type: ignore[call-arg] + updated_offer = offers_api.update_offer(offer, offer_body, venue=venue, offerer_address=offerer_address) + db.session.flush() + + if body.image: + utils.save_image(body.image, updated_offer) + if "stock" in updates: + stock = _upsert_product_stock(updated_offer, body.stock, current_api_key.provider) + + # TODO(jeremieb): this should not be needed. BUT since datetime from + # db are not timezone aware and those from the request are... + # things get complicated during serialization (which does not + # know how to serialize timezone-aware datetime). So... reload + # everything and use data from the db. + db.session.flush() + if stock: + db.session.refresh(stock) except (offers_exceptions.OfferCreationBaseException, offers_exceptions.OfferEditionBaseException) as e: raise api_errors.ApiErrors(e.errors, status_code=400) - # TODO(jeremieb): this should not be needed. BUT since datetime from - # db are not timezone aware and those from the request are... - # things get complicated during serialization (which does not - # know how to serialize timezone-aware datetime). So... reload - # everything and use data from the db. + db.session.refresh(offer) + offer = query.one_or_none() return serialization.ProductOfferResponse.build_product_offer(offer) @@ -880,24 +904,23 @@ def _upsert_product_stock( offer: offers_models.Offer, stock_body: serialization.StockEdition | None, provider: providers_models.Provider, -) -> None: +) -> offers_models.Stock | None: existing_stock = next((stock for stock in offer.activeStocks), None) if not stock_body: if existing_stock: offers_api.delete_stock(existing_stock) - return + return None if not existing_stock: if stock_body.price is None: raise api_errors.ApiErrors({"stock.price": ["Required"]}) - offers_api.create_stock( + return offers_api.create_stock( offer=offer, price=finance_utils.cents_to_full_unit(stock_body.price), quantity=serialization.deserialize_quantity(stock_body.quantity), booking_limit_datetime=stock_body.booking_limit_datetime, creating_provider=provider, ) - return stock_update_body = stock_body.dict(exclude_unset=True) price = stock_update_body.get("price", offers_api.UNCHANGED) @@ -910,6 +933,8 @@ def _upsert_product_stock( editing_provider=provider, ) + return existing_stock + @blueprints.public_api.route("/public/offers/v1/products/categories", methods=["GET"]) @provider_api_key_required @@ -925,6 +950,7 @@ def _upsert_product_stock( ) ), ) +@atomic() def get_product_categories() -> serialization.GetProductCategoriesResponse: """ Get Product Categories @@ -950,6 +976,7 @@ def get_product_categories() -> serialization.GetProductCategoriesResponse: **({"HTTP_204": (None, "Image updated successfully")} | http_responses.HTTP_40X_SHARED_BY_API_ENDPOINTS), ), ) +@atomic() def upload_image(offer_id: int, form: serialization.ImageUploadFile) -> None: """ Upload an Image diff --git a/api/src/pcapi/routes/public/individual_offers/v1/venues.py b/api/src/pcapi/routes/public/individual_offers/v1/venues.py index 322b5994a6d..d60cc43e2fb 100644 --- a/api/src/pcapi/routes/public/individual_offers/v1/venues.py +++ b/api/src/pcapi/routes/public/individual_offers/v1/venues.py @@ -3,6 +3,7 @@ from pcapi.core.offerers import api as offerers_api from pcapi.core.offerers import models as offerers_models from pcapi.models import api_errors +from pcapi.repository import atomic from pcapi.routes.public import blueprints from pcapi.routes.public import spectree_schemas from pcapi.routes.public.documentation_constants import http_responses @@ -34,6 +35,7 @@ ) ), ) +@atomic() def get_offerer_venues( query: venues_serialization.GetOfferersVenuesQuery, ) -> venues_serialization.GetOfferersVenuesResponse: diff --git a/api/tests/core/mails/transactional/bookings/booking_withdrawal_updated_test.py b/api/tests/core/mails/transactional/bookings/booking_withdrawal_updated_test.py index 4c6bb3e7f8d..dce0a40299b 100644 --- a/api/tests/core/mails/transactional/bookings/booking_withdrawal_updated_test.py +++ b/api/tests/core/mails/transactional/bookings/booking_withdrawal_updated_test.py @@ -55,4 +55,4 @@ def test_send_email_for_each_ongoing_booking( # load booking BookingFactory(stock=offers_factories.EventStockFactory(offer=offer)) with core_testing.assert_num_queries(5): - send_email_for_each_ongoing_booking(offer) + send_email_for_each_ongoing_booking(offer.id) diff --git a/api/tests/routes/public/individual_offers/v1/get_product_by_ean_test.py b/api/tests/routes/public/individual_offers/v1/get_product_by_ean_test.py index b827a67945b..487b8218598 100644 --- a/api/tests/routes/public/individual_offers/v1/get_product_by_ean_test.py +++ b/api/tests/routes/public/individual_offers/v1/get_product_by_ean_test.py @@ -12,10 +12,16 @@ class GetProductByEanTest(PublicAPIVenueEndpointHelper): endpoint_url = "/public/offers/v1/products/ean" endpoint_method = "get" - num_queries_400 = 1 # select api_key, offerer and provider - num_queries_400 += 1 # select features - num_queries_404 = num_queries_400 + 1 # check venue_provider exists - num_queries_success = num_queries_404 + 1 # select offers + num_queries_base = 1 # select api_key, offerer and provider + num_queries_base += 1 # select features + + num_queries_400 = num_queries_base + + num_queries_404 = num_queries_base + 1 # check venue_provider exists + num_queries_404 += 1 # rollback to savepoint + + num_queries_success = num_queries_base + 1 # check venue_provider exists + num_queries_success += 1 # select offers def test_should_raise_404_because_has_no_access_to_venue(self, client): plain_api_key, _ = self.setup_provider() diff --git a/api/tests/routes/public/individual_offers/v1/get_product_test.py b/api/tests/routes/public/individual_offers/v1/get_product_test.py index dc62a27abc2..4666e18f5df 100644 --- a/api/tests/routes/public/individual_offers/v1/get_product_test.py +++ b/api/tests/routes/public/individual_offers/v1/get_product_test.py @@ -32,7 +32,8 @@ def test_should_raise_404_because_has_no_access_to_venue(self, client): idAtProvider="provider_id_at_provider", ).id - with testing.assert_num_queries(self.num_queries): + num_queries = self.num_queries + 1 # rollback to savepoint + with testing.assert_num_queries(num_queries): response = client.with_explicit_token(plain_api_key).get( self.endpoint_url.format(product_id=product_offer_id) ) @@ -47,7 +48,8 @@ def test_should_raise_404_because_venue_provider_is_inactive(self, client): idAtProvider="provider_id_at_provider", ).id - with testing.assert_num_queries(self.num_queries): + num_queries = self.num_queries + 1 # rollback to savepoint + with testing.assert_num_queries(num_queries): response = client.with_explicit_token(plain_api_key).get( self.endpoint_url.format(product_id=product_offer_id) ) @@ -165,7 +167,8 @@ def test_404_when_requesting_an_event(self, client): venue = venue_provider.venue event_offer_id = offers_factories.EventOfferFactory(venue=venue).id - with testing.assert_num_queries(self.num_queries): + num_queries = self.num_queries + 1 # rollback to savepoint + with testing.assert_num_queries(num_queries): response = client.with_explicit_token(plain_api_key).get(f"/public/offers/v1/products/{event_offer_id}") assert response.status_code == 404 assert response.json == {"product_id": ["The product offer could not be found"]} diff --git a/api/tests/routes/public/individual_offers/v1/get_products_test.py b/api/tests/routes/public/individual_offers/v1/get_products_test.py index 97dfee8c8c5..2144aea3314 100644 --- a/api/tests/routes/public/individual_offers/v1/get_products_test.py +++ b/api/tests/routes/public/individual_offers/v1/get_products_test.py @@ -14,10 +14,30 @@ class GetProductsTest(PublicAPIVenueEndpointHelper): endpoint_url = "/public/offers/v1/products" endpoint_method = "get" - num_queries_400 = 1 # select api_key, offerer and provider - num_queries_400 += 1 # select features - num_queries_404 = num_queries_400 + 1 # check venue_provider exists - num_queries_success = num_queries_404 + 1 # select offer + # base_num_queries = 1 # select api_key, offerer and provider + # base_num_queries += 1 # select features + + # num_queries_400 = base_num_queries + + # base_num_queries += 1 # check venue_provider exists + # num_queries_404 = base_num_queries + 1 # rollback + # num_queries_success = base_num_queries + 1 # select offer + + # select api key + # select offerer + num_queries_400 = 2 + + # select api key + # select offerer + # select provider + # rollback + num_queries_404 = 4 + + # select api key + # select offerer + # select provider + # select offer + num_queries_success = 4 def test_should_raise_404_because_has_no_access_to_venue(self, client): plain_api_key, _ = self.setup_provider() @@ -45,8 +65,8 @@ def test_get_first_page(self, client: TestClient): self.endpoint_url, params={"venueId": venue_id, "limit": 5} ) - assert response.status_code == 200 - assert [product["id"] for product in response.json["products"]] == [offer.id for offer in offers[0:5]] + assert response.status_code == 200 + assert [product["id"] for product in response.json["products"]] == [offer.id for offer in offers[0:5]] def test_should_return_all_offers(self, client): plain_api_key, venue_provider = self.setup_active_venue_provider() @@ -61,8 +81,8 @@ def test_should_return_all_offers(self, client): with testing.assert_num_queries(no_check_on_venue_num_queries): response = client.with_explicit_token(plain_api_key).get(self.endpoint_url) - assert response.status_code == 200 - assert [product["id"] for product in response.json["products"]] == [offer.id for offer in offers] + assert response.status_code == 200 + assert [product["id"] for product in response.json["products"]] == [offer.id for offer in offers] def test_should_return_offers_linked_to_address_id(self, client): plain_api_key, venue_provider = self.setup_active_venue_provider() @@ -78,9 +98,9 @@ def test_should_return_offers_linked_to_address_id(self, client): self.endpoint_url, {"addressId": offerer_address_1.addressId} ) - assert response.status_code == 200 - assert len(response.json["products"]) == 1 - assert response.json["products"][0]["id"] == offer1.id + assert response.status_code == 200 + assert len(response.json["products"]) == 1 + assert response.json["products"][0]["id"] == offer1.id def test_get_last_page(self, client): plain_api_key, venue_provider = self.setup_active_venue_provider() @@ -94,8 +114,8 @@ def test_get_last_page(self, client): params={"venueId": venue_id, "limit": 5, "firstIndex": first_index}, ) - assert response.status_code == 200 - assert [product["id"] for product in response.json["products"]] == [offer.id for offer in offers[10:12]] + assert response.status_code == 200 + assert [product["id"] for product in response.json["products"]] == [offer.id for offer in offers[10:12]] def test_get_product_using_ids_at_provider(self, client): plain_api_key, venue_provider = self.setup_active_venue_provider() @@ -114,8 +134,8 @@ def test_get_product_using_ids_at_provider(self, client): params={"venueId": venue_id, "limit": 5, "idsAtProvider": f"{id_at_provider_1},{id_at_provider_2}"}, ) - assert response.status_code == 200 - assert [product["id"] for product in response.json["products"]] == [offer_1.id, offer_2.id] + assert response.status_code == 200 + assert [product["id"] for product in response.json["products"]] == [offer_1.id, offer_2.id] def test_should_return_200_even_if_the_offer_name_is_longer_than_90_signs_long(self, client): plain_api_key, venue_provider = self.setup_active_venue_provider() @@ -186,8 +206,8 @@ def test_get_filtered_venue_offer(self, client): with testing.assert_num_queries(self.num_queries_success): response = client.with_explicit_token(plain_api_key).get(self.endpoint_url, params={"venueId": venue_id}) - assert response.status_code == 200 - assert [product["id"] for product in response.json["products"]] == [offer.id] + assert response.status_code == 200 + assert [product["id"] for product in response.json["products"]] == [offer.id] def test_get_offer_with_more_than_1000_description(self, client): plain_api_key, venue_provider = self.setup_active_venue_provider()