Testing
Test plan for the Approval Workflow Engine — hermetic pytest smoke tests against in-memory SQLite, what they cover, and how to run against real Postgres.
All tests are hermetic — no Docker, no Postgres, no Keycloak, no network. The test suite drops the SQLAlchemy schema onto an in-memory SQLite database via aiosqlite, points dev-mode auth at empty issuer (unsigned JWTs accepted), and exercises the full FastAPI app over an in-process ASGI transport. The whole suite runs in about a second.
Run
python -m venv .venv && source .venv/bin/activate
pip install -e '.[test]'
pytest -vWhat's covered
test_health.py — service endpoints
Verifies /v1/awe/health, /v1/awe/version, /v1/awe/config return the documented envelopes and that /config does not leak Keycloak secrets.
test_policies.py — policy CRUD + lifecycle
Create a draft, list, list versions, fetch version detail.
Activate a draft → status flips to
active.Simulate: resolves approvers for a sample context, returns the same stages the policy declares.
AuthZ: unauthenticated calls get 401; non-admin tokens get 403.
Edit: draft versions can be edited in place (
PATCH); active and archived versions return409 AWE-007.
test_requests_and_tasks.py — end-to-end approval lifecycle
Two-stage happy path: create policy → create request → alice approves stage 1 (
any-1) → bob's task flips toskipped→ director approves stage 2 (allwith 1 approver) → request transitions toapproved. Verifies event timeline containsrequest_created,stage_started,stage_completed,request_approved.Reject path: both approvers reject a single stage → request ends
rejected.Cancel: admin cancels an in-flight request; outstanding tasks flip to
skipped.Idempotency: retrying
POST /requestswith the sameIdempotency-Keyreturns the samerequest_idwithout creating a second request row.Search: filter by
artifact_type/artifact_id.
test_webhook_signing.py — HMAC signing contract
Signature equals
"sha256=" + HMAC_SHA256(secret, timestamp + "." + body).Two signatures with different timestamps differ for the same body — proves replay-safety.
test_sla_monitor.py — SLA expiry
A task with
due_atin the past is flipped toexpiredby one tick of the SLA monitor.A
task_expiredevent is appended to the request timeline withtask_id,stage_order,assignee, anddue_atin the payload.A
webhook_deliveryrow is enqueuedpendingpointing at the request'scallback_url.The noop path (no due tasks) is exercised to confirm the monitor commits cleanly.
Sample payloads
Sample policy payload used across tests:
Re-usable as curl -d @policy.json against a running dev-mode instance.
Running against real Postgres (optional)
The production code path uses async Postgres via asyncpg. To exercise it locally:
The schema is ensured at startup via Base.metadata.create_all, so no migrations needed. This catches Postgres-specific oddities (SKIP LOCKED, JSONB coercion) that SQLite happily accepts.
Testing webhook delivery
The dispatcher and SLA monitor loops run as asyncio.Tasks during lifespan. In the smoke tests they're started but never tick (they sleep on their poll interval). To exercise delivery specifically:
A dedicated integration test for this path can be added later.
Last updated
Was this helpful?