1package domain
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/stretchr/testify/assert"
8)
9
10func TestIsValidRouteDomain(t *testing.T) {
11 tests := []struct {
12 name string
13 domain string
14 want bool
15 }{
16 // Valid public hostnames
17 {"valid domain", "app.example.com", true},
18 {"valid subdomain", "api.app.example.com", true},
19 {"valid single subdomain", "example.com", true},
20 {"valid with hyphens", "my-app.example-site.com", true},
21
22 // Invalid: empty
23 {"empty domain", "", false},
24
25 // Invalid: IP literals (SSRF risk)
26 {"IPv4 address", "192.168.1.1", false},
27 {"IPv4 with path", "10.0.0.1/path", false},
28 {"IPv4 localhost", "127.0.0.1", false},
29 {"IPv6 address", "::1", false},
30 {"IPv6 full", "2001:db8::1", false},
31 {"IPv6 bracketed", "[::1]", false},
32 {"IPv6 with zone", "fe80::1%eth0", false},
33
34 // Invalid: localhost variants
35 {"localhost", "localhost", false},
36 {"localhost.localdomain", "localhost.localdomain", false},
37
38 // Invalid: internal-only TLDs
39 {"dot local", "myapp.local", false},
40 {"dot internal", "service.internal", false},
41 {"subdomain local", "api.myapp.local", false},
42
43 // Invalid: port in authority
44 {"with port", "example.com:8080", false},
45 {"localhost with port", "localhost:3000", false},
46 {"IP with port", "192.168.1.1:8080", false},
47
48 // Invalid: scheme or path components
49 {"with scheme", "https://example.com", false},
50 {"with path", "example.com/path", false},
51 {"with query", "example.com?query=1", false},
52
53 // Invalid: wildcards or patterns
54 {"wildcard", "*.example.com", false},
55 {"star anywhere", "exa*mple.com", false},
56
57 // Edge cases
58 {"single label", "myapp", false},
59 {"single label numeric", "api", false},
60 {"trailing dot", "example.com.", false},
61 {"leading dot", ".example.com", false},
62 {"double dots", "exa..mple.com", false},
63
64 // Invalid: label starts or ends with hyphen
65 {"label starts with hyphen", "-app.example.com", false},
66 {"label ends with hyphen", "app-.example.com", false},
67 {"label starts and ends with hyphen", "-app-.example.com", false},
68 {"root label starts with hyphen", "-example.com", false},
69 {"root label ends with hyphen", "example-.com", false},
70
71 // Invalid: label too long (>63 chars)
72 {"label too long", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com", false},
73
74 // Invalid: total hostname too long (>253 chars)
75 {"hostname too long", strings.Repeat("a", 55) + "." + strings.Repeat("b", 55) + "." + strings.Repeat("c", 55) + "." + strings.Repeat("d", 55) + "." + strings.Repeat("e", 30) + ".com", false},
76
77 // Invalid: .localhost suffix (DNS rebinding risk)
78 {"dot localhost suffix", "app.localhost", false},
79 {"deep localhost suffix", "sub.app.localhost", false},
80 {"mixed case localhost suffix", "app.Localhost", false},
81 }
82
83 for _, tt := range tests {
84 t.Run(tt.name, func(t *testing.T) {
85 got := IsValidRouteDomain(tt.domain)
86 assert.Equal(t, tt.want, got, "IsValidRouteDomain(%q)", tt.domain)
87 })
88 }
89}