package domain import ( "testing" ) func TestGenerateSlug(t *testing.T) { t.Run("generates correct length", func(t *testing.T) { slug, err := GenerateSlug() if err != nil { t.Fatalf("unexpected error: %v", err) } if len(slug) != SlugLength { t.Errorf("slug length = %d, want %d", len(slug), SlugLength) } }) t.Run("contains only allowed characters", func(t *testing.T) { for i := 0; i < 100; i++ { slug, err := GenerateSlug() if err != nil { t.Fatalf("unexpected error: %v", err) } if err := ValidateSlug(slug); err != nil { t.Errorf("generated slug %q failed validation: %v", slug, err) } } }) t.Run("generates unique slugs", func(t *testing.T) { seen := make(map[string]bool) for i := 0; i < 1000; i++ { slug, err := GenerateSlug() if err != nil { t.Fatalf("unexpected error: %v", err) } if seen[slug] { t.Errorf("duplicate slug generated: %s", slug) } seen[slug] = true } }) } func TestValidateSlug(t *testing.T) { tests := []struct { slug string wantErr bool }{ {"k7m2x9p4", false}, {"abcdefgh", false}, {"23456789", false}, {"", true}, // empty {"short", true}, // too short {"toolongslug", true}, // too long {"ABCDEFGH", true}, // uppercase {"k7m2x9p0", true}, // contains 0 {"k7m2x9p1", true}, // contains 1 {"k7m2x9po", true}, // contains o {"k7m2x9pl", true}, // contains l {"k7m2x9pi", true}, // contains i {"k7m2-9p4", true}, // contains hyphen {"k7m2_9p4", true}, // contains underscore } for _, tt := range tests { t.Run(tt.slug, func(t *testing.T) { err := ValidateSlug(tt.slug) if (err != nil) != tt.wantErr { t.Errorf("ValidateSlug(%q) error = %v, wantErr = %v", tt.slug, err, tt.wantErr) } }) } } func TestDomainType(t *testing.T) { t.Run("Valid", func(t *testing.T) { tests := []struct { dtype DomainType want bool }{ {DomainTypePrimaryAuto, true}, {DomainTypePrimaryCustom, true}, {DomainTypeAlias, true}, {DomainType("invalid"), false}, {DomainType(""), false}, } for _, tt := range tests { if got := tt.dtype.Valid(); got != tt.want { t.Errorf("%q.Valid() = %v, want %v", tt.dtype, got, tt.want) } } }) t.Run("IsPrimary", func(t *testing.T) { tests := []struct { dtype DomainType want bool }{ {DomainTypePrimaryAuto, true}, {DomainTypePrimaryCustom, true}, {DomainTypeAlias, false}, } for _, tt := range tests { if got := tt.dtype.IsPrimary(); got != tt.want { t.Errorf("%q.IsPrimary() = %v, want %v", tt.dtype, got, tt.want) } } }) } func TestValidateSubdomain(t *testing.T) { tests := []struct { subdomain string wantErr bool }{ {"my-app", false}, {"myapp123", false}, {"a", false}, {"landing-page", false}, {"", true}, // empty {"-myapp", true}, // starts with hyphen {"123app", true}, // starts with number {"my_app", true}, // contains underscore {"MyApp", true}, // contains uppercase {"my.app", true}, // contains dot {"www", true}, // reserved {"api", true}, // reserved {"git", true}, // reserved } for _, tt := range tests { t.Run(tt.subdomain, func(t *testing.T) { err := ValidateSubdomain(tt.subdomain) if (err != nil) != tt.wantErr { t.Errorf("ValidateSubdomain(%q) error = %v, wantErr = %v", tt.subdomain, err, tt.wantErr) } }) } } func TestValidateFQDN(t *testing.T) { tests := []struct { domain string wantErr bool }{ {"example.com", false}, {"www.example.com", false}, {"sub.domain.example.com", false}, {"my-app.threesix.ai", false}, {"a.b.c", false}, {"", true}, {"example", false}, // single label is valid {"-example.com", true}, // starts with hyphen {"example-.com", true}, // ends with hyphen {"exam ple.com", true}, // contains space {"example..com", true}, // double dot } for _, tt := range tests { t.Run(tt.domain, func(t *testing.T) { err := ValidateFQDN(tt.domain) if (err != nil) != tt.wantErr { t.Errorf("ValidateFQDN(%q) error = %v, wantErr = %v", tt.domain, err, tt.wantErr) } }) } } func TestIsSubdomainOf(t *testing.T) { tests := []struct { domain string baseDomain string want bool }{ {"my-app.threesix.ai", "threesix.ai", true}, {"deep.sub.threesix.ai", "threesix.ai", true}, {"example.com", "threesix.ai", false}, {"threesix.ai", "threesix.ai", false}, // same domain, not subdomain {"MY-APP.THREESIX.AI", "threesix.ai", true}, // case insensitive } for _, tt := range tests { t.Run(tt.domain, func(t *testing.T) { if got := IsSubdomainOf(tt.domain, tt.baseDomain); got != tt.want { t.Errorf("IsSubdomainOf(%q, %q) = %v, want %v", tt.domain, tt.baseDomain, got, tt.want) } }) } } func TestExtractSubdomain(t *testing.T) { tests := []struct { domain string baseDomain string want string }{ {"my-app.threesix.ai", "threesix.ai", "my-app"}, {"deep.sub.threesix.ai", "threesix.ai", "deep.sub"}, {"example.com", "threesix.ai", ""}, {"threesix.ai", "threesix.ai", ""}, {"MY-APP.THREESIX.AI", "threesix.ai", "my-app"}, // case normalized } for _, tt := range tests { t.Run(tt.domain, func(t *testing.T) { if got := ExtractSubdomain(tt.domain, tt.baseDomain); got != tt.want { t.Errorf("ExtractSubdomain(%q, %q) = %q, want %q", tt.domain, tt.baseDomain, got, tt.want) } }) } }