|
1 | 1 | """POST /ingest (ADR 0029): flag gate, validation, and the happy-path append.""" |
2 | 2 |
|
| 3 | +from pathlib import Path |
3 | 4 | from unittest import mock |
4 | 5 |
|
| 6 | +import fitz |
5 | 7 | import pytest |
6 | 8 | from fastapi.testclient import TestClient |
7 | 9 |
|
8 | 10 | from src.api.deps import get_settings |
9 | 11 | from src.api.main import create_app |
10 | 12 | from src.config.settings import Settings |
| 13 | +from src.rag.bm25 import Bm25Index |
| 14 | +from src.rag.vectorstore import QdrantVectorStore |
| 15 | +from tests.fakes import FakeEmbedder |
11 | 16 |
|
12 | 17 |
|
13 | 18 | def _app(enable_upload: bool) -> TestClient: |
@@ -79,3 +84,42 @@ async def fake_ingest(**kwargs: object) -> mock.Mock: |
79 | 84 | assert resp.status_code == 422 |
80 | 85 | assert "internal detail" not in resp.text # no exception/path leakage to the client |
81 | 86 | assert "Could not parse" in resp.text |
| 87 | + |
| 88 | + |
| 89 | +@pytest.mark.slow |
| 90 | +async def test_ingest_route_real_pipeline(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: |
| 91 | + """The real /ingest pipeline, unmocked: Docling parse + embed + Qdrant upsert |
| 92 | + + the live chunks_by_id append, then retrievable. Uses in-memory Qdrant + a |
| 93 | + fake embedder (the repo's e2e pattern), so it needs no services — only the |
| 94 | + real Docling parse, hence `slow`.""" |
| 95 | + embedder = FakeEmbedder(dim=8) |
| 96 | + vectorstore = QdrantVectorStore(url=":memory:", collection_name="ingest_slow", dim=8) |
| 97 | + await vectorstore.ensure_collection() |
| 98 | + bm25 = Bm25Index() |
| 99 | + chunks: dict[str, object] = {} |
| 100 | + monkeypatch.setattr( |
| 101 | + "src.api.routes.ingest.get_corpus_handles", lambda: (embedder, vectorstore, bm25) |
| 102 | + ) |
| 103 | + monkeypatch.setattr("src.api.routes.ingest.get_chunks", lambda: chunks) |
| 104 | + |
| 105 | + doc = fitz.open() |
| 106 | + doc.new_page().insert_text( |
| 107 | + (72, 72), |
| 108 | + "The zorblax7731 calibration constant equals 88.5 in this synthetic test document.", |
| 109 | + fontsize=11, |
| 110 | + ) |
| 111 | + pdf = tmp_path / "zorblax.pdf" |
| 112 | + doc.save(pdf) |
| 113 | + doc.close() |
| 114 | + |
| 115 | + resp = _app(enable_upload=True).post( |
| 116 | + "/ingest", files={"file": ("zorblax.pdf", pdf.read_bytes(), "application/pdf")} |
| 117 | + ) |
| 118 | + |
| 119 | + assert resp.status_code == 200, resp.text |
| 120 | + body = resp.json() |
| 121 | + assert body["chunks_added"] >= 1 |
| 122 | + assert body["paper_id"] == "zorblax" |
| 123 | + assert len(chunks) == body["chunks_added"] # route appended to the live index |
| 124 | + hits = bm25.search("zorblax7731", top_k=5) # the uploaded doc is now retrievable |
| 125 | + assert hits and any(h.chunk_id in chunks for h in hits) |
0 commit comments