Skip to content

Commit 8e1fb05

Browse files
Merge pull request #1506 from liam-hq/test-db-functions-2
test: add database CI workflow and PGTap tests for invite_organization_member
2 parents a7ae297 + 4164166 commit 8e1fb05

4 files changed

Lines changed: 270 additions & 0 deletions

File tree

.github/workflows/database-ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Database CI
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/database-ci.yml"
7+
- "frontend/packages/db/**"
8+
merge_group:
9+
workflow_dispatch:
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
pg_tap_tests:
17+
name: PGTap Tests
18+
timeout-minutes: 10
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22+
- uses: supabase/setup-cli@d347ba47d3fb7eeeddbbc793bc8d4779caf773ea # v1.5.0
23+
with:
24+
version: latest
25+
- name: Supabase Start
26+
run: supabase start
27+
- name: Setup supabase testing
28+
run: |
29+
psql -v ON_ERROR_STOP=1 -U postgres -d postgres -h localhost -p 54322 -f frontend/packages/db/supabase/setup-testing.sql
30+
env:
31+
PGPASSWORD: postgres
32+
- name: Run Tests
33+
run: supabase test db
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
-- Create pgtap extension if it doesn't exist
2+
create extension if not exists pgtap with schema extensions;
3+
4+
-- Create tests schema
5+
CREATE SCHEMA IF NOT EXISTS tests;
6+
7+
-- Drop existing functions to avoid parameter name change errors
8+
DROP FUNCTION IF EXISTS tests.create_supabase_user(text, text, jsonb);
9+
DROP FUNCTION IF EXISTS tests.get_supabase_uid(text);
10+
11+
-- Create test user function with upsert logic
12+
CREATE FUNCTION tests.create_supabase_user(
13+
p_email text,
14+
p_name text DEFAULT NULL,
15+
p_metadata jsonb DEFAULT '{}'::jsonb
16+
) RETURNS uuid AS $$
17+
DECLARE
18+
user_id uuid;
19+
BEGIN
20+
-- Check if user already exists in auth.users
21+
SELECT id INTO user_id FROM auth.users WHERE email = p_email LIMIT 1;
22+
23+
IF user_id IS NULL THEN
24+
-- Create new user if not exists
25+
INSERT INTO auth.users (
26+
instance_id,
27+
id,
28+
aud,
29+
role,
30+
email,
31+
encrypted_password,
32+
email_confirmed_at,
33+
recovery_sent_at,
34+
last_sign_in_at,
35+
raw_app_meta_data,
36+
raw_user_meta_data,
37+
created_at,
38+
updated_at,
39+
confirmation_token,
40+
email_change,
41+
email_change_token_new,
42+
recovery_token
43+
) VALUES (
44+
'00000000-0000-0000-0000-000000000000',
45+
gen_random_uuid(),
46+
'authenticated',
47+
'authenticated',
48+
p_email,
49+
'$2a$10$Ht.3/NM5XGZnvtjP/X.YyeS0/QUJp.O6vQbGPT4WrCeY9fFIFVQQu', -- 'password123'
50+
now(),
51+
NULL,
52+
now(),
53+
'{"provider": "email", "providers": ["email"]}'::jsonb,
54+
CASE WHEN p_name IS NULL THEN '{}' ELSE jsonb_build_object('name', p_name) END || p_metadata,
55+
now(),
56+
now(),
57+
'',
58+
'',
59+
'',
60+
''
61+
) RETURNING id INTO user_id;
62+
63+
-- Try to insert into public.users table, ignore if already exists
64+
BEGIN
65+
INSERT INTO public.users (id, email, name)
66+
VALUES (user_id, p_email, COALESCE(p_name, p_email));
67+
EXCEPTION WHEN unique_violation THEN
68+
-- User already exists in public.users, do nothing
69+
END;
70+
END IF;
71+
72+
RETURN user_id;
73+
END;
74+
$$ LANGUAGE plpgsql;
75+
76+
-- Get user ID function
77+
CREATE OR REPLACE FUNCTION tests.get_supabase_uid(email text) RETURNS uuid AS $$
78+
DECLARE
79+
user_id uuid;
80+
BEGIN
81+
SELECT id INTO user_id FROM auth.users WHERE auth.users.email = $1 LIMIT 1;
82+
RETURN user_id;
83+
END;
84+
$$ LANGUAGE plpgsql;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
-- Test file for invite_organization_member function
2+
BEGIN;
3+
4+
-- Load the pgtap extension
5+
SELECT plan(7);
6+
7+
-- Create test users and organization
8+
SELECT tests.create_supabase_user('inviter@example.com', 'inviter_user');
9+
SELECT tests.create_supabase_user('existing@example.com', 'existing_user');
10+
SELECT tests.create_supabase_user('new@example.com', 'new_user');
11+
SELECT tests.create_supabase_user('reinvite@example.com', 'reinvite_user');
12+
13+
-- Create test organization
14+
INSERT INTO organizations (id, name)
15+
VALUES ('11111111-1111-1111-1111-111111111111', 'Test Organization')
16+
ON CONFLICT DO NOTHING;
17+
18+
-- Store user IDs in temporary variables
19+
DO $$
20+
DECLARE
21+
v_inviter_id uuid;
22+
v_existing_id uuid;
23+
v_new_id uuid;
24+
v_reinvite_id uuid;
25+
BEGIN
26+
-- Get user IDs
27+
SELECT tests.get_supabase_uid('inviter@example.com') INTO v_inviter_id;
28+
SELECT tests.get_supabase_uid('existing@example.com') INTO v_existing_id;
29+
SELECT tests.get_supabase_uid('new@example.com') INTO v_new_id;
30+
SELECT tests.get_supabase_uid('reinvite@example.com') INTO v_reinvite_id;
31+
32+
-- Add inviter and existing user to the organization
33+
INSERT INTO organization_members (user_id, organization_id)
34+
VALUES
35+
(v_inviter_id, '11111111-1111-1111-1111-111111111111'),
36+
(v_existing_id, '11111111-1111-1111-1111-111111111111')
37+
ON CONFLICT DO NOTHING;
38+
39+
-- Create an existing invitation for reinvite user
40+
INSERT INTO invitations (id, email, invite_by_user_id, organization_id, invited_at)
41+
VALUES (
42+
'22222222-2222-2222-2222-222222222222',
43+
'reinvite@example.com',
44+
v_inviter_id,
45+
'11111111-1111-1111-1111-111111111111',
46+
NOW() - INTERVAL '1 day'
47+
)
48+
ON CONFLICT DO NOTHING;
49+
END $$;
50+
51+
-- Set up authentication context for tests
52+
DO $$
53+
DECLARE
54+
v_inviter_id uuid;
55+
BEGIN
56+
-- Get inviter user ID
57+
SELECT tests.get_supabase_uid('inviter@example.com') INTO v_inviter_id;
58+
59+
-- Set up auth context to simulate the inviter user
60+
EXECUTE format('SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claims" = ''{"sub": "%s"}''', v_inviter_id);
61+
END $$;
62+
63+
-- Test 1: Successfully invite a new member
64+
SELECT is(
65+
(SELECT invite_organization_member('new@example.com', '11111111-1111-1111-1111-111111111111')),
66+
'{"success": true, "error": null}'::jsonb,
67+
'Should successfully invite a new member'
68+
);
69+
70+
-- Test 2: Verify the invitation was created
71+
SELECT is(
72+
(SELECT COUNT(*) FROM invitations WHERE email = 'new@example.com' AND organization_id = '11111111-1111-1111-1111-111111111111'),
73+
1::bigint,
74+
'Should create a new invitation record'
75+
);
76+
77+
-- Test 3: Attempt to invite a user who is already a member
78+
SELECT is(
79+
(SELECT invite_organization_member('existing@example.com', '11111111-1111-1111-1111-111111111111')),
80+
'{"success": false, "error": "this user is already a member of the organization"}'::jsonb,
81+
'Should fail when inviting an existing member'
82+
);
83+
84+
-- Test 4: Re-invite a user who already has a pending invitation
85+
SELECT is(
86+
(SELECT invite_organization_member('reinvite@example.com', '11111111-1111-1111-1111-111111111111')),
87+
'{"success": true, "error": null}'::jsonb,
88+
'Should successfully re-invite a user'
89+
);
90+
91+
-- Test 5: Verify the invitation was updated, not duplicated
92+
SELECT is(
93+
(SELECT COUNT(*) FROM invitations WHERE email = 'reinvite@example.com' AND organization_id = '11111111-1111-1111-1111-111111111111'),
94+
1::bigint,
95+
'Should not create duplicate invitation'
96+
);
97+
98+
-- Test 6: Verify the invited_at timestamp was updated
99+
SELECT ok(
100+
(SELECT invited_at > (NOW() - INTERVAL '10 seconds')
101+
FROM invitations
102+
WHERE email = 'reinvite@example.com' AND organization_id = '11111111-1111-1111-1111-111111111111'),
103+
'Should update the invited_at timestamp'
104+
);
105+
106+
-- Test 7: Case insensitivity test
107+
SELECT is(
108+
(SELECT invite_organization_member('NEW@example.com', '11111111-1111-1111-1111-111111111111')),
109+
'{"success": true, "error": null}'::jsonb,
110+
'Should be case insensitive when checking existing invitations'
111+
);
112+
113+
-- Reset authentication context
114+
RESET ROLE;
115+
116+
-- Finish the tests and print a diagnostic count
117+
SELECT * FROM finish();
118+
119+
ROLLBACK;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
3+
# Script to set up and run PostgreSQL RPC function tests
4+
5+
# Check if Supabase CLI is installed
6+
if ! command -v pnpm &> /dev/null; then
7+
echo "pnpm is not installed. Please install it first."
8+
exit 1
9+
fi
10+
11+
PROJECT_ROOT=$(pwd)
12+
echo "Project root: $PROJECT_ROOT"
13+
14+
# Check if Supabase is running
15+
pnpm --filter @liam-hq/db exec supabase status > /dev/null 2>&1
16+
if [ $? -ne 0 ]; then
17+
echo "Starting Supabase..."
18+
pnpm --filter @liam-hq/db exec supabase start
19+
else
20+
echo "Supabase is already running."
21+
fi
22+
23+
# Set up the testing environment
24+
echo "Setting up the testing environment..."
25+
PGPASSWORD=postgres psql -U postgres -d postgres -h localhost -p 54322 -f "$PROJECT_ROOT/frontend/packages/db/supabase/setup-testing.sql"
26+
27+
# Change to the directory with the tests
28+
cd "$PROJECT_ROOT/frontend/packages/db/supabase"
29+
30+
# Run the tests
31+
echo "Running tests..."
32+
pnpm --filter @liam-hq/db exec supabase test db
33+
34+
echo "Tests completed."

0 commit comments

Comments
 (0)