Files
member-console/internal/dnsname/dnsname_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.3 KiB
Go

package dnsname
import "testing"
func TestNormalize(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"already normal", "wiki.example.org", "wiki.example.org"},
{"uppercase", "Wiki.Example.ORG", "wiki.example.org"},
{"surrounding whitespace", " wiki.example.org\t", "wiki.example.org"},
{"absolute name loses its root label", "wiki.example.org.", "wiki.example.org"},
{"only one trailing dot is stripped", "wiki.example.org..", "wiki.example.org."},
{"whitespace then dot", " Wiki.Example.org. ", "wiki.example.org"},
{"empty", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Normalize(tt.in); got != tt.want {
t.Errorf("Normalize(%q) = %q, want %q", tt.in, got, tt.want)
}
})
}
}
func TestReverseLabels(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"two labels", "example.org", "org.example"},
{"three labels", "alice.example.org", "org.example.alice"},
{"four labels", "a.b.example.org", "org.example.b.a"},
{"single label", "test", "test"},
{"empty", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ReverseLabels(tt.in)
if got != tt.want {
t.Errorf("ReverseLabels(%q) = %q, want %q", tt.in, got, tt.want)
}
if round := ReverseLabels(got); round != tt.in {
t.Errorf("ReverseLabels is not its own inverse: %q -> %q -> %q", tt.in, got, round)
}
})
}
}
// TestReverseLabelsGivesSubtreePrefixes pins the property the registry's
// descendant scans rely on: a name is inside another's subtree exactly when
// its reversed form starts with the reversed parent plus a dot.
func TestReverseLabelsGivesSubtreePrefixes(t *testing.T) {
parent := ReverseLabels("example.org")
inside := []string{"wiki.example.org", "a.b.example.org"}
outside := []string{"example.org", "notexample.org", "example.org.evil.test", "org"}
for _, name := range inside {
if got := ReverseLabels(name); len(got) <= len(parent) || got[:len(parent)+1] != parent+"." {
t.Errorf("%q reversed to %q, want the prefix %q.", name, got, parent)
}
}
for _, name := range outside {
got := ReverseLabels(name)
if len(got) > len(parent) && got[:len(parent)+1] == parent+"." {
t.Errorf("%q reversed to %q, which wrongly matches the %q. subtree", name, got, parent)
}
}
}