From 8724333fa536ea7d1aa96bbef24756b62842dd73 Mon Sep 17 00:00:00 2001 From: Ivo Branco Date: Mon, 11 Mar 2024 14:49:23 +0000 Subject: [PATCH] fix: add missing SOAPAction HTTP header To SageX3 Soap call add missing `SOAPAction` HTTP header. fixes #273 --- apps/billing/services/processor_service.py | 2 +- .../tests/test_sagex3_processor_data.py | 258 +++++++++++++++++ .../tests/test_sagex3_processor_service.py | 272 +++--------------- 3 files changed, 294 insertions(+), 238 deletions(-) create mode 100644 apps/billing/tests/test_sagex3_processor_data.py diff --git a/apps/billing/services/processor_service.py b/apps/billing/services/processor_service.py index 719c558..6f89815 100644 --- a/apps/billing/services/processor_service.py +++ b/apps/billing/services/processor_service.py @@ -33,7 +33,7 @@ def send_transaction_to_processor(self) -> dict: response = requests.post( url=self.__processor_url, data=self.data, - headers={"Content-type": "application/xml"}, + headers={"Content-type": "application/xml", "SOAPAction": ""}, auth=( self.__user_processor_auth, self.__user_processor_password, diff --git a/apps/billing/tests/test_sagex3_processor_data.py b/apps/billing/tests/test_sagex3_processor_data.py new file mode 100644 index 0000000..748d789 --- /dev/null +++ b/apps/billing/tests/test_sagex3_processor_data.py @@ -0,0 +1,258 @@ +import xml.etree.ElementTree as ET +from datetime import datetime + +from django.test import TestCase, override_settings + +from apps.billing.factories import TransactionFactory +from apps.billing.models import Transaction +from apps.billing.services.processor_service import SageX3Processor + + +class SageX3ProcessDataTest(TestCase): + """ + A test case for the SageX3Processor data property. + """ + + @staticmethod + def _get_xml_element_from_transaction(transaction: Transaction) -> ET.Element: + xml = SageX3Processor(transaction).data + root = ET.fromstring(xml) # nosec + object_xml: str = root.findall(".//*/objectXml")[0].text.strip() + return ET.fromstring(object_xml) # nosec + + def test_data_processor_transaction_id(self): + """ + Test the SageX3Processor for transaction_id field. + """ + transaction_id = "NAU00653" + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(transaction_id=transaction_id) + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='INVREF']"), transaction_id) + + def test_data_processor_transaction_date(self): + """ + Test the SageX3Processor for transaction_date field. + """ + transaction_date = datetime.now() + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(transaction_date=transaction_date) + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='INVDAT']"), transaction_date.strftime("%Y-%m-%d")) + + @override_settings(GEOGRAPHIC_ACTIVITY_VACBPR_FIELD="XPTO") + def test_data_processor_geographic_activity_override(self): + """ + Test the SageX3Processor for geographic activity field. + """ + transaction_date = datetime.now() + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(transaction_date=transaction_date) + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='VACBPR']"), "XPTO") + + def test_data_processor_geographic_activity_default(self): + """ + Test the SageX3Processor for geographic activity field. + """ + transaction_date = datetime.now() + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(transaction_date=transaction_date) + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='VACBPR']"), "CON") + + def test_data_processor_vat_identification_country_france(self): + """ + Test the SageX3Processor for vat identification country field for france + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(vat_identification_country="FR") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRY']"), "FR") + + def test_data_processor_vat_identification_country_portugal(self): + """ + Test the SageX3Processor for vat identification country field for portugal + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(vat_identification_country="PT") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRY']"), "PT") + + def test_data_processor_vat_identification_country_portugal_alpha3(self): + """ + Test the SageX3Processor for vat identification country field for portugal + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(vat_identification_country="PRT") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRY']"), "PT") + + def test_data_processor_vat_identification_country_none(self): + """ + Test the SageX3Processor for vat identification country field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(vat_identification_country=None) + ) + self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YCRY']").text, None) + + def test_data_processor_country_code_portugal(self): + """ + Test the SageX3Processor for country code field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(country_code="PT") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRYNAM']"), "PT") + + def test_data_processor_country_code_great_britain_alpha3(self): + """ + Test the SageX3Processor for country code field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(country_code="GBR") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRYNAM']"), "GB") + + def test_data_processor_country_code_none(self): + """ + Test the SageX3Processor for country code field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(country_code=None) + ) + self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YCRYNAM']").text, None) + + def test_data_processor_postal_code_portugal(self): + """ + Test the SageX3Processor for country code field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(postal_code="1249-068") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPOSCOD']"), "1249068") + + def test_data_processor_postal_code_france(self): + """ + Test the SageX3Processor for country code field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(postal_code="93600") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPOSCOD']"), "93600") + + def test_data_processor_postal_code_none(self): + """ + Test the SageX3Processor for country code field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(postal_code=None) + ) + self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YPOSCOD']").text, None) + + def test_data_processor_vat_identification_number_corporate(self): + """ + Test the SageX3Processor for country code field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(vat_identification_number=503904040) + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YBPIEECNUM']"), "503904040") + + def test_data_processor_email(self): + """ + Test the SageX3Processor for email field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(email="example@email.com") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YILINKMAIL']"), "example@email.com") + + def test_data_processor_email_none(self): + """ + Test the SageX3Processor for email field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction(TransactionFactory(email=None)) + self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YILINKMAIL']").text, None) + + def test_data_processor_transaction_type_credit(self): + """ + Test the SageX3Processor for transaction_type field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(transaction_type="credit") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPAM']"), "credit") + + def test_data_processor_transaction_type_none(self): + """ + The transaction_type is a required field. + """ + with self.assertRaises(Exception): + self.__class__._get_xml_element_from_transaction(TransactionFactory(transaction_type=None)) + + def test_data_processor_transaction_type_strange(self): + """ + Test the SageX3Processor for transaction_type field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(transaction_type="strange") + ) + self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPAM']"), "strange") + + def test_data_processor_client_name(self): + """ + Test the SageX3Processor for email field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(client_name="John Snow") + ) + self.assertEqual(object_xml_root.findtext(".//*/LST[@NAME='YBPRNAM']/ITM"), "John Snow") + + def test_data_processor_client_name_none(self): + """ + Test the SageX3Processor for email field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(client_name=None) + ) + self.assertEqual(object_xml_root.find(".//*/LST[@NAME='YBPRNAM']/ITM").text, None) + + def test_data_processor_address_line_1(self): + """ + Test the SageX3Processor for address_line_1 field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(address_line_1="Estrada Nacional nº1") + ) + self.assertEqual(object_xml_root.findall(".//*/LST[@NAME='YBPAADDLIG']/ITM")[0].text, "Estrada Nacional nº1") + + def test_data_processor_address_line_1_none(self): + """ + Test the SageX3Processor for address_line_1 field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(address_line_1=None) + ) + self.assertEqual(object_xml_root.find(".//*/LST[@NAME='YBPAADDLIG']/ITM").text, None) + + def test_data_processor_address_line_2(self): + """ + Test the SageX3Processor for address_line_2 field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(address_line_2="Uma localidade") + ) + self.assertEqual(object_xml_root.findall(".//*/LST[@NAME='YBPAADDLIG']/ITM")[1].text, "Uma localidade") + + def test_data_processor_address_line_2_none(self): + """ + Test the SageX3Processor for address_line_2 field. + """ + object_xml_root: ET.Element = self.__class__._get_xml_element_from_transaction( + TransactionFactory(address_line_2=None) + ) + self.assertFalse(object_xml_root.findall(".//*/LST[@NAME='YBPAADDLIG']/ITM")[1].text) + + # TODO test each items diff --git a/apps/billing/tests/test_sagex3_processor_service.py b/apps/billing/tests/test_sagex3_processor_service.py index 82d79a7..a5f3a23 100644 --- a/apps/billing/tests/test_sagex3_processor_service.py +++ b/apps/billing/tests/test_sagex3_processor_service.py @@ -1,10 +1,8 @@ -import xml.etree.ElementTree as ET -from datetime import datetime +from unittest import mock from django.test import TestCase, override_settings -from apps.billing.factories import TransactionFactory -from apps.billing.models import Transaction +from apps.billing.mocks import MockResponse from apps.billing.services.processor_service import SageX3Processor @@ -13,248 +11,48 @@ class SageX3ProcessServiceTest(TestCase): A test case for the ProcessService. """ - @staticmethod - def _get_xml_element_from_transaction(transaction: Transaction) -> ET.Element: - xml = SageX3Processor(transaction).data - root = ET.fromstring(xml) # nosec - object_xml: str = root.findall(".//*/objectXml")[0].text.strip() - return ET.fromstring(object_xml) # nosec - - def test_data_processor_transaction_id(self): - """ - Test the SageX3Processor for transaction_id field. - """ - transaction_id = "NAU00653" - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(transaction_id=transaction_id) - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='INVREF']"), transaction_id) - - def test_data_processor_transaction_date(self): - """ - Test the SageX3Processor for transaction_date field. - """ - transaction_date = datetime.now() - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(transaction_date=transaction_date) - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='INVDAT']"), transaction_date.strftime("%Y-%m-%d")) - - @override_settings(GEOGRAPHIC_ACTIVITY_VACBPR_FIELD="XPTO") - def test_data_processor_geographic_activity_override(self): - """ - Test the SageX3Processor for geographic activity field. - """ - transaction_date = datetime.now() - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(transaction_date=transaction_date) - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='VACBPR']"), "XPTO") - - def test_data_processor_geographic_activity_default(self): - """ - Test the SageX3Processor for geographic activity field. - """ - transaction_date = datetime.now() - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(transaction_date=transaction_date) - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='VACBPR']"), "CON") - - def test_data_processor_vat_identification_country_france(self): - """ - Test the SageX3Processor for vat identification country field for france - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(vat_identification_country="FR") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRY']"), "FR") - - def test_data_processor_vat_identification_country_portugal(self): - """ - Test the SageX3Processor for vat identification country field for portugal + @override_settings(TRANSACTION_PROCESSOR_URL="http://fake-processor.com/somelocation") + @mock.patch("requests.post", return_value=MockResponse(data="", status_code=200)) + @mock.patch("apps.billing.services.processor_service.SageX3Processor.data", side_effect=lambda: {"some": "thing"}) + def test_send_transaction_to_processor_transaction_processor_url_setting(self, mock_data, mock_post): """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(vat_identification_country="PT") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRY']"), "PT") - - def test_data_processor_vat_identification_country_portugal_alpha3(self): - """ - Test the SageX3Processor for vat identification country field for portugal + Test the `TRANSACTION_PROCESSOR_URL` setting changes the called URL. """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(vat_identification_country="PRT") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRY']"), "PT") + SageX3Processor(None).send_transaction_to_processor() + _, kwargs = mock_post.call_args - def test_data_processor_vat_identification_country_none(self): - """ - Test the SageX3Processor for vat identification country field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(vat_identification_country=None) - ) - self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YCRY']").text, None) + self.assertEqual("http://fake-processor.com/somelocation", kwargs["url"]) - def test_data_processor_country_code_portugal(self): + @override_settings(USER_PROCESSOR_AUTH="someuser", USER_PROCESSOR_PASSWORD="somepassword") + @mock.patch("requests.post", return_value=MockResponse(data="", status_code=200)) + @mock.patch("apps.billing.services.processor_service.SageX3Processor.data", side_effect=lambda: {"some": "thing"}) + def test_send_transaction_to_processor_user_processor_auth_password(self, mock_data, mock_post): """ - Test the SageX3Processor for country code field. + Test the `USER_PROCESSOR_AUTH` and `USER_PROCESSOR_PASSWORD` settings changes + the authentication user and password. """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(country_code="PT") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRYNAM']"), "PT") + SageX3Processor(None).send_transaction_to_processor() + _, kwargs = mock_post.call_args + self.assertEqual(("someuser", "somepassword"), kwargs["auth"]) - def test_data_processor_country_code_great_britain_alpha3(self): + @mock.patch("requests.post", return_value=MockResponse(data="", status_code=200)) + @mock.patch("apps.billing.services.processor_service.SageX3Processor.data", side_effect=lambda: {"some": "thing"}) + def test_send_transaction_to_processor_header_content_type(self, mock_data, mock_post): """ - Test the SageX3Processor for country code field. + Test the Content-type HTTP header sent to SageX3 is XML. """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(country_code="GBR") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YCRYNAM']"), "GB") + SageX3Processor(None).send_transaction_to_processor() + _, kwargs = mock_post.call_args + called_headers = kwargs["headers"] + self.assertEqual("application/xml", called_headers["Content-type"]) - def test_data_processor_country_code_none(self): - """ - Test the SageX3Processor for country code field. + @mock.patch("requests.post", return_value=MockResponse(data="", status_code=200)) + @mock.patch("apps.billing.services.processor_service.SageX3Processor.data", side_effect=lambda: {"some": "thing"}) + def test_send_transaction_to_processor_header_soapaction(self, mock_data, mock_post): """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(country_code=None) - ) - self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YCRYNAM']").text, None) - - def test_data_processor_postal_code_portugal(self): - """ - Test the SageX3Processor for country code field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(postal_code="1249-068") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPOSCOD']"), "1249068") - - def test_data_processor_postal_code_france(self): + Test the SOAPAction HTTP header sent to SageX3 is XML. """ - Test the SageX3Processor for country code field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(postal_code="93600") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPOSCOD']"), "93600") - - def test_data_processor_postal_code_none(self): - """ - Test the SageX3Processor for country code field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(postal_code=None) - ) - self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YPOSCOD']").text, None) - - def test_data_processor_vat_identification_number_corporate(self): - """ - Test the SageX3Processor for country code field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(vat_identification_number=503904040) - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YBPIEECNUM']"), "503904040") - - def test_data_processor_email(self): - """ - Test the SageX3Processor for email field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(email="example@email.com") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YILINKMAIL']"), "example@email.com") - - def test_data_processor_email_none(self): - """ - Test the SageX3Processor for email field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(email=None) - ) - self.assertEqual(object_xml_root.find(".//*/FLD[@NAME='YILINKMAIL']").text, None) - - def test_data_processor_transaction_type_credit(self): - """ - Test the SageX3Processor for transaction_type field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(transaction_type="credit") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPAM']"), "credit") - - def test_data_processor_transaction_type_none(self): - """ - The transaction_type is a required field. - """ - with self.assertRaises(Exception): - SageX3ProcessServiceTest._get_xml_element_from_transaction(TransactionFactory(transaction_type=None)) - - def test_data_processor_transaction_type_strange(self): - """ - Test the SageX3Processor for transaction_type field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(transaction_type="strange") - ) - self.assertEqual(object_xml_root.findtext(".//*/FLD[@NAME='YPAM']"), "strange") - - def test_data_processor_client_name(self): - """ - Test the SageX3Processor for email field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(client_name="John Snow") - ) - self.assertEqual(object_xml_root.findtext(".//*/LST[@NAME='YBPRNAM']/ITM"), "John Snow") - - def test_data_processor_client_name_none(self): - """ - Test the SageX3Processor for email field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(client_name=None) - ) - self.assertEqual(object_xml_root.find(".//*/LST[@NAME='YBPRNAM']/ITM").text, None) - - def test_data_processor_address_line_1(self): - """ - Test the SageX3Processor for address_line_1 field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(address_line_1="Estrada Nacional nº1") - ) - self.assertEqual(object_xml_root.findall(".//*/LST[@NAME='YBPAADDLIG']/ITM")[0].text, "Estrada Nacional nº1") - - def test_data_processor_address_line_1_none(self): - """ - Test the SageX3Processor for address_line_1 field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(address_line_1=None) - ) - self.assertEqual(object_xml_root.find(".//*/LST[@NAME='YBPAADDLIG']/ITM").text, None) - - def test_data_processor_address_line_2(self): - """ - Test the SageX3Processor for address_line_2 field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(address_line_2="Uma localidade") - ) - self.assertEqual(object_xml_root.findall(".//*/LST[@NAME='YBPAADDLIG']/ITM")[1].text, "Uma localidade") - - def test_data_processor_address_line_2_none(self): - """ - Test the SageX3Processor for address_line_2 field. - """ - object_xml_root: ET.Element = SageX3ProcessServiceTest._get_xml_element_from_transaction( - TransactionFactory(address_line_2=None) - ) - self.assertFalse(object_xml_root.findall(".//*/LST[@NAME='YBPAADDLIG']/ITM")[1].text) - - # TODO test each items + SageX3Processor(None).send_transaction_to_processor() + _, kwargs = mock_post.call_args + called_headers = kwargs["headers"] + self.assertEqual("", called_headers["SOAPAction"])