Skip to content

Test Conventions

SMTA tests are pgTap SQL files. This page describes the conventions every test file must follow.

Test files mirror the source structure. Place your test file in the subdirectory that matches what it is testing:

Source areaTest directory
Schema existencetests/schema/
Membership logictests/membership/
Triggerstests/triggers/
Platform functionstests/platform/
Public functionstests/functions/
Invitationstests/invitations/
RLS policiestests/rls/
Edge casestests/edge_cases/

Name the file with a numeric prefix matching others in the directory, e.g. 05_my_feature.sql.

Every test file uses this structure without exception:

-- 01_organization_functions.sql
-- Purpose: Test public organization management functions
BEGIN;
SELECT plan(12);
-- Set auth context before any operation that depends on the current user
SELECT test_helpers.set_auth_user(test_helpers.get_test_user_id('maria@test.bellaitalia.com'));
SELECT lives_ok(
$$SELECT public.create_organization('Test Restaurant', 'A test organization')$$,
'create_organization should succeed'
);
SELECT ok(
EXISTS (SELECT 1 FROM core.organizations WHERE name = 'Test Restaurant'),
'Organization should be created with correct name'
);
SELECT finish();
ROLLBACK;

Key rules:

  1. BEGIN and ROLLBACK wrap every test file. No test leaves persistent state.
  2. SELECT plan(N) declares the exact number of assertions that follow. The number must be accurate — a mismatch causes the test run to fail.
  3. SELECT finish() must appear before ROLLBACK.
  4. Set auth context before any operation that depends on core.get_current_user_id().

Use test_helpers.set_auth_user() to set the acting user for a test. Pass the user ID via test_helpers.get_test_user_id():

-- Correct
SELECT test_helpers.set_auth_user(test_helpers.get_test_user_id('maria@test.bellaitalia.com'));

Do not write to app.current_user_id directly:

-- Do NOT do this
SET app.current_user_id = 'some-uuid';

set_auth_user sets the session variable through the same path that production code uses, ensuring tests reflect real runtime behavior. You can switch auth context mid-test to simulate operations by different users.

SELECT plan(N) must equal the number of pgTap assertion calls in the file. Count every lives_ok, throws_ok, ok, is, isnt, results_eq, row_eq, is_empty, has_table, and similar calls.

If the plan count does not match, pgTap reports a failure regardless of whether the individual assertions passed. Recount before committing.

RLS tests must verify both sides of the policy.

Unauthorized access returns no rows (not an error):

-- Switch to a user with no access
SELECT test_helpers.set_auth_user(test_helpers.get_test_user_id('stranger@example.com'));
SELECT is_empty(
$$SELECT * FROM core.organizations WHERE id = 'the-org-id'$$,
'Unauthorized user should see no rows'
);

Authorized access returns the expected rows:

-- Switch to a member of the organization
SELECT test_helpers.set_auth_user(test_helpers.get_test_user_id('maria@test.bellaitalia.com'));
SELECT ok(
EXISTS (SELECT 1 FROM core.organizations WHERE id = 'the-org-id'),
'Organization member should see the organization'
);

Never test RLS by checking for an error — RLS silently filters rows, it does not raise exceptions.

SituationFunction to use
Operation should succeed without errorlives_ok($$...$$, 'description')
Operation should raise a specific errorthrows_ok($$...$$, error_code, 'description')
Boolean condition should be trueok(condition, 'description')
Two values should be equalis(actual, expected, 'description')
Query should return no rowsis_empty($$...$$, 'description')
Query should return specific rowsresults_eq($$...$$, $$expected...$$, 'description')

While iterating on a test file, run it directly without the full suite:

Terminal window
pg_prove --dbname=postgres tests/your_category/your_test.sql

With explicit connection options:

Terminal window
pg_prove \
--host=localhost \
--port=54322 \
--dbname=postgres \
--username=postgres \
tests/functions/01_organization_functions.sql

Run the full suite before opening a PR:

Terminal window
npm test