Files
next-tjwater-server/tests/unit/test_scada_raw.py
T
jiang 26fa5d1909
Generic Container CI/CD / test-build-publish (push) Successful in 3m39s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 3m39s
feat(scada): add project monitoring replay
2026-11-04 14:27:21 +08:00

66 lines
3.1 KiB
Python

import asyncio
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock
import pytest
from app.infra.db.timescaledb.repositories.scada import ScadaRepository
from app.services.scada_raw import read_raw_page
START = datetime(2026, 10, 8, tzinfo=timezone.utc)
def reading(minutes, device="P1", value=1):
return {"time": START + timedelta(minutes=minutes), "device_id": device,
"monitored_value": value, "source_hash": "hash"}
def test_pagination_keeps_same_timestamp_devices_and_freezes_clock(monkeypatch):
fetch = AsyncMock(side_effect=[[reading(0), reading(0, "P2")], [reading(0, "P2")]])
monkeypatch.setattr(ScadaRepository, "get_raw_page", fetch)
async def run():
args = dict(project_id="project", device_ids=["P1", "P2"], start=START,
end=START + timedelta(hours=1), limit=1)
first = await read_raw_page(None, **args, through=START + timedelta(minutes=15))
second = await read_raw_page(None, **args, cursor=first.next_cursor, through=START + timedelta(minutes=30))
assert first.items[0].device_id == "P1"
assert second.items[0].device_id == "P2"
assert second.scanned_through == START + timedelta(minutes=15)
assert fetch.await_args.args[-1] == (START, "P1")
asyncio.run(run())
def test_cursor_cannot_expand_query_or_move_into_future(monkeypatch):
fetch = AsyncMock(return_value=[reading(0), reading(5)])
monkeypatch.setattr(ScadaRepository, "get_raw_page", fetch)
async def run():
args = dict(project_id="project", device_ids=["P1"], start=START,
end=START + timedelta(hours=1), limit=1)
first = await read_raw_page(None, **args, through=START + timedelta(minutes=15))
for change in ({"project_id": "other"}, {"device_ids": ["P2"]}, {"through": START}):
with pytest.raises(ValueError, match="游标"):
await read_raw_page(None, **(args | change), cursor=first.next_cursor)
asyncio.run(run())
assert fetch.await_count == 1
def test_raw_response_does_not_expose_corrected_values_and_marks_bad_numbers(monkeypatch):
monkeypatch.setattr(ScadaRepository, "get_raw_page", AsyncMock(return_value=[
reading(0, value=float("nan")), reading(5, value=None), reading(10, value=0),
]))
page = asyncio.run(read_raw_page(None, project_id="project", device_ids=["P1"], start=START,
end=START + timedelta(hours=1)))
assert [item.quality for item in page.items] == ["invalid", "missing", "unvalidated"]
assert page.items[2].monitored_value == 0
assert "cleaned_value" not in page.model_dump_json()
@pytest.mark.parametrize("cursor", ["not-base64", "e30=", "W10="])
def test_malformed_cursor_is_rejected_without_query(monkeypatch, cursor):
fetch = AsyncMock()
monkeypatch.setattr(ScadaRepository, "get_raw_page", fetch)
with pytest.raises(ValueError):
asyncio.run(read_raw_page(None, project_id="project", device_ids=["P1"], start=START,
end=START + timedelta(hours=1), cursor=cursor))
fetch.assert_not_awaited()