fischer-agentkit/tests/routes/test_documents.py

251 lines
7.8 KiB
Python

"""Tests for /api/v1/documents routes (U7)."""
from __future__ import annotations
import asyncio
from pathlib import Path
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from agentkit.documents.db import init_documents_db
from agentkit.documents.renderers.excel_renderer import ExcelRenderer
from agentkit.documents.renderers.pdf_renderer import PDFRenderer
from agentkit.documents.renderers.word_renderer import WordRenderer
from agentkit.documents.service import DocumentService
from agentkit.server.routes import documents as documents_routes
@pytest.fixture
def app(tmp_path: Path) -> FastAPI:
"""Create a test app with DocumentService initialized."""
db_path = tmp_path / "test.db"
upload_dir = tmp_path / "uploads"
asyncio.run(init_documents_db(db_path))
service = DocumentService(upload_dir=upload_dir, db_path=db_path)
service.register_renderer("word", WordRenderer())
service.register_renderer("excel", ExcelRenderer())
service.register_renderer("pdf", PDFRenderer())
app = FastAPI()
app.state.document_service = service
app.state.server_config = None # No API key configured → allow all
app.include_router(documents_routes.router, prefix="/api/v1")
return app
@pytest.fixture
def client(app: FastAPI) -> TestClient:
return TestClient(app)
# ---------------------------------------------------------------------------
# POST /create
# ---------------------------------------------------------------------------
def test_create_word(client: TestClient) -> None:
"""POST /create with format=word returns 200 + document metadata."""
resp = client.post(
"/api/v1/documents/create",
json={
"format": "word",
"content": "# Test\n\nParagraph.",
"conversation_id": "conv-1",
},
)
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["document"]["format"] == "word"
assert data["document"]["filename"].endswith(".docx")
assert data["document"]["download_url"].startswith("/api/v1/documents/download/")
def test_create_pdf(client: TestClient) -> None:
"""POST /create with format=pdf returns 200."""
resp = client.post(
"/api/v1/documents/create",
json={
"format": "pdf",
"content": "# PDF Test",
"conversation_id": "conv-1",
},
)
assert resp.status_code == 200
assert resp.json()["document"]["format"] == "pdf"
def test_create_excel_json(client: TestClient) -> None:
"""POST /create with format=excel and JSON content returns 200."""
resp = client.post(
"/api/v1/documents/create",
json={
"format": "excel",
"content": '{"Data": [["A", "B"], ["1", "2"]]}',
"conversation_id": "conv-1",
},
)
assert resp.status_code == 200
assert resp.json()["document"]["format"] == "excel"
def test_create_invalid_format(client: TestClient) -> None:
"""POST /create with invalid format returns 400."""
resp = client.post(
"/api/v1/documents/create",
json={
"format": "pptx",
"content": "test",
"conversation_id": "conv-1",
},
)
assert resp.status_code == 400
def test_create_missing_fields(client: TestClient) -> None:
"""POST /create with missing required fields returns 422."""
resp = client.post(
"/api/v1/documents/create",
json={"format": "word"},
)
assert resp.status_code == 422 # Pydantic validation error
# ---------------------------------------------------------------------------
# GET /conversation/{id}
# ---------------------------------------------------------------------------
def test_list_conversation_documents(client: TestClient) -> None:
"""GET /conversation/{id} returns documents for that conversation."""
# Create a document first
client.post(
"/api/v1/documents/create",
json={
"format": "word",
"content": "# Doc 1",
"conversation_id": "conv-list",
},
)
client.post(
"/api/v1/documents/create",
json={
"format": "pdf",
"content": "# Doc 2",
"conversation_id": "conv-list",
},
)
resp = client.get("/api/v1/documents/conversation/conv-list")
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["count"] == 2
assert data["conversation_id"] == "conv-list"
formats = [d["format"] for d in data["documents"]]
assert "word" in formats
assert "pdf" in formats
def test_list_empty_conversation(client: TestClient) -> None:
"""GET /conversation/{id} with no documents returns empty list."""
resp = client.get("/api/v1/documents/conversation/no-such-conv")
assert resp.status_code == 200
data = resp.json()
assert data["count"] == 0
assert data["documents"] == []
# ---------------------------------------------------------------------------
# GET /download/{doc_id}
# ---------------------------------------------------------------------------
def test_download_document(client: TestClient) -> None:
"""GET /download/{doc_id} returns the file."""
# Create a document
create_resp = client.post(
"/api/v1/documents/create",
json={
"format": "word",
"content": "# Downloadable",
"conversation_id": "conv-dl",
},
)
doc_id = create_resp.json()["document"]["id"]
# Download it
resp = client.get(f"/api/v1/documents/download/{doc_id}")
assert resp.status_code == 200
assert resp.headers["content-type"] == "application/octet-stream"
assert len(resp.content) > 0
def test_download_not_found(client: TestClient) -> None:
"""GET /download/{nonexistent} returns 404."""
resp = client.get("/api/v1/documents/download/nonexistent-id")
assert resp.status_code == 404
# ---------------------------------------------------------------------------
# POST /upload-template
# ---------------------------------------------------------------------------
def test_upload_template(client: TestClient, tmp_path: Path) -> None:
"""POST /upload-template accepts a .docx file and returns stored_name."""
# Create a minimal .docx file
from docx import Document
template_path = tmp_path / "test_template.docx"
doc = Document()
doc.add_paragraph("Hello {{name}}!")
doc.save(str(template_path))
with open(template_path, "rb") as f:
resp = client.post(
"/api/v1/documents/upload-template",
files={"file": ("test_template.docx", f, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")},
)
assert resp.status_code == 200
data = resp.json()
assert data["success"] is True
assert data["stored_name"].startswith("template-")
assert data["stored_name"].endswith(".docx")
def test_upload_template_wrong_format(client: TestClient) -> None:
"""POST /upload-template with non-.docx returns 400."""
resp = client.post(
"/api/v1/documents/upload-template",
files={"file": ("test.txt", b"not a docx", "text/plain")},
)
assert resp.status_code == 400
# ---------------------------------------------------------------------------
# Service unavailable
# ---------------------------------------------------------------------------
def test_service_unavailable(tmp_path: Path) -> None:
"""When document_service is not on app.state, returns 503."""
app = FastAPI()
# No document_service set
app.include_router(documents_routes.router, prefix="/api/v1")
client = TestClient(app)
resp = client.post(
"/api/v1/documents/create",
json={
"format": "word",
"content": "test",
"conversation_id": "conv-1",
},
)
assert resp.status_code == 503