110 lines
3.1 KiB
Python
110 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.main import app
|
|
|
|
|
|
def test_ml_routes_are_exposed_by_production_app() -> None:
|
|
with TestClient(app) as client:
|
|
health = client.get("/ml/health")
|
|
models = client.get("/ml/models")
|
|
|
|
assert health.status_code == 200
|
|
assert models.status_code == 200
|
|
assert isinstance(models.json()["models"], list)
|
|
|
|
|
|
def test_unknown_model_returns_404() -> None:
|
|
with TestClient(app) as client:
|
|
response = client.post(
|
|
"/ml/predict",
|
|
json={
|
|
"modelId": "missing",
|
|
"sensor_id": "sensor.kitchen",
|
|
"values": {"temperature": 21.0},
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_unsupported_sensor_returns_422(tmp_path: Path) -> None:
|
|
from app.ml.registry.model_registry import ModelRegistry
|
|
from app.ml.training import TrainedArtifact
|
|
|
|
registry = ModelRegistry(tmp_path)
|
|
registry.register(TrainedArtifact("default", ("sensor.kitchen",)))
|
|
|
|
with TestClient(app) as client:
|
|
app.state.registry = registry
|
|
response = client.post(
|
|
"/ml/predict",
|
|
json={
|
|
"modelId": "default",
|
|
"sensor_id": "sensor.unknown",
|
|
"values": {"temperature": 21.0},
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 422
|
|
|
|
|
|
def test_retrain_creates_and_replaces_persisted_model(tmp_path: Path) -> None:
|
|
from app.ml.registry.model_registry import ModelRegistry
|
|
|
|
registry = ModelRegistry(tmp_path)
|
|
with TestClient(app) as client:
|
|
app.state.registry = registry
|
|
created = client.post(
|
|
"/ml/retrain",
|
|
json={
|
|
"modelId": "home-model",
|
|
"samples": [
|
|
{
|
|
"sensor_id": "sensor.kitchen",
|
|
"values": {"temperature": 21.0},
|
|
}
|
|
],
|
|
},
|
|
)
|
|
replaced = client.post(
|
|
"/ml/retrain",
|
|
json={
|
|
"modelId": "home-model",
|
|
"samples": [
|
|
{
|
|
"sensor_id": "sensor.bedroom",
|
|
"values": {"temperature": 18.0},
|
|
}
|
|
],
|
|
},
|
|
)
|
|
|
|
assert created.status_code == 200
|
|
assert created.json() == {
|
|
"model_id": "home-model",
|
|
"supported_sensors": ["sensor.kitchen"],
|
|
"replaced": False,
|
|
}
|
|
assert replaced.status_code == 200
|
|
assert replaced.json() == {
|
|
"model_id": "home-model",
|
|
"supported_sensors": ["sensor.bedroom"],
|
|
"replaced": True,
|
|
}
|
|
restarted = ModelRegistry(tmp_path)
|
|
assert restarted.load_artifact("home-model").supported_sensors == ("sensor.bedroom",)
|
|
|
|
|
|
def test_retrain_rejects_empty_samples() -> None:
|
|
with TestClient(app) as client:
|
|
response = client.post(
|
|
"/ml/retrain",
|
|
json={"modelId": "home-model", "samples": []},
|
|
)
|
|
|
|
assert response.status_code == 422
|