diff --git a/pyproject.toml b/pyproject.toml index 0d2c204..54e599c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "htmlmin", "jsmin", "Pygments", + "requests", ] description = "Write Markdown and Jinja2 templates to create a website" maintainers = [ @@ -43,7 +44,7 @@ Repository = "https://github.com/siecje/htmd.git" [tool.ruff.lint] ignore = [ - "D100", "D101", "D103", "D104", "D203", "D211", "D212", "D213", + "D100", "D101", "D103", "D104", "D105", "D107", "D203", "D211", "D212", "D213", "INP001", "RET504", "S101", @@ -65,11 +66,12 @@ lines-after-imports = 2 order-by-type = false section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "tests/test_app.py" = ["ARG001"] "tests/test_build.py" = ["I001"] "tests/test_drafts.py" = ["ARG001", "I001"] "tests/test_post_dates.py" = ["I001"] +"tests/test_preview.py" = ["I001"] "tests/test_verify.py" = ["I001"] [tool.setuptools] diff --git a/requirements.txt b/requirements.txt index 7ce3664..839a3cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,11 @@ beautifulsoup4==4.12.3 click==8.1.7 csscompressor==0.9.5 -feedwerk==1.1.0 +feedwerk==1.2.0 Flask==3.0.1 Flask-FlatPages==0.8.2 Frozen-Flask==1.0.1 htmlmin==0.1.12 jsmin==3.0.1 Pygments==2.17.2 +requests==2.31.0 diff --git a/tests/test_preview.py b/tests/test_preview.py index 6007cfa..43e6f3e 100644 --- a/tests/test_preview.py +++ b/tests/test_preview.py @@ -1,25 +1,130 @@ +from pathlib import Path +import time +from types import TracebackType + from click.testing import CliRunner from htmd.cli import preview +import pytest +import requests +import subprocess +import sys + + +def invoke_preview(run_start: CliRunner, args: list[str]) -> None: + """ + run_start.invoke(preview) fails but it is used to track test coverage. + + I get a message that the path I pass to pytest is not found. + I've track it down to this subprocess call. + https://github.com/pallets/werkzeug/blob/d3dd65a27388fbd39d146caacf2563639ba622f0/src/werkzeug/_reloader.py#L273 + str(args) is "['/path/to/venv/bin/python', '-m', 'pytest', 'tests']" + ERROR: file or directory not found: tests + Which is how I'm running my tests. + If I pass tests/test_preview.py then I see + ERROR: file or directory not found: tests/test_preview.py + """ + run_start.invoke(preview, args) + +class run_preview: # noqa: N801 + def __init__( + self: 'run_preview', + args: list[str] | None = None, + max_tries: int = 10, + ) -> None: + self.args = args + self.max_tries = max_tries -def test_preview(run_start: CliRunner) -> None: - result = run_start.invoke(preview) - # Why is this 5? - expected_exit_code = 5 - assert result.exit_code == expected_exit_code + def __enter__(self: 'run_preview') -> None: + cmd = [sys.executable, '-m', 'htmd', 'preview'] + if self.args: + cmd += self.args + self.task = subprocess.Popen(cmd) # noqa: S603 + url = 'http://localhost:9090/' + count = 0 + while count < self.max_tries: # pragma: no branch + try: + requests.get(url, timeout=1) + except requests.exceptions.ConnectionError: + count += 1 + time.sleep(0.1) + else: + break + + def __exit__( + self: 'run_preview', + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self.task.terminate() + + +def test_preview(run_start: CliRunner) -> None: # noqa: ARG001 + url = 'http://localhost:9090/' + with pytest.raises(requests.exceptions.ConnectionError): + requests.get(url, timeout=1) + success = 200 + with run_preview(): + response = requests.get(url, timeout=0.01) + assert response.status_code == success def test_preview_css_minify_js_minify(run_start: CliRunner) -> None: - run_start.invoke(preview, ['--css-minify', '--js-minify']) + args = ['--css-minify', '--js-minify'] + invoke_preview(run_start, args) + urls = ( + (200, 'http://localhost:9090/static/combined.min.css'), + (200, 'http://localhost:9090/static/combined.min.js'), + ) + js_path = Path('static') / 'scripts.js' + with js_path.open('w') as js_file: + js_file.write('document.getElementsByTagName("body");') + + with run_preview(args): + for status, url in urls: + response = requests.get(url, timeout=0.01) + assert response.status_code == status def test_preview_no_css_minify(run_start: CliRunner) -> None: - run_start.invoke(preview, ['--no-css-minify', '--js-minify']) + args = ['--no-css-minify', '--js-minify'] + invoke_preview(run_start, args) + urls = ( + (404, 'http://localhost:9090/static/combined.min.css'), + (200, 'http://localhost:9090/static/combined.min.js'), + ) + js_path = Path('static') / 'scripts.js' + with js_path.open('w') as js_file: + js_file.write('document.getElementsByTagName("body");') + + with run_preview(args): + for status, url in urls: + response = requests.get(url, timeout=0.01) + assert response.status_code == status def test_preview_css_minify_no_js_minify(run_start: CliRunner) -> None: - run_start.invoke(preview, ['--css-minify', '--no-js-minify']) + args = ['--css-minify', '--no-js-minify'] + invoke_preview(run_start, args) + urls = ( + (200, 'http://localhost:9090/static/combined.min.css'), + (404, 'http://localhost:9090/static/combined.min.js'), + ) + with run_preview(args): + for status, url in urls: + response = requests.get(url, timeout=0.01) + assert response.status_code == status def test_preview_no_css_minify_no_js_minify(run_start: CliRunner) -> None: - run_start.invoke(preview, ['--no-css-minify', '--no-js-minify']) + args = ['--no-css-minify', '--no-js-minify'] + invoke_preview(run_start, args) + urls = ( + (404, 'http://localhost:9090/static/combined.min.css'), + (404, 'http://localhost:9090/static/combined.min.js'), + ) + with run_preview(args): + for status, url in urls: + response = requests.get(url, timeout=0.01) + assert response.status_code == status