QA: add structural and model tests for story 1.1 scaffold
Replaces placeholder stubs in test_models.py, test_gpio_ctrl.py, and provisioning/test_provision_loop.py with real assertions. Adds new test_scaffold.py covering AC3 file-presence, importlib.resources airports.csv load, constants completeness, and main.py provisioning import guard. Extends ruff per-file-ignores so tests/provisioning/*.py may import from planemapper.provisioning. All 22 tests pass; ruff check and format --check both clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,3 +38,5 @@ select = ["E", "F", "I", "TID", "UP"]
|
|||||||
# All non-main modules may import from provisioning freely
|
# All non-main modules may import from provisioning freely
|
||||||
"src/planemapper/provision.py" = ["TID251"]
|
"src/planemapper/provision.py" = ["TID251"]
|
||||||
"src/planemapper/provisioning/*.py" = ["TID251"]
|
"src/planemapper/provisioning/*.py" = ["TID251"]
|
||||||
|
# Tests may import from provisioning to test its public API
|
||||||
|
"tests/provisioning/*.py" = ["TID251"]
|
||||||
|
|||||||
@@ -1,2 +1,12 @@
|
|||||||
def test_placeholder() -> None:
|
from planemapper.provisioning import ProvisioningError
|
||||||
pass
|
|
||||||
|
|
||||||
|
def test_provisioning_error_is_exception() -> None:
|
||||||
|
assert issubclass(ProvisioningError, Exception)
|
||||||
|
|
||||||
|
|
||||||
|
def test_provisioning_error_can_be_raised_and_caught() -> None:
|
||||||
|
try:
|
||||||
|
raise ProvisioningError("test error")
|
||||||
|
except ProvisioningError as e:
|
||||||
|
assert str(e) == "test error"
|
||||||
|
|||||||
+13
-2
@@ -1,2 +1,13 @@
|
|||||||
def test_placeholder() -> None:
|
from planemapper.gpio_ctrl import ButtonHoldDetector, LEDController
|
||||||
pass
|
|
||||||
|
|
||||||
|
def test_button_hold_detector_returns_bool() -> None:
|
||||||
|
detector = ButtonHoldDetector()
|
||||||
|
result = detector.check()
|
||||||
|
assert isinstance(result, bool)
|
||||||
|
|
||||||
|
|
||||||
|
def test_led_controller_on_off_no_exception() -> None:
|
||||||
|
led = LEDController()
|
||||||
|
led.on()
|
||||||
|
led.off()
|
||||||
|
|||||||
+28
-2
@@ -1,2 +1,28 @@
|
|||||||
def test_placeholder() -> None:
|
from planemapper.models import Aircraft
|
||||||
pass
|
|
||||||
|
|
||||||
|
def test_aircraft_defaults() -> None:
|
||||||
|
a = Aircraft(icao="ABC123", lat=51.5, lon=-0.1)
|
||||||
|
assert a.heading == 0.0
|
||||||
|
assert a.altitude_ft == 0
|
||||||
|
assert a.callsign == ""
|
||||||
|
assert a.category == ""
|
||||||
|
assert a.is_mlat is False
|
||||||
|
assert a.is_stale is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_aircraft_full() -> None:
|
||||||
|
a = Aircraft(
|
||||||
|
icao="ABC123",
|
||||||
|
lat=51.5,
|
||||||
|
lon=-0.1,
|
||||||
|
heading=90.0,
|
||||||
|
altitude_ft=5000,
|
||||||
|
callsign="BAW1",
|
||||||
|
category="A3",
|
||||||
|
is_mlat=True,
|
||||||
|
is_stale=False,
|
||||||
|
)
|
||||||
|
assert a.heading == 90.0
|
||||||
|
assert a.callsign == "BAW1"
|
||||||
|
assert a.is_mlat is True
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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_does_not_import_provisioning() -> None:
|
||||||
|
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.Import, ast.ImportFrom)):
|
||||||
|
if isinstance(node, ast.ImportFrom) and node.module:
|
||||||
|
assert not node.module.startswith("planemapper.provisioning"), (
|
||||||
|
"main.py must not import from planemapper.provisioning"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user