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