from copy import deepcopy from datetime import datetime, timedelta, timezone from types import SimpleNamespace from unittest.mock import AsyncMock import pytest from app.domain.schemas.project_settings import ProjectSettings from app.services.operational_recovery import reconcile_recoveries from app.services.operational_tasks import OperationalTaskService from tests.support.operational_task_repository import TaskRepository, _active_run, _context, _task_document @pytest.fixture def anyio_backend(): return "asyncio" @pytest.mark.anyio @pytest.mark.parametrize("cached", [False, True]) async def test_recovery_uses_report_devices_instead_of_all_browsed_network_devices(monkeypatch, cached): now = datetime(2026, 7, 16, 6, tzinfo=timezone.utc) incident = now - timedelta(hours=6) returned = now - timedelta(minutes=20) task = _task_document(_context(), now, lease=now + timedelta(minutes=1)) task.update(workflow="scada_anomaly", status="waiting_human", lifecycle_status="waiting_human", current_report_revision=1, trigger={"kind": "event", "occurred_at": incident.isoformat(), "detail": "异常"}, handling_context={"phase": "waiting_scada", "returned_at": returned.isoformat()}) if cached: task["handling_context"].update(device_ids=["P1", "unrelated"], interval_seconds=900) repo = TaskRepository(task, _active_run(task, now)) repo.get_latest_report = AsyncMock(return_value={"revision": 1, "report": {"scada_device_ids": ["P1"]}}) repo.list_evidence = AsyncMock(return_value=[{"data_snapshot": {"source": "query_scada_readings", "data": {"data": [{"device_id": "P1"}, {"device_id": "unrelated"}]}}}]) repo.recovery_candidates = AsyncMock(side_effect=lambda *_: [deepcopy(repo.task)] if repo.task["handling_context"]["phase"] == "waiting_scada" else []) service = OperationalTaskService(repo, _context(), set(), clock=lambda: now) at = returned + timedelta(minutes=5) rows = [{"time": at - timedelta(days=day), "device_id": "P1", "monitored_value": 100.} for day in range(1, 16)] + [{"time": at, "device_id": "P1", "monitored_value": 100.}] reader = AsyncMock(side_effect=lambda _, devices, *args, **kwargs: rows if devices == ["P1"] else [{}] * 100_001) monkeypatch.setattr("app.infra.db.timescaledb.repositories.scada.ScadaRepository.get_recovery_scada_readings", reader) settings = ProjectSettings(scada_monitoring_interval_seconds=900, scada_monitoring={"pressure_device_ids": ["P1"]}) assert await reconcile_recoveries(service, SimpleNamespace(transaction=repo.transaction), settings) == 1 assert repo.task["handling_context"]["device_ids"] == ["P1"] assert repo.task["handling_context"]["phase"] == "verification_ready" assert repo.run["status"] == "queued" assert await reconcile_recoveries(service, SimpleNamespace(transaction=repo.transaction), settings) == 0