martian7777
feat: implement backend core with ORM models, authentication, and AI-driven telemetry diagnostics
e5be436 | """AI explanation tests (uses the built-in mock explainer; no network).""" | |
| from __future__ import annotations | |
| import pytest | |
| from httpx import AsyncClient | |
| from app.services import telemetry_service | |
| pytestmark = pytest.mark.asyncio | |
| def _patch_session(session_factory, monkeypatch): | |
| monkeypatch.setattr(telemetry_service, "AsyncSessionLocal", session_factory) | |
| def _csv(rows: int = 150) -> bytes: | |
| import numpy as np | |
| import pandas as pd | |
| rng = np.random.default_rng(3) | |
| temp = rng.normal(70, 1.0, rows) | |
| temp[10] += 30 | |
| df = pd.DataFrame( | |
| { | |
| "timestamp": pd.date_range("2026-01-01", periods=rows, freq="min"), | |
| "temperature": temp, | |
| "vibration": rng.normal(0.5, 0.05, rows), | |
| "pressure": rng.normal(30, 0.5, rows), | |
| "rotational_speed": rng.normal(1500, 10, rows), | |
| } | |
| ) | |
| return df.to_csv(index=False).encode() | |
| async def test_explain_no_data_returns_mock(auth_client: AsyncClient, machine_id: str): | |
| resp = await auth_client.get(f"/api/v1/ai/explain/{machine_id}") | |
| assert resp.status_code == 200 | |
| body = resp.json() | |
| assert body["is_mock"] is True | |
| assert body["model_used"] == "mock" | |
| assert body["anomalies_found"] == 0 | |
| assert isinstance(body["recommendations"], list) | |
| async def test_explain_after_upload(auth_client: AsyncClient, machine_id: str, _patch_session): | |
| files = {"file": ("sensors.csv", _csv(150), "text/csv")} | |
| await auth_client.post(f"/api/v1/telemetry/upload/{machine_id}", files=files) | |
| resp = await auth_client.get(f"/api/v1/ai/explain/{machine_id}?window=200") | |
| assert resp.status_code == 200 | |
| body = resp.json() | |
| assert body["window_analyzed"] == 150 | |
| assert body["anomalies_found"] >= 1 | |
| assert len(body["recommendations"]) >= 1 | |
| assert body["machine_name"] == "Pump-1" | |
| async def test_explain_requires_auth(client: AsyncClient): | |
| import uuid | |
| resp = await client.get(f"/api/v1/ai/explain/{uuid.uuid4()}") | |
| assert resp.status_code == 401 | |
| async def test_explain_uses_openrouter_when_enabled( | |
| auth_client: AsyncClient, machine_id: str, monkeypatch | |
| ): | |
| """When a key is configured, the OpenRouter path is used and parsed.""" | |
| import json as _json | |
| import types | |
| from app.core import config as config_module | |
| from app.services import ai_service as ai_module | |
| # Pretend AI is enabled. | |
| monkeypatch.setattr(config_module.settings, "openrouter_api_key", "test-key") | |
| monkeypatch.setattr(ai_module.settings, "openrouter_api_key", "test-key") | |
| payload = { | |
| "summary": "Bearing wear detected.", | |
| "explanation": "Vibration trending up.", | |
| "recommendations": ["Inspect bearings", "Re-lubricate"], | |
| } | |
| class _FakeCompletions: | |
| async def create(self, **kwargs): | |
| msg = types.SimpleNamespace(content=_json.dumps(payload)) | |
| choice = types.SimpleNamespace(message=msg) | |
| return types.SimpleNamespace(choices=[choice]) | |
| class _FakeChat: | |
| completions = _FakeCompletions() | |
| class _FakeClient: | |
| def __init__(self, *a, **k): | |
| self.chat = _FakeChat() | |
| import openai | |
| monkeypatch.setattr(openai, "AsyncOpenAI", _FakeClient) | |
| resp = await auth_client.get(f"/api/v1/ai/explain/{machine_id}") | |
| assert resp.status_code == 200 | |
| body = resp.json() | |
| assert body["is_mock"] is False | |
| assert body["summary"] == "Bearing wear detected." | |
| assert "Inspect bearings" in body["recommendations"] | |