diff --git a/.gitignore b/.gitignore index c437c99..a764550 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,7 @@ *.pyc *~ .coverage -coverage.xml -coverage +reports/ build/ dist/ venv/ diff --git a/README.md b/README.md index c991d24..c3de3cf 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,20 @@ Then also install gunicorn and run: pip install gunicorn gunicorn sambal:app -w 4 +Running Tests +------------- + +To run the tests first make sure the optional dependency group called `test` +is installed: + + pip install -e .[test] + +Then just run pytest: + + pytest + +Coverage reports end up in the `reports` folder. + Why Pyramid ----------- diff --git a/pyproject.toml b/pyproject.toml index 9722752..af858fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,13 @@ dependencies = [ "WTForms==3.1.2", ] +[project.optional-dependencies] +test = [ + "pytest==8.0.2", + "pytest-cov==4.1.0", + "WebTest==3.0.0", +] + [project.license] text = "GPL-3.0-only" @@ -53,3 +60,7 @@ license-files = [ [tool.distutils.bdist_wheel] universal = true + +[tool.pytest.ini_options] +python_files = ["tests.py", "test_*.py", "*_tests.py"] +addopts = "--cov=sambal --cov-branch --cov-report=term --cov-report=html:reports/htmlcov --cov-report=xml:reports/coverage.xml --junitxml=reports/junit.xml" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..d661efd --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,34 @@ +import pytest +import webtest +from pyramid.scripting import prepare + + +@pytest.fixture(scope="session") +def app(): + """Fixtures that returns the Sambal WSGI app. + + We can inject env vars here if necessary, but only before the import. + """ + import sambal + + return sambal.app + + +@pytest.fixture +def testapp(app): + """Fixture that returns a Sambal WebTest app.""" + return webtest.TestApp( + app, + extra_environ={ + "HTTP_HOST": "example.com", + }, + ) + + +@pytest.fixture +def app_request(app): + """Returns a real HTTP request for use with testing Sambal views.""" + with prepare(registry=app.registry) as env: + req = env["request"] + req.host = "example.com" + yield req diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..d77357a --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,8 @@ +def test_root(testapp): + response = testapp.get("/", status=200) + assert b"Sambal Login" in response.body + + +def test_notfound(testapp): + response = testapp.get("/does-not-exist/", status=404) + assert response.status_code == 404 diff --git a/tests/test_views.py b/tests/test_views.py new file mode 100644 index 0000000..11f57ca --- /dev/null +++ b/tests/test_views.py @@ -0,0 +1,15 @@ +from unittest.mock import Mock + +from sambal.views.auth import login + + +def test_login_view(app_request): + """A sample test that calls the login view directly. + + The 'real request' lacks a matched_route property required by the + login view, which can be done in the test rather than fixture. + """ + app_request.matched_route = Mock() + context = login(app_request) + assert "form" in context + assert "return_url" in context