DocDoeAI / tests /test_documents_upload.py
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
3bcdb36
Raw
History Blame Contribute Delete
4.49 kB
from __future__ import annotations
def test_document_upload_extracts_and_chunks_text_file(client) -> None:
response = client.post(
"/documents/upload",
data={
"title": "Upload smoke test",
"subject": "Biology",
"chapter": "Photosynthesis",
},
files={
"file": (
"photosynthesis.txt",
b"Photosynthesis uses light to make glucose and oxygen.",
"text/plain",
),
},
)
assert response.status_code == 201
payload = response.json()
assert payload["status"] == "ready"
assert payload["chunk_count"] >= 1
# extracted_text was removed from API response (security hardening)
assert "extracted_text" not in payload
def test_document_upload_returns_failed_document_when_indexing_fails(client, monkeypatch) -> None:
from app.routes import documents
def fail_indexing(*_args, **_kwargs) -> None:
raise RuntimeError("embedding backend unavailable")
monkeypatch.setattr(documents, "replace_document_chunks", fail_indexing)
response = client.post(
"/documents/upload",
data={"title": "Indexing failure"},
files={
"file": (
"notes.txt",
b"This text extracts but indexing fails.",
"text/plain",
),
},
)
assert response.status_code == 201
payload = response.json()
assert payload["status"] == "failed"
assert payload["chunk_count"] == 0
# extraction_error is stored server-side but must NOT be in the API response
assert "extraction_error" not in payload
def test_document_upload_strips_lone_surrogates_before_database_write(client, monkeypatch) -> None:
from app.routes import documents
monkeypatch.setattr(
documents,
"extract_text_from_file",
lambda *_args, **_kwargs: "Clean biology text \ud835 with invalid surrogate.",
)
response = client.post(
"/documents/upload",
data={"title": "Unicode repair", "subject": "Biology"},
files={
"file": (
"unicode.txt",
b"placeholder",
"text/plain",
),
},
)
assert response.status_code == 201
payload = response.json()
assert payload["status"] == "ready"
# extracted_text was removed from API response (security hardening);
# verify it is NOT leaked to the client and the upload still succeeds
assert "extracted_text" not in payload
def test_upload_rejects_unsupported_mime_type(client) -> None:
response = client.post(
"/documents/upload",
data={"title": "Bad type"},
files={"file": ("notes.exe", b"MZ\x90\x00", "application/x-msdownload")},
)
assert response.status_code == 415
def test_upload_rejects_images_before_creating_a_failed_document(client) -> None:
response = client.post(
"/documents/upload",
data={"title": "Scanned notes"},
files={"file": ("notes.png", b"\x89PNG\r\n\x1a\n", "image/png")},
)
assert response.status_code == 415
detail = response.json()["detail"]
assert "Image OCR" in detail
assert client.get("/documents").json() == []
def test_upload_rejects_file_over_the_size_limit(client) -> None:
# 20 MB + 1 byte of allowed-type content must be rejected as 413, not stored.
oversized = b"a" * (20 * 1024 * 1024 + 1)
response = client.post(
"/documents/upload",
data={"title": "Too big"},
files={"file": ("huge.txt", oversized, "text/plain")},
)
assert response.status_code == 413
def test_upload_accepts_a_malayalam_filename_and_sanitises_it(client) -> None:
response = client.post(
"/documents/upload",
data={"title": "Malayalam name"},
files={
"file": (
"ശബ്ദം physics notes.txt",
b"Sound travels as a longitudinal wave.",
"text/plain",
),
},
)
assert response.status_code == 201
assert response.json()["status"] == "ready"
def test_upload_requires_authentication(auth_client) -> None:
# No Authorization header -> the upload endpoint must refuse.
response = auth_client.post(
"/documents/upload",
data={"title": "Anon"},
files={"file": ("x.txt", b"hello", "text/plain")},
)
assert response.status_code in (401, 403)