diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 9bd2c0a..8ea7a2c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -54,7 +54,7 @@ jobs: args: check --target-version ${{ env.PYTHON_TARGET }} - name: Tests and coverage - run: hatch run cov + run: hatch test -p -c - name: Codecov - Upload coverage uses: codecov/codecov-action@v4 diff --git a/README.md b/README.md index f4dd93f..421da7e 100644 --- a/README.md +++ b/README.md @@ -452,14 +452,70 @@ For this flow the `user` field will be ignored. ## Testing -The tests uses twisted's testing framework trial, with the development -enviroment managed by hatch. Running the tests and generating a coverage report +To create virtual development env and install dependencies: +```console +hatch shell +``` + +The tests use pytest, with the development environment managed by hatch. Running the tests can be done like this: ```console -hatch run cov +hatch test ``` +#### Additional optional testing arguments: +Run the tests in parallel: `-p` + +Collect coverage data(automatically output as `lcov.info`): `-c` + +#### Running a specific test: +Selecting a specific test to run can be as easy as providing the path to the test. All tests start from +the base test directory, `tests`. If running all tests, this can be left out. For specific tests, see +the [pytest usage docs](https://docs.pytest.org/en/stable/how-to/usage.html#specifying-which-tests-to-run) for more information + +## Code Quality + +Use `hatch fmt` to automatically format code, enforce style rules, and check types using: + +- `black` and `isort` for formatting +- `ruff` for linting +- `mypy` for static type checking + +### Check Code Without Modifying It + +To check code quality without modifying files: + +- Check formatting with `isort` and `black`: + ```console + hatch fmt --check -f + ``` +- Check types and linting with `mypy` and `ruff`: + ```console + hatch fmt --check -l + ``` +- Check all of above, formatting, linting, and typing: + ```console + hatch fmt --check + ``` + +### Auto-formatting Code + +To automatically fix issues in the code: + +- Format only using `black` and `isort`: + ```console + hatch fmt -f + ``` +- Type checks(`mypy`) and lint, fixing autofixable `ruff` issues: + ```console + hatch fmt -l + ``` +- Run all tools, format, lint, type-check: + ```console + hatch fmt + ``` + ## Releasing After tagging a new version, manually create a Github release based on the tag. This will publish the package on PyPI. diff --git a/pyproject.toml b/pyproject.toml index 2acc9ba..0d59de1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "jwcrypto", + "jwcrypto<=1.5.6", "twisted", ] version = "0.13.0" @@ -41,14 +41,14 @@ dependencies = [ "mock", "matrix-synapse", "ruff", + "mypy", + "mypy-zope", + "types-PyYAML", + "types-bleach", + "types-cachetools", + "types-requests", + "types-pyOpenSSL" ] -[tool.hatch.envs.default.scripts] -cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=synapse_token_authenticator --cov=tests {args}" -format = "black ." -lint = "ruff check ." - -[tool.hatch.envs.ci.scripts] -cov = "pytest --cov-report=lcov:lcov.info --cov-config=pyproject.toml --cov=synapse_token_authenticator --cov=tests" [tool.coverage.run] branch = true @@ -57,3 +57,108 @@ omit = [] [tool.coverage.report] exclude_lines = ["no cov", "if __name__ == .__main__.:", "if TYPE_CHECKING:"] + +[tool.hatch.envs.hatch-static-analysis] +dependencies = [ + "black", + "isort", + "ruff", + "mypy", + "mypy-zope", + "types-PyYAML", + "types-bleach", + "types-cachetools", + "types-jwcrypto", + "types-requests", + "types-pyOpenSSL" +] + +[tool.hatch.envs.hatch-static-analysis.scripts] +format-check = [ + "isort . --check --diff", + "black . --check --diff" +] +format-fix = [ + "isort .", + "black ." +] +lint-check = [ + "mypy", + "ruff check" +] +lint-fix = [ + "mypy", + "ruff check --fix" +] +[tool.ruff] +target-version = "py310" +line-length = 88 + +[tool.mypy] +python_version = "3.10" +ignore_missing_imports = true +install_types = true +plugins = [ + "mypy_zope:plugin" +] +files = ["synapse_token_authenticator", "tests"] + +[tool.isort] +profile = "black" +src_paths = ["synapse_token_authenticator", "tests"] + +[tool.hatch.envs.hatch-test] +# These below are installed by default. This list is to help not reinstall something. +# Updating hatch will change the version numbers, so ignore the pinned versions. +#dependencies = [ +# "coverage-enable-subprocess==1.0", +# "coverage[toml]~=7.11", +# "pytest~=9.0", +# "pytest-mock~=3.12", +# "pytest-randomly~=3.15", +# "pytest-rerunfailures~=14.0", +# "pytest-xdist[psutil]~=3.5", +#] + +extra-dependencies = [ + "pytest-cov", + "matrix-synapse", +] +extra-args = [ + # use the loadscope dist method when running tests parallel. This keeps grouped tests + # together instead of splitting them up, which tends to get "stuck" and not finish + # the testing(have to use ^C to exit). Has no effect when tests are not parallelized. + "--dist=loadscope" +] + +# The 'hatch test ...' command has the built-in scripts that define its invocation. +# Override these scripts so coverage works as expected. +# +# In particular, the 'coverage run -m pytest ...' command does not work correctly. +# In CI, it was not producing the subprocess-enabled coverage files, even though it +# worked locally (there should have been 4 files, but only 1 was produced, and it had +# no data). Using the pytest-cov plugin does seem to produce these files, and the +# missing-lines display format is also correct. Since it handles the internal +# combine/report commands, set those scripts below to do nothing (or CI will report +# fake errors). +[tool.hatch.envs.hatch-test.scripts] +# The HATCH_TEST_ARGS environment variable is how the `test` command's flags are +# translated and internally populated without affecting the user's arguments. This is +# also the way that "extra arguments" are passed. Leave it there +run = "pytest{env:HATCH_TEST_ARGS:} {args}" +# The original 'run-cov' is below; this is overridden to produce coverage properly. +#run-cov = "coverage run -m pytest{env:HATCH_TEST_ARGS:} {args}" +# Notes for this command line: using --cov=synapse_token_authenticator instead of having +# that directory in the 'coverage.run' config above actually produces the correct +# coverage files (the ones that were missing, as mentioned above). The 'cov-report' +# options both produce useful output (one for the codecov file and the other displays +# the missing coverage lines). +run-cov = "pytest{env:HATCH_TEST_ARGS:} --cov=synapse_token_authenticator --cov-report=lcov:lcov.info --cov-report=term-missing --cov-config=pyproject.toml {args}" + +# As mentioned above, the defaults for these scripts are commented out as they will +# produce fake errors in CI. They attempt to run but the result of those commands +# already exists. This raises error codes and fails the test. +#cov-combine = "coverage combine" +#cov-report = "coverage report" +cov-combine = "" +cov-report = "" diff --git a/synapse_token_authenticator/__init__.py b/synapse_token_authenticator/__init__.py index 4196c75..0849b00 100644 --- a/synapse_token_authenticator/__init__.py +++ b/synapse_token_authenticator/__init__.py @@ -1,3 +1,3 @@ -from synapse_token_authenticator.token_authenticator import ( - TokenAuthenticator, # noqa: F401 +from synapse_token_authenticator.token_authenticator import ( # noqa: F401 + TokenAuthenticator, ) diff --git a/synapse_token_authenticator/claims_validator.py b/synapse_token_authenticator/claims_validator.py index 6486270..5553e11 100644 --- a/synapse_token_authenticator/claims_validator.py +++ b/synapse_token_authenticator/claims_validator.py @@ -16,10 +16,11 @@ more complicated, we better switch to another engine/DSL """ +import re from dataclasses import dataclass -from typing import List, Optional, Any, TypeAlias, Union +from typing import Any, List, Optional, TypeAlias, Union + from synapse_token_authenticator.utils import get_path_in_dict -import re Validator: TypeAlias = Union[ "Exist", diff --git a/synapse_token_authenticator/config.py b/synapse_token_authenticator/config.py index dce1f7d..1cadbc9 100644 --- a/synapse_token_authenticator/config.py +++ b/synapse_token_authenticator/config.py @@ -1,13 +1,15 @@ import os from dataclasses import dataclass, field -from typing import List, Literal, Union, TypeAlias, Any +from typing import Any, List, Literal, TypeAlias, Union + from jwcrypto.jwk import JWK, JWKSet + from synapse_token_authenticator.claims_validator import ( - parse_validator, - Validator, Exist, + Validator, + parse_validator, ) -from synapse_token_authenticator.utils import bearer_auth, basic_auth +from synapse_token_authenticator.utils import basic_auth, bearer_auth class TokenAuthenticatorConfig: diff --git a/synapse_token_authenticator/utils.py b/synapse_token_authenticator/utils.py index ebaf7e2..30a3171 100644 --- a/synapse_token_authenticator/utils.py +++ b/synapse_token_authenticator/utils.py @@ -1,7 +1,8 @@ +import json from base64 import b64encode +from typing import Any, List, Optional from urllib.parse import urljoin -from typing import List, Optional, Any -import json + from twisted.web import resource diff --git a/tests/test_epa.py b/tests/test_epa.py index 3747288..9b78531 100644 --- a/tests/test_epa.py +++ b/tests/test_epa.py @@ -13,13 +13,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from copy import deepcopy from unittest import mock +from jwcrypto import jwk + import tests.unittest as synapsetest from . import ModuleApiTestCase, get_enc_jwk, get_jwe_token, get_jwk, get_jwt_token -from copy import deepcopy -from jwcrypto import jwk def get_default_claims() -> dict: diff --git a/tests/test_oauth.py b/tests/test_oauth.py index cf90373..6c7eedc 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -13,13 +13,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from copy import deepcopy from unittest import mock +from jwcrypto.jwk import JWKSet + import tests.unittest as synapsetest -from . import ModuleApiTestCase, get_jwt_token, get_jwk, mock_for_oauth -from copy import deepcopy -from jwcrypto.jwk import JWKSet +from . import ModuleApiTestCase, get_jwk, get_jwt_token, mock_for_oauth default_claims = { "urn:messaging:matrix:localpart": "alice", diff --git a/tests/test_sta_utils.py b/tests/test_sta_utils.py index 1813f05..e518995 100644 --- a/tests/test_sta_utils.py +++ b/tests/test_sta_utils.py @@ -1,8 +1,8 @@ from synapse_token_authenticator.utils import ( + all_list_elems_are_equal_return_the_elem, get_path_in_dict, - validate_scopes, if_not_none, - all_list_elems_are_equal_return_the_elem, + validate_scopes, ) diff --git a/tests/test_utils/__init__.py b/tests/test_utils/__init__.py index 6e2648d..96c3db2 100644 --- a/tests/test_utils/__init__.py +++ b/tests/test_utils/__init__.py @@ -15,6 +15,7 @@ """ Utilities for running the unit tests """ + import json import sys import warnings diff --git a/tests/test_validators.py b/tests/test_validators.py index d1e2cdd..fc3533d 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,6 +1,7 @@ -from synapse_token_authenticator.claims_validator import parse_validator from pytest import fixture +from synapse_token_authenticator.claims_validator import parse_validator + def test_validator_exists(): assert parse_validator(["exist"]).validate(None)