def should_fail_when_url_has_no_scheme(self, app): # Given virtual_venue = offers_factories.VirtualVenueFactory() offer = offers_factories.OfferFactory(venue=virtual_venue) offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) # When data = { "name": "Les lièvres pas malins", "url": "missing.something", } client = TestClient(app.test_client()).with_auth("*****@*****.**") response = client.patch(f"offers/{humanize(offer.id)}", json=data) # Then assert response.status_code == 400 assert response.json["url"] == ['L\'URL doit commencer par "http://" ou "https://"']
def test_digital_limit(self): beneficiary = self._get_beneficiary() product = offers_factories.DigitalProductFactory( subcategoryId=subcategories.VOD.id) offer = offers_factories.OfferFactory(product=product) factories.IndividualBookingFactory( individualBooking__user=beneficiary, stock__price=90, 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 100 € pour les offres numériques ne vous permet pas de réserver cette offre." ]
def should_fail_when_externalTicketOfficeUrl_has_no_host(self, app): # Given virtual_venue = offers_factories.VirtualVenueFactory() offer = offers_factories.OfferFactory(venue=virtual_venue) offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) # When data = { "name": "Les lièvres pas malins", "externalTicketOfficeUrl": "https://missing", } client = TestClient(app.test_client()).with_auth("*****@*****.**") response = client.patch(f"offers/{humanize(offer.id)}", json=data) # Then assert response.status_code == 400 assert response.json["externalTicketOfficeUrl"] == ['L\'URL doit terminer par une extension (ex. ".fr")']
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_stock_is_on_an_offer_from_titelive_provider( self, app, db_session): # given provider = offerers_factories.ProviderFactory( localClass="TiteLiveThings") offer = offers_factories.OfferFactory(lastProvider=provider, idAtProviders="1") stock = offers_factories.StockFactory(offer=offer) user = users_factories.UserFactory(isAdmin=True) # when client = TestClient(app.test_client()).with_auth(user.email) response = client.delete(f"/stocks/{humanize(stock.id)}") # then assert response.status_code == 400 assert response.json["global"] == [ "Les offres importées ne sont pas modifiables" ]
def test_approve_offer_and_send_mail_to_administration( self, mocked_send_offer_validation_notification_to_administration, mocked_get_offerer_legal_category, mocked_validate_csrf_token, app, ): # Given config_yaml = """ minimum_score: 0.6 rules: - name: "check offer name" factor: 0 conditions: - model: "Offer" attribute: "name" condition: operator: "not in" comparated: "REJECTED" """ import_offer_validation_config(config_yaml) users_factories.AdminFactory(email="*****@*****.**") offer = offers_factories.OfferFactory(validation=OfferValidationStatus.PENDING, isActive=True) mocked_get_offerer_legal_category.return_value = { "legal_category_code": 5202, "legal_category_label": "Société en nom collectif", } data = dict(validation=OfferValidationStatus.APPROVED.value, action="save") client = TestClient(app.test_client()).with_session_auth("*****@*****.**") # When response = client.post(f"/pc/back-office/validation/edit?id={offer.id}", form=data) # Then assert response.status_code == 302 assert response.headers["location"] == "http://localhost/pc/back-office/validation/" assert offer.validation == OfferValidationStatus.APPROVED mocked_send_offer_validation_notification_to_administration.assert_called_once_with( OfferValidationStatus.APPROVED, offer ) assert offer.lastValidationDate == datetime.datetime(2020, 11, 17, 15)
def when_trying_to_patch_forbidden_attributes(self, app, client): # Given offer = offers_factories.OfferFactory() offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=offer.venue.managingOfferer, ) # When data = { "dateCreated": serialize(datetime(2019, 1, 1)), "dateModifiedAtLastProvider": serialize(datetime(2019, 1, 1)), "id": 1, "idAtProviders": 1, "lastProviderId": 1, "owningOffererId": "AA", "thumbCount": 2, "subcategoryId": subcategories.LIVRE_PAPIER, "type": subcategories.LIVRE_PAPIER.matching_type, } response = client.with_session_auth("*****@*****.**").patch( f"offers/{humanize(offer.id)}", json=data) # Then assert response.status_code == 400 assert response.json["owningOffererId"] == [ "Vous ne pouvez pas changer cette information" ] forbidden_keys = { "dateCreated", "dateModifiedAtLastProvider", "id", "idAtProviders", "lastProviderId", "owningOffererId", "thumbCount", "subcategoryId", "type", } for key in forbidden_keys: assert key in response.json
def test_returns_a_thing_with_activation_code_stock(self, app): # Given beneficiary = users_factories.BeneficiaryGrant18Factory() offer = offers_factories.OfferFactory( stocks=[offers_factories.StockWithActivationCodesFactory()], subcategoryId=subcategories.ABO_PLATEFORME_MUSIQUE.id, url="fake-url", ) # When client = TestClient( app.test_client()).with_session_auth(email=beneficiary.email) response = client.get(f"/offers/{humanize(offer.id)}") # Then assert response.status_code == 200 data = response.json assert data["stocks"][0]["cancellationLimitDate"] is None assert data["subcategoryId"] == "ABO_PLATEFORME_MUSIQUE" assert data["stocks"][0]["hasActivationCode"] is True
def should_ignore_soft_deleted_stocks(self, app): offer = offers_factories.OfferFactory() offers_factories.StockFactory( offer=offer, bookingLimitDatetime=datetime(2019, 12, 31), # within range ) offers_factories.StockFactory( offer=offer, bookingLimitDatetime=datetime(2020, 1, 31), # in the future isSoftDeleted=True, ) expired_offer_ids = get_paginated_offer_ids_given_booking_limit_datetime_interval( limit=1, page=0, from_date=datetime(2019, 12, 30, 10, 0, 0), to_date=datetime(2019, 12, 31, 10, 0, 0)) # Then expired_offer_ids = from_tuple_to_int(expired_offer_ids) assert expired_offer_ids == [offer.id]
def test_forbidden_on_imported_offer_on_other_fields(self): provider = offerers_factories.ProviderFactory() offer = factories.OfferFactory(lastProvider=provider, name="Old name", isDuo=False, audioDisabilityCompliant=True) with pytest.raises(models.ApiErrors) as error: api.update_offer(offer, name="New name", isDuo=True, audioDisabilityCompliant=False) assert error.value.errors == { "name": ["Vous ne pouvez pas modifier ce champ"], "isDuo": ["Vous ne pouvez pas modifier ce champ"], } offer = models.Offer.query.one() assert offer.name == "Old name" assert offer.isDuo == False assert offer.audioDisabilityCompliant == True
def test_a_digital_booking_with_activation_code_is_automatically_used( self): # Given offer = offers_factories.OfferFactory( venue__name="Lieu de l'offreur", venue__managingOfferer__name="Théâtre du coin", product=offers_factories.DigitalProductFactory( name="Super offre numérique", url="http://example.com"), ) digital_stock = offers_factories.StockWithActivationCodesFactory() first_activation_code = digital_stock.activationCodes[0] booking = bookings_factories.UsedIndividualBookingFactory( individualBooking__user__email="*****@*****.**", individualBooking__user__firstName="John", individualBooking__user__lastName="Doe", stock__offer=offer, activationCode=first_activation_code, dateCreated=datetime(2018, 1, 1), ) # When email_data = retrieve_data_for_offerer_booking_recap_email( booking.individualBooking) # Then expected = get_expected_base_email_data( booking, date="", heure="", prix="10.00 €", is_event=0, nom_offre="Super offre numérique", offer_type="VOD", quantity=1, can_expire=0, is_booking_autovalidated=1, must_use_token_for_payment=0, contremarque=booking.token, ) assert email_data == expected
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_accepts_request(app): ProviderFactory(name="Pass Culture API Stocks", localClass="PCAPIStocks") offerer = offers_factories.OffererFactory(siren=123456789) venue = offers_factories.VenueFactory(managingOfferer=offerer, id=3) offer_to_update = offers_factories.OfferFactory( product__idAtProviders="123456789", product__subcategoryId="LIVRE_PAPIER", idAtProviders=f"123456789@{venue.id}", venue=venue, ) ApiKeyFactory(offerer=offerer) test_client = TestClient(app.test_client()) test_client.auth_header = { "Authorization": f"Bearer {DEFAULT_CLEAR_API_KEY}" } response = test_client.post( f"/v2/venue/{venue.id}/stocks", json={ "stocks": [ { "ref": "123456789", "available": 4, "price": 30 }, { "ref": "1234567890", "available": 0, "price": 10 }, ] }, ) assert response.status_code == 204 assert len(offer_to_update.stocks) == 1 assert offer_to_update.stocks[0].quantity == 4 assert offer_to_update.stocks[0].price == 30
def test_rejection_email(self): # Given offer = offer_factories.OfferFactory(name="Ma petite offre", venue__name="Mon stade") # When new_offer_validation_email = retrieve_data_for_offer_rejection_email( offer) # Then assert new_offer_validation_email == { "MJ-TemplateID": 2613942, "FromEmail": "*****@*****.**", "MJ-TemplateLanguage": True, "Vars": { "offer_name": "Ma petite offre", "venue_name": "Mon stade", "pc_pro_offer_link": f"{settings.PRO_URL}/offres/{humanize(offer.id)}/edition", }, }
def test_approve_rejected_offer( self, 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.REJECTED, isActive=True) frozen_time.move_to("2020-12-20 15:00:00") data = dict(validation=OfferValidationStatus.APPROVED.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.APPROVED assert offer.lastValidationDate == datetime.datetime(2020, 12, 20, 15) mocked_send_offer_validation_notification_to_administration.assert_called_once_with( OfferValidationStatus.APPROVED, offer ) assert mocked_send_offer_validation_status_update_email.call_count == 1
def test_patch_non_approved_offer_fails(self, app): pending_validation_offer = offers_factories.OfferFactory( validation=OfferValidationStatus.PENDING) stock = offers_factories.StockFactory(offer=pending_validation_offer) offers_factories.UserOffererFactory( user__email="*****@*****.**", offerer=pending_validation_offer.venue.managingOfferer, ) stock_data = { "offerId": humanize(pending_validation_offer.id), "stocks": [{ "id": humanize(stock.id), "price": 20 }], } response = (TestClient( app.test_client()).with_session_auth("*****@*****.**").post( "/stocks/bulk/", json=stock_data)) assert response.status_code == 400 assert response.json["global"] == [ "Les offres refusées ou en attente de validation ne sont pas modifiables" ]
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.UserFactory(deposit__version=1) bookings_factories.BookingFactory(user=user, stock=stock1) bookings_factories.BookingFactory(user=user, stock=stock2, isCancelled=True) bookings_factories.BookingFactory(user=user, stock=stock3, isUsed=True, 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_use_product(self): product = factories.ProductFactory(thumbCount=1) offer = factories.OfferFactory(product=product) assert offer.thumbUrl.startswith("http") assert offer.thumbUrl == product.thumbUrl
def test_mark_as_unused_digital_offer(self): offer = offers_factories.OfferFactory(product=offers_factories.DigitalProductFactory()) booking = booking_factories.UsedIndividualBookingFactory(stock__offer=offer) api.mark_as_unused(booking) assert not booking.isUsed assert booking.status is not BookingStatus.USED
def test_editable_if_from_allocine(self): provider = providers_factories.ProviderFactory(localClass="AllocineStocks") offer = factories.OfferFactory(lastProvider=provider) assert offer.isEditable
def test_not_editabe_if_from_another_provider(self): provider = providers_factories.ProviderFactory(localClass="TiteLiveStocks") offer = factories.OfferFactory(lastProvider=provider) assert not offer.isEditable
def test_stock_with_positive_remaining_quantity(self): offer = factories.OfferFactory() stock = factories.StockFactory(offer=offer, quantity=5) assert stock.remainingQuantity == 5 assert Offer.query.filter(Stock.remainingQuantity == 5).one() == offer
def test_editable_if_not_from_provider(self): offer = factories.OfferFactory() assert offer.isEditable
def test_bookings_quantity_without_bookings(self): offer = factories.OfferFactory() stock = factories.StockFactory(offer=offer, quantity=None) assert Stock.query.filter(Stock.dnBookedQuantity == 0).one() == stock
def test_offer_without_stocks(self): offer = factories.OfferFactory() 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_pending(self): pending_offer = factories.OfferFactory( validation=OfferValidationStatus.PENDING) assert pending_offer.status == OfferStatus.PENDING
def test_rejected(self): rejected_offer = factories.OfferFactory( validation=OfferValidationStatus.REJECTED, isActive=False) assert rejected_offer.status == OfferStatus.REJECTED
def test_factory_object_defaults_to_approved(self): offer = factories.OfferFactory() assert offer.validation == OfferValidationStatus.APPROVED
def test_does_not_raise_error_when_offer_type_is_editable(self): offer = factories.OfferFactory(lastProviderId=None) validation.check_offer_is_editable(offer) # should not raise
def should_not_raise_an_error_when_offer_is_not_from_provider(self): offer = factories.OfferFactory(lastProvider=None) validation.check_stocks_are_editable_for_offer( offer) # should not raise