Coverage for tests/test_db_engine.py: 100%
39 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 22:05 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 22:05 +0000
1"""
2Connection-pool hardening checks.
4These do not need a live database: the engine is built lazily, and the one
5test that connects short-circuits libpq with a ``do_connect`` handler so we
6can read back the parameters psycopg would have been handed.
7"""
9import pytest
10from sqlalchemy import create_engine, event
12from bbconf.db_utils import CONNECT_ARGS, POOL_RECYCLE_SECONDS, BaseEngine
14KEEPALIVE_KEYS = {
15 "connect_timeout",
16 "keepalives",
17 "keepalives_idle",
18 "keepalives_interval",
19 "keepalives_count",
20 "tcp_user_timeout",
21}
24def test_connect_args_cover_dead_peer_detection():
25 assert KEEPALIVE_KEYS <= set(CONNECT_ARGS)
26 assert CONNECT_ARGS["keepalives"] == 1
27 # keepalives_idle + keepalives_interval * keepalives_count: a dead idle
28 # peer must be noticed in about a minute, not the ~2 h Linux default.
29 detection = CONNECT_ARGS["keepalives_idle"] + (
30 CONNECT_ARGS["keepalives_interval"] * CONNECT_ARGS["keepalives_count"]
31 )
32 assert detection <= 120
33 assert CONNECT_ARGS["connect_timeout"] <= 10
34 assert CONNECT_ARGS["tcp_user_timeout"] <= 60_000
37def test_base_engine_hardens_the_pool(monkeypatch):
38 """BaseEngine must build its engine with pre-ping, recycle, and keepalives."""
39 captured = {}
41 def fake_create_engine(dsn, **kwargs):
42 captured["dsn"] = dsn
43 captured["kwargs"] = kwargs
44 return create_engine("postgresql+psycopg://u:p@localhost:5432/db", **kwargs)
46 monkeypatch.setattr("bbconf.db_utils.create_engine", fake_create_engine)
47 monkeypatch.setattr(BaseEngine, "create_schema", lambda self, engine=None: None)
48 monkeypatch.setattr(BaseEngine, "check_db_connection", lambda self: None)
50 base = BaseEngine(host="localhost", database="bedbase", user="u", password="p")
52 assert captured["kwargs"]["pool_pre_ping"] is True
53 assert captured["kwargs"]["pool_recycle"] == POOL_RECYCLE_SECONDS
54 assert KEEPALIVE_KEYS <= set(captured["kwargs"]["connect_args"])
56 # And the settings survive onto the live pool object.
57 assert base.engine.pool._pre_ping is True
58 assert base.engine.pool._recycle == POOL_RECYCLE_SECONDS
61def test_connect_args_reach_the_driver():
62 """The libpq keywords are actually handed to psycopg, not silently dropped."""
63 engine = create_engine(
64 "postgresql+psycopg://u:p@localhost:5432/db",
65 connect_args=dict(CONNECT_ARGS),
66 )
68 seen = {}
70 class _Stop(Exception):
71 pass
73 @event.listens_for(engine, "do_connect")
74 def _capture(dialect, conn_rec, cargs, cparams):
75 seen.update(cparams)
76 raise _Stop()
78 with pytest.raises(Exception):
79 engine.connect()
81 for key, value in CONNECT_ARGS.items():
82 assert seen[key] == value