"""End-to-end smoke test for the D-light upload pipeline. Stays local: drives the FastAPI app via Starlette TestClient and exercises the unit-level pipeline helpers. Skips the LLM `extract-text` body so we don't need a live NVIDIA key — the offline fallback is what we want here. """ from __future__ import annotations import io from pathlib import Path from fastapi.testclient import TestClient from app.services.proj_doc_pipeline import ( IngestArtifact, collect_text_window, extract_pages_text, stamp_index, ) TEST_PDF = Path(__file__).parent / "test.pdf" def test_pipeline_helpers_text_extraction(): """Pure pipeline layer: native text + stamp heuristic on tests/test.pdf.""" if not TEST_PDF.exists(): # No fixture PDF — skip; the assertion below is meant to be smoke. return raw = TEST_PDF.read_bytes() artifact = extract_pages_text(raw) assert artifact.sha256, "sha256 must be present" assert artifact.page_count >= 1, "at least one page expected" assert isinstance(artifact.pages, list) # Stamp index is keyed on label incl. "(стр. N)" suffix. stamps = stamp_index(artifact.pages) assert isinstance(stamps, dict) # Window helper must return a non-empty string for any page. if artifact.pages: window = collect_text_window( artifact.pages, around=0, span=2 ) assert window and isinstance(window, str) def test_pipeline_artifact_default_empty_text(): """Direct call against the dataclass — no IO.""" artifact = IngestArtifact( file_name="x.pdf", mime_type="application/pdf", sha256="0" * 64, page_count=0, ) assert artifact.full_text(1) == "" assert artifact.has_native_text is False def test_uploads_blueprint_endpoint_accepts_pdf(minimal_app_client: TestClient): """POST /uploads/blueprint → 200, page list non-empty (no Supabase).""" if not TEST_PDF.exists(): return client = minimal_app_client files = {"file": ("test.pdf", TEST_PDF.read_bytes(), "application/pdf")} resp = client.post("/uploads/blueprint", files=files) assert resp.status_code == 200, resp.text body = resp.json() assert body["file_name"] == "test.pdf" assert body["page_count"] >= 1 assert isinstance(body["pages"], list) assert body["stored_in_supabase"] is False # no SB creds in tests status = client.get( f"/uploads/blueprint/{body['upload_id']}" ) assert status.status_code in (200, 404), status.text # If Supabase isn't reachable we fall back to the on-disk cache → 200. if status.status_code == 200: s = status.json() assert s["upload_id"] == body["upload_id"] assert s["page_count"] == body["page_count"] def test_uploads_blueprint_rejects_oversize(minimal_app_client: TestClient): client = minimal_app_client payload = b"x" * (51 * 1024 * 1024) # 51 MB > MAX_BYTES=50MB files = { "file": ("huge.pdf", io.BytesIO(payload), "application/pdf") } resp = client.post("/uploads/blueprint", files=files) assert resp.status_code == 413 assert "too large" in resp.json()["detail"] def test_uploads_blueprint_rejects_unsupported_mime(minimal_app_client: TestClient): client = minimal_app_client files = { "file": ("odd.avi", b"abcd", "video/x-msvideo") } resp = client.post("/uploads/blueprint", files=files) assert resp.status_code == 415