diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..0bdf9349542f84135829e676c0a0df1d475210f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.eggs/ +.idea/ +.tox/ +*.egg-info/ +*/__pycache__/ +dist/ +junit-*.xml +.mypy_cache/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..48cd4ae8a9ae7f5c8ad9c4adeb931c8a9a2262d9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,26 @@ +image: python + +stages: + - test + +before_script: + - pip install -r requirements.txt + - pip install -r requirements-test.txt + +python36: + image: python:3.6 + stage: test + script: + - tox -e py36 + +python37: + image: python:3.7 + stage: test + script: + - tox -e py37 + +python38: + image: python:3.8 + stage: test + script: + - tox -e py38 diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000000000000000000000000000000000..ea1def2e7996ac4535296d16bf43550cc8a67284 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,29 @@ +#!/usr/bin/env groovy + +pipeline { + agent { label 'python3' } + stages { + stage('Run unit tests') { + steps { + sh 'tox' + sh 'python3.6 setup.py test -a "--junitxml=junit.xml"' + } + } + stage('Build source distribution') { + steps { + // source dist -> dist/setra-client-<version>.tar.gz + sh 'python3.6 setup.py sdist' + archiveArtifacts artifacts: 'dist/setra-client-*.tar.gz' + } + } + } + post { + always { + junit '**/junit*.xml' + } + cleanup { + sh 'rm -vf junit.xml' + sh 'rm -vrf build dist' + } + } +} diff --git a/README.md b/README.md index 4cb47aa7940eb583bb544a5f879b464482bff2c3..eb004224d14539667bb1a51b9103be7ba6a0e040 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,53 @@ # Setra Client -Client for doing HTTP requests to the SETRA api \ No newline at end of file +Client for doing HTTP requests to the SETRA api + +```python +from setra_client import SetraClient +from setra_client.models import Batch, Multi, Transaction, Voucher + +c = SetraClient(url='https://example.com', + headers={'X-Gravitee-API-Key': 'c-d-a-b'}) + +batch = Batch.from_dict({ + "client": 1, + "batchid": 2, + "period": 3, + "interface": 4, + "vouchertype": 5, + "batchid_interface": 6 +}) +vouchers = [Voucher.from_dict({ + "batchid": 1, + "voucherno_interface": 2, + "exref": 3, + "voucherno": 4 +})] +transactions = [Transaction.from_dict({ + "voucherid": 1, + "account": 1, + "amount": 1, + "transdate": 1, + "curamount": 1, + "currency": 1, + "description": 1, + "dim1": 1, + "dim2": 1, + "dim3": 1, + "dim4": 1, + "dim5": 1, + "dim6": 1, + "dim7": 1, + "sequenceno": 1, + "taxcode": 1, + "transtype": 1, + "arrivaldate": 1, + "compldelay": 1, + "discdate": 1, + "duedate": 1, + "extinvref": 1 +})] + +multi = Multi(batch=batch, vouchers=vouchers, transactions=transactions) +response = c.post_multi(multi) +``` diff --git a/example-config.json b/example-config.json new file mode 100644 index 0000000000000000000000000000000000000000..f9ce49691a1ec4788794cbfcfc0c44000ab14451 --- /dev/null +++ b/example-config.json @@ -0,0 +1,33 @@ +{ + "_": "Config example for the setra_client command-line utility", + "client": { + "url": "https:/example.com/", + "batch_url": "/batch/", + "transaction_url": "/transaction/", + "voucher_url": "/voucher/", + "multi_url": "/addtrans/", + "headers": { + "X-Gravitee-Api-Key": "..." + }, + "return_objects": true, + "use_sessions": true + }, + "logging": { + "version": 1, + "replace_existing_loggers": false, + "root": { + "level": "INFO" + }, + "loggers": { + "__main__": { + "level": "INFO" + }, + "setra_client": { + "level": "WARNING" + }, + "urllib3": { + "level": "DEBUG" + } + } + } +} diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 0000000000000000000000000000000000000000..b74d7b0763e9b7aed45201e3d92efaeac408cd2c --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,3 @@ +pytest +requests_mock +tox diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3ebed4dc42c006aebe7f8593613fafa1566d6458 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pydantic +requests +setuptools diff --git a/setra_client/__init__.py b/setra_client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a0552908729bd7e2a3727ec5a38ee053422fe178 --- /dev/null +++ b/setra_client/__init__.py @@ -0,0 +1,6 @@ +from .client import SetraClient +from .version import get_distribution + + +__all__ = ['SetraClient'] +__version__ = get_distribution().version diff --git a/setra_client/client.py b/setra_client/client.py new file mode 100644 index 0000000000000000000000000000000000000000..a0d1ae87904845cd650b10e71663b76c3666e3f7 --- /dev/null +++ b/setra_client/client.py @@ -0,0 +1,217 @@ +"""Client for connecting to SETRA API""" +import json +import logging +import os +import urllib.parse +from typing import Tuple, Union, List + +import requests + +from setra_client.models import Multi + +logger = logging.getLogger(__name__) + + +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: + data = json.load(f) + return data + + +def merge_dicts(*dicts): + """ + Combine a series of dicts without mutating any of them. + + >>> merge_dicts({'a': 1}, {'b': 2}) + {'a': 1, 'b': 2} + >>> merge_dicts({'a': 1}, {'a': 2}) + {'a': 2} + >>> merge_dicts(None, None, None) + {} + """ + combined = dict() + for d in dicts: + if not d: + continue + for k in d: + combined[k] = d[k] + return combined + + +class SetraEndpoints: + def __init__(self, + url, + batch_url='batch/', + transaction_url='transaction/', + voucher_url='voucher/', + multi_url='addtrans/' + ): + self.baseurl = url + self.batch_url = batch_url + self.transaction_url = transaction_url + self.voucher_url = voucher_url + self.multi_url = multi_url + + """ Get endpoints relative to the SETRA API URL. """ + + def __repr__(self): + return '{cls.__name__}({url!r})'.format( + cls=type(self), + url=self.baseurl) + + def batch(self, batch_id: str = None): + """ + URL for Batch endpoint + """ + 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))) + + def transaction(self, trans_id: str = None): + """ + Url for Transaction endpoint + """ + 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))) + + def voucher(self, vouch_id: str = None): + """ + Url for Voucher endpoint + """ + 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))) + + def post_multi(self): + return urllib.parse.urljoin(self.baseurl, self.multi_url) + + +class SetraClient(object): + default_headers = { + 'Accept': 'application/json', + } + + def __init__(self, + url: str, + headers: Union[None, dict] = None, + return_objects: bool = True, + use_sessions: bool = True, + ): + """ + SETRA API client. + + :param str url: Base API URL + :param dict headers: Append extra headers to all requests + :param bool return_objects: Return objects instead of raw JSON + :param bool use_sessions: Keep HTTP connections alive (default True) + """ + + self.urls = SetraEndpoints(url) + self.headers = merge_dicts(self.default_headers, headers) + self.return_objects = return_objects + if use_sessions: + self.session = requests.Session() + else: + self.session = requests + + def _build_request_headers(self, headers): + request_headers = {} + for h in self.headers: + request_headers[h] = self.headers[h] + 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): + 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) + if r.status_code in (500, 400, 401): + logger.warning('Got HTTP %d: %r', r.status_code, r.content) + if return_response: + return r + r.raise_for_status() + return r.json() + + def get(self, url, **kwargs): + return self.call('GET', url, **kwargs) + + def put(self, url, **kwargs): + return self.call('PUT', url, **kwargs) + + def post(self, url, **kwargs): + return self.call('POST', url, **kwargs) + + def object_or_data(self, cls, data) -> Union[object, dict]: + if not self.return_objects: + return data + return cls.from_dict(data) + + def get_batch(self, batch_id: int = None): + """ + GETs one or all batches from SETRA + """ + url = self.urls.batch(str(batch_id)) + response = self.get(url) + return response.json() + + def get_voucher(self, vouch_id: int): + """ + GETs one or all batches from SETRA + """ + url = self.urls.voucher(str(vouch_id)) + response = self.get(url) + return response.json() + + def get_transaction(self, trans_id: int): + """ + GETs one or all batches from SETRA + """ + url = self.urls.transaction(str(trans_id)) + response = self.get(url) + return response.json() + + def post_multi(self, multidata: Multi): + """ + POST combination of batch, vouchers and transactions + """ + url = self.urls.post_multi() + headers = {'Content-Type': 'application/json'} + response = self.post(url, + data=multidata.json(), + headers=headers) + return response + + +def get_client(config_dict): + """ + Get a SetraClient from configuration. + """ + return SetraClient(**config_dict) diff --git a/setra_client/models.py b/setra_client/models.py new file mode 100644 index 0000000000000000000000000000000000000000..569587d0080a5240b32275de5fe50080dd87d4e8 --- /dev/null +++ b/setra_client/models.py @@ -0,0 +1,79 @@ +"""Models used by the client""" +import datetime +import json +import typing +from typing import Optional, TypeVar + +import pydantic +from pydantic import validator + +NameType = TypeVar('NameType') + + +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)]) + + +class BaseModel(pydantic.BaseModel): + """Expanded BaseModel for convenience""" + + @classmethod + def from_dict(cls, data: dict): + """Initialize class from dict""" + return cls(**data) + + @classmethod + def from_json(cls, json_data: str): + """Initialize class from json file""" + data = json.loads(json_data) + return cls.from_dict(data) + + +class Batch(BaseModel): + """Model for making json formatted list of a Batch""" + client: str + batchid: str + period: str + interface: str + vouchertype: str + batchid_interface: str + + +class Voucher(BaseModel): + voucherdate: Optional[datetime.date] + exref: str + voucherno: int + + +class Transaction(BaseModel): + account: str + amount: float + transdate: datetime.date + curamount: float + currency: str + description: str + dim1: str + dim2: str + dim3: str + dim4: str + dim5: str + dim6: str + dim7: str + sequenceno: int + taxcode: str + transtype: str + arrivaldate: datetime.date + compldelay: datetime.date + discdate: datetime.date + duedate: datetime.date + extinvref: str + voucherno_interface: str + + +class Multi(BaseModel): + batch: Batch + vouchers: typing.List[Voucher] + transactions: typing.List[Transaction] + diff --git a/setra_client/version.py b/setra_client/version.py new file mode 100644 index 0000000000000000000000000000000000000000..b81165e678ffcd287f1acf5cc8d3822cb69fc50c --- /dev/null +++ b/setra_client/version.py @@ -0,0 +1,18 @@ +"""Version for distribution purposes""" +import os + +import pkg_resources + + +DISTRIBUTION_NAME = 'setra-client' + + +def get_distribution(): + """ 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__)) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..a72c1fa24efb53105f8a3213bf25b34cb36763ab --- /dev/null +++ b/setup.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import sys + +import setuptools +import setuptools.command.test + + +def get_requirements(filename): + """ 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() + 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: + return f.read().lstrip() + + +def get_packages(): + """ 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. + + From `http://doc.pytest.org/en/latest/goodpractices.html`. + + """ + + user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")] + + def initialize_options(self): + super().initialize_options() + self.pytest_args = [] + + def run_tests(self): + import shlex + import pytest + args = self.pytest_args + if args: + args = shlex.split(args) + errno = pytest.main(args) + raise SystemExit(errno) + + +def run_setup(): + 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')) + 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', + + use_scm_version=True, + packages=get_packages(), + setup_requires=setup_requirements, + install_requires=install_requirements, + tests_require=test_requirements, + cmdclass={ + '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', + ], + keywords='SETRA API client', + ) + + +if __name__ == '__main__': + run_setup() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..94ea78eb653b27697a059be636d07887c3306571 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,61 @@ +import json + +import pytest +import requests_mock + +from setra_client.client import SetraClient, SetraEndpoints, load_json_file + + +@pytest.fixture +def baseurl(): + return 'https://localhost' + + +@pytest.fixture +def endpoints(baseurl): + return SetraEndpoints(baseurl) + + +@pytest.fixture +def custom_endpoints(baseurl): + return SetraEndpoints(baseurl, + '/custom/batch/', + '/custom/transaction/', + '/custom/voucher/', + '/custom/addtrans/' + ) + + +@pytest.fixture +def client(baseurl): + return SetraClient(baseurl) + + +@pytest.fixture +def batch_fixture(): + return load_json_file('batch_fixture.json') + + +@pytest.fixture +def voucher_fixture(): + return load_json_file('voucher_fixture.json') + + +@pytest.fixture +def trans_fixture(): + return load_json_file('trans_fixture.json') + + +@pytest.fixture +def trans_fail_fixture(): + return load_json_file('trans_fail_fixture.json') + + +@pytest.fixture +def multi_fixture(): + return load_json_file('multi_fixture.json') + + +@pytest.fixture +def multi_fail_fixture(): + return load_json_file('multi_fail_fixture.json') diff --git a/tests/fixtures/batch_fixture.json b/tests/fixtures/batch_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..c5fefce8118a31b0b307e1de525ebf410cb14157 --- /dev/null +++ b/tests/fixtures/batch_fixture.json @@ -0,0 +1,8 @@ +{ + "client": 1, + "batchid": 2, + "period": 3, + "interface": 4, + "vouchertype": 5, + "batchid_interface": 6 +} diff --git a/tests/fixtures/multi_fail_fixture.json b/tests/fixtures/multi_fail_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..dca0847e589189fdafd1803ece23549659ba7e7e --- /dev/null +++ b/tests/fixtures/multi_fail_fixture.json @@ -0,0 +1,68 @@ +{ + "batch": { + "client": 1, + "batchid": 54, + "period": 1, + "interface": 1, + "vouchertype": 1, + "batchid_interface": 1 + }, + "vouchers": [ + { + "batchid": 55, + "voucherno_interface": 1, + "exref": 1, + "voucherno": 65 + } + ], + "transactions": [ + { + "voucherid": 66, + "account": 1, + "amount": 1, + "transdate": 1, + "curamount": 1, + "currency": 1, + "description": 1, + "dim1": 1, + "dim2": 1, + "dim3": 1, + "dim4": 1, + "dim5": 1, + "dim6": 1, + "dim7": 1, + "sequenceno": 1, + "taxcode": 1, + "transtype": 1, + "arrivaldate": 1, + "compldelay": 1, + "discdate": 1, + "duedate": 1, + "extinvref": 1 + }, + { + "voucherid": 66, + "account": 2, + "amount": 2, + "transdate": 2, + "curamount": 2, + "currency": 2, + "description": 2, + "dim1": 2, + "dim2": 2, + "dim3": 2, + "dim4": 2, + "dim5": 2, + "dim6": 2, + "dim7": 2, + "sequenceno": 2, + "taxcode": 2, + "transtype": 2, + "arrivaldate": 2, + "compldelay": 2, + "discdate": 2, + "duedate": 2, + "extinvref": 2 + } + ] +} diff --git a/tests/fixtures/multi_fixture.json b/tests/fixtures/multi_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..8749f4224593fd0320f2836a5f088e11bdedd34f --- /dev/null +++ b/tests/fixtures/multi_fixture.json @@ -0,0 +1,68 @@ +{ + "batch": { + "client": 1, + "batchid": 55, + "period": 1, + "interface": 1, + "vouchertype": 1, + "batchid_interface": 1 + }, + "vouchers": [ + { + "batchid": 55, + "voucherno_interface": 1, + "exref": 1, + "voucherno": 66 + } + ], + "transactions": [ + { + "voucherid": 66, + "account": 1, + "amount": 1, + "transdate": 1, + "curamount": 1, + "currency": 1, + "description": 1, + "dim1": 1, + "dim2": 1, + "dim3": 1, + "dim4": 1, + "dim5": 1, + "dim6": 1, + "dim7": 1, + "sequenceno": 1, + "taxcode": 1, + "transtype": 1, + "arrivaldate": 1, + "compldelay": 1, + "discdate": 1, + "duedate": 1, + "extinvref": 1 + }, + { + "voucherid": 66, + "account": 2, + "amount": 2, + "transdate": 2, + "curamount": 2, + "currency": 2, + "description": 2, + "dim1": 2, + "dim2": 2, + "dim3": 2, + "dim4": 2, + "dim5": 2, + "dim6": 2, + "dim7": 2, + "sequenceno": 2, + "taxcode": 2, + "transtype": 2, + "arrivaldate": 2, + "compldelay": 2, + "discdate": 2, + "duedate": 2, + "extinvref": 2 + } + ] +} diff --git a/tests/fixtures/trans_fail_fixture.json b/tests/fixtures/trans_fail_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..d867364ba00cd4e223ea4006d91397466ff50310 --- /dev/null +++ b/tests/fixtures/trans_fail_fixture.json @@ -0,0 +1,23 @@ +{ + "voucherid": 1, + "account": 1, + "amount": 1, + "transdate": 1, + "curamount": 1, + "currency": 1, + "description": 1, + "dim1": 1, + "dim2": 1, + "dim3": 1, + "dim4": 1, + "dim5": 1, + "dim6": 1, + "dim7": 1, + "sequenceno": 1, + "taxcode": 1, + "transtype": 1, + "arrivaldate": 1, + "compldelay": 1, + "discdate": 1, + "duedate": 1 +} diff --git a/tests/fixtures/trans_fixture.json b/tests/fixtures/trans_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..ddbaaf0abe48dc7ba0fc365eee66cfe42e6febe3 --- /dev/null +++ b/tests/fixtures/trans_fixture.json @@ -0,0 +1,24 @@ +{ + "voucherid": 1, + "account": 1, + "amount": 1, + "transdate": 1, + "curamount": 1, + "currency": 1, + "description": 1, + "dim1": 1, + "dim2": 1, + "dim3": 1, + "dim4": 1, + "dim5": 1, + "dim6": 1, + "dim7": 1, + "sequenceno": 1, + "taxcode": 1, + "transtype": 1, + "arrivaldate": 1, + "compldelay": 1, + "discdate": 1, + "duedate": 1, + "extinvref": 1 +} diff --git a/tests/fixtures/voucher_fixture.json b/tests/fixtures/voucher_fixture.json new file mode 100644 index 0000000000000000000000000000000000000000..c63fb58b5736ff119133a6eae39401f06b6786bb --- /dev/null +++ b/tests/fixtures/voucher_fixture.json @@ -0,0 +1,6 @@ +{ + "batchid": 1, + "voucherno_interface": 2, + "exref": 3, + "voucherno": 4 +} diff --git a/tests/test_client.py b/tests/test_client.py new file mode 100644 index 0000000000000000000000000000000000000000..cd667a913fd155a5eb9d1212d35af5a189b578ea --- /dev/null +++ b/tests/test_client.py @@ -0,0 +1,50 @@ +import pytest +from requests import HTTPError + +from setra_client.client import SetraClient + + +@pytest.fixture +def header_name(): + return 'X-Test' + + +@pytest.fixture +def client_cls(header_name): + class TestClient(SetraClient): + default_headers = { + header_name: '6a9a32f0-7322-4ef3-bbce-6685a3388e67', + } + + return TestClient + + +def test_init_does_not_mutate_arg(client_cls, baseurl): + headers = {} + client = client_cls(baseurl, headers=headers) + assert headers is not client.headers + assert not headers + + +def test_init_applies_default_headers(client_cls, baseurl, header_name): + headers = {} + client = client_cls(baseurl, headers=headers) + assert header_name in client.headers + assert client.headers[header_name] == client.default_headers[header_name] + + +def test_init_modify_defaults(client_cls, baseurl, header_name): + 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] + # Check that we don't do this by mutating default_headers + assert client.default_headers[header_name] != headers[header_name] + +# +# def test_get_update_schema(client, requests_mock): +# """Ensure getting update schema works""" +# requests_mock.get('https://localhost/_webservices/?ws=contacts/upsert/1.0', +# json={'foo': 'bar'}) +# response = client.get_update_schema() +# assert response == {'foo': 'bar'} diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py new file mode 100644 index 0000000000000000000000000000000000000000..fcf3743ba559c9d4aed94ab1232829247e5817a9 --- /dev/null +++ b/tests/test_endpoints.py @@ -0,0 +1,8 @@ +from setra_client.client import SetraEndpoints + + +def test_init(baseurl): + endpoints = SetraEndpoints(baseurl) + assert endpoints.baseurl == baseurl + + diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000000000000000000000000000000000000..fb1049bbf4c11e293f08751667c97780326b749c --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,34 @@ +import pytest +from pydantic import ValidationError + +from setra_client.models import Batch, Multi, Voucher, Transaction + + +def test_batch(batch_fixture): + # Check correct example work + assert Batch(**batch_fixture) + + +def test_voucher(voucher_fixture): + # Check correct example work + assert Voucher(**voucher_fixture) + + +def test_transaction(trans_fixture): + # Check correct example work + assert Transaction(**trans_fixture) + + +def test_transaction_fail(trans_fail_fixture): + # Check missing required field fails + with pytest.raises(ValidationError): + Transaction(**trans_fail_fixture) + + +def test_multi(multi_fixture): + assert Multi(**multi_fixture) + + +def test_multi_fail(multi_fail_fixture): + with pytest.raises(ValidationError): + Multi(**multi_fail_fixture) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000000000000000000000000000000000000..657b6b712e968077478a1f1118b27223a6524f93 --- /dev/null +++ b/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist = py36,py37,py38 + +[testenv] +description = Run tests with {basepython} +deps = + -rrequirements-test.txt + -rrequirements.txt +commands = + {envpython} -m pytest --junitxml=junit-{envname}.xml {posargs} + +[pytest] +xfail_strict = true +addopts = -rxs -v