def test_notify_on_order_paid(self): """Test that a notification is triggered when an order is marked as paid.""" order = OrderWithAcceptedQuoteFactory(assignees=[]) OrderAssigneeFactory.create_batch(1, order=order) OrderSubscriberFactory.create_batch(2, order=order) notify.client.reset_mock() order.mark_as_paid( by=AdviserFactory(), payments_data=[ { 'amount': order.total_cost, 'received_on': dateutil_parse('2017-01-02').date(), }, ], ) # 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.order_paid_for_customer.value, Template.order_paid_for_adviser.value, Template.order_paid_for_adviser.value, Template.order_paid_for_adviser.value, ]
def test_ok_if_amounts_greater_than_total_cost(self): """ Test that if the sum of the amounts is greater than order.total_cost, the reconciliation is successful. """ order = OrderWithAcceptedQuoteFactory() url = reverse('api-v3:omis:payment:collection', kwargs={'order_pk': order.pk}) response = self.api_client.post( url, [ { 'amount': order.total_cost, 'method': PaymentMethod.bacs, 'received_on': '2017-04-20', }, { 'amount': 1, 'method': PaymentMethod.bacs, 'received_on': '2017-04-21', }, ], ) assert response.status_code == status.HTTP_201_CREATED
def test_exception_if_govuk_pay_errors_when_creating( self, govuk_status_code, requests_mock, ): """ Test that if GOV.UK Pay errors whilst creating a new payment, the method raises GOVUKPayAPIException. Possible GOV.UK Pay errors: - 400 - BAD REQUEST - 401 - UNAUTHORIZED - 422 - UNPROCESSABLE ENTITY - 500 - INTERNAL SERVER ERROR """ requests_mock.post( govuk_url('payments'), status_code=govuk_status_code, ) assert PaymentGatewaySession.objects.count() == 0 order = OrderWithAcceptedQuoteFactory() with pytest.raises(GOVUKPayAPIException): PaymentGatewaySession.objects.create_from_order(order) assert PaymentGatewaySession.objects.count() == 0
def test_500_if_govuk_pay_errors(self, govuk_status_code, requests_mock): """ Test that if GOV.UK Pay errors whilst getting a payment, the endpoint returns 500. Possible GOV.UK errors: - 401 - UNAUTHORIZED - 404 - NOT FOUND - 500 - INTERNAL SERVER ERROR """ order = OrderWithAcceptedQuoteFactory() session = PaymentGatewaySessionFactory( order=order, status=PaymentGatewaySessionStatus.created, ) requests_mock.get( govuk_url(f'payments/{session.govuk_payment_id}'), status_code=govuk_status_code, ) # make API call url = reverse( 'api-v3:omis-public:payment-gateway-session:detail', kwargs={ 'public_token': order.public_token, 'pk': session.id }, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.get(url) assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
def test_create_first_session_from_order(self, requests_mock, monkeypatch): """ Test the successful creation of the first payment gateway session for an order. """ monkeypatch.setattr( 'uuid.uuid4', mock.Mock(return_value='0123abcd-0000-0000-0000-000000000000'), ) # mock request govuk_payment_id = '123abc123abc123abc123abc12' govuk_payments_url = govuk_url('payments') requests_mock.post( govuk_payments_url, status_code=201, json={ 'state': {'status': 'created', 'finished': False}, 'payment_id': govuk_payment_id, '_links': { 'next_url': { 'href': 'https://payment.example.com/123abc', 'method': 'GET', }, }, }, ) assert PaymentGatewaySession.objects.count() == 0 # call method adviser = AdviserFactory() order = OrderWithAcceptedQuoteFactory() session = PaymentGatewaySession.objects.create_from_order( order=order, attrs={'created_by': adviser}, ) # check session assert session.order == order assert session.status == PaymentGatewaySessionStatus.CREATED assert session.govuk_payment_id == govuk_payment_id assert session.created_by == adviser assert PaymentGatewaySession.objects.count() == 1 # check mocked request assert requests_mock.call_count == 1 assert requests_mock.request_history[-1].url == govuk_payments_url assert requests_mock.request_history[-1].json() == { 'amount': order.total_cost, 'reference': f'{order.reference}-0123ABCD', 'description': settings.GOVUK_PAY_PAYMENT_DESCRIPTION.format( reference=order.reference, ), 'return_url': settings.GOVUK_PAY_RETURN_URL.format( public_token=order.public_token, session_id=session.pk, ), }
def test_verbs_not_allowed(self, verb, public_omis_api_client): """Test that makes sure the other verbs are not allowed.""" order = OrderWithAcceptedQuoteFactory() url = reverse( 'api-v3:public-omis:invoice:detail', kwargs={'public_token': order.public_token}, ) response = getattr(public_omis_api_client, verb)(url, json_={}) assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_verbs_not_allowed(self, verb_kwargs, public_omis_api_client): """Test that makes sure the other verbs are not allowed.""" order = OrderWithAcceptedQuoteFactory() verb, kwargs = verb_kwargs url = reverse( 'api-v3:public-omis:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) response = getattr(public_omis_api_client, verb)(url, **kwargs) assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_429_if_too_many_requests_made( self, local_memory_cache, requests_mock, monkeypatch, public_omis_api_client, ): """Test that the throttling for the create endpoint works if its rate is set.""" monkeypatch.setitem( settings.REST_FRAMEWORK['DEFAULT_THROTTLE_RATES'], 'payment_gateway_session.create', '3/sec', ) # mock GOV.UK response govuk_payment_id = '123abc123abc123abc123abc12' json_response = { 'state': {'status': 'created', 'finished': False}, 'payment_id': govuk_payment_id, '_links': { 'next_url': { 'href': 'https://payment.example.com/123abc', 'method': 'GET', }, }, } requests_mock.post( govuk_url('payments'), # create payment status_code=201, json=json_response, ) requests_mock.get( govuk_url(f'payments/{govuk_payment_id}'), # get payment status_code=200, json=json_response, ) requests_mock.post( govuk_url(f'payments/{govuk_payment_id}/cancel'), # cancel payment status_code=204, ) order = OrderWithAcceptedQuoteFactory() url = reverse( 'api-v3:public-omis:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) # the 4th time it should error for _ in range(3): response = public_omis_api_client.post(url, json_={}) assert response.status_code == status.HTTP_201_CREATED response = public_omis_api_client.post(url, json_={}) assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
def test_create_first_session(self, requests_mock, public_omis_api_client): """ Test a successful call to create a payment gateway session. This starts a GOV.UK payment and creates an OMIS payment gateway session object tracking it. """ # mock GOV.UK response govuk_payment_id = '123abc123abc123abc123abc12' next_url = 'https://payment.example.com/123abc' json_response = { 'state': {'status': 'created', 'finished': False}, 'payment_id': govuk_payment_id, '_links': { 'next_url': { 'href': next_url, 'method': 'GET', }, }, } requests_mock.post( govuk_url('payments'), # create payment status_code=201, json=json_response, ) requests_mock.get( govuk_url(f'payments/{govuk_payment_id}'), # get payment status_code=200, json=json_response, ) assert PaymentGatewaySession.objects.count() == 0 # make API call order = OrderWithAcceptedQuoteFactory() url = reverse( 'api-v3:public-omis:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) response = public_omis_api_client.post(url, json_={}) assert response.status_code == status.HTTP_201_CREATED # check payment gateway session record created assert PaymentGatewaySession.objects.count() == 1 session = PaymentGatewaySession.objects.first() assert session.govuk_payment_id == govuk_payment_id assert session.status == PaymentGatewaySessionStatus.CREATED # check API response assert response.json() == { 'id': str(session.id), 'created_on': format_date_or_datetime(session.created_on), 'status': PaymentGatewaySessionStatus.CREATED, 'payment_url': next_url, }
def test_500_if_govuk_pay_errors_when_cancelling(self, govuk_status_code, requests_mock): """ Test that if GOV.UK Pay errors whilst cancelling some other ongoing sessions/payments, the endpoint returns 500 to keep the system consistent. Possible GOV.UK errors when cancelling: - 400 - BAD REQUEST - 401 - UNAUTHORIZED - 404 - NOT FOUND - 409 - CONFLICT - 500 - INTERNAL SERVER ERROR In all these cases we return 500 as all those GOV.UK errors are unexpected. """ order = OrderWithAcceptedQuoteFactory() existing_session = PaymentGatewaySessionFactory( order=order, status=PaymentGatewaySessionStatus.created, ) # mock GOV.UK requests used to # - refresh the existing payment gateway session # - cancel the GOV.UK payment requests_mock.get( govuk_url(f'payments/{existing_session.govuk_payment_id}'), status_code=200, json={ 'state': { 'status': existing_session.status }, }, ) requests_mock.post( govuk_url(f'payments/{existing_session.govuk_payment_id}/cancel'), status_code=govuk_status_code, ) # make API call assert PaymentGatewaySession.objects.count() == 1 url = reverse( 'api-v3:omis-public:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.post(url) assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR # check no session created assert PaymentGatewaySession.objects.count() == 1
def test_verbs_not_allowed(self, verb, public_omis_api_client): """Test that makes sure the other verbs are not allowed.""" order = OrderWithAcceptedQuoteFactory() session = PaymentGatewaySessionFactory(order=order) url = reverse( 'api-v3:public-omis:payment-gateway-session:detail', kwargs={'public_token': order.public_token, 'pk': session.id}, ) response = getattr(public_omis_api_client, verb)(url, json_={}) assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def setup_data(setup_es): """Sets up data for the tests.""" with freeze_time('2017-01-01 13:00:00'): company = CompanyFactory(name='Mercury trading', alias='Uranus supplies') contact = ContactFactory(company=company, first_name='John', last_name='Doe') order = OrderFactory( reference='abcd', primary_market_id=constants.Country.japan.value.id, uk_region_id=constants.UKRegion.channel_islands.value.id, assignees=[], status=OrderStatus.draft, company=company, contact=contact, discount_value=0, delivery_date=dateutil_parse('2018-01-01').date(), vat_verified=False, ) OrderSubscriberFactory( order=order, adviser=AdviserFactory(dit_team_id=constants.Team.healthcare_uk.value.id), ) OrderAssigneeFactory( order=order, adviser=AdviserFactory(dit_team_id=constants.Team.tees_valley_lep.value.id), estimated_time=60, ) with freeze_time('2017-02-01 13:00:00'): company = CompanyFactory(name='Venus Ltd', alias='Earth outsourcing') contact = ContactFactory(company=company, first_name='Jenny', last_name='Cakeman') order = OrderWithAcceptedQuoteFactory( reference='efgh', primary_market_id=constants.Country.france.value.id, uk_region_id=constants.UKRegion.east_midlands.value.id, assignees=[], status=OrderStatus.quote_awaiting_acceptance, company=company, contact=contact, discount_value=0, delivery_date=dateutil_parse('2018-02-01').date(), vat_verified=False, ) OrderSubscriberFactory( order=order, adviser=AdviserFactory(dit_team_id=constants.Team.td_events_healthcare.value.id), ) OrderAssigneeFactory( order=order, adviser=AdviserFactory(dit_team_id=constants.Team.food_from_britain.value.id), estimated_time=120, ) setup_es.indices.refresh()
def test_404_if_session_doesnt_exist(self, public_omis_api_client): """Test that if the payment gateway session doesn't exist, the endpoint returns 404.""" order = OrderWithAcceptedQuoteFactory() url = reverse( 'api-v3:public-omis:payment-gateway-session:detail', kwargs={ 'public_token': order.public_token, 'pk': '00000000-0000-0000-0000-000000000000', }, ) response = public_omis_api_client.get(url) assert response.status_code == status.HTTP_404_NOT_FOUND
def test_404_if_in_disallowed_status(self, order_status, public_omis_api_client): """Test that if the order is not in an allowed state, the endpoint returns 404.""" order = OrderWithAcceptedQuoteFactory(status=order_status) url = reverse( 'api-v3:public-omis:invoice:detail', kwargs={'public_token': order.public_token}, ) response = public_omis_api_client.get(url) assert response.status_code == status.HTTP_404_NOT_FOUND
def test_403_if_scope_not_allowed(self, scope): """Test that other oauth2 scopes are not allowed.""" order = OrderWithAcceptedQuoteFactory() url = reverse( 'api-v3:omis-public:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) client = self.create_api_client( scope=scope, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.post(url) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_verbs_not_allowed(self, verb): """Test that makes sure the other verbs are not allowed.""" order = OrderWithAcceptedQuoteFactory() url = reverse( 'api-v3:omis-public:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = getattr(client, verb)(url) assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_404_if_in_disallowed_status(self, order_status): """Test that if the order is not in an allowed state, the endpoint returns 404.""" order = OrderWithAcceptedQuoteFactory(status=order_status) url = reverse( 'api-v3:omis-public:invoice:detail', kwargs={'public_token': order.public_token}, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.get(url) assert response.status_code == status.HTTP_404_NOT_FOUND
def test_ongoing(self): """ Test that given: session 1 - order 1 - status created session 2 - order 1 - status submitted session 3 - order 1 - status failed session 4 - order 2 - status started session 5 - order 2 - status success session 6 - order 2 - status cancelled the method .ongoing() on the queryset only returns the sessions which are considered not finished. """ order1, order2 = OrderWithAcceptedQuoteFactory.create_batch(2) order1_sessions = PaymentGatewaySessionFactory.create_batch( 3, order=order1, status=factory.Iterator([ PaymentGatewaySessionStatus.CREATED, PaymentGatewaySessionStatus.SUBMITTED, PaymentGatewaySessionStatus.FAILED, ]), ) order2_sessions = PaymentGatewaySessionFactory.create_batch( 3, order=order2, status=factory.Iterator([ PaymentGatewaySessionStatus.STARTED, PaymentGatewaySessionStatus.SUCCESS, PaymentGatewaySessionStatus.CANCELLED, ]), ) # test qs without filters qs = PaymentGatewaySession.objects.ongoing() assert set(qs.values_list('id', flat=True)) == { order1_sessions[0].id, order1_sessions[1].id, order2_sessions[0].id, } # test qs with order filter qs = PaymentGatewaySession.objects.filter(order=order1).ongoing() assert set(qs.values_list('id', flat=True)) == { order1_sessions[0].id, order1_sessions[1].id, }
def test_404_if_session_belongs_to_another_order(self, public_omis_api_client): """ Test that if the payment gateway session belongs to another order, the endpoint returns 404. """ orders = OrderWithAcceptedQuoteFactory.create_batch(2) sessions = PaymentGatewaySessionFactory.create_batch(2, order=factory.Iterator(orders)) url = reverse( 'api-v3:public-omis:payment-gateway-session:detail', kwargs={ 'public_token': orders[0].public_token, 'pk': sessions[1].id, }, ) response = public_omis_api_client.get(url) assert response.status_code == status.HTTP_404_NOT_FOUND
def test_get(self): """Test a successful call to get a invoice.""" order = OrderWithAcceptedQuoteFactory() invoice = order.invoice url = reverse('api-v3:omis:invoice: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(invoice.created_on), 'invoice_number': invoice.invoice_number, 'invoice_company_name': invoice.invoice_company_name, 'invoice_address_1': invoice.invoice_address_1, 'invoice_address_2': invoice.invoice_address_2, 'invoice_address_county': invoice.invoice_address_county, 'invoice_address_town': invoice.invoice_address_town, 'invoice_address_postcode': invoice.invoice_address_postcode, 'invoice_address_country': { 'id': str(invoice.invoice_address_country.pk), 'name': invoice.invoice_address_country.name, }, 'invoice_vat_number': invoice.invoice_vat_number, 'payment_due_date': invoice.payment_due_date.isoformat(), 'billing_contact_name': invoice.billing_contact_name, 'billing_company_name': invoice.billing_company_name, 'billing_address_1': invoice.billing_address_1, 'billing_address_2': invoice.billing_address_2, 'billing_address_county': invoice.billing_address_county, 'billing_address_postcode': invoice.billing_address_postcode, 'billing_address_town': invoice.billing_address_town, 'billing_address_country': { 'id': str(invoice.billing_address_country.pk), 'name': invoice.billing_address_country.name, }, 'po_number': invoice.po_number, 'vat_status': invoice.vat_status, 'vat_number': invoice.vat_number, 'vat_verified': invoice.vat_verified, 'net_cost': invoice.net_cost, 'subtotal_cost': invoice.subtotal_cost, 'vat_cost': invoice.vat_cost, 'total_cost': invoice.total_cost, }
def test_first_invoice_number_of_the_day(self): """Test that the first invoice number of the day is generated as expected.""" # create some in different months/days dts = ( '2017-01-01 13:00:00', '2017-03-01 13:00:00', '2016-02-01 13:00:00', '2018-02-01 13:00:00', ) for dt in dts: with freeze_time(dt): OrderWithAcceptedQuoteFactory() assert Invoice.objects.count() == 4 with freeze_time('2017-02-01 13:00:00'): invoice_number = generate_datetime_based_reference(Invoice, field='invoice_number') assert invoice_number == '201702010001'
def test_exception_if_govuk_pay_errors_when_cancelling( self, govuk_status_code, requests_mock, ): """ Test that if GOV.UK Pay errors whilst cancelling some other ongoing sessions/payments, the method raises GOVUKPayAPIException to keep the system consistent. Possible GOV.UK Pay errors when cancelling: - 400 - BAD REQUEST - 401 - UNAUTHORIZED - 404 - NOT FOUND - 409 - CONFLICT - 500 - INTERNAL SERVER ERROR """ order = OrderWithAcceptedQuoteFactory() existing_session = PaymentGatewaySessionFactory( order=order, status=PaymentGatewaySessionStatus.CREATED, ) # mock GOV.UK requests used to # - refresh the existing payment gateway session # - cancel the GOV.UK payment requests_mock.get( govuk_url(f'payments/{existing_session.govuk_payment_id}'), status_code=200, json={ 'state': {'status': existing_session.status}, }, ) requests_mock.post( govuk_url(f'payments/{existing_session.govuk_payment_id}/cancel'), status_code=govuk_status_code, ) assert PaymentGatewaySession.objects.count() == 1 with pytest.raises(GOVUKPayAPIException): PaymentGatewaySession.objects.create_from_order(order) assert PaymentGatewaySession.objects.count() == 1
def test_500_if_govuk_pay_errors_when_creating(self, govuk_status_code, requests_mock): """ Test that if GOV.UK Pay errors whilst creating a new payment, the endpoint returns 500. Possible GOV.UK errors: - 400 - BAD REQUEST - 401 - UNAUTHORIZED - 422 - UNPROCESSABLE ENTITY - 500 - INTERNAL SERVER ERROR In all these cases we return 500 as all those GOV.UK errors are unexpected. """ # mock GOV.UK response requests_mock.post( govuk_url('payments'), status_code=govuk_status_code, ) assert PaymentGatewaySession.objects.count() == 0 # make API call order = OrderWithAcceptedQuoteFactory() url = reverse( 'api-v3:omis-public:payment-gateway-session:collection', kwargs={'public_token': order.public_token}, ) client = self.create_api_client( scope=Scope.public_omis_front_end, grant_type=Application.GRANT_CLIENT_CREDENTIALS, ) response = client.post(url) assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR # check no session created assert PaymentGatewaySession.objects.count() == 0