diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 00000000..5555107e --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,32 @@ +name: testing + +on: + pull_request: + push: + branches: [master] + +jobs: + testing: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install pytest-cov, codecov + run: | + pip install pytest-cov codecov + + - name: Test with pytest + run: | + python -m pytest --cov=./ + + - name: Upload to CodeCov + uses: codecov/codecov-action@v1 diff --git a/environment.yml b/environment.yml index f81a135f..67687d42 100644 --- a/environment.yml +++ b/environment.yml @@ -18,6 +18,9 @@ dependencies: - email-validator - phonenumbers # phonenumberslite? + # Testing + - pytest + # Development (Tools, Style, Linting) - ipython - black diff --git a/requirements.txt b/requirements.txt index 93809011..803e6f89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,9 @@ pydantic email-validator phonenumbers +# testing +pytest + # dev ipython black diff --git a/tests/test_compass_core_errors.py b/tests/test_compass_core_errors.py new file mode 100644 index 00000000..5961a4fa --- /dev/null +++ b/tests/test_compass_core_errors.py @@ -0,0 +1,83 @@ +import pytest + +from compass.core import errors + + +class TestErrors: + def test_compass_error(self): + # Given + data = "message" + + # Then + with pytest.raises(errors.CompassError, match=data): + # When + raise errors.CompassError(data) + + def test_compass_authentication_error(self): + # Given + data = "message" + + # Then + with pytest.raises(errors.CompassAuthenticationError, match=data): + # When + raise errors.CompassAuthenticationError(data) + + def test_compass_report_error(self): + # Given + data = "message" + + # Then + with pytest.raises(errors.CompassReportError, match=data): + # When + raise errors.CompassReportError(data) + + def test_compass_report_permission_error(self): + # Given + data = "message" + + # Then + with pytest.raises(errors.CompassReportPermissionError, match=data): + # When + raise errors.CompassReportPermissionError(data) + + def test_compass_authentication_error_inheritance(self): + # Given + data = "message" + + # When + try: + raise errors.CompassAuthenticationError(data) + # Then + except errors.CompassError as err: + assert str(err) == data + + def test_compass_report_error_inheritance(self): + # Given + data = "message" + + # When + try: + raise errors.CompassReportError(data) + # Then + except errors.CompassError as err: + assert str(err) == data + + def test_compass_report_permission_error_inheritance(self): + # Given + data = "message" + + # Dual inheritance: + + # When + try: + raise errors.CompassReportPermissionError(data) + # Then + except PermissionError as err: + assert str(err) == data + + # When + try: + raise errors.CompassReportPermissionError(data) + # Then + except errors.CompassError as err: + assert str(err) == data diff --git a/tests/test_compass_core_interface_base.py b/tests/test_compass_core_interface_base.py new file mode 100644 index 00000000..274f078f --- /dev/null +++ b/tests/test_compass_core_interface_base.py @@ -0,0 +1,62 @@ +from pytest import MonkeyPatch +import requests + +from compass.core.interface_base import InterfaceBase +from compass.core.settings import Settings + + +class TestInterfaceBase: + def test_get(self, monkeypatch: MonkeyPatch): + # Given + data = "https://example.org" + reqs = Settings.total_requests + + # When + s = requests.Session() + monkeypatch.setattr(s, "get", lambda *args, **kwargs: dict(args=args, kwargs=kwargs)) + result = InterfaceBase(s)._get(data) # pylint: disable=protected-access + + # Then + assert result["args"][0] == data + assert reqs + 1 == Settings.total_requests + + def test_post(self, monkeypatch: MonkeyPatch): + # Given + data = "https://example.org" + json = {"abc": 123, "xyz": 321} + reqs = Settings.total_requests + + # When + s = requests.Session() + monkeypatch.setattr(s, "post", lambda *args, **kwargs: dict(args=args, kwargs=kwargs)) + result = InterfaceBase(s)._post(data, json=json) # pylint: disable=protected-access + + # Then + assert result["args"][0] == data + assert result["kwargs"]["json"] == json + assert reqs + 1 == Settings.total_requests + + def test_head(self, monkeypatch: MonkeyPatch): + # Given + data = "https://example.org" + reqs = Settings.total_requests + + # When + s = requests.Session() + monkeypatch.setattr(s, "head", lambda *args, **kwargs: dict(args=args, kwargs=kwargs)) + result = InterfaceBase(s)._head(data) # pylint: disable=protected-access + + # Then + assert result["args"][0] == data + assert reqs + 1 == Settings.total_requests + + def test_update_headers(self): + # Given + data = {"header_one": 123, "abc_xyz": 321} + + # When + ib = InterfaceBase(requests.Session()) + ib._update_headers(data) # pylint: disable=protected-access + + # Then + assert data.items() <= ib.s.headers.items() diff --git a/tests/test_compass_core_settings.py b/tests/test_compass_core_settings.py new file mode 100644 index 00000000..6fd60cbf --- /dev/null +++ b/tests/test_compass_core_settings.py @@ -0,0 +1,56 @@ +from urllib.parse import urlparse + +import _strptime + +from compass.core.settings import Settings + + +class TestSettings: + def test_base_url(self): + # Given + data = Settings.base_url + + # When + result = urlparse(data) + + # Then + assert isinstance(data, str) + assert bool(result.scheme) and bool(result.hostname) + + def test_date_format(self): + # Given + data = Settings.date_format + + # When + result = _strptime._TimeRE_cache.pattern(data) # pylint: disable=protected-access + + # Then + assert isinstance(data, str) + assert bool(result) + + def test_org_number(self): + # Given When + data = Settings.org_number + + # Then + assert isinstance(data, int) + assert 0 < data < 100_000_000 # IDs are between 1 and 99,999,999 + + def test_wcf_json_endpoint(self): + # Given When + data = Settings.wcf_json_endpoint + + assert isinstance(data, str) + assert data[0] == "/" + assert ".svc" in data + + def test_web_service_path(self): + # Given + data = Settings.web_service_path + + # When + result = urlparse(data) + + # Then + assert isinstance(data, str) + assert bool(result.scheme) and bool(result.hostname) and bool(result.path) diff --git a/tests/test_compass_core_utility.py b/tests/test_compass_core_utility.py new file mode 100644 index 00000000..bd67ba11 --- /dev/null +++ b/tests/test_compass_core_utility.py @@ -0,0 +1,165 @@ +import datetime + +import pytest + +from compass.core import utility + + +class TestUtility: + def test_hash_code(self): + # Given + data = "testing" + + # When + result = utility.hash_code(data) + + # Then + assert isinstance(result, int) + # TODO Aim for property based aspects instead of fixed values! + assert result == -1422446064 # TODO max/min = +/- 2**32? + + def test_compass_restify(self): + # Given + data = {"a": 1, "b": 2, "c": 3} + num_pairs = len(data) + + # When + result = utility.compass_restify(data) + + # Then + assert isinstance(result, list) + assert len(result) == num_pairs + assert all(item.keys() == {"Key", "Value"} for item in result) + # TODO Aim for property based aspects instead of fixed values! + assert result == [{"Key": "a", "Value": "1"}, {"Key": "b", "Value": "2"}, {"Key": "c", "Value": "3"}] + + def test_cast_ast_eval_false_int(self): + # Given + data = 123 + + # When + result = utility.cast(data, ast_eval=False) + result_str = utility.cast(str(data), ast_eval=False) + + # Then + assert result == data + assert result_str == data + + def test_cast_ast_eval_false_str(self): + # Given + data = "abc" + + # When + result = utility.cast(data, ast_eval=False) + + # Then + assert result == data + + def test_cast_ast_eval_false_list(self): + # Given + data = [1, 2, 3] + + # When + result = utility.cast(data, ast_eval=False) + result_str = utility.cast(str(data), ast_eval=False) + + # Then + assert result == str(data) + assert result_str == str(data) + + def test_cast_ast_eval_true_int(self): + # Given + data = 123 + + # When + result = utility.cast(data, ast_eval=True) + result_str = utility.cast(str(data), ast_eval=True) + + # Then + assert result == data + assert result_str == data + + def test_cast_ast_eval_true_str(self): + # Given + data = "abc" + + # When + result = utility.cast(data, ast_eval=True) + + # Then + assert result == data + + def test_cast_ast_eval_true_list(self): + # Given + data = [1, 2, 3] + + # When + result = utility.cast(data, ast_eval=True) + result_str = utility.cast(str(data), ast_eval=True) + + # Then + assert result == data + assert result_str == data + + def test_maybe_int_int(self): + # Given + data = 123 + + # When + result = utility.maybe_int(data) + result_str = utility.cast(str(data)) + + # Then + assert result == data + assert result_str == data + + def test_maybe_int_str(self): + # Given + data = "abc" + + # When + result = utility.maybe_int(data) + + # Then + assert result is None + + def test_parse_month_short(self): + # Given + data = "01 Jan 2000" + + # When + result = utility.parse(data) + + # Then + assert isinstance(result, datetime.datetime) + assert result == datetime.datetime(2000, 1, 1) + + def test_parse_month_long(self): + # Given + data = "01 January 2000" + + # When + result = utility.parse(data) + + # Then + assert isinstance(result, datetime.datetime) + assert result == datetime.datetime(2000, 1, 1) + + def test_parse_non_date(self): + # Given + data = "abc" + + # Then + with pytest.raises(ValueError): + # When + utility.parse(data) + + def test_parse_empty(self): + # Given + data = "" + + # When + result = utility.parse(data) + + # Then + assert result is None