diff --git a/.github/workflows/badgerdoc-cli.yml b/.github/workflows/badgerdoc-cli.yml new file mode 100644 index 000000000..9bedea31b --- /dev/null +++ b/.github/workflows/badgerdoc-cli.yml @@ -0,0 +1,32 @@ +name: badgerdoc_cli linters and tests +on: + push: + paths: + - lib/badgerdoc_cli/** + - .github/worlflows/badgerdoc-cli.yml + pull_request: + paths: + - lib/badgerdoc_cli/** + - .github/worlflows/badgerdoc-cli.yml +jobs: + badgerdoc-cli-test: + strategy: + matrix: + python-version: [ "3.8.15" ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ".[dev]" + - name: Run linters and checkers [isort -> black -> mypy -> pylint] + run: | + pre-commit run --all-files + - name: Run tests + run: | + pytest tests/ diff --git a/lib/badgerdoc_cli/.pre-commit-config.yaml b/lib/badgerdoc_cli/.pre-commit-config.yaml index 65f0cbead..65b5d6016 100644 --- a/lib/badgerdoc_cli/.pre-commit-config.yaml +++ b/lib/badgerdoc_cli/.pre-commit-config.yaml @@ -30,6 +30,9 @@ repos: require_serial: true args: - --strict + - --ignore-missing-imports + - --allow-untyped-decorators + - --python-version=3.8 - repo: https://github.com/pycqa/pylint rev: v2.17.1 hooks: @@ -38,3 +41,4 @@ repos: args: - --max-line-length=79 - --errors-only + - --disable=E0401 diff --git a/lib/badgerdoc_cli/badgerdoc_cli/__init__.py b/lib/badgerdoc_cli/badgerdoc_cli/__init__.py index b511f8ad2..74eb54607 100644 --- a/lib/badgerdoc_cli/badgerdoc_cli/__init__.py +++ b/lib/badgerdoc_cli/badgerdoc_cli/__init__.py @@ -1,6 +1,6 @@ from .main import cli_handler, init_cli_app __all__ = [ - cli_handler, - init_cli_app, + "cli_handler", + "init_cli_app", ] diff --git a/lib/badgerdoc_cli/badgerdoc_cli/main.py b/lib/badgerdoc_cli/badgerdoc_cli/main.py index 9d7a7d281..c877f9671 100644 --- a/lib/badgerdoc_cli/badgerdoc_cli/main.py +++ b/lib/badgerdoc_cli/badgerdoc_cli/main.py @@ -45,13 +45,13 @@ def inject_openapi_commands( app: Optional[FastAPI], subparsers: SupportsAddParser ) -> None: def _generate_openapi(arguments: Dict[str, Any]) -> None: - path: str = arguments["path"] - if not path.endswith(".json"): - raise ValueError("Invalid file format. Must be .json") if app is None: raise ValueError( "CLI is not initialized. Add init_cli_app call to set up CLI" ) + path: str = arguments["path"] + if not path.endswith(".json"): + raise ValueError("Invalid file format. Must be .json") generate_openapi(app=app, file_path=path, indent=arguments["indent"]) openapi_parser = subparsers.add_parser( @@ -59,7 +59,7 @@ def _generate_openapi(arguments: Dict[str, Any]) -> None: ) openapi_parser.add_argument("path", help="path to save spec to") openapi_parser.add_argument( - "--indent", help="indents in json open api spec", default=2 + "--indent", help="indents in json open api spec", type=int, default=2 ) openapi_parser.set_defaults(func_with_args=_generate_openapi) @@ -72,7 +72,7 @@ def init_cli_handlers(app: Optional[FastAPI], arguments: Any) -> None: help="show full traceback and other debug info", action="store_true", ) - subparsers = parser.add_subparsers() + subparsers: Any = parser.add_subparsers() inject_openapi_commands(app, subparsers) params = vars(parser.parse_args(arguments)) try: @@ -103,5 +103,5 @@ def init_cli_app(app: FastAPI) -> None: APP = app -def cli_handler(): +def cli_handler() -> None: init_cli_handlers(APP, sys.argv[1:]) diff --git a/lib/badgerdoc_cli/pyproject.toml b/lib/badgerdoc_cli/pyproject.toml index 5e62f8cf2..2981960a8 100644 --- a/lib/badgerdoc_cli/pyproject.toml +++ b/lib/badgerdoc_cli/pyproject.toml @@ -15,7 +15,6 @@ requires = ["setuptools>=67.0.0"] build-backend = "setuptools.build_meta" # Dev tools settings - [tool.black] line-length = 79 @@ -26,7 +25,10 @@ line_length = 79 [tool.mypy] python_version = 3.8 strict = "True" +ignore_missing_imports = "True" +allow_untyped_decorators = "True" [tool.pylint.basic] max-line-length=79 errors-only = "True" +disable = ["E0401"] diff --git a/lib/badgerdoc_cli/tests/__init__.py b/lib/badgerdoc_cli/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/badgerdoc_cli/tests/test_main.py b/lib/badgerdoc_cli/tests/test_main.py new file mode 100644 index 000000000..9e040c839 --- /dev/null +++ b/lib/badgerdoc_cli/tests/test_main.py @@ -0,0 +1,68 @@ +from typing import Optional +from unittest.mock import patch + +import pytest +from _pytest.capture import CaptureFixture +from fastapi import FastAPI + +from badgerdoc_cli import main + + +def test_init_cli_app() -> None: + app = FastAPI() + main.init_cli_app(app) + assert main.APP is app + + +def test_init_cli_app_invalid_app() -> None: + fake_app = type("FastAPI", (), {}) + with pytest.raises(TypeError, match="Invalid app type. Must be FastAPI"): + main.init_cli_app(fake_app) + + +def test_verbose_param() -> None: + with patch( + "badgerdoc_cli.main.generate_openapi", + side_effect=ValueError("Foo!"), + ), pytest.raises(ValueError, match="Foo"): + app = FastAPI() + cli_args = ("--verbose", "openapi", "foo.json") + main.init_cli_handlers(app, cli_args) + + +@pytest.mark.parametrize(("indent", "expected_indent"), ((None, 2), ("4", 4))) +def test_openapi_command(indent: Optional[str], expected_indent: int) -> None: + app = FastAPI() + cli_args = ["openapi", "foo.json"] + if indent is not None: + cli_args.append(f"--indent={indent}") + + with patch("badgerdoc_cli.main.generate_openapi") as gendoc_mock: + main.init_cli_handlers(app, cli_args) + gendoc_mock.assert_called_once_with( + app=app, + file_path="foo.json", + indent=expected_indent, + ) + + +def test_openapi_command_non_initialized_cli( + capsys: CaptureFixture[str], +) -> None: + app = None + cli_args = ("openapi", "foo.json") + main.init_cli_handlers(app, cli_args) + captured_stdout = capsys.readouterr().out + expected = "CLI is not initialized. Add init_cli_app call to set up CLI\n" + assert captured_stdout == expected + + +def test_openapi_command_invalid_file_format( + capsys: CaptureFixture[str], +) -> None: + app = FastAPI() + cli_args = ("openapi", "foo.pdf") + main.init_cli_handlers(app, cli_args) + captured_stdout = capsys.readouterr().out + expected = "Invalid file format. Must be .json\n" + assert captured_stdout == expected