Skip to content

Commit 3a234a4

Browse files
committed
Tighten constructive-testing: route to pgsql-test skill, trim duplication, add cross-references
1 parent 685c34b commit 3a234a4

3 files changed

Lines changed: 25 additions & 196 deletions

File tree

skills/constructive-testing/SKILL.md

Lines changed: 18 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -51,49 +51,15 @@ Choose the **highest-level framework** that fits your test scenario:
5151

5252
## Quick Start Patterns
5353

54-
### Pattern 1: SQL-Level Testing (`pgsql-test`)
54+
### SQL-Level Testing (`pgsql-test`)
5555

5656
Best for: RLS policies, database functions, raw SQL operations.
5757

58-
```typescript
59-
import { getConnections, PgTestClient } from 'pgsql-test';
58+
> **See the `pgsql-test` skill for full documentation** — it covers `getConnections()`, `PgTestClient`, RLS testing, seeding (`loadJson`/`loadSql`/`loadCsv`), savepoints, snapshots, JWT context, and complex multi-client scenarios.
6059
61-
let pg: PgTestClient; // Superuser — bypasses RLS
62-
let db: PgTestClient; // App-level — enforces RLS
63-
let teardown: () => Promise<void>;
60+
All higher-level frameworks below build on `pgsql-test` — they return the same `pg` and `db` clients with the same lifecycle hooks.
6461

65-
beforeAll(async () => {
66-
({ pg, db, teardown } = await getConnections());
67-
});
68-
69-
afterAll(async () => {
70-
await teardown();
71-
});
72-
73-
beforeEach(async () => {
74-
await pg.beforeEach();
75-
await db.beforeEach();
76-
});
77-
78-
afterEach(async () => {
79-
await db.afterEach();
80-
await pg.afterEach();
81-
});
82-
83-
it('enforces RLS', async () => {
84-
// Seed with superuser
85-
await pg.loadJson({
86-
'app.users': [{ id: 'user-1', email: 'alice@test.com' }]
87-
});
88-
89-
// Test with app-level client
90-
db.setContext({ role: 'authenticated', 'jwt.claims.user_id': 'user-1' });
91-
const result = await db.query('SELECT * FROM app.users');
92-
expect(result.rows).toHaveLength(1);
93-
});
94-
```
95-
96-
### Pattern 2: GraphQL Schema Testing (`graphile-test`)
62+
### GraphQL Schema Testing (`graphile-test`)
9763

9864
Best for: Testing PostGraphile schema generation, GraphQL queries without HTTP.
9965

@@ -136,7 +102,7 @@ it('returns users via GraphQL', async () => {
136102
});
137103
```
138104

139-
### Pattern 3: GraphQL + Constructive Plugins (`@constructive-io/graphql-test`)
105+
### GraphQL + Constructive Plugins (`@constructive-io/graphql-test`)
140106

141107
Best for: Testing with full Constructive graphile-settings plugins loaded.
142108

@@ -165,7 +131,7 @@ it('uses search plugin', async () => {
165131
});
166132
```
167133

168-
### Pattern 4: HTTP-Level Testing (`@constructive-io/graphql-server-test`)
134+
### HTTP-Level Testing (`@constructive-io/graphql-server-test`)
169135

170136
Best for: Testing full HTTP request/response cycle, auth headers, middleware.
171137

@@ -200,86 +166,15 @@ it('supports custom headers via SuperTest', async () => {
200166
});
201167
```
202168

203-
## Anti-Patterns
204-
205-
### NEVER: Manual pg.Pool / pg.Client Creation
206-
207-
```typescript
208-
// BAD — Do not do this in tests!
209-
const pg = require('pg');
210-
const pool = new pg.Pool({
211-
connectionString: `postgres://user:pass@localhost:5432/mydb`
212-
});
213-
214-
beforeAll(() => { /* manual pool setup */ });
215-
afterAll(() => { pool.end(); });
216-
```
217-
218-
**Why this is wrong:**
219-
- No test isolation (no savepoints, no rollback between tests)
220-
- No automatic database creation/cleanup
221-
- No RLS context management
222-
- Connection string is fragile and environment-dependent
223-
- Misses all the utilities (seeding, snapshots, etc.)
224-
225-
**Do this instead:**
226-
```typescript
227-
import { getConnections } from 'pgsql-test';
228-
229-
let pg, db, teardown;
230-
beforeAll(async () => {
231-
({ pg, db, teardown } = await getConnections());
232-
});
233-
afterAll(async () => { await teardown(); });
234-
```
235-
236-
### NEVER: Direct `getPgPool()` in Tests (unless testing pg-cache itself)
237-
238-
```typescript
239-
// BAD — bypasses test isolation
240-
import { getPgPool } from 'pg-cache';
241-
const pool = getPgPool({ database: 'mydb' });
242-
```
169+
## Anti-Patterns (Summary)
243170

244-
`pg-cache` is an infrastructure package for production connection pooling. Tests should use `pgsql-test` which manages pools internally with proper lifecycle.
171+
These are the most common mistakes. See [references/anti-patterns.md](references/anti-patterns.md) for detailed examples and fixes.
245172

246-
### NEVER: Manual Database Creation in Tests
247-
248-
```typescript
249-
// BAD — manual database management
250-
await adminPool.query(`CREATE DATABASE "test-${uuid}"`);
251-
// ... tests ...
252-
await adminPool.query(`DROP DATABASE "test-${uuid}"`);
253-
```
254-
255-
`pgsql-test`'s `getConnections()` handles all of this automatically — creating an isolated database, installing extensions, seeding, and tearing down.
256-
257-
### NEVER: Skipping beforeEach/afterEach Hooks
258-
259-
```typescript
260-
// BAD — tests leak state to each other
261-
beforeAll(async () => {
262-
({ pg, db, teardown } = await getConnections());
263-
});
264-
265-
it('test 1', async () => {
266-
await pg.query("INSERT INTO users ...");
267-
// This data persists into test 2!
268-
});
269-
```
270-
271-
**Always include the hooks:**
272-
```typescript
273-
beforeEach(async () => {
274-
await pg.beforeEach();
275-
await db.beforeEach();
276-
});
277-
278-
afterEach(async () => {
279-
await db.afterEach();
280-
await pg.afterEach();
281-
});
282-
```
173+
1. **Manual `pg.Pool` / `pg.Client` creation** — always use `getConnections()` from the appropriate framework
174+
2. **Using `pg-cache` in tests** — it's for production connection pooling, not test isolation
175+
3. **Manual `CREATE DATABASE` / `DROP DATABASE`**`getConnections()` handles this automatically
176+
4. **Missing `beforeEach`/`afterEach` hooks** — without them, tests leak state to each other
177+
5. **Using a lower-level framework than needed** — e.g., raw SQL queries when testing GraphQL behavior (use `graphile-test` instead)
283178

284179
## Reference Guide
285180

@@ -290,6 +185,8 @@ afterEach(async () => {
290185

291186
## Cross-References
292187

293-
- `pgsql-test` skill — Deep dive into SQL-level testing (RLS, seeding, snapshots, savepoints)
294-
- `constructive-env` skill — How test frameworks resolve database configuration via `getEnvOptions()`
295-
- `pgpm` skill (`references/testing.md`) — PGPM test setup and seed adapters
188+
- **`pgsql-test` skill** — The primary reference for SQL-level testing. Covers `getConnections()`, `PgTestClient`, RLS testing, seeding, savepoints, snapshots, JWT context, helpers, and complex scenarios in detail. Start here if you're writing database-level tests.
189+
- **`drizzle-orm-test` skill** — Drop-in replacement for `pgsql-test` that adds type-safe queries with Drizzle ORM
190+
- **`supabase-test` skill** — Testing Supabase applications with ephemeral databases and multi-user simulation
191+
- **`constructive-env` skill** — How test frameworks resolve database configuration via `getEnvOptions()`
192+
- **`pgpm` skill** (`references/testing.md`) — PGPM test setup and seed adapters

skills/constructive-testing/references/framework-details.md

Lines changed: 6 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,14 @@
11
# Testing Framework Details
22

3-
Detailed API documentation for each testing framework in the Constructive stack.
3+
Detailed API documentation for the higher-level testing frameworks in the Constructive stack.
44

5-
## 1. pgsql-test (SQL-Level)
5+
For `pgsql-test` (SQL-level) documentation, see the **`pgsql-test` skill** — it covers `getConnections()`, `PgTestClient`, RLS testing, seeding, savepoints, snapshots, JWT context, and complex scenarios in full detail.
66

7-
**Package**: `pgsql-test`
8-
**Source**: `postgres/pgsql-test/`
9-
10-
### `getConnections(opts?, seedAdapters?)`
11-
12-
Creates an isolated test database and returns connection clients.
13-
14-
**Parameters:**
15-
- `opts.pg` — Partial `PgConfig` overrides (host, port, user, database, etc.)
16-
- `opts.db` — Partial `PgTestConnectionOptions` (extensions, prefix, template, rootDb, etc.)
17-
- `seedAdapters` — Array of `SeedAdapter[]` (defaults to `[seed.pgpm()]`)
18-
19-
**Returns:**
20-
```typescript
21-
interface GetConnectionResult {
22-
pg: PgTestClient; // Superuser client (bypasses RLS)
23-
db: PgTestClient; // App-level client (enforces RLS)
24-
admin: DbAdmin; // Database admin operations
25-
teardown: (opts?) => Promise<void>; // Cleanup function
26-
manager: PgTestConnector; // Connection manager (pool access)
27-
}
28-
```
29-
30-
**Lifecycle:**
31-
1. Creates a unique database with UUID-based name
32-
2. Creates app-level user role
33-
3. Installs extensions (from `opts.db.extensions` or defaults)
34-
4. Runs seed adapters (default: pgpm seed)
35-
5. Returns `pg` (superuser) and `db` (app-level) clients
36-
37-
**Teardown:**
38-
```typescript
39-
await teardown(); // Default: drops database
40-
await teardown({ keepDb: true }); // Keep database for debugging
41-
```
42-
43-
### `PgTestClient` API
44-
45-
```typescript
46-
// Transaction isolation
47-
await client.beforeEach(); // Start savepoint
48-
await client.afterEach(); // Rollback to savepoint
49-
50-
// Context management (for RLS)
51-
client.setContext({ role: 'authenticated', 'jwt.claims.user_id': id });
52-
client.clearContext();
53-
54-
// Queries
55-
const result = await client.query('SELECT * FROM users WHERE id = $1', [id]);
56-
57-
// Savepoint management (for expected failures)
58-
await client.savepoint('name');
59-
await client.rollback('name');
60-
61-
// Seeding
62-
await client.loadJson({ 'schema.table': [{ col: 'val' }] });
63-
await client.loadSql(['/path/to/seed.sql']);
64-
await client.loadCsv({ 'schema.table': '/path/to/data.csv' });
65-
```
66-
67-
### Pool Access (when needed)
68-
69-
If you genuinely need a raw `Pool` object (rare), get it from the manager:
70-
71-
```typescript
72-
const { manager, pg } = await getConnections();
73-
const pool = manager.getPool(pg.config);
74-
```
75-
76-
This is still managed by pgsql-test and cleaned up on teardown.
7+
All frameworks below build on `pgsql-test` and return the same `pg`/`db` clients with the same lifecycle hooks.
778

789
---
7910

80-
## 2. graphile-test (GraphQL Schema-Level)
11+
## graphile-test (GraphQL Schema-Level)
8112

8213
**Package**: `graphile-test`
8314
**Source**: `graphile/graphile-test/`
@@ -139,7 +70,7 @@ const result = await query({
13970

14071
---
14172

142-
## 3. @constructive-io/graphql-test (Constructive Plugins)
73+
## @constructive-io/graphql-test (Constructive Plugins)
14374

14475
**Package**: `@constructive-io/graphql-test`
14576
**Source**: `graphql/test/`
@@ -163,7 +94,7 @@ import { GraphQLTestAdapter } from '@constructive-io/graphql-test';
16394

16495
---
16596

166-
## 4. @constructive-io/graphql-server-test (HTTP-Level)
97+
## @constructive-io/graphql-server-test (HTTP-Level)
16798

16899
**Package**: `@constructive-io/graphql-server-test`
169100
**Source**: `graphql/server-test/`

skills/pgsql-test/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ Consult these reference files for detailed documentation on specific topics:
208208
## Cross-References
209209

210210
Related skills (separate from this skill):
211+
- **`constructive-testing`** — Framework selection guide: which testing framework to use (pgsql-test vs graphile-test vs graphql-test vs server-test) and anti-patterns to avoid
211212
- `pgpm` (`references/testing.md`) — General pgpm test setup and seed adapters
212213
- `drizzle-orm-test` — Testing with Drizzle ORM (uses pgsql-test utilities)
213214
- `constructive-safegres` — Safegres authorization policies that RLS tests validate

0 commit comments

Comments
 (0)