Files
supabase/apps/studio/lib/validation/http-url.test.ts
Danny White fd17b246e1 fix(studio): tighten webhook endpoint validation (#43892)
## What kind of change does this PR introduce?

Bug fix.

## What is the current behavior?

Webhook endpoint validation is inconsistent across Studio forms. The
webhook sheet accepts incomplete hostnames like `https://webhook`, event
type validation is not surfaced clearly, and HTTP endpoint validation
differs between webhooks, log drains, cron jobs, and database hooks.

## What is the new behavior?

- Tightens webhook endpoint URL validation and rejects incomplete
hostnames while still allowing localhost and IP-based endpoints.
- Surfaces the Event types validation through the standard form error
styling and highlights the existing accordion item border when invalid.
- **Extracts a shared HTTP endpoint URL validator and reuses it in
webhooks, log drains, cron jobs, and database hooks.**
- Adds focused regression tests for the webhook sheet and the
shared/consumer validation paths.

| After |
| --- |
| <img width="1728" height="997" alt="Webhooks Settings AWS Healthy
Toolshed Supabase-CB0D999C-D0BF-47AA-A10F-342A2E328DF9"
src="https://github.com/user-attachments/assets/bcbe4876-f9a7-497a-b288-460087a65546"
/> |

## To test

Form behaviour (in particular URL validation) on:

- Webhook endpoint
- Log drains
- Cron jobs
- Database hooks
2026-03-19 11:43:02 +11:00

63 lines
1.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { httpEndpointUrlSchema, isValidHttpEndpointUrl } from './http-url'
const schema = httpEndpointUrlSchema({
requiredMessage: 'required',
invalidMessage: 'invalid',
prefixMessage: 'prefix',
})
describe('isValidHttpEndpointUrl', () => {
it('accepts valid http and https endpoints', () => {
expect(isValidHttpEndpointUrl('https://api.supabase.com/webhooks')).toBe(true)
expect(isValidHttpEndpointUrl('http://localhost:3000/hooks')).toBe(true)
expect(isValidHttpEndpointUrl('https://127.0.0.1:4318/v1/logs')).toBe(true)
expect(isValidHttpEndpointUrl('https://[::1]:4318/v1/logs')).toBe(true)
})
it('rejects invalid endpoint URLs', () => {
expect(isValidHttpEndpointUrl('https://webhook')).toBe(false)
expect(isValidHttpEndpointUrl('ftp://api.supabase.com/webhooks')).toBe(false)
expect(isValidHttpEndpointUrl('not a url')).toBe(false)
})
})
describe('httpEndpointUrlSchema', () => {
it('rejects empty values', () => {
const result = schema.safeParse('')
expect(result.success).toBe(false)
if (!result.success) {
expect(result.error.issues[0].message).toBe('required')
}
})
it('rejects URLs without an http or https prefix', () => {
const result = schema.safeParse('api.supabase.com/webhooks')
expect(result.success).toBe(false)
if (!result.success) {
expect(result.error.issues[0].message).toBe('prefix')
}
})
it('rejects incomplete hostnames', () => {
const result = schema.safeParse('https://webhook')
expect(result.success).toBe(false)
if (!result.success) {
expect(result.error.issues[0].message).toBe('invalid')
}
})
it('accepts valid endpoints after trimming', () => {
const result = schema.safeParse(' https://api.supabase.com/webhooks ')
expect(result.success).toBe(true)
if (result.success) {
expect(result.data).toBe('https://api.supabase.com/webhooks')
}
})
})