def test_event_offer(self): offer = factories.EventOfferFactory() first = datetime.datetime.now() + datetime.timedelta(days=1) last = datetime.datetime.now() + datetime.timedelta(days=5) factories.StockFactory(offer=offer, beginningDatetime=first) factories.StockFactory(offer=offer, beginningDatetime=last) assert offer.dateRange == DateTimes(first, last)
def should_book_an_offer_even_if_physical_offer_capping_is_exeeded( self, mocked_redis, app): # Given product = offers_factories.DigitalProductFactory() stock1 = offers_factories.StockFactory( price=200, offer__type=str(ThingType.AUDIOVISUEL), offer__product=product, ) booking = bookings_factories.BookingFactory(stock=stock1) user = booking.user stock2 = offers_factories.StockFactory( price=200, offer__type=str(ThingType.AUDIOVISUEL), offer__product=product, ) # When create_booking_for_user_on_specific_stock_bypassing_capping_limits( user.id, stock2.id) # Then assert Booking.query.filter_by(stockId=stock2.id).one() is not None mocked_redis.add_offer_id.assert_called_once_with( client=app.redis_client, offer_id=stock2.offer.id)
def test_queryNotSoftDeleted(): alive = factories.StockFactory() deleted = factories.StockFactory(isSoftDeleted=True) stocks = Stock.queryNotSoftDeleted().all() assert len(stocks) == 1 assert alive in stocks assert deleted not in stocks
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_with_stock_with_no_booking_limit_datetime(self): stock = factories.StockFactory(bookingLimitDatetime=None) offer = stock.offer past = datetime.datetime.now() - datetime.timedelta(days=1) stock = factories.StockFactory(offer=offer, isSoftDeleted=True, bookingLimitDatetime=past) assert not offer.hasBookingLimitDatetimesPassed
def test_expression_sold_out(self): sold_out_stock = factories.StockFactory(quantity=0) sold_out_offer = sold_out_stock.offer not_sold_out_stock = factories.StockFactory(quantity=10) not_sold_out_offer = not_sold_out_stock.offer assert Offer.query.filter( Offer.status == OfferStatus.SOLD_OUT.name).all() == [ sold_out_offer ] assert Offer.query.filter( Offer.status != OfferStatus.SOLD_OUT.name).all() == [ not_sold_out_offer ]
def test_offer_with_soft_deleted_stock(self): stock = factories.StockFactory(quantity=10, isSoftDeleted=True) offer = stock.offer assert offer.isSoldOut assert Offer.query.filter(Offer.isSoldOut.is_(True)).one() == offer assert Offer.query.filter(Offer.isSoldOut.is_(False)).count() == 0
def test_offer_with_unlimited_stock_is_not_sold_out(self): offer = factories.OfferFactory() factories.StockFactory(offer=offer, quantity=None) assert not offer.isSoldOut assert Offer.query.filter(Offer.isSoldOut.is_(True)).count() == 0 assert Offer.query.filter(Offer.isSoldOut.is_(False)).one() == offer
def test_offer_with_stock_quantity_is_not_sold_out(self): offer = factories.OfferFactory() factories.StockFactory(offer=offer, quantity=5) assert offer.isSoldOut is False assert Offer.query.filter(Offer.isSoldOut.is_(True)).count() == 0 assert Offer.query.filter(Offer.isSoldOut.is_(False)).all() == [offer]
def test_reject_approved_offer_with_bookings( self, mocked_send_cancel_booking_notification, mocked_send_offer_validation_notification_to_administration, mocked_send_offer_validation_status_update_email, mocked_validate_csrf_token, app, ): users_factories.AdminFactory(email="*****@*****.**") with freeze_time("2020-11-17 15:00:00") as frozen_time: offer = offers_factories.OfferFactory(validation=OfferValidationStatus.APPROVED, isActive=True) stock = offers_factories.StockFactory(offer=offer, price=10) unused_booking = booking_factories.IndividualBookingFactory(stock=stock) used_booking = booking_factories.UsedBookingFactory(stock=stock) frozen_time.move_to("2020-12-20 15:00:00") data = dict(validation=OfferValidationStatus.REJECTED.value) client = TestClient(app.test_client()).with_session_auth("*****@*****.**") response = client.post(f"/pc/back-office/offer/edit/?id={offer.id}", form=data) assert response.status_code == 302 assert offer.validation == OfferValidationStatus.REJECTED assert offer.lastValidationDate == datetime.datetime(2020, 12, 20, 15) assert unused_booking.isCancelled assert unused_booking.status is BookingStatus.CANCELLED assert not used_booking.isCancelled assert used_booking.status is not BookingStatus.CANCELLED mocked_send_cancel_booking_notification.assert_called_once_with([unused_booking.id])
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 when_current_user_has_rights_on_offer(self, app, db_session): # given offer = offers_factories.OfferFactory() offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) stock = offers_factories.StockFactory(offer=offer) booking = IndividualBookingFactory(stock=stock) # when client = TestClient( app.test_client()).with_session_auth("*****@*****.**") response = client.delete(f"/stocks/{humanize(stock.id)}") # then assert response.status_code == 200 assert response.json == {"id": humanize(stock.id)} assert stock.isSoftDeleted assert push_testing.requests[-1] == { "group_id": "Cancel_booking", "message": { "body": f"""Ta commande "{offer.name}" a été annulée par l'offreur.""", "title": "Commande annulée", }, "user_ids": [booking.individualBooking.userId], }
def when_beginning_and_booking_limit_datetime_are_updated_for_event( self, app): # given offer = offers_factories.EventOfferFactory() offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) stock = offers_factories.StockFactory( offer=offer, price=100, quantity=10, ) # when client = TestClient(app.test_client()).with_auth("*****@*****.**") beginning = datetime(2020, 1, 1, 12, 0, 0) booking_limit = datetime(2020, 1, 1, 10, 0, 0) data = { "price": 20, "quantity": 5, "beginningDatetime": beginning.isoformat(), "bookingLimitDatetime": booking_limit.isoformat(), } response = client.patch(f"/stocks/{humanize(stock.id)}", json=data) # then assert response.status_code == 200 stock = Stock.query.one() assert stock.price == 20 assert stock.quantity == 5 assert stock.beginningDatetime == beginning assert stock.bookingLimitDatetime == booking_limit
def should_not_invalidate_booking_token_when_event_is_reported_in_less_than_48_hours( self, mock_mark_as_unused, mock_update_confirmation_dates): # Given now = datetime.now() date_used_in_48_hours = datetime.now() + timedelta(days=2) event_in_3_days = now + timedelta(days=3) event_reported_in_less_48_hours = now + timedelta(days=1) offer = factories.EventOfferFactory() existing_stock = factories.StockFactory( offer=offer, beginningDatetime=event_in_3_days) booking = bookings_factories.BookingFactory( stock=existing_stock, dateCreated=now, isUsed=True, dateUsed=date_used_in_48_hours) edited_stock_data = StockEditionBodyModel( id=existing_stock.id, beginningDatetime=event_reported_in_less_48_hours, bookingLimitDatetime=existing_stock.bookingLimitDatetime, price=2, ) # When api.upsert_stocks(offer_id=offer.id, stock_data_list=[edited_stock_data]) # 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_does_not_allow_beginning_datetime_on_thing_offer_on_creation_and_edition( self, mock_update_confirmation_dates): # Given offer = factories.ThingOfferFactory() beginning_date = datetime.utcnow() + timedelta(days=4) existing_stock = factories.StockFactory(offer=offer, price=10) created_stock_data = StockCreationBodyModel( price=-1, beginningDatetime=beginning_date, bookingLimitDatetime=beginning_date) edited_stock_data = StockEditionBodyModel( id=existing_stock.id, price=0, beginningDatetime=beginning_date, bookingLimitDatetime=beginning_date) # When with pytest.raises(api_errors.ApiErrors) as error: api.upsert_stocks( offer_id=offer.id, stock_data_list=[created_stock_data, edited_stock_data]) # Then assert error.value.errors == { "global": [ "Impossible de mettre une date de début si l'offre ne porte pas sur un événement" ], }
def should_invalidate_booking_token_when_event_is_reported( self, mock_update_confirmation_dates): # Given now = datetime.now() booking_made_3_days_ago = now - timedelta(days=3) event_in_4_days = now + timedelta(days=4) event_reported_in_10_days = now + timedelta(days=10) offer = factories.EventOfferFactory() existing_stock = factories.StockFactory( offer=offer, beginningDatetime=event_in_4_days) booking = bookings_factories.BookingFactory( stock=existing_stock, dateCreated=booking_made_3_days_ago, isUsed=True) edited_stock_data = StockEditionBodyModel( id=existing_stock.id, beginningDatetime=event_reported_in_10_days, bookingLimitDatetime=existing_stock.bookingLimitDatetime, price=2, ) # When api.upsert_stocks(offer_id=offer.id, stock_data_list=[edited_stock_data]) # 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 should_update_bookings_confirmation_date_if_report_of_event( self, mock_update_confirmation_dates): # Given now = datetime.now() event_in_4_days = now + timedelta(days=4) event_reported_in_10_days = now + timedelta(days=10) offer = factories.EventOfferFactory() existing_stock = factories.StockFactory( offer=offer, beginningDatetime=event_in_4_days) booking = bookings_factories.BookingFactory(stock=existing_stock, dateCreated=now) edited_stock_data = StockEditionBodyModel( id=existing_stock.id, beginningDatetime=event_reported_in_10_days, bookingLimitDatetime=existing_stock.bookingLimitDatetime, price=2, ) # When api.upsert_stocks(offer_id=offer.id, stock_data_list=[edited_stock_data]) # Then mock_update_confirmation_dates.assert_called_once_with( [booking], event_reported_in_10_days)
def test_raise_if_not_bookable(self): yesterday = datetime.now() - timedelta(days=1) stock = offers_factories.StockFactory(bookingLimitDatetime=yesterday) with pytest.raises(exceptions.StockIsNotBookable) as error: validation.check_stock_is_bookable(stock) assert error.value.errors == {"stock": ["Ce stock n'est pas réservable"]}
def test_stock_with_unlimited_remaining_quantity(self): offer = factories.OfferFactory() stock = factories.StockFactory(offer=offer, quantity=None) assert stock.remainingQuantity == "unlimited" assert Offer.query.filter( Stock.remainingQuantity.is_(None)).one() == offer
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_do_not_update_dateModified_if_price_changes(self): initial_dt = datetime.datetime(2018, 2, 12) stock = factories.StockFactory(dateModified=initial_dt, price=1) stock.price = 10 repository.save(stock) stock = Stock.query.one() assert stock.dateModified == initial_dt
def test_cancel_booking(self): stock = offers_factories.StockFactory(offer__bookingEmail="*****@*****.**") booking = booking_factories.IndividualBookingFactory.create_batch(20, stock=stock)[0] queries = 1 # select booking queries += 1 # select stock for update queries += 1 # refresh booking queries += 3 # update stock ; update booking ; release savepoint queries += 8 # (update batch attributes): select booking ; individualBooking ; user ; user.bookings ; deposit ; user_offerer ; favorites ; stock queries += 1 # select offer queries += 2 # insert email ; release savepoint queries += 4 # (TODO: optimize) select booking ; stock ; offer ; user queries += 1 # select bookings of same stock with users joinedloaded to avoid N+1 requests queries += 2 # select venue ; offerer queries += 2 # insert email ; release savepoint with assert_num_queries(queries): api.cancel_booking_by_beneficiary(booking.individualBooking.user, booking) # cancellation can trigger more than one request to Batch assert len(push_testing.requests) >= 1 assert booking.isCancelled assert booking.status is BookingStatus.CANCELLED assert booking.cancellationReason == BookingCancellationReasons.BENEFICIARY assert len(mails_testing.outbox) == 2 email_data1 = mails_testing.outbox[0].sent_data assert email_data1["Mj-TemplateID"] == 1091464 # to beneficiary email_data2 = mails_testing.outbox[1].sent_data assert email_data2["MJ-TemplateID"] == 780015 # to offerer
def test_upsert_multiple_stocks(self, mocked_add_offer_id, mock_update_confirmation_dates): # Given offer = factories.ThingOfferFactory() existing_stock = factories.StockFactory(offer=offer, price=10) created_stock_data = StockCreationBodyModel(price=10, quantity=7) edited_stock_data = StockEditionBodyModel(id=existing_stock.id, price=5, quantity=7) # When stocks_upserted = api.upsert_stocks( offer_id=offer.id, stock_data_list=[created_stock_data, edited_stock_data]) # Then created_stock = Stock.query.filter_by(id=stocks_upserted[0].id).first() assert created_stock.offer == offer assert created_stock.price == 10 assert created_stock.quantity == 7 edited_stock = Stock.query.filter_by(id=existing_stock.id).first() assert edited_stock.price == 5 assert edited_stock.quantity == 7 mocked_add_offer_id.assert_called_once_with(client=app.redis_client, offer_id=offer.id)
def test_edit_one_stock(self, app): # Given offer = offers_factories.ThingOfferFactory() existing_stock = offers_factories.StockFactory(offer=offer, price=10) offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) # When stock_data = { "offerId": humanize(offer.id), "stocks": [{ "id": humanize(existing_stock.id), "price": 20 }], } response = TestClient( app.test_client()).with_auth("*****@*****.**").post( "/stocks/bulk/", json=stock_data) # Then assert response.status_code == 201 response_dict = response.json assert len(response_dict["stockIds"]) == len(stock_data["stocks"]) edited_stock = Stock.query.get( dehumanize(response_dict["stockIds"][0]["id"])) assert edited_stock.price == 20
def test_does_not_allow_edition_of_beginningDateTime_for_stocks_of_offers_synchronized_with_allocine( self, mock_update_confirmation_dates): # Given offer = factories.EventOfferFactory( lastProvider=offerers_factories.ProviderFactory( localClass="AllocineStocks")) date_in_the_future = datetime.utcnow() + timedelta(days=4) other_date_in_the_future = datetime.utcnow() + timedelta(days=6) existing_stock = factories.StockFactory( offer=offer, price=10, beginningDatetime=date_in_the_future) edited_stock_data = StockEditionBodyModel( id=existing_stock.id, beginningDatetime=other_date_in_the_future, bookingLimitDatetime=other_date_in_the_future, 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 == { "global": [ "Pour les offres importées, certains champs ne sont pas modifiables" ] }
def test_sends_email_if_beginning_date_changes_on_edition( self, mocked_send_email): # Given offer = factories.EventOfferFactory() existing_stock = factories.StockFactory(offer=offer, price=10) beginning = datetime.now() + timedelta(days=10) edited_stock_data = StockEditionBodyModel( id=existing_stock.id, beginningDatetime=beginning, bookingLimitDatetime=existing_stock.bookingLimitDatetime, price=2, ) booking = bookings_factories.BookingFactory(stock=existing_stock) bookings_factories.BookingFactory(stock=existing_stock, isCancelled=True) # When api.upsert_stocks(offer_id=offer.id, stock_data_list=[edited_stock_data]) # Then stock = models.Stock.query.one() assert stock.beginningDatetime == beginning notified_bookings = mocked_send_email.call_args_list[0][0][0] assert notified_bookings == [booking]
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_inactive(self): inactive_offer = factories.OfferFactory( validation=OfferValidationStatus.APPROVED, isActive=False, stocks=[factories.StockFactory()]) assert inactive_offer.status == OfferStatus.INACTIVE
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_invalid_booking_limit_datetime(self, app): # Given offer = offers_factories.ThingOfferFactory(url="https://chartreu.se") offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) existing_stock = offers_factories.StockFactory(offer=offer) offers_factories.ActivationCodeFactory(expirationDate=datetime( 2020, 5, 2, 23, 59, 59), stock=existing_stock) offers_factories.ActivationCodeFactory(expirationDate=datetime( 2020, 5, 2, 23, 59, 59), stock=existing_stock) # When stock_data = { "offerId": humanize(offer.id), "stocks": [{ "id": humanize(existing_stock.id), "bookingLimitDatetime": "2020-05-2T23:59:59Z", "price": 20.0, }], } response = (TestClient( app.test_client()).with_session_auth("*****@*****.**").post( "/stocks/bulk/", json=stock_data)) # Then assert response.status_code == 400 assert response.json["activationCodesExpirationDatetime"] == [( "La date limite de validité des codes d'activation doit être ultérieure" " d'au moins 7 jours à la date limite de réservation")]