Files
member-console/internal/dnsname/validate_test.go
T
cgalo5758 8d05934e93 Add domains registry with claims and placements
Domain names become an allocatable resource with one authority. A new
core module (schema `domains`, own migration stream between core and the
integrations) owns claims — a DNS node plus its whole subtree, mutually
disjoint: operator shared-domain roots, member claims carved from them,
and bring-your-own names proven by TXT verification — and placements,
which bind a name inside a claim to a provider slug and resource ref.

Verification moves to the claim and decouples from creation. A member
proves control of a domain once; afterwards every name inside it places
instantly, wildcard-CNAME friendly, with no further DNS work. The claim
workflow activates the claim and stops — it no longer creates a site —
so the sites list offers a one-click create once a domain verifies.

/domains/ask answers from placements and is registered by core rather
than the FedWiki adapter; its HTTP contract is unchanged. A configured
`domains-ask-fallback-url` forwards names the registry does not know to
a legacy answerer, the strangler seam wiki.cafe's migration needs; a
name the registry knows but has archived is refused locally.

FedWiki's create saga reserves the name before the farm call, carrying a
workflow-minted site id so retries are idempotent, and compensates on
failure. Sync places only names it owns, never stealing a member's;
lifecycle transitions and the retention purge maintain servability. An
unconditional boot pass seeds operator roots, releases orphaned
placements, and adopts pre-existing sites — grandfathering member-owned
external domains shortest-name-first, and skipping name policy, so a
live single-letter site cannot lose its certificate.

Members manage domains at /domains: claims with verification status, DNS
records including an optional wildcard row, check-now, cancel, release.
Name policy (reserved, blocked, premium, plus a single-letter guard) is
operator data; refusals collapse to a plain "unavailable" so the console
never becomes an oracle for who holds what.

BREAKING (pre-release): `fedwiki.custom_domain_verifications` and
`sites.is_custom_domain` are dropped, the flag now derived from the
placement's claim kind; resource key `fedwiki_custom_domains` migrates
to the platform-owned `external_domain_claims`; running
verify-custom-domain workflows must be terminated before deploy.
2026-07-24 21:25:40 -05:00

73 lines
2.1 KiB
Go

package dnsname
import (
"strings"
"testing"
)
func TestValidateDNSLabel(t *testing.T) {
tests := []struct {
name string
label string
wantErr string
}{
{"valid short", "a", ""},
{"valid typical", "my-site", ""},
{"valid numeric", "123", ""},
{"valid max length", strings.Repeat("a", 63), ""},
{"too long", strings.Repeat("a", 64), "63-character limit"},
{"way too long", strings.Repeat("a", 200), "63-character limit"},
{"uppercase", "MySite", "lowercase letters"},
{"underscore", "my_site", "lowercase letters"},
{"starts with hyphen", "-mysite", "lowercase letters"},
{"ends with hyphen", "mysite-", "lowercase letters"},
{"empty", "", "lowercase letters"},
{"spaces", "my site", "lowercase letters"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ValidateDNSLabel(tt.label)
if tt.wantErr == "" {
if got != "" {
t.Errorf("ValidateDNSLabel(%q) = %q, want no error", tt.label, got)
}
} else {
if got == "" {
t.Errorf("ValidateDNSLabel(%q) = no error, want error containing %q", tt.label, tt.wantErr)
} else if !strings.Contains(got, tt.wantErr) {
t.Errorf("ValidateDNSLabel(%q) = %q, want error containing %q", tt.label, got, tt.wantErr)
}
}
})
}
}
func TestValidateFQDN(t *testing.T) {
tests := []struct {
name string
label string
parent string
wantErr bool
}{
{"typical", "mysite", "example.com", false},
{"max label with short domain", strings.Repeat("a", 63), "w.co", false},
// 63-char label + "." + 189-char domain = 253 chars exactly
{"exactly 253", strings.Repeat("a", 63), strings.Repeat("b", 189), false},
// 63-char label + "." + 190-char domain = 254 chars
{"exceeds 253", strings.Repeat("a", 63), strings.Repeat("b", 190), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ValidateFQDN(tt.label, tt.parent)
if tt.wantErr && got == "" {
t.Errorf("ValidateFQDN(%q, %q) = no error, want error", tt.label, tt.parent)
}
if !tt.wantErr && got != "" {
t.Errorf("ValidateFQDN(%q, %q) = %q, want no error", tt.label, tt.parent, got)
}
})
}
}