"""Smoke tests for codex.db — get_conn and apply_schema.""" from __future__ import annotations from unittest.mock import MagicMock, patch def test_get_conn_context_manager() -> None: """get_conn() yields a context manager that calls psycopg.connect and cleans up.""" from codex.db import get_conn mock_conn = MagicMock() mock_connect = MagicMock() # psycopg.connect is itself used as a context manager inside get_conn mock_connect.return_value.__enter__ = MagicMock(return_value=mock_conn) mock_connect.return_value.__exit__ = MagicMock(return_value=False) with patch("codex.db.psycopg.connect", mock_connect), get_conn() as conn: assert conn is mock_conn mock_connect.assert_called_once() def test_apply_schema_executes_sql() -> None: """apply_schema(conn) calls conn.execute and conn.commit.""" from codex.db import apply_schema mock_conn = MagicMock() # Provide a fake schema.sql so Path.read_text succeeds with patch("codex.db.Path.read_text", return_value="CREATE TABLE IF NOT EXISTS test ();"): apply_schema(mock_conn) mock_conn.execute.assert_called_once() mock_conn.commit.assert_called_once() def test_schema_sql_is_idempotent() -> None: """Every CREATE TABLE/INDEX in infra/schema.sql uses IF NOT EXISTS (audit M-1, T-2). Re-applying schema.sql must be a safe no-op so the migration can run repeatedly against an existing database. """ import re from pathlib import Path schema = (Path(__file__).resolve().parents[2] / "infra" / "schema.sql").read_text() # Drop line comments so commented-out example queries don't count. code = "\n".join(line.split("--", 1)[0] for line in schema.splitlines()) non_idempotent = re.findall( r"CREATE\s+(?:UNIQUE\s+)?(?:TABLE|INDEX)\s+(?!IF\s+NOT\s+EXISTS)(\w+)", code, re.IGNORECASE, ) assert not non_idempotent, f"schema.sql has non-idempotent CREATE statements: {non_idempotent}" def test_migrate_command_reports_privilege_error() -> None: """`codex migrate` surfaces InsufficientPrivilege with guidance and exits 1 (M-1).""" import psycopg from typer.testing import CliRunner from codex.cli import app def boom(*args: object, **kwargs: object) -> object: raise psycopg.errors.InsufficientPrivilege("must be owner of table chunks") with patch("psycopg.connect", boom): result = CliRunner().invoke(app, ["migrate"]) assert result.exit_code == 1 assert "MIGRATION_DATABASE_URL" in result.output