| 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 |
| |
| 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 |
| |
| 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" |
| |
| |
| 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: |
| |
| 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: |
| |
| response = auth_client.post( |
| "/documents/upload", |
| data={"title": "Anon"}, |
| files={"file": ("x.txt", b"hello", "text/plain")}, |
| ) |
| assert response.status_code in (401, 403) |
|
|