From a017108d413088b9dfaca61c74fa14d51b175701 Mon Sep 17 00:00:00 2001 From: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:44:16 +0100 Subject: [PATCH] refactor(tests): add temp sqlite helper (#2930) --- tests/README.md | 10 ++++++++ tests/helpers/sqlite_db.py | 29 +++++++++++++++++++++++ tests/test_calendar_rrule.py | 14 ++--------- tests/test_calendar_update_event_tz.py | 14 ++--------- tests/test_replace_messages_multimodal.py | 14 ++--------- 5 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 tests/helpers/sqlite_db.py diff --git a/tests/README.md b/tests/README.md index 03633ae98..d7d6e4399 100644 --- a/tests/README.md +++ b/tests/README.md @@ -72,6 +72,16 @@ Use only for the guarded fake/stub `src.endpoint_resolver` cleanup pattern. cached against them. - Accepts explicit extra dependent module names to evict alongside the defaults. +### `tests.helpers.sqlite_db.make_temp_sqlite` + +Use for the repeated file-backed temp sqlite setup in tests. + +- Only constructs `(SessionLocal, engine, tmpfile)` from the repeated block. +- Does not patch modules and does not clean up the temp file. +- The caller must bind `SessionLocal` explicitly onto whatever module the code + under test reads, and must keep the returned objects alive. +- Do not use it as a general DB fixture framework. + ## What not to abstract yet Some remaining patterns should stay as-is for now rather than being forced into diff --git a/tests/helpers/sqlite_db.py b/tests/helpers/sqlite_db.py new file mode 100644 index 000000000..27002cc0d --- /dev/null +++ b/tests/helpers/sqlite_db.py @@ -0,0 +1,29 @@ +"""Construct a file-backed temp sqlite DB for tests. + +Only builds the SQLAlchemy objects from the repeated temp-sqlite block. It +does not patch modules, manage cleanup, or own any global state — the caller +keeps the returned objects alive and binds ``SessionLocal`` where needed. +""" +import tempfile + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import NullPool + + +def make_temp_sqlite(metadata): + """Build a file-backed temp sqlite database and create its tables. + + Returns ``(SessionLocal, engine, tmpfile)``. The caller must keep these + references alive (temp file and engine GC are the caller's concern) and + bind ``SessionLocal`` onto whatever module the code under test reads. + """ + tmpfile = tempfile.NamedTemporaryFile(suffix=".db", delete=False) + engine = create_engine( + f"sqlite:///{tmpfile.name}", + connect_args={"check_same_thread": False}, + poolclass=NullPool, + ) + metadata.create_all(engine) + SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False) + return SessionLocal, engine, tmpfile diff --git a/tests/test_calendar_rrule.py b/tests/test_calendar_rrule.py index 18d6eaadd..6a14010dc 100644 --- a/tests/test_calendar_rrule.py +++ b/tests/test_calendar_rrule.py @@ -7,29 +7,19 @@ calling do_manage_calendar with an rrule stores a single event carrying that RRU import json import sys -import tempfile import uuid import pytest -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import NullPool from tests.helpers.import_state import clear_fake_database_modules +from tests.helpers.sqlite_db import make_temp_sqlite clear_fake_database_modules() import core.database as cdb from core.database import CalendarEvent -_TMPDB = tempfile.NamedTemporaryFile(suffix=".db", delete=False) -_ENGINE = create_engine( - f"sqlite:///{_TMPDB.name}", - connect_args={"check_same_thread": False}, - poolclass=NullPool, -) -cdb.Base.metadata.create_all(_ENGINE) -_TS = sessionmaker(bind=_ENGINE, autoflush=False, autocommit=False) +_TS, _ENGINE, _TMPDB = make_temp_sqlite(cdb.Base.metadata) @pytest.fixture(autouse=True) diff --git a/tests/test_calendar_update_event_tz.py b/tests/test_calendar_update_event_tz.py index e4c22aa98..1ebbfce56 100644 --- a/tests/test_calendar_update_event_tz.py +++ b/tests/test_calendar_update_event_tz.py @@ -9,25 +9,15 @@ Tokyo user) and left is_utc inconsistent. The do_manage_notes update path was already fixed for the analogous issue. """ import json -import tempfile import uuid import pytest -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import NullPool import core.database as cdb from core.database import CalendarEvent +from tests.helpers.sqlite_db import make_temp_sqlite -_TMPDB = tempfile.NamedTemporaryFile(suffix=".db", delete=False) -_ENGINE = create_engine( - f"sqlite:///{_TMPDB.name}", - connect_args={"check_same_thread": False}, - poolclass=NullPool, -) -cdb.Base.metadata.create_all(_ENGINE) -_TS = sessionmaker(bind=_ENGINE, autoflush=False, autocommit=False) +_TS, _ENGINE, _TMPDB = make_temp_sqlite(cdb.Base.metadata) @pytest.fixture(autouse=True) diff --git a/tests/test_replace_messages_multimodal.py b/tests/test_replace_messages_multimodal.py index ac1558649..c21cd5121 100644 --- a/tests/test_replace_messages_multimodal.py +++ b/tests/test_replace_messages_multimodal.py @@ -10,26 +10,16 @@ back as a corrupted string blob - the attachment was destroyed. The sibling _persist_message json.dumps-es list content; replace_messages did not. """ -import tempfile import uuid import pytest -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker -from sqlalchemy.pool import NullPool import core.database as cdb from core.database import Session as DbSession from core.models import ChatMessage +from tests.helpers.sqlite_db import make_temp_sqlite -_TMPDB = tempfile.NamedTemporaryFile(suffix=".db", delete=False) -_ENGINE = create_engine( - f"sqlite:///{_TMPDB.name}", - connect_args={"check_same_thread": False}, - poolclass=NullPool, -) -cdb.Base.metadata.create_all(_ENGINE) -_TS = sessionmaker(bind=_ENGINE, autoflush=False, autocommit=False) +_TS, _ENGINE, _TMPDB = make_temp_sqlite(cdb.Base.metadata) @pytest.fixture