"""Tests for SQLite connection pragmas and runtime behavior.""" import sqlite3 import atocore.config as config from atocore.models.database import get_connection, init_db def test_get_connection_applies_busy_timeout_and_wal(tmp_path, monkeypatch): monkeypatch.setenv("ATOCORE_DATA_DIR", str(tmp_path / "data")) monkeypatch.setenv("ATOCORE_DB_BUSY_TIMEOUT_MS", "7000") original_settings = config.settings try: config.settings = config.Settings() init_db() with get_connection() as conn: busy_timeout = conn.execute("PRAGMA busy_timeout").fetchone()[0] journal_mode = conn.execute("PRAGMA journal_mode").fetchone()[0] foreign_keys = conn.execute("PRAGMA foreign_keys").fetchone()[0] finally: config.settings = original_settings assert busy_timeout == 7000 assert str(journal_mode).lower() == "wal" assert foreign_keys == 1 def test_get_connection_uses_configured_timeout_value(tmp_path, monkeypatch): monkeypatch.setenv("ATOCORE_DATA_DIR", str(tmp_path / "data")) monkeypatch.setenv("ATOCORE_DB_BUSY_TIMEOUT_MS", "2500") original_settings = config.settings original_connect = sqlite3.connect calls = [] def fake_connect(*args, **kwargs): calls.append(kwargs.get("timeout")) return original_connect(*args, **kwargs) try: config.settings = config.Settings() monkeypatch.setattr("atocore.models.database.sqlite3.connect", fake_connect) init_db() finally: config.settings = original_settings assert calls assert calls[0] == 2.5 def test_init_db_upgrades_pre_phase9_schema_without_failing(tmp_path, monkeypatch): """Regression test for the schema init ordering bug caught during the first real Dalidou deploy (report from 2026-04-08). Before the fix, SCHEMA_SQL contained CREATE INDEX statements that referenced columns (memories.project, interactions.project, interactions.session_id) added by _apply_migrations later in init_db. On a fresh install this worked because CREATE TABLE created the tables with the new columns before the CREATE INDEX ran, but on UPGRADE from a pre-Phase-9 schema the CREATE TABLE IF NOT EXISTS was a no-op and the CREATE INDEX hit OperationalError: no such column. This test seeds the tables with the OLD pre-Phase-9 shape then calls init_db() and verifies that: - init_db does not raise - The new columns were added via _apply_migrations - The new indexes exist If the bug is reintroduced by moving a CREATE INDEX for a migration column back into SCHEMA_SQL, this test will fail with OperationalError before reaching the assertions. """ monkeypatch.setenv("ATOCORE_DATA_DIR", str(tmp_path / "data")) original_settings = config.settings try: config.settings = config.Settings() # Step 1: create the data dir and open a direct connection config.ensure_runtime_dirs() db_path = config.settings.db_path # Step 2: seed the DB with the old pre-Phase-9 shape. No # project/last_referenced_at/reference_count on memories; no # project/client/session_id/response/memories_used/chunks_used # on interactions. We also need the prerequisite tables # (projects, source_documents, source_chunks) because the # memories table has an FK to source_chunks. with sqlite3.connect(str(db_path)) as conn: conn.executescript( """ CREATE TABLE source_documents ( id TEXT PRIMARY KEY, file_path TEXT UNIQUE NOT NULL, file_hash TEXT NOT NULL, title TEXT, doc_type TEXT DEFAULT 'markdown', tags TEXT DEFAULT '[]', ingested_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE source_chunks ( id TEXT PRIMARY KEY, document_id TEXT NOT NULL REFERENCES source_documents(id) ON DELETE CASCADE, chunk_index INTEGER NOT NULL, content TEXT NOT NULL, heading_path TEXT DEFAULT '', char_count INTEGER NOT NULL, metadata TEXT DEFAULT '{}', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE memories ( id TEXT PRIMARY KEY, memory_type TEXT NOT NULL, content TEXT NOT NULL, source_chunk_id TEXT REFERENCES source_chunks(id), confidence REAL DEFAULT 1.0, status TEXT DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE projects ( id TEXT PRIMARY KEY, name TEXT UNIQUE NOT NULL, description TEXT DEFAULT '', status TEXT DEFAULT 'active', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE interactions ( id TEXT PRIMARY KEY, prompt TEXT NOT NULL, context_pack TEXT DEFAULT '{}', response_summary TEXT DEFAULT '', project_id TEXT REFERENCES projects(id), created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); """ ) conn.commit() # Step 3: call init_db — this used to raise on the upgrade # path. After the fix it should succeed. init_db() # Step 4: verify the migrations ran — Phase 9 columns present with sqlite3.connect(str(db_path)) as conn: conn.row_factory = sqlite3.Row memories_cols = { row["name"] for row in conn.execute("PRAGMA table_info(memories)") } interactions_cols = { row["name"] for row in conn.execute("PRAGMA table_info(interactions)") } assert "project" in memories_cols assert "last_referenced_at" in memories_cols assert "reference_count" in memories_cols assert "project" in interactions_cols assert "client" in interactions_cols assert "session_id" in interactions_cols assert "response" in interactions_cols assert "memories_used" in interactions_cols assert "chunks_used" in interactions_cols # Step 5: verify the indexes on migration columns exist index_rows = conn.execute( "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name IN ('memories','interactions')" ).fetchall() index_names = {row["name"] for row in index_rows} assert "idx_memories_project" in index_names assert "idx_interactions_project_name" in index_names assert "idx_interactions_session" in index_names finally: config.settings = original_settings