feat(pdf): #74 au complet — fix auth 500 stream + endpoint pdf/info + HTTP Range 206 + indexation incrementale PDF + config taille/timeout
This commit is contained in:
@@ -124,6 +124,144 @@ class TestPdfReader:
|
||||
assert isinstance(toc, list)
|
||||
|
||||
|
||||
class TestPdfIncrementalIndexing:
|
||||
"""_index_single_file_sync (watcher/update path) must handle binary PDFs.
|
||||
|
||||
Regression: it used to read_text() every file, producing garbage for PDFs.
|
||||
"""
|
||||
|
||||
def test_index_single_file_sync_extracts_pdf_text(self, pdf_dir: Path):
|
||||
from backend.indexer import _index_single_file_sync
|
||||
|
||||
info = _index_single_file_sync("v", str(pdf_dir), str(pdf_dir / "simple.pdf"))
|
||||
assert info is not None
|
||||
assert info["extension"] == ".pdf"
|
||||
assert "ObsiGate test PDF" in info["content"]
|
||||
assert "uniqueword0" in info["content"]
|
||||
|
||||
def test_index_single_file_sync_pdf_title_from_metadata(self, pdf_dir: Path):
|
||||
from backend.indexer import _index_single_file_sync
|
||||
|
||||
info = _index_single_file_sync("v", str(pdf_dir), str(pdf_dir / "simple.pdf"))
|
||||
# title metadata was set at generation time
|
||||
assert info["title"] == "Simple Test"
|
||||
|
||||
|
||||
class TestPdfSizeLimit:
|
||||
def test_oversized_pdf_skips_text_extraction(self, pdf_dir: Path, monkeypatch):
|
||||
import backend.pdf_reader as pr
|
||||
|
||||
monkeypatch.setattr(pr, "PDF_MAX_SIZE_MB", 0) # everything is "too large"
|
||||
text = pr.extract_pdf_text(pdf_dir / "simple.pdf")
|
||||
assert text == ""
|
||||
|
||||
def test_normal_pdf_within_limit_extracts(self, pdf_dir: Path, monkeypatch):
|
||||
import backend.pdf_reader as pr
|
||||
|
||||
monkeypatch.setattr(pr, "PDF_MAX_SIZE_MB", 50)
|
||||
text = pr.extract_pdf_text(pdf_dir / "simple.pdf")
|
||||
assert "ObsiGate test PDF" in text
|
||||
|
||||
|
||||
# ── API: /pdf/stream (206 Range) + /pdf/info ───────────────────────────────
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pdf_client():
|
||||
"""TestClient (auth disabled) over a vault containing one generated PDF."""
|
||||
tmp = Path(tempfile.mkdtemp())
|
||||
vault = tmp / "PdfVault"
|
||||
vault.mkdir()
|
||||
make_simple_pdf(vault / "doc.pdf", pages=2, title="Doc Test", author="Bruno")
|
||||
|
||||
os.environ["VAULT_1_NAME"] = "PdfVault"
|
||||
os.environ["VAULT_1_PATH"] = str(vault)
|
||||
os.environ["OBSIGATE_AUTH_ENABLED"] = "false"
|
||||
os.environ["OBSIGATE_WATCHER_ENABLED"] = "false"
|
||||
|
||||
import backend.main
|
||||
backend.main._load_config = lambda: {"watcher_enabled": False}
|
||||
|
||||
from backend.indexer import build_index, index
|
||||
for key in list(index.keys()):
|
||||
del index[key]
|
||||
|
||||
import asyncio
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(build_index())
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
client = TestClient(backend.main.app)
|
||||
yield client
|
||||
client.close()
|
||||
shutil.rmtree(str(tmp), ignore_errors=True)
|
||||
for k in ["VAULT_1_NAME", "VAULT_1_PATH", "OBSIGATE_AUTH_ENABLED", "OBSIGATE_WATCHER_ENABLED"]:
|
||||
os.environ.pop(k, None)
|
||||
|
||||
|
||||
class TestPdfStreamApi:
|
||||
def test_stream_returns_200_application_pdf(self, pdf_client):
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.pdf")
|
||||
assert r.status_code == 200
|
||||
assert r.headers["content-type"] == "application/pdf"
|
||||
assert r.headers.get("accept-ranges") == "bytes"
|
||||
assert r.content[:4] == b"%PDF"
|
||||
|
||||
def test_stream_full_range_returns_whole_file(self, pdf_client):
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.pdf",
|
||||
headers={"Range": "bytes=0-99999999"})
|
||||
assert r.status_code == 206
|
||||
assert r.headers["content-range"].startswith("bytes 0-")
|
||||
assert r.content[:4] == b"%PDF"
|
||||
|
||||
def test_stream_partial_range(self, pdf_client):
|
||||
full = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.pdf").content
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.pdf",
|
||||
headers={"Range": "bytes=10-19"})
|
||||
assert r.status_code == 206
|
||||
assert r.content == full[10:20]
|
||||
assert len(r.content) == 10
|
||||
|
||||
def test_stream_open_ended_range(self, pdf_client):
|
||||
full = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.pdf").content
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.pdf",
|
||||
headers={"Range": "bytes=100-"})
|
||||
assert r.status_code == 206
|
||||
assert r.content == full[100:]
|
||||
|
||||
def test_stream_bad_range_returns_416(self, pdf_client):
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.pdf",
|
||||
headers={"Range": "bytes=999999999-"})
|
||||
assert r.status_code == 416
|
||||
assert "bytes */" in r.headers.get("content-range", "")
|
||||
|
||||
def test_stream_rejects_non_pdf(self, pdf_client):
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/stream?path=doc.md")
|
||||
assert r.status_code == 404 or r.status_code == 400
|
||||
|
||||
|
||||
class TestPdfInfoApi:
|
||||
def test_info_returns_metadata_without_content(self, pdf_client):
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/info?path=doc.pdf")
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["pages"] == 2
|
||||
assert body["title"] in ("Doc Test", "doc.pdf")
|
||||
assert body["size_bytes"] > 0
|
||||
assert "path" in body and body["vault"] == "PdfVault"
|
||||
# no heavy content in the payload
|
||||
assert "html" not in body
|
||||
|
||||
def test_info_missing_file_404(self, pdf_client):
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/info?path=nope.pdf")
|
||||
assert r.status_code == 404
|
||||
|
||||
def test_info_missing_non_pdf_404(self, pdf_client):
|
||||
r = pdf_client.get("/api/file/PdfVault/pdf/info?path=readme.txt")
|
||||
assert r.status_code == 404 # missing file checked before extension
|
||||
|
||||
|
||||
# ── backend/indexer.py ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user