def test_delete_cascade_venue_should_abort_when_offerer_has_any_bookings(): # Given booking = bookings_factories.BookingFactory() venue_to_delete = booking.venue # When with pytest.raises(CannotDeleteVenueWithBookingsException) as exception: delete_cascade_venue_by_id(venue_to_delete.id) # Then assert exception.value.errors[ "cannotDeleteVenueWithBookingsException"] == [ "Lieu non supprimable car il contient des réservations" ] assert Offerer.query.count() == 1 assert Venue.query.count() == 1 assert Stock.query.count() == 1 assert Booking.query.count() == 1
def when_booking_not_confirmed(self, mocked_check_is_usable, app): # Given next_week = datetime.utcnow() + timedelta(weeks=1) unconfirmed_booking = bookings_factories.BookingFactory( stock__beginningDatetime=next_week) pro_user = users_factories.UserFactory(email="*****@*****.**") offerer = unconfirmed_booking.stock.offer.venue.managingOfferer offers_factories.UserOffererFactory(user=pro_user, offerer=offerer) url = f"/v2/bookings/token/{unconfirmed_booking.token}" mocked_check_is_usable.side_effect = api_errors.ForbiddenError( errors={"booking": ["Not confirmed"]}) # When response = TestClient( app.test_client()).with_auth("*****@*****.**").get(url) # Then assert response.status_code == 403 assert response.json["booking"] == ["Not confirmed"]
def test_save_cancellation_date_postgresql_function(): # In this test, we manually COMMIT so that save_cancellation_date # PotsgreSQL function is triggered. booking = factories.BookingFactory() assert booking.cancellationDate is None booking.isCancelled = True db.session.commit() assert booking.cancellationDate is not None # Don't change cancellationDate when another attribute is updated. previous = booking.cancellationDate booking.isUsed = True db.session.commit() assert booking.cancellationDate == previous booking.isCancelled = False db.session.commit() assert booking.cancellationDate is None
def test_should_send_email_when_stock_date_have_been_changed(self): # Given stock = offers_factories.EventStockFactory(beginningDatetime=datetime(2019, 8, 20, 12, 0, 0)) booking = bookings_factories.BookingFactory(stock=stock) # When booking_info_for_mailjet = retrieve_data_to_warn_user_after_stock_update_affecting_booking(booking) # Then assert booking_info_for_mailjet == { "MJ-TemplateID": 1332139, "MJ-TemplateLanguage": True, "Vars": { "offer_name": booking.stock.offer.name, "user_first_name": booking.firstName, "venue_name": booking.venue.name, "event_date": "mardi 20 août 2019", "event_hour": "14h", }, }
def test_digital_limit(self): beneficiary = users_factories.UserFactory(deposit__version=1) product = offers_factories.DigitalProductFactory( type=str(ThingType.AUDIOVISUEL)) offer = offers_factories.OfferFactory(product=product) factories.BookingFactory( user=beneficiary, stock__price=190, stock__offer=offer, ) validation.check_expenses_limits(beneficiary, 10, offer) # should not raise with pytest.raises( exceptions.DigitalExpenseLimitHasBeenReached) as error: validation.check_expenses_limits(beneficiary, 11, offer) assert error.value.errors["global"] == [ "Le plafond de 200 € pour les offres numériques ne vous permet pas de réserver cette offre." ]
def when_pro_user_has_rights_on_managing_offerer(self, app): # given booking = bookings_factories.BookingFactory() bookings_factories.UsedBookingFactory(stock=booking.stock) venue = booking.venue venue_owner = offers_factories.UserOffererFactory( offerer=venue.managingOfferer).user auth_request = TestClient( app.test_client()).with_session_auth(email=venue_owner.email) # when response = auth_request.get("/venues/%s/stats" % humanize(venue.id)) # then assert response.status_code == 200 response_json = response.json assert response_json["activeBookingsQuantity"] == 1 assert response_json["validatedBookingsQuantity"] == 1 assert response_json["activeOffersCount"] == 1 assert response_json["soldOutOffersCount"] == 0
def test_does_not_allow_a_negative_remaining_quantity_on_edition( self, mock_update_confirmation_dates): # Given offer = factories.ThingOfferFactory() booking = bookings_factories.BookingFactory(stock__offer=offer, stock__quantity=10) existing_stock = booking.stock edited_stock_data = StockEditionBodyModel(id=existing_stock.id, quantity=0, price=10) # When with pytest.raises(api_errors.ApiErrors) as error: api.upsert_stocks(offer_id=offer.id, stock_data_list=[edited_stock_data]) # Then assert error.value.errors == { "quantity": [ "Le stock total ne peut être inférieur au nombre de réservations" ] }
def test_fully_sync_venue(): api_url = "https://example.com/provider/api" provider = offerers_factories.APIProviderFactory(apiUrl=api_url) venue_provider = offerers_factories.VenueProviderFactory(provider=provider) venue = venue_provider.venue stock = offers_factories.StockFactory(quantity=10, offer__venue=venue, offer__idAtProviders="1") bookings_factories.BookingFactory(stock=stock) product2 = offers_factories.ProductFactory( idAtProviders="1234", extraData={"prix_livre": 10}, subcategoryId=subcategories.LIVRE_PAPIER.id, ) with requests_mock.Mocker() as mock: response = { "total": 1, "stocks": [{ "ref": "1234", "available": 5 }], } mock.get(f"{api_url}/{venue_provider.venueIdAtOfferProvider}", [{ "json": response }, { "json": { "stocks": [] } }]) fully_sync_venue.fully_sync_venue(venue) # Check that the quantity of existing stocks has been reset. assert stock.quantity == 1 # Check that offers and stocks have been created or updated. offer2 = offers_models.Offer.query.filter_by(product=product2).one() assert offer2.stocks[0].quantity == 5
def make_booking(**kwargs): attributes = dict( dateCreated=datetime(2019, 10, 3, 13, 24, 6, tzinfo=timezone.utc), token="ABC123", user__firstName="Joe", stock__beginningDatetime=datetime(2019, 11, 6, 14, 59, 5, tzinfo=timezone.utc), stock__price=23.99, stock__offer__name="Super événement", stock__offer__product__type=str(models.EventType.SPECTACLE_VIVANT), stock__offer__venue__name="Lieu de l'offreur", stock__offer__venue__address="25 avenue du lieu", stock__offer__venue__postalCode="75010", stock__offer__venue__city="Paris", stock__offer__venue__managingOfferer__name="Théâtre du coin", ) attributes.update(kwargs) return bookings_factories.BookingFactory(**attributes)
def should_invalidate_booking_token_when_event_is_reported(self): # Given now = datetime.datetime.now() booking_made_3_days_ago = now - datetime.timedelta(days=3) event_in_4_days = now + datetime.timedelta(days=4) event_reported_in_10_days = now + datetime.timedelta(days=10) stock = factories.EventStockFactory(beginningDatetime=event_in_4_days) booking = bookings_factories.BookingFactory(stock=stock, dateCreated=booking_made_3_days_ago, isUsed=True) # When api.edit_stock( stock, price=5, quantity=20, beginning=event_reported_in_10_days, booking_limit_datetime=stock.bookingLimitDatetime, ) # Then updated_booking = Booking.query.get(booking.id) assert updated_booking.isUsed is False assert updated_booking.dateUsed is None assert updated_booking.confirmationDate == booking.confirmationDate
def test_a_list_of_payments_is_returned_with_statuses_in_error_or_retry_or_pending( self): # Given booking = bookings_factories.BookingFactory() offerer = booking.stock.offer.venue.managingOfferer error_payment = create_payment(booking, offerer, 10) retry_payment = create_payment(booking, offerer, 10) pending_payment = create_payment(booking, offerer, 10) not_processable_payment = create_payment(booking, offerer, 10) error_status = PaymentStatus() error_status.status = TransactionStatus.ERROR error_payment.statuses.append(error_status) retry_status = PaymentStatus() retry_status.status = TransactionStatus.RETRY retry_payment.statuses.append(retry_status) not_processable_status = PaymentStatus() not_processable_status.status = TransactionStatus.NOT_PROCESSABLE not_processable_payment.statuses.append(not_processable_status) repository.save(error_payment, retry_payment, pending_payment) # When payments = concatenate_payments_with_errors_and_retries( [pending_payment]) # Then assert len(payments) == 3 allowed_statuses = (TransactionStatus.RETRY, TransactionStatus.ERROR, TransactionStatus.PENDING) assert all( map(lambda p: p.currentStatus.status in allowed_statuses, payments))
def should_not_invalidate_booking_token_when_event_is_reported_in_less_than_48_hours(self, mock_mark_as_unused): # Given now = datetime.datetime.now() date_used_in_48_hours = datetime.datetime.now() + datetime.timedelta(days=2) event_in_3_days = now + datetime.timedelta(days=3) event_reported_in_less_48_hours = now + datetime.timedelta(days=1) stock = factories.EventStockFactory(beginningDatetime=event_in_3_days) booking = bookings_factories.BookingFactory( stock=stock, dateCreated=now, isUsed=True, dateUsed=date_used_in_48_hours ) # When api.edit_stock( stock, price=5, quantity=20, beginning=event_reported_in_less_48_hours, booking_limit_datetime=event_reported_in_less_48_hours, ) # Then updated_booking = Booking.query.get(booking.id) assert updated_booking.isUsed is True assert updated_booking.dateUsed == date_used_in_48_hours
def test_dont_raise_if_user_cancelled(self): booking = factories.BookingFactory(isCancelled=True) validation.check_offer_already_booked(booking.user, booking.stock.offer) # should not raise
def should_does_not_raise_error_if_not_cancelled_but_used_and_no_beginning_datetime(self): booking = factories.BookingFactory( isUsed=True, stock__beginningDatetime=None, ) validation.check_booking_token_is_keepable(booking) # should not raise
def should_dont_raise_if_stock_beginning_datetime_in_less_than_72_hours(self): booking = factories.BookingFactory( isUsed=True, stock__beginningDatetime=datetime.utcnow() + timedelta(days=2), ) validation.check_booking_token_is_keepable(booking) # should not raise
def test_raise_if_not_the_benficiary(self): booking = factories.BookingFactory() other_user = users_factories.UserFactory() with pytest.raises(exceptions.BookingDoesntExist): validation.check_beneficiary_can_cancel_booking(other_user, booking)
def test_bookable_if_stock_is_unlimited(self): stock = factories.ThingStockFactory(quantity=None) bookings_factories.BookingFactory(stock=stock) assert stock.isBookable
def should_raises_resource_gone_error_if_not_used(self, app): booking = factories.BookingFactory(isUsed=False) with pytest.raises(api_errors.ResourceGoneError) as exc: validation.check_can_be_mark_as_unused(booking) assert exc.value.errors["booking"] == ["Cette réservation n'a pas encore été validée"]
def test_can_cancel(self): booking = factories.BookingFactory() validation.check_beneficiary_can_cancel_booking(booking.user, booking) # should not raise
def test_should_send_email_to_users_address_when_environment_is_production( mock_feature_send_mail_to_users_enabled): booking = bookings_factories.BookingFactory(user__email="*****@*****.**") email_data = retrieve_data_for_beneficiary_booking_confirmation_email( booking) assert email_data["To"] == "*****@*****.**"
def test_can_cancel(self): booking = factories.BookingFactory() validation.check_offerer_can_cancel_booking(booking) # should not raise
def test_raise_if_already_booked(self): booking = factories.BookingFactory() with pytest.raises(exceptions.OfferIsAlreadyBooked) as error: validation.check_offer_already_booked(booking.user, booking.stock.offer) assert error.value.errors == {"offerId": ["Cette offre a déja été reservée par l'utilisateur"]}
def test_raise_if_already_used(self): booking = factories.BookingFactory(isUsed=True) with pytest.raises(exceptions.BookingIsAlreadyUsed): validation.check_beneficiary_can_cancel_booking(booking.user, booking)
def should_raises_forbidden_error_if_validated_and_cancelled(self, app): booking = factories.BookingFactory(isUsed=True, isCancelled=True) with pytest.raises(api_errors.ForbiddenError) as exc: validation.check_can_be_mark_as_unused(booking) assert exc.value.errors["booking"] == ["Cette réservation a été annulée"]
def test_not_bookable_if_no_remaining_stock(self): stock = factories.StockFactory(quantity=1) bookings_factories.BookingFactory(stock=stock) assert not stock.isBookable
def should_raises_resource_gone_error_if_payement_exists(self, app): booking = factories.BookingFactory(isUsed=True) payments_factories.PaymentFactory(booking=booking) with pytest.raises(api_errors.ResourceGoneError) as exc: validation.check_can_be_mark_as_unused(booking) assert exc.value.errors["payment"] == ["Le remboursement est en cours de traitement"]
def test_raise_if_already_cancelled(self): booking = factories.BookingFactory(isCancelled=True) with pytest.raises(api_errors.ResourceGoneError) as exc: validation.check_offerer_can_cancel_booking(booking) assert exc.value.errors["global"] == ["Cette contremarque a déjà été annulée"]
def test_can_cancel_if_event_is_in_a_long_time(self): booking = factories.BookingFactory( stock__beginningDatetime=datetime.utcnow() + timedelta(days=10), ) validation.check_beneficiary_can_cancel_booking(booking.user, booking) # should not raise
def test_should_return_total_price_for_duo_offers(): booking = bookings_factories.BookingFactory(quantity=2, stock__price=10) email_data = retrieve_data_for_beneficiary_booking_confirmation_email( booking) assert email_data["Vars"]["offer_price"] == "20.00"
def test_raise_if_already_used(self): booking = factories.BookingFactory(isUsed=True) with pytest.raises(api_errors.ForbiddenError) as exc: validation.check_offerer_can_cancel_booking(booking) assert exc.value.errors["global"] == ["Impossible d'annuler une réservation consommée"]