Files
netbird/management/internals/server/config/relay_test.go
Claude a52a004737 management: validate and normalize relay endpoint transports
Adds Relay.HasURLs/AllURLs helpers so all relay-enabled checks use the
same definition ("any URL is configured, regardless of whether it came
from Addresses or Endpoints"), and a Normalize step run at startup that:

- Drops unknown transport identifiers (anything outside ws/quic/wt),
  reporting them via a warning log so misconfigurations surface loudly
  instead of being silently advertised to clients.
- De-duplicates endpoint URLs and per-endpoint transports.
- Preserves empty Transports lists — that's the valid "unknown, let the
  client try whatever it supports" signal, distinct from a typo'd list.

The two relay-token guard sites in server.go switch to HasURLs so a
deployment with only Endpoints (no Addresses) actually issues tokens;
before this change those branches would skip token generation. The
startup log now prints the merged URL list plus per-endpoint transport
hints, matching what clients will receive.
2026-05-17 11:25:53 +00:00

100 lines
3.1 KiB
Go

package config
import (
"sort"
"testing"
)
func TestRelay_HasURLs(t *testing.T) {
t.Parallel()
cases := []struct {
name string
r *Relay
want bool
}{
{name: "nil", r: nil, want: false},
{name: "empty", r: &Relay{}, want: false},
{name: "addresses only", r: &Relay{Addresses: []string{"rels://a"}}, want: true},
{name: "endpoints only", r: &Relay{Endpoints: []RelayEndpoint{{URL: "rels://a"}}}, want: true},
{name: "endpoint with empty url", r: &Relay{Endpoints: []RelayEndpoint{{URL: ""}}}, want: false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := tc.r.HasURLs(); got != tc.want {
t.Fatalf("HasURLs = %v, want %v", got, tc.want)
}
})
}
}
func TestRelay_AllURLs_dedupes_and_preserves_order(t *testing.T) {
t.Parallel()
r := &Relay{
Addresses: []string{"rels://shared", "rels://addr-only"},
Endpoints: []RelayEndpoint{
{URL: "rels://ep-only"},
{URL: "rels://shared"}, // also in Addresses; should not double up
{URL: ""}, // skipped
},
}
got := r.AllURLs()
want := []string{"rels://ep-only", "rels://shared", "rels://addr-only"}
if len(got) != len(want) {
t.Fatalf("AllURLs = %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("AllURLs[%d] = %q, want %q", i, got[i], want[i])
}
}
}
func TestRelay_Normalize_drops_unknown_transports_and_dupes(t *testing.T) {
t.Parallel()
r := &Relay{
Endpoints: []RelayEndpoint{
{URL: "rels://a", Transports: []string{"ws", "ws", "wt", "bogus", "h2"}},
{URL: "rels://b", Transports: []string{"quic", "wt"}},
{URL: "rels://a", Transports: []string{"ws"}}, // duplicate URL — dropped
{URL: ""}, // empty URL — dropped
},
}
unknown := r.Normalize()
// Unknown transports should be reported (order not specified).
sort.Strings(unknown)
wantUnknown := []string{"bogus", "h2"}
if len(unknown) != len(wantUnknown) {
t.Fatalf("unknown = %v, want %v", unknown, wantUnknown)
}
for i := range wantUnknown {
if unknown[i] != wantUnknown[i] {
t.Fatalf("unknown[%d] = %q, want %q", i, unknown[i], wantUnknown[i])
}
}
if len(r.Endpoints) != 2 {
t.Fatalf("endpoints after Normalize = %d, want 2: %#v", len(r.Endpoints), r.Endpoints)
}
if r.Endpoints[0].URL != "rels://a" || len(r.Endpoints[0].Transports) != 2 ||
r.Endpoints[0].Transports[0] != "ws" || r.Endpoints[0].Transports[1] != "wt" {
t.Fatalf("endpoint a after Normalize = %#v", r.Endpoints[0])
}
if r.Endpoints[1].URL != "rels://b" || len(r.Endpoints[1].Transports) != 2 {
t.Fatalf("endpoint b after Normalize = %#v", r.Endpoints[1])
}
}
func TestRelay_Normalize_keeps_empty_transports(t *testing.T) {
t.Parallel()
// An empty Transports list is "unknown — try every dialer", which is a
// valid signal we must preserve (distinct from "I typoed all entries").
r := &Relay{Endpoints: []RelayEndpoint{{URL: "rels://a"}}}
if u := r.Normalize(); len(u) != 0 {
t.Fatalf("Normalize reported unknown transports on empty list: %v", u)
}
if len(r.Endpoints) != 1 || len(r.Endpoints[0].Transports) != 0 {
t.Fatalf("endpoint mutated: %#v", r.Endpoints)
}
}