@@ -3,6 +3,8 @@ import { http, HttpResponse } from "msw";
33import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
44/* eslint-enable workers-sdk/no-vitest-import-expect */
55import { ServiceType } from "../vpc/index" ;
6+ import { validateHostname , validateRequest } from "../vpc/validation" ;
7+ import type { ServiceArgs } from "../vpc/validation" ;
68import { endEventLoop } from "./helpers/end-event-loop" ;
79import { mockAccountId , mockApiToken } from "./helpers/mock-account-id" ;
810import { mockConsoleMethods } from "./helpers/mock-console" ;
@@ -325,6 +327,150 @@ describe("vpc service commands", () => {
325327 } ) ;
326328} ) ;
327329
330+ describe ( "hostname validation" , ( ) => {
331+ it ( "should accept valid hostnames" , ( { expect } ) => {
332+ expect ( ( ) => validateHostname ( "api.example.com" ) ) . not . toThrow ( ) ;
333+ expect ( ( ) => validateHostname ( "localhost" ) ) . not . toThrow ( ) ;
334+ expect ( ( ) => validateHostname ( "my-service.internal.local" ) ) . not . toThrow ( ) ;
335+ expect ( ( ) => validateHostname ( "sub.domain.example.co.uk" ) ) . not . toThrow ( ) ;
336+ } ) ;
337+
338+ it ( "should reject empty hostname" , ( { expect } ) => {
339+ expect ( ( ) => validateHostname ( "" ) ) . toThrow ( "Hostname cannot be empty." ) ;
340+ expect ( ( ) => validateHostname ( " " ) ) . toThrow ( "Hostname cannot be empty." ) ;
341+ } ) ;
342+
343+ it ( "should reject hostname exceeding 253 characters" , ( { expect } ) => {
344+ const longHostname = "a" . repeat ( 254 ) ;
345+ expect ( ( ) => validateHostname ( longHostname ) ) . toThrow (
346+ "Hostname is too long. Maximum length is 253 characters."
347+ ) ;
348+ } ) ;
349+
350+ it ( "should accept hostname at exactly 253 characters" , ( { expect } ) => {
351+ const label = "a" . repeat ( 63 ) ;
352+ const hostname = `${ label } .${ label } .${ label } .${ label . slice ( 0 , 61 ) } ` ;
353+ expect ( hostname . length ) . toBe ( 253 ) ;
354+ expect ( ( ) => validateHostname ( hostname ) ) . not . toThrow ( ) ;
355+ } ) ;
356+
357+ it ( "should reject hostname with URL scheme" , ( { expect } ) => {
358+ expect ( ( ) => validateHostname ( "https://example.com" ) ) . toThrow (
359+ "Hostname must not include a URL scheme"
360+ ) ;
361+ expect ( ( ) => validateHostname ( "http://example.com" ) ) . toThrow (
362+ "Hostname must not include a URL scheme"
363+ ) ;
364+ } ) ;
365+
366+ it ( "should reject hostname with path" , ( { expect } ) => {
367+ expect ( ( ) => validateHostname ( "example.com/path" ) ) . toThrow (
368+ "Hostname must not include a path"
369+ ) ;
370+ } ) ;
371+
372+ it ( "should reject bare IPv4 address" , ( { expect } ) => {
373+ expect ( ( ) => validateHostname ( "192.168.1.1" ) ) . toThrow (
374+ "Hostname must not be an IP address. Use --ipv4 or --ipv6 instead."
375+ ) ;
376+ expect ( ( ) => validateHostname ( "10.0.0.1" ) ) . toThrow (
377+ "Hostname must not be an IP address"
378+ ) ;
379+ } ) ;
380+
381+ it ( "should reject bare IPv6 address" , ( { expect } ) => {
382+ expect ( ( ) => validateHostname ( "::1" ) ) . toThrow (
383+ "Hostname must not be an IP address"
384+ ) ;
385+ expect ( ( ) => validateHostname ( "2001:db8::1" ) ) . toThrow (
386+ "Hostname must not be an IP address"
387+ ) ;
388+ expect ( ( ) => validateHostname ( "[::1]" ) ) . toThrow (
389+ "Hostname must not be an IP address"
390+ ) ;
391+ } ) ;
392+
393+ it ( "should reject hostname with port" , ( { expect } ) => {
394+ expect ( ( ) => validateHostname ( "example.com:8080" ) ) . toThrow (
395+ "Hostname must not include a port number"
396+ ) ;
397+ } ) ;
398+
399+ it ( "should reject hostname with whitespace" , ( { expect } ) => {
400+ expect ( ( ) => validateHostname ( "bad host.com" ) ) . toThrow (
401+ "Hostname must not contain whitespace"
402+ ) ;
403+ } ) ;
404+
405+ it ( "should accept hostnames with underscores" , ( { expect } ) => {
406+ expect ( ( ) => validateHostname ( "_dmarc.example.com" ) ) . not . toThrow ( ) ;
407+ expect ( ( ) => validateHostname ( "my_service.internal" ) ) . not . toThrow ( ) ;
408+ } ) ;
409+
410+ it ( "should reject invalid hostname via wrangler service create" , async ( ) => {
411+ await expect ( ( ) =>
412+ runWrangler (
413+ "vpc service create test-bad-hostname --type http --hostname https://example.com --tunnel-id 550e8400-e29b-41d4-a716-446655440000"
414+ )
415+ ) . rejects . toThrow ( "Hostname must not include a URL scheme" ) ;
416+ } ) ;
417+
418+ it ( "should reject IP address as hostname via wrangler service create" , async ( ) => {
419+ await expect ( ( ) =>
420+ runWrangler (
421+ "vpc service create test-ip-hostname --type http --hostname 192.168.1.1 --tunnel-id 550e8400-e29b-41d4-a716-446655440000"
422+ )
423+ ) . rejects . toThrow ( "Hostname must not be an IP address" ) ;
424+ } ) ;
425+ } ) ;
426+
427+ describe ( "IP address validation" , ( ) => {
428+ const baseArgs : ServiceArgs = {
429+ name : "test" ,
430+ type : ServiceType . Http ,
431+ tunnelId : "550e8400-e29b-41d4-a716-446655440000" ,
432+ } ;
433+
434+ it ( "should accept valid IPv4 addresses" , ( { expect } ) => {
435+ expect ( ( ) =>
436+ validateRequest ( { ...baseArgs , ipv4 : "192.168.1.1" } )
437+ ) . not . toThrow ( ) ;
438+ expect ( ( ) =>
439+ validateRequest ( { ...baseArgs , ipv4 : "10.0.0.1" } )
440+ ) . not . toThrow ( ) ;
441+ } ) ;
442+
443+ it ( "should reject invalid IPv4 addresses" , ( { expect } ) => {
444+ expect ( ( ) =>
445+ validateRequest ( { ...baseArgs , ipv4 : "not-an-ip" } )
446+ ) . toThrow ( "Invalid IPv4 address" ) ;
447+ expect ( ( ) =>
448+ validateRequest ( { ...baseArgs , ipv4 : "999.999.999.999" } )
449+ ) . toThrow ( "Invalid IPv4 address" ) ;
450+ expect ( ( ) =>
451+ validateRequest ( { ...baseArgs , ipv4 : "example.com" } )
452+ ) . toThrow ( "Invalid IPv4 address" ) ;
453+ } ) ;
454+
455+ it ( "should accept valid IPv6 addresses" , ( { expect } ) => {
456+ expect ( ( ) =>
457+ validateRequest ( { ...baseArgs , ipv6 : "::1" } )
458+ ) . not . toThrow ( ) ;
459+ expect ( ( ) =>
460+ validateRequest ( { ...baseArgs , ipv6 : "2001:db8::1" } )
461+ ) . not . toThrow ( ) ;
462+ } ) ;
463+
464+ it ( "should reject invalid IPv6 addresses" , ( { expect } ) => {
465+ expect ( ( ) =>
466+ validateRequest ( { ...baseArgs , ipv6 : "not-an-ip" } )
467+ ) . toThrow ( "Invalid IPv6 address" ) ;
468+ expect ( ( ) =>
469+ validateRequest ( { ...baseArgs , ipv6 : "192.168.1.1" } )
470+ ) . toThrow ( "Invalid IPv6 address" ) ;
471+ } ) ;
472+ } ) ;
473+
328474const mockService : ConnectivityService = {
329475 service_id : "service-uuid" ,
330476 type : ServiceType . Http ,
0 commit comments