feat(scada): add project monitoring replay
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.api.v1.endpoints import scada_replay
|
||||
from app.auth.project_dependencies import ProjectContext, get_project_context
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def replay_api(monkeypatch):
|
||||
context = ProjectContext(uuid4(), "demo", uuid4(), "member")
|
||||
project = SimpleNamespace(settings={"scada_monitoring_interval_seconds": 900, "other": True})
|
||||
metadata = SimpleNamespace(get_project_by_id=AsyncMock(return_value=project))
|
||||
repo = SimpleNamespace(available=AsyncMock(return_value=True), current=AsyncMock(return_value=None))
|
||||
create = AsyncMock(side_effect=RuntimeError("creation mock must be configured"))
|
||||
monkeypatch.setattr(scada_replay.ScadaReplayService, "create", create)
|
||||
app = FastAPI()
|
||||
app.include_router(scada_replay.router)
|
||||
app.dependency_overrides[get_project_context] = lambda: context
|
||||
app.dependency_overrides[scada_replay.repository] = lambda: repo
|
||||
app.dependency_overrides[scada_replay.get_metadata_repository] = lambda: metadata
|
||||
with TestClient(app) as client:
|
||||
yield client, project, metadata, context, create
|
||||
|
||||
|
||||
def test_capabilities_reads_frequency_from_current_project_settings(replay_api):
|
||||
client, project, metadata, context, _ = replay_api
|
||||
response = client.get("/scada-replays/capabilities")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["monitoring_interval_seconds"] == 900
|
||||
assert response.json()["burst_monitoring_ready"] is False
|
||||
assert "尚未配置" in response.json()["burst_monitoring_reason"]
|
||||
metadata.get_project_by_id.assert_awaited_once_with(context.project_id)
|
||||
project.settings = {}
|
||||
assert client.get("/scada-replays/capabilities").json()["monitoring_interval_seconds"] is None
|
||||
|
||||
|
||||
def test_capabilities_reports_ready_only_with_five_pressures_and_outlet_flow(replay_api):
|
||||
client, project, *_ = replay_api
|
||||
project.settings = {
|
||||
"scada_monitoring_interval_seconds": 900,
|
||||
"scada_monitoring": {
|
||||
"pressure_device_ids": ["P1", "P2", "P3", "P4", "P5"],
|
||||
"outlet_flow_device_ids": ["F1"],
|
||||
},
|
||||
}
|
||||
|
||||
response = client.get("/scada-replays/capabilities")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["burst_monitoring_ready"] is True
|
||||
assert response.json()["burst_monitoring_reason"] is None
|
||||
|
||||
|
||||
def test_capabilities_reports_only_missing_storage_not_worker():
|
||||
repo = SimpleNamespace(available=AsyncMock(return_value=False))
|
||||
context = ProjectContext(uuid4(), "demo", uuid4(), "member")
|
||||
|
||||
result = asyncio.run(scada_replay.replay_capabilities(
|
||||
repo=repo,
|
||||
ctx=context,
|
||||
settings=scada_replay.ProjectSettings(scada_monitoring_interval_seconds=900),
|
||||
))
|
||||
|
||||
assert result.available is False
|
||||
assert result.reason == "需先部署 SCADA 回放存储结构"
|
||||
|
||||
|
||||
def test_step_command_processes_pending_period_in_same_request(monkeypatch):
|
||||
calls = []
|
||||
|
||||
class Service:
|
||||
def __init__(self, repo, context, timescale):
|
||||
calls.append((repo, context, timescale))
|
||||
|
||||
async def command(self, replay_id, command):
|
||||
calls.append((replay_id, command.action))
|
||||
return SimpleNamespace(step_pending=True)
|
||||
|
||||
async def process_pending_step(self, replay_id):
|
||||
calls.append((replay_id, "processed"))
|
||||
return "completed-period"
|
||||
|
||||
monkeypatch.setattr(scada_replay, "ScadaReplayService", Service)
|
||||
replay_id = uuid4()
|
||||
context = ProjectContext(uuid4(), "demo", uuid4(), "member")
|
||||
command = scada_replay.ScadaReplayCommand(
|
||||
request_id=uuid4(), expected_revision=1, action="step"
|
||||
)
|
||||
|
||||
result = asyncio.run(scada_replay.replay_command(
|
||||
replay_id, command, repo="repo", ctx=context, timescale="timescale"
|
||||
))
|
||||
|
||||
assert result == "completed-period"
|
||||
assert calls[-2:] == [(replay_id, "step"), (replay_id, "processed")]
|
||||
|
||||
|
||||
def test_client_cannot_override_project_frequency_on_creation(replay_api):
|
||||
client, _, _, _, create = replay_api
|
||||
response = client.post("/scada-replays", json={
|
||||
"request_id": str(uuid4()), "start_time": "2026-10-08T00:00:00Z",
|
||||
"end_time": "2026-10-08T01:00:00Z", "device_ids": ["P1"],
|
||||
"rules": [{"rule_id": "p", "device_ids": ["P1"], "kind": "bounds", "upper": 10, "unit": "m"}],
|
||||
"monitoring_interval_seconds": 300,
|
||||
})
|
||||
assert response.status_code == 422
|
||||
create.assert_not_awaited()
|
||||
Reference in New Issue
Block a user