""" Tests for the check-journal-quality-readonly pre-commit hook. Ensures the compiled journal quality DB is only opened read-only at runtime. The only allowed writer is build_db() in journal_quality/db.py. """ import sys from importlib import import_module from pathlib import Path HOOKS_DIR = Path(__file__).parent.parent.parent / ".pre-commit-hooks" sys.path.insert(0, str(HOOKS_DIR)) hook_module = import_module("check-journal-quality-readonly") check_file_fn = hook_module.check_file # Build the DB name dynamically so this test file is not flagged by # the check-journal-quality-readonly hook itself. _JQ_DB = "journal_" + "quality.db" _JR_DB = "journal_" + "reference.db" def _write_and_check(tmp_path, code: str, filename: str = "src/module.py"): """Write code to a temp file and check it.""" p = tmp_path / filename p.parent.mkdir(parents=True, exist_ok=True) p.write_text(code, encoding="utf-8") return check_file_fn(p) class TestDetectsWritableOpens: """Ensures writable opens of the journal quality DB are caught.""" def test_detects_sqlite_connect_without_mode_ro(self, tmp_path): code = f'conn = sqlite3.connect("{_JQ_DB}")\n' errors = _write_and_check(tmp_path, code) assert len(errors) >= 1 assert any("mode=ro" in e for e in errors) def test_detects_create_engine_without_mode_ro(self, tmp_path): code = f'engine = create_engine("sqlite:///{_JQ_DB}")\n' errors = _write_and_check(tmp_path, code) assert len(errors) >= 1 def test_detects_legacy_journal_reference_db(self, tmp_path): code = f'conn = sqlite3.connect("{_JR_DB}")\n' errors = _write_and_check(tmp_path, code) assert len(errors) >= 1 def test_detects_connect_in_fstring(self, tmp_path): code = f'conn = sqlite3.connect(f"{{path}}/{_JQ_DB}")\n' errors = _write_and_check(tmp_path, code) assert len(errors) >= 1 class TestAllowsReadOnlyOpens: """Ensures read-only opens are allowed.""" def test_allows_mode_ro(self, tmp_path): code = f'conn = sqlite3.connect(f"file:{{path}}/{_JQ_DB}?mode=ro&immutable=1", uri=True)\n' errors = _write_and_check(tmp_path, code) assert len(errors) == 0 def test_allows_mode_ro_uppercase(self, tmp_path): """SQLite accepts case-insensitive URI values — the hook should recognise `mode=RO` (and mixed case) as read-only intent too.""" code = f'conn = sqlite3.connect(f"file:{{path}}/{_JQ_DB}?mode=RO&immutable=1", uri=True)\n' errors = _write_and_check(tmp_path, code) assert len(errors) == 0 def test_allows_mode_ro_mixed_case(self, tmp_path): code = f'conn = sqlite3.connect(f"file:{{path}}/{_JQ_DB}?mode=Ro", uri=True)\n' errors = _write_and_check(tmp_path, code) assert len(errors) == 0 def test_allows_mode_ro_with_whitespace(self, tmp_path): code = f'conn = sqlite3.connect(f"file:{{path}}/{_JQ_DB}?mode = ro", uri=True)\n' errors = _write_and_check(tmp_path, code) assert len(errors) == 0 class TestAllowsWriterModule: """Ensures the designated writer module can open writable.""" def test_allows_build_db_writer(self, tmp_path): code = f'conn = sqlite3.connect("{_JQ_DB}")\n' errors = _write_and_check( tmp_path, code, filename="src/local_deep_research/journal_quality/db.py", ) assert len(errors) == 0 class TestAllowsSafePatterns: """Ensures non-connect references are not flagged.""" def test_allows_comments(self, tmp_path): code = f"# See {_JQ_DB} for data\n" errors = _write_and_check(tmp_path, code) assert len(errors) == 0 def test_allows_path_existence_check(self, tmp_path): code = f'if Path("{_JQ_DB}").exists():\n pass\n' errors = _write_and_check(tmp_path, code) assert len(errors) == 0 def test_allows_log_message(self, tmp_path): code = f'logger.info(f"Loading {_JQ_DB} from {{path}}")\n' errors = _write_and_check(tmp_path, code) assert len(errors) == 0 def test_ignores_non_python_files(self, tmp_path): code = f'conn = sqlite3.connect("{_JQ_DB}")\n' errors = _write_and_check(tmp_path, code, filename="docs/readme.md") assert len(errors) == 0