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.
153 lines
4.4 KiB
Python
153 lines
4.4 KiB
Python
from contextlib import contextmanager
|
|
|
|
import pytest
|
|
|
|
from app.infra.db.timescaledb import sync_pool
|
|
|
|
|
|
class _FakePool:
|
|
def __init__(self, *, conninfo, **_kwargs):
|
|
self.conninfo = conninfo
|
|
self.closed = False
|
|
self.borrowed = 0
|
|
self.returned = 0
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
@contextmanager
|
|
def connection(self):
|
|
self.borrowed += 1
|
|
try:
|
|
yield object()
|
|
finally:
|
|
self.returned += 1
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_pools():
|
|
sync_pool._pools.clear()
|
|
sync_pool._pool_conninfo.clear()
|
|
sync_pool._pool_borrows.clear()
|
|
yield
|
|
sync_pool._pools.clear()
|
|
sync_pool._pool_conninfo.clear()
|
|
sync_pool._pool_borrows.clear()
|
|
|
|
|
|
def test_pool_reuses_same_routed_timescale_dsn(monkeypatch):
|
|
monkeypatch.setattr(sync_pool, "ConnectionPool", _FakePool)
|
|
monkeypatch.setattr(
|
|
sync_pool,
|
|
"get_project_timescale_pgconn_string",
|
|
lambda *, db_name: f"dbname={db_name}",
|
|
)
|
|
|
|
first = sync_pool.get_timescale_pool("tjwater_next")
|
|
second = sync_pool.get_timescale_pool("tjwater_next")
|
|
|
|
assert first is second
|
|
|
|
|
|
def test_pool_rebuilds_after_routing_change(monkeypatch):
|
|
monkeypatch.setattr(sync_pool, "ConnectionPool", _FakePool)
|
|
dsn = {"value": "host=old dbname=tjwater_next"}
|
|
monkeypatch.setattr(
|
|
sync_pool,
|
|
"get_project_timescale_pgconn_string",
|
|
lambda *, db_name: dsn["value"],
|
|
)
|
|
|
|
old = sync_pool.get_timescale_pool("tjwater_next")
|
|
dsn["value"] = "host=new dbname=tjwater_next"
|
|
new = sync_pool.get_timescale_pool("tjwater_next")
|
|
|
|
assert old.closed is True
|
|
assert new is not old
|
|
|
|
|
|
def test_connection_is_returned_to_pool(monkeypatch):
|
|
monkeypatch.setattr(sync_pool, "ConnectionPool", _FakePool)
|
|
monkeypatch.setattr(
|
|
sync_pool,
|
|
"get_project_timescale_pgconn_string",
|
|
lambda *, db_name: f"dbname={db_name}",
|
|
)
|
|
|
|
pool = sync_pool.get_timescale_pool("tjwater_next")
|
|
with sync_pool.timescale_connection("tjwater_next"):
|
|
assert pool.borrowed == 1
|
|
assert pool.returned == 0
|
|
assert pool.returned == 1
|
|
|
|
|
|
def test_close_removes_pool(monkeypatch):
|
|
monkeypatch.setattr(sync_pool, "ConnectionPool", _FakePool)
|
|
monkeypatch.setattr(
|
|
sync_pool,
|
|
"get_project_timescale_pgconn_string",
|
|
lambda *, db_name: f"dbname={db_name}",
|
|
)
|
|
|
|
pool = sync_pool.get_timescale_pool("tjwater_next")
|
|
sync_pool.close_timescale_pool("tjwater_next")
|
|
|
|
assert pool.closed is True
|
|
assert "tjwater_next" not in sync_pool._pools
|
|
|
|
|
|
def test_close_all_removes_every_pool(monkeypatch):
|
|
monkeypatch.setattr(sync_pool, "ConnectionPool", _FakePool)
|
|
monkeypatch.setattr(
|
|
sync_pool,
|
|
"get_project_timescale_pgconn_string",
|
|
lambda *, db_name: f"dbname={db_name}",
|
|
)
|
|
|
|
first = sync_pool.get_timescale_pool("first")
|
|
second = sync_pool.get_timescale_pool("second")
|
|
sync_pool.close_all_timescale_pools()
|
|
|
|
assert first.closed is True
|
|
assert second.closed is True
|
|
assert sync_pool._pools == {}
|
|
|
|
|
|
def test_pool_cache_evicts_least_recently_used_idle_pool(monkeypatch):
|
|
monkeypatch.setattr(sync_pool, "ConnectionPool", _FakePool)
|
|
monkeypatch.setattr(sync_pool.settings, "PROJECT_TS_CACHE_SIZE", 2)
|
|
monkeypatch.setattr(
|
|
sync_pool,
|
|
"get_project_timescale_pgconn_string",
|
|
lambda *, db_name: f"dbname={db_name}",
|
|
)
|
|
|
|
first = sync_pool.get_timescale_pool("first")
|
|
second = sync_pool.get_timescale_pool("second")
|
|
sync_pool.get_timescale_pool("first")
|
|
third = sync_pool.get_timescale_pool("third")
|
|
|
|
assert list(sync_pool._pools) == ["first", "third"]
|
|
assert second.closed is True
|
|
assert first.closed is False
|
|
assert third.closed is False
|
|
|
|
|
|
def test_pool_cache_does_not_evict_active_pool(monkeypatch):
|
|
monkeypatch.setattr(sync_pool, "ConnectionPool", _FakePool)
|
|
monkeypatch.setattr(sync_pool.settings, "PROJECT_TS_CACHE_SIZE", 1)
|
|
monkeypatch.setattr(
|
|
sync_pool,
|
|
"get_project_timescale_pgconn_string",
|
|
lambda *, db_name: f"dbname={db_name}",
|
|
)
|
|
|
|
with sync_pool.timescale_connection("active"):
|
|
active = sync_pool._pools["active"]
|
|
sync_pool.get_timescale_pool("new")
|
|
assert active.closed is False
|
|
assert set(sync_pool._pools) == {"active", "new"}
|
|
|
|
assert list(sync_pool._pools) == ["new"]
|
|
assert active.closed is True
|