"""Phase 6 maintenance API tests.""" from __future__ import annotations import importlib from collections.abc import Generator from contextlib import asynccontextmanager from datetime import datetime from fastapi.testclient import TestClient from ont_platform.api import db_deps from ont_platform.storage.candidate_repository import CandidateRepository from ont_platform.storage.models import Base, SourceDocument from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.pool import StaticPool main_module = importlib.import_module("ont_platform.api.main") @asynccontextmanager async def _noop_lifespan(app): yield def _phase6_client(monkeypatch) -> TestClient: monkeypatch.setenv("PHASE", "6") engine = create_engine( "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(engine) session_factory = sessionmaker(bind=engine) def override_get_db() -> Generator[Session, None, None]: db = session_factory() if db.get(SourceDocument, "doc_api_phase6") is None: _seed(db) db.commit() try: yield db finally: db.close() app = main_module.create_app() app.router.lifespan_context = _noop_lifespan app.dependency_overrides[db_deps.get_db] = override_get_db return TestClient(app, raise_server_exceptions=True, backend="asyncio") def _seed(db: Session) -> None: db.add( SourceDocument( id="doc_api_phase6", project_id="proj_api_phase6", source_url="https://example.test/api-phase6", document_type="html", title="API Phase 6 Source", text="Acme appears in a source.", content_hash="hash_api_phase6", fingerprint="fp_api_phase6", retrieved_at=datetime.utcnow(), extracted_by="trafilatura", ) ) CandidateRepository(db).save_lightweight_result( project_id="proj_api_phase6", document_id="doc_api_phase6", result={ "entities": [ { "id": "E_api_phase6", "label": "Acme", "type": "org", "confidence": 0.4, "evidence_ids": [], } ], "relations": [], "evidence_spans": [], }, ) def test_phase6_api_runs_loop_and_reviews_proposal(monkeypatch) -> None: with _phase6_client(monkeypatch) as client: run_response = client.post( "/api/v1/maintenance/runs", json={ "project_id": "proj_api_phase6", "requested_by": "ops", "actor_role": "admin", }, ) proposal_response = client.get( "/api/v1/maintenance/proposals", params={"project_id": "proj_api_phase6", "status": "pending_review"}, ) proposal_id = proposal_response.json()["proposals"][0]["id"] review_response = client.post( f"/api/v1/maintenance/proposals/{proposal_id}/review", json={"reviewed_by": "admin", "actor_role": "admin", "approve": True}, ) assert run_response.status_code == 200, run_response.text assert run_response.json()["run"]["summary"]["direct_mutations"] == 0 assert proposal_response.status_code == 200 assert proposal_response.json()["proposals"] assert review_response.status_code == 200, review_response.text assert review_response.json()["proposal"]["status"] == "approved"