From 5c6087fb44ac77cf55c2998649bc5a1e2b81cef1 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 29 Dec 2020 13:58:51 +0100 Subject: [PATCH] add pytests --- .github/workflows/pytest.yml | 41 ++++++++++ Makefile | 9 +++ pyproject.toml | 5 ++ run_pytest.py | 25 ++++++ tests/__init__.py | 0 tests/test_django_project.py | 145 +++++++++++++++++++++++++++++++++++ tests/test_lint.py | 13 ++++ tests/test_project_setup.py | 43 +++++++++++ tests/test_utils.py | 8 ++ 9 files changed, 289 insertions(+) create mode 100644 .github/workflows/pytest.yml create mode 100644 run_pytest.py create mode 100644 tests/__init__.py create mode 100644 tests/test_django_project.py create mode 100644 tests/test_lint.py create mode 100644 tests/test_project_setup.py create mode 100644 tests/test_utils.py diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..e190805 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,41 @@ +name: pytest + +on: + schedule: + - cron: '0 8 * * *' + push: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + max-parallel: 2 + matrix: + python-version: [3.9, 3.8, 3.7] + steps: + - uses: actions/checkout@v1 + - name: 'Set up Python ${{ matrix.python-version }}' + uses: actions/setup-python@v1 + with: + python-version: '${{ matrix.python-version }}' + + - name: 'Install package' + run: | + pip3 install poetry + make install + + - name: 'List installed packages' + run: | + poetry run pip freeze + + - name: 'Run tests with Python v${{ matrix.python-version }}' + run: | + make pytest + + - name: 'Upload coverage report' + run: bash <(curl -s https://codecov.io/bash) + + - name: 'Run linters' + if: matrix.python-version == '3.8' + run: | + make lint diff --git a/Makefile b/Makefile index 01bfae7..3692cc4 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,15 @@ fix-code-style: ## Fix code formatting poetry run black --verbose --safe --line-length=${MAX_LINE_LENGTH} --skip-string-normalization . poetry run isort . +tox-listenvs: check-poetry ## List all tox test environments + poetry run tox --listenvs + +tox: check-poetry ## Run pytest via tox with all environments + poetry run tox + +pytest: install ## Run pytest + poetry run python3 ./run_pytest.py + local-test: install ## Run local_test.py to run the project locally poetry run python3 ./local_test.py diff --git a/pyproject.toml b/pyproject.toml index 0fadb29..cc73f64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,11 @@ django_ynh = "*" [tool.poetry.dev-dependencies] bx_py_utils = "*" +tox = "*" +pytest = "*" +pytest-cov = "*" +pytest-django = "*" +coveralls = "*" isort = "*" flake8 = "*" flynt = "*" diff --git a/run_pytest.py b/run_pytest.py new file mode 100644 index 0000000..a398526 --- /dev/null +++ b/run_pytest.py @@ -0,0 +1,25 @@ +""" + Run pytest against local test creation +""" + +from pathlib import Path + + +try: + from django_ynh.pytest_helper import run_pytest +except ImportError as err: + raise ImportError('Did you forget to activate a virtual environment?') from err + + +BASE_PATH = Path(__file__).parent + + +def main(): + run_pytest( + django_settings_path=BASE_PATH / 'conf' / 'settings.py', + destination=BASE_PATH / 'local_test', + ) + + +if __name__ == '__main__': + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_django_project.py b/tests/test_django_project.py new file mode 100644 index 0000000..73fa685 --- /dev/null +++ b/tests/test_django_project.py @@ -0,0 +1,145 @@ +from axes.models import AccessLog +from bx_py_utils.test_utils.html_assertion import HtmlAssertionMixin +from django.conf import settings +from django.contrib.auth.models import User +from django.test import override_settings +from django.test.testcases import TestCase +from django.urls import NoReverseMatch +from django.urls.base import reverse +from django_ynh.test_utils import generate_basic_auth +from django_ynh.views import request_media_debug_view + +import inventory + + +@override_settings(DEBUG=False) +class DjangoYnhTestCase(HtmlAssertionMixin, TestCase): + def setUp(self): + super().setUp() + + # Always start a fresh session: + self.client = self.client_class() + + def test_settings(self): + assert settings.PATH_URL == 'app_path' + + assert str(settings.FINAL_HOME_PATH).endswith('/local_test/opt_yunohost') + assert str(settings.FINAL_WWW_PATH).endswith('/local_test/var_www') + assert str(settings.LOG_FILE).endswith('/local_test/var_log_django_ynh.log') + + assert settings.ROOT_URLCONF == 'urls' + + def test_urls(self): + assert reverse('admin:index') == '/app_path/' + + # The django_ynh debug view should not be avaiable: + with self.assertRaises(NoReverseMatch): + reverse(request_media_debug_view) + + # Serve user uploads via django_tools.serve_media_app: + assert settings.MEDIA_URL == '/app_path/media/' + assert reverse('serve_media_app:serve-media', kwargs={'user_token': 'token', 'path': 'foo/bar/'}) == ( + '/app_path/media/token/foo/bar/' + ) + + def test_auth(self): + response = self.client.get('/app_path/') + self.assertRedirects(response, expected_url='/app_path/login/?next=/app_path/') + + def test_create_unknown_user(self): + assert User.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'test' + + response = self.client.get( + path='/app_path/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='test', + HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz', + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler + assert user.is_superuser is False + + self.assert_html_parts( + response, + parts=( + f'Site administration | PyInventory v{inventory.__version__}', + 'test', + ), + ) + + def test_wrong_auth_user(self): + assert User.objects.count() == 0 + assert AccessLog.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'test' + + response = self.client.get( + path='/app_path/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='foobar', # <<< wrong user name + HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz', + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler + assert user.is_superuser is False + + assert AccessLog.objects.count() == 1 + + assert response.status_code == 403 # Forbidden + + def test_wrong_cookie(self): + assert User.objects.count() == 0 + assert AccessLog.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'foobar' # <<< wrong user name + + response = self.client.get( + path='/app_path/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='test', + HTTP_AUTHORIZATION='basic dGVzdDp0ZXN0MTIz', + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler + assert user.is_superuser is False + + assert AccessLog.objects.count() == 1 + + assert response.status_code == 403 # Forbidden + + def test_wrong_authorization_user(self): + assert User.objects.count() == 0 + + self.client.cookies['SSOwAuthUser'] = 'test' + + response = self.client.get( + path='/app_path/', + HTTP_REMOTE_USER='test', + HTTP_AUTH_USER='test', + HTTP_AUTHORIZATION=generate_basic_auth(username='foobar', password='test123'), # <<< wrong user name + ) + + assert User.objects.count() == 1 + user = User.objects.first() + assert user.username == 'test' + assert user.is_active is True + assert user.is_staff is True # Set by: conf.django_ynh_demo_urls.setup_user_handler + assert user.is_superuser is False + + assert AccessLog.objects.count() == 1 + + assert response.status_code == 403 # Forbidden diff --git a/tests/test_lint.py b/tests/test_lint.py new file mode 100644 index 0000000..003dc76 --- /dev/null +++ b/tests/test_lint.py @@ -0,0 +1,13 @@ +import shutil +import subprocess +from pathlib import Path + + +BASE_PATH = Path(__file__).parent.parent + + +def test_lint(): + assert Path(BASE_PATH, 'Makefile').is_file() + make_bin = shutil.which('make') + assert make_bin is not None + subprocess.check_call([make_bin, 'lint'], cwd=BASE_PATH) diff --git a/tests/test_project_setup.py b/tests/test_project_setup.py new file mode 100644 index 0000000..f5a6651 --- /dev/null +++ b/tests/test_project_setup.py @@ -0,0 +1,43 @@ +import os +import shutil +import subprocess +from pathlib import Path + +import inventory + + +PACKAGE_ROOT = Path(__file__).parent.parent + + +def assert_file_contains_string(file_path, string): + with file_path.open('r') as f: + for line in f: + if string in line: + return + raise AssertionError(f'File {file_path} does not contain {string!r} !') + + +def test_version(): + version = inventory.__version__ + + if 'dev' not in version and 'rc' not in version: + version_string = f'v{version}' + + assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'README.md'), string=version_string) + + assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'pyproject.toml'), string=f'version = "{version}~ynh') + assert_file_contains_string(file_path=Path(PACKAGE_ROOT, 'manifest.json'), string=f'"version": "{version}~ynh') + + +def test_poetry_check(): + poerty_bin = shutil.which('poetry') + + output = subprocess.check_output( + [poerty_bin, 'check'], + universal_newlines=True, + env=os.environ, + stderr=subprocess.STDOUT, + cwd=str(PACKAGE_ROOT), + ) + print(output) + assert output == 'All set!\n' diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..a9927a7 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,8 @@ +from unittest.case import TestCase + +from django_ynh.test_utils import generate_basic_auth + + +class TestUtilsTestCase(TestCase): + def test_generate_basic_auth(self): + assert generate_basic_auth(username='test', password='test123') == 'basic dGVzdDp0ZXN0MTIz'