import { describe, expect, test } from 'bun:test'; import { validatePath, validateRouteRequest, validateSlug } from './route-validation'; describe('validateSlug', () => { test('accepts valid kebab-case slugs', () => { expect(validateSlug('lunacycle').valid).toBe(true); expect(validateSlug('my-app').valid).toBe(true); expect(validateSlug('app1').valid).toBe(true); expect(validateSlug('dns-external').valid).toBe(true); expect(validateSlug('a').valid).toBe(true); }); test('rejects invalid slugs', () => { expect(validateSlug('MyApp').valid).toBe(false); expect(validateSlug('my_app').valid).toBe(false); expect(validateSlug('my--app').valid).toBe(false); expect(validateSlug('-app').valid).toBe(false); expect(validateSlug('app-').valid).toBe(false); expect(validateSlug('').valid).toBe(false); expect(validateSlug('App').valid).toBe(false); }); }); describe('validatePath', () => { test('accepts valid paths', () => { expect(validatePath('/lunacycle').valid).toBe(true); expect(validatePath('/lunacycle/api').valid).toBe(true); expect(validatePath('/a').valid).toBe(true); expect(validatePath('/foo/bar/baz').valid).toBe(true); }); test('rejects paths without leading slash', () => { const result = validatePath('lunacycle'); expect(result.valid).toBe(false); expect(result.errors[0]).toContain('start with /'); }); test('rejects paths with trailing slash', () => { const result = validatePath('/lunacycle/'); expect(result.valid).toBe(false); expect(result.errors[0]).toContain('trailing slash'); }); test('rejects paths with double slashes', () => { const result = validatePath('/luna//cycle'); expect(result.valid).toBe(false); expect(result.errors[0]).toContain('double slashes'); }); test('rejects empty path', () => { expect(validatePath('').valid).toBe(false); }); }); describe('validateRouteRequest', () => { test('accepts valid static route', () => { const result = validateRouteRequest({ slug: 'lunacycle', type: 'static', path: '/lunacycle', }); expect(result.valid).toBe(true); }); test('accepts valid reverse_proxy route', () => { const result = validateRouteRequest({ slug: 'lunacycle', type: 'reverse_proxy', path: '/lunacycle/api', targetHost: '10.0.20.5', targetPort: 3000, }); expect(result.valid).toBe(true); }); test('rejects reverse_proxy without targetHost', () => { const result = validateRouteRequest({ slug: 'lunacycle', type: 'reverse_proxy', path: '/lunacycle/api', targetPort: 3000, }); expect(result.valid).toBe(false); expect(result.errors).toContain('reverse_proxy route requires targetHost'); }); test('rejects reverse_proxy without targetPort', () => { const result = validateRouteRequest({ slug: 'lunacycle', type: 'reverse_proxy', path: '/lunacycle/api', targetHost: '10.0.20.5', }); expect(result.valid).toBe(false); expect(result.errors).toContain('reverse_proxy route requires targetPort'); }); test('rejects invalid port numbers', () => { const result = validateRouteRequest({ slug: 'lunacycle', type: 'reverse_proxy', path: '/lunacycle/api', targetHost: '10.0.20.5', targetPort: 70000, }); expect(result.valid).toBe(false); expect(result.errors[0]).toContain('between 1 and 65535'); }); test('collects multiple errors', () => { const result = validateRouteRequest({ slug: 'BAD SLUG', type: 'reverse_proxy', path: 'no-slash', targetHost: undefined, targetPort: undefined, }); expect(result.valid).toBe(false); expect(result.errors.length).toBeGreaterThan(2); }); });