コード例 #1
0
    def test_notify_on_quote_cancelled(self, mocked_notify_client):
        """Test that a notification is triggered when a quote is cancelled."""
        order = OrderWithOpenQuoteFactory(assignees=[])
        OrderAssigneeFactory.create_batch(1, order=order)
        OrderSubscriberFactory.create_batch(2, order=order)

        mocked_notify_client.reset_mock()

        order.reopen(by=AdviserFactory())

        #  1 = customer, 3 = assignees/subscribers
        assert len(
            mocked_notify_client.send_email_notification.call_args_list) == (
                3 + 1)

        templates_called = [
            data[1]['template_id'] for data in
            mocked_notify_client.send_email_notification.call_args_list
        ]
        assert templates_called == [
            Template.quote_cancelled_for_customer.value,
            Template.quote_cancelled_for_adviser.value,
            Template.quote_cancelled_for_adviser.value,
            Template.quote_cancelled_for_adviser.value,
        ]
コード例 #2
0
    def test_advisers_notified(self):
        """
        Test that calling `quote_generated` sends an email to all advisers notifying them that
        the quote has been sent.
        """
        order = OrderWithOpenQuoteFactory(assignees=[])
        assignees = OrderAssigneeFactory.create_batch(2, order=order)
        subscribers = OrderSubscriberFactory.create_batch(2, order=order)

        notify.client.reset_mock()

        notify.quote_generated(order)

        assert notify.client.send_email_notification.called
        # 1 = customer, 4 = assignees/subscribers
        assert len(
            notify.client.send_email_notification.call_args_list) == (4 + 1)

        calls_by_email = {
            data['email_address']: {
                'template_id': data['template_id'],
                'personalisation': data['personalisation'],
            }
            for _, data in notify.client.send_email_notification.call_args_list
        }
        for item in itertools.chain(assignees, subscribers):
            call = calls_by_email[item.adviser.get_current_email()]
            assert call['template_id'] == Template.quote_sent_for_adviser.value
            assert call['personalisation'][
                'recipient name'] == item.adviser.name
            assert call['personalisation'][
                'embedded link'] == order.get_datahub_frontend_url()
コード例 #3
0
    def test_get(self):
        """Test a successful call to get a quote."""
        order = OrderWithOpenQuoteFactory()
        quote = order.quote

        url = reverse('api-v3:omis:quote:detail', kwargs={'order_pk': order.pk})
        response = self.api_client.get(url)

        assert response.status_code == status.HTTP_200_OK
        assert response.json() == {
            'created_on': format_date_or_datetime(quote.created_on),
            'created_by': {
                'id': str(quote.created_by.pk),
                'first_name': quote.created_by.first_name,
                'last_name': quote.created_by.last_name,
                'name': quote.created_by.name,
            },
            'cancelled_on': None,
            'cancelled_by': None,
            'accepted_on': None,
            'accepted_by': None,
            'expires_on': quote.expires_on.isoformat(),
            'content': quote.content,
            'terms_and_conditions': TermsAndConditions.objects.first().content,
        }
コード例 #4
0
    def test_accept(self):
        """Test that a quote can get accepted."""
        order = OrderWithOpenQuoteFactory()
        quote = order.quote

        url = reverse(
            f'api-v3:omis-public:quote:accept',
            kwargs={'public_token': order.public_token},
        )

        client = self.create_api_client(
            scope=Scope.public_omis_front_end,
            grant_type=Application.GRANT_CLIENT_CREDENTIALS,
        )
        with freeze_time('2017-07-12 13:00'):
            response = client.post(url)

            assert response.status_code == status.HTTP_200_OK
            assert response.json() == {
                'created_on': format_date_or_datetime(quote.created_on),
                'accepted_on': format_date_or_datetime(now()),
                'cancelled_on': None,
                'expires_on': quote.expires_on.isoformat(),
                'content': quote.content,
                'terms_and_conditions': TermsAndConditions.objects.first().content,
            }

            quote.refresh_from_db()
            assert quote.is_accepted()
            assert quote.accepted_on == now()
コード例 #5
0
    def test_with_active_quote(self):
        """
        Test that if an order with an active quote is reopened, the quote is cancelled.
        """
        order = OrderWithOpenQuoteFactory()
        assert not order.quote.is_cancelled()

        adviser = AdviserFactory()

        with freeze_time('2017-07-12 13:00'):
            order.reopen(by=adviser)

            assert order.quote.is_cancelled()
            assert order.quote.cancelled_by == adviser
            assert order.quote.cancelled_on == now()
            assert order.status == OrderStatus.draft
コード例 #6
0
    def test_accept(self, public_omis_api_client):
        """Test that a quote can get accepted."""
        order = OrderWithOpenQuoteFactory()
        quote = order.quote

        url = reverse(
            'api-v3:public-omis:quote:accept',
            kwargs={'public_token': order.public_token},
        )

        with freeze_time('2017-07-12 13:00'):
            response = public_omis_api_client.post(url, json_={})

            assert response.status_code == status.HTTP_200_OK
            assert response.json() == {
                'created_on': format_date_or_datetime(quote.created_on),
                'accepted_on': format_date_or_datetime(now()),
                'cancelled_on': None,
                'expires_on': quote.expires_on.isoformat(),
                'content': quote.content,
                'terms_and_conditions':
                TermsAndConditions.objects.first().content,
            }

            quote.refresh_from_db()
            assert quote.is_accepted()
            assert quote.accepted_on == now()
コード例 #7
0
    def test_with_existing_active_quote(self):
        """Test that if there's already an active quote, the validation fails."""
        order = OrderWithOpenQuoteFactory()

        validator = NoOtherActiveQuoteExistsSubValidator()

        with pytest.raises(APIConflictException):
            validator(order=order)
コード例 #8
0
    def test_quote_cancelled(self, end_to_end_notify, notify_task_return_value_tracker):
        """
        Test templates of quote cancelled for customer and advisers.
        If the template variables have been changed in GOV.UK notifications the
        celery task will be unsuccessful.
        """
        order = OrderWithOpenQuoteFactory()

        end_to_end_notify.quote_cancelled(order, by=AdviserFactory())
        self._assert_tasks_successful(2, notify_task_return_value_tracker)
コード例 #9
0
    def test_customer_notified(self):
        """
        Test that calling `quote_generated` sends an email notifying the customer that
        they have to accept the quote.
        """
        order = OrderWithOpenQuoteFactory()

        notify.client.reset_mock()

        notify.quote_generated(order)

        assert notify.client.send_email_notification.called
        call_args = notify.client.send_email_notification.call_args_list[0][1]
        assert call_args['email_address'] == order.get_current_contact_email()
        assert call_args[
            'template_id'] == Template.quote_sent_for_customer.value
        assert call_args['personalisation'][
            'recipient name'] == order.contact.name
        assert call_args['personalisation'][
            'embedded link'] == order.get_public_facing_url()
コード例 #10
0
    def test_quote_cancelled(self, settings):
        """
        Test templates of quote cancelled for customer and advisers.
        If the template variables have been changed in GOV.UK notifications this
        is going to raise HTTPError (400 - Bad Request).
        """
        settings.OMIS_NOTIFICATION_API_KEY = settings.OMIS_NOTIFICATION_TEST_API_KEY
        notify = Notify()

        order = OrderWithOpenQuoteFactory()

        notify.quote_cancelled(order, by=AdviserFactory())
コード例 #11
0
    def test_409_if_theres_already_a_valid_quote(self, quote_view_name):
        """Test that if the order has already an active quote, the endpoint returns 409."""
        order = OrderWithOpenQuoteFactory()

        url = reverse(
            f'api-v3:omis:quote:{quote_view_name}',
            kwargs={'order_pk': order.pk},
        )
        response = self.api_client.post(url)

        assert response.status_code == status.HTTP_409_CONFLICT
        assert response.json() == {'detail': "There's already an active quote."}
コード例 #12
0
ファイル: test_signals.py プロジェクト: uktrade/data-hub-api
def test_accepting_quote_updates_opensearch(opensearch_with_signals):
    """
    Test that when a quote is accepted and the invoice created, the payment_due_date field
    in OpenSearch gets updated.
    """
    order = OrderWithOpenQuoteFactory()
    opensearch_with_signals.indices.refresh()

    result = opensearch_with_signals.get(
        index=OrderSearchApp.search_model.get_write_index(),
        id=order.pk,
    )
    assert not result['_source']['payment_due_date']

    order.accept_quote(by=None)
    opensearch_with_signals.indices.refresh()

    result = opensearch_with_signals.get(
        index=OrderSearchApp.search_model.get_write_index(),
        id=order.pk,
    )
    assert result['_source'][
        'payment_due_date'] == order.invoice.payment_due_date.isoformat()
コード例 #13
0
def test_accepting_quote_updates_es(setup_es):
    """
    Test that when a quote is accepted and the invoice created, the payment_due_date field
    in ES gets updated.
    """
    order = OrderWithOpenQuoteFactory()
    setup_es.indices.refresh()

    result = setup_es.get(
        index=OrderSearchApp.es_model.get_write_index(),
        doc_type=OrderSearchApp.name,
        id=order.pk,
    )
    assert not result['_source']['payment_due_date']

    order.accept_quote(by=None)
    setup_es.indices.refresh()

    result = setup_es.get(
        index=OrderSearchApp.es_model.get_write_index(),
        doc_type=OrderSearchApp.name,
        id=order.pk,
    )
    assert result['_source']['payment_due_date'] == order.invoice.payment_due_date.isoformat()
コード例 #14
0
    def test_notify_on_quote_accepted(self):
        """Test that a notification is triggered when a quote is accepted."""
        order = OrderWithOpenQuoteFactory(assignees=[])
        OrderAssigneeFactory.create_batch(1, order=order, is_lead=True)
        OrderSubscriberFactory.create_batch(2, order=order)

        notify.client.reset_mock()

        order.accept_quote(by=None)

        #  1 = customer, 3 = assignees/subscribers
        assert len(
            notify.client.send_email_notification.call_args_list) == (3 + 1)

        templates_called = [
            data[1]['template_id']
            for data in notify.client.send_email_notification.call_args_list
        ]
        assert templates_called == [
            Template.quote_accepted_for_customer.value,
            Template.quote_accepted_for_adviser.value,
            Template.quote_accepted_for_adviser.value,
            Template.quote_accepted_for_adviser.value,
        ]
コード例 #15
0
    def test_atomicity(self):
        """Test that if there's a problem with saving the order, the quote is not saved either."""
        order = OrderWithOpenQuoteFactory()
        with mock.patch.object(order, 'save') as mocked_save:
            mocked_save.side_effect = Exception()

            with pytest.raises(Exception):
                order.accept_quote(by=None)

            quote = order.quote
            order.refresh_from_db()
            quote.refresh_from_db()
            assert not quote.is_accepted()
            assert not order.invoice
            assert not Invoice.objects.count()
コード例 #16
0
    def test_with_open_quote(self):
        """Test that if the quote is open, it gets cancelled."""
        order = OrderWithOpenQuoteFactory()
        quote = order.quote

        url = reverse(
            f'api-v3:omis:quote:cancel',
            kwargs={'order_pk': order.pk},
        )
        with freeze_time('2017-07-12 13:00') as mocked_now:
            response = self.api_client.post(url)

            assert response.status_code == status.HTTP_200_OK
            assert response.json() == {
                'created_on': format_date_or_datetime(quote.created_on),
                'created_by': {
                    'id': str(quote.created_by.pk),
                    'first_name': quote.created_by.first_name,
                    'last_name': quote.created_by.last_name,
                    'name': quote.created_by.name,
                },
                'cancelled_on': format_date_or_datetime(mocked_now()),
                'cancelled_by': {
                    'id': str(self.user.pk),
                    'first_name': self.user.first_name,
                    'last_name': self.user.last_name,
                    'name': self.user.name,
                },
                'accepted_on': None,
                'accepted_by': None,
                'expires_on': quote.expires_on.isoformat(),
                'content': quote.content,
                'terms_and_conditions':
                TermsAndConditions.objects.first().content,
            }

            quote.refresh_from_db()
            assert quote.is_cancelled()
コード例 #17
0
    def test_ok_if_order_in_allowed_status(self, allowed_status):
        """
        Test that the quote of an order can be accepted if the order is
        in one of the allowed statuses.
        """
        order = OrderWithOpenQuoteFactory(status=allowed_status)
        contact = ContactFactory()

        order.accept_quote(by=contact)

        order.refresh_from_db()
        assert order.status == OrderStatus.quote_accepted
        assert order.quote.accepted_on
        assert order.quote.accepted_by == contact
        assert order.invoice
        assert order.invoice.billing_company_name == order.billing_company_name
        assert order.invoice.billing_address_1 == order.billing_address_1
        assert order.invoice.billing_address_2 == order.billing_address_2
        assert order.invoice.billing_address_town == order.billing_address_town
        assert order.invoice.billing_address_county == order.billing_address_county
        assert order.invoice.billing_address_postcode == order.billing_address_postcode
        assert order.invoice.billing_address_country == order.billing_address_country
        assert order.invoice.po_number == order.po_number
        assert order.invoice.contact_email == order.get_current_contact_email()
コード例 #18
0
ファイル: test_admin.py プロジェクト: uktrade/data-hub-api
class TestRefundAdmin(AdminTestMixin):
    """Tests for the Refund Admin."""
    def test_add(self):
        """
        Test adding a refund with status 'Approved'.
        This is the only status allowed when creating a record at the moment.
        """
        order = OrderPaidFactory()
        now_datetime = now()
        now_date_str = now_datetime.date().isoformat()
        now_time_str = now_datetime.time().isoformat()

        assert Refund.objects.count() == 0

        url = reverse('admin:omis_payment_refund_add')
        data = {
            'order': order.pk,
            'status': RefundStatus.APPROVED,
            'requested_on_0': now_date_str,
            'requested_on_1': now_time_str,
            'requested_by': AdviserFactory().pk,
            'requested_amount': order.total_cost,
            'refund_reason': 'lorem ipsum refund reason',
            'level1_approved_on_0': now_date_str,
            'level1_approved_on_1': now_time_str,
            'level1_approved_by': AdviserFactory().pk,
            'level1_approval_notes': 'lorem ipsum level 1',
            'level2_approved_on_0': now_date_str,
            'level2_approved_on_1': now_time_str,
            'level2_approved_by': AdviserFactory().pk,
            'level2_approval_notes': 'lorem ipsum level 2',
            'method': PaymentMethod.BACS,
            'net_amount': order.total_cost - 1,
            'vat_amount': 1,
            'additional_reference': 'additional reference',
            'rejection_reason': 'lorem ipsum rejection reason',
        }
        response = self.client.post(url, data, follow=True)

        assert response.status_code == status.HTTP_200_OK

        assert Refund.objects.count() == 1
        refund = Refund.objects.first()

        assert refund.order.pk == data['order']
        assert refund.status == data['status']
        assert refund.requested_on == now_datetime
        assert refund.requested_by.pk == data['requested_by']
        assert refund.requested_amount == data['requested_amount']
        assert refund.refund_reason == data['refund_reason']
        assert refund.level1_approved_on == now_datetime
        assert refund.level1_approved_by.pk == data['level1_approved_by']
        assert refund.level1_approval_notes == data['level1_approval_notes']
        assert refund.level2_approved_on == now_datetime
        assert refund.level2_approved_by.pk == data['level2_approved_by']
        assert refund.level2_approval_notes == data['level2_approval_notes']
        assert refund.method == data['method']
        assert refund.net_amount == data['net_amount']
        assert refund.vat_amount == data['vat_amount']
        assert refund.additional_reference == data['additional_reference']
        assert refund.rejection_reason == data['rejection_reason']

        assert refund.total_amount == order.total_cost
        assert refund.created_by == self.user
        assert refund.modified_by == self.user
        assert not refund.payment

    @pytest.mark.parametrize(
        'refund_factory',
        (
            RequestedRefundFactory,
            ApprovedRefundFactory,
            RejectedRefundFactory,
        ),
    )
    def test_change(self, refund_factory):
        """Test changing a refund record, its status cannot change at this point."""
        refund = refund_factory()
        order = OrderPaidFactory()

        now_datetime = now()
        now_date_str = now_datetime.date().isoformat()
        now_time_str = now_datetime.time().isoformat()

        url = reverse('admin:omis_payment_refund_change', args=(refund.id, ))
        data = {
            'order': order.pk,
            'status': refund.status,
            'requested_on_0': now_date_str,
            'requested_on_1': now_time_str,
            'requested_by': AdviserFactory().pk,
            'requested_amount': order.total_cost,
            'refund_reason': 'lorem ipsum refund reason',
            'level1_approved_on_0': now_date_str,
            'level1_approved_on_1': now_time_str,
            'level1_approved_by': AdviserFactory().pk,
            'level1_approval_notes': 'lorem ipsum level 1',
            'level2_approved_on_0': now_date_str,
            'level2_approved_on_1': now_time_str,
            'level2_approved_by': AdviserFactory().pk,
            'level2_approval_notes': 'lorem ipsum level 2',
            'method': PaymentMethod.BACS,
            'net_amount': order.total_cost - 1,
            'vat_amount': 1,
            'additional_reference': 'additional reference',
            'rejection_reason': 'lorem ipsum rejection reason',
        }
        response = self.client.post(url, data, follow=True)

        assert response.status_code == status.HTTP_200_OK
        refund.refresh_from_db()

        assert refund.order.pk == data['order']
        assert refund.status == data['status']
        assert refund.requested_on == now_datetime
        assert refund.requested_by.pk == data['requested_by']
        assert refund.requested_amount == data['requested_amount']
        assert refund.refund_reason == data['refund_reason']
        assert refund.level1_approved_on == now_datetime
        assert refund.level1_approved_by.pk == data['level1_approved_by']
        assert refund.level1_approval_notes == data['level1_approval_notes']
        assert refund.level2_approved_on == now_datetime
        assert refund.level2_approved_by.pk == data['level2_approved_by']
        assert refund.level2_approval_notes == data['level2_approval_notes']
        assert refund.method == data['method']
        assert refund.net_amount == data['net_amount']
        assert refund.vat_amount == data['vat_amount']
        assert refund.additional_reference == data['additional_reference']
        assert refund.rejection_reason == data['rejection_reason']

        assert refund.total_amount == order.total_cost
        assert refund.created_by != self.user
        assert refund.modified_by == self.user
        assert not refund.payment

    @pytest.mark.parametrize(
        'data_delta,errors',
        (
            # invalid status
            (
                {
                    'status': RefundStatus.REJECTED
                },
                {
                    'status': [
                        'Select a valid choice. rejected is not one of the available choices.',
                    ],
                },
            ),

            # invalid order status
            (
                {
                    'order': lambda *_: OrderWithOpenQuoteFactory()
                },
                {
                    'order': ['This order has not been paid for.']
                },
            ),

            # requested on < order.paid_on
            (
                {
                    'order':
                    lambda *_: OrderPaidFactory(paid_on=dateutil_parse(
                        '2018-01-01T13:00Z'), ),
                    'requested_on_0':
                    '2018-01-01',
                    'requested_on_1':
                    '12:59',
                },
                {
                    'requested_on': [
                        'Please specify a value greater than or equal to Jan. 1, 2018, 1 p.m..',
                    ],
                },
            ),

            # level1 approved on < order.paid_on
            (
                {
                    'order':
                    lambda *_: OrderPaidFactory(paid_on=dateutil_parse(
                        '2018-01-01T13:00Z'), ),
                    'level1_approved_on_0':
                    '2018-01-01',
                    'level1_approved_on_1':
                    '12:59',
                },
                {
                    'level1_approved_on': [
                        'Please specify a value greater than or equal to Jan. 1, 2018, 1 p.m..',
                    ],
                },
            ),

            # level2 approved on < order.paid_on
            (
                {
                    'order':
                    lambda *_: OrderPaidFactory(paid_on=dateutil_parse(
                        '2018-01-01T13:00Z'), ),
                    'level2_approved_on_0':
                    '2018-01-01',
                    'level2_approved_on_1':
                    '12:59',
                },
                {
                    'level2_approved_on': [
                        'Please specify a value greater than or equal to Jan. 1, 2018, 1 p.m..',
                    ],
                },
            ),

            # same level1 and level2 approver
            (
                {
                    'level1_approved_by': lambda *_: AdviserFactory().pk,
                    'level2_approved_by': lambda _, d: d['level1_approved_by'],
                },
                {
                    'level1_approved_by':
                    ['Approvers level1 and level2 have to be different.'],
                },
            ),

            # net_amount + vat_amount > order.total_cost
            (
                {
                    'net_amount': lambda o, _: o.total_cost,
                    'vat_amount': lambda *_: 1,
                },
                {
                    'net_amount':
                    lambda o, _: [
                        f'Remaining amount that can be refunded: {o.total_cost}.',
                    ],
                },
            ),
        ),
    )
    def test_validation_error(self, data_delta, errors):
        """Test validation errors."""
        def resolve(value, order, data):
            if callable(value):
                return value(order, data)
            return value

        order = data_delta.pop('order', None) or OrderPaidFactory()
        order = resolve(order, None, None)

        now_datetime = now()
        now_date_str = now_datetime.date().isoformat()
        now_time_str = now_datetime.time().isoformat()

        url = reverse('admin:omis_payment_refund_add')
        data = {
            'order': order.pk,
            'status': RefundStatus.APPROVED,
            'requested_on_0': now_date_str,
            'requested_on_1': now_time_str,
            'requested_by': AdviserFactory().pk,
            'requested_amount': order.total_cost,
            'refund_reason': 'lorem ipsum refund reason',
            'level1_approved_on_0': now_date_str,
            'level1_approved_on_1': now_time_str,
            'level1_approved_by': AdviserFactory().pk,
            'level1_approval_notes': 'lorem ipsum level 1',
            'level2_approved_on_0': now_date_str,
            'level2_approved_on_1': now_time_str,
            'level2_approved_by': AdviserFactory().pk,
            'level2_approval_notes': 'lorem ipsum level 2',
            'method': PaymentMethod.BACS,
            'net_amount': order.total_cost - 1,
            'vat_amount': 1,
            'additional_reference': 'additional reference',
        }

        for data_key, data_value in data_delta.items():
            data[data_key] = resolve(data_value, order, data)
        response = self.client.post(url, data, follow=True)

        assert response.status_code == status.HTTP_200_OK

        form = response.context['adminform'].form
        assert not form.is_valid()

        for error_key, error_value in errors.items():
            errors[error_key] = resolve(error_value, order, errors)
        assert form.errors == errors

    @pytest.mark.parametrize(
        'refund_factory,required_fields',
        (
            (
                RequestedRefundFactory,
                (
                    'order',
                    'status',
                    'requested_on',
                    'requested_amount',
                ),
            ),
            (
                ApprovedRefundFactory,
                (
                    'order',
                    'status',
                    'requested_on',
                    'requested_amount',
                    'level1_approved_on',
                    'level1_approved_by',
                    'level2_approved_on',
                    'level2_approved_by',
                    'method',
                    'net_amount',
                    'vat_amount',
                ),
            ),
            (
                RejectedRefundFactory,
                (
                    'order',
                    'status',
                    'requested_on',
                    'requested_amount',
                ),
            ),
        ),
    )
    def test_required_fields(self, refund_factory, required_fields):
        """Test required fields depending on the status of the refund."""
        refund = refund_factory()

        url = reverse('admin:omis_payment_refund_change', args=(refund.id, ))
        data = {
            'order': '',
            'status': '',
            'requested_on_0': '',
            'requested_on_1': '',
            'requested_by': '',
            'requested_amount': '',
            'refund_reason': '',
            'level1_approved_on_0': '',
            'level1_approved_on_1': '',
            'level1_approved_by': '',
            'level1_approval_notes': '',
            'level2_approved_on_0': '',
            'level2_approved_on_1': '',
            'level2_approved_by': '',
            'level2_approval_notes': '',
            'method': '',
            'net_amount': '',
            'vat_amount': '',
            'additional_reference': '',
            'rejection_reason': '',
        }
        response = self.client.post(url, data, follow=True)

        form = response.context['adminform'].form
        assert not form.is_valid()

        assert form.errors == {
            required_field: ['This field is required.']
            for required_field in required_fields
        }

    @pytest.mark.parametrize(
        'refund_factory',
        (
            RequestedRefundFactory,
            ApprovedRefundFactory,
            RejectedRefundFactory,
        ),
    )
    def test_cannot_change_status(self, refund_factory):
        """Test that the status field cannot be changed at any point."""
        refund = refund_factory()

        now_datetime = now()
        date_str = now_datetime.date().isoformat()
        time_str = now_datetime.time().isoformat()

        url = reverse('admin:omis_payment_refund_change', args=(refund.id, ))
        default_data = {
            'order': refund.order.pk,
            'requested_on_0': date_str,
            'requested_on_1': time_str,
            'requested_amount': refund.requested_amount,
            'refund_reason': refund.refund_reason,
            'level1_approved_on_0': date_str,
            'level1_approved_on_1': time_str,
            'level1_approved_by': AdviserFactory().pk,
            'level2_approved_on_0': date_str,
            'level2_approved_on_1': time_str,
            'level2_approved_by': AdviserFactory().pk,
            'method': refund.method or '',
            'net_amount':
            '' if refund.net_amount is None else refund.net_amount,
            'vat_amount':
            '' if refund.vat_amount is None else refund.vat_amount,
        }

        for changed_status, _ in RefundStatus.choices:
            if changed_status == refund.status:
                continue

            data = {
                **default_data,
                'status': changed_status,
            }
            response = self.client.post(url, data, follow=True)

            assert response.status_code == status.HTTP_200_OK
            form = response.context['adminform'].form
            assert not form.is_valid()
            assert form.errors == {
                'status': [
                    f'Select a valid choice. {changed_status} is not one of the available '
                    f'choices.',
                ],
            }