hscontrol/servertest: add policy, route, ephemeral, and content tests

Extend the servertest harness with:
- TestClient.Direct() accessor for advanced operations
- TestClient.WaitForPeerCount and WaitForCondition helpers
- TestHarness.ChangePolicy for ACL policy testing
- AssertDERPMapPresent and AssertSelfHasAddresses

New test suites:
- content_test.go: self node, DERP map, peer properties, user profiles,
  update history monotonicity, and endpoint update propagation
- policy_test.go: default allow-all, explicit policy, policy triggers
  updates on all nodes, multiple policy changes, multi-user mesh
- ephemeral_test.go: ephemeral connect, cleanup after disconnect,
  mixed ephemeral/regular, reconnect prevents cleanup
- routes_test.go: addresses in AllowedIPs, route advertise and approve,
  advertised routes via hostinfo, CGNAT range validation

Also fix node_departs test to use WaitForCondition instead of
assert.Eventually, and convert concurrent_join_and_leave to
interleaved_join_and_leave with grace-period-tolerant assertions.
This commit is contained in:
Kristoffer Dalby
2026-03-16 19:09:10 +00:00
parent ca7362e9aa
commit f87b08676d
9 changed files with 932 additions and 30 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/juanfont/headscale/hscontrol/servertest"
"github.com/stretchr/testify/assert"
"tailscale.com/types/netmap"
)
// TestConnectionLifecycle exercises the core node lifecycle:
@@ -41,13 +42,22 @@ func TestConnectionLifecycle(t *testing.T) {
departingName := h.Client(2).Name
h.Client(2).Disconnect(t)
// The remaining clients should eventually stop seeing the
// departed node (after the grace period).
assert.Eventually(t, func() bool {
_, found := h.Client(0).PeerByName(departingName)
return !found
}, 30*time.Second, 500*time.Millisecond,
"client 0 should stop seeing departed node")
// The remaining clients should eventually see the departed
// node go offline or disappear. The grace period in poll.go
// is 10 seconds, so we need a generous timeout.
h.Client(0).WaitForCondition(t, "peer offline or gone", 60*time.Second,
func(nm *netmap.NetworkMap) bool {
for _, p := range nm.Peers {
hi := p.Hostinfo()
if hi.Valid() && hi.Hostname() == departingName {
isOnline, known := p.Online().GetOk()
// Peer is still present but offline is acceptable.
return known && !isOnline
}
}
// Peer gone entirely is also acceptable.
return true
})
})
t.Run("reconnect_restores_mesh", func(t *testing.T) {