示例#1
0
    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
            },
        }
示例#2
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": [],
            },
        }
示例#3
0
    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
示例#4
0
 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",
            },
        }
示例#6
0
 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"
     ]
示例#7
0
 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"
     ]
示例#8
0
 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
示例#10
0
    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 == []
示例#11
0
    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
示例#13
0
    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
示例#17
0
    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]
示例#18
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)
示例#19
0
    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
示例#22
0
    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)
示例#23
0
 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
示例#24
0
 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
示例#25
0
    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