Testing Guide
Quick Start
# Install dependencies (includes pytest, httpx, pytest-cov)
pip install -r requirements.txt
# Run all tests
pytest tests/ -v
# Run with coverage report
pytest tests/ --cov=app --cov-report=term-missing
# Run a single test file
pytest tests/services/test_pdf_processor.py -v
# Run a specific test
pytest tests/routers/test_auth.py::test_signup_success -v
Test Suite Overview
148 tests across 19 test files, covering every endpoint, service, and the LangGraph teacher agent.
| Category | Files | Tests | What's Tested |
|---|---|---|---|
| Services (pure) | 3 | 25 | pdf_processor, tier_config, cache |
| Services (mocked) | 5 | 47 | quality_checker, deduplication, text_normalizer, wallet_reservation, llm |
| Routers | 10 | 66 | health, auth, me, curriculum, wallet, admin (users/ingestion/documents), scraping, chat |
| Agents | 1 | 10 | teacher_agent node functions |
| Total | 19 | 148 |
Test Structure
tests/
├── conftest.py # Shared fixtures, mocks, TestClient
├── test_health.py # GET /health
├── services/
│ ├── test_pdf_processor.py # extract_pages, chunk_text, build_chunks
│ ├── test_tier_config.py # get_limits, top_k, rerank_n, costs
│ ├── test_cache.py # LRU: get, set, TTL, eviction, invalidation
│ ├── test_quality_checker.py # text length, OCR confidence, encoding, file size
│ ├── test_deduplication.py # SimHash fingerprint, hamming distance
│ ├── test_text_normalizer.py # Arabic normalization, whitespace, boilerplate
│ ├── test_wallet_reservation.py # reserve, finalize, topup, expire, cost calc
│ └── test_llm.py # detect_exercise, translate_query, generate_answer
├── routers/
│ ├── test_auth.py # signup, signin, logout, reset-password
│ ├── test_me.py # get/update profile
│ ├── test_chat.py # POST /chat JSON mode
│ ├── test_curriculum.py # subjects, textbooks, detail
│ ├── test_wallet.py # balance, reservations, topup, transactions
│ ├── test_admin_users.py # test-role, stats, CRUD, role, hint, reset-pw
│ ├── test_admin_ingestion.py # ingest, dispatch, jobs, requeue
│ ├── test_admin_documents.py # references, scrape-runs, update/delete doc
│ └── test_scraping.py # sources, sync, runs, references
└── agents/
└── test_teacher_agent.py # check_wallet, decide_path, retrieve, clarify, finalize
Mocking Strategy
All external services are mocked at the library level so tests run fast with no credentials needed:
- Supabase —
supabase.create_clientreturns a MagicMock with chainable.table().select().eq().execute()pattern - OpenAI —
openai.OpenAIreturns a MagicMock - Pinecone —
pinecone.Pineconereturns a MagicMock - LangGraph —
build_teacher_graph()is patched before the chat router loads
The conftest.py starts these patches at module level (before any app.* import) because app/core/dependencies.py creates real clients at import time.
Writing New Tests
- Service tests — Import the class/function directly, mock only what's needed (supabase client for DB-dependent services).
- Router tests — Use the
clientfixture (TestClient with auth overrides), patchapp.api.routers.<module>.supabase_serviceat the router level. - Agent tests — Import node functions directly, patch
app.agents.teacher_agent.supabase_service/wallet_service/retrieval_pipeline.
Dependencies
Added to requirements.txt:
httpx is pinned to <0.28 for compatibility with starlette==0.27 (bundled with fastapi==0.104.1).