From a227465bf59184be696647d613952622bc7c1bdf Mon Sep 17 00:00:00 2001 From: Henrich Neumann <henrich.neumann@usit.uio.no> Date: Wed, 4 Jan 2023 12:40:01 +0100 Subject: [PATCH] Add linting python and yaml and error checking json files to pipeline --- .gitignore | 1 + .gitlab-ci.yml | 15 +++ .yamllint.yaml | 32 ++++++ setra_client/__init__.py | 2 +- setra_client/client.py | 236 +++++++++++++++++++++------------------ setra_client/models.py | 39 ++++--- setra_client/version.py | 9 +- setup.py | 66 ++++++----- tests/conftest.py | 39 +++---- tests/test_client.py | 84 +++++++++----- tox.ini | 21 ++++ 11 files changed, 333 insertions(+), 211 deletions(-) create mode 100644 .yamllint.yaml diff --git a/.gitignore b/.gitignore index e00047d..a7136ad 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist/ junit-*.xml .mypy_cache/ venv/ +.venv/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 48cd4ae..2df9383 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,21 @@ before_script: - pip install -r requirements.txt - pip install -r requirements-test.txt +code-format: + image: python:3.8 + stage: test + script: tox -e black + +yamllint: + image: python:3.8 + stage: test + script: tox -e yamllint + +json: + image: python:3.8 + stage: test + script: tox -e json + python36: image: python:3.6 stage: test diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..0246e48 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,32 @@ +ignore-from-file: .gitignore + +rules: + braces: + max-spaces-inside-empty: 0 + brackets: + max-spaces-inside-empty: 0 + colons: enable + commas: enable + comments: enable + comments-indentation: enable + document-end: disable + document-start: disable + empty-lines: + max: 100 + empty-values: enable + float-values: disable + hyphens: enable + indentation: + spaces: 2 + key-duplicates: enable + key-ordering: disable + line-length: disable + new-line-at-end-of-file: enable + new-lines: enable + octal-values: disable + quoted-strings: + quote-type: double + required: false + allow-quoted-quotes: true + trailing-spaces: enable + truthy: enable diff --git a/setra_client/__init__.py b/setra_client/__init__.py index a055290..7b5c675 100644 --- a/setra_client/__init__.py +++ b/setra_client/__init__.py @@ -2,5 +2,5 @@ from .client import SetraClient from .version import get_distribution -__all__ = ['SetraClient'] +__all__ = ["SetraClient"] __version__ = get_distribution().version diff --git a/setra_client/client.py b/setra_client/client.py index fa92bf8..e8fbc99 100644 --- a/setra_client/client.py +++ b/setra_client/client.py @@ -49,27 +49,26 @@ class IncorrectPathError(Exception): class SetraEndpoints: - def __init__(self, - url, - batch_url='api/batch/', - transaction_url='api/transaction/', - voucher_url='api/voucher/', - new_batch_url='api/addtrans/', - put_batch_url='api/addtrans/', - 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/", - ): - + def __init__( + self, + url, + batch_url="api/batch/", + transaction_url="api/transaction/", + voucher_url="api/voucher/", + new_batch_url="api/addtrans/", + put_batch_url="api/addtrans/", + 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 @@ -91,9 +90,7 @@ class SetraEndpoints: """ Get endpoints relative to the SETRA API URL. """ def __repr__(self): - return '{cls.__name__}({url!r})'.format( - cls=type(self), - url=self.baseurl) + return "{cls.__name__}({url!r})".format(cls=type(self), url=self.baseurl) def batch(self, batch_id: str = None): """ @@ -102,8 +99,9 @@ class SetraEndpoints: if batch_id is None: return urllib.parse.urljoin(self.baseurl, self.batch_url) else: - return urllib.parse.urljoin(self.baseurl, - '/'.join((self.batch_url, batch_id))) + return urllib.parse.urljoin( + self.baseurl, "/".join((self.batch_url, batch_id)) + ) def transaction(self, trans_id: str = None): """ @@ -112,9 +110,9 @@ class SetraEndpoints: if trans_id is None: return urllib.parse.urljoin(self.baseurl, self.transaction_url) else: - return urllib.parse.urljoin(self.baseurl, - '/'.join((self.transaction_url, - trans_id))) + return urllib.parse.urljoin( + self.baseurl, "/".join((self.transaction_url, trans_id)) + ) def voucher(self, vouch_id: str = None): """ @@ -123,8 +121,9 @@ class SetraEndpoints: if vouch_id is None: return urllib.parse.urljoin(self.baseurl, self.voucher_url) else: - return urllib.parse.urljoin(self.baseurl, - '/'.join((self.voucher_url, vouch_id))) + return urllib.parse.urljoin( + self.baseurl, "/".join((self.voucher_url, vouch_id)) + ) def post_new_batch(self): return urllib.parse.urljoin(self.baseurl, self.new_batch_url) @@ -136,15 +135,16 @@ class SetraEndpoints: """ URL for Batch endpoint """ - return urllib.parse.urljoin(self.baseurl, - '/'.join((self.batch_complete_url, batch_id))) + return urllib.parse.urljoin( + self.baseurl, "/".join((self.batch_complete_url, batch_id)) + ) def batch_error(self, batch_id: str): """ URL for batch_error endpoint """ return urllib.parse.urljoin( - self.baseurl, '/'.join((self.batch_error_url, batch_id)) + self.baseurl, "/".join((self.batch_error_url, batch_id)) ) def parameters(self): @@ -155,8 +155,9 @@ class SetraEndpoints: """ 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))) + return urllib.parse.urljoin( + self.baseurl, "/".join((self.abw_order_complete_url, abw_order_id)) + ) def add_abw_order(self): """ @@ -171,49 +172,55 @@ class SetraEndpoints: 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))) + 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))) + 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))) + 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))) + 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))) + return urllib.parse.urljoin( + self.baseurl, "/".join((self.abw_order_errors_url, abw_order_id)) + ) class SetraClient(object): default_headers = { - 'Accept': 'application/json', + "Accept": "application/json", } - def __init__(self, - url: str, - headers: Union[None, dict] = None, - return_objects: bool = True, - use_sessions: bool = True, - ): + def __init__( + self, + url: str, + headers: Union[None, dict] = None, + return_objects: bool = True, + use_sessions: bool = True, + ): """ SETRA API client. @@ -235,43 +242,45 @@ class SetraClient(object): request_headers = {} for h in self.headers: request_headers[h] = self.headers[h] - for h in (headers or ()): + for h in headers or (): request_headers[h] = headers[h] return request_headers - def call(self, - method_name, - url, - headers=None, - params=None, - return_response=True, - **kwargs): - if method_name == 'GET': + def call( + self, + method_name, + url, + headers=None, + params=None, + return_response=True, + **kwargs, + ): + if method_name == "GET": return_response = False headers = self._build_request_headers(headers) if params is None: params = {} - logger.debug('Calling %s %s with params=%r', - method_name, - urllib.parse.urlparse(url).path, - params) - r = self.session.request(method_name, - url, - headers=headers, - params=params, - **kwargs) + logger.debug( + "Calling %s %s with params=%r", + method_name, + urllib.parse.urlparse(url).path, + params, + ) + r = self.session.request( + method_name, url, headers=headers, params=params, **kwargs + ) if r.status_code in (500, 400, 401): - logger.warning('Got HTTP %d: %r', r.status_code, r.content) - elif r.status_code == 404 and method_name == 'GET': + logger.warning("Got HTTP %d: %r", r.status_code, r.content) + elif r.status_code == 404 and method_name == "GET": try: data = r.json() - if 'detail' in data and 'Not found' in data['detail']: + if "detail" in data and "Not found" in data["detail"]: return None else: raise IncorrectPathError except: data = r.text - if 'Not found' in data: + if "Not found" in data: return None else: raise IncorrectPathError @@ -288,17 +297,15 @@ class SetraClient(object): return return_data def get(self, url, **kwargs): - return self.call('GET', url, **kwargs) + return self.call("GET", url, **kwargs) def put(self, url, **kwargs): - return self.call('PUT', url, **kwargs) + return self.call("PUT", url, **kwargs) def post(self, url, **kwargs): - return self.call('POST', url, **kwargs) + return self.call("POST", url, **kwargs) - def object_or_data( - self, cls, data: Union[dict, List[dict]] - ) -> Union[object, dict]: + def object_or_data(self, cls, data: Union[dict, List[dict]]) -> Union[object, dict]: """Create list of objects or return data as is""" if not self.return_objects: return data @@ -325,10 +332,10 @@ class SetraClient(object): """ params = { - 'created__gte': min_created_date, - 'created__lte': max_created_date, - 'batch_progress': batch_progress, - 'interface': interface, + "created__gte": min_created_date, + "created__lte": max_created_date, + "batch_progress": batch_progress, + "interface": interface, } url = self.urls.batch() @@ -380,23 +387,27 @@ class SetraClient(object): POST combination of batch, vouchers and transactions """ url = self.urls.post_new_batch() - headers = {'Content-Type': 'application/json'} - response = self.post(url, - data=batchdata.json(exclude_unset=True), - headers=headers, - return_response=True) + headers = {"Content-Type": "application/json"} + response = self.post( + url, + data=batchdata.json(exclude_unset=True), + headers=headers, + return_response=True, + ) try: content = response.json() except ValueError: content = response.content if response.status_code == 202: - return ResponseStatusEnum.ACCEPTED, {'code': 202, 'content': None} + return ResponseStatusEnum.ACCEPTED, {"code": 202, "content": None} elif response.status_code == 409: - return ResponseStatusEnum.CONFLICT, {'code': 409, 'content': content} + return ResponseStatusEnum.CONFLICT, {"code": 409, "content": content} else: return ResponseStatusEnum.UNKNOWN, { - 'code': response.status_code, 'content': content} + "code": response.status_code, + "content": content, + } def put_update_batch(self, batchdata: InputBatch): """ @@ -406,11 +417,13 @@ class SetraClient(object): was found, but did not meet the status criteria mentioned above. """ url = self.urls.put_update_batch() - headers = {'Content-Type': 'application/json'} - response = self.put(url, - data=batchdata.json(exclude_unset=True), - headers=headers, - return_response=True) + headers = {"Content-Type": "application/json"} + response = self.put( + url, + data=batchdata.json(exclude_unset=True), + headers=headers, + return_response=True, + ) try: content = response.json() @@ -418,12 +431,14 @@ class SetraClient(object): content = response.content if response.status_code == 204: - return ResponseStatusEnum.ACCEPTED, {'code': 204, 'content': None} + return ResponseStatusEnum.ACCEPTED, {"code": 204, "content": None} elif response.status_code == 409: - return ResponseStatusEnum.CONFLICT, {'code': 409, 'content': content} + return ResponseStatusEnum.CONFLICT, {"code": 409, "content": content} else: return ResponseStatusEnum.UNKNOWN, { - 'code': response.status_code, 'content': content} + "code": response.status_code, + "content": content, + } def get_batch_complete(self, batch_id: str): """ @@ -532,24 +547,25 @@ class SetraClient(object): 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) - + headers = {"Content-Type": "application/json"} + response = self.post( + url, data=abworder.json(), headers=headers, return_response=True + ) + try: content = response.json() except ValueError: content = response.content if response.status_code == 202: - return ResponseStatusEnum.ACCEPTED, {'code': 202, 'content': content} + return ResponseStatusEnum.ACCEPTED, {"code": 202, "content": content} elif response.status_code == 409: - return ResponseStatusEnum.CONFLICT, {'code': 409, 'content': content} + return ResponseStatusEnum.CONFLICT, {"code": 409, "content": content} else: return ResponseStatusEnum.UNKNOWN, { - 'code': response.status_code, 'content': content} + "code": response.status_code, + "content": content, + } def get_abw_order_errors(self, abw_order_id: str): """ diff --git a/setra_client/models.py b/setra_client/models.py index c68b27c..0fa2645 100644 --- a/setra_client/models.py +++ b/setra_client/models.py @@ -8,8 +8,8 @@ import pydantic def to_lower_camel(s: str) -> str: """Alias generator to avoid breaking PEP8""" - first, *others = s.split('_') - return ''.join([first.lower(), *map(str.capitalize, others)]) + first, *others = s.split("_") + return "".join([first.lower(), *map(str.capitalize, others)]) class BaseModel(pydantic.BaseModel): @@ -28,26 +28,26 @@ class BaseModel(pydantic.BaseModel): class BatchProgressEnum(str, Enum): - CREATED = 'created' - VALIDATION_COMPLETED = 'validation_completed' - VALIDATION_FAILED = 'validation_failed' - SENT_TO_UBW = 'sent_to_ubw' - SEND_TO_UBW_FAILED = 'send_to_ubw_failed' - POLLING_COMPLETED = 'polling_completed' - POLLING_FAILED = 'polling_failed' - UBW_IMPORT_OK = 'ubw_import_ok' - UBW_IMPORT_FAILED = 'ubw_import_failed' - FETCH_FINAL_VOUCHERNO_COMPLETED = 'fetch_final_voucherno_completed' - FETCH_FINAL_VOUCHERNO_FAILED = 'fetch_final_voucherno_failed' + CREATED = "created" + VALIDATION_COMPLETED = "validation_completed" + VALIDATION_FAILED = "validation_failed" + SENT_TO_UBW = "sent_to_ubw" + SEND_TO_UBW_FAILED = "send_to_ubw_failed" + POLLING_COMPLETED = "polling_completed" + POLLING_FAILED = "polling_failed" + UBW_IMPORT_OK = "ubw_import_ok" + UBW_IMPORT_FAILED = "ubw_import_failed" + FETCH_FINAL_VOUCHERNO_COMPLETED = "fetch_final_voucherno_completed" + FETCH_FINAL_VOUCHERNO_FAILED = "fetch_final_voucherno_failed" def __str__(self): return str(self.value) class ResponseStatusEnum(str, Enum): - ACCEPTED = 'Accepted' - CONFLICT = 'Conflict' - UNKNOWN = 'Unknown' + ACCEPTED = "Accepted" + CONFLICT = "Conflict" + UNKNOWN = "Unknown" def __str__(self): return str(self.value) @@ -98,6 +98,7 @@ class OutputBatch(BaseModel): """ Model representing a batch, with a list of voucher ids connected to that batch. """ + id: int created: str batchid: str @@ -149,6 +150,7 @@ class ErrorBatch(BaseModel): class BatchErrors(BaseModel): """Model for the /batch_error/<id> endpoint""" + batch_errors: List[ErrorBatch] voucher_errors: List[ErrorVoucher] transaction_errors: List[ErrorTransaction] @@ -192,6 +194,7 @@ class CompleteVoucher(BaseModel): class CompleteBatch(BaseModel): """Model for the /batch_complete/<id> endpoint""" + created: datetime.datetime batchid: str period: Optional[int] @@ -273,6 +276,7 @@ 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.datetime] # Cannot be sent in @@ -280,7 +284,7 @@ class AbwOrder(BaseModel): interface: str client: str abworderid: str - ordrenr_ubw: Optional[str] # Not sent in, but returned by the API. + ordrenr_ubw: Optional[str] # Not sent in, but returned by the API. orders: List[Order] @@ -312,6 +316,7 @@ class ErrorAbwOrder(BaseModel): 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/setra_client/version.py b/setra_client/version.py index b81165e..6071371 100644 --- a/setra_client/version.py +++ b/setra_client/version.py @@ -4,15 +4,16 @@ import os import pkg_resources -DISTRIBUTION_NAME = 'setra-client' +DISTRIBUTION_NAME = "setra-client" def get_distribution(): - """ Get the distribution object for this single module dist. """ + """Get the distribution object for this single module dist.""" try: return pkg_resources.get_distribution(DISTRIBUTION_NAME) except pkg_resources.DistributionNotFound: return pkg_resources.Distribution( project_name=DISTRIBUTION_NAME, - version='0.0.0', - location=os.path.dirname(__file__)) + version="0.0.0", + location=os.path.dirname(__file__), + ) diff --git a/setup.py b/setup.py index a72c1fa..665399c 100644 --- a/setup.py +++ b/setup.py @@ -6,36 +6,35 @@ import setuptools.command.test def get_requirements(filename): - """ Read requirements from file. """ - with open(filename, mode='rt', encoding='utf-8') as f: + """Read requirements from file.""" + with open(filename, mode="rt", encoding="utf-8") as f: for line in f: # TODO: Will not work with #egg-info - requirement = line.partition('#')[0].strip() + requirement = line.partition("#")[0].strip() if not requirement: continue yield requirement def get_textfile(filename): - """ Get contents from a text file. """ - with open(filename, mode='rt', encoding='utf-8') as f: + """Get contents from a text file.""" + with open(filename, mode="rt", encoding="utf-8") as f: return f.read().lstrip() def get_packages(): - """ List of (sub)packages to install. """ - return setuptools.find_packages('.', include=('setra_client', - 'setra_client.*')) + """List of (sub)packages to install.""" + return setuptools.find_packages(".", include=("setra_client", "setra_client.*")) class PyTest(setuptools.command.test.test): - """ Run tests using pytest. + """Run tests using pytest. From `http://doc.pytest.org/en/latest/goodpractices.html`. """ - user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")] + user_options = [("pytest-args=", "a", "Arguments to pass to pytest")] def initialize_options(self): super().initialize_options() @@ -44,6 +43,7 @@ class PyTest(setuptools.command.test.test): def run_tests(self): import shlex import pytest + args = self.pytest_args if args: args = shlex.split(args) @@ -52,44 +52,42 @@ class PyTest(setuptools.command.test.test): def run_setup(): - setup_requirements = ['setuptools_scm'] - test_requirements = list(get_requirements('requirements-test.txt')) - install_requirements = list(get_requirements('requirements.txt')) + setup_requirements = ["setuptools_scm"] + test_requirements = list(get_requirements("requirements-test.txt")) + install_requirements = list(get_requirements("requirements.txt")) - if {'build_sphinx', 'upload_docs'}.intersection(sys.argv): - setup_requirements.extend(get_requirements('docs/requirements.txt')) + if {"build_sphinx", "upload_docs"}.intersection(sys.argv): + setup_requirements.extend(get_requirements("docs/requirements.txt")) setup_requirements.extend(install_requirements) setuptools.setup( - name='setra-client', - description='Client for the SETRA API', - long_description=get_textfile('README.md'), - long_description_content_type='text/markdown', - - url='https://git.app.uib.no/it-bott-integrasjoner/setra-client', - author='BOTT-INT', - author_email='bnt-int@usit.uio.no', - + name="setra-client", + description="Client for the SETRA API", + long_description=get_textfile("README.md"), + long_description_content_type="text/markdown", + url="https://git.app.uib.no/it-bott-integrasjoner/setra-client", + author="BOTT-INT", + author_email="bnt-int@usit.uio.no", use_scm_version=True, packages=get_packages(), setup_requires=setup_requirements, install_requires=install_requirements, tests_require=test_requirements, cmdclass={ - 'test': PyTest, + "test": PyTest, }, classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], - keywords='SETRA API client', + keywords="SETRA API client", ) -if __name__ == '__main__': +if __name__ == "__main__": run_setup() diff --git a/tests/conftest.py b/tests/conftest.py index 11bc095..34c1121 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,16 +9,16 @@ from setra_client.client import SetraClient, SetraEndpoints def load_json_file(name): """Load json file from the fixtures directory""" here = os.path.realpath( - os.path.join(os.getcwd(), - os.path.dirname(__file__).rsplit('/', 1)[0])) - with open(os.path.join(here, 'tests/fixtures', name)) as f: + os.path.join(os.getcwd(), os.path.dirname(__file__).rsplit("/", 1)[0]) + ) + with open(os.path.join(here, "tests/fixtures", name)) as f: data = json.load(f) return data @pytest.fixture def baseurl(): - return 'https://localhost' + return "https://localhost" @pytest.fixture @@ -28,13 +28,14 @@ def endpoints(baseurl): @pytest.fixture def custom_endpoints(baseurl): - return SetraEndpoints(baseurl, - '/custom/batch/', - '/custom/transaction/', - '/custom/voucher/', - '/custom/addtrans/', - '/custom/batch_complete/' - ) + return SetraEndpoints( + baseurl, + "/custom/batch/", + "/custom/transaction/", + "/custom/voucher/", + "/custom/addtrans/", + "/custom/batch_complete/", + ) @pytest.fixture @@ -54,42 +55,42 @@ def client_with_a_header(baseurl): @pytest.fixture def batch_url(baseurl): - return SetraEndpoints(baseurl).batch() # example: https://localhost/api/batch + return SetraEndpoints(baseurl).batch() # example: https://localhost/api/batch @pytest.fixture def batch_fixture(): - return load_json_file('batch_fixture.json') + return load_json_file("batch_fixture.json") @pytest.fixture def voucher_fixture(): - return load_json_file('voucher_fixture.json') + return load_json_file("voucher_fixture.json") @pytest.fixture def trans_fixture(): - return load_json_file('trans_fixture.json') + return load_json_file("trans_fixture.json") @pytest.fixture def trans_fail_fixture(): - return load_json_file('trans_fail_fixture.json') + return load_json_file("trans_fail_fixture.json") @pytest.fixture def batch_with_voucher_fixture(): - return load_json_file('batch_with_voucher_fixture.json') + return load_json_file("batch_with_voucher_fixture.json") @pytest.fixture def batch_without_voucher_field(): - return load_json_file('batch_without_voucher_field.json') + return load_json_file("batch_without_voucher_field.json") @pytest.fixture def batch_fail_fixture(): - return load_json_file('batch_fail_fixture.json') + return load_json_file("batch_fail_fixture.json") @pytest.fixture diff --git a/tests/test_client.py b/tests/test_client.py index f058348..d6132ac 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -85,7 +85,7 @@ def test_get_failing_batch_with_json_content(client, batch_url, requests_mock, b """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) - + with pytest.raises(IncorrectPathError): resp = client.call(method_name="GET", url=batch_url) @@ -261,7 +261,7 @@ def test_successfully_getting_single_voucher(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 """ + """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) @@ -283,11 +283,12 @@ def test_successfully_getting_single_voucher_with_alphanumeric_voucherid( response = client.get_voucher("abcd123efg") # using alphanum string as voucherid assert response == {"foo": "bar"} -#CLARIFY + +# CLARIFY def test_failing_to_get_all_vouchers(client, requests_mock, baseurl): - """A failing GET all vouchers call should still return json""" #REALLY? + """A failing GET all vouchers call should still return json""" # REALLY? url = SetraEndpoints(baseurl).voucher() - requests_mock.get(url, json={'detail': 'Not found.'}, status_code=404) + requests_mock.get(url, json={"detail": "Not found."}, status_code=404) response = client.get_voucher() assert response == None @@ -308,7 +309,7 @@ def test_successfully_getting_single_transaction(client, requests_mock, baseurl) 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={'detail': 'Not found.'}, status_code=404) + requests_mock.get(url, json={"detail": "Not found."}, status_code=404) response = client.get_transaction() assert response == None @@ -316,15 +317,23 @@ def test_failing_to_get_all_transactions(client, requests_mock, baseurl): # Test post_new_batch method -def test_successfully_post_batch_with_voucher(client, batch_with_voucher_fixture, requests_mock, baseurl): + +def test_successfully_post_batch_with_voucher( + client, batch_with_voucher_fixture, requests_mock, baseurl +): """A working GET call should return HTTP 202, with json content""" url = SetraEndpoints(baseurl).post_new_batch() batch = InputBatch.from_dict(batch_with_voucher_fixture) - requests_mock.post(url, json={}, status_code=202, request_headers={"Content-Type": "application/json"}) + requests_mock.post( + url, + json={}, + status_code=202, + request_headers={"Content-Type": "application/json"}, + ) state, data = client.post_new_batch(batch) # we get a response object back - assert state == 'Accepted' - assert data == {'code': 202, 'content': None} + assert state == "Accepted" + assert data == {"code": 202, "content": None} def test_successfully_post_batch_with_voucher_and_response( @@ -333,30 +342,49 @@ def test_successfully_post_batch_with_voucher_and_response( """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() - requests_mock.post(url, json={}, status_code=202, request_headers={"Content-Type": "application/json"}) #expect json content + requests_mock.post( + url, + json={}, + status_code=202, + request_headers={"Content-Type": "application/json"}, + ) # expect json content batch = InputBatch.from_dict(batch_with_voucher_fixture) state, data = client.post_new_batch(batch) # we get a response object back - assert state == 'Accepted' - assert data == {'code': 202, 'content': None} + assert state == "Accepted" + assert data == {"code": 202, "content": None} -def test_conflicting_post_new_batch(client, batch_with_voucher_fixture, requests_mock, baseurl): +def test_conflicting_post_new_batch( + client, batch_with_voucher_fixture, requests_mock, baseurl +): url = SetraEndpoints(baseurl).post_new_batch() - requests_mock.post(url, json={'error': 'batch is being processed'}, status_code=409, request_headers={"Content-Type": "application/json"}) #expect json content + requests_mock.post( + url, + json={"error": "batch is being processed"}, + status_code=409, + request_headers={"Content-Type": "application/json"}, + ) # expect json content batch = InputBatch.from_dict(batch_with_voucher_fixture) state, data = client.post_new_batch(batch) - assert state == 'Conflict' - assert data == {'code': 409, 'content': {'error': 'batch is being processed'}} + assert state == "Conflict" + assert data == {"code": 409, "content": {"error": "batch is being processed"}} -def test_unknown_post_new_batch_state(client, batch_with_voucher_fixture, requests_mock, baseurl): +def test_unknown_post_new_batch_state( + client, batch_with_voucher_fixture, requests_mock, baseurl +): url = SetraEndpoints(baseurl).post_new_batch() - requests_mock.post(url, json={'error': 'Batch is malformed, no id'}, status_code=500, request_headers={"Content-Type": "application/json"}) #expect json content + requests_mock.post( + url, + json={"error": "Batch is malformed, no id"}, + status_code=500, + request_headers={"Content-Type": "application/json"}, + ) # expect json content batch = InputBatch.from_dict(batch_with_voucher_fixture) state, data = client.post_new_batch(batch) - assert state == 'Unknown' - assert data == {'code': 500, 'content': {'error': 'Batch is malformed, no id'}} + assert state == "Unknown" + assert data == {"code": 500, "content": {"error": "Batch is malformed, no id"}} def test_successfully_getting_batch_complete( @@ -519,10 +547,14 @@ def test_send_in_abworder(client, requests_mock, baseurl, complete_abw_order_fix response = client.post_add_abw_order(abworder) if isinstance(response, tuple): assert response[0] == "Accepted" - assert response[1] == {'code': 202, 'content': { - 'responsible': 'responsible2', - 'interface': 'testinterface', - 'client': 'testclient'}} + assert response[1] == { + "code": 202, + "content": { + "responsible": "responsible2", + "interface": "testinterface", + "client": "testclient", + }, + } def test_send_in_abworder_failure_conflict( @@ -540,7 +572,7 @@ def test_send_in_abworder_failure_conflict( response = client.post_add_abw_order(abworder) assert isinstance(response, tuple) - assert response[1]['content'] == resp + assert response[1]["content"] == resp assert response[0] == "Conflict" diff --git a/tox.ini b/tox.ini index 657b6b7..7686b09 100644 --- a/tox.ini +++ b/tox.ini @@ -12,3 +12,24 @@ commands = [pytest] xfail_strict = true addopts = -rxs -v + +[testenv:black] +basepython = python3 +deps = + black +commands = + black --check --diff --target-version py310 . + +[testenv:yamllint] +deps = + yamllint +commands = + yamllint --config-file .yamllint.yaml . + +[testenv:json] +deps = + demjson3 +allowlist_externals = + sh +commands = + sh -c 'git ls-files -z "*.json" | xargs -0 jsonlint --strict' -- GitLab