Lambda Testing¶
The Lambda ships 223 pytest tests spread across unit-style route tests, validation tests, and live-WordPress integration tests.
Layout¶
lambda/tests/
├── conftest.py
├── fixtures/
├── test_auth.py
├── test_cors.py
├── test_database.py
├── test_health.py
├── test_items.py
├── test_lambda.py
├── test_models.py
├── test_registry_e2e.py # requires live WP
├── test_registry_routes.py
├── test_registry_story.py
├── test_registry_wp_integration.py # requires live WP
├── integration/
│ └── test_wp_client_resilience.py
└── validation/
└── test_xss_inputs.py
Running the suite¶
cd lambda
uv sync
.venv/bin/pytest # full suite (needs live WP)
# Skip the WP-integration tests:
.venv/bin/pytest \
--ignore=tests/test_registry_e2e.py \
--ignore=tests/test_registry_wp_integration.py
The make targets:
make lambda-test # local pytest (delegates to lambda/Makefile test)
make lambda-test-staging # runs against the staging Lambda + WP
make lambda-test-prod # runs against prod (read-only smoke tests)
Key fixtures (tests/conftest.py)¶
| Fixture | What it gives you |
|---|---|
client |
TestClient(app) with get_current_user overridden to return a non-admin WPUser. No network round-trip happens. |
admin_client |
Same, but the injected user has the administrator role — drives the admin response shape. |
unauthed_client |
Same, but no override for get_current_user. Use this to test the 401 paths. |
The pattern in each fixture:
@pytest.fixture(scope="function")
def client():
app.dependency_overrides[get_current_user] = lambda: _make_wp_user(admin=False)
with TestClient(app) as test_client:
yield test_client
app.dependency_overrides.clear()
unauthed_client snapshots and restores any pre-existing overrides so it can
be combined with the others in a single test session.
Writing a route test¶
def test_create_item_returns_201(client):
resp = client.post("/items", json={
"registry_id": 1,
"name": "Chef's Knife",
"url": "https://example.com/knife",
"price": 79.99,
})
assert resp.status_code == 201
assert resp.json()["data"]["name"] == "Chef's Knife"
For admin-only behavior, swap to admin_client:
def test_admin_sees_affiliate_fields(admin_client):
resp = admin_client.get("/items/1")
assert "affiliate_url" in resp.json()["data"]
Validation tests¶
tests/validation/test_xss_inputs.py exercises the pydantic validators for
length limits, escaping, and the affiliate-URL constraints. New
input-validation rules should add a row here, not in the route tests.
Integration tests (tests/integration/)¶
test_wp_client_resilience.py tests wp_python failure modes — timeouts,
401s, malformed responses — to make sure validate_credentials() returns
None (not a partial WPUser) in every error path.
Live-WordPress tests¶
Two files require a real WordPress instance:
test_registry_e2e.py— full create/list/update/delete flow against a real WP REST API.test_registry_wp_integration.py— exercise_client_for_userand_post_to_registryround-trips.
Run them with make up && make seed first, then:
cd lambda
.venv/bin/pytest tests/test_registry_e2e.py tests/test_registry_wp_integration.py
These use the credentials in .env (WP_LOCAL_URL, WP_LOCAL_APP_PWD for
local; WP_STAGING_* for staging via make lambda-test-staging).
Linting + types¶
cd lambda
uv run ruff check .
uv run mypy app/
Both are wired into make lint and make typecheck from the top level.
CI¶
ci.yml runs make lambda-test (skipping live-WP tests) on every push.
deploy-prod.yml runs the same suite again before the build/deploy step —
no untested code makes it to prod.