def when_user_has_cancelled_some_offers(self, app): # Given user = BeneficiaryGrant18Factory(email="*****@*****.**", postalCode="75130", deposit__version=1) booking_factories.CancelledIndividualBookingFactory( individualBooking__user=user) # When response = (TestClient(app.test_client()).with_session_auth( "*****@*****.**").get("/beneficiaries/current")) # Then assert response.json["wallet_balance"] == 500.0 assert response.json["domainsCredit"] == { "all": { "initial": 500.0, "remaining": 500.0 }, "digital": { "initial": 200.0, "remaining": 200.0 }, "physical": { "initial": 200.0, "remaining": 200.0 }, }
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_cancel_all_bookings_from_stock(self, app): stock = offers_factories.StockFactory(dnBookedQuantity=1) booking_1 = booking_factories.IndividualBookingFactory(stock=stock) booking_2 = booking_factories.IndividualBookingFactory(stock=stock) used_booking = booking_factories.UsedIndividualBookingFactory(stock=stock) cancelled_booking = booking_factories.CancelledIndividualBookingFactory(stock=stock) api.cancel_bookings_when_offerer_deletes_stock(stock) # cancellation can trigger more than one request to Batch assert len(push_testing.requests) >= 1 assert models.Booking.query.filter().count() == 4 assert models.Booking.query.filter(models.Booking.isCancelled == True).count() == 3 assert models.Booking.query.filter(models.Booking.isUsed == True).count() == 1 assert booking_1.isCancelled assert booking_1.status is BookingStatus.CANCELLED assert booking_1.cancellationReason == BookingCancellationReasons.OFFERER assert booking_2.isCancelled assert booking_2.status is BookingStatus.CANCELLED assert booking_2.cancellationReason == BookingCancellationReasons.OFFERER assert not used_booking.isCancelled assert used_booking.status is not BookingStatus.CANCELLED assert not used_booking.cancellationReason assert cancelled_booking.isCancelled assert cancelled_booking.status is BookingStatus.CANCELLED assert cancelled_booking.cancellationReason == BookingCancellationReasons.BENEFICIARY
def test_should_raises_forbidden_error_if_cancelled(self, app): booking = factories.CancelledIndividualBookingFactory() 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_should_return_event_data_when_booking_is_an_event(self): # Given booking = factories.CancelledIndividualBookingFactory( individualBooking__user=BeneficiaryGrant18Factory( email="*****@*****.**", firstName="Fabien"), stock=EventStockFactory( price=10.2, beginningDatetime=datetime.utcnow(), offer__name="Test event name", offer__id=123456, ), ) # When email_data = make_beneficiary_booking_cancellation_email_data( booking.individualBooking) # Then assert email_data == { "Mj-TemplateID": 1091464, "Mj-TemplateLanguage": True, "Vars": { "can_book_again": 1, "event_date": "26 novembre 2019", "event_hour": "19h29", "is_event": 1, "is_free_offer": 0, "offer_id": "AHREA", "offer_name": "Test event name", "offer_price": "10.20", "user_first_name": "Fabien", }, }
def test_raise_if_already_cancelled(self): booking = factories.CancelledIndividualBookingFactory() with pytest.raises(api_errors.ResourceGoneError) as exc: validation.check_booking_can_be_cancelled(booking) assert exc.value.errors["global"] == [ "Cette contremarque a déjà été annulée" ]
def should_raise_if_cancelled(self): booking = factories.CancelledIndividualBookingFactory() with pytest.raises(api_errors.ForbiddenError) as exc: validation.check_is_usable(booking) assert exc.value.errors["booking"] == [ "Cette réservation a été annulée" ]
def test_mark_as_used_with_uncancel(self): booking = booking_factories.CancelledIndividualBookingFactory() api.mark_as_used_with_uncancelling(booking) assert booking.isUsed assert not booking.isCancelled assert booking.status is BookingStatus.USED assert booking.dateUsed == datetime.utcnow() assert not booking.cancellationReason
def test_should_return_is_free_offer_when_offer_price_equals_to_zero(self): # Given booking = factories.CancelledIndividualBookingFactory(stock__price=0) # When email_data = make_beneficiary_booking_cancellation_email_data( booking.individualBooking) # Then assert email_data["Vars"]["is_free_offer"] == 1
def test_raise_if_already_cancelled(self): booking = booking_factories.CancelledIndividualBookingFactory( cancellationReason=BookingCancellationReasons.BENEFICIARY ) with pytest.raises(api_errors.ResourceGoneError): api.cancel_booking_by_offerer(booking) assert booking.isCancelled assert booking.status is BookingStatus.CANCELLED assert booking.cancellationReason == BookingCancellationReasons.BENEFICIARY # unchanged assert push_testing.requests == []
def test_should_return_only_most_recent_booking_when_two_cancelled_on_same_stock(self, app): # Given now = datetime.utcnow() two_days_ago = now - timedelta(days=2) three_days_ago = now - timedelta(days=3) beneficiary = BeneficiaryGrant18Factory() offer = EventOfferFactory() stock = EventStockFactory(offer=offer) booking1 = booking_factories.CancelledIndividualBookingFactory( individualBooking__user=beneficiary, stock=stock, dateCreated=two_days_ago ) booking_factories.CancelledIndividualBookingFactory( individualBooking__user=beneficiary, stock=stock, dateCreated=three_days_ago ) # When result = BeneficiaryBookingsSQLRepository().get_beneficiary_bookings(beneficiary_id=beneficiary.id) # Then assert len(result.bookings) == 1 assert result.bookings[0].id == booking1.id
def test_show_mark_as_used_button(self, app): users_factories.AdminFactory(email="*****@*****.**") bookings_factories.CancelledIndividualBookingFactory(token="ABCDEF") client = TestClient( app.test_client()).with_session_auth("*****@*****.**") response = client.post("/pc/back-office/bookings/", form={"token": "abcdeF"}) assert response.status_code == 200 content = response.data.decode(response.charset) assert "Marquer comme utilisée" in content
def test_should_return_mailjet_data_on_thing_offer( self, mock_is_offer_active, mock_build_pc_pro_offer_link): # Given stock = offers_factories.ThingStockFactory() booking1 = bookings_factories.CancelledIndividualBookingFactory( stock=stock, quantity=2) booking2 = bookings_factories.IndividualBookingFactory(stock=stock, quantity=1) # When mailjet_data = retrieve_offerer_booking_recap_email_data_after_user_cancellation( booking1) # 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": 0, "date": "", "heure": "", "quantite": booking1.quantity, "user_name": booking1.publicName, "user_email": booking1.email, "is_active": 0, "nombre_resa": 1, "users": [{ "contremarque": booking2.token, "email": booking2.email, "firstName": booking2.firstName, "lastName": booking2.lastName, }], }, }
def test_should_return_the_price_multiplied_by_quantity_when_it_is_a_duo_offer( self): # Given booking = factories.CancelledIndividualBookingFactory(quantity=2, stock__price=10) # When email_data = make_beneficiary_booking_cancellation_email_data( booking.individualBooking) # Then assert email_data["Vars"]["offer_price"] == "20.00"
def test_should_notify_of_todays_expired_individual_bookings( self, mocked_send_email_recap, app) -> None: now = datetime.utcnow() yesterday = now - timedelta(days=1) long_ago = now - timedelta(days=31) very_long_ago = now - timedelta(days=32) dvd = ProductFactory( subcategoryId=subcategories.SUPPORT_PHYSIQUE_FILM.id) expired_today_dvd_booking = booking_factories.CancelledIndividualBookingFactory( stock__offer__product=dvd, dateCreated=long_ago, cancellationReason=BookingCancellationReasons.EXPIRED, ) cd = ProductFactory( subcategoryId=subcategories.SUPPORT_PHYSIQUE_MUSIQUE.id) expired_today_cd_booking = booking_factories.CancelledIndividualBookingFactory( stock__offer__product=cd, dateCreated=long_ago, cancellationReason=BookingCancellationReasons.EXPIRED, ) painting = ProductFactory(subcategoryId=subcategories.OEUVRE_ART.id) _expired_yesterday_booking = booking_factories.CancelledIndividualBookingFactory( stock__offer__product=painting, dateCreated=very_long_ago, cancellationReason=BookingCancellationReasons.EXPIRED, cancellationDate=yesterday, ) handle_expired_bookings.notify_offerers_of_expired_individual_bookings( ) assert mocked_send_email_recap.call_count == 2 assert mocked_send_email_recap.call_args_list[0][0] == ( expired_today_dvd_booking.offerer, [expired_today_dvd_booking.individualBooking.booking], ) assert mocked_send_email_recap.call_args_list[1][0] == ( expired_today_cd_booking.offerer, [expired_today_cd_booking.individualBooking.booking], )
def should_not_update_cancelled_old_thing_that_can_expire_booking( self, app) -> None: book = ProductFactory(subcategoryId=subcategories.LIVRE_PAPIER.id) old_book_booking = booking_factories.CancelledIndividualBookingFactory( stock__offer__product=book) initial_cancellation_date = old_book_booking.cancellationDate handle_expired_bookings.cancel_expired_individual_bookings() assert old_book_booking.isCancelled assert old_book_booking.status is BookingStatus.CANCELLED assert old_book_booking.cancellationDate == initial_cancellation_date assert old_book_booking.cancellationReason == BookingCancellationReasons.BENEFICIARY
def test_cannot_uncancel_with_expired_deposit(self): # The user once booked and cancelled their booking. booking = factories.CancelledIndividualBookingFactory() user = booking.individualBooking.user # But now their deposit expired. self._expire_deposit(user) # The backend should not do that, but if it does, the database # should prevent it. booking.uncancel_booking_set_used() with pytest.raises(sqlalchemy.exc.InternalError) as exc: db.session.add(booking) db.session.flush() assert "insufficientFunds" in exc.args[0]
def test_balance(self): # given user = users_factories.BeneficiaryGrant18Factory(deposit__version=1) bookings_factories.UsedIndividualBookingFactory( individualBooking__user=user, quantity=1, amount=10) bookings_factories.UsedIndividualBookingFactory( individualBooking__user=user, quantity=2, amount=20) bookings_factories.IndividualBookingFactory( individualBooking__user=user, quantity=3, amount=30) bookings_factories.CancelledIndividualBookingFactory( individualBooking__user=user, quantity=4, amount=40) # then assert user.wallet_balance == 500 - (10 + 2 * 20 + 3 * 30) assert user.real_wallet_balance == 500 - (10 + 2 * 20)
def test_cancel_all_bookings_from_stock(self, app): stock = offers_factories.StockFactory(dnBookedQuantity=1) booking_factories.IndividualBookingFactory(stock=stock) booking_factories.IndividualBookingFactory(stock=stock) booking_factories.UsedIndividualBookingFactory(stock=stock) booking_factories.CancelledIndividualBookingFactory(stock=stock) # open a second connection on purpose and lock the stock engine = create_engine(app.config["SQLALCHEMY_DATABASE_URI"]) with engine.connect() as connection: connection.execute(text("""SELECT * FROM stock WHERE stock.id = :stock_id FOR UPDATE"""), stock_id=stock.id) with pytest.raises(sqlalchemy.exc.OperationalError): api.cancel_bookings_when_offerer_deletes_stock(stock) assert models.Booking.query.filter().count() == 4 assert models.Booking.query.filter(models.Booking.isCancelled == True).count() == 1
def test_cant_cancel_cancelled_booking(self, app): users_factories.UserFactory(email="*****@*****.**", isAdmin=True) booking = bookings_factories.CancelledIndividualBookingFactory() client = TestClient( app.test_client()).with_session_auth("*****@*****.**") route = f"/pc/back-office/bookings/cancel/{booking.id}" response = client.post(route, form={}) assert response.status_code == 302 assert response.location == f"http://localhost/pc/back-office/bookings/?id={booking.id}" response = client.get(response.location) content = response.data.decode(response.charset) assert "L'opération a échoué : la réservation a déjà été annulée" in content booking = Booking.query.get(booking.id) assert booking.isCancelled
def test_uncancel_and_mark_as_used(self, app): users_factories.AdminFactory(email="*****@*****.**") booking = bookings_factories.CancelledIndividualBookingFactory() client = TestClient( app.test_client()).with_session_auth("*****@*****.**") route = f"/pc/back-office/bookings/mark-as-used/{booking.id}" response = client.post(route, form={}) assert response.status_code == 302 assert response.location == f"http://localhost/pc/back-office/bookings/?id={booking.id}" response = client.get(response.location) content = response.data.decode(response.charset) assert "La réservation a été dés-annulée et marquée comme utilisée." in content booking = Booking.query.get(booking.id) assert not booking.isCancelled assert booking.status is not BookingStatus.CANCELLED assert booking.isUsed assert booking.status is BookingStatus.USED
def test_returns_both_current_and_real_balances(self): # given offer = offers_factories.OfferFactory() stock1 = offers_factories.StockFactory(offer=offer, price=20) stock2 = offers_factories.StockFactory(offer=offer, price=30) stock3 = offers_factories.StockFactory(offer=offer, price=40) user = users_factories.BeneficiaryGrant18Factory(deposit__version=1) bookings_factories.IndividualBookingFactory( individualBooking__user=user, stock=stock1) bookings_factories.CancelledIndividualBookingFactory( individualBooking__user=user, stock=stock2) bookings_factories.UsedIndividualBookingFactory( individualBooking__user=user, stock=stock3, quantity=2) # when balances = get_all_users_wallet_balances() # then balance = balances[0] assert balance.current_balance == 500 - (20 + 40 * 2) assert balance.real_balance == 500 - (40 * 2)
def test_raise_if_cancelled(self): booking = booking_factories.CancelledIndividualBookingFactory() with pytest.raises(api_errors.ForbiddenError): api.mark_as_used(booking) assert not booking.isUsed assert booking.status is not BookingStatus.USED
def test_dont_raise_if_user_cancelled(self): booking = factories.CancelledIndividualBookingFactory() validation.check_offer_already_booked( booking.individualBooking.user, booking.stock.offer) # should not raise
def test_get_bookings(self, app): OFFER_URL = "https://demo.pass/some/path?token={token}&email={email}&offerId={offerId}" user = users_factories.BeneficiaryGrant18Factory(email=self.identifier) permanent_booking = booking_factories.UsedIndividualBookingFactory( individualBooking__user=user, stock__offer__subcategoryId=subcategories. TELECHARGEMENT_LIVRE_AUDIO.id, dateUsed=datetime(2021, 2, 3), ) event_booking = booking_factories.IndividualBookingFactory( individualBooking__user=user, stock=EventStockFactory(beginningDatetime=datetime(2021, 3, 14))) digital_stock = StockWithActivationCodesFactory() first_activation_code = digital_stock.activationCodes[0] second_activation_code = digital_stock.activationCodes[1] digital_booking = booking_factories.UsedIndividualBookingFactory( individualBooking__user=user, stock=digital_stock, activationCode=first_activation_code, ) ended_digital_booking = booking_factories.UsedIndividualBookingFactory( individualBooking__user=user, displayAsEnded=True, stock=digital_stock, activationCode=second_activation_code, ) expire_tomorrow = booking_factories.IndividualBookingFactory( individualBooking__user=user, dateCreated=datetime.now() - timedelta(days=29)) used_but_in_future = booking_factories.UsedIndividualBookingFactory( individualBooking__user=user, dateUsed=datetime(2021, 3, 11), stock=StockFactory(beginningDatetime=datetime(2021, 3, 15)), ) cancelled_permanent_booking = booking_factories.CancelledIndividualBookingFactory( individualBooking__user=user, stock__offer__subcategoryId=subcategories. TELECHARGEMENT_LIVRE_AUDIO.id, cancellation_date=datetime(2021, 3, 10), ) cancelled = booking_factories.CancelledIndividualBookingFactory( individualBooking__user=user, cancellation_date=datetime(2021, 3, 8)) used1 = booking_factories.UsedIndividualBookingFactory( individualBooking__user=user, dateUsed=datetime(2021, 3, 1)) used2 = booking_factories.UsedIndividualBookingFactory( individualBooking__user=user, displayAsEnded=True, dateUsed=datetime(2021, 3, 2), stock__offer__url=OFFER_URL, cancellation_limit_date=datetime(2021, 3, 2), ) mediation = MediationFactory(id=111, offer=used2.stock.offer, thumbCount=1, credit="street credit") access_token = create_access_token(identity=self.identifier) test_client = TestClient(app.test_client()) test_client.auth_header = {"Authorization": f"Bearer {access_token}"} # 1: get the user # 1: get the bookings # 1: get AUTO_ACTIVATE_DIGITAL_BOOKINGS feature # 1: rollback with assert_num_queries(4): response = test_client.get("/native/v1/bookings") assert response.status_code == 200 assert [b["id"] for b in response.json["ongoing_bookings"]] == [ expire_tomorrow.id, event_booking.id, used_but_in_future.id, digital_booking.id, permanent_booking.id, ] assert response.json["ongoing_bookings"][3]["activationCode"] assert [b["id"] for b in response.json["ended_bookings"]] == [ ended_digital_booking.id, cancelled_permanent_booking.id, cancelled.id, used2.id, used1.id, ] assert response.json["ended_bookings"][3] == { "activationCode": None, "cancellationDate": None, "cancellationReason": None, "confirmationDate": "2021-03-02T00:00:00Z", "completedUrl": f"https://demo.pass/some/path?token={used2.token}&[email protected]&offerId={humanize(used2.stock.offer.id)}", "dateUsed": "2021-03-02T00:00:00Z", "expirationDate": None, "quantity": 1, "qrCodeData": None, "id": used2.id, "stock": { "beginningDatetime": None, "id": used2.stock.id, "offer": { "category": { "categoryType": "Thing", "label": "Support physique (DVD, Blu-ray...)", "name": "FILM", }, "subcategoryId": subcategories.SUPPORT_PHYSIQUE_FILM.id, "extraData": None, "id": used2.stock.offer.id, "image": { "credit": "street credit", "url": mediation.thumbUrl }, "isDigital": True, "isPermanent": False, "name": used2.stock.offer.name, "url": f"https://demo.pass/some/path?token={used2.token}&[email protected]&offerId={humanize(used2.stock.offer.id)}", "venue": { "city": "Paris", "coordinates": { "latitude": 48.87004, "longitude": 2.3785 }, "id": used2.venue.id, "name": used2.venue.name, "publicName": used2.venue.publicName, }, "withdrawalDetails": None, }, }, "token": used2.token, "totalAmount": 1000, } for booking in response.json["ongoing_bookings"]: assert booking["qrCodeData"] is not None