diff --git a/setra_client/client.py b/setra_client/client.py index 53595a207a817416bea5bbd2824089177216c3e1..43a5197f45dbe3d181c853c6f718b40f98d7e466 100644 --- a/setra_client/client.py +++ b/setra_client/client.py @@ -13,6 +13,10 @@ from setra_client.models import ( InputBatch, OutputBatch, Parameter, + Order, + Detail, + AbwOrder, + AbwOrderErrors, ) logger = logging.getLogger(__name__) @@ -49,7 +53,17 @@ class SetraEndpoints: batch_complete_url='api/batch_complete/', batch_error_url="api/batch_error/", parameters_url="api/parameters/", + + # Order urls (sotra): + order_url="api/order/", + order_complete_url="api/order_complete/", + detail_url="api/detail/", + details_in_order_url="api/details_in_order/", + abw_order_complete_url="api/abw_order_complete/", + post_add_abw_order_url="api/add_abw_order/", + abw_order_errors_url="api/abw_order_errors/", ): + self.baseurl = url self.batch_url = batch_url self.transaction_url = transaction_url @@ -59,6 +73,14 @@ class SetraEndpoints: self.batch_complete_url = batch_complete_url self.batch_error_url = batch_error_url self.parameters_url = parameters_url + # order urls: + self.order_url = order_url + self.order_complete_url = order_complete_url + self.detail_url = detail_url + self.details_in_order_url = details_in_order_url + self.abw_order_complete_url = abw_order_complete_url + self.post_add_abw_order_url = post_add_abw_order_url + self.abw_order_errors_url = abw_order_errors_url """ Get endpoints relative to the SETRA API URL. """ @@ -123,6 +145,57 @@ class SetraEndpoints: """Get url for parameters endpoint""" return urllib.parse.urljoin(self.baseurl, self.parameters_url) + def abw_order_complete(self, abw_order_id: str = None): + """ + URL for getting an abw order including a list of its orders, and each order contains a list of its detail objects + """ + return urllib.parse.urljoin(self.baseurl, + '/'.join((self.abw_order_complete_url, abw_order_id))) + + def add_abw_order(self): + """ + URL for posting a new complete abw order containing order and detail objects + """ + return urllib.parse.urljoin(self.baseurl, self.post_add_abw_order_url) + + def order(self, order_id: str = None): + """ + URL for order endpoint + """ + if order_id is None: + return urllib.parse.urljoin(self.baseurl, self.order_url) + else: + return urllib.parse.urljoin(self.baseurl, + '/'.join((self.order_url, order_id))) + + def order_complete(self, order_id: str): + """ + URL for getting an order object including a list of its detail objects + """ + return urllib.parse.urljoin(self.baseurl, + '/'.join((self.order_complete_url, order_id))) + + def detail(self, detail_id: str): + """ + URL for detail endpoint + """ + return urllib.parse.urljoin(self.baseurl, + '/'.join((self.detail_url, detail_id))) + + def details_in_order(self, order_id: str): + """ + URL for getting a list of detail objects in an order + """ + return urllib.parse.urljoin(self.baseurl, + '/'.join((self.details_in_order_url, order_id))) + + def abw_order_errors(self, abw_order_id: str): + """ + URL for getting an object containing lists of all errors for AbwOrder, Orders and Details + """ + return urllib.parse.urljoin(self.baseurl, + '/'.join((self.abw_order_errors_url, abw_order_id))) + class SetraClient(object): default_headers = { @@ -344,6 +417,123 @@ class SetraClient(object): response = self.get(url, params=queryparams) return self.object_or_data(Parameter, response.json()) + # Order ("Sotra") functions: + + def get_order(self, order_id: str) -> Union[Order, dict]: + """ + GETs one order object + """ + url = self.urls.order(str(order_id)) + response = self.get(url) + data = response.json() + response.raise_for_status() + if self.return_objects: + return Order(**data) + else: + return data + + def get_detail(self, detail_id: str) -> Union[Detail, dict]: + """ + GETs one detail object + """ + url = self.urls.detail(str(detail_id)) + response = self.get(url) + data = response.json() + response.raise_for_status() + if self.return_objects: + return Detail(**data) + else: + return data + + def get_order_list(self): + """ + GETs a list of all orders, without detail objects + """ + url = self.urls.order() + response = self.get(url) + data = response.json() + response.raise_for_status() + return self.object_or_data(Order, response.json()) + + def get_details_in_order(self, order_id: str) -> Union[List[Detail], dict]: + """ + GETs list of all detail objects belonging to an order + """ + url = self.urls.details_in_order(str(order_id)) + response = self.get(url) + data = response.json() + response.raise_for_status() + if self.return_objects: + if isinstance(data, list): + return [Detail(**item) for item in data] + elif isinstance(data, dict): + return [Detail(**data)] + else: + return data + + def get_order_complete(self, order_id: str) -> Union[Order, dict]: + """ + GETs one order, with all detail objects + """ + url = self.urls.order_complete(str(order_id)) + response = self.get(url) + data = response.json() + response.raise_for_status() + if self.return_objects: + return Order(**data) + else: + return data + + def get_abw_order_complete(self, abw_order_id: str) -> Union[AbwOrder, dict]: + """ + GETs one abworder, with all order and detail objects + """ + url = self.urls.abw_order_complete(str(abw_order_id)) + response = self.get(url) + data = response.json() + response.raise_for_status() + if self.return_objects: + return AbwOrder(**data) + else: + return data + + def post_add_abw_order(self, abworder: AbwOrder): + """ + POST one AbwOrder, with its orders and its details. + + Note: There are some fields that cannot be sent in when creating a new AbwOrder: + AbwOrder.id + AbwOrder.progress + AbwOrder.abworder_validated_ok_date + Order.id + Order.abw_order_id + Detail.id + Detail.order_id + + Returns tuple, with (data, status) + """ + url = self.urls.add_abw_order() + headers = {'Content-Type': 'application/json'} + response = self.post(url, + data=abworder.json(), + headers=headers, + return_response=True) + if response.status_code == 202: + return response.content, 'Accepted' + elif response.status_code == 409: + return response.content, "Conflict" + else: + response.raise_for_status() + return response + + def get_abw_order_errors(self, abw_order_id: str): + """ + Gets an object containing three lists of all errors, for each of AbwOrder, Orders and Details + """ + url = self.urls.abw_order_errors(abw_order_id) + response = self.get(url) + return self.object_or_data(AbwOrderErrors, response.json()) + def get_client(config_dict): """ diff --git a/setra_client/models.py b/setra_client/models.py index 19474776a31c07b777ce77f523f88c43d2838069..f1d62c19d80ad38ec611231c0e8f85f85fad8ae9 100644 --- a/setra_client/models.py +++ b/setra_client/models.py @@ -217,3 +217,91 @@ class Parameter(BaseModel): valid_from: Optional[datetime.date] valid_to: Optional[datetime.date] description: Optional[str] + + +class Detail(BaseModel): + id: Optional[int] # Cannot be sent in + order_id: Optional[int] # Cannot be sent in + account: str + buyer_product_descr: Optional[str] + buyer_product_code: str + line_total: Optional[float] + koststed: str + prosjekt: Optional[str] + t1: Optional[str] + r00: Optional[str] + delprosjekt: str + anlegg: Optional[str] + aktivitet: Optional[str] + line_no: int + product_specification: int + price: Optional[float] + quantity: Optional[float] + + +class Order(BaseModel): + id: Optional[int] # Cannot be sent in + abw_order_id: Optional[int] # Cannot be sent in + accountable: Optional[str] + buyer_no: str + currency: str + ext_order_ref: Optional[str] + order_date: Optional[datetime.date] + order_type: str + sales_man: str + trans_type: str + ext_order_id: Optional[str] + order_no: Optional[int] + deliv_date: Optional[datetime.date] + text3: Optional[str] + text4: Optional[str] + header_text: Optional[str] + footer_text: Optional[str] + details: Optional[List[Detail]] + + +class AbwOrder(BaseModel): + """ + Model representing an AbwOrder, with a list of orders (and each order has a list of details.) + """ + id: Optional[int] # Cannot be sent in + progress: Optional[str] # Cannot be sent in + abworder_validated_ok_date: Optional[datetime.date] # Cannot be sent in + responsible: str + interface: str + client: str + abworderid: str + orders: List[Order] + + +class ErrorDetail(BaseModel): + id: int + detail_id: int + validation_date: datetime.datetime + validation_error: str + validation_error_type_code: str + + +class ErrorOrder(BaseModel): + id: int + order_id: int + validation_date: datetime.datetime + validation_error: str + validation_error_type_code: str + + +class ErrorAbwOrder(BaseModel): + id: int + abworder_id: int + date: datetime.datetime + error: str + error_type_code: str + error_level: str + error_source: str + + +class AbwOrderErrors(BaseModel): + """Model for the /abw_order_errors/<id> endpoint""" + abw_order_errors: List[ErrorAbwOrder] + order_errors: List[ErrorOrder] + detail_errors: List[ErrorDetail] diff --git a/tests/conftest.py b/tests/conftest.py index 7b8e99b02371fb6c6c15cb0b8fc226fe67a7a1e9..11bc09593d2f1e424522ad09bb953b290cab05a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -110,3 +110,38 @@ def complete_batch(): @pytest.fixture def param_list(): return load_json_file("params.json") + + +@pytest.fixture +def abw_order_fixture(): + return load_json_file("abw_order_fixture.json") + + +@pytest.fixture +def order_fixture(): + return load_json_file("order_fixture.json") + + +@pytest.fixture +def detail_fixture(): + return load_json_file("detail_fixture.json") + + +@pytest.fixture +def order_with_detail_fixture(): + return load_json_file("order_with_detail_fixture.json") + + +@pytest.fixture +def order_list_fixture(): + return load_json_file("order_list_fixture.json") + + +@pytest.fixture +def detail_list_fixture(): + return load_json_file("detail_list_fixture.json") + + +@pytest.fixture +def complete_abw_order_fixture(): + return load_json_file("complete_abw_order_fixture.json") diff --git a/tests/fixtures/abw_order_fixture.json b/tests/fixtures/abw_order_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..393ca558e479836339e0593c13eeb8f3979eea3a --- /dev/null +++ b/tests/fixtures/abw_order_fixture.json @@ -0,0 +1,8 @@ +{ + "id": 2, + "responsible": "responsible2", + "client": "testclient", + "interface": "testinterface", + "abworderid": "some-external-order-id", + "orders": [] +} diff --git a/tests/fixtures/complete_abw_order_fixture.json b/tests/fixtures/complete_abw_order_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..dfd8da084d502d720eaad455637e750aac93ea0b --- /dev/null +++ b/tests/fixtures/complete_abw_order_fixture.json @@ -0,0 +1,89 @@ +{ + "id": 2, + "responsible": "responsible2", + "client": "testclient", + "interface": "testinterface", + "abworderid": "some-external-order-id", + "orders": [ + { + "id": 3, + "abw_order_id": 2, + "accountable": "accountable3", + "buyer_no": "buyerno", + "currency": "currency", + "ext_order_ref": "extorderref", + "order_date": "2021-07-20", + "order_type": "ab", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "extorderid", + "order_no": 1, + "deliv_date": "2021-07-20", + "text3": "txt3", + "text4": "txt4", + "header_text": "headertext", + "footer_text": "footertext", + "details": [ + { + "id": 2, + "order_id": 3, + "account": "account4", + "buyer_product_descr": "buyer prod decrs", + "buyer_product_code": "product code", + "line_total": 4444.0, + "koststed": "123123", + "prosjekt": "prosjekt", + "t1": "t1", + "r00": "r00", + "delprosjekt": "prosjekt", + "anlegg": "anlegg", + "aktivitet": "aktivitet", + "line_no": 1, + "product_specification": 213, + "price": 444.0, + "quantity": 2.0 + } + ] + }, + { + "id": 4, + "abw_order_id": 2, + "accountable": "accc4", + "buyer_no": "buyerno2", + "currency": "curr", + "ext_order_ref": "ext order ref", + "order_date": "2021-07-20", + "order_type": "ty", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "ext order id", + "order_no": 2, + "deliv_date": "2021-07-20", + "text3": "222222", + "text4": "txt444", + "header_text": "headertext", + "footer_text": "footertext", + "details": [ + { + "id": 3, + "order_id": 4, + "account": "account5", + "buyer_product_descr": "buyer prod decrs", + "buyer_product_code": "product code", + "line_total": 555.0, + "koststed": "123123", + "prosjekt": "prosjekt", + "t1": "t1", + "r00": "r00", + "delprosjekt": "prosjekt", + "anlegg": "anlegg", + "aktivitet": "aktivitet", + "line_no": 1, + "product_specification": 213, + "price": 555.0, + "quantity": 2.0 + } + ] + } + ] +} diff --git a/tests/fixtures/detail_fixture.json b/tests/fixtures/detail_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..e766f33b59ec7cffd12528b072dd98eb44125af3 --- /dev/null +++ b/tests/fixtures/detail_fixture.json @@ -0,0 +1,19 @@ +{ + "id": 1, + "order": 1, + "account": "account", + "buyer_product_descr": "buyer prod decrs", + "buyer_product_code": "product code", + "line_total": "123.00", + "koststed": "123123", + "prosjekt": "prosjekt", + "t1": "t1", + "r00": "r00", + "delprosjekt": "prosjekt", + "anlegg": "anlegg", + "aktivitet": "aktivitet", + "line_no": 1, + "product_specification": 213, + "price": "1234.00", + "quantity": 2.0 +} diff --git a/tests/fixtures/detail_list_fixture.json b/tests/fixtures/detail_list_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..5116b0dd3d076cef1615544a891a93acca7d1060 --- /dev/null +++ b/tests/fixtures/detail_list_fixture.json @@ -0,0 +1,40 @@ +[ + { + "id": 1, + "order": 1, + "account": "account", + "buyer_product_descr": "buyer prod decrs", + "buyer_product_code": "product code", + "line_total": "123.00", + "koststed": "123123", + "prosjekt": "prosjekt", + "t1": "t1", + "r00": "r00", + "delprosjekt": "prosjekt", + "anlegg": "anlegg", + "aktivitet": "aktivitet", + "line_no": 1, + "product_specification": 213, + "price": "1234.00", + "quantity": 2.0 + }, + { + "id": 2, + "order": 1, + "account": "account", + "buyer_product_descr": "buyer prod decrs", + "buyer_product_code": "product code", + "line_total": "123.00", + "koststed": "123123", + "prosjekt": "prosjekt", + "t1": "t1", + "r00": "r00", + "delprosjekt": "prosjekt", + "anlegg": "anlegg", + "aktivitet": "aktivitet", + "line_no": 1, + "product_specification": 213, + "price": "1234.00", + "quantity": 2.0 + } +] diff --git a/tests/fixtures/order_fixture.json b/tests/fixtures/order_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..ecd44bf39d5a63a1ee95c35cec0c30e55431e015 --- /dev/null +++ b/tests/fixtures/order_fixture.json @@ -0,0 +1,20 @@ +{ + "id": 1, + "abw_order_id": 1, + "accountable": "accountable", + "buyer_no": "buyerno", + "currency": "currency", + "ext_order_ref": "extorderref", + "order_date": "2021-07-20", + "order_type": "ab", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "extorderid", + "order_no": 1, + "deliv_date": "2021-07-20", + "text3": "txt3", + "text4": "txt4", + "header_text": "headertext", + "footer_text": "footertext", + "details": [] +} diff --git a/tests/fixtures/order_list_fixture.json b/tests/fixtures/order_list_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..f24eb229185d068fd8cf57f3d673211f5c389359 --- /dev/null +++ b/tests/fixtures/order_list_fixture.json @@ -0,0 +1,82 @@ +[ + { + "id": 1, + "abw_order_id": 1, + "accountable": "accountable", + "buyer_no": "buyerno", + "currency": "currency", + "ext_order_ref": "extorderref", + "order_date": "2021-07-20", + "order_type": "ab", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "extorderid", + "order_no": 1, + "deliv_date": "2021-07-20", + "text3": "txt3", + "text4": "txt4", + "header_text": "headertext", + "footer_text": "footertext", + "details": [] + }, + { + "id": 2, + "abw_order_id": 1, + "accountable": "accc2", + "buyer_no": "buyerno2", + "currency": "curr", + "ext_order_ref": "ext order ref", + "order_date": "2021-07-20", + "order_type": "ty", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "ext order id", + "order_no": 2, + "deliv_date": "2021-07-20", + "text3": "222222", + "text4": "txt444", + "header_text": "headertext", + "footer_text": "footertext", + "details": [] + }, + { + "id": 3, + "abw_order_id": 2, + "accountable": "accountable3", + "buyer_no": "buyerno", + "currency": "currency", + "ext_order_ref": "extorderref", + "order_date": "2021-07-20", + "order_type": "ab", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "extorderid", + "order_no": 1, + "deliv_date": "2021-07-20", + "text3": "txt3", + "text4": "txt4", + "header_text": "headertext", + "footer_text": "footertext", + "details": [] + }, + { + "id": 4, + "abw_order_id": 2, + "accountable": "accc4", + "buyer_no": "buyerno2", + "currency": "curr", + "ext_order_ref": "ext order ref", + "order_date": "2021-07-20", + "order_type": "ty", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "ext order id", + "order_no": 2, + "deliv_date": "2021-07-20", + "text3": "222222", + "text4": "txt444", + "header_text": "headertext", + "footer_text": "footertext", + "details": [] + } +] diff --git a/tests/fixtures/order_with_detail_fixture.json b/tests/fixtures/order_with_detail_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..474ee1127677eb49b585f7978b20cf1d721c34f5 --- /dev/null +++ b/tests/fixtures/order_with_detail_fixture.json @@ -0,0 +1,59 @@ +{ + "id": 1, + "abw_order_id": 1, + "accountable": "accountable", + "buyer_no": "buyerno", + "currency": "currency", + "ext_order_ref": "extorderref", + "order_date": "2021-07-20", + "order_type": "ab", + "sales_man": "sales man", + "trans_type": "tr", + "ext_order_id": "extorderid", + "order_no": 1, + "deliv_date": "2021-07-20", + "text3": "txt3", + "text4": "txt4", + "header_text": "headertext", + "footer_text": "footertext", + "details": [ + { + "id": 1, + "order_id": 1, + "account": "account", + "buyer_product_descr": "buyer prod decrs", + "buyer_product_code": "product code", + "line_total": 123.0, + "koststed": "123123", + "prosjekt": "prosjekt", + "t1": "t1", + "r00": "r00", + "delprosjekt": "prosjekt", + "anlegg": "anlegg", + "aktivitet": "aktivitet", + "line_no": 1, + "product_specification": 213, + "price": 1234.0, + "quantity": 2.0 + }, + { + "id": 2, + "order_id": 1, + "account": "account2", + "buyer_product_descr": "buyer prod decrs2", + "buyer_product_code": "product code2", + "line_total": 124.0, + "koststed": "444", + "prosjekt": "prosjekt2", + "t1": "t2", + "r00": "r00", + "delprosjekt": "prosjekt2", + "anlegg": "anlegg2", + "aktivitet": "aktivitet2", + "line_no": 2, + "product_specification": 213, + "price": 1234.0, + "quantity": 2.0 + } + ] +} diff --git a/tests/test_client.py b/tests/test_client.py index 45d4355cb891ebf6463d14f0da41d9d38ec2bdc1..b339596c08294ca2d0adddb82a1a0f9a5a103c26 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,4 +1,5 @@ import datetime +import json from json.decoder import JSONDecodeError import pytest @@ -7,20 +8,28 @@ from requests import HTTPError from setra_client.client import SetraClient from setra_client.client import SetraEndpoints -from setra_client.models import CompleteBatch, BatchErrors, Parameter, InputBatch, \ - OutputBatch +from setra_client.models import ( + CompleteBatch, + BatchErrors, + Parameter, + InputBatch, + OutputBatch, + AbwOrder, + Order, + Detail, +) @pytest.fixture def header_name(): - return 'X-Test' + return "X-Test" @pytest.fixture def client_cls(header_name): class TestClient(SetraClient): default_headers = { - header_name: '6a9a32f0-7322-4ef3-bbce-6685a3388e67', + header_name: "6a9a32f0-7322-4ef3-bbce-6685a3388e67", } return TestClient @@ -41,7 +50,7 @@ def test_init_applies_default_headers(client_cls, baseurl, header_name): def test_init_modify_defaults(client_cls, baseurl, header_name): - headers = {header_name: 'ede37fdd-a2ae-4a96-9d80-110528425ea6'} + headers = {header_name: "ede37fdd-a2ae-4a96-9d80-110528425ea6"} client = client_cls(baseurl, headers=headers) # Check that we respect the headers arg, and don't use default_headers assert client.headers[header_name] == headers[header_name] @@ -51,24 +60,25 @@ def test_init_modify_defaults(client_cls, baseurl, header_name): # Test call method + def test_get_successful_batch_with_json_content(client, requests_mock, baseurl): """A working GET call should return HTTP 200, with json content""" - url = SetraEndpoints(baseurl).batch(batch_id='3') # https://localhost/api/batch/3 - requests_mock.get(url, json={'foo': 'bar'}, status_code=200) + url = SetraEndpoints(baseurl).batch(batch_id="3") # https://localhost/api/batch/3 + requests_mock.get(url, json={"foo": "bar"}, status_code=200) - response = client.call(method_name='GET', url=url) + response = client.call(method_name="GET", url=url) assert response.status_code == 200 - assert response.json() == {'foo': 'bar'} + assert response.json() == {"foo": "bar"} def test_get_successful_batch_with_text_content(client, requests_mock, baseurl): """A working GET call should return HTTP 200, with json content""" - url = SetraEndpoints(baseurl).batch(batch_id='3') # https://localhost/api/batch/3 + url = SetraEndpoints(baseurl).batch(batch_id="3") # https://localhost/api/batch/3 requests_mock.get(url, text="some content", status_code=200) - response = client.call(method_name='GET', url=url) + response = client.call(method_name="GET", url=url) assert response.status_code == 200 assert response.text == "some content" @@ -76,11 +86,11 @@ def test_get_successful_batch_with_text_content(client, requests_mock, baseurl): def test_get_failing_batch_with_json_content(client, batch_url, requests_mock, baseurl): """A failing GET call with 404 should pass through the same response from the client, and return HTTP 404, and the request with json content""" - requests_mock.get(batch_url, json={'a': 'b'}, status_code=404) + requests_mock.get(batch_url, json={"a": "b"}, status_code=404) - resp = client.call(method_name='GET', url=batch_url) + resp = client.call(method_name="GET", url=batch_url) assert resp.status_code == 404 - assert resp.json() == {'a': 'b'} + assert resp.json() == {"a": "b"} def test_get_failing_batch_with_text_content(client, batch_url, requests_mock, baseurl): @@ -88,83 +98,99 @@ def test_get_failing_batch_with_text_content(client, batch_url, requests_mock, b and the request with json content""" requests_mock.get(batch_url, text="some content", status_code=404) - resp = client.call(method_name='GET', url=batch_url) + resp = client.call(method_name="GET", url=batch_url) assert resp.status_code == 404 assert resp.text == "some content" -def test_get_failing_batch_without_return_response_text(client, batch_url, requests_mock, baseurl): +def test_get_failing_batch_without_return_response_text( + client, batch_url, requests_mock, baseurl +): """A failing GET call with 404 should raise HTTPError from the client, and return HTTP 404, and the request with text content""" requests_mock.get(batch_url, text="some content", status_code=404) with pytest.raises(HTTPError) as err: - client.call(method_name='GET', url=batch_url, return_response=False) + client.call(method_name="GET", url=batch_url, return_response=False) assert err.type == requests.exceptions.HTTPError assert err.value.response.status_code == 404 assert err.value.response.text == "some content" -def test_get_failing_batch_without_return_response_json(client, batch_url, requests_mock, baseurl): +def test_get_failing_batch_without_return_response_json( + client, batch_url, requests_mock, baseurl +): """A failing GET call with 404 should raise HTTPError from the client, and return HTTP 404, and the request with json content""" - requests_mock.get(batch_url, json={'a': 'b'}, status_code=404) + requests_mock.get(batch_url, json={"a": "b"}, status_code=404) with pytest.raises(HTTPError) as err: - client.call(method_name='GET', url=batch_url, return_response=False) + client.call(method_name="GET", url=batch_url, return_response=False) assert err.type == requests.exceptions.HTTPError assert err.value.response.status_code == 404 - assert err.value.response.json() == {'a': 'b'} + assert err.value.response.json() == {"a": "b"} -def test_get_failing_batch_without_return_response2(client, batch_url, requests_mock, baseurl): +def test_get_failing_batch_without_return_response2( + client, batch_url, requests_mock, baseurl +): """A failing GET call with 404 returning text content, with return_response=False should raise for HTTPError from the client - (because we expect json from setra, and it will give error with text content) """ + (because we expect json from setra, and it will give error with text content)""" requests_mock.get(batch_url, text="some content", status_code=200) with pytest.raises(JSONDecodeError) as err: - client.call(method_name='GET', url=batch_url, return_response=False) + client.call(method_name="GET", url=batch_url, return_response=False) assert err.type == JSONDecodeError + # Test post method + def test_get_failing_batch_with_http500(client, batch_url, requests_mock, baseurl): """A failing POST call with return_response=False and http 500, should raise HTTPError from the client, and return HTTP 500, and the request with json content""" - requests_mock.post(batch_url, json={'a': 'b'}, status_code=500) + requests_mock.post(batch_url, json={"a": "b"}, status_code=500) with pytest.raises(HTTPError) as err: client.post(url=batch_url, return_response=False) assert err.type == requests.exceptions.HTTPError assert err.value.response.status_code == 500 - assert err.value.response.json() == {'a': 'b'} + assert err.value.response.json() == {"a": "b"} def test_get_failing_batch_with_http5002(client, batch_url, requests_mock, baseurl): """A failing POST call with 500 with return_response=True, should just return the response, with 500""" - requests_mock.post(batch_url, json={'a': 'b'}, status_code=500) + requests_mock.post(batch_url, json={"a": "b"}, status_code=500) response = client.post(url=batch_url) assert response.status_code == 500 - assert response.json() == {'a': 'b'} + assert response.json() == {"a": "b"} # Test setting headers + def test_header_replacement(client_with_a_header, batch_url, requests_mock, baseurl): """Given a SetraClient created with a "global" header, making a request with the same header key, should replace the "global" header value""" - requests_mock.get(batch_url, text="some content", status_code=200, headers={"content-type": "replaced"}) + requests_mock.get( + batch_url, + text="some content", + status_code=200, + headers={"content-type": "replaced"}, + ) - response = client_with_a_header.call(method_name='GET', url=batch_url, headers={"content-type": "replaced"}) + response = client_with_a_header.call( + method_name="GET", url=batch_url, headers={"content-type": "replaced"} + ) assert response.status_code == 200 assert response.text == "some content" assert response.headers == {"content-type": "replaced"} @@ -175,9 +201,16 @@ def test_header_replacement2(client_with_a_header, batch_url, requests_mock, bas making a request with the same header key, should replace the "global" header value, while new headers should be added to the request""" - requests_mock.get(batch_url, text="some content", status_code=200, headers={"content-type": "replaced", "key": "val"}) + requests_mock.get( + batch_url, + text="some content", + status_code=200, + headers={"content-type": "replaced", "key": "val"}, + ) - response = client_with_a_header.call(method_name='GET', url=batch_url, headers={"content-type": "replaced"}) + response = client_with_a_header.call( + method_name="GET", url=batch_url, headers={"content-type": "replaced"} + ) assert response.status_code == 200 assert response.text == "some content" assert response.headers == {"content-type": "replaced", "key": "val"} @@ -185,6 +218,7 @@ def test_header_replacement2(client_with_a_header, batch_url, requests_mock, bas # Test get_batches method + def test_successful_get_all_batches(client, requests_mock, baseurl, batch_fixture): """A working GET call should return HTTP 200, with json content""" url = SetraEndpoints(baseurl).batch() @@ -195,7 +229,7 @@ def test_successful_get_all_batches(client, requests_mock, baseurl, batch_fixtur def test_successful_get_batches_filtered_by_status( - client, requests_mock, baseurl, batch_fixture + client, requests_mock, baseurl, batch_fixture ): """A working GET call should return HTTP 200, with json content""" url = SetraEndpoints(baseurl).batch() @@ -206,10 +240,10 @@ def test_successful_get_batches_filtered_by_status( def test_successfully_getting_single_batch( - client, requests_mock, baseurl, batch_fixture + client, requests_mock, baseurl, batch_fixture ): """A working GET call should return HTTP 200, with json content""" - url = SetraEndpoints(baseurl).batch(batch_id='3') + url = SetraEndpoints(baseurl).batch(batch_id="3") requests_mock.get(url, json=batch_fixture, status_code=200) response = client.get_batch(3) @@ -226,64 +260,70 @@ def test_failing_to_get_all_batches(client, batch_url, requests_mock, baseurl): # Test get_voucher method + def test_successfully_getting_single_voucher(client, requests_mock, baseurl): """A working GET call should return HTTP 200, with json content""" - url = SetraEndpoints(baseurl).voucher(vouch_id='5') - requests_mock.get(url, json={'foo': 'bar'}, status_code=200) + url = SetraEndpoints(baseurl).voucher(vouch_id="5") + requests_mock.get(url, json={"foo": "bar"}, status_code=200) response = client.get_voucher(5) - assert response == {'foo': 'bar'} + assert response == {"foo": "bar"} -def test_requesting_single_voucher_with_invalid_voucherid(client, requests_mock, baseurl): +def test_requesting_single_voucher_with_invalid_voucherid( + client, requests_mock, baseurl +): """Requesting a voucher, with None as voucherid, will request get all vouchers instead """ url = SetraEndpoints(baseurl).voucher() - requests_mock.get(url, json={'foo': 'bar'}, status_code=200) + requests_mock.get(url, json={"foo": "bar"}, status_code=200) - response = client.get_voucher(None) #using None as voucherid - assert response == {'foo': 'bar'} + response = client.get_voucher(None) # using None as voucherid + assert response == {"foo": "bar"} -def test_successfully_getting_single_voucher_with_alphanumeric_voucherid(client, requests_mock, baseurl): +def test_successfully_getting_single_voucher_with_alphanumeric_voucherid( + client, requests_mock, baseurl +): """Requesting a voucher, with alphanumeric voucherid, will work, and not crash TODO: investigate if this is intentional behaviour (need spec on voucher id format) """ url = SetraEndpoints(baseurl).voucher("abcd123efg") - requests_mock.get(url, json={'foo': 'bar'}, status_code=200) + requests_mock.get(url, json={"foo": "bar"}, status_code=200) - response = client.get_voucher("abcd123efg") #using alphanum string as voucherid - assert response == {'foo': 'bar'} + response = client.get_voucher("abcd123efg") # using alphanum string as voucherid + assert response == {"foo": "bar"} def test_failing_to_get_all_vouchers(client, requests_mock, baseurl): """A failing GET all vouchers call should still return json""" url = SetraEndpoints(baseurl).voucher() - requests_mock.get(url, json={'error': 'some json error message'}, status_code=404) + requests_mock.get(url, json={"error": "some json error message"}, status_code=404) response = client.get_voucher() - assert response == {'error': 'some json error message'} + assert response == {"error": "some json error message"} # Test get_transaction method + def test_successfully_getting_single_transaction(client, requests_mock, baseurl): """A working GET call should return HTTP 200, with json content""" - url = SetraEndpoints(baseurl).transaction(trans_id='9') - requests_mock.get(url, json={'foo': 'bar'}, status_code=200) + url = SetraEndpoints(baseurl).transaction(trans_id="9") + requests_mock.get(url, json={"foo": "bar"}, status_code=200) response = client.get_transaction(9) - assert response == {'foo': 'bar'} + assert response == {"foo": "bar"} def test_failing_to_get_all_transactions(client, requests_mock, baseurl): """A failing GET all vouchers call should still return json""" url = SetraEndpoints(baseurl).transaction() - requests_mock.get(url, json={'error': 'some json error message'}, status_code=404) + requests_mock.get(url, json={"error": "some json error message"}, status_code=404) response = client.get_transaction() - assert response == {'error': 'some json error message'} + assert response == {"error": "some json error message"} # Test post_new_batch method @@ -299,7 +339,9 @@ def test_successfully_post_batch_with_voucher(client, batch_with_voucher_fixture assert data == {} -def test_successfully_post_batch_with_voucher_and_response(client, batch_with_voucher_fixture, requests_mock, baseurl): +def test_successfully_post_batch_with_voucher_and_response( + client, batch_with_voucher_fixture, requests_mock, baseurl +): """A working POST new batch call with return_response=True, should return the response with HTTP 202, with json content""" url = SetraEndpoints(baseurl).post_new_batch() @@ -330,13 +372,13 @@ def test_unknown_post_new_batch_state(client, batch_with_voucher_fixture, reques def test_successfully_getting_batch_complete( - client, requests_mock, baseurl, complete_batch + client, requests_mock, baseurl, complete_batch ): """A working GET call should return HTTP 200, with json content""" - url = SetraEndpoints(baseurl).batch_complete(batch_id='1') + url = SetraEndpoints(baseurl).batch_complete(batch_id="1") requests_mock.get(url, json=complete_batch, status_code=200) - response = client.get_batch_complete('1') + response = client.get_batch_complete("1") assert response == CompleteBatch(**complete_batch) @@ -348,18 +390,18 @@ def test_get_batch_error(baseurl, requests_mock, client, batch_errors): def test_successfully_getting_batch_complete_objectless( - client_no_objects, requests_mock, baseurl, complete_batch + client_no_objects, requests_mock, baseurl, complete_batch ): """A working GET call should return HTTP 200, with json content""" - url = SetraEndpoints(baseurl).batch_complete(batch_id='1') + url = SetraEndpoints(baseurl).batch_complete(batch_id="1") requests_mock.get(url, json=complete_batch, status_code=200) - response = client_no_objects.get_batch_complete('1') + response = client_no_objects.get_batch_complete("1") assert isinstance(response, dict) def test_get_batch_error_objectless( - client_no_objects, requests_mock, baseurl, batch_errors + client_no_objects, requests_mock, baseurl, batch_errors ): url = SetraEndpoints(baseurl).batch_error("5") requests_mock.get(url, json=batch_errors, status_code=200) @@ -384,7 +426,7 @@ def test_get_params_interface(client_no_objects, requests_mock, param_list): requests_mock.get( "https://localhost/api/parameters/?interface=HB", json=param_list[2:], - status_code=200 + status_code=200, ) response = client_no_objects.get_parameters(interface="HB") assert isinstance(response, list) @@ -412,9 +454,114 @@ def test_get_params_interface_object(client, requests_mock, param_list): requests_mock.get( "https://localhost/api/parameters/?interface=HB", json=param_list[2:], - status_code=200 + status_code=200, ) response = client.get_parameters(interface="HB") assert isinstance(response, list) assert isinstance(response[0], Parameter) assert len(response) == 4 + + +# Sotra tests: + + +def test_get_abworder(client, requests_mock, baseurl, complete_abw_order_fixture): + """Should return HTTP 200, with a complete abw order, with orders and detail objects""" + url = SetraEndpoints(baseurl).abw_order_complete(abw_order_id="2") + requests_mock.get(url, json=complete_abw_order_fixture, status_code=200) + + response = client.get_abw_order_complete(2) + assert response == AbwOrder(**complete_abw_order_fixture) + assert len(response.orders) == 2 + assert len(response.orders[0].details) == 1 + assert len(response.orders[1].details) == 1 + + +def test_get_order(client, requests_mock, baseurl, order_with_detail_fixture): + """Should return HTTP 200, with a complete order with detail objects""" + url = SetraEndpoints(baseurl).order_complete(order_id="2") + requests_mock.get(url, json=order_with_detail_fixture, status_code=200) + + response = client.get_order_complete(2) + assert response == Order(**order_with_detail_fixture) + + +def test_get_detail(client, requests_mock, baseurl, detail_fixture): + """Should return HTTP 200, with one detail""" + url = SetraEndpoints(baseurl).detail(detail_id="1") + requests_mock.get(url, json=detail_fixture, status_code=200) + + response = client.get_detail(1) + assert response == Detail(**detail_fixture) + + +def test_get_order_list(client, requests_mock, baseurl, order_list_fixture): + """Should return HTTP 200, with a list of orders""" + url = SetraEndpoints(baseurl).order() + requests_mock.get(url, json=order_list_fixture, status_code=200) + + response = client.get_order_list() + assert isinstance(response, list) + assert len(response) == 4 + + +def test_get_order_details_list(client, requests_mock, baseurl, detail_list_fixture): + """Should return HTTP 200, with a list of details for an order""" + url = SetraEndpoints(baseurl).details_in_order("1") + requests_mock.get(url, json=detail_list_fixture, status_code=200) + + response = client.get_details_in_order(order_id="1") + assert isinstance(response, list) + assert len(response) == 2 + + +def test_send_in_abworder(client, requests_mock, baseurl, complete_abw_order_fixture): + """Setra client should give us a tuple, with the data, and a status""" + url = SetraEndpoints(baseurl).add_abw_order() + resp = { + "responsible": "responsible2", + "interface": "testinterface", + "client": "testclient", + } + requests_mock.post(url, json=resp, status_code=202) + abworder = AbwOrder(**complete_abw_order_fixture) + + response = client.post_add_abw_order(abworder) + if isinstance(response, tuple): + assert response[0].decode("utf-8") == json.dumps(resp) + assert response[1] == "Accepted" + + +def test_send_in_abworder_failure_conflict( + client, requests_mock, baseurl, complete_abw_order_fixture +): + """Setra client should give us a tuple, with the data, and a status""" + url = SetraEndpoints(baseurl).add_abw_order() + resp = { + "responsible": "responsible2", + "interface": "testinterface", + "client": "testclient", + } + requests_mock.post(url, json=resp, status_code=409) + abworder = AbwOrder(**complete_abw_order_fixture) + + response = client.post_add_abw_order(abworder) + assert isinstance(response, tuple) + assert response[0].decode("utf-8") == json.dumps(resp) + assert response[1] == "Conflict" + + +def test_send_in_abworder_failure( + client, requests_mock, baseurl, complete_abw_order_fixture +): + """Setra client should get HTTPError if Setra returns 404""" + url = SetraEndpoints(baseurl).add_abw_order() + requests_mock.post(url, status_code=404) + abworder = AbwOrder(**complete_abw_order_fixture) + + with pytest.raises(HTTPError) as err: + client.post_add_abw_order(abworder) + + assert err.type == requests.exceptions.HTTPError + assert err.value.response.status_code == 404 + assert err.value.response.text == "" diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 5d0cde877ef71fcb0a489e0efe5dabce3872eda7..df614389c63c9934aef8e25491c5ac5ed582b2dc 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -45,3 +45,43 @@ def test_init_batch_complete_with_value(baseurl): def test_init_batch_error(baseurl): endpoints = SetraEndpoints(baseurl) assert endpoints.batch_error("123") == baseurl + "/api/batch_error/123" + + +def test_init_order_complete_with_value(baseurl): + endpoints = SetraEndpoints(baseurl) + assert endpoints.order_complete(order_id="7") == baseurl + "/api/order_complete/7" + + +def test_init_abw_order_complete_with_value(baseurl): + endpoints = SetraEndpoints(baseurl) + assert ( + endpoints.abw_order_complete(abw_order_id="4") + == baseurl + "/api/abw_order_complete/4" + ) + + +def test_init_order(baseurl): + endpoints = SetraEndpoints(baseurl) + assert endpoints.order() == baseurl + "/api/order/" + + +def test_init_order_with_value(baseurl): + endpoints = SetraEndpoints(baseurl) + assert endpoints.order(order_id="4") == baseurl + "/api/order/4" + + +def test_init_detail_with_value(baseurl): + endpoints = SetraEndpoints(baseurl) + assert endpoints.detail(detail_id="3") == baseurl + "/api/detail/3" + + +def test_init_details_in_order(baseurl): + endpoints = SetraEndpoints(baseurl) + assert ( + endpoints.details_in_order(order_id="9") == baseurl + "/api/details_in_order/9" + ) + + +def test_init_add_abw_order(baseurl): + endpoints = SetraEndpoints(baseurl) + assert endpoints.add_abw_order() == baseurl + "/api/add_abw_order/" diff --git a/tests/test_models.py b/tests/test_models.py index a845e4bdef54a8c15071d9904568bf9d8f20c4d2..4bef489aacda1a6e96264f7b61e12123e62bf370 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -9,7 +9,12 @@ from setra_client.models import ( ErrorBatch, Parameter, Transaction, - Voucher, InputBatch, OutputBatch, + Voucher, + InputBatch, + OutputBatch, + AbwOrder, + Order, + Detail, ) @@ -28,8 +33,8 @@ def test_voucher(voucher_fixture): def test_voucher_has_transactions(voucher_fixture): voucher = Voucher(**voucher_fixture) assert len(voucher.transactions) == 2 - assert voucher.transactions.__getitem__(0).amount == 1 - assert voucher.transactions.__getitem__(1).amount == 2 + assert voucher.transactions[0].amount == 1 + assert voucher.transactions[1].amount == 2 def test_transaction(trans_fixture): @@ -77,3 +82,33 @@ def test_params(param_list): assert param[0].created == datetime.datetime( 2020, 1, 1, 2, 0, 0, tzinfo=datetime.timezone.utc ) + + +def test_abw_order(abw_order_fixture): + assert AbwOrder(**abw_order_fixture) + + +def test_order(order_fixture): + assert Order(**order_fixture) + + +def test_detail(detail_fixture): + assert Detail(**detail_fixture) + + +def test_order_with_detail(order_with_detail_fixture): + order = Order(**order_with_detail_fixture) + assert len(order.details) == 2 + assert order.details[0].koststed == "123123" + assert order.details[1].koststed == "444" + + +def test_complete_abw_order(complete_abw_order_fixture): + abworder = AbwOrder(**complete_abw_order_fixture) + assert len(abworder.orders) == 2 + assert len(abworder.orders[0].details) == 1 + assert len(abworder.orders[1].details) == 1 + assert abworder.orders[0].id == 3 + assert abworder.orders[0].details[0].id == 2 + assert abworder.orders[1].id == 4 + assert abworder.orders[1].details[0].id == 3