diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 4b33fd0..9b8b43b 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -2,7 +2,7 @@ > **Note:** This document is automatically generated and verified against the live test suite by `scripts/generate_requirements.py` and `tests/backend/test_requirements_sync.py`. -**Test Verification Baseline:** **1115 Automated Tests** (757 Pytest Backend + 308 Vitest Frontend + 50 Playwright E2E). +**Test Verification Baseline:** **1117 Automated Tests** (759 Pytest Backend + 308 Vitest Frontend + 50 Playwright E2E). --- @@ -1126,6 +1126,10 @@ and leaves the prior indexed state intact without data loss._ - `test_set_embedding_db_config_persists_to_system_metadata` - `test_update_embedding_config_service` +#### `tests/test_native_postgres_readiness.py` (2 tests) +- `test_native_postgres_readiness_accepts_initialized_schema` +- `test_native_postgres_missing_schema_is_sanitized` + #### `tests/test_pdf_extractor.py` (14 tests) - `test_extract_digital_pdf_text` - `test_extract_scanned_pdf_triggers_vision_ocr` diff --git a/app/services/database/connection.py b/app/services/database/connection.py index a7219e5..0a38e89 100644 --- a/app/services/database/connection.py +++ b/app/services/database/connection.py @@ -4,7 +4,7 @@ import logging from typing import Optional, Dict, Any, Tuple -from sqlalchemy import select, func +from sqlalchemy import select, func, text from app.services.database.schema import TABLES from app.services.database.engine import ( diff --git a/tests/test_native_postgres_readiness.py b/tests/test_native_postgres_readiness.py new file mode 100644 index 0000000..27ea4c6 --- /dev/null +++ b/tests/test_native_postgres_readiness.py @@ -0,0 +1,61 @@ +import os +import uuid + +import pytest +from fastapi.testclient import TestClient +from sqlalchemy import create_engine, text +from sqlalchemy.engine import make_url + +import app.services.database as db_service +from app.services.database.engine import dispose_global_engine +from main import app + + +@pytest.fixture +def postgres_readiness_db(monkeypatch, tmp_path): + database_url = os.getenv("TEST_POSTGRES_URL") + if not database_url: + pytest.skip("TEST_POSTGRES_URL is required for native PostgreSQL tests") + + schema = f"readiness_{uuid.uuid4().hex}" + admin_engine = create_engine(database_url) + with admin_engine.begin() as conn: + conn.execute(text(f"CREATE SCHEMA {schema}")) + isolated_url = make_url(database_url).update_query_dict( + {"options": f"-csearch_path={schema},public"} + ) + monkeypatch.setenv( + "DATABASE_URL", isolated_url.render_as_string(hide_password=False) + ) + dispose_global_engine() + try: + db_service.init_db(vault_path=str(tmp_path)) + yield + finally: + dispose_global_engine() + with admin_engine.begin() as conn: + conn.execute(text(f"DROP SCHEMA {schema} CASCADE")) + admin_engine.dispose() + + +def test_native_postgres_readiness_accepts_initialized_schema(postgres_readiness_db): + client = TestClient(app) + for endpoint in ("/ready", "/readiness", "/health/ready", "/healthz/ready"): + response = client.get(endpoint) + assert response.status_code == 200 + assert response.json()["database"] == {"status": "ok", "type": "postgresql"} + + +def test_native_postgres_missing_schema_is_sanitized(postgres_readiness_db): + from app.services.database.engine import get_db_engine + + with get_db_engine().begin() as conn: + conn.execute(text("DROP TABLE indexed_files")) + + client = TestClient(app) + response = client.get("/readiness") + assert response.status_code == 503 + assert response.json()["database"] == {"status": "error", "type": "postgresql"} + assert response.json()["error"] == "Required database schema or metadata unavailable" + assert "indexed_files" not in response.text + assert client.get("/health").status_code == 200