roverdevkit / webapp /backend /tests /conftest.py
jjreif's picture
Deploy roverdevkit @ 2676a67
b3d14e3
Raw
History Blame Contribute Delete
3.78 kB
"""Shared fixtures for the webapp backend test suite.
The fixtures here are intentionally small: build a real FastAPI app
backed by the real on-disk artifacts, and hand it to a `TestClient`
once per test session. We do **not** mock the surrogate or the
scenario loaders -- the whole point of this test suite is to catch
artifact-on-disk drift before it hits the frontend.
If the quantile-calibration quantile bundle is missing the suite will skip the
predict tests rather than fail outright; this lets a contributor who
has not yet generated the artifact still run health / scenarios /
registry tests locally.
"""
from __future__ import annotations
from collections.abc import Iterator
import pytest
from fastapi.testclient import TestClient
from webapp.backend.app import create_app
from webapp.backend.config import get_settings
from webapp.backend.loaders import reset_caches
@pytest.fixture(scope="session")
def client() -> Iterator[TestClient]:
"""Return a `TestClient` for the real backend app, session-scoped."""
reset_caches()
app = create_app()
with TestClient(app) as c:
yield c
reset_caches()
@pytest.fixture(scope="session")
def artifacts_present() -> bool:
"""Whether the on-disk surrogate artifact is loadable.
Tracks a *file-existence* condition only; the surrogate may still
be schema-incompatible with the live evaluator (e.g. while a
schema-bump retrain is in flight). Use
:func:`surrogate_v7_1_compatible` to gate tests that actually call
``/predict`` end-to-end.
"""
return get_settings().artifacts_present
@pytest.fixture(scope="session")
def surrogate_v7_1_compatible() -> bool:
"""Whether the on-disk surrogate is schema-compatible with the live
feature-row builder (currently schema v9).
Schema v7_1 promoted ``scenario_operational_duty_cycle`` to a true
surrogate input; schema v9 added ``scenario_payload_mass_kg`` and
``scenario_payload_power_w``. A bundle trained before these columns
existed KeyErrors at predict time once the feature-row builder
includes them, so predict / evaluate / surrogate-sweep tests skip
on schema mismatch instead of failing red until the v9 recalibrate
lands. (Fixture name retained for call-site stability.)
"""
settings = get_settings()
if not settings.artifacts_present:
return False
try:
import joblib
bundles = joblib.load(settings.quantile_bundles_path)
any_bundle = next(iter(bundles.values()))
feature_columns = list(getattr(any_bundle, "feature_columns", []))
except Exception:
return False
return (
"design_peak_wheel_torque_nm" in feature_columns
and "design_designed_duty_cycle" not in feature_columns
and "scenario_operational_duty_cycle" in feature_columns
and "scenario_payload_mass_kg" in feature_columns
and "scenario_payload_power_w" in feature_columns
)
@pytest.fixture()
def sample_design() -> dict[str, float | int]:
"""A safely in-bounds design vector (Yutu-2-ish) for predict tests.
Mirrors the real Yutu-2 design except where the design schema's
bounds force a tweak, so the request payload always validates.
Kept out of the registry on purpose -- the predict tests should
work even if the registry export ever changes.
"""
return {
"mobility_architecture": "rigid_4wheel",
"wheel_radius_m": 0.10,
"wheel_width_m": 0.10,
"grouser_height_m": 0.012,
"grouser_count": 14,
"n_wheels": 4,
"chassis_mass_kg": 20.0,
"wheelbase_m": 0.6,
"solar_area_m2": 0.5,
"battery_capacity_wh": 100.0,
"avionics_power_w": 15.0,
"peak_wheel_torque_nm": 1.5,
}