For the complete documentation index, see llms.txt. This page is also available as Markdown.

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 -v

What'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 return 409 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 to skipped → director approves stage 2 (all with 1 approver) → request transitions to approved. Verifies event timeline contains request_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 /requests with the same Idempotency-Key returns the same request_id without 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_at in the past is flipped to expired by one tick of the SLA monitor.

  • A task_expired event is appended to the request timeline with task_id, stage_order, assignee, and due_at in the payload.

  • A webhook_delivery row is enqueued pending pointing at the request's callback_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?