harden delivery pipeline and production runtime
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
@@ -12,7 +14,7 @@ def test_ml_routes_are_exposed_by_production_app() -> None:
|
||||
|
||||
assert health.status_code == 200
|
||||
assert models.status_code == 200
|
||||
assert models.json() == {"models": []}
|
||||
assert isinstance(models.json()["models"], list)
|
||||
|
||||
|
||||
def test_unknown_model_returns_404() -> None:
|
||||
@@ -29,7 +31,7 @@ def test_unknown_model_returns_404() -> None:
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_unsupported_sensor_returns_422(tmp_path) -> None:
|
||||
def test_unsupported_sensor_returns_422(tmp_path: Path) -> None:
|
||||
from app.ml.registry.model_registry import ModelRegistry
|
||||
from app.ml.training import TrainedArtifact
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.ml.feature_store import FeatureStore, FeatureVector
|
||||
|
||||
|
||||
@@ -31,12 +29,18 @@ def test_add_batch_appends_all_vectors() -> None:
|
||||
]
|
||||
store.add_batch(vectors)
|
||||
assert len(store.all()) == 3
|
||||
assert store.latest("sensor.kitchen").values["temperature"] == 20.0
|
||||
latest = store.latest("sensor.kitchen")
|
||||
assert latest is not None
|
||||
assert latest.values["temperature"] == 20.0
|
||||
|
||||
|
||||
def test_different_sensors_are_stored_independently() -> None:
|
||||
store = FeatureStore()
|
||||
store.add(_vector("sensor.living_room", 21.0))
|
||||
store.add(_vector("sensor.bedroom", 18.5))
|
||||
assert store.latest("sensor.living_room").values["temperature"] == 21.0
|
||||
assert store.latest("sensor.bedroom").values["temperature"] == 18.5
|
||||
living_room = store.latest("sensor.living_room")
|
||||
bedroom = store.latest("sensor.bedroom")
|
||||
assert living_room is not None
|
||||
assert bedroom is not None
|
||||
assert living_room.values["temperature"] == 21.0
|
||||
assert bedroom.values["temperature"] == 18.5
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -8,7 +9,7 @@ from app.ml.registry.model_registry import ModelRegistry
|
||||
from app.ml.training import TrainedArtifact
|
||||
|
||||
|
||||
def test_registry_loads_persisted_artifacts_after_restart(tmp_path) -> None:
|
||||
def test_registry_loads_persisted_artifacts_after_restart(tmp_path: Path) -> None:
|
||||
registry = ModelRegistry(tmp_path)
|
||||
artifact = TrainedArtifact("model-v1", ("sensor.kitchen", "sensor.bedroom"))
|
||||
registry.register(artifact)
|
||||
@@ -19,7 +20,7 @@ def test_registry_loads_persisted_artifacts_after_restart(tmp_path) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("artifact_id", ["../escape", "nested/model", "..", ""])
|
||||
def test_registry_rejects_unsafe_artifact_ids(tmp_path, artifact_id: str) -> None:
|
||||
def test_registry_rejects_unsafe_artifact_ids(tmp_path: Path, artifact_id: str) -> None:
|
||||
registry = ModelRegistry(tmp_path)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
@@ -28,7 +29,7 @@ def test_registry_rejects_unsafe_artifact_ids(tmp_path, artifact_id: str) -> Non
|
||||
assert list(tmp_path.parent.glob("escape.json")) == []
|
||||
|
||||
|
||||
def test_registry_rejects_corrupt_persisted_artifact(tmp_path) -> None:
|
||||
def test_registry_rejects_corrupt_persisted_artifact(tmp_path: Path) -> None:
|
||||
(tmp_path / "broken.json").write_text(
|
||||
json.dumps({"artifact_id": "../broken", "supported_sensors": []}),
|
||||
encoding="utf-8",
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
from app.ml.feature_store import FeatureStore, FeatureVector
|
||||
from app.ml.training import TrainingPipeline, TrainedArtifact
|
||||
from app.ml.training import TrainingPipeline
|
||||
|
||||
|
||||
def _vector(sensor_id: str, temperature: float, label: str | None = None) -> FeatureVector:
|
||||
@@ -45,4 +45,4 @@ def test_export_returns_registered_artifact() -> None:
|
||||
def test_export_missing_artifact_raises_key_error() -> None:
|
||||
pipeline = store_with_data()
|
||||
with pytest.raises(KeyError):
|
||||
pipeline.export("artifact_v1")
|
||||
pipeline.export("artifact_v1")
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from app.config import load_settings
|
||||
|
||||
|
||||
def test_load_settings_reads_documented_environment(monkeypatch) -> None:
|
||||
def test_load_settings_reads_documented_environment(monkeypatch: MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("SILLYHOME_HA_URL", "http://ha.local:8123")
|
||||
monkeypatch.setenv("SILLYHOME_HA_TOKEN", "secret")
|
||||
monkeypatch.setenv("SILLYHOME_MODEL_STORE", "/tmp/models")
|
||||
|
||||
Reference in New Issue
Block a user