From 0d9c3185bf5df299ac29bb9c24bc754d0c872cde Mon Sep 17 00:00:00 2001 From: NetDevAutomate Date: Fri, 18 Sep 2026 17:43:48 +0100 Subject: [PATCH 1/2] =?UTF-8?q?test(db):=20RED=20=E2=80=94=20per-test=20se?= =?UTF-8?q?ssions.db=20is=20a=20copy=20of=20one=20migrated=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit studyloop.history._connection._connect creates the sessions database from SCHEMA_FILE and runs every migration the first time any caller opens a path that does not exist. Fourteen test modules point STUDYLOOP_DB at a fresh tmp_path / "sessions.db" per test, so each test's first write — usually `plan new` indexing its document — pays that bootstrap: measured here at ~59 ms and a 1 MB, 75-table, WAL file (user_version 48), 241 times across those modules (test_plan_application 49, test_now_plan_guidance 38, test_plan_application_mutations 33, test_cli_plan_seam 29, ...). On a slow runner disk it is the first step to stall: CI run 35350636500 timed out two `plan close` seam tests at 60 s each inside `plan new` creating the database, before `plan close` ran; the module runs in 2.5 s locally. The fix this pins is not a shared live database — test_plan_application's fixture (council review 1, F6) needs "no history" to be a fact about the test, not about what a shared file holds. Each test keeps its own file at its own path; only the bootstrap is shared: one template built once per process through the production _connect, copied per test. Five tests pin the helper: the template has the schema and user_version a genuine first _connect produces; seeding gives distinct files, sets STUDYLOOP_DB, and a first connect on a seeded path never logs "Created sessions DB"; the template is built once (a rebuild stub must not be called); template and copy are a single checkpointed file with no -wal/-shm sidecar; and a lint over tests/ refuses the retired setenv(tmp_path / "sessions.db") pattern so it cannot come back. RED: the module fails at collection (no _sessions_db_template helper). The pyright: ignore[reportMissingImports] tags on the two import lines are the RED pattern used since item 3b; GREEN strips them. --- .../tests/test_sessions_db_template.py | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 packages/studyloop/tests/test_sessions_db_template.py diff --git a/packages/studyloop/tests/test_sessions_db_template.py b/packages/studyloop/tests/test_sessions_db_template.py new file mode 100644 index 000000000..93953eae4 --- /dev/null +++ b/packages/studyloop/tests/test_sessions_db_template.py @@ -0,0 +1,149 @@ +"""The per-test ``sessions.db`` is a copy of one migrated template, not a bootstrap. + +``studyloop.history._connection._connect`` creates the sessions database from +``SCHEMA_FILE`` and runs every migration the first time any caller opens a +path that does not exist. Fourteen test modules pointed ``STUDYLOOP_DB`` at a +fresh ``tmp_path / "sessions.db"`` per test, so each test's first write — +usually ``plan new`` indexing its document — paid that bootstrap (~60 ms and +a 1 MB file locally, 241 times across those modules). On a slow runner disk +that step is the first to stall: CI run 35350636500 timed out two ``plan +close`` seam tests at 60 s each inside ``plan new`` *creating the database*, +before ``plan close`` ran. + +The fix keeps per-test isolation — ``test_plan_application.py`` (council +review 1, F6) needs "no history" to be a fact about the test, not about a +shared database — and removes the bootstrap from every test's critical path: +one template is built once per process through the production ``_connect``, +and each test receives its own copy at its own path. +""" + +from __future__ import annotations + +import os +import re +import sqlite3 +from pathlib import Path + +import _sessions_db_template as template_module # pyright: ignore[reportMissingImports] +from _sessions_db_template import ( # pyright: ignore[reportMissingImports] + seed_sessions_db, + template_path, +) + + +def _schema(db: Path) -> set[tuple[str, str, str]]: + conn = sqlite3.connect(db) + try: + rows = conn.execute( + "SELECT type, name, COALESCE(sql, '') FROM sqlite_master " + "WHERE name NOT LIKE 'sqlite_%' ORDER BY type, name" + ).fetchall() + return {(t, n, s) for t, n, s in rows} + finally: + conn.close() + + +def _user_version(db: Path) -> int: + conn = sqlite3.connect(db) + try: + return conn.execute("PRAGMA user_version").fetchone()[0] + finally: + conn.close() + + +def test_template_is_what_the_production_bootstrap_produces(tmp_path, monkeypatch) -> None: + """Same schema, same user_version as a genuine first ``_connect`` on a fresh path.""" + from agent_session_tools.migrations import CURRENT_VERSION + from studyloop.history import _connection + + fresh = tmp_path / "fresh" / "sessions.db" + monkeypatch.setenv("STUDYLOOP_DB", str(fresh)) + monkeypatch.setenv("STUDYLOOP_CONFIG", str(tmp_path / "no-config.yaml")) + conn = _connection._connect() + assert conn is not None + conn.close() + + template = template_path() + assert template.is_file() + assert _user_version(template) == CURRENT_VERSION == _user_version(fresh) + assert _schema(template) == _schema(fresh) + + +def test_seed_gives_each_test_its_own_file_and_skips_the_bootstrap( + tmp_path, monkeypatch, caplog +) -> None: + from studyloop.history import _connection + + first = seed_sessions_db(tmp_path / "a" / "sessions.db", monkeypatch) + second = seed_sessions_db(tmp_path / "b" / "sessions.db", monkeypatch) + + assert first != second and first.is_file() and second.is_file() + assert os.environ["STUDYLOOP_DB"] == str(second), "the last seed wins the env var" + + with caplog.at_level("INFO", logger="studyloop.history._connection"): + conn = _connection._connect() + assert conn is not None + conn.execute( + "INSERT INTO study_sessions (id, topic, started_at) VALUES ('s1', 'sql', '2026-01-01')" + ) + conn.commit() + conn.close() + assert "Created sessions DB" not in caplog.text, "a seeded path must not be bootstrapped" + + other = sqlite3.connect(first) + try: + assert other.execute("SELECT count(*) FROM study_sessions").fetchone()[0] == 0, ( + "a write to one test's database must not be visible from another's" + ) + finally: + other.close() + + +def test_template_is_built_once_per_process(monkeypatch) -> None: + template_path() # ensure built + + def must_not_rebuild(): # pragma: no cover - the assertion is that it is never called + raise AssertionError("template rebuilt") + + monkeypatch.setattr(template_module, "_build", must_not_rebuild) + assert template_path() == template_path() + + +def test_seed_copies_a_single_checkpointed_file(tmp_path, monkeypatch) -> None: + """No ``-wal`` / ``-shm`` sidecar travels with the template or the copy.""" + template = template_path() + assert sorted(p.name for p in template.parent.iterdir()) == ["sessions.db"] + + dest = seed_sessions_db(tmp_path / "sessions.db", monkeypatch) + assert sorted(p.name for p in dest.parent.glob("sessions.db*")) == ["sessions.db"] + conn = sqlite3.connect(dest) + try: + assert conn.execute("PRAGMA journal_mode").fetchone()[0] == "wal" + assert conn.execute("PRAGMA integrity_check").fetchone()[0] == "ok" + finally: + conn.close() + + +_FRESH_PATH_PATTERN = re.compile( + r'setenv\(\s*"STUDYLOOP_DB"\s*,\s*str\(tmp_path\s*/\s*"sessions\.db"\)' +) + + +def test_no_test_module_bootstraps_a_fresh_sessions_db_per_test() -> None: + """Lint: the pattern this module retires must not come back. + + A module that needs a database at ``tmp_path / "sessions.db"`` seeds it + with :func:`seed_sessions_db`; a module that builds its own database (a + drifted FTS index, a scope fixture) uses its own file name and is not + matched. + """ + tests_dir = Path(__file__).parent + offenders = sorted( + str(path.relative_to(tests_dir)) + for path in tests_dir.rglob("test_*.py") + if path != Path(__file__) and _FRESH_PATH_PATTERN.search(path.read_text(encoding="utf-8")) + ) + assert offenders == [], ( + "these modules bootstrap a fresh sessions.db per test; " + f"seed it from the template instead: {offenders}" + ) From a5b9f9038a5470a19822045b20e8138daf68452f Mon Sep 17 00:00:00 2001 From: NetDevAutomate Date: Fri, 18 Sep 2026 17:56:44 +0100 Subject: [PATCH 2/2] =?UTF-8?q?test(db):=20seed=20each=20test's=20sessions?= =?UTF-8?q?.db=20from=20one=20migrated=20template=20=E2=80=94=20GREEN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every test that pointed STUDYLOOP_DB at a fresh tmp_path / "sessions.db" paid the production bootstrap on its first write: history._connection ._connect created the file from SCHEMA_FILE, ran all 48 migrations and converted it to WAL — ~59 ms and a 1 MB, 75-table file, 241 times across the 20 modules that set the variable (measured by counting the "Created sessions DB" log line). On a slow runner disk that is the first step to stall: CI run 35350636500 timed out two `plan close` seam tests at 60 s each inside `plan new` creating the database, before `plan close` ran. tests/_sessions_db_template.py builds one template per process through the real _connect (under a MonkeyPatch context that also points STUDYLOOP_CONFIG at an absent file, so a config's own session_db key — which outranks the env var by design — cannot route the build elsewhere), closes the connection so SQLite checkpoints away the -wal/-shm sidecars, and seed_sessions_db(path, monkeypatch) copies that one file to the test's own path and sets STUDYLOOP_DB. The 14 modules that used the fresh path swap the setenv line for that call; the 6 that build their own database (drifted FTS, scope fixtures, records.connect) are unchanged. Not a shared live database: test_plan_application's fixture (council review 1, F6) needs "no history" to be a fact about the test, not about what a shared file holds. Each test still gets its own file; only the bootstrap is shared, and the guard test pins that a write to one test's copy is invisible from another's. Measured on the same 20-file set: 37.57 s -> 22.84 s, 241 bootstraps -> 1; test_cli_plan_seam.py alone 2.50 s -> 0.97 s. Guard 5/5 (template schema and user_version equal a genuine fresh bootstrap; seeded first connect never logs "Created sessions DB"; built once; single checkpointed file; lint over tests/ refuses the retired pattern). test_mcp_next_action.py, which imports isolate_now_world, 10/10. ruff, format, pyright clean. Full suite 30 failed / 5117 passed / 14 errors; the 44 failed+errored ids are byte-identical to the item-4 control's committed environmental set (run - control = empty, control - run = empty). --- .../studyloop/tests/_sessions_db_template.py | 79 +++++++++++++++++++ .../tests/test_agent_prompt_contract.py | 3 +- packages/studyloop/tests/test_cli_doctor.py | 3 +- .../studyloop/tests/test_cli_plan_seam.py | 3 +- .../tests/test_mcp_plan_record_seam.py | 3 +- .../studyloop/tests/test_mcp_plan_tools.py | 3 +- .../studyloop/tests/test_now_plan_guidance.py | 3 +- .../studyloop/tests/test_plan_application.py | 3 +- .../tests/test_plan_application_mutations.py | 3 +- .../studyloop/tests/test_plan_guidance.py | 3 +- .../tests/test_plan_intent_snapshots.py | 3 +- .../tests/test_plan_journey_combined.py | 3 +- .../tests/test_plan_recording_failures.py | 5 +- .../tests/test_session_start_purpose.py | 3 +- .../tests/test_sessions_db_template.py | 7 +- .../studyloop/tests/test_web_plans_seam.py | 3 +- 16 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 packages/studyloop/tests/_sessions_db_template.py diff --git a/packages/studyloop/tests/_sessions_db_template.py b/packages/studyloop/tests/_sessions_db_template.py new file mode 100644 index 000000000..ab7e8576f --- /dev/null +++ b/packages/studyloop/tests/_sessions_db_template.py @@ -0,0 +1,79 @@ +"""One migrated ``sessions.db`` template per process, copied per test. + +``studyloop.history._connection._connect`` creates the sessions database from +``SCHEMA_FILE`` and runs every migration the first time any caller opens a +path that does not exist yet. A test module that points ``STUDYLOOP_DB`` at a +fresh ``tmp_path / "sessions.db"`` therefore pays that bootstrap on its first +write — ~60 ms and a 1 MB, 75-table file per test on a laptop, and on a slow +runner disk the first step to stall (CI run 35350636500 timed out two seam +tests inside ``plan new`` creating the database). Seeding the path from a +template turns the bootstrap into one buffered file copy. + +This is deliberately *not* a shared live database. ``test_plan_application.py`` +(council review 1, F6) needs "no history" to be a fact about the test rather +than about what a shared file holds, so every test still gets its own file at +its own path; only the schema bootstrap is shared. + +The template is built through the production ``_connect`` — not a copy of its +steps — so a seeded database is what a genuine first connect would have +produced (``test_sessions_db_template.py`` pins the schema and +``user_version`` against a fresh bootstrap). ``connect_db`` sets +``journal_mode=WAL`` on it; the connection is closed before the first copy so +SQLite has checkpointed and removed the ``-wal``/``-shm`` sidecars, and the +main file is the whole database. + +Kept out of ``conftest.py`` for the same reason ``_readiness.py`` and +``_vault_isolation.py`` are: the helper is the subject of a guard test, and a +test cannot import symbols from a conftest in a way a type checker resolves. +""" + +from __future__ import annotations + +import atexit +import shutil +import tempfile +from pathlib import Path + +import pytest + +_TEMPLATE: Path | None = None + + +def _build(destination: Path) -> Path: + """Bootstrap ``destination`` exactly as the first production connect would.""" + from studyloop.history import _connection + + with pytest.MonkeyPatch.context() as env: + env.setenv("STUDYLOOP_DB", str(destination)) + # An absent config file falls through to defaults, so the config's own + # ``session_db`` key — which outranks STUDYLOOP_DB by design — cannot + # route the bootstrap at a test's or the learner's database. + env.setenv("STUDYLOOP_CONFIG", str(destination.parent / "no-config.yaml")) + conn = _connection._connect() + if conn is None: # pragma: no cover - agent-session-tools is a hard dependency here + raise RuntimeError("could not bootstrap the sessions.db template") + conn.close() + return destination + + +def template_path() -> Path: + """The process-wide migrated template; built on first use.""" + global _TEMPLATE + if _TEMPLATE is None: + root = Path(tempfile.mkdtemp(prefix="studyloop-test-sessions-template-")) + atexit.register(shutil.rmtree, root, ignore_errors=True) + _TEMPLATE = _build(root / "sessions.db") + return _TEMPLATE + + +def seed_sessions_db(destination: Path, monkeypatch: pytest.MonkeyPatch) -> Path: + """Copy the template to ``destination`` and point ``STUDYLOOP_DB`` at it. + + The one-line replacement for ``monkeypatch.setenv("STUDYLOOP_DB", + str(tmp_path / "sessions.db"))``: same path, same isolation, no bootstrap + on the test's first write. + """ + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(template_path(), destination) + monkeypatch.setenv("STUDYLOOP_DB", str(destination)) + return destination diff --git a/packages/studyloop/tests/test_agent_prompt_contract.py b/packages/studyloop/tests/test_agent_prompt_contract.py index 6fecede4a..f18ca00ff 100644 --- a/packages/studyloop/tests/test_agent_prompt_contract.py +++ b/packages/studyloop/tests/test_agent_prompt_contract.py @@ -19,6 +19,7 @@ import click import pytest import typer +from _sessions_db_template import seed_sessions_db from typer.testing import CliRunner runner = CliRunner() @@ -455,7 +456,7 @@ def test_bridge_add_persists_to_the_database_not_config_yaml(tmp_path, monkeypat ) before = config_path.read_bytes() monkeypatch.setenv("STUDYLOOP_CONFIG", str(config_path)) - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) result = click_runner.invoke( cli, diff --git a/packages/studyloop/tests/test_cli_doctor.py b/packages/studyloop/tests/test_cli_doctor.py index d27f0a18a..ce1a49a23 100644 --- a/packages/studyloop/tests/test_cli_doctor.py +++ b/packages/studyloop/tests/test_cli_doctor.py @@ -7,6 +7,7 @@ import click import pytest +from _sessions_db_template import seed_sessions_db from click.testing import CliRunner from studyloop.doctor.models import CheckResult @@ -201,7 +202,7 @@ def _isolated_plans(self, tmp_path, monkeypatch): from studyloop.planning import store monkeypatch.setenv(store.PLANS_DIR_ENV, str(tmp_path / "study-plans")) - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) self.plans_dir = store.plans_dir() def test_doctor_names_each_active_but_unready_plan_with_its_blockers(self) -> None: diff --git a/packages/studyloop/tests/test_cli_plan_seam.py b/packages/studyloop/tests/test_cli_plan_seam.py index 070cdb649..da3bd97f3 100644 --- a/packages/studyloop/tests/test_cli_plan_seam.py +++ b/packages/studyloop/tests/test_cli_plan_seam.py @@ -25,6 +25,7 @@ import re import pytest +from _sessions_db_template import seed_sessions_db from click.testing import CliRunner from studyloop.cli import cli @@ -51,7 +52,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): @pytest.fixture(autouse=True) def isolated_checkpoint_db(tmp_path, monkeypatch): - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) @pytest.fixture diff --git a/packages/studyloop/tests/test_mcp_plan_record_seam.py b/packages/studyloop/tests/test_mcp_plan_record_seam.py index 4e7eadc7b..f77521006 100644 --- a/packages/studyloop/tests/test_mcp_plan_record_seam.py +++ b/packages/studyloop/tests/test_mcp_plan_record_seam.py @@ -15,6 +15,7 @@ pytest.importorskip("mcp") +from _sessions_db_template import seed_sessions_db from mcp.server.fastmcp.exceptions import ToolError from studyloop.planning import ( @@ -38,7 +39,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): def isolated_checkpoint_db(tmp_path, monkeypatch): """``store.create_plan`` refreshes the derived index in the sessions database; keep that off any developer database (council review 2, F10).""" - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) def _tool(): diff --git a/packages/studyloop/tests/test_mcp_plan_tools.py b/packages/studyloop/tests/test_mcp_plan_tools.py index 489c27ddd..f37ddd990 100644 --- a/packages/studyloop/tests/test_mcp_plan_tools.py +++ b/packages/studyloop/tests/test_mcp_plan_tools.py @@ -42,6 +42,7 @@ pytest.importorskip("mcp") +from _sessions_db_template import seed_sessions_db from mcp.server.fastmcp.exceptions import ToolError from studyloop.planning import ( @@ -121,7 +122,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): def isolated_checkpoint_db(tmp_path, monkeypatch): """``store.create_plan`` refreshes the derived index in the sessions database; keep that off any developer database (council review 2, F10).""" - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) @pytest.fixture diff --git a/packages/studyloop/tests/test_now_plan_guidance.py b/packages/studyloop/tests/test_now_plan_guidance.py index 74d26728e..2bd3d24df 100644 --- a/packages/studyloop/tests/test_now_plan_guidance.py +++ b/packages/studyloop/tests/test_now_plan_guidance.py @@ -28,6 +28,7 @@ from typing import TYPE_CHECKING import pytest +from _sessions_db_template import seed_sessions_db from studyloop.learning import decision from studyloop.learning.decision import PlanRef, _Candidate, build_now_plan @@ -76,7 +77,7 @@ def isolate_now_world(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: encoding="utf-8", ) monkeypatch.setenv("STUDYLOOP_CONFIG", str(config)) - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) monkeypatch.setenv("STUDYLOOP_STATE_DIR", str(tmp_path / "state")) monkeypatch.setenv(store.PLANS_DIR_ENV, str(tmp_path / "study-plans")) monkeypatch.setattr(decision, "datetime", _FrozenDatetime) diff --git a/packages/studyloop/tests/test_plan_application.py b/packages/studyloop/tests/test_plan_application.py index dd3053bf5..b8fbe5bfc 100644 --- a/packages/studyloop/tests/test_plan_application.py +++ b/packages/studyloop/tests/test_plan_application.py @@ -17,6 +17,7 @@ import json import pytest +from _sessions_db_template import seed_sessions_db from studyloop.planning import store from studyloop.planning.application import PlanApplication @@ -49,7 +50,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): def isolated_checkpoint_db(tmp_path, monkeypatch): """A fresh checkpoint database per test, so "no history" is a fact about this test rather than about what the suite's shared database holds (F6).""" - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) return tmp_path / "sessions.db" diff --git a/packages/studyloop/tests/test_plan_application_mutations.py b/packages/studyloop/tests/test_plan_application_mutations.py index d671ceeff..2e4db03b5 100644 --- a/packages/studyloop/tests/test_plan_application_mutations.py +++ b/packages/studyloop/tests/test_plan_application_mutations.py @@ -23,6 +23,7 @@ import json import pytest +from _sessions_db_template import seed_sessions_db from studyloop.planning import index as index_module from studyloop.planning import store @@ -63,7 +64,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): @pytest.fixture(autouse=True) def isolated_checkpoint_db(tmp_path, monkeypatch): """A fresh checkpoint database per test (council review 1, F6).""" - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) return tmp_path / "sessions.db" diff --git a/packages/studyloop/tests/test_plan_guidance.py b/packages/studyloop/tests/test_plan_guidance.py index f61d5a7a1..58c47b7aa 100644 --- a/packages/studyloop/tests/test_plan_guidance.py +++ b/packages/studyloop/tests/test_plan_guidance.py @@ -21,6 +21,7 @@ from datetime import UTC, date, datetime, timedelta import pytest +from _sessions_db_template import seed_sessions_db from studyloop.planning import store from studyloop.planning.application import PlanApplication @@ -44,7 +45,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): @pytest.fixture(autouse=True) def isolated_checkpoint_db(tmp_path, monkeypatch): - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) @pytest.fixture diff --git a/packages/studyloop/tests/test_plan_intent_snapshots.py b/packages/studyloop/tests/test_plan_intent_snapshots.py index 9aadb0527..b05564e1b 100644 --- a/packages/studyloop/tests/test_plan_intent_snapshots.py +++ b/packages/studyloop/tests/test_plan_intent_snapshots.py @@ -18,6 +18,7 @@ from collections.abc import Mapping import pytest +from _sessions_db_template import seed_sessions_db from studyloop.planning import CreatePlan, PlanApplication, store @@ -25,7 +26,7 @@ @pytest.fixture(autouse=True) def isolated_world(tmp_path, monkeypatch): monkeypatch.setenv(store.PLANS_DIR_ENV, str(tmp_path / "study-plans")) - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) def _answers() -> dict[str, object]: diff --git a/packages/studyloop/tests/test_plan_journey_combined.py b/packages/studyloop/tests/test_plan_journey_combined.py index 1c04c51f3..6c90e7a83 100644 --- a/packages/studyloop/tests/test_plan_journey_combined.py +++ b/packages/studyloop/tests/test_plan_journey_combined.py @@ -46,6 +46,7 @@ pytest.importorskip("fastapi") pytest.importorskip("mcp") +from _sessions_db_template import seed_sessions_db from fastapi.testclient import TestClient # pyright: ignore[reportMissingImports] from studyloop.planning import store @@ -111,7 +112,7 @@ def plans_dir(tmp_path, monkeypatch) -> Path: """One plans directory for BOTH the Web routes and the MCP tools — the point of the journey is that they see the same documents.""" monkeypatch.setenv(store.PLANS_DIR_ENV, str(tmp_path / "study-plans")) - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) return tmp_path / "study-plans" diff --git a/packages/studyloop/tests/test_plan_recording_failures.py b/packages/studyloop/tests/test_plan_recording_failures.py index 971337242..333a1f9dc 100644 --- a/packages/studyloop/tests/test_plan_recording_failures.py +++ b/packages/studyloop/tests/test_plan_recording_failures.py @@ -15,6 +15,7 @@ from __future__ import annotations import pytest +from _sessions_db_template import seed_sessions_db from studyloop.planning import evaluation as evaluation_module from studyloop.planning import index as index_module @@ -33,8 +34,8 @@ def isolated_plans_dir(tmp_path, monkeypatch): @pytest.fixture(autouse=True) def isolated_checkpoint_db(tmp_path, monkeypatch): - """A fresh sessions database per test; the schema is created on first connect.""" - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + """A fresh sessions database per test, seeded from the migrated template.""" + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) return tmp_path / "sessions.db" diff --git a/packages/studyloop/tests/test_session_start_purpose.py b/packages/studyloop/tests/test_session_start_purpose.py index 839aafc52..65c0ae66d 100644 --- a/packages/studyloop/tests/test_session_start_purpose.py +++ b/packages/studyloop/tests/test_session_start_purpose.py @@ -30,6 +30,7 @@ pytest.importorskip("fastapi") +from _sessions_db_template import seed_sessions_db from fastapi.testclient import TestClient # pyright: ignore[reportMissingImports] from studyloop.planning import store @@ -112,7 +113,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): @pytest.fixture(autouse=True) def _isolated_db(tmp_path, monkeypatch): - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) @pytest.fixture() diff --git a/packages/studyloop/tests/test_sessions_db_template.py b/packages/studyloop/tests/test_sessions_db_template.py index 93953eae4..570d8287f 100644 --- a/packages/studyloop/tests/test_sessions_db_template.py +++ b/packages/studyloop/tests/test_sessions_db_template.py @@ -24,11 +24,8 @@ import sqlite3 from pathlib import Path -import _sessions_db_template as template_module # pyright: ignore[reportMissingImports] -from _sessions_db_template import ( # pyright: ignore[reportMissingImports] - seed_sessions_db, - template_path, -) +import _sessions_db_template as template_module +from _sessions_db_template import seed_sessions_db, template_path def _schema(db: Path) -> set[tuple[str, str, str]]: diff --git a/packages/studyloop/tests/test_web_plans_seam.py b/packages/studyloop/tests/test_web_plans_seam.py index 4abfa98f9..bf61296a6 100644 --- a/packages/studyloop/tests/test_web_plans_seam.py +++ b/packages/studyloop/tests/test_web_plans_seam.py @@ -20,6 +20,7 @@ pytest.importorskip("fastapi") +from _sessions_db_template import seed_sessions_db from fastapi.testclient import TestClient from studyloop.planning import PlanApplication, store @@ -35,7 +36,7 @@ def isolated_plans_dir(tmp_path, monkeypatch): @pytest.fixture(autouse=True) def isolated_checkpoint_db(tmp_path, monkeypatch): - monkeypatch.setenv("STUDYLOOP_DB", str(tmp_path / "sessions.db")) + seed_sessions_db(tmp_path / "sessions.db", monkeypatch) @pytest.fixture