Skip to content

test: added postgre rls, auth and bucket tests #1502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ jobs:
if: ${{ matrix.deno == '2.x' }}
run: npm run test:integration:browser

- name: Run Edge Functions Tests
if: ${{ matrix.deno == '2.x' }}
run: |
cd test/deno
npm run test:edge-functions

- name: Stop Supabase
if: always()
run: supabase stop
Expand Down Expand Up @@ -176,8 +182,13 @@ jobs:
npm ci
npm run build

- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq

- name: Run integration tests
run: npm run test:integration || npm run test:integration
run: |
export SUPABASE_SERVICE_ROLE_KEY="$(supabase status --output json | jq -r '.SERVICE_ROLE_KEY')"
npm run test:integration || npm run test:integration

- name: Stop Supabase
if: always()
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"test:coverage": "jest --runInBand --coverage --testPathIgnorePatterns=\"test/integration|test/deno\"",
"test:integration": "jest --runInBand --detectOpenHandles test/integration.test.ts",
"test:integration:browser": "deno test --allow-all test/integration.browser.test.ts",
"test:edge-functions": "deno test --allow-all --no-check test/deno/edge-functions-integration.test.ts",
"test:watch": "jest --watch --verbose false --silent false",
"test:node:playwright": "cd test/integration/node-browser && npm install && cp ../../../dist/umd/supabase.js . && npm run test",
"test:bun": "cd test/integration/bun && bun install && bun test",
Expand Down
33 changes: 33 additions & 0 deletions supabase/functions/echo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { serve } from 'https://deno.land/[email protected]/http/server.ts'

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

serve(async (req) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}

try {
const body = await req.json()
const data = {
echo: body,
method: req.method,
url: req.url,
timestamp: new Date().toISOString(),
}

return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
})
}
})
31 changes: 31 additions & 0 deletions supabase/functions/hello/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { serve } from 'https://deno.land/[email protected]/http/server.ts'

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

serve(async (req) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}

try {
const { name } = await req.json()
const data = {
message: `Hello ${name || 'World'}!`,
timestamp: new Date().toISOString(),
}

return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
})
}
})
33 changes: 33 additions & 0 deletions supabase/functions/status/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { serve } from 'https://deno.land/[email protected]/http/server.ts'

const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

serve(async (req) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}

try {
const data = {
status: 'ok',
timestamp: new Date().toISOString(),
environment: Deno.env.get('ENVIRONMENT') || 'development',
version: '1.0.0',
uptime: Date.now(),
}

return new Response(JSON.stringify(data), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 200,
})
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 500,
})
}
})
47 changes: 42 additions & 5 deletions supabase/migrations/20250422000000_create_todos_table.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,52 @@ CREATE TABLE IF NOT EXISTS public.todos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
task TEXT NOT NULL,
is_complete BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
user_id UUID REFERENCES auth.users(id)
);

-- Set up Row Level Security (RLS)
ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;

-- Create policies
CREATE POLICY "Allow anonymous access to todos" ON public.todos
FOR ALL
-- Allow anonymous users to read all todos (public data)
CREATE POLICY "Allow anonymous read access" ON public.todos
FOR SELECT
TO anon
USING (true);

-- Allow anonymous users to insert todos (for backward compatibility with old tests)
CREATE POLICY "Allow anonymous insert access" ON public.todos
FOR INSERT
TO anon
USING (true)
WITH CHECK (true);

-- Allow anonymous users to delete todos (for backward compatibility with old tests)
CREATE POLICY "Allow anonymous delete access" ON public.todos
FOR DELETE
TO anon
USING (true);

-- Allow authenticated users to read their own todos
CREATE POLICY "Allow authenticated read own todos" ON public.todos
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);

-- Allow authenticated users to insert their own todos
CREATE POLICY "Allow authenticated insert own todos" ON public.todos
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);

-- Allow authenticated users to update their own todos
CREATE POLICY "Allow authenticated update own todos" ON public.todos
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

-- Allow authenticated users to delete their own todos
CREATE POLICY "Allow authenticated delete own todos" ON public.todos
FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
4 changes: 4 additions & 0 deletions supabase/migrations/20250424000000_storage_anon_policy.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Create test bucket for storage tests
insert into storage.buckets (id, name, public)
values ('test-bucket', 'test-bucket', false)
on conflict (id) do nothing;
132 changes: 132 additions & 0 deletions test/deno/edge-functions-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { assertEquals, assertExists } from 'https://deno.land/[email protected]/assert/mod.ts'
import { createClient } from '../../dist/module/index.js'

// These tests are for integration testing with actual deployed edge functions
// To run these tests, you need to:
// 1. Deploy the edge functions to a Supabase project
// 2. Set the SUPABASE_URL and SUPABASE_ANON_KEY environment variables
// 3. Or use the local development credentials below

Deno.test(
'Edge Functions Integration Tests',
{ sanitizeOps: false, sanitizeResources: false },
async (t) => {
// Use environment variables or fall back to local development
const SUPABASE_URL = Deno.env.get('SUPABASE_URL') || 'http://127.0.0.1:54321'
const ANON_KEY =
Deno.env.get('SUPABASE_ANON_KEY') ||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'

const supabase = createClient(SUPABASE_URL, ANON_KEY, {
realtime: { heartbeatIntervalMs: 500 },
})

try {
await t.step('hello function - should return greeting with name', async () => {
const { data, error } = await supabase.functions.invoke('hello', {
body: { name: 'Test User' },
})

assertEquals(error, null)
assertExists(data)
assertEquals(typeof data.message, 'string')
assertEquals(data.message, 'Hello Test User!')
assertEquals(typeof data.timestamp, 'string')
})

await t.step('hello function - should return default greeting without name', async () => {
const { data, error } = await supabase.functions.invoke('hello', {
body: {},
})

assertEquals(error, null)
assertExists(data)
assertEquals(typeof data.message, 'string')
assertEquals(data.message, 'Hello World!')
assertEquals(typeof data.timestamp, 'string')
})

await t.step('echo function - should echo request body', async () => {
const testData = {
message: 'Hello Echo!',
number: 42,
array: [1, 2, 3],
nested: { key: 'value' },
}

const { data, error } = await supabase.functions.invoke('echo', {
body: testData,
})

assertEquals(error, null)
assertExists(data)
assertEquals(data.echo, testData)
assertEquals(typeof data.method, 'string')
assertEquals(typeof data.url, 'string')
assertEquals(typeof data.timestamp, 'string')
})

await t.step('status function - should return system status', async () => {
const { data, error } = await supabase.functions.invoke('status', {
body: {},
})

assertEquals(error, null)
assertExists(data)
assertEquals(data.status, 'ok')
assertEquals(typeof data.timestamp, 'string')
assertEquals(typeof data.environment, 'string')
assertEquals(data.version, '1.0.0')
assertEquals(typeof data.uptime, 'number')
})

await t.step('should handle non-existent function', async () => {
const { data, error } = await supabase.functions.invoke('non-existent-function', {
body: {},
})

assertExists(error)
assertEquals(data, null)
})

await t.step('should handle concurrent function calls', async () => {
const promises = Array.from({ length: 5 }, (_, i) =>
supabase.functions.invoke('hello', {
body: { name: `Concurrent Test ${i}` },
})
)

const results = await Promise.all(promises)

// Check if any functions are deployed
const hasDeployedFunctions = results.some(({ error }) => !error)

if (!hasDeployedFunctions) {
console.log('No functions deployed, skipping concurrent execution test')
return
}

results.forEach(({ data, error }) => {
if (!error) {
assertEquals(error, null)
assertExists(data)
assertEquals(typeof data.message, 'string')
assertEquals(typeof data.timestamp, 'string')
}
})
})

await t.step('should handle function errors gracefully', async () => {
const { data, error } = await supabase.functions.invoke('hello', {
body: 'invalid json',
})

assertExists(error)
assertEquals(data, null)
})
} catch (error) {
console.error('Test error:', error)
throw error
}
}
)
Loading
Loading