hasura-tests
Overview
Per
hasura.io/docs/2.0/auth/authorization/quickstart/,
Hasura permissions are configured per table, role, and
operation (select / insert / update / delete), with row-
filter expressions and column-level scopes.
The testable concerns are different from Apollo / Yoga:
- Permission rules are declarative; tests verify the rule
produces the expected filter SQL.
- Roles + session variables drive everything; tests inject
x-hasura-role + custom claims and assert the response shape.
- Metadata is the source of truth; tests apply a fixture
metadata then run queries.
When to use
- Testing a Hasura-backed API where row-level isolation is the
primary concern (composes with
cross-tenant-data-leak-tests in the qa-multi-tenancy plugin).
- Verifying a new role's permission rules.
- PR review of
hasura/metadata/ changes.
- Regression-testing role transitions (user becomes admin, etc.).
How to use
- Bring up the ephemeral test stack (
docker compose -f docker-compose.test.yml up -d)
with introspection disabled and the console off.
- Apply the fixture metadata that declares the permission rules under test, via the
/v1/metadata API or hasura metadata apply.
- Seed per-test rows so no test depends on another test's data.
- Send queries to
/v1/graphql with the admin secret plus an x-hasura-role
override (and x-hasura-user-id / custom claims) to act as any role.
- Assert the response shape: rows are row-filtered to the role, or the failure
carries
extensions.code = "permission-error" (or affected_rows: 0).
- Cover the full role x table x operation matrix from a checked-in fixture, and add
one smoke test through the real JWT path.
- Tear the stack down with
-v and reset metadata between suites.
Authoring
Test instance setup
# docker-compose.test.yml
services:
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${PG_PASSWORD:?set PG_PASSWORD}
hasura:
image: hasura/graphql-engine:v2.42.0
ports: ["8080:8080"]
depends_on: [postgres]
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:${PG_PASSWORD}@postgres:5432/postgres
HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET:?set HASURA_GRAPHQL_ADMIN_SECRET}
HASURA_GRAPHQL_DISABLE_INTROSPECTION_PUBLIC_API: "true" # per graphql-complexity-limit-tester references/introspection.md
HASURA_GRAPHQL_ENABLE_CONSOLE: "false"
docker compose -f docker-compose.test.yml up -d
Never write the admin secret or DB password into the compose file or a
test. Per Compose interpolation, ${VAR} is read from the
shell or a git-ignored .env, and ${VAR:?error} stops Compose when it is
unset. Every command and test below reads HASURA_GRAPHQL_ADMIN_SECRET from
the environment.
Apply metadata for test
Per Hasura docs, the metadata API at /v1/metadata:
curl -X POST http://localhost:8080/v1/metadata \
-H "x-hasura-admin-secret: $HASURA_GRAPHQL_ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d @hasura/metadata-fixture.json
metadata-fixture.json contains the declarative permission
rules being tested.
Or via hasura CLI:
hasura migrate apply --endpoint http://localhost:8080 --admin-secret "$HASURA_GRAPHQL_ADMIN_SECRET"
hasura metadata apply --endpoint http://localhost:8080 --admin-secret "$HASURA_GRAPHQL_ADMIN_SECRET"
Role-based permission tests
Tests act as any role via admin secret + x-hasura-role override, and a full
per-role x per-table x per-operation audit is generated from a checked-in fixture.
Both patterns - the httpx by-role queries and the parametrized matrix - are in
references/permission-matrix-tests.md.
Worked example
Scenario: a new user role must see only its own rows in the user table.
- In the fixture metadata, give
user a select permission on user filtered by
id = X-Hasura-User-Id; apply it with hasura metadata apply.
- POST
{ user { id name } } to /v1/graphql with headers x-hasura-admin-secret,
x-hasura-role: user, x-hasura-user-id: 3.
- Assert
status_code == 200 and every returned row has id == 3 - the row filter
held.
- POST the same query with
x-hasura-role: admin (no user-id) and assert
len(rows) > 1 - admin is not restricted.
Result: one pair of requests proves the row-filter rule for the restricted role and
confirms it does not leak into the admin role.
Running
docker compose -f docker-compose.test.yml up -d
hasura metadata apply --endpoint http://localhost:8080 --admin-secret "$HASURA_GRAPHQL_ADMIN_SECRET"
pytest tests/hasura/
docker compose -f docker-compose.test.yml down -v
Parsing results
Hasura returns standard GraphQL response format:
{
"data": { "user": [{"id": 3, "name": "alice"}] },
"errors": [{"message": "user.id: permission has failed", "extensions": {"code": "permission-error"}}]
}
Permission failures use extensions.code = "permission-error".
Assertion patterns:
def test_user_cannot_update_other_users_row():
resp = httpx.post(ENDPOINT, headers={
"x-hasura-admin-secret": os.environ["HASURA_GRAPHQL_ADMIN_SECRET"],
"x-hasura-role": "user",
"x-hasura-user-id": "3",
}, json={
"query": "mutation { update_user_by_pk(pk_columns: {id: 4}, _set: {name: \"hacked\"}) { id } }"
})
body = resp.json()
# Either errors out OR returns affected_rows: 0 depending on permission setup
if "errors" in body:
assert any(
"permission" in e["extensions"].get("code", "")
for e in body["errors"]
)
else:
assert body["data"]["update_user_by_pk"] is None # row was filtered out
CI integration
jobs:
hasura-permission-matrix:
runs-on: ubuntu-latest
env:
HASURA_GRAPHQL_ADMIN_SECRET: ${{ secrets.HASURA_ADMIN_SECRET }}
services:
postgres:
image: postgres:16
env: { POSTGRES_PASSWORD: "${{ secrets.CI_DB_PASSWORD }}" }
ports: [5432]
hasura:
image: hasura/graphql-engine:v2.42.0
env:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:${{ secrets.CI_DB_PASSWORD }}@postgres:5432/postgres
HASURA_GRAPHQL_ADMIN_SECRET: ${{ secrets.HASURA_ADMIN_SECRET }}
HASURA_GRAPHQL_DISABLE_INTROSPECTION_PUBLIC_API: "true"
ports: [8080]
steps:
- uses: actions/checkout@v5
- run: |
npm install -g hasura-cli
hasura migrate apply --endpoint http://localhost:8080 --admin-secret "$HASURA_GRAPHQL_ADMIN_SECRET"
hasura metadata apply --endpoint http://localhost:8080 --admin-secret "$HASURA_GRAPHQL_ADMIN_SECRET"
- run: pytest tests/hasura/ --tb=short
Per the GitHub Actions contexts reference, secrets is
available in job env and in service-container env, so neither the admin
secret nor the DB password appears in the workflow file.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|
| Tests run against shared Hasura instance | Permission changes leak between tests | Per-test or per-suite ephemeral DB + metadata reset |
Skipping HASURA_GRAPHQL_DISABLE_INTROSPECTION_PUBLIC_API in CI | Production-config drift; introspection assertions don't hold | Set in test docker-compose |
Permission tests using admin secret without x-hasura-role override | Tests run as admin -> bypass all permissions | Always add x-hasura-role |
| Hardcoded user IDs across tests | One test mutates user 3 -> next test stale | Per-test user seeding |
| Skipping insert / update / delete tests | Permission rules differ per operation | Cover the full matrix |
| Not testing JWT path | Admin-secret-override bypasses production auth flow | One smoke test using real JWT against HASURA_GRAPHQL_JWT_SECRET config |
| Permission matrix in code only | PR reviewers can't see what changes | Matrix as a checked-in YAML / JSON fixture |
| Tests don't reset metadata between suites | Migration drift between test runs | hasura metadata clear + reapply in setup |
Limitations
- Doesn't test the auth service. Hasura accepts JWT / webhook
/ admin-secret claims; verifying the auth-service issues
correct claims is separate.
- Permission rules are SQL-translated. Some rules that look
right in metadata may produce slow SQL; performance is a
separate test concern (use
EXPLAIN queries via Hasura's
introspection - admin-only).
- Action / Remote-schema testing. Hasura's Actions and
Remote Schemas delegate to other services; this skill tests
the Hasura -> upstream contract, not the upstream itself.
- Subscription testing requires a long-running connection.
Hasura subscriptions use server-sent updates over HTTP/WS;
pytest async patterns needed.
- Event-trigger testing. Hasura's webhook event triggers
fire on data changes; test via the Webhook receiver, not
Hasura itself.
References