Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 🧪 Testing

This document describes how to run the FireForm test suite locally.

## Prerequisites

Make sure you have installed all dependencies:

```bash
pip install -r requirements.txt
```

## Running Tests

From the project root directory:

```bash
python -m pytest tests/ -v
```

> **Note:** Use `python -m pytest` instead of `pytest` directly to ensure the project root is on the Python path.

## Test Coverage

| File | Tests | What it covers |
|------|-------|----------------|
| `tests/test_llm.py` | 15 | LLM class — batch prompt, field extraction, plural handling |
| `tests/test_templates.py` | 10 | `POST /templates/create`, `GET /templates`, `GET /templates/{id}` |
| `tests/test_forms.py` | 7 | `POST /forms/fill`, `GET /forms/{id}`, `GET /forms/download/{id}` |

**Total: 52 tests**

## Test Design

- All tests use an **in-memory SQLite database** — your local `fireform.db` is never touched
- Each test gets a **fresh empty database** — no data leaks between tests
- Ollama is **never called** during tests — all LLM calls are mocked

## Key Test Cases

**LLM extraction (`test_llm.py`)**
- Batch prompt contains all field keys and human-readable labels
- `main_loop()` makes exactly **1 Ollama call** regardless of field count (O(1) assertion)
- Graceful fallback when Mistral returns invalid JSON
- `-1` responses stored as `None`, not as the string `"-1"`

**Template endpoints (`test_templates.py`)**
- Valid PDF upload returns 200 with field data
- Non-PDF upload returns 400
- Missing file returns 422
- Non-existent template returns 404

**Form endpoints (`test_forms.py`)**
- Non-existent template returns 404
- Ollama connection failure returns 503
- Missing filled PDF on disk returns 404
- Non-existent submission returns 404
15 changes: 10 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
from sqlalchemy.pool import StaticPool
import pytest


from api.main import app
from api.deps import get_db
from api.db.models import Template, FormSubmission

# In-memory SQLite database for tests
TEST_DATABASE_URL = "sqlite://"

engine = create_engine(
Expand All @@ -23,12 +21,12 @@ def override_get_db():
yield session


# Apply dependency override
app.dependency_overrides[get_db] = override_get_db


@pytest.fixture(scope="session", autouse=True)
def create_test_db():
@pytest.fixture(autouse=True)
def reset_db():
SQLModel.metadata.drop_all(engine)
SQLModel.metadata.create_all(engine)
yield
SQLModel.metadata.drop_all(engine)
Expand All @@ -37,3 +35,10 @@ def create_test_db():
@pytest.fixture
def client():
return TestClient(app)


@pytest.fixture
def db_session():
"""Direct DB session for test setup."""
with Session(engine) as session:
yield session
132 changes: 107 additions & 25 deletions tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,107 @@
def test_submit_form(client):
pass
# First create a template
# form_payload = {
# "template_id": 3,
# "input_text": "Hi. The employee's name is John Doe. His job title is managing director. His department supervisor is Jane Doe. His phone number is 123456. His email is jdoe@ucsc.edu. The signature is <Mamañema>, and the date is 01/02/2005",
# }

# template_res = client.post("/templates/", json=template_payload)
# template_id = template_res.json()["id"]

# # Submit a form
# form_payload = {
# "template_id": template_id,
# "data": {"rating": 5, "comment": "Great service"},
# }

# response = client.post("/forms/", json=form_payload)

# assert response.status_code == 200

# data = response.json()
# assert data["id"] is not None
# assert data["template_id"] == template_id
# assert data["data"] == form_payload["data"]
"""
Tests for /forms endpoints.
Closes #165, #205, #163
"""

import pytest
from unittest.mock import patch
from api.db.models import Template, FormSubmission
from datetime import datetime


# ── helpers ───────────────────────────────────────────────────────────────────

def make_template(db_session):
t = Template(
name="Test Form",
fields={"JobTitle": "Job Title"},
pdf_path="/tmp/test.pdf",
created_at=datetime.utcnow(),
)
db_session.add(t)
db_session.commit()
db_session.refresh(t)
return t.id


def make_submission(db_session, template_id, output_path="/tmp/filled.pdf"):
s = FormSubmission(
template_id=template_id,
input_text="John Smith is a firefighter.",
output_pdf_path=output_path,
created_at=datetime.utcnow(),
)
db_session.add(s)
db_session.commit()
db_session.refresh(s)
return s.id


# ── POST /forms/fill ──────────────────────────────────────────────────────────

class TestFillForm:

def test_fill_form_template_not_found(self, client):
"""Returns 404 when template_id does not exist."""
response = client.post("/forms/fill", json={
"template_id": 999999,
"input_text": "John Smith is a firefighter.",
})
assert response.status_code == 404

def test_fill_form_missing_fields_returns_422(self, client):
"""Returns 422 when required fields are missing."""
response = client.post("/forms/fill", json={
"template_id": 1,
})
assert response.status_code == 422

def test_fill_form_ollama_down_returns_503(self, client, db_session):
"""Returns 503 when Ollama is not reachable."""
template_id = make_template(db_session)

with patch("src.controller.Controller.fill_form",
side_effect=ConnectionError("Ollama not running")):
response = client.post("/forms/fill", json={
"template_id": template_id,
"input_text": "John Smith is a firefighter.",
})

assert response.status_code == 503


# ── GET /forms/{submission_id} ────────────────────────────────────────────────

class TestGetSubmission:

def test_get_submission_not_found(self, client):
"""Returns 404 for non-existent submission ID."""
response = client.get("/forms/999999")
assert response.status_code == 404

def test_get_submission_invalid_id(self, client):
"""Returns 422 for non-integer submission ID."""
response = client.get("/forms/not-an-id")
assert response.status_code == 422


# ── GET /forms/download/{submission_id} ───────────────────────────────────────

class TestDownloadSubmission:

def test_download_not_found_submission(self, client):
"""Returns 404 when submission does not exist."""
response = client.get("/forms/download/999999")
assert response.status_code == 404

def test_download_file_missing_on_disk(self, client, db_session):
"""Returns 404 when submission exists but PDF missing on disk."""
template_id = make_template(db_session)
submission_id = make_submission(
db_session, template_id, "/nonexistent/filled.pdf"
)

with patch("os.path.exists", return_value=False):
response = client.get(f"/forms/download/{submission_id}")

assert response.status_code == 404
Loading