def test_reimburses_95_percent_for_book_product_when_bookings_exceed_100000_euros( self): # Given cutoff = datetime.datetime.now() before_cutoff = cutoff - datetime.timedelta(days=1) beneficiary = users_factories.BeneficiaryGrant18Factory( email="*****@*****.**") offerer1 = offers_factories.OffererFactory(siren="123456789") offers_factories.BankInformationFactory( bic="BDFEFR2LCCB", iban="FR7630006000011234567890189", offerer=offerer1) venue1 = offers_factories.VenueFactory(managingOfferer=offerer1, siret="12345678912345") venue2 = offers_factories.VenueFactory(managingOfferer=offerer1, siret="98765432154321") venue3 = offers_factories.VenueFactory(managingOfferer=offerer1, siret="98123432154321") product = offers_factories.ThingProductFactory( subcategoryId=subcategories.LIVRE_PAPIER.id) offer1 = offers_factories.ThingOfferFactory(venue=venue1, product=product) offer2 = offers_factories.ThingOfferFactory(venue=venue2, product=product) offer3 = offers_factories.ThingOfferFactory(venue=venue3, product=product) paying_stock1 = offers_factories.ThingStockFactory(offer=offer1, price=10000) paying_stock2 = offers_factories.ThingStockFactory(offer=offer2, price=10000) paying_stock3 = offers_factories.ThingStockFactory(offer=offer3, price=100000) offers_factories.ThingStockFactory(offer=offer1, price=0) beneficiary.deposit.amount = 120000 repository.save(beneficiary.deposit) bookings_factories.UsedBookingFactory(user=beneficiary, stock=paying_stock1, dateUsed=before_cutoff, quantity=1) bookings_factories.UsedBookingFactory(user=beneficiary, stock=paying_stock2, dateUsed=before_cutoff, quantity=1) bookings_factories.UsedBookingFactory(user=beneficiary, stock=paying_stock3, dateUsed=before_cutoff, quantity=1) # When generate_new_payments(cutoff, batch_date=datetime.datetime.now()) # Then pending = get_pending_payments() assert pending.count() == 3 assert total_amount(pending) == 115000 assert get_not_processable_payments().count() == 0
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 when_mediation_is_created_with_bad_thumb_url(self, app): # given offer = offers_factories.ThingOfferFactory() offerer = offer.venue.managingOfferer offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offerer, ) # when client = TestClient( app.test_client()).with_auth(email="*****@*****.**") data = { "offerId": humanize(offer.id), "offererId": humanize(offerer.id), "thumbUrl": "https://example.com/image.jpg", } with requests_mock.Mocker() as mock: mock.get("https://example.com/image.jpg", status_code=404) response = client.post("/mediations", form=data) # then assert response.status_code == 400 assert response.json == { "thumbUrl": ["L'adresse saisie n'est pas valide"] }
def when_mediation_is_created_with_thumb_file(self, app): # given offer = offers_factories.ThingOfferFactory() offerer = offer.venue.managingOfferer offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offerer, ) # when client = TestClient( app.test_client()).with_auth(email="*****@*****.**") thumb = (IMAGES_DIR / "mouette_full_size.jpg").read_bytes() files = { "offerId": humanize(offer.id), "offererId": humanize(offerer.id), "thumb": (BytesIO(thumb), "image.png"), } response = client.post("/mediations", files=files) # then assert response.status_code == 201 mediation = Mediation.query.one() assert mediation.thumbCount == 1 assert response.status_code == 201
def when_mediation_is_created_with_thumb_url(self, app): # given offer = offers_factories.ThingOfferFactory() offerer = offer.venue.managingOfferer offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offerer, ) # when client = TestClient( app.test_client()).with_auth(email="*****@*****.**") image_as_bytes = (IMAGES_DIR / "mouette_full_size.jpg").read_bytes() data = { "offerId": humanize(offer.id), "offererId": humanize(offerer.id), "thumbUrl": "https://example.com/image.jpg", } with requests_mock.Mocker() as mock: mock.get( "https://example.com/image.jpg", content=image_as_bytes, headers={"Content-Type": "image/jpeg"}, ) response = client.post("/mediations", form=data) # then assert response.status_code == 201 mediation = Mediation.query.one() assert mediation.thumbCount == 1 assert response.json == { "id": humanize(mediation.id), }
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 when_user_has_no_rights_and_creating_stock_from_offer_id( self, app, db_session): # Given user = users_factories.UserFactory(email="*****@*****.**") offer = offers_factories.ThingOfferFactory() offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer) booking_datetime = datetime.utcnow() # When stock_data = { "offerId": humanize(offer.id), "stocks": [ { "quantity": 10, "price": 0, "bookingLimitDatetime": serialize(booking_datetime), }, ], } response = TestClient(app.test_client()).with_auth(user.email).post( "/stocks/bulk/", json=stock_data) # Then assert response.status_code == 403 assert response.json == { "global": [ "Vous n'avez pas les droits d'accès suffisant pour accéder à cette information." ] }
def when_missing_offer_id(self, app): # Given offer = offers_factories.ThingOfferFactory() offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) booking_limit_datetime = datetime(2019, 2, 14) # When stock_data = { "stocks": [ { "quantity": -2, "price": 0, "bookingLimitDatetime": serialize(booking_limit_datetime), }, ], } response = TestClient( app.test_client()).with_auth("*****@*****.**").post( "/stocks/bulk/", json=stock_data) # Then assert response.status_code == 400 assert response.json == {"offerId": ["Ce champ est obligatoire"]}
def test_erase_former_mediations(self): # Given user = users_factories.UserFactory() offer = factories.ThingOfferFactory() image_as_bytes = (IMAGES_DIR / "mouette_full_size.jpg").read_bytes() existing_number_of_files = len(os.listdir(self.THUMBS_DIR)) mediation_1 = api.create_mediation_v2(user, offer, "©Photographe", image_as_bytes) mediation_2 = api.create_mediation_v2(user, offer, "©Alice", image_as_bytes) thumb_1_id = humanize(mediation_1.id) thumb_2_id = humanize(mediation_2.id) # When api.create_mediation_v2(user, offer, "©moi", image_as_bytes) # Then mediation_3 = models.Mediation.query.one() assert mediation_3.credit == "©moi" thumb_3_id = humanize(mediation_3.id) assert not (self.THUMBS_DIR / thumb_1_id).exists() assert not (self.THUMBS_DIR / (thumb_1_id + ".type")).exists() assert not (self.THUMBS_DIR / thumb_2_id).exists() assert not (self.THUMBS_DIR / (thumb_2_id + ".type")).exists() assert len(os.listdir(self.THUMBS_DIR)) == existing_number_of_files + 2 assert (self.THUMBS_DIR / thumb_3_id).exists() assert (self.THUMBS_DIR / (thumb_3_id + ".type")).exists()
def test_when_offer_is_not_digital(self, app): # Given offer = offers_factories.ThingOfferFactory(url=None) offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) # When stock_data = { "offerId": humanize(offer.id), "stocks": [{ "price": 20, "activationCodes": ["AZ3"], "bookingLimitDatetime": "2021-06-15T02:59:59Z", "activationCodesExpirationDatetime": "2021-07-15T02:59:59Z", }], } response = (TestClient( app.test_client()).with_session_auth("*****@*****.**").post( "/stocks/bulk/", json=stock_data)) # Then assert response.status_code == 400 assert response.json["global"] == [ "Impossible de créer des codes d'activation sur une offre non-numérique" ]
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")]
def test_invalid_activation_codes_expiration_datetime(self, app): # Given offer = offers_factories.ThingOfferFactory(url="https://chartreu.se") offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) # When stock_data = { "offerId": humanize(offer.id), "stocks": [{ "price": 20, "activationCodes": ["AZ3"], "bookingLimitDatetime": "2021-06-15T02:59:59Z", "activationCodesExpirationDatetime": "2021-06-16T02:59:59Z", }], } 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")]
def when_setting_beginning_datetime_on_offer_with_thing( self, app, db_session): # Given user = users_factories.UserFactory(isAdmin=True) offer = offers_factories.ThingOfferFactory() beginningDatetime = datetime(2019, 2, 14) data = { "price": 0, "offerId": humanize(offer.id), "beginningDatetime": serialize(beginningDatetime), "bookingLimitDatetime": serialize(beginningDatetime - timedelta(days=2)), } # When response = TestClient(app.test_client()).with_auth(user.email).post( "/stocks", json=data) # Then assert response.status_code == 400 assert response.json == { "global": [ "Impossible de mettre une date de début si l'offre ne porte pas sur un événement" ] }
def test_thing_prices(self): offer = offers_factories.ThingOfferFactory() validation.check_stock_price(0, offer) validation.check_stock_price(1.5, offer) with pytest.raises(ApiErrors) as error: validation.check_stock_price(-1.5, offer) assert error.value.errors["price"] == ["Le prix doit être positif"]
def test_thing_offer_ok_without_booking_limit_datetime(self): offer = factories.ThingOfferFactory() validation.check_required_dates_for_stock( offer, beginning=None, booking_limit_datetime=None, )
def test_fail_if_offer_is_not_editable(self): provider = offerers_factories.ProviderFactory() offer = factories.ThingOfferFactory(lastProvider=provider) with pytest.raises(api_errors.ApiErrors) as error: api.create_stock(offer=offer, price=10, beginning=None, booking_limit_datetime=None) assert error.value.errors == {"global": ["Les offres importées ne sont pas modifiables"]}
def test_check_image_quality(self): user = users_factories.UserFactory() offer = factories.ThingOfferFactory() image_as_bytes = (IMAGES_DIR / "mouette_small.jpg").read_bytes() with pytest.raises(models.ApiErrors) as error: api.create_mediation(user, offer, "credits", image_as_bytes) assert error.value.errors["thumb"] == ["L'image doit faire 400 * 400 px minimum"] assert models.Mediation.query.count() == 0
def test_should_return_mailjet_data_when_multiple_bookings_and_offer_is_a_thing(self, build_pc_pro_offer_link): # Given beneficiary = users_factories.BeneficiaryGrant18Factory( publicName="John Doe", firstName="John", lastName="Doe", email="*****@*****.**" ) offer = offer_factories.ThingOfferFactory( venue__name="La petite librairie", venue__publicName="La grande librairie", product__name="Le récit de voyage", ) booking = booking_factories.IndividualBookingFactory( user=beneficiary, individualBooking__user=beneficiary, stock__offer=offer, stock__price=0, token="12346", quantity=6, ) other_beneficiary = users_factories.BeneficiaryGrant18Factory( publicName="James Bond", firstName="James", lastName="Bond", email="*****@*****.**" ) booking2 = booking_factories.IndividualBookingFactory( user=other_beneficiary, individualBooking__user=other_beneficiary, stock__offer=offer, stock__price=0, token="12345", quantity=1, ) bookings = [booking, booking2] # When mailjet_data = retrieve_offerer_bookings_recap_email_data_after_offerer_cancellation(bookings) # Then assert mailjet_data == { "MJ-TemplateID": 1116333, "MJ-TemplateLanguage": True, "Vars": { "offer_name": "Le récit de voyage", "lien_offre_pcpro": "http://pc_pro.com/offer_link", "venue_name": "La grande librairie", "price": "Gratuit", "is_event": 0, "event_date": "", "event_hour": "", "quantity": 7, "reservations_number": 2, "users": [ {"countermark": "12346", "email": "*****@*****.**", "firstName": "John", "lastName": "Doe"}, {"countermark": "12345", "email": "*****@*****.**", "firstName": "James", "lastName": "Bond"}, ], }, }
def test_creates_pending_and_not_processable_payments(self): # Given cutoff = datetime.datetime.now() before_cutoff = cutoff - datetime.timedelta(days=1) beneficiary = users_factories.BeneficiaryGrant18Factory( email="*****@*****.**") offerer1 = offers_factories.OffererFactory(siren="123456789") offerer2 = offers_factories.OffererFactory(siren="987654321") offers_factories.BankInformationFactory( bic="BDFEFR2LCCB", iban="FR7630006000011234567890189", offerer=offerer1) venue1 = offers_factories.VenueFactory(managingOfferer=offerer1, siret="12345678912345") venue2 = offers_factories.VenueFactory(managingOfferer=offerer2, siret="98765432154321") offer1 = offers_factories.ThingOfferFactory(venue=venue1) offer2 = offers_factories.ThingOfferFactory(venue=venue2) paying_stock1 = offers_factories.ThingStockFactory(offer=offer1) paying_stock2 = offers_factories.ThingStockFactory(offer=offer2) free_stock1 = offers_factories.ThingStockFactory(offer=offer1, price=0) bookings_factories.UsedBookingFactory(user=beneficiary, stock=paying_stock1, dateUsed=before_cutoff) bookings_factories.UsedBookingFactory(user=beneficiary, stock=paying_stock1, dateUsed=before_cutoff) bookings_factories.UsedBookingFactory(user=beneficiary, stock=paying_stock2, dateUsed=before_cutoff) bookings_factories.UsedBookingFactory(user=beneficiary, stock=free_stock1, dateUsed=before_cutoff) # When generate_new_payments(cutoff, batch_date=datetime.datetime.now()) # Then assert get_pending_payments().count() == 2 assert get_not_processable_payments().count() == 1
def test_returns_a_list_of_beneficiary_favorites(self, app): # given beneficiary = users_factories.UserFactory() venue = offers_factories.VenueFactory() offer_1 = offers_factories.ThingOfferFactory(venue=venue) mediation_1 = offers_factories.MediationFactory(offer=offer_1) users_factories.FavoriteFactory(mediation=mediation_1, offer=offer_1, user=beneficiary) offer_2 = offers_factories.ThingOfferFactory(venue=venue) users_factories.FavoriteFactory(offer=offer_2, user=beneficiary) # when favorites = repository.find_favorites_domain_by_beneficiary( beneficiary.id) # then assert len(favorites) == 2 assert isinstance(favorites[0], FavoriteDomain) assert isinstance(favorites[1], FavoriteDomain)
def test_crop_params(self, mocked_store_public_object): user = users_factories.UserFactory() offer = factories.ThingOfferFactory() image_as_bytes = (IMAGES_DIR / "mouette_full_size.jpg").read_bytes() crop_params = (0.8, 0.8, 1) mediation = api.create_mediation(user, offer, "credits", image_as_bytes, crop_params) assert mediation.thumbCount == 1 resized_as_bytes = mocked_store_public_object.call_args[1]["blob"] resized = PIL.Image.open(io.BytesIO(resized_as_bytes)) assert resized.size == (357, 357)
def test_create_thing_offer(self, mocked_add_offer_id): offer = factories.ThingOfferFactory() stock = api.create_stock(offer=offer, price=10) assert stock.offer == offer assert stock.price == 10 assert stock.quantity is None assert stock.beginningDatetime is None assert stock.bookingLimitDatetime is None mocked_add_offer_id.assert_called_once_with(client=app.redis_client, offer_id=offer.id)
def create_digital_booking(quantity=1, price=10, user=None, product_type=None): user = user or users_factories.UserFactory() product_kwargs = {} if product_type: product_kwargs = {"type": product_type} product = offers_factories.DigitalProductFactory(**product_kwargs) stock = offers_factories.StockFactory( price=price, offer=offers_factories.ThingOfferFactory(product=product), ) return bookings_factories.BookingFactory(user=user, stock=stock, quantity=quantity)
def get_pro_user(): user_offerer = offers_factories.UserOffererFactory( validationToken=None, offerer__validationToken=None, user__validationToken=None, user__phoneNumber="01 00 00 00 00", ) venue = offers_factories.VenueFactory(managingOfferer=user_offerer.offerer) offers_factories.ThingOfferFactory(venue=venue, isActive=True) return {"user": get_pro_helper(user_offerer.user)}
def test_create_mediation_basics(self, mocked_add_offer_id): user = users_factories.UserFactory() offer = factories.ThingOfferFactory() image_as_bytes = (IMAGES_DIR / "mouette_full_size.jpg").read_bytes() mediation = api.create_mediation(user, offer, "credits", image_as_bytes) assert mediation.author == user assert mediation.offer == offer assert mediation.credit == "credits" assert mediation.thumbCount == 1 mocked_add_offer_id.assert_called_once_with(client=app.redis_client, offer_id=offer.id)
def test_do_not_sync_algolia_if_feature_is_disabled( self, mocked_add_offer_id, mock_update_confirmation_dates): # Given offer = factories.ThingOfferFactory() created_stock_data = StockCreationBodyModel(price=10) # When api.upsert_stocks(offer_id=offer.id, stock_data_list=[created_stock_data]) # Then mocked_add_offer_id.assert_not_called()
def test_thing_offer_must_not_have_beginning(self): offer = factories.ThingOfferFactory() with pytest.raises(ApiErrors) as error: validation.check_required_dates_for_stock( offer, beginning=datetime.datetime.now(), booking_limit_datetime=None, ) assert error.value.errors["global"] == [ "Impossible de mettre une date de début si l'offre ne porte pas sur un événement" ]
def test_access_even_if_offerer_has_no_siren(self, app): # Given beneficiary = users_factories.UserFactory() offer = offers_factories.ThingOfferFactory( venue__managingOfferer__siren=None, ) # When client = TestClient( app.test_client()).with_auth(email=beneficiary.email) response = client.get(f"/offers/{humanize(offer.id)}") # Then assert response.status_code == 200
def test_does_not_allow_edition_of_stock_of_another_offer_than_given( self, mock_update_confirmation_dates): # Given offer = factories.ThingOfferFactory() other_offer = factories.ThingOfferFactory() existing_stock_on_other_offer = factories.StockFactory( offer=other_offer, price=10) edited_stock_data = StockEditionBodyModel( id=existing_stock_on_other_offer.id, price=30) # 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.status_code == 403 assert error.value.errors == { "global": [ "Vous n'avez pas les droits d'accès suffisant pour accéder à cette information." ] }