|
| 1 | +--- |
| 2 | +name: pgtap-testing |
| 3 | +description: Guide pgTAP test writing in pgflow. Use when user asks to write pgTAP test, add test for feature, test SQL function, or asks how to test database scenarios. Provides test patterns and helper functions. |
| 4 | +--- |
| 5 | + |
| 6 | +# pgTAP Testing Guide |
| 7 | + |
| 8 | +**CRITICAL**: Tests live in `pkgs/core/supabase/tests/`, helpers in `pkgs/core/supabase/seed.sql` |
| 9 | + |
| 10 | +## Quick Reference |
| 11 | + |
| 12 | +**Test Structure:** |
| 13 | +```sql |
| 14 | +begin; |
| 15 | +select plan(N); -- Declare number of tests |
| 16 | +select pgflow_tests.reset_db(); -- Clean state |
| 17 | +-- ... setup and assertions ... |
| 18 | +select finish(); |
| 19 | +rollback; |
| 20 | +``` |
| 21 | + |
| 22 | +**Common Assertions:** |
| 23 | +- `is(actual, expected, description)` - Equality check |
| 24 | +- `results_eq(query1, query2, description)` - Compare query results |
| 25 | +- `ok(boolean, description)` - Boolean check |
| 26 | +- `throws_ok(query, description)` - Expect error |
| 27 | + |
| 28 | +**Running Tests:** |
| 29 | +```bash |
| 30 | +# Single test |
| 31 | +./scripts/run-test-with-colors pkgs/core/supabase/tests/path/to/test.sql |
| 32 | + |
| 33 | +# All tests |
| 34 | +pnpm nx test:pgtap core |
| 35 | +``` |
| 36 | + |
| 37 | +## Test Structure |
| 38 | + |
| 39 | +All pgTAP tests follow this pattern: |
| 40 | + |
| 41 | +```sql |
| 42 | +begin; -- Start transaction |
| 43 | +select plan(N); -- Declare number of tests |
| 44 | + |
| 45 | +-- Setup phase |
| 46 | +select pgflow_tests.reset_db(); |
| 47 | +select pgflow_tests.setup_flow('sequential'); |
| 48 | + |
| 49 | +-- Test assertions |
| 50 | +select is( |
| 51 | + (select count(*) from pgflow.runs), |
| 52 | + 1::bigint, |
| 53 | + 'Should create one run' |
| 54 | +); |
| 55 | + |
| 56 | +select finish(); -- Complete tests |
| 57 | +rollback; -- Roll back transaction |
| 58 | +``` |
| 59 | + |
| 60 | +**Key points:** |
| 61 | +- Transaction ensures isolation (BEGIN...ROLLBACK) |
| 62 | +- `plan(N)` must match exact number of assertions |
| 63 | +- `reset_db()` cleans state between test runs |
| 64 | +- All changes are rolled back |
| 65 | + |
| 66 | +## Common Patterns |
| 67 | + |
| 68 | +### Testing Single Functions |
| 69 | + |
| 70 | +Test a function's return value or side effects: |
| 71 | + |
| 72 | +```sql |
| 73 | +begin; |
| 74 | +select plan(2); |
| 75 | +select pgflow_tests.reset_db(); |
| 76 | +select pgflow_tests.setup_flow('sequential'); |
| 77 | + |
| 78 | +-- Execute function |
| 79 | +select pgflow.start_flow('sequential', '"hello"'::jsonb); |
| 80 | + |
| 81 | +-- Test: Check created run |
| 82 | +select results_eq( |
| 83 | + $$ SELECT flow_slug, status from pgflow.runs $$, |
| 84 | + $$ VALUES ('sequential', 'started') $$, |
| 85 | + 'Run should be created with correct status' |
| 86 | +); |
| 87 | + |
| 88 | +select finish(); |
| 89 | +rollback; |
| 90 | +``` |
| 91 | + |
| 92 | +### Testing Workflows (Setup → Execute → Assert) |
| 93 | + |
| 94 | +Test complete workflows with multiple steps: |
| 95 | + |
| 96 | +```sql |
| 97 | +begin; |
| 98 | +select plan(3); |
| 99 | +select pgflow_tests.reset_db(); |
| 100 | +select pgflow_tests.setup_flow('sequential'); |
| 101 | + |
| 102 | +-- Start flow |
| 103 | +select pgflow.start_flow('sequential', '"hello"'::jsonb); |
| 104 | + |
| 105 | +-- Poll and start task |
| 106 | +select pgflow_tests.read_and_start('sequential'); |
| 107 | + |
| 108 | +-- Complete task |
| 109 | +select pgflow.complete_task( |
| 110 | + (select run_id from pgflow.runs limit 1), |
| 111 | + 'first', |
| 112 | + 0, |
| 113 | + '{"result": "done"}'::jsonb |
| 114 | +); |
| 115 | + |
| 116 | +-- Test: Task completed |
| 117 | +select is( |
| 118 | + (select status from pgflow.step_tasks |
| 119 | + where step_slug = 'first' limit 1), |
| 120 | + 'completed', |
| 121 | + 'Task should be completed' |
| 122 | +); |
| 123 | + |
| 124 | +select finish(); |
| 125 | +rollback; |
| 126 | +``` |
| 127 | + |
| 128 | +### Testing Error Conditions |
| 129 | + |
| 130 | +Verify functions throw expected errors: |
| 131 | + |
| 132 | +```sql |
| 133 | +begin; |
| 134 | +select plan(1); |
| 135 | +select pgflow_tests.reset_db(); |
| 136 | + |
| 137 | +-- Test: Invalid flow slug |
| 138 | +select throws_ok( |
| 139 | + $$ SELECT pgflow.start_flow('nonexistent', '{}') $$, |
| 140 | + 'Flow not found: nonexistent' |
| 141 | +); |
| 142 | + |
| 143 | +select finish(); |
| 144 | +rollback; |
| 145 | +``` |
| 146 | + |
| 147 | +### Testing Realtime Events |
| 148 | + |
| 149 | +Verify realtime notifications are sent: |
| 150 | + |
| 151 | +```sql |
| 152 | +begin; |
| 153 | +select plan(3); |
| 154 | + |
| 155 | +-- CRITICAL: Create partition before testing realtime |
| 156 | +select pgflow_tests.create_realtime_partition(); |
| 157 | + |
| 158 | +select pgflow_tests.reset_db(); |
| 159 | +select pgflow.create_flow('sequential'); |
| 160 | +select pgflow.add_step('sequential', 'first'); |
| 161 | + |
| 162 | +-- Capture run_id in temporary table |
| 163 | +with flow as ( |
| 164 | + select * from pgflow.start_flow('sequential', '{}') |
| 165 | +) |
| 166 | +select run_id into temporary run_ids from flow; |
| 167 | + |
| 168 | +-- Test: Event was sent |
| 169 | +select is( |
| 170 | + pgflow_tests.count_realtime_events( |
| 171 | + 'run:started', |
| 172 | + (select run_id from run_ids) |
| 173 | + ), |
| 174 | + 1::int, |
| 175 | + 'Should send run:started event' |
| 176 | +); |
| 177 | + |
| 178 | +-- Test: Event payload is correct |
| 179 | +select is( |
| 180 | + (select payload->>'status' |
| 181 | + from pgflow_tests.get_realtime_message( |
| 182 | + 'run:started', |
| 183 | + (select run_id from run_ids) |
| 184 | + )), |
| 185 | + 'started', |
| 186 | + 'Event should have correct status' |
| 187 | +); |
| 188 | + |
| 189 | +-- Cleanup |
| 190 | +drop table if exists run_ids; |
| 191 | + |
| 192 | +select finish(); |
| 193 | +rollback; |
| 194 | +``` |
| 195 | + |
| 196 | +## Common Assertions |
| 197 | + |
| 198 | +**Equality checks:** |
| 199 | +```sql |
| 200 | +select is(actual, expected, 'description'); |
| 201 | +``` |
| 202 | + |
| 203 | +**Query result comparison:** |
| 204 | +```sql |
| 205 | +select results_eq( |
| 206 | + $$ SELECT col1, col2 FROM table1 $$, |
| 207 | + $$ VALUES ('val1', 'val2') $$, |
| 208 | + 'description' |
| 209 | +); |
| 210 | +``` |
| 211 | + |
| 212 | +**Boolean checks:** |
| 213 | +```sql |
| 214 | +select ok(boolean_expression, 'description'); |
| 215 | +``` |
| 216 | + |
| 217 | +**Error handling:** |
| 218 | +```sql |
| 219 | +select throws_ok($$ SELECT function_call() $$, 'error message'); |
| 220 | +select lives_ok($$ SELECT function_call() $$, 'should not error'); |
| 221 | +``` |
| 222 | + |
| 223 | +**Pattern matching:** |
| 224 | +```sql |
| 225 | +select alike(actual, 'pattern%', 'description'); |
| 226 | +``` |
| 227 | + |
| 228 | +## Helper Functions |
| 229 | + |
| 230 | +pgflow provides test helpers in `pgflow_tests` schema. See [helpers.md](helpers.md) for complete reference. |
| 231 | + |
| 232 | +**Most commonly used:** |
| 233 | +- `reset_db()` - Clean all pgflow data and queues |
| 234 | +- `setup_flow(slug)` - Create predefined test flow |
| 235 | +- `read_and_start(flow_slug)` - Poll and start tasks |
| 236 | +- `poll_and_complete(flow_slug)` - Poll and complete task |
| 237 | +- `poll_and_fail(flow_slug)` - Poll and fail task |
| 238 | +- `create_realtime_partition()` - Required for realtime tests |
| 239 | +- `count_realtime_events(type, run_id)` - Count events sent |
| 240 | +- `get_realtime_message(type, run_id)` - Get full event message |
| 241 | + |
| 242 | +## Test Organization |
| 243 | + |
| 244 | +**Directory structure:** |
| 245 | +``` |
| 246 | +pkgs/core/supabase/tests/ |
| 247 | +├── start_flow/ # Tests for starting flows |
| 248 | +├── complete_task/ # Tests for task completion |
| 249 | +├── fail_task/ # Tests for task failures |
| 250 | +├── realtime/ # Tests for realtime events |
| 251 | +└── [feature]/ # Group related tests by feature |
| 252 | +``` |
| 253 | + |
| 254 | +**Naming convention:** |
| 255 | +- Test files: `descriptive_name.test.sql` |
| 256 | +- One test file per specific behavior |
| 257 | +- Group related tests in directories |
| 258 | + |
| 259 | +## Running Tests |
| 260 | + |
| 261 | +**Single test:** |
| 262 | +```bash |
| 263 | +./scripts/run-test-with-colors pkgs/core/supabase/tests/start_flow/creates_run.test.sql |
| 264 | +``` |
| 265 | + |
| 266 | +**All tests:** |
| 267 | +```bash |
| 268 | +pnpm nx test:pgtap core |
| 269 | +``` |
| 270 | + |
| 271 | +**Watch mode:** |
| 272 | +```bash |
| 273 | +pnpm nx test:pgtap:watch core |
| 274 | +``` |
| 275 | + |
| 276 | +## Example Test Files |
| 277 | + |
| 278 | +See existing tests for patterns: |
| 279 | +- `tests/start_flow/creates_run.test.sql` - Basic workflow |
| 280 | +- `tests/complete_task/completes_task_and_updates_dependents.test.sql` - Complex workflow |
| 281 | +- `tests/realtime/start_flow_events.test.sql` - Realtime testing |
| 282 | +- `tests/type_violations/*.test.sql` - Error testing |
0 commit comments