Files
Stirling-PDF/engine/tests/test_stirling_api.py
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

258 lines
8.3 KiB
Python
Raw Normal View History

from conftest import build_app_settings
2026-03-26 10:35:47 +00:00
from fastapi.testclient import TestClient
from stirling.api import app
from stirling.api.dependencies import (
2026-04-21 16:59:31 +01:00
get_document_extractor_agent,
2026-03-26 10:35:47 +00:00
get_execution_planning_agent,
2026-04-21 16:59:31 +01:00
get_form_analyser_agent,
get_form_filler_agent,
2026-03-26 10:35:47 +00:00
get_orchestrator_agent,
get_pdf_edit_agent,
get_pdf_question_agent,
get_user_spec_agent,
)
from stirling.config import load_settings
2026-03-26 10:35:47 +00:00
from stirling.contracts import (
AgentDraft,
AgentDraftRequest,
AgentDraftResponse,
AgentExecutionRequest,
AgentRevisionRequest,
AgentRevisionResponse,
2026-04-21 16:59:31 +01:00
AnalysedFileResult,
2026-03-26 10:35:47 +00:00
CannotContinueExecutionAction,
2026-04-21 16:59:31 +01:00
CrossFileRole,
DetectedRole,
DocumentExtractionRequest,
2026-03-26 10:35:47 +00:00
EditCannotDoResponse,
2026-04-21 16:59:31 +01:00
FileFillResult,
FormAnalysisRequest,
FormAnalysisResponse,
FormFillBatchRequest,
FormFillBatchResponse,
KnowledgeUpdateResponse,
2026-03-26 10:35:47 +00:00
OrchestratorRequest,
PdfEditRequest,
PdfQuestionNeedContentResponse,
2026-03-26 10:35:47 +00:00
PdfQuestionNotFoundResponse,
PdfQuestionRequest,
2026-04-08 14:58:35 +01:00
)
from stirling.models.tool_models import Angle, RotatePdfParams
2026-03-26 10:35:47 +00:00
class StubOrchestratorAgent:
async def handle(self, request: OrchestratorRequest) -> PdfQuestionNeedContentResponse:
return PdfQuestionNeedContentResponse(reason=request.user_message, files=[], max_pages=1, max_characters=1000)
2026-03-26 10:35:47 +00:00
class StubPdfEditAgent:
async def handle(self, request: PdfEditRequest) -> EditCannotDoResponse:
return EditCannotDoResponse(reason=request.user_message)
class StubPdfQuestionAgent:
async def handle(self, request: PdfQuestionRequest) -> PdfQuestionNotFoundResponse:
return PdfQuestionNotFoundResponse(reason=request.question)
class StubUserSpecAgent:
async def draft(self, request: AgentDraftRequest) -> AgentDraftResponse:
return AgentDraftResponse(
draft=AgentDraft(
name="Drafted",
description="Route wiring test",
objective=request.user_message,
steps=[],
)
)
async def revise(self, request: AgentRevisionRequest) -> AgentRevisionResponse:
return AgentRevisionResponse(draft=request.current_draft)
2026-04-21 16:59:31 +01:00
class StubFormAnalyserAgent:
async def analyse(self, request: FormAnalysisRequest) -> FormAnalysisResponse:
return FormAnalysisResponse(
per_file=[
AnalysedFileResult(
file_id=f.file_id,
file_name=f.file_name,
detected_roles=[
DetectedRole(
role_label="Primary", field_names=[ff.name for ff in f.form_fields], is_primary_person=True
)
],
)
for f in request.files
],
cross_file_roles=[
CrossFileRole(
role_label="Primary",
file_ids=[f.file_id for f in request.files],
field_names_by_file={f.file_id: [ff.name for ff in f.form_fields] for f in request.files},
is_primary_person=True,
)
],
message="Stub analysis.",
2026-04-08 14:58:35 +01:00
)
2026-04-21 16:59:31 +01:00
class StubFormFillerAgent:
async def fill_batch(self, request: FormFillBatchRequest) -> FormFillBatchResponse:
return FormFillBatchResponse(
per_file=[FileFillResult(file_id=f.file_id, filled_fields=[]) for f in request.files],
message="Stub fill.",
)
class StubDocumentExtractorAgent:
async def extract_multiple(self, request: DocumentExtractionRequest) -> KnowledgeUpdateResponse:
return KnowledgeUpdateResponse(proposed_entries=[], message="Stub extraction.")
2026-03-26 10:35:47 +00:00
class StubExecutionPlanningAgent:
async def next_action(self, request: AgentExecutionRequest) -> CannotContinueExecutionAction:
return CannotContinueExecutionAction(reason=str(request.current_step_index))
app.dependency_overrides[load_settings] = build_app_settings
2026-04-21 16:59:31 +01:00
app.dependency_overrides[get_orchestrator_agent] = lambda: StubOrchestratorAgent()
app.dependency_overrides[get_pdf_edit_agent] = lambda: StubPdfEditAgent()
app.dependency_overrides[get_pdf_question_agent] = lambda: StubPdfQuestionAgent()
app.dependency_overrides[get_user_spec_agent] = lambda: StubUserSpecAgent()
app.dependency_overrides[get_execution_planning_agent] = lambda: StubExecutionPlanningAgent()
app.dependency_overrides[get_form_analyser_agent] = lambda: StubFormAnalyserAgent()
app.dependency_overrides[get_form_filler_agent] = lambda: StubFormFillerAgent()
app.dependency_overrides[get_document_extractor_agent] = lambda: StubDocumentExtractorAgent()
2026-03-26 10:35:47 +00:00
client: TestClient = TestClient(app)
2026-03-26 10:35:47 +00:00
def test_health_route() -> None:
response = client.get("/health")
assert response.status_code == 200
assert response.json()["status"] == "ok"
def test_orchestrator_route() -> None:
response = client.post("/api/v1/orchestrator", json={"userMessage": "route this", "fileNames": ["test.pdf"]})
2026-03-26 10:35:47 +00:00
assert response.status_code == 200
assert response.json()["outcome"] == "need_content"
2026-03-26 10:35:47 +00:00
def test_pdf_edit_route() -> None:
response = client.post("/api/v1/pdf/edit", json={"userMessage": "rotate this"})
assert response.status_code == 200
assert response.json()["outcome"] == "cannot_do"
def test_pdf_questions_route() -> None:
response = client.post(
"/api/v1/pdf/questions",
json={
"question": "what is this?",
"fileNames": ["test.pdf"],
"pageText": [{"fileName": "test.pdf", "pages": [{"pageNumber": 1, "text": "Example"}]}],
},
)
2026-03-26 10:35:47 +00:00
assert response.status_code == 200
assert response.json()["outcome"] == "not_found"
def test_agent_draft_route() -> None:
response = client.post("/api/v1/agents/draft", json={"userMessage": "build me an agent"})
assert response.status_code == 200
assert response.json()["outcome"] == "draft"
def test_agent_revise_route() -> None:
response = client.post(
"/api/v1/agents/revise",
json={
"userMessage": "revise it",
"currentDraft": {
"name": "Drafted",
"description": "Route wiring test",
"objective": "build me an agent",
"steps": [
{
"kind": "tool",
"tool": "/api/v1/general/rotate-pdf",
"parameters": RotatePdfParams(angle=Angle(90)).model_dump(by_alias=True),
2026-03-26 10:35:47 +00:00
}
],
},
},
)
assert response.status_code == 200
assert response.json()["outcome"] == "draft"
2026-04-21 16:59:31 +01:00
def test_form_analyse_route() -> None:
response = client.post(
"/api/v1/form/ai/analyse",
json={
"files": [
{
"fileId": "f1",
"fileName": "test.pdf",
"formFields": [{"name": "name", "type": "text"}],
}
]
},
)
assert response.status_code == 200
assert len(response.json()["crossFileRoles"]) > 0
def test_form_fill_batch_route() -> None:
response = client.post(
"/api/v1/form/ai/fill-batch",
json={
"files": [
{
"fileId": "f1",
"formFields": [{"name": "name", "type": "text"}],
"roleLabel": "Primary",
}
],
"knowledge": {"first_name": "John"},
},
)
2026-04-08 14:58:35 +01:00
assert response.status_code == 200
2026-04-21 16:59:31 +01:00
assert response.json()["outcome"] == "batch_fill_result"
2026-04-08 14:58:35 +01:00
2026-03-26 10:35:47 +00:00
def test_next_action_route() -> None:
response = client.post(
"/api/v1/agents/next-action",
json={
"agentSpec": {
"name": "Drafted",
"description": "Route wiring test",
"objective": "build me an agent",
"steps": [
{
"kind": "tool",
"tool": "/api/v1/general/rotate-pdf",
"parameters": RotatePdfParams(angle=Angle(90)).model_dump(by_alias=True),
2026-03-26 10:35:47 +00:00
}
],
},
"currentStepIndex": 0,
"executionContext": {"inputFiles": ["input.pdf"], "metadata": {}},
},
)
assert response.status_code == 200
assert response.json()["outcome"] == "cannot_continue"