"""Packaging + lazy-import discipline tests for ratatoskr.web per issue #16. - `index.html` resolvable via importlib.resources (ships in wheel) - `ratatoskr.web.entrypoint` importable without starlette+uvicorn, prints install-hint and exits non-zero in that mode """ from importlib.resources import files def test_index_html_in_package() -> None: """static/index.html is locatable via importlib.resources. INV-009 packaging discipline. The asset must be part of the installed package — `_static_dir()` in the server uses this exact resolution path at startup. """ path = files("ratatoskr.web") / "static" / "index.html" assert path.is_file(), f"index.html missing at {path}" content = path.read_text() assert " None: """INV-001: importing `ratatoskr.web.entrypoint` MUST NOT import starlette or uvicorn at the module level. Verified by AST inspection of the source — checks no top-level `import starlette` / `from starlette` / `import uvicorn` / `from uvicorn` statements. """ import ast from importlib.resources import files src = (files("ratatoskr.web") / "entrypoint.py").read_text() tree = ast.parse(src) banned = {"starlette", "uvicorn"} for node in tree.body: if isinstance(node, ast.Import): for alias in node.names: top = alias.name.split(".")[0] assert top not in banned, ( f"top-level `import {alias.name}` violates INV-001 " "lazy-import discipline" ) elif isinstance(node, ast.ImportFrom): mod = (node.module or "").split(".")[0] assert mod not in banned, ( f"top-level `from {node.module} import ...` violates " "INV-001 lazy-import discipline" ) def test_entrypoint_missing_api_key_returns_11(monkeypatch) -> None: """missing_api_key [error]: WORLDTREE_API_KEY unset → exit 11.""" monkeypatch.delenv("WORLDTREE_API_KEY", raising=False) from ratatoskr.web.entrypoint import main rc = main(["--port", "0"]) assert rc == 11 def test_entrypoint_missing_extras_returns_12(monkeypatch) -> None: """missing_extras [error]: starlette unimportable → exit 12. v0.16.0: the missing-extras probe is scoped to the OPTIONAL extras (starlette / uvicorn) only. Simulated by shadowing `starlette` to None in sys.modules so its import raises ImportError inside the narrow try-block. """ import sys monkeypatch.setenv("WORLDTREE_API_KEY", "k") monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com") monkeypatch.setitem(sys.modules, "starlette", None) from ratatoskr.web.entrypoint import main rc = main(["--port", "0"]) assert rc == 12 def test_entrypoint_real_import_bug_propagates(monkeypatch) -> None: """v0.16.0: an ImportError from a BASELINE module (not an extra) MUST propagate as a real traceback, not be masked as exit-12 missing-extras. Shadowing ratatoskr.web.server (a first-party module, present with or without the [web] extras) should raise, not return 12. """ import sys import pytest as _pytest monkeypatch.setenv("WORLDTREE_API_KEY", "k") monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com") monkeypatch.setitem(sys.modules, "ratatoskr.web.server", None) from ratatoskr.web.entrypoint import main with _pytest.raises(ImportError): main(["--port", "0"]) class TestEntrypointArgs: """FN main argparse + serve-loop traces (contract TESTS).""" def test_default_host_is_zero(self) -> None: """default_host_is_zero [trace]: argv=[] → host == '0.0.0.0'.""" from ratatoskr.web.entrypoint import _build_arg_parser args = _build_arg_parser().parse_args([]) assert args.host == "0.0.0.0" assert args.port == 8765 assert args.open is False def test_port_zero_supported(self) -> None: """port_zero_supported [trace]: argv=['--port','0'] → port == 0.""" from ratatoskr.web.entrypoint import _build_arg_parser args = _build_arg_parser().parse_args(["--port", "0"]) assert args.port == 0 def test_happy_argv_serves_and_returns_zero(self, monkeypatch) -> None: """happy_argv [tracer]: env set + uvicorn.run mocked → main returns 0.""" import uvicorn monkeypatch.setenv("WORLDTREE_API_KEY", "k") monkeypatch.setenv("WORLDTREE_API_URL", "https://example.com") calls = {} monkeypatch.setattr(uvicorn, "run", lambda app, **kw: calls.update(kw)) from ratatoskr.web.entrypoint import main rc = main(["--port", "0"]) assert rc == 0 assert calls["host"] == "0.0.0.0" assert calls["port"] == 0 def test_open_flag_calls_webbrowser(self, monkeypatch) -> None: """open_flag_calls_webbrowser [trace]: --open → webbrowser.open called.""" import uvicorn import webbrowser monkeypatch.setenv("WORLDTREE_API_KEY", "k") monkeypatch.setattr(uvicorn, "run", lambda app, **kw: None) opened = [] monkeypatch.setattr(webbrowser, "open", lambda url: opened.append(url)) from ratatoskr.web.entrypoint import main main(["--port", "0", "--open"]) assert len(opened) == 1 def test_no_open_default(self, monkeypatch) -> None: """no_open_default [trace]: argv without --open → webbrowser.open NOT called.""" import uvicorn import webbrowser monkeypatch.setenv("WORLDTREE_API_KEY", "k") monkeypatch.setattr(uvicorn, "run", lambda app, **kw: None) opened = [] monkeypatch.setattr(webbrowser, "open", lambda url: opened.append(url)) from ratatoskr.web.entrypoint import main main(["--port", "0"]) assert opened == []