"""Regression coverage for the lifespan reopen path on ``Database``. The production server only runs a single FastAPI lifespan per process, so ``Database.close()`` releasing the connection is fine. Pytest is different: ``ledgrab.main`` is imported once and its module-level ``db`` singleton survives across every TestClient session, each of which closes the connection on shutdown. Before :meth:`Database.ensure_open` existed, the second TestClient session hit ``sqlite3.ProgrammingError: Cannot operate on a closed database`` at fixture-setup time when the lifespan startup tried to read settings (the ``AutoBackupEngine`` constructor was the first reader). These tests pin the close+reopen cycle so the cascade can't silently come back. """ import sqlite3 import pytest from ledgrab.storage.database import Database def test_close_then_ensure_open_reopens_connection(tmp_path): db = Database(tmp_path / "reopen.db") db.set_setting("k", {"v": 1}) db.close() db.ensure_open() assert db.get_setting("k") == {"v": 1} db.close() def test_ensure_open_is_idempotent_when_already_open(tmp_path): db = Database(tmp_path / "idempotent.db") original_conn = db._conn db.ensure_open() # Same live connection — no spurious reconnect when already open. assert db._conn is original_conn db.close() def test_close_is_idempotent(tmp_path): db = Database(tmp_path / "double_close.db") db.close() # Second close must not raise (lifespan shutdown can run twice in # quick test sessions). Connection stays released. db.close() assert db._conn is None def test_operation_after_close_without_reopen_raises(tmp_path): db = Database(tmp_path / "no_reopen.db") db.close() # Without ensure_open, attempting to use the DB fails loudly rather # than silently re-connecting — callers must opt in. with pytest.raises((sqlite3.ProgrammingError, AttributeError)): db.get_setting("anything")