def test_raise_if_invalid_quantity(self): with pytest.raises(exceptions.QuantityIsInvalid): api.book_offer( beneficiary=users_factories.BeneficiaryGrant18Factory(), stock_id=offers_factories.StockFactory().id, quantity=2, )
def test_do_not_sync_algolia_if_feature_is_disabled( self, mocked_add_offer_id): user = users_factories.UserFactory() stock = offers_factories.StockFactory() api.book_offer(beneficiary=user, stock=stock, quantity=1) mocked_add_offer_id.assert_not_called()
def test_raise_if_offer_is_educational(self): with pytest.raises(exceptions.EducationalOfferCannotBeBooked): api.book_offer( beneficiary=users_factories.BeneficiaryGrant18Factory(), stock_id=offers_factories.EducationalEventStockFactory(offer__isEducational=True).id, quantity=2, )
def test_raise_if_no_more_stock(self): booking = booking_factories.IndividualBookingFactory(stock__quantity=1) with pytest.raises(exceptions.StockIsNotBookable): api.book_offer( beneficiary=users_factories.BeneficiaryGrant18Factory(), stock_id=booking.stock.id, quantity=1, )
def test_raise_if_user_has_already_booked(self): booking = booking_factories.IndividualBookingFactory() with pytest.raises(exceptions.OfferIsAlreadyBooked): api.book_offer( beneficiary=booking.individualBooking.user, stock_id=booking.stock.id, quantity=1, )
def test_raise_if_no_more_stock(self): booking = factories.BookingFactory(stock__quantity=1) with pytest.raises(exceptions.StockIsNotBookable): api.book_offer( beneficiary=users_factories.UserFactory(), stock=booking.stock, quantity=1, )
def test_raise_if_user_has_no_more_money(self): stock = offers_factories.StockFactory(price=800) with pytest.raises(exceptions.UserHasInsufficientFunds): api.book_offer( beneficiary=users_factories.BeneficiaryGrant18Factory(), stock_id=stock.id, quantity=1, )
def test_user_can_book_a_free_offer_even_if_expired_deposit(self): # The user once booked. booking = factories.BookingFactory() user = booking.user # But now their deposit expired. self._expire_deposit(user) # They should be able to book free offers stock = offers_factories.StockFactory(price=0) api.book_offer(user, stock, quantity=1) assert models.Booking.query.filter_by(user=user).count() == 2
def test_raise_when_activation_codes_are_expired(self): # Given beneficiary = users_factories.BeneficiaryGrant18Factory() stock = offers_factories.StockWithActivationCodesFactory( activationCodes__expirationDate=datetime(2000, 1, 1) ) # When with pytest.raises(exceptions.NoActivationCodeAvailable) as error: api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) # Then assert error.value.errors == { "noActivationCodeAvailable": ["Ce stock ne contient plus de code d'activation disponible."] }
def test_create_booking(self, app): beneficiary = users_factories.BeneficiaryGrant18Factory() stock = offers_factories.StockFactory(price=10, dnBookedQuantity=5) assert models.Booking.query.count() == 0 # 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.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) assert models.Booking.query.count() == 0 assert offers_models.Stock.query.filter_by(id=stock.id, dnBookedQuantity=5).count() == 1
def test_booked_categories_are_sent_to_batch_backend(self, app): offer1 = offers_factories.OfferFactory(subcategoryId=subcategories.SUPPORT_PHYSIQUE_FILM.id) offer2 = offers_factories.OfferFactory(subcategoryId=subcategories.CARTE_CINE_ILLIMITE.id) offers_factories.OfferFactory(subcategoryId=subcategories.ACHAT_INSTRUMENT.id) stock1 = offers_factories.StockFactory(price=10, dnBookedQuantity=5, offer=offer1) stock2 = offers_factories.StockFactory(price=10, dnBookedQuantity=5, offer=offer2) beneficiary = users_factories.BeneficiaryGrant18Factory() date_created = datetime.now() - timedelta(days=5) booking_factories.IndividualBookingFactory.create_batch( 3, individualBooking__user=beneficiary, dateCreated=date_created, stock=stock2 ) booking = api.book_offer(beneficiary=beneficiary, stock_id=stock1.id, quantity=1) # One request should have been sent to Batch with the user's # updated attributes assert len(push_testing.requests) == 1 data = push_testing.requests[0] expected_date = booking.dateCreated.strftime(BATCH_DATETIME_FORMAT) assert data["attribute_values"]["date(u.last_booking_date)"] == expected_date expected_categories = ["CINEMA", "FILM"] assert sorted(data["attribute_values"]["ut.booking_categories"]) == expected_categories
def test_raise_when_no_activation_code_available(self): # Given beneficiary = users_factories.BeneficiaryGrant18Factory() booking = booking_factories.UsedIndividualBookingFactory(token="ABCDEF") stock = offers_factories.StockWithActivationCodesFactory(activationCodes=["code-vgya451afvyux"]) stock.activationCodes[0].booking = booking # When with pytest.raises(exceptions.NoActivationCodeAvailable) as error: api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) # Then assert Booking.query.count() == 1 assert error.value.errors == { "noActivationCodeAvailable": ["Ce stock ne contient plus de code d'activation disponible."] }
def test_create_event_booking(self): ten_days_from_now = datetime.utcnow() + timedelta(days=10) beneficiary = users_factories.BeneficiaryGrant18Factory(deposit__version=1) stock = offers_factories.StockFactory(price=10, beginningDatetime=ten_days_from_now, dnBookedQuantity=5) booking = api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) # One request should have been sent to Batch with the user's # updated attributes assert len(push_testing.requests) == 1 data = push_testing.requests[0] assert data["attribute_values"]["u.credit"] == 49_000 # values in cents expected_date = booking.dateCreated.strftime(BATCH_DATETIME_FORMAT) assert data["attribute_values"]["date(u.last_booking_date)"] == expected_date two_days_after_booking = booking.dateCreated + timedelta(days=2) assert booking.quantity == 1 assert booking.amount == 10 assert booking.stock == stock assert stock.dnBookedQuantity == 6 assert len(booking.token) == 6 assert not booking.isCancelled assert not booking.isUsed assert booking.status not in [BookingStatus.CANCELLED, BookingStatus.USED] assert booking.cancellationLimitDate == two_days_after_booking
def test_free_offer_booking_by_ex_beneficiary(self): with freeze_time(datetime.utcnow() - relativedelta(years=2, months=5)): ex_beneficiary = users_factories.BeneficiaryGrant18Factory() stock = offers_factories.StockFactory(price=0, dnBookedQuantity=5, offer__bookingEmail="*****@*****.**") booking = api.book_offer(beneficiary=ex_beneficiary, stock_id=stock.id, quantity=1) assert not booking.individualBooking.deposit
def test_book_stock_with_unlimited_quantity(self): beneficiary = users_factories.BeneficiaryGrant18Factory() stock = offers_factories.StockFactory(price=10, quantity=None) booking = api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) assert booking.quantity == 1 assert stock.quantity is None
def test_booking_on_digital_offer_without_activation_stock(self): offer = offers_factories.OfferFactory(product=offers_factories.DigitalProductFactory()) stock = offers_factories.StockFactory(price=10, dnBookedQuantity=5, offer=offer) beneficiary = users_factories.BeneficiaryGrant18Factory() booking = api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) assert not booking.isUsed assert booking.status is not BookingStatus.USED
def create_booking(body: PostBookingBodyModel) -> PostBookingResponseModel: stock = Stock.query.filter_by(id=dehumanize(body.stock_id)).first_or_404() if body.stock_id else None booking = bookings_api.book_offer( beneficiary=current_user, stock=stock, quantity=body.quantity, ) return PostBookingResponseModel(**serialize_booking_minimal(booking))
def test_book_offer_with_first_activation_code_available(self): # Given beneficiary = users_factories.BeneficiaryGrant18Factory() stock = offers_factories.StockWithActivationCodesFactory() first_activation_code = stock.activationCodes[0] # When booking = api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) # Then assert booking.activationCode == first_activation_code
def create_booking(body: PostBookingBodyModel) -> PostBookingResponseModel: if not body.stock_id: abort(404) try: booking = bookings_api.book_offer( beneficiary=current_user, stock_id=dehumanize(body.stock_id), quantity=body.quantity, ) except StockDoesNotExist: abort(404) return PostBookingResponseModel(**serialize_booking_minimal(booking))
def test_ignore_activation_that_is_already_used_for_booking(self): # Given beneficiary = users_factories.BeneficiaryGrant18Factory() booking = booking_factories.UsedIndividualBookingFactory(token="ABCDEF") stock = offers_factories.StockWithActivationCodesFactory( activationCodes=["code-vgya451afvyux", "code-bha45k15fuz"] ) stock.activationCodes[0].booking = booking # When booking = api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) # Then assert booking.activationCode.code == "code-bha45k15fuz"
def test_create_event_booking(self): ten_days_from_now = datetime.utcnow() + timedelta(days=10) user = users_factories.UserFactory() stock = offers_factories.StockFactory( price=10, beginningDatetime=ten_days_from_now) booking = api.book_offer(beneficiary=user, stock=stock, quantity=1) two_days_after_booking = booking.dateCreated + timedelta(days=2) assert booking.quantity == 1 assert booking.amount == 10 assert booking.stock == stock assert len(booking.token) == 6 assert not booking.isCancelled assert not booking.isUsed assert booking.confirmationDate == two_days_after_booking
def book_offer(user: User, body: BookOfferRequest) -> None: stock = Stock.query.get(body.stock_id) if not stock: raise ApiErrors({"stock": "stock introuvable"}, status_code=404) try: bookings_api.book_offer( beneficiary=user, stock=stock, quantity=body.quantity, ) except ( exceptions.UserHasInsufficientFunds, exceptions.DigitalExpenseLimitHasBeenReached, exceptions.PhysicalExpenseLimitHasBeenReached, ): raise ApiErrors({"code": "INSUFFICIENT_CREDIT"}) except exceptions.OfferIsAlreadyBooked: raise ApiErrors({"code": "ALREADY_BOOKED"}) except exceptions.StockIsNotBookable: raise ApiErrors({"code": "STOCK_NOT_BOOKABLE"})
def test_offer_indexation_on_booking_cycle(app): beneficiary = users_factories.BeneficiaryGrant18Factory() stock = offers_factories.StockFactory(quantity=1) offer = stock.offer assert search_testing.search_store["offers"] == {} search.async_index_offer_ids([offer.id]) assert search_testing.search_store["offers"] == {} search.index_offers_in_queue() assert offer.id in search_testing.search_store["offers"] booking = bookings_api.book_offer(beneficiary, stock.id, quantity=1) search.index_offers_in_queue() assert offer.id not in search_testing.search_store["offers"] bookings_api.cancel_booking_by_beneficiary(beneficiary, booking) search.index_offers_in_queue() assert offer.id in search_testing.search_store["offers"]
def test_create_booking(self, mocked_add_offer_id, mocked_send_raw_email): user = users_factories.UserFactory() stock = offers_factories.StockFactory(price=10) booking = api.book_offer(beneficiary=user, stock=stock, quantity=1) assert booking.quantity == 1 assert booking.amount == 10 assert booking.stock == stock assert len(booking.token) == 6 assert not booking.isCancelled assert not booking.isUsed assert booking.confirmationDate is None mocked_add_offer_id.assert_called_once_with(client=app.redis_client, offer_id=stock.offer.id) email_data1 = mocked_send_raw_email.call_args_list[0][1]["data"] assert email_data1["MJ-TemplateID"] == 2113444 # to offerer email_data2 = mocked_send_raw_email.call_args_list[1][1]["data"] assert email_data2["MJ-TemplateID"] == 1163067 # to beneficiary
def test_create_booking(self, mocked_async_index_offer_ids, app): beneficiary = users_factories.BeneficiaryGrant18Factory(deposit__version=1) stock = offers_factories.StockFactory(price=10, dnBookedQuantity=5, offer__bookingEmail="*****@*****.**") booking = api.book_offer(beneficiary=beneficiary, stock_id=stock.id, quantity=1) # One request should have been sent to Batch with the user's # updated attributes assert len(push_testing.requests) == 1 data = push_testing.requests[0] assert data["attribute_values"]["u.credit"] == 49_000 # values in cents assert data["attribute_values"]["ut.booking_categories"] == ["FILM"] expected_date = booking.dateCreated.strftime(BATCH_DATETIME_FORMAT) assert data["attribute_values"]["date(u.last_booking_date)"] == expected_date expected_date = booking.dateCreated.strftime(BATCH_DATETIME_FORMAT) assert data["attribute_values"]["date(u.last_booking_date)"] == expected_date assert booking.quantity == 1 assert booking.individualBookingId is not None assert booking.individualBooking.userId == beneficiary.id assert booking.individualBooking.depositId == beneficiary.deposit.id assert booking.amount == 10 assert booking.stock == stock assert len(booking.token) == 6 assert not booking.isCancelled assert not booking.isUsed assert booking.status not in [BookingStatus.CANCELLED, BookingStatus.USED] assert booking.cancellationLimitDate is None assert stock.dnBookedQuantity == 6 mocked_async_index_offer_ids.assert_called_once_with([stock.offer.id]) assert len(mails_testing.outbox) == 2 email_data1 = mails_testing.outbox[0].sent_data assert email_data1["MJ-TemplateID"] == 3095147 # to offerer email_data2 = mails_testing.outbox[1].sent_data assert email_data2["MJ-TemplateID"] == 3094927 # to beneficiary
def test_create_booking(self, mocked_add_offer_id): user = users_factories.UserFactory() stock = offers_factories.StockFactory(price=10) booking = api.book_offer(beneficiary=user, stock=stock, quantity=1) assert booking.quantity == 1 assert booking.amount == 10 assert booking.stock == stock assert len(booking.token) == 6 assert not booking.isCancelled assert not booking.isUsed assert booking.confirmationDate is None mocked_add_offer_id.assert_called_once_with(client=app.redis_client, offer_id=stock.offer.id) assert len(mails_testing.outbox) == 2 email_data1 = mails_testing.outbox[0].sent_data assert email_data1["MJ-TemplateID"] == 2418750 # to offerer email_data2 = mails_testing.outbox[1].sent_data assert email_data2["MJ-TemplateID"] == 1163067 # to beneficiary
def book_offer(user: User, body: BookOfferRequest) -> BookOfferResponse: stock = Stock.query.get(body.stock_id) if not stock: logger.info("Could not book offer: stock does not exist", extra={"stock_id": body.stock_id}) raise ApiErrors({"stock": "stock introuvable"}, status_code=400) try: booking = bookings_api.book_offer( beneficiary=user, stock_id=body.stock_id, quantity=body.quantity, ) except StockDoesNotExist: logger.info("Could not book offer: stock does not exist", extra={"stock_id": body.stock_id}) raise ApiErrors({"stock": "stock introuvable"}, status_code=400) except ( exceptions.UserHasInsufficientFunds, exceptions.DigitalExpenseLimitHasBeenReached, exceptions.PhysicalExpenseLimitHasBeenReached, ): logger.info("Could not book offer: insufficient credit", extra={"stock_id": body.stock_id}) raise ApiErrors({"code": "INSUFFICIENT_CREDIT"}) except exceptions.OfferIsAlreadyBooked: logger.info("Could not book offer: offer already booked", extra={"stock_id": body.stock_id}) raise ApiErrors({"code": "ALREADY_BOOKED"}) except exceptions.StockIsNotBookable: logger.info("Could not book offer: stock is not bookable", extra={"stock_id": body.stock_id}) raise ApiErrors({"code": "STOCK_NOT_BOOKABLE"}) return BookOfferResponse(bookingId=booking.id)
def test_raise_if_pro_user(self): user = users_factories.UserFactory(isBeneficiary=False, isAdmin=False) stock = offers_factories.StockFactory() with pytest.raises(exceptions.UserHasInsufficientFunds): api.book_offer(beneficiary=user, stock=stock, quantity=1)
def test_raise_if_pro_user(self): pro = users_factories.ProFactory() stock = offers_factories.StockFactory() with pytest.raises(exceptions.UserHasInsufficientFunds): api.book_offer(beneficiary=pro, stock_id=stock.id, quantity=1)
def test_raise_if_is_admin(self): user = users_factories.AdminFactory() stock = offers_factories.StockFactory() with pytest.raises(exceptions.UserHasInsufficientFunds): api.book_offer(beneficiary=user, stock_id=stock.id, quantity=1)