mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
- adds up to: https://github.com/supabase/cli/pull/5862 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an “Error docs” link in Edge Function testing UI when an `sb-error-code` header is present. * **Bug Fixes** * Improved the Edge Function test proxy to consistently preserve upstream status, headers (including repeated headers), and response bodies without transformation. * Enhanced handling for invalid function URLs and upstream fetch failures. * **Tests** * Added unit, API, and Playwright E2E coverage for error docs linking and response proxy behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
142 lines
4.0 KiB
TypeScript
142 lines
4.0 KiB
TypeScript
import { createMocks } from 'node-mocks-http'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import handler from '../../../../pages/api/edge-functions/test'
|
|
|
|
vi.mock('common', () => ({ IS_PLATFORM: true }))
|
|
|
|
const createRequest = (url = 'https://abcdefghijklmnopqrst.supabase.co/functions/v1/test') =>
|
|
createMocks({
|
|
method: 'POST',
|
|
body: {
|
|
url,
|
|
method: 'POST',
|
|
body: '{}',
|
|
headers: {},
|
|
},
|
|
})
|
|
|
|
describe('/api/edge-functions/test', () => {
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
it('preserves unsuccessful edge function responses', async () => {
|
|
const body = JSON.stringify({
|
|
code: 'UNAUTHORIZED_NO_AUTH_HEADER',
|
|
message: 'Missing authorization header',
|
|
})
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValue(
|
|
new Response(body, {
|
|
status: 401,
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
'sb-error-code': 'UNAUTHORIZED_NO_AUTH_HEADER',
|
|
},
|
|
})
|
|
)
|
|
)
|
|
const { req, res } = createRequest()
|
|
|
|
await handler(req, res)
|
|
|
|
expect(res._getStatusCode()).toBe(200)
|
|
expect(JSON.parse(res._getData())).toEqual({
|
|
status: 401,
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
'sb-error-code': 'UNAUTHORIZED_NO_AUTH_HEADER',
|
|
},
|
|
body,
|
|
})
|
|
})
|
|
|
|
it('preserves multiple Set-Cookie headers', async () => {
|
|
const headers = new Headers()
|
|
headers.append('set-cookie', 'session=one; Path=/; HttpOnly')
|
|
headers.append('set-cookie', 'csrf=two; Path=/; SameSite=Lax')
|
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(null, { headers })))
|
|
const { req, res } = createRequest()
|
|
|
|
await handler(req, res)
|
|
|
|
expect(JSON.parse(res._getData())).toEqual({
|
|
status: 200,
|
|
headers: {
|
|
'set-cookie': ['session=one; Path=/; HttpOnly', 'csrf=two; Path=/; SameSite=Lax'],
|
|
},
|
|
body: '',
|
|
})
|
|
})
|
|
|
|
it.each([
|
|
['successful JSON', JSON.stringify({ ok: true }), 'application/json', 200],
|
|
[
|
|
'gateway JSON',
|
|
JSON.stringify({ message: 'Name resolution failed' }),
|
|
'application/json',
|
|
503,
|
|
],
|
|
['legacy JSON', JSON.stringify({ msg: 'Invalid JWT' }), 'application/json', 503],
|
|
[
|
|
'nested JSON',
|
|
JSON.stringify({ error: { message: 'Function failed' } }),
|
|
'application/json',
|
|
503,
|
|
],
|
|
['arbitrary JSON', JSON.stringify({ details: ['Function failed'] }), 'application/json', 503],
|
|
['plain text', 'Bad Gateway', 'text/plain', 503],
|
|
['malformed JSON', '{"message": invalid json', 'application/json', 503],
|
|
])('preserves %s bodies without parsing them', async (_name, body, contentType, status) => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValue(
|
|
new Response(body, {
|
|
status,
|
|
headers: { 'content-type': contentType },
|
|
})
|
|
)
|
|
)
|
|
const { req, res } = createRequest()
|
|
|
|
await handler(req, res)
|
|
|
|
expect(res._getStatusCode()).toBe(200)
|
|
expect(JSON.parse(res._getData())).toEqual({
|
|
status,
|
|
headers: { 'content-type': contentType },
|
|
body,
|
|
})
|
|
})
|
|
|
|
it('rejects invalid URLs without making an upstream request', async () => {
|
|
const fetchMock = vi.fn()
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
const { req, res } = createRequest('https://example.com/functions/v1/test')
|
|
|
|
await handler(req, res)
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
expect(res._getStatusCode()).toBe(400)
|
|
expect(JSON.parse(res._getData())).toEqual({
|
|
status: 400,
|
|
error: { message: 'Provided URL is not a valid Supabase edge function URL' },
|
|
})
|
|
})
|
|
|
|
it('returns fetch failures as proxy errors', async () => {
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Connection refused')))
|
|
const { req, res } = createRequest()
|
|
|
|
await handler(req, res)
|
|
|
|
expect(res._getStatusCode()).toBe(500)
|
|
expect(JSON.parse(res._getData())).toEqual({
|
|
status: 500,
|
|
error: { message: 'Connection refused' },
|
|
})
|
|
})
|
|
})
|