From sqlproof
Write property-based tests for PostgreSQL schemas and SQL behavior using sqlproof. Use whenever the user asks to test a Supabase project, RLS policies, RPC functions, SQL functions, database triggers, migrations, or any code that touches PostgreSQL. Also use whenever the task mentions sqlproof, dataset generation respecting FK/CHECK/UNIQUE constraints, the `@sqlproof` decorator, `proof.check`, `proof.client_for_dataset`, the seeded auth.users pool, `supabase_proof`/`supabase_db` fixtures, or testing with savepoint isolation. This skill explains when to use sqlproof, the four canonical fixtures the pytest plugin ships, how to bootstrap a new project, and what NOT to do. For pattern-specific guidance, also load the relevant sibling skill (`sqlproof-rls-testing`, `sqlproof-rpc-testing`, `sqlproof-stateful-testing`, `sqlproof-ci-setup`). Without this skill, generated tests tend to reinvent fixtures the plugin already ships, hand-roll INSERT helpers that defeat property-based testing, or leak postgres internals (manual JWT claims) into test code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sqlproof:sqlproofThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Property-based testing for PostgreSQL with sqlproof. This skill
Property-based testing for PostgreSQL with sqlproof. This skill covers the always-on guidance: when to reach for sqlproof, what the fixtures are, how to bootstrap a project, and the anti-patterns the agent must avoid.
For pattern-specific guidance, ALSO load the relevant sibling skill:
sqlproof-rls-testing — RLS policy testssqlproof-rpc-testing — SQL function / RPC testssqlproof-stateful-testing — sequence-dependent bugssqlproof-ci-setup — wiring into GitHub ActionsThis skill also assumes the foundational property-based-testing
patterns are available via the companion alialavia/pbt-skills
plugin. Load that first.
Use sqlproof for any test that touches:
public.* SQL functions / RPCs — verify outputs for given inputsDo not use sqlproof for:
information_schema query in pgTAP or skip entirelysyrupy or pytest's snapshotThe user typically runs Supabase locally via supabase start. The
local DB lives at postgresql://postgres:[email protected]:54322/postgres.
# pyproject.toml
[project.optional-dependencies]
dev = [
"sqlproof",
"pytest>=8",
"hypothesis>=6.100",
"psycopg[binary]>=3.1",
]
[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
Then pip install -e ".[dev]" (or uv sync --extra dev).
export SUPABASE_DB_URL='postgresql://postgres:[email protected]:54322/postgres'
Resolution chain: --sqlproof-database-url flag → $SQLPROOF_DATABASE_URL
→ $SUPABASE_DB_URL. Without any of these, sqlproof's fixtures skip
cleanly (they don't fail).
There is no tests/conftest.py to write. sqlproof's pytest
plugin auto-registers these:
proof (session) — SqlProof connected to the DSNdb (per-test) — SqlProofClient with savepoint isolationsupabase_proof (session) — like proof, but with the
deterministic auth.users test pool seeded and registered as
an external table for FK draws. Use this for any test that
touches RLS, auth.uid(), or RPCs keyed off auth users.supabase_db (per-test) — SqlProofClient backed by supabase_proofRule: if a test touches auth → take supabase_db. Otherwise → take db.
Don't define proof/db yourself unless you need a custom external
table beyond the auth-users pool.
The whole reason to use sqlproof over pgTAP is that it generates many valid datasets for a single test. Edge cases surface that you'd never think to type.
Don't write _insert_user, _insert_project, _insert_event
helpers in your tests. Hand-rolled INSERTs test only the shape
you remembered, not the shape your schema actually permits.
Do use dataset_strategy to generate, then assert:
from hypothesis import given
from hypothesis import strategies as st
@given(data=st.data())
def test_my_invariant(supabase_proof, data):
dataset = data.draw(supabase_proof.dataset_strategy(
sizes={"projects": 1, "events": 5},
))
with supabase_proof.client_for_dataset(dataset) as db:
# ... assertions
Each example generates a fresh dataset that respects every FK, CHECK, UNIQUE (single-column AND composite), and NOT NULL in the schema.
Two cases where hand-rolled INSERTs are acceptable:
00000000-...-000000000000, don't generate.proof.client_for_dataset(...)
to seed the initial state.tests/conftest.py with proof/db fixturesThe plugin ships them. The only reason to override is registering
an additional external table beyond auth.users.
# Wrong — leaks postgres internals:
db.execute(
"SELECT set_config('request.jwt.claims', %s, true)",
json.dumps({"sub": user_id, "role": "authenticated"}),
)
# Right — readable, restored on exit, exception-safe:
from sqlproof.contrib.supabase import as_supabase_user
with as_supabase_user(db, user_id):
...
db.execute as a listSqlProofClient's methods take *params (splat), not a single
sequence:
# Wrong — sends ONE parameter (the list) to a query with TWO placeholders:
db.execute(
"INSERT INTO posts (org_id, author_id) VALUES (%s, %s)",
[org_id, author_id],
)
# psycopg.errors.SyntaxError: the query has 2 placeholders but 1 parameters
# Right:
db.execute(
"INSERT INTO posts (org_id, author_id) VALUES (%s, %s)",
org_id,
author_id,
)
columns={...}The dataset generator omits columns with DB defaults from the
returned dataset. If your test reads dataset["posts"][0]["is_premium"],
you need to declare is_premium in columns={...} explicitly.
auth.users if the connection lacks permissionUse supabase_proof (which seeds once per session) and sample from
the pool. Don't re-seed per test.
:: cast in raw SQLdb.scalar("SELECT my_function(%s, %s)", uuid_value, integer_value) # ambiguous
db.scalar("SELECT my_function(%s::uuid, %s::int)", uuid_value, integer_value) # explicit
Postgres function overload resolution requires explicit casts when the parameter types aren't obvious.
project_root/
├── tests/
│ ├── test_rls_<table>.py # one file per table with RLS
│ ├── test_rpc_<function_name>.py # one file per public function
│ ├── test_trigger_<trigger_name>.py # one file per trigger
│ └── test_migration_<n>_<desc>.py # migration safety tests
└── supabase/
├── migrations/...
├── schemas/...
└── tests/... # pgTAP files (separate suite)
Test names: test_<subject>_<expected_behavior>_<conditions>. Good:
test_owner_can_read_their_own_project. Bad: test_policy_22.
If the user asks for a test you don't have a pattern for, ask ONE clarifying question about the intended invariant. Don't guess. Once the invariant is clear, write the test. If still uncertain after one question, write the smallest possible example test and flag the area for the user to expand.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
npx claudepluginhub alialavia/sqlproof-skills --plugin sqlproof