mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
feat(studio-local): functions management api - test functions (#42350)
Feature ## What is the current behavior? Functions page on self-hosted differs from Platform ## What is the new behavior? Adds the possibility to try/test functions in Self-Host version. ## Summary by CodeRabbit * **Bug Fixes** * Improved edge function URL validation so testing works reliably both on-platform and off-platform, including proper URL handling for local setups. * **UI Improvements** * Moved the Test button in the edge functions interface for more consistent layout while preserving its behavior. * **Tests** * Expanded tests to cover platform-aware URL validation scenarios. --------- Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
This commit is contained in:
1 parent
6e4aeed6d9
commit
70510acf5b
4 files changed
+76
-30
No files matched your search
@@ -299,27 +299,25 @@ const EdgeFunctionDetailsLayout = ({
|
||||
</div>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
{IS_PLATFORM && (
|
||||
<>
|
||||
{!!functionSlug && (
|
||||
<Button
|
||||
type="default"
|
||||
icon={<Send />}
|
||||
onClick={() => {
|
||||
setIsOpen(true)
|
||||
sendEvent({
|
||||
action: 'edge_function_test_side_panel_opened',
|
||||
groups: {
|
||||
project: ref ?? 'Unknown',
|
||||
organization: org?.slug ?? 'Unknown',
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
Test
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
{!!functionSlug && (
|
||||
<Button
|
||||
type="default"
|
||||
icon={<Send />}
|
||||
onClick={() => {
|
||||
setIsOpen(true)
|
||||
if (IS_PLATFORM) {
|
||||
sendEvent({
|
||||
action: 'edge_function_test_side_panel_opened',
|
||||
groups: {
|
||||
project: ref ?? 'Unknown',
|
||||
organization: org?.slug ?? 'Unknown',
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
Test
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</PageHeaderAside>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, describe, it } from 'vitest'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { isValidEdgeFunctionURL } from './edgeFunctions'
|
||||
|
||||
describe('isValidEdgeFunctionURL', () => {
|
||||
@@ -9,7 +10,16 @@ describe('isValidEdgeFunctionURL', () => {
|
||||
'https://projectref.supabase.red/functions/v3/hello-world',
|
||||
]
|
||||
|
||||
const invalidEdgeFunctionUrls = [
|
||||
const validLocalEdgeFunctionsUrls = [
|
||||
'https://projectref.notsupabase.com/functions/v1/test',
|
||||
'https://notsupabase.com/functions/v1/test',
|
||||
'http://localhost:54321/functions/v1/test-2',
|
||||
'http://kong:8000/functions/v1/hello-world',
|
||||
'https://127.0.0.1:54321/functions/v1/test-3',
|
||||
'https://127.0.0.1:54321/functions/v1/test-5',
|
||||
]
|
||||
|
||||
const invalidPlatformEdgeFunctionUrls = [
|
||||
'https://notsupabase.com/functions/v1/test',
|
||||
'https://projectref.notsupabase.com/functions/v1/test',
|
||||
'https://localhost?https://aaaa.supabase.co/functions/v1/xxx',
|
||||
@@ -17,15 +27,41 @@ describe('isValidEdgeFunctionURL', () => {
|
||||
'http://localhost:3000/?https://aaaa.supabase.co/functions/v1/xxx',
|
||||
]
|
||||
|
||||
it('should match valid edge function URLs', () => {
|
||||
const invalidEdgeFunctionUrls = [
|
||||
'https://localhost?https://aaaa.supabase.co/functions/v1/xxx',
|
||||
'https://localhost:3000/?https://aaaa.supabase.co/functions/v1/xxx',
|
||||
'http://localhost:3000/?https://aaaa.supabase.co/functions/v1/xxx',
|
||||
]
|
||||
|
||||
it('should match valid edge function URLs on platform', () => {
|
||||
for (const url of validEdgeFunctionUrls) {
|
||||
expect(isValidEdgeFunctionURL(url), `Expected ${url} to be valid`).toBe(true)
|
||||
expect(isValidEdgeFunctionURL(url, true), `Expected ${url} to be valid`).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not match invalid edge function URLs', () => {
|
||||
it('should not match local URLs on platform', () => {
|
||||
for (const url of validLocalEdgeFunctionsUrls) {
|
||||
expect(isValidEdgeFunctionURL(url, true), `Expected ${url} to be invalid on platform`).toBe(
|
||||
false
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should match valid local edge function URLs off platform', () => {
|
||||
for (const url of validLocalEdgeFunctionsUrls) {
|
||||
expect(isValidEdgeFunctionURL(url, false), `Expected ${url} to be valid`).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not match invalid edge function URLs on platform', () => {
|
||||
for (const url of invalidPlatformEdgeFunctionUrls) {
|
||||
expect(isValidEdgeFunctionURL(url, true), `Expected ${url} to be invalid`).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not match invalid edge function URLs off platform', () => {
|
||||
for (const url of invalidEdgeFunctionUrls) {
|
||||
expect(isValidEdgeFunctionURL(url), `Expected ${url} to be invalid`).toBe(false)
|
||||
expect(isValidEdgeFunctionURL(url, false), `Expected ${url} to be invalid`).toBe(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,12 +1,20 @@
|
||||
const NIMBUS_PROD_PROJECTS_URL = process.env.NIMBUS_PROD_PROJECTS_URL
|
||||
|
||||
export const isValidEdgeFunctionURL = (url: string) => {
|
||||
export const isValidEdgeFunctionURL = (url: string, isPlatform: boolean) => {
|
||||
if (NIMBUS_PROD_PROJECTS_URL !== undefined) {
|
||||
const apexDomain = NIMBUS_PROD_PROJECTS_URL.replace('https://*.', '').replace(/\./g, '\\.')
|
||||
const nimbusRegex = new RegExp('^https://[a-z]*\\.' + apexDomain + '/functions/v[0-9]{1}/.*$')
|
||||
return nimbusRegex.test(url)
|
||||
}
|
||||
|
||||
if (!isPlatform) {
|
||||
const regexValidLocalEdgeFunctionURL = new RegExp(
|
||||
'^https?://[^\\s/?#]+/functions/v[0-9]{1}/.*$'
|
||||
)
|
||||
|
||||
return regexValidLocalEdgeFunctionURL.test(url)
|
||||
}
|
||||
|
||||
const regexValidEdgeFunctionURL = new RegExp(
|
||||
'^https://[a-z]*.supabase.(red|co)/functions/v[0-9]{1}/.*$'
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { isValidEdgeFunctionURL } from 'lib/api/edgeFunctions'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
@@ -20,9 +21,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
async function handlePost(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { url, method, body: requestBody, headers: customHeaders } = req.body
|
||||
const { url: requestUrl, method, body: requestBody, headers: customHeaders } = req.body
|
||||
const url = IS_PLATFORM
|
||||
? requestUrl
|
||||
: requestUrl.replace(process.env.SUPABASE_PUBLIC_URL, process.env.SUPABASE_URL)
|
||||
|
||||
const validEdgeFnUrl = isValidEdgeFunctionURL(url)
|
||||
const validEdgeFnUrl = isValidEdgeFunctionURL(url, IS_PLATFORM)
|
||||
|
||||
if (!validEdgeFnUrl) {
|
||||
return res.status(400).json({
|
||||
|
||||
Reference in new issue
Block a user