Reorganize WNDB by responsibility and remove legacy scheme endpoints.\n\nRoute analysis and time-series access through project pools, preserve transactional realtime replacement, and refresh GIS materialized views after writes.\n\nAdd database architecture documentation, live pooling coverage, API contract updates, and executable container verification.\n\nBREAKING CHANGE: legacy scheme APIs and flat app.native.wndb module imports are removed.
104 lines
2.7 KiB
Python
104 lines
2.7 KiB
Python
import asyncio
|
|
from contextlib import contextmanager
|
|
from datetime import datetime, timezone
|
|
|
|
from app.infra.db.timescaledb.repositories.realtime import RealtimeRepository
|
|
|
|
|
|
class _FakeCursor:
|
|
def __init__(self):
|
|
self.calls: list[tuple[str, tuple]] = []
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc, tb):
|
|
return False
|
|
|
|
async def execute(self, query, params):
|
|
self.calls.append((str(query), params))
|
|
|
|
async def fetchall(self):
|
|
return []
|
|
|
|
|
|
class _FakeConnection:
|
|
def __init__(self):
|
|
self.cursor_instance = _FakeCursor()
|
|
|
|
def cursor(self):
|
|
return self.cursor_instance
|
|
|
|
|
|
def test_get_links_by_time_range_normalizes_inputs_to_utc():
|
|
conn = _FakeConnection()
|
|
|
|
asyncio.run(
|
|
RealtimeRepository.get_links_by_time_range(
|
|
conn,
|
|
datetime.fromisoformat("2026-06-01T08:00:00+08:00"),
|
|
datetime.fromisoformat("2026-06-01T09:00:00+08:00"),
|
|
)
|
|
)
|
|
|
|
assert len(conn.cursor_instance.calls) == 1
|
|
_, params = conn.cursor_instance.calls[0]
|
|
assert params == (
|
|
datetime(2026, 6, 1, 0, 0, tzinfo=timezone.utc),
|
|
datetime(2026, 6, 1, 1, 0, tzinfo=timezone.utc),
|
|
)
|
|
|
|
|
|
def test_get_nodes_by_time_range_normalizes_inputs_to_utc():
|
|
conn = _FakeConnection()
|
|
|
|
asyncio.run(
|
|
RealtimeRepository.get_nodes_by_time_range(
|
|
conn,
|
|
datetime.fromisoformat("2026-06-01T08:00:00+08:00"),
|
|
datetime.fromisoformat("2026-06-01T09:00:00+08:00"),
|
|
)
|
|
)
|
|
|
|
assert len(conn.cursor_instance.calls) == 1
|
|
_, params = conn.cursor_instance.calls[0]
|
|
assert params == (
|
|
datetime(2026, 6, 1, 0, 0, tzinfo=timezone.utc),
|
|
datetime(2026, 6, 1, 1, 0, tzinfo=timezone.utc),
|
|
)
|
|
|
|
|
|
class _SyncTransactionConnection:
|
|
def __init__(self):
|
|
self.transactions = 0
|
|
|
|
@contextmanager
|
|
def transaction(self):
|
|
self.transactions += 1
|
|
yield
|
|
|
|
|
|
def test_realtime_node_and_link_replacement_share_outer_transaction(monkeypatch):
|
|
conn = _SyncTransactionConnection()
|
|
calls: list[str] = []
|
|
monkeypatch.setattr(
|
|
RealtimeRepository,
|
|
"insert_nodes_batch_sync",
|
|
lambda _conn, _data: calls.append("nodes"),
|
|
)
|
|
monkeypatch.setattr(
|
|
RealtimeRepository,
|
|
"insert_links_batch_sync",
|
|
lambda _conn, _data: calls.append("links"),
|
|
)
|
|
|
|
RealtimeRepository.store_realtime_simulation_result_sync(
|
|
conn,
|
|
[{"node": "N1", "result": [{"pressure": 1.0}]}],
|
|
[{"link": "L1", "result": [{"flow": 2.0}]}],
|
|
"2026-06-01T00:00:00Z",
|
|
)
|
|
|
|
assert conn.transactions == 1
|
|
assert calls == ["nodes", "links"]
|