def test_should_not_create_educational_booking_when_requested_offer_is_not_educational( self): # Given educational_institution = educational_factories.EducationalInstitutionFactory( ) educational_factories.EducationalYearFactory() educational_redactor = educational_factories.EducationalRedactorFactory( email="*****@*****.**") stock = offers_factories.EventStockFactory( offer__isEducational=False, beginningDatetime=datetime.datetime(2021, 5, 15)) redactor_informations = AuthenticatedInformation( email=educational_redactor.email, civility=educational_redactor.civility, firstname=educational_redactor.firstName, lastname=educational_redactor.lastName, uai=educational_institution.institutionId, ) # When with pytest.raises(exceptions.OfferIsNotEducational) as error: educational_api.book_educational_offer( redactor_informations=redactor_informations, stock_id=stock.id, ) # Then assert error.value.errors == { "offer": [f"L'offre {stock.offer.id} n'est pas une offre éducationnelle"] } saved_bookings = EducationalBooking.query.join(Booking).filter( Booking.stockId == stock.id).all() assert len(saved_bookings) == 0
def test_is_deletable_when_stock_is_expired_since_less_than_event_automatic_refund_delay( self): dt = datetime.datetime.utcnow( ) - bookings_constants.AUTO_USE_AFTER_EVENT_TIME_DELAY + datetime.timedelta( 1) stock = factories.EventStockFactory(beginningDatetime=dt) assert stock.isEventDeletable
def test_returns_an_event_stock(self, app): # Given now = datetime.utcnow() pro = users_factories.UserFactory(isBeneficiary=False) stock = offers_factories.EventStockFactory( dateCreated=now, dateModified=now, dateModifiedAtLastProvider=now, beginningDatetime=now, bookingLimitDatetime=now, ) offers_factories.UserOffererFactory( user=pro, offerer=stock.offer.venue.managingOfferer) client = TestClient(app.test_client()).with_auth(email=pro.email) # When response = client.get(f"/offers/{humanize(stock.offer.id)}/stocks") # Then assert response.status_code == 200 assert response.json == { "stocks": [{ "beginningDatetime": "2020-10-15T00:00:00Z", "bookingLimitDatetime": "2020-10-15T00:00:00Z", "bookingsQuantity": 0, "dateCreated": "2020-10-15T00:00:00Z", "dateModified": "2020-10-15T00:00:00Z", "id": humanize(stock.id), "isEventDeletable": True, "isEventExpired": True, "offerId": humanize(stock.offer.id), "price": 10.0, "quantity": 1000, }], }
def test_should_return_redactor_first_name_when_booking_is_educational( self): # Given stock = offers_factories.EventStockFactory(beginningDatetime=datetime( 2019, 7, 20, 12, 0, 0, tzinfo=timezone.utc)) booking = bookings_factories.EducationalBookingFactory( stock=stock, educationalBooking__educationalRedactor__firstName="Georgio", educationalBooking__educationalRedactor__lastName="Di georgio", ) # When mailjet_data = retrieve_data_to_warn_user_after_pro_booking_cancellation( booking) # Then assert mailjet_data == { "MJ-TemplateID": 3192295, "MJ-TemplateLanguage": True, "Vars": { "event_date": "samedi 20 juillet 2019", "event_hour": "14h", "is_event": 1, "is_free_offer": 0, "is_online": 0, "is_thing": 0, "offer_name": booking.stock.offer.name, "offer_price": "10.00", "offerer_name": booking.offerer.name, "user_first_name": "Georgio", "user_last_name": "Di georgio", "venue_name": booking.venue.name, }, }
def test_delete_stock_cancel_bookings_and_send_emails( self, mocked_send_to_beneficiaries, mocked_send_to_offerer): stock = factories.EventStockFactory() booking1 = bookings_factories.BookingFactory(stock=stock) booking2 = bookings_factories.BookingFactory(stock=stock, isCancelled=True) booking3 = bookings_factories.BookingFactory(stock=stock, isUsed=True) api.delete_stock(stock) stock = models.Stock.query.one() assert stock.isSoftDeleted booking1 = models.Booking.query.get(booking1.id) assert booking1.isCancelled booking2 = models.Booking.query.get(booking2.id) assert booking2.isCancelled # unchanged booking3 = models.Booking.query.get(booking3.id) assert not booking3.isCancelled # unchanged notified_bookings_beneficiaries = mocked_send_to_beneficiaries.call_args_list[ 0][0][0] notified_bookings_offerers = mocked_send_to_offerer.call_args_list[0][ 0][0] assert notified_bookings_beneficiaries == notified_bookings_offerers assert notified_bookings_beneficiaries == [booking1]
def test_should_return_mailjet_data_with_no_ongoing_booking( self, mock_is_offer_active, mock_build_pc_pro_offer_link): # Given stock = offers_factories.EventStockFactory( beginningDatetime=datetime(2019, 10, 9, 10, 20, 00)) booking = bookings_factories.CancelledIndividualBookingFactory( stock=stock, quantity=2) # When mailjet_data = retrieve_offerer_booking_recap_email_data_after_user_cancellation( booking) # Then venue = stock.offer.venue assert mailjet_data == { "MJ-TemplateID": 780015, "MJ-TemplateLanguage": True, "Vars": { "departement": venue.departementCode, "nom_offre": stock.offer.name, "lien_offre_pcpro": "http://pc_pro.com/offer_link", "nom_lieu": venue.name, "prix": f"{stock.price}", "is_event": 1, "date": "09-Oct-2019", "heure": "12h20", "quantite": booking.quantity, "user_name": booking.publicName, "user_email": booking.email, "is_active": 1, "nombre_resa": 0, "users": [], }, }
def test_should_not_allow_booking_when_offer_is_not_educational( self, test_data, app): # Given _, educational_institution, educational_redactor = test_data stock = offer_factories.EventStockFactory(beginningDatetime=stock_date, offer__isEducational=False) adage_jwt_fake_valid_token = _create_adage_valid_token_with_email( email=educational_redactor.email, uai=educational_institution.institutionId) test_client = TestClient(app.test_client()) test_client.auth_header = { "Authorization": f"Bearer {adage_jwt_fake_valid_token}" } # When response = test_client.post( "/adage-iframe/bookings", json={ "stockId": stock.id, }, ) # Then assert response.status_code == 400 assert response.json == { "offer": "L'offre n'est pas une offre éducationnelle" }
def test_should_returns_204_with_cancellation_allowed(self, client): # Given stock = offers_factories.EventStockFactory( offer__name="Chouette concert") booking = bookings_factories.IndividualBookingFactory(stock=stock) ApiKeyFactory(offerer=booking.offerer) # When response = client.patch( f"/v2/bookings/cancel/token/{booking.token}", headers={"Authorization": "Bearer " + DEFAULT_CLEAR_API_KEY}, ) # Then # cancellation can trigger more than one request to Batch assert len(push_testing.requests) >= 1 assert response.status_code == 204 updated_booking = Booking.query.one() assert updated_booking.isCancelled assert updated_booking.status is BookingStatus.CANCELLED assert push_testing.requests[-1] == { "group_id": "Cancel_booking", "message": { "body": """Ta réservation "Chouette concert" a été annulée par l'offreur.""", "title": "Réservation annulée", }, "user_ids": [booking.individualBooking.userId], }
def full_offer_factory(): stock = offers_factories.EventStockFactory( offer__product__thumbCount=1, offer__extraData={"author": "Jane Doe"}, ) offers_factories.OfferCriterionFactory(offer=stock.offer) return stock.offer
def test_should_return_event_data_when_booking_is_on_an_event(self): # Given stock = offers_factories.EventStockFactory(beginningDatetime=datetime( 2019, 7, 20, 12, 0, 0, tzinfo=timezone.utc)) booking = bookings_factories.BookingFactory(stock=stock) # When mailjet_data = retrieve_data_to_warn_beneficiary_after_pro_booking_cancellation( booking) # Then assert mailjet_data == { "MJ-TemplateID": 1116690, "MJ-TemplateLanguage": True, "Vars": { "can_book_again": 1, "event_date": "samedi 20 juillet 2019", "event_hour": "14h", "is_event": 1, "is_free_offer": 0, "is_online": 0, "is_thing": 0, "offer_name": booking.stock.offer.name, "offer_price": "10.00", "offerer_name": booking.stock.offer.venue.managingOfferer.name, "user_first_name": "Jeanne", "venue_name": booking.stock.offer.venue.name, }, }
def test_make_offerer_driven_cancellation_email_for_offerer_event_when_other_booking( self, app): # Given other_beneficiary = users_factories.BeneficiaryGrant18Factory() stock = offers_factories.EventStockFactory(beginningDatetime=datetime( 2019, 7, 20, 12, 0, 0, tzinfo=timezone.utc), price=20, quantity=10) booking1 = bookings_factories.IndividualBookingFactory(stock=stock, token="98765") booking2 = bookings_factories.IndividualBookingFactory( individualBooking__user=other_beneficiary, stock=stock, token="12345") # When with patch("pcapi.utils.mailing.find_ongoing_bookings_by_stock", return_value=[booking2]): email = make_offerer_driven_cancellation_email_for_offerer( booking1) # Then email_html = BeautifulSoup(email["Html-part"], "html.parser") html_recap_table = email_html.find("table", {"id": "recap-table"}).text assert "Prénom" in html_recap_table assert "Nom" in html_recap_table assert "Email" in html_recap_table assert other_beneficiary.firstName in html_recap_table assert other_beneficiary.lastName in html_recap_table assert other_beneficiary.email in html_recap_table assert booking2.token in html_recap_table
def test_can_delete_if_event_ended_recently(self): recently = datetime.datetime.now() - datetime.timedelta(days=1) stock = factories.EventStockFactory(beginningDatetime=recently) api.delete_stock(stock) stock = models.Stock.query.one() assert stock.isSoftDeleted
def test_can_edit_non_restricted_fields_if_provider_is_allocine(self): provider = offerers_factories.ProviderFactory(localClass="AllocineStocks") stock = factories.EventStockFactory( offer__lastProvider=provider, offer__idAtProviders="1", ) initial_beginning = stock.beginningDatetime # Change various attributes, but keep the same beginning # (which we are not allowed to change). new_booking_limit = datetime.datetime.now() + datetime.timedelta(days=1) changes = { "price": 5, "quantity": 20, # FIXME (dbaty, 2020-11-25): see comment in edit_stock, # this is to match what the frontend sends. "beginning": stock.beginningDatetime.replace(tzinfo=pytz.UTC), "booking_limit_datetime": new_booking_limit, } api.edit_stock(stock, **changes) stock = models.Stock.query.one() assert stock.price == 5 assert stock.quantity == 20 assert stock.beginningDatetime == initial_beginning assert stock.bookingLimitDatetime == new_booking_limit assert set(stock.fieldsUpdated) == {"price", "quantity", "bookingLimitDatetime"}
def expect_the_booking_to_be_cancelled_by_current_user(self, app): # Given in_four_days = datetime.utcnow() + timedelta(days=4) stock = offers_factories.EventStockFactory( beginningDatetime=in_four_days) booking = bookings_factories.BookingFactory(stock=stock) # When client = TestClient(app.test_client()).with_auth(booking.user.email) response = client.put(f"/bookings/{humanize(booking.id)}/cancel") # Then assert response.status_code == 200 assert Booking.query.get(booking.id).isCancelled assert response.json == { "amount": 10.0, "completedUrl": None, "id": humanize(booking.id), "isCancelled": True, "quantity": booking.quantity, "stock": { "price": 10.0 }, "stockId": humanize(stock.id), "token": booking.token, }
def test_checks_number_of_reservations(self): stock = factories.EventStockFactory() bookings_factories.BookingFactory(stock=stock) bookings_factories.BookingFactory(stock=stock) bookings_factories.BookingFactory(stock=stock, isCancelled=True) # With a quantity too low quantity = 0 with pytest.raises(api_errors.ApiErrors) as error: api.edit_stock( stock, price=stock.price, quantity=quantity, beginning=stock.beginningDatetime, booking_limit_datetime=stock.bookingLimitDatetime, ) msg = "Le stock total ne peut être inférieur au nombre de réservations" assert error.value.errors["quantity"][0] == msg # With enough quantity quantity = 2 api.edit_stock( stock, price=stock.price, quantity=quantity, beginning=stock.beginningDatetime, booking_limit_datetime=stock.bookingLimitDatetime, ) stock = models.Stock.query.one() assert stock.quantity == 2
def test_delete_stock_basics(self, mocked_add_offer_id): stock = factories.EventStockFactory() api.delete_stock(stock) stock = models.Stock.query.one() assert stock.isSoftDeleted mocked_add_offer_id.assert_called_once_with(client=app.redis_client, offer_id=stock.offerId)
def test_cannot_delete_if_too_late(self): too_long_ago = datetime.datetime.now() - datetime.timedelta(days=3) stock = factories.EventStockFactory(beginningDatetime=too_long_ago) with pytest.raises(exceptions.TooLateToDeleteStock): api.delete_stock(stock) stock = models.Stock.query.one() assert not stock.isSoftDeleted
def test_should_return_event_specific_data_for_email_when_offer_is_an_event(): booking = bookings_factories.IndividualBookingFactory( stock=offers_factories.EventStockFactory(price=23.99), dateCreated=datetime.utcnow() ) mediation = offers_factories.MediationFactory(offer=booking.stock.offer) email_data = retrieve_data_for_beneficiary_booking_confirmation_email(booking.individualBooking) expected = get_expected_base_email_data(booking, mediation) assert email_data == expected
def test_serialize_offer_dates_and_times(): offer = offers_factories.OfferFactory( subcategoryId=subcategories.SEANCE_CINE.id) dt = datetime.datetime(2032, 1, 1, 12, 15) offers_factories.EventStockFactory(offer=offer, beginningDatetime=dt) serialized = appsearch.AppSearchBackend().serialize_offer(offer) assert serialized["date_created"] == offer.dateCreated assert serialized["dates"] == [dt] assert serialized["times"] == [12 * 60 * 60 + 15 * 60]
def test_past_event_stock(self): recently = datetime.datetime.now() - datetime.timedelta(minutes=1) stock = offers_factories.EventStockFactory(beginningDatetime=recently) with pytest.raises(ApiErrors) as error: validation.check_stock_is_updatable(stock) assert error.value.errors["global"] == [ "Les événements passés ne sont pas modifiables" ]
def test_should_only_index_bookable_offers_stock(self): offer = offers_factories.EventOfferFactory() _not_bookable_stock = offers_factories.EventStockFactory( beginningDatetime=datetime(2019, 1, 5), bookingLimitDatetime=datetime(2019, 1, 3), offer=offer, quantity=1, ) bookable_stock = offers_factories.EventStockFactory( beginningDatetime=datetime(2019, 1, 12), bookingLimitDatetime=datetime(2019, 1, 10), offer=offer, quantity=1, ) data = _build_offer_details_to_be_indexed(offer) assert data["dates"] == [ datetime.timestamp(bookable_stock.beginningDatetime) ]
def test_long_begun_event_stock(self): too_long_ago = datetime.datetime.now() - datetime.timedelta(days=3) stock = offers_factories.EventStockFactory( beginningDatetime=too_long_ago) with pytest.raises(exceptions.TooLateToDeleteStock) as error: validation.check_stock_is_deletable(stock) assert error.value.errors["global"] == [ "L'événement s'est terminé il y a plus de deux jours, la suppression est impossible." ]
def test_checks_booking_limit_is_after_beginning(self): stock = factories.EventStockFactory() with pytest.raises(api_errors.ApiErrors) as error: api.edit_stock( stock, price=stock.price, quantity=stock.quantity, beginning=datetime.datetime.now(), booking_limit_datetime=datetime.datetime.now() + datetime.timedelta(days=1), ) msg = "La date limite de réservation pour cette offre est postérieure à la date de début de l'évènement" assert error.value.errors["bookingLimitDatetime"][0] == msg
def test_update_booking_used_when_event_date_is_only_1_day_before(self): # Given beginning = datetime(2019, 10, 9, 10, 20, 0) stock = offers_factories.EventStockFactory(beginningDatetime=beginning) bookings_factories.BookingFactory(stock=stock) # When update_booking_used_after_stock_occurrence() # Then booking = Booking.query.first() assert not booking.isUsed assert booking.dateUsed is None
def test_cannot_edit_if_provider_is_titelive(self): provider = offerers_factories.ProviderFactory(localClass="TiteLiveStocks") stock = factories.EventStockFactory(offer__lastProvider=provider, offer__idAtProviders="1") with pytest.raises(api_errors.ApiErrors) as error: api.edit_stock( stock, price=stock.price, quantity=stock.quantity, beginning=stock.beginningDatetime, booking_limit_datetime=stock.bookingLimitDatetime, ) msg = "Les offres importées ne sont pas modifiables" assert error.value.errors["global"][0] == msg
def test_should_return_false_when_offer_is_not_active(self, app): # Given event_date = datetime.now() + timedelta(days=6) stock = offers_factories.EventStockFactory( offer__isActive=False, quantity=2, bookingLimitDatetime=event_date, beginningDatetime=event_date) # When is_active = _is_offer_active_for_recap(stock) # Then assert not is_active
def test_should_return_false_when_stock_booking_limit_is_past(self, app): # Given stock = offers_factories.EventStockFactory( offer__isActive=True, price=0, quantity=2, bookingLimitDatetime=datetime.now() - timedelta(days=6)) bookings_factories.IndividualBookingFactory(stock=stock, quantity=2) # When is_active = _is_offer_active_for_recap(stock) # Then assert not is_active
def test_does_not_update_booking_if_already_used(self): # Given beginning = datetime(2019, 10, 9, 10, 20, 0) stock = offers_factories.EventStockFactory(beginningDatetime=beginning) booking = bookings_factories.BookingFactory(stock=stock, isUsed=True) initial_date_used = booking.dateUsed # When update_booking_used_after_stock_occurrence() # Then booking = Booking.query.first() assert booking.isUsed assert booking.dateUsed == initial_date_used
def when_requested_event_date_is_iso_format(self, app): requested_date = datetime(2020, 8, 12, 20, 00) requested_date_iso_format = "2020-08-12T00:00:00Z" stock = offers_factories.EventStockFactory(beginningDatetime=requested_date) booking = bookings_factories.BookingFactory(stock=stock, token="AAAAAA", dateCreated=datetime(2020, 8, 11)) bookings_factories.BookingFactory(stock=offers_factories.EventStockFactory(), token="BBBBBB") pro_user = users_factories.ProFactory(email="*****@*****.**") offerer = stock.offer.venue.managingOfferer offers_factories.UserOffererFactory(user=pro_user, offerer=offerer) client = TestClient(app.test_client()).with_session_auth(pro_user.email) with assert_num_queries( testing.AUTHENTICATION_QUERIES + 2 + 1 # TODO: query for feature flag, to be removed when IMPROVE_BOOKINGS_PERF is definitely adopted ): response = client.get(f"/bookings/pro?{BOOKING_PERIOD_PARAMS}&eventDate={requested_date_iso_format}") assert response.status_code == 200 assert len(response.json["bookings_recap"]) == 1 assert response.json["bookings_recap"][0]["booking_token"] == booking.token assert response.json["page"] == 1 assert response.json["pages"] == 1 assert response.json["total"] == 1
def test_returns_an_event_stock(self, app, assert_num_queries): # Given now = datetime.utcnow() pro = users_factories.ProFactory() stock = offers_factories.EventStockFactory( dateCreated=now, dateModified=now, dateModifiedAtLastProvider=now, beginningDatetime=now, bookingLimitDatetime=now, ) bookings_factories.BookingFactory.create_batch(3, stock=stock) offers_factories.UserOffererFactory( user=pro, offerer=stock.offer.venue.managingOfferer) client = TestClient( app.test_client()).with_session_auth(email=pro.email) # When offer_id = stock.offer.id n_query_select_offerer = 1 n_query_exist_user_offerer = 1 n_query_select_stock = 1 with assert_num_queries(testing.AUTHENTICATION_QUERIES + n_query_select_offerer + n_query_exist_user_offerer + n_query_select_stock): response = client.get(f"/offers/{humanize(offer_id)}/stocks") # Then assert response.status_code == 200 assert response.json == { "stocks": [{ "hasActivationCodes": False, "activationCodesExpirationDatetime": None, "beginningDatetime": "2020-10-15T00:00:00Z", "bookingLimitDatetime": "2020-10-15T00:00:00Z", "bookingsQuantity": 3, "dateCreated": "2020-10-15T00:00:00Z", "dateModified": "2020-10-15T00:00:00Z", "id": humanize(stock.id), "isEventDeletable": True, "isEventExpired": True, "offerId": humanize(stock.offer.id), "price": 10.0, "quantity": 1000, }], }