-
Notifications
You must be signed in to change notification settings - Fork 12
/
supabase_test_helpers--0.0.1.sql
246 lines (230 loc) · 8.13 KB
/
supabase_test_helpers--0.0.1.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION supabase_test_helpers" to load this file. \quit
-- We want to store all of this in the tests schema to keep it
-- separate from any application data
CREATE SCHEMA IF NOT EXISTS tests;
-- anon, authenticated, and service_role should have access to tests schema
GRANT USAGE ON SCHEMA tests TO anon, authenticated, service_role;
-- Don't allow public to execute any functions in the tests schema
ALTER DEFAULT PRIVILEGES IN SCHEMA tests REVOKE EXECUTE ON FUNCTIONS FROM public;
-- Grant execute to anon, authenticated, and service_role for testing purposes
ALTER DEFAULT PRIVILEGES IN SCHEMA tests GRANT EXECUTE ON FUNCTIONS TO anon, authenticated, service_role;
/**
* ### tests.create_supabase_user(identifier text, email text, phone text)
*
* Creates a new user in the `auth.users` table.
* You can recall a user's info by using `tests.get_supabase_user(identifier text)`.
*
* Parameters:
* - `identifier` - A unique identifier for the user. We recommend you keep it memorable like "test_owner" or "test_member"
* - `email` - (Optional) The email address of the user
* - `phone` - (Optional) The phone number of the user
* - `metadata` - (Optional) Additional metadata to be added to the user
*
* Returns:
* - `user_id` - The UUID of the user in the `auth.users` table
*
* Example:
* ```sql
* SELECT tests.create_supabase_user('test_owner');
* SELECT tests.create_supabase_user('test_member', '[email protected]', '555-555-5555');
* SELECT tests.create_supabase_user('test_member', '[email protected]', '555-555-5555', '{"key": "value"}'::jsonb);
* ```
*/
CREATE OR REPLACE FUNCTION tests.create_supabase_user(identifier text, email text default null, phone text default null, metadata jsonb default null)
RETURNS uuid
SECURITY DEFINER
SET search_path = auth, pg_temp
AS $$
DECLARE
user_id uuid;
BEGIN
-- create the user
user_id := extensions.uuid_generate_v4();
INSERT INTO auth.users (id, email, phone, raw_user_meta_data)
VALUES (user_id, coalesce(email, concat(user_id, '@test.com')), phone, jsonb_build_object('test_identifier', identifier) || coalesce(metadata, '{}'::jsonb))
RETURNING id INTO user_id;
RETURN user_id;
END;
$$ LANGUAGE plpgsql;
/**
* ### tests.get_supabase_user(identifier text)
*
* Returns the user info for a user created with `tests.create_supabase_user`.
*
* Parameters:
* - `identifier` - The unique identifier for the user
*
* Returns:
* - `user_id` - The UUID of the user in the `auth.users` table
*
* Example:
* ```sql
* SELECT posts where posts.user_id = tests.get_supabase_user('test_owner') -> 'id';
* ```
*/
CREATE OR REPLACE FUNCTION tests.get_supabase_user(identifier text)
RETURNS json
SECURITY DEFINER
SET search_path = auth, pg_temp
AS $$
DECLARE
supabase_user json;
BEGIN
SELECT json_build_object('id', id, 'email', email, 'phone', phone, 'raw_user_meta_data', raw_user_meta_data) into supabase_user FROM auth.users WHERE raw_user_meta_data ->> 'test_identifier' = identifier limit 1;
if supabase_user is null OR supabase_user -> 'id' IS NULL then
RAISE EXCEPTION 'User with identifier % not found', identifier;
end if;
RETURN supabase_user;
END;
$$ LANGUAGE plpgsql;
/**
* ### tests.get_supabase_uid(identifier text)
*
* Returns the user UUID for a user created with `tests.create_supabase_user`.
*
* Parameters:
* - `identifier` - The unique identifier for the user
*
* Returns:
* - `user_id` - The UUID of the user in the `auth.users` table
*
* Example:
* ```sql
* SELECT posts where posts.user_id = tests.get_supabase_uid('test_owner') -> 'id';
* ```
*/
CREATE OR REPLACE FUNCTION tests.get_supabase_uid(identifier text)
RETURNS uuid
SECURITY DEFINER
SET search_path = auth, pg_temp
AS $$
DECLARE
supabase_user uuid;
BEGIN
SELECT id into supabase_user FROM auth.users WHERE raw_user_meta_data ->> 'test_identifier' = identifier limit 1;
if supabase_user is null then
RAISE EXCEPTION 'User with identifier % not found', identifier;
end if;
RETURN supabase_user;
END;
$$ LANGUAGE plpgsql;
/**
* ### tests.authenticate_as(identifier text)
* Authenticates as a user created with `tests.create_supabase_user`.
*
* Parameters:
* - `identifier` - The unique identifier for the user
*
* Returns:
* - `void`
*
* Example:
* ```sql
* SELECT tests.create_supabase_user('test_owner');
* SELECT tests.authenticate_as('test_owner');
* ```
*/
CREATE OR REPLACE FUNCTION tests.authenticate_as (identifier text)
RETURNS void
AS $$
DECLARE
user_data json;
original_auth_data text;
BEGIN
-- store the request.jwt.claims in a variable in case we need it
original_auth_data := current_setting('request.jwt.claims', true);
user_data := tests.get_supabase_user(identifier);
if user_data is null OR user_data ->> 'id' IS NULL then
RAISE EXCEPTION 'User with identifier % not found', identifier;
end if;
perform set_config('role', 'authenticated', true);
perform set_config('request.jwt.claims', json_build_object('sub', user_data ->> 'id', 'email', user_data ->> 'email', 'phone', user_data ->> 'phone')::text, true);
EXCEPTION
-- revert back to original auth data
WHEN OTHERS THEN
set local role authenticated;
set local "request.jwt.claims" to original_auth_data;
RAISE;
END
$$ LANGUAGE plpgsql;
/**
* ### tests.clear_authentication()
* Clears out the authentication and sets role to anon
*
* Returns:
* - `void`
*
* Example:
* ```sql
* SELECT tests.create_supabase_user('test_owner');
* SELECT tests.authenticate_as('test_owner');
* SELECT tests.clear_authentication();
* ```
*/
CREATE OR REPLACE FUNCTION tests.clear_authentication()
RETURNS void AS $$
BEGIN
perform set_config('role', 'anon', true);
perform set_config('request.jwt.claims', null, true);
END
$$ LANGUAGE plpgsql;
/**
* ### tests.rls_enabled(testing_schema text)
* pgTAP function to check if RLS is enabled on all tables in a provided schema
*
* Parameters:
* - schema_name text - The name of the schema to check
*
* Example:
* ```sql
* BEGIN;
* select plan(1);
* select tests.rls_enabled('public');
* SELECT * FROM finish();
* ROLLBACK;
* ```
*/
CREATE OR REPLACE FUNCTION tests.rls_enabled (testing_schema text)
RETURNS text AS $$
select is(
(select
count(pc.relname)::integer
from pg_class pc
join pg_namespace pn on pn.oid = pc.relnamespace and pn.nspname = rls_enabled.testing_schema
join pg_type pt on pt.oid = pc.reltype
where relrowsecurity = FALSE)
,
0,
'All tables in the' || testing_schema || ' schema should have row level security enabled');
$$ LANGUAGE sql;
/**
* ### tests.rls_enabled(testing_schema text, testing_table text)
* pgTAP function to check if RLS is enabled on a specific table
*
* Parameters:
* - schema_name text - The name of the schema to check
* - testing_table text - The name of the table to check
*
* Example:
* ```sql
* BEGIN;
* select plan(1);
* select tests.rls_enabled('public', 'accounts');
* SELECT * FROM finish();
* ROLLBACK;
* ```
*/
CREATE OR REPLACE FUNCTION tests.rls_enabled (testing_schema text, testing_table text)
RETURNS TEXT AS $$
select is(
(select
count(*)::integer
from pg_class pc
join pg_namespace pn on pn.oid = pc.relnamespace and pn.nspname = rls_enabled.testing_schema and pc.relname = rls_enabled.testing_table
join pg_type pt on pt.oid = pc.reltype
where relrowsecurity = TRUE),
1,
testing_table || 'table in the' || testing_schema || ' schema should have row level security enabled'
);
$$ LANGUAGE sql;