Skip to content

Commit f4e2a6b

Browse files
authored
added more tests
- added bucket tests for bun and deno - added edge tests
2 parents ed7accd + 115bc9a commit f4e2a6b

File tree

9 files changed

+311
-0
lines changed

9 files changed

+311
-0
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ jobs:
145145
if: ${{ matrix.deno == '2.x' }}
146146
run: npm run test:integration:browser
147147

148+
- name: Run Edge Functions Tests
149+
if: ${{ matrix.deno == '2.x' }}
150+
run: |
151+
cd test/deno
152+
npm run test:edge-functions
153+
148154
- name: Stop Supabase
149155
if: always()
150156
run: supabase stop

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+
})
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+
)

test/deno/integration.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,43 @@ Deno.test(
197197
// Cleanup channel
198198
await channel.unsubscribe()
199199
})
200+
201+
await t.step('Storage - should upload and list file in bucket', async () => {
202+
const bucket = 'test-bucket'
203+
const filePath = 'test-file.txt'
204+
const fileContent = new Blob(['Hello, Supabase Storage!'], { type: 'text/plain' })
205+
206+
// use service_role key for bypass RLS
207+
const SERVICE_ROLE_KEY =
208+
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ||
209+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'
210+
const supabaseWithServiceRole = createClient(SUPABASE_URL, SERVICE_ROLE_KEY, {
211+
realtime: { heartbeatIntervalMs: 500 },
212+
})
213+
214+
// upload
215+
const { data: uploadData, error: uploadError } = await supabaseWithServiceRole.storage
216+
.from(bucket)
217+
.upload(filePath, fileContent, { upsert: true })
218+
assertEquals(uploadError, null)
219+
assertExists(uploadData)
220+
221+
// list
222+
const { data: listData, error: listError } = await supabaseWithServiceRole.storage
223+
.from(bucket)
224+
.list()
225+
assertEquals(listError, null)
226+
assertEquals(Array.isArray(listData), true)
227+
if (!listData) throw new Error('listData is null')
228+
const fileNames = listData.map((f: any) => f.name)
229+
assertEquals(fileNames.includes('test-file.txt'), true)
230+
231+
// delete file
232+
const { error: deleteError } = await supabaseWithServiceRole.storage
233+
.from(bucket)
234+
.remove([filePath])
235+
assertEquals(deleteError, null)
236+
})
200237
} finally {
201238
// Ensure cleanup runs even if tests fail
202239
await cleanup()

test/deno/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"private": true,
44
"scripts": {
55
"test": "npm run setup-deps && deno test --allow-all --unstable-sloppy-imports integration.test.ts",
6+
"test:edge-functions": "npm run setup-deps && deno test --allow-all --no-check --unstable-sloppy-imports edge-functions-integration.test.ts",
67
"setup-deps": "node setup-deps.js"
78
},
89
"dependencies": {

test/integration/bun/integration.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,40 @@ test('should handle invalid credentials', async () => {
109109
expect(error).not.toBeNull()
110110
expect(data.user).toBeNull()
111111
})
112+
113+
test('should upload and list file in bucket', async () => {
114+
const bucket = 'test-bucket'
115+
const filePath = 'test-file.txt'
116+
const fileContent = new Blob(['Hello, Supabase Storage!'], { type: 'text/plain' })
117+
118+
// use service_role key for bypass RLS
119+
const SERVICE_ROLE_KEY =
120+
process.env.SUPABASE_SERVICE_ROLE_KEY ||
121+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU'
122+
const supabaseWithServiceRole = createClient(SUPABASE_URL, SERVICE_ROLE_KEY, {
123+
realtime: { heartbeatIntervalMs: 500 },
124+
})
125+
126+
// upload
127+
const { data: uploadData, error: uploadError } = await supabaseWithServiceRole.storage
128+
.from(bucket)
129+
.upload(filePath, fileContent, { upsert: true })
130+
expect(uploadError).toBeNull()
131+
expect(uploadData).toBeDefined()
132+
133+
// list
134+
const { data: listData, error: listError } = await supabaseWithServiceRole.storage
135+
.from(bucket)
136+
.list()
137+
expect(listError).toBeNull()
138+
expect(Array.isArray(listData)).toBe(true)
139+
if (!listData) throw new Error('listData is null')
140+
const fileNames = listData.map((f: any) => f.name)
141+
expect(fileNames).toContain('test-file.txt')
142+
143+
// delete file
144+
const { error: deleteError } = await supabaseWithServiceRole.storage
145+
.from(bucket)
146+
.remove([filePath])
147+
expect(deleteError).toBeNull()
148+
})

0 commit comments

Comments
 (0)