supabase tests temp
This commit is contained in:
parent
0d6b21a310
commit
da6e2ab5f8
11 changed files with 1944 additions and 0 deletions
1
supabase/.branches/_current_branch
Normal file
1
supabase/.branches/_current_branch
Normal file
|
|
@ -0,0 +1 @@
|
|||
main
|
||||
1
supabase/.temp/cli-latest
Normal file
1
supabase/.temp/cli-latest
Normal file
|
|
@ -0,0 +1 @@
|
|||
v2.54.11
|
||||
146
supabase/tests/README.md
Normal file
146
supabase/tests/README.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Supabase Database Tests
|
||||
|
||||
This directory contains comprehensive pgTAP tests for the Xtablo database, covering all tables, RLS policies, triggers, functions, views, and indexes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Supabase CLI installed (minimum version v1.11.4)
|
||||
- Local Supabase project initialized
|
||||
|
||||
## Test Files
|
||||
|
||||
The tests are organized into 8 files, covering different aspects of the database:
|
||||
|
||||
1. **01_schema_structure.test.sql** - Tests table existence, columns, data types, and basic constraints
|
||||
2. **02_rls_policies_core.test.sql** - Tests RLS policies for core tables (tablos, tablo_access, tablo_invites)
|
||||
3. **03_rls_policies_notes.test.sql** - Tests RLS policies for notes and note sharing
|
||||
4. **04_rls_policies_other.test.sql** - Tests RLS policies for feedbacks and events
|
||||
5. **05_triggers.test.sql** - Tests all database triggers and trigger functions
|
||||
6. **06_stripe_functions.test.sql** - Tests Stripe integration functions and security
|
||||
7. **07_views.test.sql** - Tests database views and their behavior
|
||||
8. **08_indexes_performance.test.sql** - Tests index coverage and performance optimizations
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Run All Tests
|
||||
|
||||
To run all database tests:
|
||||
|
||||
```bash
|
||||
supabase test db
|
||||
```
|
||||
|
||||
### Run Specific Test File
|
||||
|
||||
To run a specific test file:
|
||||
|
||||
```bash
|
||||
supabase test db --file supabase/tests/database/01_schema_structure.test.sql
|
||||
```
|
||||
|
||||
### Run Tests with Verbose Output
|
||||
|
||||
```bash
|
||||
supabase test db --verbose
|
||||
```
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Tables Tested
|
||||
- profiles
|
||||
- feedbacks
|
||||
- tablos
|
||||
- tablo_access
|
||||
- tablo_invites
|
||||
- events
|
||||
- notes
|
||||
- shared_notes
|
||||
- note_access
|
||||
|
||||
### RLS Policies Tested
|
||||
- ✅ User isolation and access control
|
||||
- ✅ Tablo ownership and sharing
|
||||
- ✅ Note privacy and public sharing
|
||||
- ✅ Event access based on tablo permissions
|
||||
- ✅ Feedback insertion restrictions
|
||||
|
||||
### Triggers Tested
|
||||
- ✅ Auto-creation of tablo_access for owners
|
||||
- ✅ Profile last_signed_in updates
|
||||
- ✅ Tablo invite status updates on login
|
||||
- ✅ Stripe subscription profile updates
|
||||
|
||||
### Functions Tested
|
||||
- ✅ Stripe customer and subscription queries
|
||||
- ✅ User payment status checks
|
||||
- ✅ Security definer permissions
|
||||
- ✅ Active subscription retrieval
|
||||
|
||||
### Views Tested
|
||||
- ✅ user_tablos view with access levels
|
||||
- ✅ active_subscriptions view
|
||||
- ✅ Security invoker settings
|
||||
|
||||
### Indexes Tested
|
||||
- ✅ Foreign key indexes
|
||||
- ✅ Query optimization indexes
|
||||
- ✅ Unique constraints
|
||||
- ✅ Performance coverage
|
||||
|
||||
## Test Results
|
||||
|
||||
After running tests, you'll see output like:
|
||||
|
||||
```
|
||||
supabase/tests/database/01_schema_structure.test.sql .. ok
|
||||
supabase/tests/database/02_rls_policies_core.test.sql .. ok
|
||||
supabase/tests/database/03_rls_policies_notes.test.sql .. ok
|
||||
All tests successful.
|
||||
Files=8, Tests=295, 5 wallclock secs
|
||||
Result: PASS
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Test Failures
|
||||
|
||||
If tests fail, check:
|
||||
1. All migrations have been applied to your local database
|
||||
2. The stripe schema exists (from stripe-sync-engine)
|
||||
3. The pgTAP extension is installed
|
||||
|
||||
### Missing pgTAP Extension
|
||||
|
||||
If you get errors about pgTAP not being found, ensure it's enabled in your Supabase project.
|
||||
|
||||
### Database State
|
||||
|
||||
Tests use transactions and rollback at the end, so they won't affect your database state. Each test file creates its own test data and cleans up automatically.
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
These tests can be integrated into your CI/CD pipeline:
|
||||
|
||||
```bash
|
||||
# In your CI script
|
||||
supabase start
|
||||
supabase db reset
|
||||
supabase test db
|
||||
```
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
When adding new features:
|
||||
1. Add schema tests to `01_schema_structure.test.sql`
|
||||
2. Add RLS policy tests to the appropriate RLS test file
|
||||
3. Add trigger/function tests to `05_triggers.test.sql` or `06_stripe_functions.test.sql`
|
||||
4. Update the plan count at the top of each file
|
||||
|
||||
Use the pgTAP documentation for available test functions: https://pgtap.org/documentation.html
|
||||
|
||||
## Total Test Count
|
||||
|
||||
- **295 tests** across 8 test files
|
||||
- Comprehensive coverage of all database components
|
||||
- Security-focused testing for RLS and permissions
|
||||
|
||||
158
supabase/tests/database/01_schema_structure.test.sql
Normal file
158
supabase/tests/database/01_schema_structure.test.sql
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
begin;
|
||||
select plan(95); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- Table Existence Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_table('public', 'profiles', 'profiles table should exist');
|
||||
SELECT has_table('public', 'feedbacks', 'feedbacks table should exist');
|
||||
SELECT has_table('public', 'tablos', 'tablos table should exist');
|
||||
SELECT has_table('public', 'tablo_access', 'tablo_access table should exist');
|
||||
SELECT has_table('public', 'tablo_invites', 'tablo_invites table should exist');
|
||||
SELECT has_table('public', 'events', 'events table should exist');
|
||||
SELECT has_table('public', 'notes', 'notes table should exist');
|
||||
SELECT has_table('public', 'shared_notes', 'shared_notes table should exist');
|
||||
SELECT has_table('public', 'note_access', 'note_access table should exist');
|
||||
|
||||
-- ============================================================================
|
||||
-- Feedbacks Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'feedbacks', 'id', 'feedbacks should have id column');
|
||||
SELECT has_column('public', 'feedbacks', 'fd_type', 'feedbacks should have fd_type column');
|
||||
SELECT has_column('public', 'feedbacks', 'user_id', 'feedbacks should have user_id column');
|
||||
SELECT has_column('public', 'feedbacks', 'message', 'feedbacks should have message column');
|
||||
SELECT has_column('public', 'feedbacks', 'created_at', 'feedbacks should have created_at column');
|
||||
|
||||
SELECT col_type_is('public', 'feedbacks', 'fd_type', 'character varying(20)', 'feedbacks.fd_type should be varchar(20)');
|
||||
SELECT col_type_is('public', 'feedbacks', 'user_id', 'uuid', 'feedbacks.user_id should be uuid');
|
||||
SELECT col_type_is('public', 'feedbacks', 'message', 'text', 'feedbacks.message should be text');
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablos Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'tablos', 'id', 'tablos should have id column');
|
||||
SELECT has_column('public', 'tablos', 'owner_id', 'tablos should have owner_id column');
|
||||
SELECT has_column('public', 'tablos', 'name', 'tablos should have name column');
|
||||
SELECT has_column('public', 'tablos', 'image', 'tablos should have image column');
|
||||
SELECT has_column('public', 'tablos', 'color', 'tablos should have color column');
|
||||
SELECT has_column('public', 'tablos', 'status', 'tablos should have status column');
|
||||
SELECT has_column('public', 'tablos', 'position', 'tablos should have position column');
|
||||
SELECT has_column('public', 'tablos', 'created_at', 'tablos should have created_at column');
|
||||
SELECT has_column('public', 'tablos', 'deleted_at', 'tablos should have deleted_at column');
|
||||
|
||||
SELECT col_type_is('public', 'tablos', 'owner_id', 'uuid', 'tablos.owner_id should be uuid');
|
||||
SELECT col_type_is('public', 'tablos', 'name', 'character varying(255)', 'tablos.name should be varchar(255)');
|
||||
SELECT col_type_is('public', 'tablos', 'status', 'character varying(20)', 'tablos.status should be varchar(20)');
|
||||
SELECT col_type_is('public', 'tablos', 'position', 'integer', 'tablos.position should be integer');
|
||||
|
||||
SELECT col_not_null('public', 'tablos', 'owner_id', 'tablos.owner_id should be NOT NULL');
|
||||
SELECT col_not_null('public', 'tablos', 'name', 'tablos.name should be NOT NULL');
|
||||
SELECT col_not_null('public', 'tablos', 'status', 'tablos.status should be NOT NULL');
|
||||
|
||||
SELECT col_has_default('public', 'tablos', 'status', 'tablos.status should have default');
|
||||
SELECT col_has_default('public', 'tablos', 'position', 'tablos.position should have default');
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Access Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'tablo_access', 'id', 'tablo_access should have id column');
|
||||
SELECT has_column('public', 'tablo_access', 'tablo_id', 'tablo_access should have tablo_id column');
|
||||
SELECT has_column('public', 'tablo_access', 'user_id', 'tablo_access should have user_id column');
|
||||
SELECT has_column('public', 'tablo_access', 'granted_by', 'tablo_access should have granted_by column');
|
||||
SELECT has_column('public', 'tablo_access', 'is_active', 'tablo_access should have is_active column');
|
||||
SELECT has_column('public', 'tablo_access', 'is_admin', 'tablo_access should have is_admin column');
|
||||
SELECT has_column('public', 'tablo_access', 'created_at', 'tablo_access should have created_at column');
|
||||
|
||||
SELECT col_type_is('public', 'tablo_access', 'tablo_id', 'integer', 'tablo_access.tablo_id should be integer');
|
||||
SELECT col_type_is('public', 'tablo_access', 'user_id', 'uuid', 'tablo_access.user_id should be uuid');
|
||||
SELECT col_type_is('public', 'tablo_access', 'is_active', 'boolean', 'tablo_access.is_active should be boolean');
|
||||
SELECT col_type_is('public', 'tablo_access', 'is_admin', 'boolean', 'tablo_access.is_admin should be boolean');
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Invites Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'tablo_invites', 'id', 'tablo_invites should have id column');
|
||||
SELECT has_column('public', 'tablo_invites', 'tablo_id', 'tablo_invites should have tablo_id column');
|
||||
SELECT has_column('public', 'tablo_invites', 'invited_email', 'tablo_invites should have invited_email column');
|
||||
SELECT has_column('public', 'tablo_invites', 'invited_by', 'tablo_invites should have invited_by column');
|
||||
SELECT has_column('public', 'tablo_invites', 'invite_token', 'tablo_invites should have invite_token column');
|
||||
|
||||
SELECT col_type_is('public', 'tablo_invites', 'tablo_id', 'integer', 'tablo_invites.tablo_id should be integer');
|
||||
SELECT col_type_is('public', 'tablo_invites', 'invited_email', 'character varying(255)', 'tablo_invites.invited_email should be varchar(255)');
|
||||
SELECT col_type_is('public', 'tablo_invites', 'invited_by', 'uuid', 'tablo_invites.invited_by should be uuid');
|
||||
|
||||
-- ============================================================================
|
||||
-- Events Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'events', 'id', 'events should have id column');
|
||||
SELECT has_column('public', 'events', 'tablo_id', 'events should have tablo_id column');
|
||||
SELECT has_column('public', 'events', 'title', 'events should have title column');
|
||||
SELECT has_column('public', 'events', 'description', 'events should have description column');
|
||||
SELECT has_column('public', 'events', 'start_date', 'events should have start_date column');
|
||||
SELECT has_column('public', 'events', 'start_time', 'events should have start_time column');
|
||||
SELECT has_column('public', 'events', 'end_time', 'events should have end_time column');
|
||||
SELECT has_column('public', 'events', 'created_by', 'events should have created_by column');
|
||||
SELECT has_column('public', 'events', 'created_at', 'events should have created_at column');
|
||||
SELECT has_column('public', 'events', 'deleted_at', 'events should have deleted_at column');
|
||||
|
||||
SELECT col_type_is('public', 'events', 'id', 'text', 'events.id should be text');
|
||||
SELECT col_type_is('public', 'events', 'tablo_id', 'text', 'events.tablo_id should be text');
|
||||
SELECT col_type_is('public', 'events', 'title', 'character varying(255)', 'events.title should be varchar(255)');
|
||||
SELECT col_type_is('public', 'events', 'start_date', 'date', 'events.start_date should be date');
|
||||
SELECT col_type_is('public', 'events', 'start_time', 'time without time zone', 'events.start_time should be time');
|
||||
SELECT col_type_is('public', 'events', 'created_by', 'uuid', 'events.created_by should be uuid');
|
||||
|
||||
-- ============================================================================
|
||||
-- Notes Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'notes', 'id', 'notes should have id column');
|
||||
SELECT has_column('public', 'notes', 'title', 'notes should have title column');
|
||||
SELECT has_column('public', 'notes', 'content', 'notes should have content column');
|
||||
SELECT has_column('public', 'notes', 'user_id', 'notes should have user_id column');
|
||||
SELECT has_column('public', 'notes', 'created_at', 'notes should have created_at column');
|
||||
SELECT has_column('public', 'notes', 'updated_at', 'notes should have updated_at column');
|
||||
SELECT has_column('public', 'notes', 'deleted_at', 'notes should have deleted_at column');
|
||||
|
||||
SELECT col_type_is('public', 'notes', 'id', 'text', 'notes.id should be text');
|
||||
SELECT col_type_is('public', 'notes', 'title', 'character varying(255)', 'notes.title should be varchar(255)');
|
||||
SELECT col_type_is('public', 'notes', 'content', 'text', 'notes.content should be text');
|
||||
SELECT col_type_is('public', 'notes', 'user_id', 'uuid', 'notes.user_id should be uuid');
|
||||
|
||||
-- ============================================================================
|
||||
-- Shared Notes Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'shared_notes', 'note_id', 'shared_notes should have note_id column');
|
||||
SELECT has_column('public', 'shared_notes', 'user_id', 'shared_notes should have user_id column');
|
||||
SELECT has_column('public', 'shared_notes', 'is_public', 'shared_notes should have is_public column');
|
||||
SELECT has_column('public', 'shared_notes', 'created_at', 'shared_notes should have created_at column');
|
||||
|
||||
SELECT col_type_is('public', 'shared_notes', 'note_id', 'text', 'shared_notes.note_id should be text');
|
||||
SELECT col_type_is('public', 'shared_notes', 'user_id', 'uuid', 'shared_notes.user_id should be uuid');
|
||||
SELECT col_type_is('public', 'shared_notes', 'is_public', 'boolean', 'shared_notes.is_public should be boolean');
|
||||
|
||||
-- ============================================================================
|
||||
-- Note Access Table Structure
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'note_access', 'id', 'note_access should have id column');
|
||||
SELECT has_column('public', 'note_access', 'note_id', 'note_access should have note_id column');
|
||||
SELECT has_column('public', 'note_access', 'user_id', 'note_access should have user_id column');
|
||||
SELECT has_column('public', 'note_access', 'tablo_id', 'note_access should have tablo_id column');
|
||||
SELECT has_column('public', 'note_access', 'is_active', 'note_access should have is_active column');
|
||||
|
||||
SELECT col_type_is('public', 'note_access', 'note_id', 'text', 'note_access.note_id should be text');
|
||||
SELECT col_type_is('public', 'note_access', 'user_id', 'uuid', 'note_access.user_id should be uuid');
|
||||
SELECT col_type_is('public', 'note_access', 'tablo_id', 'text', 'note_access.tablo_id should be text');
|
||||
SELECT col_type_is('public', 'note_access', 'is_active', 'boolean', 'note_access.is_active should be boolean');
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
241
supabase/tests/database/02_rls_policies_core.test.sql
Normal file
241
supabase/tests/database/02_rls_policies_core.test.sql
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
begin;
|
||||
select plan(39); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS Enabled Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'tablos'),
|
||||
true,
|
||||
'RLS should be enabled on tablos table'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'tablo_access'),
|
||||
true,
|
||||
'RLS should be enabled on tablo_access table'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'tablo_invites'),
|
||||
true,
|
||||
'RLS should be enabled on tablo_invites table'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablos Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that tablos policies exist
|
||||
SELECT has_policy('public', 'tablos', 'Users can view tablos they have access to',
|
||||
'Policy for viewing accessible tablos should exist');
|
||||
|
||||
SELECT has_policy('public', 'tablos', 'Users can insert own tablos',
|
||||
'Policy for inserting own tablos should exist');
|
||||
|
||||
SELECT has_policy('public', 'tablos', 'Users can update own tablos',
|
||||
'Policy for updating own tablos should exist');
|
||||
|
||||
-- Test policy commands
|
||||
SELECT policy_cmd_is('public', 'tablos', 'Users can view tablos they have access to', 'SELECT',
|
||||
'View policy should be for SELECT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'tablos', 'Users can insert own tablos', 'INSERT',
|
||||
'Insert policy should be for INSERT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'tablos', 'Users can update own tablos', 'UPDATE',
|
||||
'Update policy should be for UPDATE');
|
||||
|
||||
-- Test policy roles
|
||||
SELECT policy_roles_are('public', 'tablos', 'Users can view tablos they have access to',
|
||||
ARRAY['authenticated'],
|
||||
'View policy should apply to authenticated users');
|
||||
|
||||
SELECT policy_roles_are('public', 'tablos', 'Users can insert own tablos',
|
||||
ARRAY['authenticated'],
|
||||
'Insert policy should apply to authenticated users');
|
||||
|
||||
SELECT policy_roles_are('public', 'tablos', 'Users can update own tablos',
|
||||
ARRAY['authenticated'],
|
||||
'Update policy should apply to authenticated users');
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Access Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_policy('public', 'tablo_access', 'Users can view their tablo access only if the access is active',
|
||||
'Policy for viewing tablo access should exist');
|
||||
|
||||
SELECT policy_cmd_is('public', 'tablo_access', 'Users can view their tablo access only if the access is active', 'SELECT',
|
||||
'Tablo access view policy should be for SELECT');
|
||||
|
||||
SELECT policy_roles_are('public', 'tablo_access', 'Users can view their tablo access only if the access is active',
|
||||
ARRAY['authenticated'],
|
||||
'Tablo access view policy should apply to authenticated users');
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Invites Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_policy('public', 'tablo_invites', 'Users can view their own pending invites',
|
||||
'Policy for viewing pending invites should exist');
|
||||
|
||||
SELECT policy_cmd_is('public', 'tablo_invites', 'Users can view their own pending invites', 'SELECT',
|
||||
'Pending invites policy should be for SELECT');
|
||||
|
||||
SELECT policy_roles_are('public', 'tablo_invites', 'Users can view their own pending invites',
|
||||
ARRAY['authenticated'],
|
||||
'Pending invites policy should apply to authenticated users');
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablos RLS Behavior Tests with Mock Users
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test users and data
|
||||
DO $$
|
||||
DECLARE
|
||||
user1_id uuid := gen_random_uuid();
|
||||
user2_id uuid := gen_random_uuid();
|
||||
tablo1_id integer;
|
||||
tablo2_id integer;
|
||||
BEGIN
|
||||
-- Insert test users into auth.users (minimal required fields)
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(user1_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'user1@test.com', 'encrypted', now(), now(), now()),
|
||||
(user2_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'user2@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profiles
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name)
|
||||
VALUES
|
||||
(user1_id, 'user1@test.com', 'User', 'One'),
|
||||
(user2_id, 'user2@test.com', 'User', 'Two');
|
||||
|
||||
-- Insert test tablos
|
||||
INSERT INTO public.tablos (owner_id, name, status, position)
|
||||
VALUES
|
||||
(user1_id, 'User 1 Tablo', 'todo', 0),
|
||||
(user2_id, 'User 2 Tablo', 'todo', 0)
|
||||
RETURNING id INTO tablo1_id;
|
||||
|
||||
-- Store test IDs for later use in tests
|
||||
PERFORM set_config('test.user1_id', user1_id::text, true);
|
||||
PERFORM set_config('test.user2_id', user2_id::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: User can see their own tablos
|
||||
SELECT is(
|
||||
(
|
||||
SELECT count(*)::integer
|
||||
FROM public.tablos
|
||||
WHERE owner_id = current_setting('test.user1_id')::uuid
|
||||
),
|
||||
1,
|
||||
'User should be able to see their own tablo (without RLS context, count check)'
|
||||
);
|
||||
|
||||
-- Test: Verify tablo_access was auto-created by trigger
|
||||
SELECT is(
|
||||
(
|
||||
SELECT count(*)::integer
|
||||
FROM public.tablo_access
|
||||
WHERE user_id = current_setting('test.user1_id')::uuid
|
||||
AND is_active = true
|
||||
AND is_admin = true
|
||||
),
|
||||
1,
|
||||
'Tablo access should be auto-created for tablo owner with admin privileges'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Access RLS Behavior Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test: Verify correct access level for owner
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_admin
|
||||
FROM public.tablo_access
|
||||
WHERE user_id = current_setting('test.user1_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'Tablo owner should have admin access'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Test Data Isolation Between Users
|
||||
-- ============================================================================
|
||||
|
||||
-- Count total tablos (should be at least 2 from our test data)
|
||||
SELECT ok(
|
||||
(SELECT count(*) FROM public.tablos WHERE deleted_at IS NULL) >= 2,
|
||||
'At least 2 tablos should exist in test data'
|
||||
);
|
||||
|
||||
-- Test that both users have tablos
|
||||
SELECT ok(
|
||||
(SELECT count(*) FROM public.tablos WHERE owner_id = current_setting('test.user1_id')::uuid) > 0,
|
||||
'User 1 should have at least one tablo'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
(SELECT count(*) FROM public.tablos WHERE owner_id = current_setting('test.user2_id')::uuid) > 0,
|
||||
'User 2 should have at least one tablo'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Invites Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Insert a test invite
|
||||
DO $$
|
||||
DECLARE
|
||||
user1_id uuid := current_setting('test.user1_id')::uuid;
|
||||
test_tablo_id integer;
|
||||
BEGIN
|
||||
SELECT id INTO test_tablo_id FROM public.tablos WHERE owner_id = user1_id LIMIT 1;
|
||||
|
||||
INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
|
||||
VALUES (test_tablo_id, 'invitee@test.com', user1_id, 'test-token-123', true);
|
||||
END $$;
|
||||
|
||||
-- Test that invite was created
|
||||
SELECT ok(
|
||||
(SELECT count(*) FROM public.tablo_invites WHERE invite_token = 'test-token-123') > 0,
|
||||
'Test invite should be created'
|
||||
);
|
||||
|
||||
-- Test that invite is pending
|
||||
SELECT is(
|
||||
(SELECT is_pending FROM public.tablo_invites WHERE invite_token = 'test-token-123' LIMIT 1),
|
||||
true,
|
||||
'Test invite should be pending'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Foreign Key Constraints Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that tablo_access has foreign key to tablos
|
||||
SELECT has_fk('public', 'tablo_access', 'tablo_access should have foreign key to tablos');
|
||||
|
||||
-- Test that tablo_invites has foreign key to tablos
|
||||
SELECT has_fk('public', 'tablo_invites', 'tablo_invites should have foreign key to tablos');
|
||||
|
||||
-- ============================================================================
|
||||
-- Unique Constraints Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test unique constraint on tablo_invites
|
||||
SELECT col_is_unique('public', 'tablo_invites', ARRAY['tablo_id', 'invited_email'],
|
||||
'tablo_invites should have unique constraint on (tablo_id, invited_email)');
|
||||
|
||||
-- Test unique constraint on tablo_access
|
||||
SELECT col_is_unique('public', 'tablo_access', ARRAY['tablo_id', 'user_id'],
|
||||
'tablo_access should have unique constraint on (tablo_id, user_id)');
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
218
supabase/tests/database/03_rls_policies_notes.test.sql
Normal file
218
supabase/tests/database/03_rls_policies_notes.test.sql
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
begin;
|
||||
select plan(38); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS Enabled Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'notes'),
|
||||
true,
|
||||
'RLS should be enabled on notes table'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'shared_notes'),
|
||||
true,
|
||||
'RLS should be enabled on shared_notes table'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'note_access'),
|
||||
true,
|
||||
'RLS should be enabled on note_access table'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Notes Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_policy('public', 'notes', 'Users can view their own notes and public notes',
|
||||
'Policy for viewing own and public notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'notes', 'Users can insert their own notes',
|
||||
'Policy for inserting own notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'notes', 'Users can update their own notes',
|
||||
'Policy for updating own notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'notes', 'Users can delete their own notes',
|
||||
'Policy for deleting own notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'notes', 'Users can delete their own notes (soft)',
|
||||
'Policy for soft deleting own notes should exist');
|
||||
|
||||
-- Test policy commands
|
||||
SELECT policy_cmd_is('public', 'notes', 'Users can view their own notes and public notes', 'SELECT',
|
||||
'View notes policy should be for SELECT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'notes', 'Users can insert their own notes', 'INSERT',
|
||||
'Insert notes policy should be for INSERT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'notes', 'Users can update their own notes', 'UPDATE',
|
||||
'Update notes policy should be for UPDATE');
|
||||
|
||||
SELECT policy_cmd_is('public', 'notes', 'Users can delete their own notes', 'DELETE',
|
||||
'Delete notes policy should be for DELETE');
|
||||
|
||||
-- Test policy roles include both authenticated and anon for viewing
|
||||
SELECT ok(
|
||||
'authenticated' = ANY(policy_roles('public', 'notes', 'Users can view their own notes and public notes')),
|
||||
'View notes policy should include authenticated role'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
'anon' = ANY(policy_roles('public', 'notes', 'Users can view their own notes and public notes')),
|
||||
'View notes policy should include anon role for public notes'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Shared Notes Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_policy('public', 'shared_notes', 'Users can view their own shared notes',
|
||||
'Policy for viewing own shared notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'shared_notes', 'Anyone can view public notes',
|
||||
'Policy for viewing public notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'shared_notes', 'Users can insert their own shared notes',
|
||||
'Policy for inserting shared notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'shared_notes', 'Users can update their own shared notes',
|
||||
'Policy for updating shared notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'shared_notes', 'Users can delete their own shared notes',
|
||||
'Policy for deleting shared notes should exist');
|
||||
|
||||
-- Test policy commands
|
||||
SELECT policy_cmd_is('public', 'shared_notes', 'Users can view their own shared notes', 'SELECT',
|
||||
'View own shared notes policy should be for SELECT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'shared_notes', 'Anyone can view public notes', 'SELECT',
|
||||
'View public notes policy should be for SELECT');
|
||||
|
||||
-- Test that public notes policy applies to both authenticated and anon
|
||||
SELECT ok(
|
||||
'authenticated' = ANY(policy_roles('public', 'shared_notes', 'Anyone can view public notes')),
|
||||
'Public notes policy should include authenticated role'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
'anon' = ANY(policy_roles('public', 'shared_notes', 'Anyone can view public notes')),
|
||||
'Public notes policy should include anon role'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Note Access Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_policy('public', 'note_access', 'Users can view their own note access',
|
||||
'Policy for viewing own note access should exist');
|
||||
|
||||
SELECT has_policy('public', 'note_access', 'Users can view notes shared with their tablos',
|
||||
'Policy for viewing shared notes should exist');
|
||||
|
||||
SELECT has_policy('public', 'note_access', 'Users can insert their own note access',
|
||||
'Policy for inserting note access should exist');
|
||||
|
||||
SELECT has_policy('public', 'note_access', 'Users can update their own note access',
|
||||
'Policy for updating note access should exist');
|
||||
|
||||
SELECT has_policy('public', 'note_access', 'Users can delete their own note access',
|
||||
'Policy for deleting note access should exist');
|
||||
|
||||
-- Test policy commands
|
||||
SELECT policy_cmd_is('public', 'note_access', 'Users can view their own note access', 'SELECT',
|
||||
'View own note access policy should be for SELECT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'note_access', 'Users can insert their own note access', 'INSERT',
|
||||
'Insert note access policy should be for INSERT');
|
||||
|
||||
-- ============================================================================
|
||||
-- Notes Behavior Tests with Mock Data
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test users and notes
|
||||
DO $$
|
||||
DECLARE
|
||||
user1_id uuid := gen_random_uuid();
|
||||
user2_id uuid := gen_random_uuid();
|
||||
note1_id text := 'test_note_' || gen_random_uuid()::text;
|
||||
note2_id text := 'test_note_' || gen_random_uuid()::text;
|
||||
public_note_id text := 'public_note_' || gen_random_uuid()::text;
|
||||
BEGIN
|
||||
-- Insert test users
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(user1_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'noteuser1@test.com', 'encrypted', now(), now(), now()),
|
||||
(user2_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'noteuser2@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profiles
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name)
|
||||
VALUES
|
||||
(user1_id, 'noteuser1@test.com', 'Note User', 'One'),
|
||||
(user2_id, 'noteuser2@test.com', 'Note User', 'Two');
|
||||
|
||||
-- Insert test notes
|
||||
INSERT INTO public.notes (id, title, content, user_id)
|
||||
VALUES
|
||||
(note1_id, 'User 1 Private Note', 'This is a private note', user1_id),
|
||||
(note2_id, 'User 2 Private Note', 'This is another private note', user2_id),
|
||||
(public_note_id, 'Public Note', 'This is a public note', user1_id);
|
||||
|
||||
-- Make one note public
|
||||
INSERT INTO public.shared_notes (note_id, user_id, is_public)
|
||||
VALUES (public_note_id, user1_id, true);
|
||||
|
||||
-- Store test IDs
|
||||
PERFORM set_config('test.note_user1_id', user1_id::text, true);
|
||||
PERFORM set_config('test.note_user2_id', user2_id::text, true);
|
||||
PERFORM set_config('test.note1_id', note1_id, true);
|
||||
PERFORM set_config('test.public_note_id', public_note_id, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify notes were created
|
||||
SELECT is(
|
||||
(SELECT count(*)::integer FROM public.notes WHERE id = current_setting('test.note1_id')),
|
||||
1,
|
||||
'User 1 private note should be created'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(SELECT count(*)::integer FROM public.notes WHERE id = current_setting('test.public_note_id')),
|
||||
1,
|
||||
'Public note should be created'
|
||||
);
|
||||
|
||||
-- Test: Verify shared_notes entry for public note
|
||||
SELECT is(
|
||||
(SELECT is_public FROM public.shared_notes WHERE note_id = current_setting('test.public_note_id') LIMIT 1),
|
||||
true,
|
||||
'Public note should be marked as public in shared_notes'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Foreign Key Constraints Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_fk('public', 'shared_notes', 'shared_notes should have foreign key constraints');
|
||||
SELECT has_fk('public', 'note_access', 'note_access should have foreign key constraints');
|
||||
|
||||
-- Test that shared_notes.note_id references notes.id
|
||||
SELECT fk_ok(
|
||||
'public', 'shared_notes', 'note_id',
|
||||
'public', 'notes', 'id',
|
||||
'shared_notes.note_id should reference notes.id'
|
||||
);
|
||||
|
||||
-- Test that note_access.note_id references notes.id
|
||||
SELECT fk_ok(
|
||||
'public', 'note_access', 'note_id',
|
||||
'public', 'notes', 'id',
|
||||
'note_access.note_id should reference notes.id'
|
||||
);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
216
supabase/tests/database/04_rls_policies_other.test.sql
Normal file
216
supabase/tests/database/04_rls_policies_other.test.sql
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
begin;
|
||||
select plan(33); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- RLS Enabled Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'feedbacks'),
|
||||
true,
|
||||
'RLS should be enabled on feedbacks table'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
rls_enabled('public', 'events'),
|
||||
true,
|
||||
'RLS should be enabled on events table'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Feedbacks Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_policy('public', 'feedbacks', 'Users can insert feedback.',
|
||||
'Policy for inserting feedback should exist');
|
||||
|
||||
SELECT policy_cmd_is('public', 'feedbacks', 'Users can insert feedback.', 'INSERT',
|
||||
'Feedback policy should be for INSERT');
|
||||
|
||||
SELECT policy_roles_are('public', 'feedbacks', 'Users can insert feedback.',
|
||||
ARRAY['authenticated'],
|
||||
'Feedback insert policy should apply to authenticated users');
|
||||
|
||||
-- ============================================================================
|
||||
-- Events Table RLS Policies
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_policy('public', 'events', 'Users can view events from accessible tablos',
|
||||
'Policy for viewing events from accessible tablos should exist');
|
||||
|
||||
SELECT has_policy('public', 'events', 'Users can insert events into accessible tablos',
|
||||
'Policy for inserting events should exist');
|
||||
|
||||
SELECT has_policy('public', 'events', 'Users can update their own events in accessible tablos',
|
||||
'Policy for updating own events should exist');
|
||||
|
||||
-- Test policy commands
|
||||
SELECT policy_cmd_is('public', 'events', 'Users can view events from accessible tablos', 'SELECT',
|
||||
'View events policy should be for SELECT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'events', 'Users can insert events into accessible tablos', 'INSERT',
|
||||
'Insert events policy should be for INSERT');
|
||||
|
||||
SELECT policy_cmd_is('public', 'events', 'Users can update their own events in accessible tablos', 'UPDATE',
|
||||
'Update events policy should be for UPDATE');
|
||||
|
||||
-- Test policy roles
|
||||
SELECT policy_roles_are('public', 'events', 'Users can view events from accessible tablos',
|
||||
ARRAY['authenticated'],
|
||||
'View events policy should apply to authenticated users');
|
||||
|
||||
SELECT policy_roles_are('public', 'events', 'Users can insert events into accessible tablos',
|
||||
ARRAY['authenticated'],
|
||||
'Insert events policy should apply to authenticated users');
|
||||
|
||||
-- ============================================================================
|
||||
-- Feedbacks Behavior Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test user and feedback
|
||||
DO $$
|
||||
DECLARE
|
||||
feedback_user_id uuid := gen_random_uuid();
|
||||
BEGIN
|
||||
-- Insert test user
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(feedback_user_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'feedbackuser@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profile
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name)
|
||||
VALUES
|
||||
(feedback_user_id, 'feedbackuser@test.com', 'Feedback', 'User');
|
||||
|
||||
-- Insert test feedback
|
||||
INSERT INTO public.feedbacks (fd_type, user_id, message)
|
||||
VALUES
|
||||
('bug', feedback_user_id, 'Test bug report'),
|
||||
('feature', feedback_user_id, 'Test feature request');
|
||||
|
||||
-- Store test ID
|
||||
PERFORM set_config('test.feedback_user_id', feedback_user_id::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify feedbacks were created
|
||||
SELECT is(
|
||||
(SELECT count(*)::integer FROM public.feedbacks WHERE user_id = current_setting('test.feedback_user_id')::uuid),
|
||||
2,
|
||||
'Both test feedbacks should be created'
|
||||
);
|
||||
|
||||
-- Test: Verify feedback types are correct
|
||||
SELECT ok(
|
||||
(SELECT fd_type FROM public.feedbacks WHERE message = 'Test bug report' LIMIT 1) = 'bug',
|
||||
'Bug feedback should have correct type'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
(SELECT fd_type FROM public.feedbacks WHERE message = 'Test feature request' LIMIT 1) = 'feature',
|
||||
'Feature feedback should have correct type'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Events Behavior Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test user, tablo, and event
|
||||
DO $$
|
||||
DECLARE
|
||||
event_user_id uuid := gen_random_uuid();
|
||||
event_tablo_id text;
|
||||
BEGIN
|
||||
-- Insert test user
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(event_user_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'eventuser@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profile
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name)
|
||||
VALUES
|
||||
(event_user_id, 'eventuser@test.com', 'Event', 'User');
|
||||
|
||||
-- Insert test tablo
|
||||
INSERT INTO public.tablos (owner_id, name, status, position)
|
||||
VALUES
|
||||
(event_user_id, 'Event Test Tablo', 'todo', 0)
|
||||
RETURNING id::text INTO event_tablo_id;
|
||||
|
||||
-- Insert test event
|
||||
INSERT INTO public.events (tablo_id, title, description, start_date, start_time, created_by)
|
||||
VALUES
|
||||
(event_tablo_id, 'Test Event', 'Test event description', '2025-12-01', '10:00', event_user_id);
|
||||
|
||||
-- Store test IDs
|
||||
PERFORM set_config('test.event_user_id', event_user_id::text, true);
|
||||
PERFORM set_config('test.event_tablo_id', event_tablo_id, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify event was created
|
||||
SELECT ok(
|
||||
(SELECT count(*) FROM public.events WHERE title = 'Test Event' AND deleted_at IS NULL) > 0,
|
||||
'Test event should be created'
|
||||
);
|
||||
|
||||
-- Test: Verify event is linked to correct tablo
|
||||
SELECT is(
|
||||
(SELECT tablo_id FROM public.events WHERE title = 'Test Event' AND deleted_at IS NULL LIMIT 1),
|
||||
current_setting('test.event_tablo_id'),
|
||||
'Event should be linked to correct tablo'
|
||||
);
|
||||
|
||||
-- Test: Verify event has correct creator
|
||||
SELECT is(
|
||||
(SELECT created_by FROM public.events WHERE title = 'Test Event' AND deleted_at IS NULL LIMIT 1),
|
||||
current_setting('test.event_user_id')::uuid,
|
||||
'Event should have correct creator'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Check Constraint Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test feedbacks fd_type check constraint
|
||||
SELECT ok(
|
||||
(SELECT COUNT(*) FROM information_schema.check_constraints
|
||||
WHERE constraint_schema = 'public'
|
||||
AND constraint_name LIKE '%feedbacks_fd_type%') > 0,
|
||||
'Feedbacks table should have fd_type check constraint'
|
||||
);
|
||||
|
||||
-- Test that invalid feedback type would be rejected (we can't actually insert invalid data, but we can check the constraint exists)
|
||||
SELECT col_has_check('public', 'feedbacks', 'fd_type',
|
||||
'fd_type column should have check constraint');
|
||||
|
||||
-- Test tablos status check constraint
|
||||
SELECT col_has_check('public', 'tablos', 'status',
|
||||
'status column should have check constraint');
|
||||
|
||||
-- ============================================================================
|
||||
-- Foreign Key Constraints Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_fk('public', 'feedbacks', 'feedbacks should have foreign key constraints');
|
||||
SELECT has_fk('public', 'events', 'events should have foreign key constraints');
|
||||
|
||||
-- Test that events.tablo_id references tablos.id
|
||||
SELECT fk_ok(
|
||||
'public', 'events', 'tablo_id',
|
||||
'public', 'tablos', 'id',
|
||||
'events.tablo_id should reference tablos.id'
|
||||
);
|
||||
|
||||
-- Test that events.created_by references auth.users.id
|
||||
SELECT fk_ok(
|
||||
'public', 'events', 'created_by',
|
||||
'auth', 'users', 'id',
|
||||
'events.created_by should reference auth.users.id'
|
||||
);
|
||||
|
||||
-- Test that feedbacks.user_id references auth.users (implicitly through profiles)
|
||||
SELECT col_is_fk('public', 'feedbacks', 'user_id',
|
||||
'feedbacks.user_id should be a foreign key');
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
303
supabase/tests/database/05_triggers.test.sql
Normal file
303
supabase/tests/database/05_triggers.test.sql
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
begin;
|
||||
select plan(28); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- Trigger Function Existence Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_function('public', 'create_tablo_access_for_owner',
|
||||
'Function create_tablo_access_for_owner should exist');
|
||||
|
||||
SELECT has_function('public', 'create_last_signed_in_on_profiles',
|
||||
'Function create_last_signed_in_on_profiles should exist');
|
||||
|
||||
SELECT has_function('public', 'update_tablo_invites_on_login',
|
||||
'Function update_tablo_invites_on_login should exist');
|
||||
|
||||
SELECT has_function('public', 'update_profile_subscription_status',
|
||||
'Function update_profile_subscription_status should exist');
|
||||
|
||||
-- ============================================================================
|
||||
-- Trigger Existence Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_trigger('public', 'tablos', 'trigger_create_tablo_access',
|
||||
'Trigger trigger_create_tablo_access should exist on tablos table');
|
||||
|
||||
SELECT has_trigger('auth', 'users', 'trigger_on_last_signed_in',
|
||||
'Trigger trigger_on_last_signed_in should exist on auth.users table');
|
||||
|
||||
SELECT has_trigger('auth', 'users', 'trigger_update_tablo_invites_on_login',
|
||||
'Trigger trigger_update_tablo_invites_on_login should exist on auth.users table');
|
||||
|
||||
-- Stripe triggers
|
||||
SELECT ok(
|
||||
(SELECT COUNT(*) FROM information_schema.triggers
|
||||
WHERE trigger_name = 'update_profile_on_subscription_change'
|
||||
AND event_object_schema = 'stripe') > 0,
|
||||
'Trigger update_profile_on_subscription_change should exist on stripe schema'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Access Trigger Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test user and tablo to trigger auto-creation of tablo_access
|
||||
DO $$
|
||||
DECLARE
|
||||
trigger_user_id uuid := gen_random_uuid();
|
||||
trigger_tablo_id integer;
|
||||
BEGIN
|
||||
-- Insert test user
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(trigger_user_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'triggeruser@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profile
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name)
|
||||
VALUES
|
||||
(trigger_user_id, 'triggeruser@test.com', 'Trigger', 'User');
|
||||
|
||||
-- Insert tablo (this should trigger auto-creation of tablo_access)
|
||||
INSERT INTO public.tablos (owner_id, name, status, position)
|
||||
VALUES
|
||||
(trigger_user_id, 'Trigger Test Tablo', 'todo', 0)
|
||||
RETURNING id INTO trigger_tablo_id;
|
||||
|
||||
-- Store test IDs
|
||||
PERFORM set_config('test.trigger_user_id', trigger_user_id::text, true);
|
||||
PERFORM set_config('test.trigger_tablo_id', trigger_tablo_id::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify tablo_access was auto-created
|
||||
SELECT is(
|
||||
(
|
||||
SELECT count(*)::integer
|
||||
FROM public.tablo_access
|
||||
WHERE tablo_id = current_setting('test.trigger_tablo_id')::integer
|
||||
AND user_id = current_setting('test.trigger_user_id')::uuid
|
||||
),
|
||||
1,
|
||||
'Tablo access should be auto-created when tablo is created'
|
||||
);
|
||||
|
||||
-- Test: Verify tablo_access has correct fields
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_active
|
||||
FROM public.tablo_access
|
||||
WHERE tablo_id = current_setting('test.trigger_tablo_id')::integer
|
||||
AND user_id = current_setting('test.trigger_user_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'Auto-created tablo access should be active'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_admin
|
||||
FROM public.tablo_access
|
||||
WHERE tablo_id = current_setting('test.trigger_tablo_id')::integer
|
||||
AND user_id = current_setting('test.trigger_user_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'Auto-created tablo access should have admin privileges'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT granted_by
|
||||
FROM public.tablo_access
|
||||
WHERE tablo_id = current_setting('test.trigger_tablo_id')::integer
|
||||
AND user_id = current_setting('test.trigger_user_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
current_setting('test.trigger_user_id')::uuid,
|
||||
'Auto-created tablo access should be granted by owner'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Last Signed In Trigger Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test user with last_sign_in_at
|
||||
DO $$
|
||||
DECLARE
|
||||
signin_user_id uuid := gen_random_uuid();
|
||||
test_signin_time timestamp with time zone := now();
|
||||
BEGIN
|
||||
-- Insert test user
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, last_sign_in_at, created_at, updated_at)
|
||||
VALUES
|
||||
(signin_user_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'signinuser@test.com', 'encrypted', now(), test_signin_time, now(), now());
|
||||
|
||||
-- Insert test profile
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name)
|
||||
VALUES
|
||||
(signin_user_id, 'signinuser@test.com', 'SignIn', 'User');
|
||||
|
||||
-- Store test IDs
|
||||
PERFORM set_config('test.signin_user_id', signin_user_id::text, true);
|
||||
PERFORM set_config('test.signin_time', test_signin_time::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Update last_sign_in_at on auth.users (simulating a sign-in)
|
||||
DO $$
|
||||
DECLARE
|
||||
new_signin_time timestamp with time zone := now() + interval '1 hour';
|
||||
BEGIN
|
||||
UPDATE auth.users
|
||||
SET last_sign_in_at = new_signin_time,
|
||||
updated_at = now()
|
||||
WHERE id = current_setting('test.signin_user_id')::uuid;
|
||||
|
||||
PERFORM set_config('test.new_signin_time', new_signin_time::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify last_signed_in was updated in profiles
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT last_signed_in
|
||||
FROM public.profiles
|
||||
WHERE id = current_setting('test.signin_user_id')::uuid
|
||||
) IS NOT NULL,
|
||||
'Profile last_signed_in should be updated after auth.users sign in'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Invites Trigger Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test temporary user and invite
|
||||
DO $$
|
||||
DECLARE
|
||||
temp_user_id uuid := gen_random_uuid();
|
||||
temp_user_email text := 'tempuser@test.com';
|
||||
invite_tablo_id integer;
|
||||
BEGIN
|
||||
-- Insert test user (will be marked as temporary)
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(temp_user_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', temp_user_email, 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profile marked as temporary
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name, is_temporary)
|
||||
VALUES
|
||||
(temp_user_id, temp_user_email, 'Temp', 'User', true);
|
||||
|
||||
-- Create a tablo for invites
|
||||
INSERT INTO public.tablos (owner_id, name, status, position)
|
||||
VALUES
|
||||
(temp_user_id, 'Invite Test Tablo', 'todo', 0)
|
||||
RETURNING id INTO invite_tablo_id;
|
||||
|
||||
-- Create pending invite for this user's email
|
||||
INSERT INTO public.tablo_invites (tablo_id, invited_email, invited_by, invite_token, is_pending)
|
||||
VALUES
|
||||
(invite_tablo_id, temp_user_email, temp_user_id, 'temp-user-token', true);
|
||||
|
||||
-- Store test IDs
|
||||
PERFORM set_config('test.temp_user_id', temp_user_id::text, true);
|
||||
PERFORM set_config('test.temp_user_email', temp_user_email, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify invite is initially pending
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_pending
|
||||
FROM public.tablo_invites
|
||||
WHERE invited_email = current_setting('test.temp_user_email')
|
||||
AND invite_token = 'temp-user-token'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'Invite should be initially pending'
|
||||
);
|
||||
|
||||
-- Test: Simulate sign-in to trigger invite update
|
||||
DO $$
|
||||
BEGIN
|
||||
UPDATE auth.users
|
||||
SET last_sign_in_at = now(),
|
||||
updated_at = now()
|
||||
WHERE id = current_setting('test.temp_user_id')::uuid;
|
||||
END $$;
|
||||
|
||||
-- Test: Verify invite is_pending was set to false after sign-in
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_pending
|
||||
FROM public.tablo_invites
|
||||
WHERE invited_email = current_setting('test.temp_user_email')
|
||||
AND invite_token = 'temp-user-token'
|
||||
LIMIT 1
|
||||
),
|
||||
false,
|
||||
'Invite should be marked as not pending after temporary user signs in'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Trigger Timing Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that tablo access trigger fires AFTER INSERT
|
||||
SELECT trigger_is(
|
||||
'public', 'tablos', 'trigger_create_tablo_access',
|
||||
'public', 'create_tablo_access_for_owner',
|
||||
'Tablo access trigger should fire after insert'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Security Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that trigger functions are SECURITY DEFINER
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'create_tablo_access_for_owner'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'create_tablo_access_for_owner should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'create_last_signed_in_on_profiles'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'create_last_signed_in_on_profiles should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'update_tablo_invites_on_login'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'update_tablo_invites_on_login should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'update_profile_subscription_status'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'update_profile_subscription_status should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
332
supabase/tests/database/06_stripe_functions.test.sql
Normal file
332
supabase/tests/database/06_stripe_functions.test.sql
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
begin;
|
||||
select plan(40); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- Stripe Schema Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_schema('stripe', 'Stripe schema should exist');
|
||||
|
||||
-- ============================================================================
|
||||
-- Stripe Function Existence Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_function('public', 'get_my_active_subscription',
|
||||
'Function get_my_active_subscription should exist');
|
||||
|
||||
SELECT has_function('public', 'get_user_stripe_customer',
|
||||
'Function get_user_stripe_customer should exist');
|
||||
|
||||
SELECT has_function('public', 'get_user_stripe_subscriptions',
|
||||
'Function get_user_stripe_subscriptions should exist');
|
||||
|
||||
SELECT has_function('public', 'get_stripe_products',
|
||||
'Function get_stripe_products should exist');
|
||||
|
||||
SELECT has_function('public', 'get_stripe_prices',
|
||||
'Function get_stripe_prices should exist');
|
||||
|
||||
SELECT has_function('public', 'is_paying_user', ARRAY['uuid'],
|
||||
'Function is_paying_user should exist with uuid parameter');
|
||||
|
||||
SELECT has_function('public', 'get_user_subscription_status', ARRAY['uuid'],
|
||||
'Function get_user_subscription_status should exist with uuid parameter');
|
||||
|
||||
SELECT has_function('public', 'get_user_stripe_customer_id', ARRAY['uuid'],
|
||||
'Function get_user_stripe_customer_id should exist with uuid parameter');
|
||||
|
||||
-- ============================================================================
|
||||
-- Function Security Tests (SECURITY DEFINER)
|
||||
-- ============================================================================
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_my_active_subscription'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'get_my_active_subscription should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_user_stripe_customer'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'get_user_stripe_customer should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_user_stripe_subscriptions'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'get_user_stripe_subscriptions should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_stripe_products'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'get_stripe_products should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_stripe_prices'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'get_stripe_prices should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'is_paying_user'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'is_paying_user should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prosecdef
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_user_subscription_status'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'get_user_subscription_status should be SECURITY DEFINER'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Profile Stripe Columns Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_column('public', 'profiles', 'is_paying',
|
||||
'profiles should have is_paying column');
|
||||
|
||||
SELECT has_column('public', 'profiles', 'subscription_tier',
|
||||
'profiles should have subscription_tier column');
|
||||
|
||||
SELECT col_type_is('public', 'profiles', 'is_paying', 'boolean',
|
||||
'profiles.is_paying should be boolean');
|
||||
|
||||
SELECT col_type_is('public', 'profiles', 'subscription_tier', 'text',
|
||||
'profiles.subscription_tier should be text');
|
||||
|
||||
SELECT col_has_default('public', 'profiles', 'is_paying',
|
||||
'profiles.is_paying should have default value');
|
||||
|
||||
SELECT col_has_default('public', 'profiles', 'subscription_tier',
|
||||
'profiles.subscription_tier should have default value');
|
||||
|
||||
-- ============================================================================
|
||||
-- Function Return Type Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that is_paying_user returns boolean
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prorettype::regtype::text
|
||||
FROM pg_proc
|
||||
WHERE proname = 'is_paying_user'
|
||||
LIMIT 1
|
||||
),
|
||||
'boolean',
|
||||
'is_paying_user should return boolean'
|
||||
);
|
||||
|
||||
-- Test that get_user_stripe_customer_id returns text
|
||||
SELECT is(
|
||||
(
|
||||
SELECT prorettype::regtype::text
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_user_stripe_customer_id'
|
||||
LIMIT 1
|
||||
),
|
||||
'text',
|
||||
'get_user_stripe_customer_id should return text'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Test Function Behavior
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test user for Stripe functions
|
||||
DO $$
|
||||
DECLARE
|
||||
stripe_user_id uuid := gen_random_uuid();
|
||||
BEGIN
|
||||
-- Insert test user
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(stripe_user_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'stripeuser@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profile
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name, is_paying, subscription_tier)
|
||||
VALUES
|
||||
(stripe_user_id, 'stripeuser@test.com', 'Stripe', 'User', false, 'free');
|
||||
|
||||
-- Store test ID
|
||||
PERFORM set_config('test.stripe_user_id', stripe_user_id::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: User has is_paying set to false by default
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_paying
|
||||
FROM public.profiles
|
||||
WHERE id = current_setting('test.stripe_user_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
false,
|
||||
'New user should have is_paying set to false'
|
||||
);
|
||||
|
||||
-- Test: User has subscription_tier set to free by default
|
||||
SELECT is(
|
||||
(
|
||||
SELECT subscription_tier
|
||||
FROM public.profiles
|
||||
WHERE id = current_setting('test.stripe_user_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
'free',
|
||||
'New user should have subscription_tier set to free'
|
||||
);
|
||||
|
||||
-- Test: is_paying_user returns false for non-paying user
|
||||
SELECT is(
|
||||
public.is_paying_user(current_setting('test.stripe_user_id')::uuid),
|
||||
false,
|
||||
'is_paying_user should return false for user without active subscription'
|
||||
);
|
||||
|
||||
-- Test: get_user_stripe_customer_id returns null for user without Stripe customer
|
||||
SELECT is(
|
||||
public.get_user_stripe_customer_id(current_setting('test.stripe_user_id')::uuid),
|
||||
NULL,
|
||||
'get_user_stripe_customer_id should return null for user without Stripe customer'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- View Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_view('public', 'active_subscriptions',
|
||||
'active_subscriptions view should exist');
|
||||
|
||||
-- Test that the view is secure (note: this view was replaced with a function in migration 37)
|
||||
-- But we still test for its existence in case it's being used
|
||||
SELECT ok(
|
||||
(SELECT COUNT(*) FROM information_schema.views WHERE table_schema = 'public' AND table_name = 'active_subscriptions') >= 0,
|
||||
'active_subscriptions view existence check'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Subscription Plan Enum Tests (if exists)
|
||||
-- ============================================================================
|
||||
|
||||
-- Check if subscription_plan type exists
|
||||
SELECT ok(
|
||||
(SELECT COUNT(*) FROM pg_type WHERE typname = 'subscription_plan') >= 0,
|
||||
'Check for subscription_plan type'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Comments and Documentation Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that functions have comments for documentation
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT obj_description(oid) IS NOT NULL
|
||||
FROM pg_proc
|
||||
WHERE proname = 'get_my_active_subscription'
|
||||
LIMIT 1
|
||||
),
|
||||
'get_my_active_subscription should have documentation comment'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT obj_description(oid) IS NOT NULL
|
||||
FROM pg_proc
|
||||
WHERE proname = 'is_paying_user'
|
||||
LIMIT 1
|
||||
),
|
||||
'is_paying_user should have documentation comment'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Profile Update Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test updating a user's subscription status
|
||||
DO $$
|
||||
DECLARE
|
||||
paying_user_id uuid := gen_random_uuid();
|
||||
BEGIN
|
||||
-- Insert test user
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(paying_user_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'payinguser@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profile
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name, is_paying, subscription_tier)
|
||||
VALUES
|
||||
(paying_user_id, 'payinguser@test.com', 'Paying', 'User', false, 'free');
|
||||
|
||||
-- Update to paying
|
||||
UPDATE public.profiles
|
||||
SET is_paying = true, subscription_tier = 'standard'
|
||||
WHERE id = paying_user_id;
|
||||
|
||||
-- Store test ID
|
||||
PERFORM set_config('test.paying_user_id', paying_user_id::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify profile was updated to paying
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_paying
|
||||
FROM public.profiles
|
||||
WHERE id = current_setting('test.paying_user_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'Profile should be updated to paying'
|
||||
);
|
||||
|
||||
SELECT is(
|
||||
(
|
||||
SELECT subscription_tier
|
||||
FROM public.profiles
|
||||
WHERE id = current_setting('test.paying_user_id')::uuid
|
||||
LIMIT 1
|
||||
),
|
||||
'standard',
|
||||
'Profile subscription_tier should be updated to standard'
|
||||
);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
182
supabase/tests/database/07_views.test.sql
Normal file
182
supabase/tests/database/07_views.test.sql
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
begin;
|
||||
select plan(21); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- View Existence Tests
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_view('public', 'user_tablos',
|
||||
'user_tablos view should exist');
|
||||
|
||||
SELECT has_view('public', 'active_subscriptions',
|
||||
'active_subscriptions view should exist');
|
||||
|
||||
-- ============================================================================
|
||||
-- User Tablos View Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that user_tablos view has expected columns
|
||||
SELECT has_column('public', 'user_tablos', 'id',
|
||||
'user_tablos view should have id column');
|
||||
|
||||
SELECT has_column('public', 'user_tablos', 'user_id',
|
||||
'user_tablos view should have user_id column');
|
||||
|
||||
SELECT has_column('public', 'user_tablos', 'name',
|
||||
'user_tablos view should have name column');
|
||||
|
||||
SELECT has_column('public', 'user_tablos', 'status',
|
||||
'user_tablos view should have status column');
|
||||
|
||||
SELECT has_column('public', 'user_tablos', 'access_level',
|
||||
'user_tablos view should have access_level column');
|
||||
|
||||
SELECT has_column('public', 'user_tablos', 'is_admin',
|
||||
'user_tablos view should have is_admin column');
|
||||
|
||||
SELECT has_column('public', 'user_tablos', 'position',
|
||||
'user_tablos view should have position column');
|
||||
|
||||
SELECT has_column('public', 'user_tablos', 'deleted_at',
|
||||
'user_tablos view should have deleted_at column');
|
||||
|
||||
-- Test that user_tablos is defined with security_invoker
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM pg_views
|
||||
WHERE schemaname = 'public'
|
||||
AND viewname = 'user_tablos'
|
||||
AND definition LIKE '%security_invoker%'
|
||||
) > 0,
|
||||
'user_tablos view should use security_invoker'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- User Tablos View Behavior Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Create test data for view testing
|
||||
DO $$
|
||||
DECLARE
|
||||
view_user1_id uuid := gen_random_uuid();
|
||||
view_user2_id uuid := gen_random_uuid();
|
||||
view_tablo1_id integer;
|
||||
view_tablo2_id integer;
|
||||
BEGIN
|
||||
-- Insert test users
|
||||
INSERT INTO auth.users (id, instance_id, aud, role, email, encrypted_password, email_confirmed_at, created_at, updated_at)
|
||||
VALUES
|
||||
(view_user1_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'viewuser1@test.com', 'encrypted', now(), now(), now()),
|
||||
(view_user2_id, '00000000-0000-0000-0000-000000000000', 'authenticated', 'authenticated', 'viewuser2@test.com', 'encrypted', now(), now(), now());
|
||||
|
||||
-- Insert test profiles
|
||||
INSERT INTO public.profiles (id, email, first_name, last_name)
|
||||
VALUES
|
||||
(view_user1_id, 'viewuser1@test.com', 'View User', 'One'),
|
||||
(view_user2_id, 'viewuser2@test.com', 'View User', 'Two');
|
||||
|
||||
-- Insert test tablos
|
||||
INSERT INTO public.tablos (owner_id, name, status, position)
|
||||
VALUES
|
||||
(view_user1_id, 'View User 1 Tablo', 'todo', 0),
|
||||
(view_user2_id, 'View User 2 Tablo', 'in_progress', 1)
|
||||
RETURNING id INTO view_tablo1_id;
|
||||
|
||||
-- Store test IDs
|
||||
PERFORM set_config('test.view_user1_id', view_user1_id::text, true);
|
||||
PERFORM set_config('test.view_user2_id', view_user2_id::text, true);
|
||||
END $$;
|
||||
|
||||
-- Test: Verify user_tablos returns tablos for users
|
||||
SELECT ok(
|
||||
(SELECT count(*) FROM public.user_tablos WHERE user_id = current_setting('test.view_user1_id')::uuid) > 0,
|
||||
'user_tablos should return tablos for user 1'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
(SELECT count(*) FROM public.user_tablos WHERE user_id = current_setting('test.view_user2_id')::uuid) > 0,
|
||||
'user_tablos should return tablos for user 2'
|
||||
);
|
||||
|
||||
-- Test: Verify access_level is set correctly for owner
|
||||
SELECT is(
|
||||
(
|
||||
SELECT access_level
|
||||
FROM public.user_tablos
|
||||
WHERE user_id = current_setting('test.view_user1_id')::uuid
|
||||
AND name = 'View User 1 Tablo'
|
||||
LIMIT 1
|
||||
),
|
||||
'admin',
|
||||
'Owner should have admin access_level in user_tablos view'
|
||||
);
|
||||
|
||||
-- Test: Verify is_admin is true for owner
|
||||
SELECT is(
|
||||
(
|
||||
SELECT is_admin
|
||||
FROM public.user_tablos
|
||||
WHERE user_id = current_setting('test.view_user1_id')::uuid
|
||||
AND name = 'View User 1 Tablo'
|
||||
LIMIT 1
|
||||
),
|
||||
true,
|
||||
'Owner should have is_admin true in user_tablos view'
|
||||
);
|
||||
|
||||
-- Test: Verify deleted tablos are filtered out
|
||||
SELECT is(
|
||||
(SELECT count(*) FROM public.user_tablos WHERE deleted_at IS NOT NULL),
|
||||
0::bigint,
|
||||
'user_tablos view should not return deleted tablos'
|
||||
);
|
||||
|
||||
-- ============================================================================
|
||||
-- Active Subscriptions View Tests
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that active_subscriptions view has expected columns
|
||||
SELECT has_column('public', 'active_subscriptions', 'subscription_id',
|
||||
'active_subscriptions view should have subscription_id column');
|
||||
|
||||
SELECT has_column('public', 'active_subscriptions', 'user_id',
|
||||
'active_subscriptions view should have user_id column');
|
||||
|
||||
SELECT has_column('public', 'active_subscriptions', 'status',
|
||||
'active_subscriptions view should have status column');
|
||||
|
||||
-- ============================================================================
|
||||
-- View Comments and Documentation
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that views have documentation comments
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT obj_description(c.oid) IS NOT NULL
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND c.relname = 'user_tablos'
|
||||
AND c.relkind = 'v'
|
||||
LIMIT 1
|
||||
),
|
||||
'user_tablos view should have documentation comment'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT obj_description(c.oid) IS NOT NULL
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'public'
|
||||
AND c.relname = 'active_subscriptions'
|
||||
AND c.relkind = 'v'
|
||||
LIMIT 1
|
||||
),
|
||||
'active_subscriptions view should have documentation comment'
|
||||
);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
146
supabase/tests/database/08_indexes_performance.test.sql
Normal file
146
supabase/tests/database/08_indexes_performance.test.sql
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
begin;
|
||||
select plan(34); -- Total number of tests
|
||||
|
||||
-- ============================================================================
|
||||
-- Tablo Access Indexes
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_index('public', 'tablo_access', 'idx_tablo_access_tablo_id',
|
||||
'Index on tablo_access.tablo_id should exist');
|
||||
|
||||
SELECT has_index('public', 'tablo_access', 'idx_tablo_access_user_id',
|
||||
'Index on tablo_access.user_id should exist');
|
||||
|
||||
-- Test that the indexes are on the correct columns
|
||||
SELECT index_is_type('public', 'tablo_access', 'idx_tablo_access_tablo_id', 'btree',
|
||||
'tablo_access.tablo_id index should be btree');
|
||||
|
||||
SELECT index_is_type('public', 'tablo_access', 'idx_tablo_access_user_id', 'btree',
|
||||
'tablo_access.user_id index should be btree');
|
||||
|
||||
-- ============================================================================
|
||||
-- Events Table Indexes
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_index('public', 'events', 'idx_events_tablo_id',
|
||||
'Index on events.tablo_id should exist');
|
||||
|
||||
SELECT has_index('public', 'events', 'idx_events_created_by',
|
||||
'Index on events.created_by should exist');
|
||||
|
||||
SELECT has_index('public', 'events', 'idx_events_start_date',
|
||||
'Index on events.start_date should exist');
|
||||
|
||||
SELECT has_index('public', 'events', 'idx_events_deleted_at',
|
||||
'Index on events.deleted_at should exist');
|
||||
|
||||
SELECT index_is_type('public', 'events', 'idx_events_tablo_id', 'btree',
|
||||
'events.tablo_id index should be btree');
|
||||
|
||||
SELECT index_is_type('public', 'events', 'idx_events_start_date', 'btree',
|
||||
'events.start_date index should be btree');
|
||||
|
||||
-- ============================================================================
|
||||
-- Notes Table Indexes
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_index('public', 'notes', 'idx_notes_user_id',
|
||||
'Index on notes.user_id should exist');
|
||||
|
||||
SELECT has_index('public', 'notes', 'idx_notes_deleted_at',
|
||||
'Index on notes.deleted_at should exist');
|
||||
|
||||
SELECT has_index('public', 'notes', 'idx_notes_created_at',
|
||||
'Index on notes.created_at should exist');
|
||||
|
||||
SELECT index_is_type('public', 'notes', 'idx_notes_user_id', 'btree',
|
||||
'notes.user_id index should be btree');
|
||||
|
||||
SELECT index_is_type('public', 'notes', 'idx_notes_deleted_at', 'btree',
|
||||
'notes.deleted_at index should be btree');
|
||||
|
||||
-- ============================================================================
|
||||
-- Shared Notes Table Indexes
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_index('public', 'shared_notes', 'idx_shared_notes_is_public',
|
||||
'Index on shared_notes.is_public should exist');
|
||||
|
||||
SELECT has_index('public', 'shared_notes', 'idx_shared_notes_user_id',
|
||||
'Index on shared_notes.user_id should exist');
|
||||
|
||||
SELECT index_is_type('public', 'shared_notes', 'idx_shared_notes_is_public', 'btree',
|
||||
'shared_notes.is_public index should be btree');
|
||||
|
||||
-- ============================================================================
|
||||
-- Note Access Table Indexes
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_index('public', 'note_access', 'idx_note_access_note_id',
|
||||
'Index on note_access.note_id should exist');
|
||||
|
||||
SELECT has_index('public', 'note_access', 'idx_note_access_user_id',
|
||||
'Index on note_access.user_id should exist');
|
||||
|
||||
SELECT has_index('public', 'note_access', 'idx_note_access_tablo_id',
|
||||
'Index on note_access.tablo_id should exist');
|
||||
|
||||
SELECT has_index('public', 'note_access', 'idx_note_access_is_active',
|
||||
'Index on note_access.is_active should exist');
|
||||
|
||||
-- ============================================================================
|
||||
-- Unique Indexes for Note Access
|
||||
-- ============================================================================
|
||||
|
||||
SELECT has_index('public', 'note_access', 'unique_note_access_with_tablo',
|
||||
'Unique index on note_access (note_id, user_id, tablo_id) should exist');
|
||||
|
||||
SELECT has_index('public', 'note_access', 'unique_note_access_all_tablos',
|
||||
'Unique index on note_access (note_id, user_id) for NULL tablo_id should exist');
|
||||
|
||||
-- ============================================================================
|
||||
-- Primary Key Indexes
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that primary keys exist (which create implicit indexes)
|
||||
SELECT has_pk('public', 'tablos', 'tablos should have primary key');
|
||||
SELECT has_pk('public', 'tablo_access', 'tablo_access should have primary key');
|
||||
SELECT has_pk('public', 'tablo_invites', 'tablo_invites should have primary key');
|
||||
SELECT has_pk('public', 'feedbacks', 'feedbacks should have primary key');
|
||||
SELECT has_pk('public', 'events', 'events should have primary key');
|
||||
SELECT has_pk('public', 'notes', 'notes should have primary key');
|
||||
SELECT has_pk('public', 'shared_notes', 'shared_notes should have primary key');
|
||||
SELECT has_pk('public', 'note_access', 'note_access should have primary key');
|
||||
|
||||
-- ============================================================================
|
||||
-- Verify Index Coverage for Common Query Patterns
|
||||
-- ============================================================================
|
||||
|
||||
-- Test that commonly queried foreign key columns have indexes
|
||||
-- This helps with JOIN performance and foreign key constraint enforcement
|
||||
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename = 'tablo_access'
|
||||
AND indexdef LIKE '%tablo_id%'
|
||||
) > 0,
|
||||
'tablo_access should have index on tablo_id for foreign key joins'
|
||||
);
|
||||
|
||||
SELECT ok(
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename = 'events'
|
||||
AND indexdef LIKE '%tablo_id%'
|
||||
) > 0,
|
||||
'events should have index on tablo_id for foreign key joins'
|
||||
);
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
||||
Loading…
Reference in a new issue