Testing Guide¶
This page documents how to run the full test suite for db-graphql-gateway, including unit tests, conformance tests, and the live integration tests against real databases.
Test Layers¶
| Layer | Location | What it covers |
|---|---|---|
| Unit | tests/unit/ |
Auth, SQL compiler, IR builder, type mapping |
| Conformance | tests/conformance/ |
Cross-adapter correctness (SQLite in-process) |
| Integration | integration_tests/ |
Full HTTP stack against live Postgres & SQLite |
Unit & Conformance Tests¶
These run in-process with no external dependencies:
# Install dev dependencies
uv sync --all-extras --dev
# Run all unit tests
uv run pytest tests/unit/ -v
# Run all conformance tests
uv run pytest tests/conformance/ -v
# Run everything
uv run pytest tests/ -v
Integration Tests¶
The integration tests spin up a real HTTP gateway server and fire GraphQL requests over httpx. They verify the full stack end-to-end, including auth, row-level policies, pagination, mutations, and DataLoader batching.
SQLite (no Docker required)¶
cd integration_tests
bash run_sqlite.sh
This script:
- Drops and recreates
sgql_test_db.sqlite3 - Seeds the database with
seed_sqlite.py - Starts the gateway server in the background
- Runs all test levels (L1–L5) via
run_integration.py - Cleans up the DB file on exit
PostgreSQL (requires Docker)¶
cd integration_tests
bash run_all.sh
This script:
- Starts a Postgres container via
docker compose up -d - Waits for the healthcheck to pass
- Seeds the database with
seed.py - Starts the gateway server in the background
- Runs all test levels (L1–L5)
- Tears down the container including volumes (
docker compose down -v)
Volume cleanup
The run_all.sh script uses docker compose down -v on both the success and failure paths, ensuring the Postgres data volume is fully wiped between runs. This prevents DuplicateTableError on re-runs.
Test Levels¶
| Level | Module | What it tests |
|---|---|---|
| L1 | level1_basic.py |
Simple queries, filters |
| L2 | level2_medium.py |
Nested queries, pagination, mutations |
| L3 | level3_advanced.py |
Depth limits, aliases, auth, backward pagination, M2M |
| L4 | level4_mutations.py |
Composite PKs, read-only tables, enums |
| L5 | level5_batching.py |
Empirical O(1) DataLoader batching correctness |
Level 5: Batching Correctness Tests¶
Level 5 is the most important test for the db-graphql-gateway performance guarantee.
It empirically proves that DB query count does not scale with row count.
How query counting works¶
| Adapter | Mechanism |
|---|---|
| Postgres | pg_stat_statements (enabled by schema.sql). The engine resets it before each query and reads the DML-only count after. |
| SQLite | aiosqlite.set_trace_callback installed at gateway startup. Count is exposed at GET /admin/query-count and reset at GET /admin/query-count/reset. |
Individual tests¶
| Test | Assertion |
|---|---|
| Fan-out independence | Query count is identical for breadths 5, 20, and 50 |
| Depth linearity | Query count grows by ≤1 per additional depth level |
| M2M batching | posts → tags emits ≤7 queries regardless of join cardinality |
| Sibling relations | Extra queries per sibling are additive, not multiplicative |
| Circular reference | users → posts → users bounded at ≤6 queries |
Sample output¶
--- Level 5: Batching Correctness (sqlite) ---
L5-1: Fan-out independence (breadth scaling, depth=2)
breadth=5: 2 queries
breadth=20: 2 queries
breadth=50: 2 queries
L5-2: Depth linearity (depth scaling, fixed breadth)
depth=1: 1 queries
depth=2: 2 queries
depth=3: 3 queries
growth ratio per depth: 2.00x (linear OK)
L5-3: M2M batching (posts -> tags, one query for level)
posts+tags query count: 3
✅ L5-1: Fan-out independence (0.0ms, 2 queries)
✅ L5-2: Depth linearity (0.0ms, 3 queries)
✅ L5-3: M2M batching (6.4ms, 3 queries)
✅ L5-4: Sibling relations (12.2ms, 5 queries)
✅ L5-5: Circular reference (15.3ms, 3 queries)
CI Pipeline¶
The full test suite runs on every push and pull request:
| Workflow | Trigger | What runs |
|---|---|---|
ci.yml |
Every push / PR | pre-commit (ruff, mypy), unit tests |
integration.yml |
Every push to main |
Full SQLite integration suite |
conformance.yml |
Every push / PR | Cross-adapter conformance tests |
docs.yml |
Every push to main |
MkDocs build → GitHub Pages |
Any failure in any test level is build-breaking — run_integration.py exits with code 1 on the first failed test, causing the shell script to propagate the failure to CI.