9f6d442df8
Implements story 2-7: full main.py with _make_startup_screen(), _run_one_cycle() with per-phase timing and slow-render warning, and main() connecting config → bounds → fetcher → renderer → display. Adds provision.py early-exit when already provisioned. Adds User=root to both systemd service files. Adds tests/test_main.py (3 new tests, 99 total). Updates scaffold test to allow provisioning.config import from main.py. All quality gates pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
2.6 KiB
Python
86 lines
2.6 KiB
Python
import ast
|
|
import importlib.resources
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).parent.parent
|
|
|
|
|
|
def test_required_toplevel_modules_exist() -> None:
|
|
base = REPO_ROOT / "src" / "planemapper"
|
|
for name in [
|
|
"__init__.py",
|
|
"constants.py",
|
|
"models.py",
|
|
"main.py",
|
|
"provision.py",
|
|
"fetcher.py",
|
|
"gpio_ctrl.py",
|
|
"display.py",
|
|
]:
|
|
assert (base / name).exists(), f"Missing: {name}"
|
|
|
|
|
|
def test_provisioning_subpackage_exists() -> None:
|
|
base = REPO_ROOT / "src" / "planemapper" / "provisioning"
|
|
for name in [
|
|
"__init__.py",
|
|
"portal.py",
|
|
"location.py",
|
|
"tiles.py",
|
|
"airspace.py",
|
|
"wifi.py",
|
|
"config.py",
|
|
]:
|
|
assert (base / name).exists(), f"Missing provisioning/{name}"
|
|
|
|
|
|
def test_renderer_subpackage_exists() -> None:
|
|
base = REPO_ROOT / "src" / "planemapper" / "renderer"
|
|
for name in [
|
|
"__init__.py",
|
|
"renderer.py",
|
|
"projection.py",
|
|
"basemap.py",
|
|
"aircraft.py",
|
|
"airspace.py",
|
|
"colours.py",
|
|
"icons.py",
|
|
]:
|
|
assert (base / name).exists(), f"Missing renderer/{name}"
|
|
|
|
|
|
def test_systemd_units_exist() -> None:
|
|
systemd = REPO_ROOT / "systemd"
|
|
assert (systemd / "planemapper-provision.service").exists()
|
|
assert (systemd / "planemapper-radar.service").exists()
|
|
|
|
|
|
def test_airports_csv_via_importlib_resources() -> None:
|
|
ref = importlib.resources.files("planemapper.data").joinpath("airports.csv")
|
|
content = ref.read_text(encoding="utf-8")
|
|
assert len(content) > 0
|
|
assert "icao_code" in content or "ident" in content # OurAirports CSV header
|
|
|
|
|
|
def test_constants_colours_complete() -> None:
|
|
from planemapper import constants
|
|
|
|
assert len(constants.ALTITUDE_COLOURS) == 6
|
|
assert len(constants.ALTITUDE_BANDS_FT) == 6
|
|
assert constants.DISPLAY_WIDTH == 800
|
|
assert constants.DISPLAY_HEIGHT == 480
|
|
assert constants.REFRESH_INTERVAL_S == 60
|
|
assert constants.FETCH_TIMEOUT_S == 5
|
|
|
|
|
|
def test_main_only_imports_config_from_provisioning() -> None:
|
|
"""main.py may import planemapper.provisioning.config (to read stored config)
|
|
but must not import other provisioning sub-modules (portal, wifi, tiles, etc.)."""
|
|
allowed = {"planemapper.provisioning.config"}
|
|
main_path = REPO_ROOT / "src" / "planemapper" / "main.py"
|
|
tree = ast.parse(main_path.read_text())
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.ImportFrom) and node.module:
|
|
if node.module.startswith("planemapper.provisioning"):
|
|
assert node.module in allowed, f"main.py must not import from {node.module}"
|