supabase tests temp

This commit is contained in:
Arthur Belleville 2025-11-04 22:20:56 +01:00
parent 0d6b21a310
commit da6e2ab5f8
No known key found for this signature in database
11 changed files with 1944 additions and 0 deletions

View file

@ -0,0 +1 @@
main

View file

@ -0,0 +1 @@
v2.54.11

146
supabase/tests/README.md Normal file
View 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

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;