Skip to content

Commit 137caec

Browse files
authored
Merge pull request #1502 from georgRusanov/more_test
test: added postgre rls, auth and bucket tests
2 parents 4530d64 + f4e2a6b commit 137caec

File tree

14 files changed

+722
-28
lines changed

14 files changed

+722
-28
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ jobs:
158158
if: ${{ matrix.deno == '2.x' }}
159159
run: npm run test:integration:browser
160160

161+
- name: Run Edge Functions Tests
162+
if: ${{ matrix.deno == '2.x' }}
163+
run: |
164+
cd test/deno
165+
npm run test:edge-functions
166+
161167
- name: Stop Supabase
162168
if: always()
163169
run: supabase stop
@@ -189,8 +195,13 @@ jobs:
189195
npm ci
190196
npm run build
191197
198+
- name: Install jq
199+
run: sudo apt-get update && sudo apt-get install -y jq
200+
192201
- name: Run integration tests
193-
run: npm run test:integration || npm run test:integration
202+
run: |
203+
export SUPABASE_SERVICE_ROLE_KEY="$(supabase status --output json | jq -r '.SERVICE_ROLE_KEY')"
204+
npm run test:integration || npm run test:integration
194205
195206
- name: Stop Supabase
196207
if: always()

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"test:coverage": "jest --runInBand --coverage --testPathIgnorePatterns=\"test/integration|test/deno\"",
3636
"test:integration": "jest --runInBand --detectOpenHandles test/integration.test.ts",
3737
"test:integration:browser": "deno test --allow-all test/integration.browser.test.ts",
38+
"test:edge-functions": "deno test --allow-all --no-check test/deno/edge-functions-integration.test.ts",
3839
"test:watch": "jest --watch --verbose false --silent false",
3940
"test:node:playwright": "cd test/integration/node-browser && npm install && cp ../../../dist/umd/supabase.js . && npm run test",
4041
"test:bun": "cd test/integration/bun && bun install && bun test",

supabase/functions/echo/index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
2+
3+
const corsHeaders = {
4+
'Access-Control-Allow-Origin': '*',
5+
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
6+
}
7+
8+
serve(async (req) => {
9+
// Handle CORS preflight requests
10+
if (req.method === 'OPTIONS') {
11+
return new Response('ok', { headers: corsHeaders })
12+
}
13+
14+
try {
15+
const body = await req.json()
16+
const data = {
17+
echo: body,
18+
method: req.method,
19+
url: req.url,
20+
timestamp: new Date().toISOString(),
21+
}
22+
23+
return new Response(JSON.stringify(data), {
24+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
25+
status: 200,
26+
})
27+
} catch (error) {
28+
return new Response(JSON.stringify({ error: error.message }), {
29+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
30+
status: 400,
31+
})
32+
}
33+
})

supabase/functions/hello/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
2+
3+
const corsHeaders = {
4+
'Access-Control-Allow-Origin': '*',
5+
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
6+
}
7+
8+
serve(async (req) => {
9+
// Handle CORS preflight requests
10+
if (req.method === 'OPTIONS') {
11+
return new Response('ok', { headers: corsHeaders })
12+
}
13+
14+
try {
15+
const { name } = await req.json()
16+
const data = {
17+
message: `Hello ${name || 'World'}!`,
18+
timestamp: new Date().toISOString(),
19+
}
20+
21+
return new Response(JSON.stringify(data), {
22+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
23+
status: 200,
24+
})
25+
} catch (error) {
26+
return new Response(JSON.stringify({ error: error.message }), {
27+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
28+
status: 400,
29+
})
30+
}
31+
})

supabase/functions/status/index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { serve } from 'https://deno.land/[email protected]/http/server.ts'
2+
3+
const corsHeaders = {
4+
'Access-Control-Allow-Origin': '*',
5+
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
6+
}
7+
8+
serve(async (req) => {
9+
// Handle CORS preflight requests
10+
if (req.method === 'OPTIONS') {
11+
return new Response('ok', { headers: corsHeaders })
12+
}
13+
14+
try {
15+
const data = {
16+
status: 'ok',
17+
timestamp: new Date().toISOString(),
18+
environment: Deno.env.get('ENVIRONMENT') || 'development',
19+
version: '1.0.0',
20+
uptime: Date.now(),
21+
}
22+
23+
return new Response(JSON.stringify(data), {
24+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
25+
status: 200,
26+
})
27+
} catch (error) {
28+
return new Response(JSON.stringify({ error: error.message }), {
29+
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
30+
status: 500,
31+
})
32+
}
33+
})

supabase/migrations/20250422000000_create_todos_table.sql

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,52 @@ CREATE TABLE IF NOT EXISTS public.todos (
33
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
44
task TEXT NOT NULL,
55
is_complete BOOLEAN NOT NULL DEFAULT FALSE,
6-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
6+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
7+
user_id UUID REFERENCES auth.users(id)
78
);
89

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

12-
-- Create policies
13-
CREATE POLICY "Allow anonymous access to todos" ON public.todos
14-
FOR ALL
13+
-- Allow anonymous users to read all todos (public data)
14+
CREATE POLICY "Allow anonymous read access" ON public.todos
15+
FOR SELECT
16+
TO anon
17+
USING (true);
18+
19+
-- Allow anonymous users to insert todos (for backward compatibility with old tests)
20+
CREATE POLICY "Allow anonymous insert access" ON public.todos
21+
FOR INSERT
1522
TO anon
16-
USING (true)
1723
WITH CHECK (true);
24+
25+
-- Allow anonymous users to delete todos (for backward compatibility with old tests)
26+
CREATE POLICY "Allow anonymous delete access" ON public.todos
27+
FOR DELETE
28+
TO anon
29+
USING (true);
30+
31+
-- Allow authenticated users to read their own todos
32+
CREATE POLICY "Allow authenticated read own todos" ON public.todos
33+
FOR SELECT
34+
TO authenticated
35+
USING (auth.uid() = user_id);
36+
37+
-- Allow authenticated users to insert their own todos
38+
CREATE POLICY "Allow authenticated insert own todos" ON public.todos
39+
FOR INSERT
40+
TO authenticated
41+
WITH CHECK (auth.uid() = user_id);
42+
43+
-- Allow authenticated users to update their own todos
44+
CREATE POLICY "Allow authenticated update own todos" ON public.todos
45+
FOR UPDATE
46+
TO authenticated
47+
USING (auth.uid() = user_id)
48+
WITH CHECK (auth.uid() = user_id);
49+
50+
-- Allow authenticated users to delete their own todos
51+
CREATE POLICY "Allow authenticated delete own todos" ON public.todos
52+
FOR DELETE
53+
TO authenticated
54+
USING (auth.uid() = user_id);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Create test bucket for storage tests
2+
insert into storage.buckets (id, name, public)
3+
values ('test-bucket', 'test-bucket', false)
4+
on conflict (id) do nothing;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { assertEquals, assertExists } from 'https://deno.land/[email protected]/assert/mod.ts'
2+
import { createClient } from '../../dist/module/index.js'
3+
4+
// These tests are for integration testing with actual deployed edge functions
5+
// To run these tests, you need to:
6+
// 1. Deploy the edge functions to a Supabase project
7+
// 2. Set the SUPABASE_URL and SUPABASE_ANON_KEY environment variables
8+
// 3. Or use the local development credentials below
9+
10+
Deno.test(
11+
'Edge Functions Integration Tests',
12+
{ sanitizeOps: false, sanitizeResources: false },
13+
async (t) => {
14+
// Use environment variables or fall back to local development
15+
const SUPABASE_URL = Deno.env.get('SUPABASE_URL') || 'http://127.0.0.1:54321'
16+
const ANON_KEY =
17+
Deno.env.get('SUPABASE_ANON_KEY') ||
18+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'
19+
20+
const supabase = createClient(SUPABASE_URL, ANON_KEY, {
21+
realtime: { heartbeatIntervalMs: 500 },
22+
})
23+
24+
try {
25+
await t.step('hello function - should return greeting with name', async () => {
26+
const { data, error } = await supabase.functions.invoke('hello', {
27+
body: { name: 'Test User' },
28+
})
29+
30+
assertEquals(error, null)
31+
assertExists(data)
32+
assertEquals(typeof data.message, 'string')
33+
assertEquals(data.message, 'Hello Test User!')
34+
assertEquals(typeof data.timestamp, 'string')
35+
})
36+
37+
await t.step('hello function - should return default greeting without name', async () => {
38+
const { data, error } = await supabase.functions.invoke('hello', {
39+
body: {},
40+
})
41+
42+
assertEquals(error, null)
43+
assertExists(data)
44+
assertEquals(typeof data.message, 'string')
45+
assertEquals(data.message, 'Hello World!')
46+
assertEquals(typeof data.timestamp, 'string')
47+
})
48+
49+
await t.step('echo function - should echo request body', async () => {
50+
const testData = {
51+
message: 'Hello Echo!',
52+
number: 42,
53+
array: [1, 2, 3],
54+
nested: { key: 'value' },
55+
}
56+
57+
const { data, error } = await supabase.functions.invoke('echo', {
58+
body: testData,
59+
})
60+
61+
assertEquals(error, null)
62+
assertExists(data)
63+
assertEquals(data.echo, testData)
64+
assertEquals(typeof data.method, 'string')
65+
assertEquals(typeof data.url, 'string')
66+
assertEquals(typeof data.timestamp, 'string')
67+
})
68+
69+
await t.step('status function - should return system status', async () => {
70+
const { data, error } = await supabase.functions.invoke('status', {
71+
body: {},
72+
})
73+
74+
assertEquals(error, null)
75+
assertExists(data)
76+
assertEquals(data.status, 'ok')
77+
assertEquals(typeof data.timestamp, 'string')
78+
assertEquals(typeof data.environment, 'string')
79+
assertEquals(data.version, '1.0.0')
80+
assertEquals(typeof data.uptime, 'number')
81+
})
82+
83+
await t.step('should handle non-existent function', async () => {
84+
const { data, error } = await supabase.functions.invoke('non-existent-function', {
85+
body: {},
86+
})
87+
88+
assertExists(error)
89+
assertEquals(data, null)
90+
})
91+
92+
await t.step('should handle concurrent function calls', async () => {
93+
const promises = Array.from({ length: 5 }, (_, i) =>
94+
supabase.functions.invoke('hello', {
95+
body: { name: `Concurrent Test ${i}` },
96+
})
97+
)
98+
99+
const results = await Promise.all(promises)
100+
101+
// Check if any functions are deployed
102+
const hasDeployedFunctions = results.some(({ error }) => !error)
103+
104+
if (!hasDeployedFunctions) {
105+
console.log('No functions deployed, skipping concurrent execution test')
106+
return
107+
}
108+
109+
results.forEach(({ data, error }) => {
110+
if (!error) {
111+
assertEquals(error, null)
112+
assertExists(data)
113+
assertEquals(typeof data.message, 'string')
114+
assertEquals(typeof data.timestamp, 'string')
115+
}
116+
})
117+
})
118+
119+
await t.step('should handle function errors gracefully', async () => {
120+
const { data, error } = await supabase.functions.invoke('hello', {
121+
body: 'invalid json',
122+
})
123+
124+
assertExists(error)
125+
assertEquals(data, null)
126+
})
127+
} catch (error) {
128+
console.error('Test error:', error)
129+
throw error
130+
}
131+
}
132+
)

0 commit comments

Comments
 (0)