all: apply godoc [Name] link conventions across comments
Some checks are pending
Build / build-nix (push) Waiting to run
Build / build-cross (GOARCH=amd64 GOOS=darwin) (push) Waiting to run
Build / build-cross (GOARCH=amd64 GOOS=linux) (push) Waiting to run
Build / build-cross (GOARCH=arm64 GOOS=darwin) (push) Waiting to run
Build / build-cross (GOARCH=arm64 GOOS=linux) (push) Waiting to run
Check Generated Files / check-generated (push) Waiting to run
Build (main) / container (push) Waiting to run
Build (main) / binaries (amd64, darwin) (push) Waiting to run
Build (main) / binaries (amd64, linux) (push) Waiting to run
Build (main) / binaries (arm64, darwin) (push) Waiting to run
Build (main) / binaries (arm64, linux) (push) Waiting to run
NixOS Module Tests / nix-module-check (push) Waiting to run
Tests / test (push) Waiting to run

Every Go-identifier reference in // and /* */ comments now uses
godoc's [Name] linking syntax so pkg.go.dev and `go doc` render
them as clickable cross-references. No behaviour change.

Pattern applied across the tree:
  In-package         [Foo], [Foo.Bar]
  Cross-package      [pkg.Foo], [pkg.Foo.Bar]
  Stdlib             [netip.Prefix], [errors.Is], [context.Context]
  Tailscale          [tailcfg.MapResponse], [tailcfg.Node.CapMap],
                     [tailcfg.NodeAttrSuggestExitNode]

Skip rules:
  - File:line refs left as plain text
  - HuJSON wire keys inside backtick raw strings untouched
  - ACL/policy syntax tokens (tag:foo, autogroup:self, ...) not Go
    symbols, left as plain text
  - JSON/OIDC wire keys, gorm tags, RFC IPv6 placeholders, markdown
    link tags, decorative dividers — all left as-is
This commit is contained in:
Kristoffer Dalby 2026-05-18 18:35:53 +00:00
parent 17236fd284
commit 4cca63155d
124 changed files with 1037 additions and 1011 deletions

View file

@ -1268,9 +1268,9 @@ func TestACLAutogroupTagged(t *testing.T) {
}
// Create the tailscale node with appropriate options.
// CACert and HeadscaleName are passed explicitly because
// nodes created via CreateTailscaleNode are not part of
// the standard CreateHeadscaleEnv flow.
// [tsic.WithCACert] and [tsic.WithHeadscaleName] are passed explicitly because
// nodes created via [Scenario.CreateTailscaleNode] are not part of
// the standard [Scenario.CreateHeadscaleEnv] flow.
opts := []tsic.Option{
tsic.WithCACert(headscale.GetCert()),
tsic.WithHeadscaleName(headscale.GetHostname()),
@ -1558,9 +1558,9 @@ func TestACLAutogroupSelf(t *testing.T) {
require.NoError(t, err)
// Create router node (tags come from the PreAuthKey).
// CACert and HeadscaleName are passed explicitly because
// nodes created via tsic.New are not part of the standard
// CreateHeadscaleEnv flow.
// [tsic.WithCACert] and [tsic.WithHeadscaleName] are passed explicitly because
// nodes created via [tsic.New] are not part of the standard
// [Scenario.CreateHeadscaleEnv] flow.
routerClient, err := tsic.New(
scenario.Pool(),
"unstable",
@ -2084,9 +2084,9 @@ func TestACLPolicyPropagationOverTime(t *testing.T) {
err = headscale.SetPolicy(user1ToUser2Policy)
require.NoError(t, err)
// Note: Cannot use WaitForTailscaleSync() here because directional policy means
// Note: Cannot use [Scenario.WaitForTailscaleSync] here because directional policy means
// user2 nodes don't see user1 nodes in their peer list (asymmetric visibility).
// The EventuallyWithT block below will handle waiting for policy propagation.
// The [assert.EventuallyWithT] block below will handle waiting for policy propagation.
// Test ALL connectivity (positive and negative) in one block after policy settles
t.Logf("Iteration %d: Phase 3 - Testing all connectivity with directional policy", iteration)
@ -2625,9 +2625,9 @@ func TestACLTagPropagation(t *testing.T) {
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying tag change applied")
// Step 3: Verify final NetMap visibility first (fast signal that
// the MapResponse propagated to the client).
// the [tailcfg.MapResponse] propagated to the client).
// The full propagation chain (docker exec → gRPC → state update →
// batcher delay → MapResponse → noise transport → client processing)
// batcher delay → [tailcfg.MapResponse] → noise transport → client processing)
// can take over 120s on congested CI runners, so use a generous
// base timeout.
t.Logf("Step 3: Verifying final NetMap visibility (expect visible=%v)", tt.finalAccess)
@ -2653,7 +2653,7 @@ func TestACLTagPropagation(t *testing.T) {
}, integrationutil.HASlowConvergeTimeout, integrationutil.SlowPoll, "verifying NetMap visibility propagated after tag change")
// Step 4: Verify final access state (this is the key test for #2389).
// Even though Step 3 confirmed the MapResponse arrived, the full
// Even though Step 3 confirmed the [tailcfg.MapResponse] arrived, the full
// WireGuard handshake and tunnel establishment can take significant
// time on congested CI runners, so use the same generous base
// timeout as Step 3.
@ -2858,7 +2858,7 @@ func TestACLTagPropagationPortSpecific(t *testing.T) {
// Step 4: Verify HTTP on port 80 now fails (tag:sshonly only allows port 22).
// Port-specific filter changes are harder than peer removal because
// the WireGuard tunnel stays up and both endpoints must process
// the new PacketFilter from the MapResponse.
// the new [tailcfg.PacketFilter] from the [tailcfg.MapResponse].
t.Log("Step 4: Verifying HTTP access is now blocked (tag:sshonly only allows port 22)")
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertCurlFailWithCollect(c, user2Node, targetURL, "HTTP should fail with tag:sshonly (only port 22 allowed)")
@ -3099,7 +3099,7 @@ func TestACLGroupAfterUserDeletion(t *testing.T) {
assertCurlDockerHostname(c, user1, url, "user1 should still be able to reach user2 after user3 deletion (stale cache)")
}, integrationutil.HAConvergeTimeout, integrationutil.SlowPoll, "user1 -> user2 after user3 deletion")
// Step 4: Create a NEW user - this triggers updatePolicyManagerUsers() which
// Step 4: Create a NEW user - this triggers [State.updatePolicyManagerUsers] which
// re-evaluates the policy. According to issue #2967, this is when the bug manifests:
// the deleted user3@ in the group causes the entire group to fail resolution.
t.Log("Step 4: Creating a new user (user4) to trigger policy re-evaluation")
@ -3275,7 +3275,7 @@ func TestACLGroupDeletionExactReproduction(t *testing.T) {
t.Log("Step 3: PASSED - connectivity works after user2 deletion")
// Step 4: Create a NEW user - this triggers updatePolicyManagerUsers()
// Step 4: Create a NEW user - this triggers [State.updatePolicyManagerUsers]
// According to the reporter, this is when the bug manifests
t.Log("Step 4: Creating new user (user4) - this triggers policy re-evaluation")
@ -3283,8 +3283,8 @@ func TestACLGroupDeletionExactReproduction(t *testing.T) {
require.NoError(t, err)
// Step 5: THE CRITICAL TEST - verify connectivity STILL works
// Without the fix: DeleteUser didn't update policy, so when CreateUser
// triggers updatePolicyManagerUsers(), the stale user2@ is now unknown,
// Without the fix: [state.State.DeleteUser] didn't update policy, so when [state.State.CreateUser]
// triggers [State.updatePolicyManagerUsers], the stale user2@ is now unknown,
// potentially breaking the group.
t.Log("Step 5: Verifying connectivity AFTER creating new user (BUG trigger point)")

View file

@ -522,8 +522,8 @@ func TestGRPCAuthenticationBypass(t *testing.T) {
require.NoError(t, err,
"gRPC connection with valid API key should succeed, output: %s", output)
// CLI outputs the users array directly, not wrapped in ListUsersResponse
// Parse as JSON array (CLI uses json.Marshal, not protojson)
// CLI outputs the users array directly, not wrapped in [v1.ListUsersResponse]
// Parse as JSON array (CLI uses [json.Marshal], not protojson)
var users []*v1.User
err = json.Unmarshal([]byte(output), &users)
@ -681,8 +681,8 @@ cli:
require.NoError(t, err,
"CLI with valid API key should succeed")
// CLI outputs the users array directly, not wrapped in ListUsersResponse
// Parse as JSON array (CLI uses json.Marshal, not protojson)
// CLI outputs the users array directly, not wrapped in [v1.ListUsersResponse]
// Parse as JSON array (CLI uses [json.Marshal], not protojson)
var users []*v1.User
err = json.Unmarshal([]byte(output), &users)

View file

@ -57,7 +57,7 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
expectedNodes := collectExpectedNodeIDs(t, allClients)
requireAllClientsOnline(t, headscale, expectedNodes, true, "all clients should be connected", integrationutil.ScaledTimeout(120*time.Second))
// Validate that all nodes have NetInfo and DERP servers before logout
// Validate that all nodes have [tailcfg.NetInfo] and DERP servers before logout
requireAllClientsNetInfoAndDERP(t, headscale, expectedNodes, "all clients should have NetInfo and DERP before logout", 3*time.Minute)
// assertClientsState(t, allClients)
@ -161,11 +161,11 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
requireAllClientsOnline(t, headscale, expectedNodes, true, "all clients should be connected to batcher", integrationutil.ScaledTimeout(120*time.Second))
// Wait for Tailscale sync before validating NetInfo to ensure proper state propagation
// Wait for Tailscale sync before validating [tailcfg.NetInfo] to ensure proper state propagation
err = scenario.WaitForTailscaleSync()
requireNoErrSync(t, err)
// Validate that all nodes have NetInfo and DERP servers after reconnection
// Validate that all nodes have [tailcfg.NetInfo] and DERP servers after reconnection
requireAllClientsNetInfoAndDERP(t, headscale, expectedNodes, "all clients should have NetInfo and DERP after reconnection", 3*time.Minute)
err = scenario.WaitForTailscaleSync()
@ -475,7 +475,7 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) {
// Steps:
// 1. Create node with auth key
// 2. DELETE the auth key from database (completely remove it)
// 3. Restart node - should successfully reconnect using MachineKey identity.
// 3. Restart node - should successfully reconnect using [tailcfg.Node.MachineKey] identity.
func TestAuthKeyDeleteKey(t *testing.T) {
IntegrationSkip(t)
@ -561,7 +561,7 @@ func TestAuthKeyDeleteKey(t *testing.T) {
// Verify node comes back online
// This will FAIL without the fix because auth key validation will reject deleted key
// With the fix, MachineKey identity allows reconnection even with deleted key
// With the fix, [tailcfg.Node.MachineKey] identity allows reconnection even with deleted key
requireAllClientsOnline(t, headscale, []types.NodeID{types.NodeID(nodeID)}, true, "node should reconnect after restart despite deleted key", integrationutil.ScaledTimeout(120*time.Second))
t.Logf("✓ Node successfully reconnected after its auth key was deleted")
@ -725,9 +725,9 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) {
node.GetAvailableRoutes(), node.GetApprovedRoutes(), node.GetSubnetRoutes())
// This is where issue #2896 manifests:
// - Available shows the route (from Hostinfo.RoutableIPs)
// - Approved shows the route (from ApprovedRoutes)
// - BUT Serving (SubnetRoutes/PrimaryRoutes) is EMPTY!
// - Available shows the route (from [tailcfg.Hostinfo.RoutableIPs])
// - Approved shows the route (from [tailcfg.Node.ApprovedRoutes])
// - BUT Serving ([tailcfg.Node.SubnetRoutes]/[ipnstate.PeerStatus.PrimaryRoutes]) is EMPTY!
assert.Lenf(c, node.GetAvailableRoutes(), 1,
"Node should have 1 available route after relogin, got %v", node.GetAvailableRoutes())
assert.Lenf(c, node.GetApprovedRoutes(), 1,

View file

@ -28,7 +28,7 @@ import (
func TestOIDCAuthenticationPingAll(t *testing.T) {
IntegrationSkip(t)
// Logins to MockOIDC is served by a queue with a strict order,
// Logins to [mockoidc.MockOIDC] is served by a queue with a strict order,
// if we use more than one node per user, the order of the logins
// will not be deterministic and the test will fail.
spec := ScenarioSpec{
@ -202,10 +202,10 @@ func TestOIDCExpireNodesBasedOnTokenExpiry(t *testing.T) {
t.Logf("Waiting %v for OIDC tokens to expire (TTL: %v, spread: %v, buffer: %v)",
totalWaitTime, shortAccessTTL, loginTimeSpread, safetyBuffer)
// EventuallyWithT retries the test function until it passes or times out.
// IMPORTANT: Use 'ct' (CollectT) for all assertions inside the function, not 't'.
// [assert.EventuallyWithT] retries the test function until it passes or times out.
// IMPORTANT: Use 'ct' ([assert.CollectT]) for all assertions inside the function, not 't'.
// Using 't' would cause immediate test failure without retries, defeating the purpose
// of EventuallyWithT which is designed to handle timing-dependent conditions.
// of [assert.EventuallyWithT] which is designed to handle timing-dependent conditions.
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
// Check each client's status individually to provide better diagnostics
expiredCount := 0
@ -718,7 +718,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for user2 logout to complete before user1 relogin")
// Before logging back in, ensure we still have exactly 2 nodes
// Note: We skip validateLogoutComplete here since it expects all nodes to be offline,
// Note: We skip [validateLogoutComplete] here since it expects all nodes to be offline,
// but in OIDC scenario we maintain both nodes in DB with only active user online
// Additional validation that nodes are properly maintained during logout
@ -1468,8 +1468,8 @@ func TestOIDCExpiryAfterRestart(t *testing.T) {
// 4. Verifies that the OIDC user's node IMMEDIATELY sees the advertised route
//
// Expected behavior:
// - Without fix: OIDC node cannot see the route (PrimaryRoutes is nil/empty)
// - With fix: OIDC node immediately sees the route in PrimaryRoutes
// - Without fix: OIDC node cannot see the route ([ipnstate.PeerStatus.PrimaryRoutes] is nil/empty)
// - With fix: OIDC node immediately sees the route in [ipnstate.PeerStatus.PrimaryRoutes]
//
// Root cause: The buggy code called a.h.Change(c) immediately after user
// creation but BEFORE node registration completed, creating a race condition
@ -1618,8 +1618,8 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
// see the gateway's advertised route WITHOUT needing a client restart.
//
// This is where the bug manifests:
// - Without fix: PrimaryRoutes will be nil/empty
// - With fix: PrimaryRoutes immediately contains the advertised route
// - Without fix: [ipnstate.PeerStatus.PrimaryRoutes] will be nil/empty
// - With fix: [ipnstate.PeerStatus.PrimaryRoutes] immediately contains the advertised route
t.Logf("Verifying OIDC user can immediately see advertised routes at %s", time.Now().Format(TimestampFormat))
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
@ -1641,7 +1641,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
assert.NotNil(ct, gatewayPeer, "OIDC user should see gateway as peer")
if gatewayPeer != nil {
// This is the critical assertion - PrimaryRoutes should NOT be nil
// This is the critical assertion - [ipnstate.PeerStatus.PrimaryRoutes] should NOT be nil
assert.NotNil(ct, gatewayPeer.PrimaryRoutes,
"BUG #2888: Gateway peer PrimaryRoutes is nil - ACL policy not applied to new OIDC node!")
@ -1652,7 +1652,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
t.Logf("SUCCESS: OIDC user can see advertised route %s in gateway's PrimaryRoutes", advertiseRoute)
}
// Also verify AllowedIPs includes the route
// Also verify [ipnstate.PeerStatus.AllowedIPs] includes the route
if gatewayPeer.AllowedIPs != nil && gatewayPeer.AllowedIPs.Len() > 0 {
allowedIPs := gatewayPeer.AllowedIPs.AsSlice()
t.Logf("Gateway peer AllowedIPs: %v", allowedIPs)
@ -1914,9 +1914,9 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
node.GetAvailableRoutes(), node.GetApprovedRoutes(), node.GetSubnetRoutes())
// This is where issue #2896 manifests:
// - Available shows the route (from Hostinfo.RoutableIPs)
// - Approved shows the route (from ApprovedRoutes)
// - BUT Serving (SubnetRoutes/PrimaryRoutes) is EMPTY!
// - Available shows the route (from [tailcfg.Hostinfo.RoutableIPs])
// - Approved shows the route (from [tailcfg.Node.ApprovedRoutes])
// - BUT Serving ([tailcfg.Node.SubnetRoutes]/[ipnstate.PeerStatus.PrimaryRoutes]) is EMPTY!
assert.Lenf(c, node.GetAvailableRoutes(), 1,
"Node should have 1 available route after relogin, got %v", node.GetAvailableRoutes())
assert.Lenf(c, node.GetApprovedRoutes(), 1,

View file

@ -339,7 +339,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth keys list")
// There is one key created by "scenario.CreateHeadscaleEnv"
// There is one key created by [Scenario.CreateHeadscaleEnv]
assert.Len(t, listedPreAuthKeys, 4)
assert.Equal(
@ -476,7 +476,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth keys list")
// There is one key created by "scenario.CreateHeadscaleEnv"
// There is one key created by [Scenario.CreateHeadscaleEnv]
assert.Len(t, listedPreAuthKeys, 2)
assert.True(t, listedPreAuthKeys[1].GetExpiration().AsTime().After(time.Now()))
@ -565,7 +565,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth keys list after reusable/ephemeral creation")
// There is one key created by "scenario.CreateHeadscaleEnv"
// There is one key created by [Scenario.CreateHeadscaleEnv]
assert.Len(t, listedPreAuthKeys, 3)
}
@ -662,7 +662,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
status, err := client.Status()
assert.NoError(ct, err)
assert.Equal(ct, "Running", status.BackendState, "Expected node to be logged in, backend state: %s", status.BackendState)
// With tags-as-identity model, tagged nodes show as TaggedDevices user (2147455555)
// With tags-as-identity model, tagged nodes show as [types.TaggedDevices] user (2147455555)
// The PreAuthKey was created with tags, so the node is tagged
assert.Equal(ct, "userid:2147455555", status.Self.UserID.String(), "Expected node to be logged in as tagged-devices user")
}, integrationutil.StatusReadyTimeout, 2*time.Second)
@ -761,7 +761,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
status, err := client.Status()
assert.NoError(ct, err)
assert.Equal(ct, "Running", status.BackendState, "Expected node to be logged in, backend state: %s", status.BackendState)
// With tags-as-identity model, tagged nodes show as TaggedDevices user (2147455555)
// With tags-as-identity model, tagged nodes show as [types.TaggedDevices] user (2147455555)
assert.Equal(ct, "userid:2147455555", status.Self.UserID.String(), "Expected node to be logged in as tagged-devices user")
}, integrationutil.StatusReadyTimeout, 2*time.Second)

View file

@ -72,12 +72,12 @@ func TestDERPVerifyEndpoint(t *testing.T) {
},
}
// WithHostname is used instead of WithTestName because the hostname
// [hsic.WithHostname] is used instead of [hsic.WithTestName] because the hostname
// must match the pre-generated TLS certificate created above.
// The test name "derpverify" is embedded in the hostname variable.
//
// WithCACert passes the external DERP server's certificate so
// tailscale clients trust it. WithCustomTLS and WithDERPConfig
// [tsic.WithCACert] passes the external DERP server's certificate so
// tailscale clients trust it. [hsic.WithCustomTLS] and [hsic.WithDERPConfig]
// configure headscale to use the external DERP server created
// above instead of the default embedded one.
err = scenario.CreateHeadscaleEnv([]tsic.Option{tsic.WithCACert(derper.GetCert())},

View file

@ -22,9 +22,9 @@ func GetIntegrationRunID() string {
return os.Getenv("HEADSCALE_INTEGRATION_RUN_ID")
}
// DockerAddIntegrationLabels adds integration test labels to Docker RunOptions.
// DockerAddIntegrationLabels adds integration test labels to Docker [dockertest.RunOptions].
// This allows the hi tool to identify containers belonging to specific test runs.
// This function should be called before passing RunOptions to dockertest functions.
// This function should be called before passing [dockertest.RunOptions] to dockertest functions.
func DockerAddIntegrationLabels(opts *dockertest.RunOptions, testType string) {
runID := GetIntegrationRunID()
if runID == "" {

View file

@ -40,7 +40,7 @@ func ExecuteCommandTimeout(timeout time.Duration) ExecuteCommandOption {
})
}
// buffer is a goroutine safe bytes.buffer.
// buffer is a goroutine safe [bytes.Buffer].
type buffer struct {
store bytes.Buffer
mutex sync.Mutex

View file

@ -150,8 +150,9 @@ func DisconnectContainerFromNetwork(
return waitContainerRouteAbsent(pool, containerID, network, DockerOpMaxElapsedTime)
}
// ReconnectContainerToNetwork inverts DisconnectContainerFromNetwork
// and waits until libnetwork has wired up a fresh IPv4 address.
// ReconnectContainerToNetwork is the inverse of
// [DisconnectContainerFromNetwork] — re-attaches the container to the
// network so traffic can flow again.
func ReconnectContainerToNetwork(
pool *dockertest.Pool,
network *dockertest.Network,

View file

@ -136,7 +136,7 @@ func (dsic *DERPServerInContainer) buildEntrypoint(derperArgs string) []string {
return []string{"/bin/sh", "-c", strings.Join(commands, " ; ")}
}
// New returns a new TailscaleInContainer instance.
// New returns a new [tsic.TailscaleInContainer] instance.
func New(
pool *dockertest.Pool,
version string,
@ -179,7 +179,7 @@ func New(
}
// Install the CA cert so the DERP server trusts its own certificate
// and any headscale CA certs passed via WithCACert.
// and any headscale CA certs passed via [WithCACert].
dsic.caCerts = append(dsic.caCerts, tlsCACert)
for _, opt := range opts {
@ -319,7 +319,7 @@ func (t *DERPServerInContainer) Version() string {
return t.version
}
// ID returns the Docker container ID of the DERPServerInContainer
// ID returns the Docker container ID of the [DERPServerInContainer]
// instance.
func (t *DERPServerInContainer) ID() string {
return t.container.Container.ID

View file

@ -180,7 +180,7 @@ func derpServerScenario(
t.Logf("Run 1: %d successful pings out of %d", success, len(allClients)*len(allHostnames))
// Let the DERP updater run a couple of times to ensure it does not
// break the DERPMap. The updater runs on a 10s interval by default.
// break the [tailcfg.DERPMap]. The updater runs on a 10s interval by default.
//nolint:forbidigo // Intentional delay: must wait for DERP updater to run multiple times (interval-based)
time.Sleep(30 * time.Second)

View file

@ -767,8 +767,8 @@ func TestUpdateHostnameFromClient(t *testing.T) {
// Pre-rewrite these were rejected by ApplyHostnameFromHostInfo with
// "invalid characters" and the node was stuck on an invalid-<rand>
// GivenName with the HostName update dropped. The assertions below
// verify both raw preservation (node.Name) and SaaS-matching sanitisation
// (node.GivenName) for each awkward input.
// verify both raw preservation ([v1.Node.Name]) and SaaS-matching sanitisation
// ([v1.Node.GivenName]) for each awkward input.
hostnames := map[string]string{
"1": "Joe's Mac mini",
"2": "Test@Host",
@ -813,7 +813,7 @@ func TestUpdateHostnameFromClient(t *testing.T) {
requireNoErrSync(t, err)
// Wait for nodestore batch processing to complete
// NodeStore batching timeout is 500ms, so we wait up to 1 second
// [state.NodeStore] batching timeout is 500ms, so we wait up to 1 second
var nodes []*v1.Node
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
err := executeAndUnmarshal(
@ -834,7 +834,7 @@ func TestUpdateHostnameFromClient(t *testing.T) {
hostname := hostnames[strconv.FormatUint(node.GetId(), 10)]
assert.Equal(ct, hostname, node.GetName(), "Node name should match hostname")
// GivenName is sanitised via dnsname.SanitizeHostname (SaaS algorithm).
// GivenName is sanitised via [dnsname.SanitizeHostname] (SaaS algorithm).
assert.Equal(ct, dnsname.SanitizeHostname(hostname), node.GetGivenName(),
"Given name should match SaaS hostname-sanitisation rules")
}
@ -912,7 +912,7 @@ func TestUpdateHostnameFromClient(t *testing.T) {
requireNoErrSync(t, err)
// Wait for nodestore batch processing to complete
// NodeStore batching timeout is 500ms, so we wait up to 1 second
// [state.NodeStore] batching timeout is 500ms, so we wait up to 1 second
assert.Eventually(t, func() bool {
err = executeAndUnmarshal(
headscale,
@ -987,7 +987,7 @@ func TestExpireNode(t *testing.T) {
require.NoError(t, err)
// TODO(kradalby): This is Headscale specific and would not play nicely
// with other implementations of the ControlServer interface
// with other implementations of the [ControlServer] interface
result, err := headscale.Execute([]string{
"headscale", "nodes", "expire", "--identifier", "1", "--output", "json",
})

View file

@ -17,8 +17,8 @@ import (
"tailscale.com/wgengine/filter"
)
// hasCapMatchInPacketFilter checks if any Match entry in the packet
// filter contains a CapMatch with the given capability name.
// hasCapMatchInPacketFilter checks if any [filter.Match] entry in the packet
// filter contains a [filter.CapMatch] with the given capability name.
func hasCapMatchInPacketFilter(pf []filter.Match, peerCap tailcfg.PeerCapability) bool {
for _, m := range pf {
for _, cm := range m.Caps {
@ -31,7 +31,7 @@ func hasCapMatchInPacketFilter(pf []filter.Match, peerCap tailcfg.PeerCapability
return false
}
// hasCapMatchForIP checks if any CapMatch with the given capability
// hasCapMatchForIP checks if any [filter.CapMatch] with the given capability
// has a Dst prefix that contains the given IP. This validates that
// the cap is directed at the correct node, not just present.
func hasCapMatchForIP(pf []filter.Match, peerCap tailcfg.PeerCapability, ip netip.Addr) bool {
@ -47,7 +47,7 @@ func hasCapMatchForIP(pf []filter.Match, peerCap tailcfg.PeerCapability, ip neti
}
// parsePeerRelay parses a PeerRelay string of the form "ip:port:vni:N"
// and returns the address and VNI. Returns zero values on parse failure.
// and returns the [netip.AddrPort] and VNI. Returns zero values on parse failure.
func parsePeerRelay(pr string) (netip.AddrPort, string, bool) {
// Format: "172.18.0.4:58738:vni:1"
// Split into: host part "172.18.0.4:58738" and vni part "vni:1"

View file

@ -114,7 +114,7 @@ func requireNoErrLogout(t *testing.T, err error) {
require.NoError(t, err, "failed to log out tailscale nodes")
}
// collectExpectedNodeIDs extracts node IDs from a list of TailscaleClients for validation purposes.
// collectExpectedNodeIDs extracts node IDs from a list of [TailscaleClient]s for validation purposes.
func collectExpectedNodeIDs(t *testing.T, clients []TailscaleClient) []types.NodeID {
t.Helper()
@ -131,7 +131,7 @@ func collectExpectedNodeIDs(t *testing.T, clients []TailscaleClient) []types.Nod
}
// validateInitialConnection performs comprehensive validation after initial client login.
// Validates that all nodes are online and have proper NetInfo/DERP configuration,
// Validates that all nodes are online and have proper [tailcfg.NetInfo]/DERP configuration,
// essential for ensuring successful initial connection state in relogin tests.
func validateInitialConnection(t *testing.T, headscale ControlServer, expectedNodes []types.NodeID) {
t.Helper()
@ -150,7 +150,7 @@ func validateLogoutComplete(t *testing.T, headscale ControlServer, expectedNodes
}
// validateReloginComplete performs comprehensive validation after client relogin.
// Validates that all nodes are back online with proper NetInfo/DERP configuration,
// Validates that all nodes are back online with proper [tailcfg.NetInfo]/DERP configuration,
// ensuring successful relogin state restoration in integration tests.
func validateReloginComplete(t *testing.T, headscale ControlServer, expectedNodes []types.NodeID) {
t.Helper()
@ -256,7 +256,7 @@ func requireAllClientsOnlineWithSingleTimeout(t *testing.T, headscale ControlSer
}
}
// Check map responses using buildExpectedOnlineMap
// Check map responses using [integrationutil.BuildExpectedOnlineMap]
onlineFromMaps := make(map[types.NodeID]bool)
onlineMap := integrationutil.BuildExpectedOnlineMap(mapResponses)
@ -475,9 +475,9 @@ func requireAllClientsOfflineStaged(t *testing.T, headscale ControlServer, expec
t.Logf("All stages completed: nodes are fully offline across all systems")
}
// requireAllClientsNetInfoAndDERP validates that all nodes have NetInfo in the database
// and a valid DERP server based on the NetInfo. This function follows the pattern of
// requireAllClientsOnline by using hsic.DebugNodeStore to get the database state.
// requireAllClientsNetInfoAndDERP validates that all nodes have [tailcfg.NetInfo] in the database
// and a valid DERP server based on the [tailcfg.NetInfo]. This function follows the pattern of
// [requireAllClientsOnline] by using [hsic.HeadscaleInContainer.DebugNodeStore] to get the database state.
//
//nolint:unparam // timeout is configurable for flexibility even though callers currently use same value
func requireAllClientsNetInfoAndDERP(t *testing.T, headscale ControlServer, expectedNodes []types.NodeID, message string, timeout time.Duration) {
@ -510,7 +510,7 @@ func requireAllClientsNetInfoAndDERP(t *testing.T, headscale ControlServer, expe
continue
}
// Validate that the node has Hostinfo
// Validate that the node has [tailcfg.Hostinfo]
assert.NotNil(c, node.Hostinfo, "Node %d (%s) should have Hostinfo for NetInfo validation", nodeID, node.Hostname)
if node.Hostinfo == nil {
@ -518,7 +518,7 @@ func requireAllClientsNetInfoAndDERP(t *testing.T, headscale ControlServer, expe
continue
}
// Validate that the node has NetInfo
// Validate that the node has [tailcfg.NetInfo]
assert.NotNil(c, node.Hostinfo.NetInfo, "Node %d (%s) should have NetInfo in Hostinfo for DERP connectivity", nodeID, node.Hostname)
if node.Hostinfo.NetInfo == nil {
@ -553,7 +553,7 @@ func assertLastSeenSetWithCollect(c *assert.CollectT, node *v1.Node) {
}
// assertCurlSuccessWithCollect asserts that a curl request succeeds with
// non-empty content. For use inside EventuallyWithT blocks.
// non-empty content. For use inside [assert.EventuallyWithT] blocks.
func assertCurlSuccessWithCollect(c *assert.CollectT, client TailscaleClient, url, msg string) {
result, err := client.Curl(url)
assert.NoError(c, err, msg) //nolint:testifylint // CollectT requires assert, not require
@ -562,7 +562,7 @@ func assertCurlSuccessWithCollect(c *assert.CollectT, client TailscaleClient, ur
// assertCurlDockerHostname curls url and asserts the body is the
// 13-byte Docker auto-generated container hostname (12 hex chars +
// trailing newline from /etc/hostname). For use inside EventuallyWithT.
// trailing newline from /etc/hostname). For use inside [assert.EventuallyWithT].
func assertCurlDockerHostname(c *assert.CollectT, client TailscaleClient, url, msg string) {
const dockerHostnameLen = 13
@ -572,7 +572,7 @@ func assertCurlDockerHostname(c *assert.CollectT, client TailscaleClient, url, m
}
// snapshotClientFilters snapshots each client's current netmap
// PacketFilter keyed by hostname. Pair with waitForClientFilterChange.
// PacketFilter keyed by hostname. Pair with [waitForClientFilterChange].
func snapshotClientFilters(t *testing.T, clients []TailscaleClient) map[string][]filter.Match {
t.Helper()
@ -611,9 +611,9 @@ func waitForClientFilterChange(t *testing.T, clients []TailscaleClient, baseline
}
// assertCurlFailWithCollect asserts that a curl request fails. Uses
// CurlFailFast internally for aggressive timeouts, avoiding wasted
// [tsic.TailscaleInContainer.CurlFailFast] internally for aggressive timeouts, avoiding wasted
// time on retries when we expect the connection to be blocked.
// For use inside EventuallyWithT blocks.
// For use inside [assert.EventuallyWithT] blocks.
func assertCurlFailWithCollect(c *assert.CollectT, client TailscaleClient, url, msg string) {
_, err := client.CurlFailFast(url)
assert.Error(c, err, msg)
@ -635,7 +635,7 @@ func assertTailscaleNodesLogout(t assert.TestingT, clients []TailscaleClient) {
}
// assertPingAll verifies that every client can ping every address.
// The entire ping matrix is retried via EventuallyWithT to handle
// The entire ping matrix is retried via [assert.EventuallyWithT] to handle
// transient failures on slow CI runners. The timeout scales with
// the number of pings since they run serially and each can take
// up to ~2s on CI (docker exec overhead + ping timeout).
@ -660,9 +660,9 @@ func assertPingAll(t *testing.T, clients []TailscaleClient, addrs []string, opts
}
// assertPingAllWithCollect pings every address from every client and
// collects failures on the provided CollectT. Pings run serially to
// collects failures on the provided [assert.CollectT]. Pings run serially to
// avoid overloading the Docker daemon on resource-constrained CI
// runners. For use inside EventuallyWithT blocks when the caller
// runners. For use inside [assert.EventuallyWithT] blocks when the caller
// needs custom timeout or retry control.
func assertPingAllWithCollect(c *assert.CollectT, clients []TailscaleClient, addrs []string, opts ...tsic.PingOption) {
for _, client := range clients {
@ -897,7 +897,7 @@ func assertValidNetcheck(t *testing.T, client TailscaleClient) {
// assertCommandOutputContains executes a command with exponential backoff retry until the output
// contains the expected string or timeout is reached (10 seconds).
// This implements eventual consistency patterns and should be used instead of time.Sleep
// This implements eventual consistency patterns and should be used instead of [time.Sleep]
// before executing commands that depend on network state propagation.
//
// Timeout: 10 seconds with exponential backoff
@ -982,42 +982,43 @@ func countMatchingLines(in io.Reader, predicate func(string) bool) (int, error)
// wildcard returns a wildcard alias (*) for use in policy v2 configurations.
// Provides a convenient helper for creating permissive policy rules.
// Returns [policyv2.Wildcard].
func wildcard() policyv2.Alias {
return policyv2.Wildcard
}
// usernamep returns a pointer to a Username as an Alias for policy v2 configurations.
// usernamep returns a pointer to a [policyv2.Username] as an [policyv2.Alias] for policy v2 configurations.
// Used in ACL rules to reference specific users in network access policies.
func usernamep(name string) policyv2.Alias {
return new(policyv2.Username(name))
}
// hostp returns a pointer to a Host as an Alias for policy v2 configurations.
// hostp returns a pointer to a [policyv2.Host] as an [policyv2.Alias] for policy v2 configurations.
// Used in ACL rules to reference specific hosts in network access policies.
func hostp(name string) policyv2.Alias {
return new(policyv2.Host(name))
}
// groupp returns a pointer to a Group as an Alias for policy v2 configurations.
// groupp returns a pointer to a [policyv2.Group] as an [policyv2.Alias] for policy v2 configurations.
// Used in ACL rules to reference user groups in network access policies.
func groupp(name string) policyv2.Alias {
return new(policyv2.Group(name))
}
// tagp returns a pointer to a Tag as an Alias for policy v2 configurations.
// tagp returns a pointer to a [policyv2.Tag] as an [policyv2.Alias] for policy v2 configurations.
// Used in ACL rules to reference node tags in network access policies.
func tagp(name string) policyv2.Alias {
return new(policyv2.Tag(name))
}
// prefixp returns a pointer to a Prefix from a CIDR string for policy v2 configurations.
// prefixp returns a pointer to a [policyv2.Prefix] from a CIDR string for policy v2 configurations.
// Converts CIDR notation to policy prefix format for network range specifications.
func prefixp(cidr string) policyv2.Alias {
p := policyv2.Prefix(netip.MustParsePrefix(cidr))
return &p
}
// aliasWithPorts creates an AliasWithPorts structure from an alias and port ranges.
// aliasWithPorts creates an [policyv2.AliasWithPorts] structure from an alias and port ranges.
// Combines network targets with specific port restrictions for fine-grained
// access control in policy v2 configurations.
func aliasWithPorts(alias policyv2.Alias, ports ...tailcfg.PortRange) policyv2.AliasWithPorts {
@ -1027,13 +1028,13 @@ func aliasWithPorts(alias policyv2.Alias, ports ...tailcfg.PortRange) policyv2.A
}
}
// usernameOwner returns a Username as an Owner for use in TagOwners policies.
// usernameOwner returns a [policyv2.Username] as an [policyv2.Owner] for use in [policyv2.TagOwners] policies.
// Specifies which users can assign and manage specific tags in ACL configurations.
func usernameOwner(name string) policyv2.Owner {
return new(policyv2.Username(name))
}
// groupOwner returns a Group as an Owner for use in TagOwners policies.
// groupOwner returns a [policyv2.Group] as an [policyv2.Owner] for use in [policyv2.TagOwners] policies.
// Specifies which groups can assign and manage specific tags in ACL configurations.
//
//nolint:unused
@ -1041,25 +1042,25 @@ func groupOwner(name string) policyv2.Owner {
return new(policyv2.Group(name))
}
// usernameApprover returns a Username as an AutoApprover for subnet route policies.
// usernameApprover returns a [policyv2.Username] as an [policyv2.AutoApprover] for subnet route policies.
// Specifies which users can automatically approve subnet route advertisements.
func usernameApprover(name string) policyv2.AutoApprover {
return new(policyv2.Username(name))
}
// groupApprover returns a Group as an AutoApprover for subnet route policies.
// groupApprover returns a [policyv2.Group] as an [policyv2.AutoApprover] for subnet route policies.
// Specifies which groups can automatically approve subnet route advertisements.
func groupApprover(name string) policyv2.AutoApprover {
return new(policyv2.Group(name))
}
// tagApprover returns a Tag as an AutoApprover for subnet route policies.
// tagApprover returns a [policyv2.Tag] as an [policyv2.AutoApprover] for subnet route policies.
// Specifies which tagged nodes can automatically approve subnet route advertisements.
func tagApprover(name string) policyv2.AutoApprover {
return new(policyv2.Tag(name))
}
// oidcMockUser creates a MockUser for OIDC authentication testing.
// oidcMockUser creates a [mockoidc.MockUser] for OIDC authentication testing.
// Generates consistent test user data with configurable email verification status
// for validating OIDC integration flows in headscale authentication tests.
func oidcMockUser(username string, emailVerified bool) mockoidc.MockUser {
@ -1193,7 +1194,7 @@ func (s *Scenario) AddAndLoginClient(
return newClient, nil
}
// MustAddAndLoginClient is like AddAndLoginClient but fails the test on error.
// MustAddAndLoginClient is like [Scenario.AddAndLoginClient] but fails the test on error.
func (s *Scenario) MustAddAndLoginClient(
t *testing.T,
username string,

View file

@ -26,7 +26,7 @@ var (
StatusReadyTimeout = ScaledTimeout(30 * time.Second)
)
// Polling intervals for EventuallyWithT.
// Polling intervals for [assert.EventuallyWithT].
const (
// FastPoll: in-process reads (HA state, route table snapshots).
FastPoll = 200 * time.Millisecond

View file

@ -88,7 +88,7 @@ func TestEnablingRoutes(t *testing.T) {
requireNoErrSync(t, err)
var nodes []*v1.Node
// Wait for route advertisements to propagate to NodeStore
// Wait for route advertisements to propagate to [state.NodeStore]
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var err error
@ -125,7 +125,7 @@ func TestEnablingRoutes(t *testing.T) {
require.NoError(t, err)
}
// Wait for route approvals to propagate to NodeStore
// Wait for route approvals to propagate to [state.NodeStore]
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var err error
@ -361,7 +361,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying no routes are active before approval")
}
// Declare variables that will be used across multiple EventuallyWithT blocks
// Declare variables that will be used across multiple [assert.EventuallyWithT] blocks
var (
srs1, srs2, srs3 *ipnstate.Status
clientStatus *ipnstate.Status
@ -956,7 +956,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
require.NoError(t, err)
// Wait for nodestore batch processing to complete and online status to be updated
// NodeStore batching timeout is 500ms, so we wait up to 10 seconds for all routers to be online
// [state.NodeStore] batching timeout is 500ms, so we wait up to 10 seconds for all routers to be online
assert.EventuallyWithT(t, func(c *assert.CollectT) {
clientStatus, err = client.Status()
assert.NoError(c, err)
@ -1033,7 +1033,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
_, err = headscale.ApproveRoutes(MustFindNode(subRouter3.Hostname(), nodes).GetId(), []netip.Prefix{})
// Wait for nodestore batch processing and route state changes to complete
// NodeStore batching timeout is 500ms, so we wait up to 10 seconds for route failover
// [state.NodeStore] batching timeout is 500ms, so we wait up to 10 seconds for route failover
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err = headscale.ListNodes()
assert.NoError(c, err)
@ -1119,7 +1119,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
_, err = headscale.ApproveRoutes(MustFindNode(subRouter1.Hostname(), nodes).GetId(), []netip.Prefix{})
// Wait for nodestore batch processing and route state changes to complete
// NodeStore batching timeout is 500ms, so we wait up to 10 seconds for route failover
// [state.NodeStore] batching timeout is 500ms, so we wait up to 10 seconds for route failover
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err = headscale.ListNodes()
assert.NoError(c, err)
@ -1453,7 +1453,7 @@ func TestSubnetRouteACL(t *testing.T) {
assert.NotNil(c, routeNode, "could not find node that should have route")
assert.NotNil(c, otherNode, "could not find node that should not have route")
// After NodeStore fix: routes are properly tracked in route manager
// After [state.NodeStore] fix: routes are properly tracked in route manager
// This test uses a policy with NO auto-approvers, so routes should be:
// announced=1, approved=0, subnet=0 (routes announced but not approved)
requireNodeRouteCountWithCollect(c, routeNode, 1, 0, 0)
@ -1705,7 +1705,7 @@ func TestEnablingExitRoutes(t *testing.T) {
// (verified against a live tailnet on 2026-04-28; see captures
// routes-b17/b18 in tscap). The bug was that headscale stripped
// autogroup:internet rules from both the client packet filter AND the
// matcher source used by Node.CanAccess, breaking exit-node visibility.
// matcher source used by [types.Node.CanAccess], breaking exit-node visibility.
func TestExitRoutesWithAutogroupInternetACL(t *testing.T) {
IntegrationSkip(t)
@ -1750,9 +1750,9 @@ func TestExitRoutesWithAutogroupInternetACL(t *testing.T) {
requireNoErrGetHeadscale(t, err)
// The autogroup:internet ACL grants no peer visibility until the
// exit routes are approved (Node.IsExitNode() flips on approval),
// exit routes are approved ([types.Node.IsExitNode] flips on approval),
// so the standard WaitForTailscaleSync wait would deadlock here —
// the post-approval EventuallyWithT block below covers the peer
// the post-approval [assert.EventuallyWithT] block below covers the peer
// state we actually care about.
var nodes []*v1.Node
@ -1792,7 +1792,7 @@ func TestExitRoutesWithAutogroupInternetACL(t *testing.T) {
// The end-to-end UX assertion: every client must see the OTHER
// node as a peer carrying both default-route prefixes in
// AllowedIPs. Tailscale derives PeerStatus.ExitNodeOption from
// AllowedIPs. Tailscale derives [ipnstate.PeerStatus.ExitNodeOption] from
// those AllowedIPs, which is what `tailscale exit-node list`
// reads (see tailscale.com/ipn/ipnlocal/local.go).
for _, client := range allClients {
@ -1903,7 +1903,7 @@ func TestSubnetRouterMultiNetwork(t *testing.T) {
require.NoErrorf(t, err, "failed to advertise route: %s", err)
var nodes []*v1.Node
// Wait for route advertisements to propagate to NodeStore
// Wait for route advertisements to propagate to [state.NodeStore]
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var err error
@ -2182,7 +2182,7 @@ func MustFindNode(hostname string, nodes []*v1.Node) *v1.Node {
func TestAutoApproveMultiNetwork(t *testing.T) {
IntegrationSkip(t)
// Timeout for EventuallyWithT assertions.
// Timeout for [assert.EventuallyWithT] assertions.
// Set generously to account for CI infrastructure variability.
assertTimeout := integrationutil.ScaledTimeout(60 * time.Second)
@ -2430,7 +2430,7 @@ func TestAutoApproveMultiNetwork(t *testing.T) {
name := fmt.Sprintf("%s-advertiseduringup-%t-pol-%s", tt.name, advertiseDuringUp, polMode)
t.Run(name, func(t *testing.T) {
// Create a deep copy of the policy to avoid mutating the shared test case.
// Each subtest modifies AutoApprovers.Routes (add then delete), so we need
// Each subtest modifies [policyv2.AutoApproverPolicy.Routes] (add then delete), so we need
// an isolated copy to prevent state leakage between sequential test runs.
pol := &policyv2.Policy{
ACLs: slices.Clone(tt.pol.ACLs),
@ -3017,14 +3017,14 @@ func TestAutoApproveMultiNetwork(t *testing.T) {
}
}
// assertTracerouteViaIPWithCollect is a version of assertTracerouteViaIP that works with assert.CollectT.
// assertTracerouteViaIPWithCollect is a version of [assertTracerouteViaIP] that works with [assert.CollectT].
func assertTracerouteViaIPWithCollect(c *assert.CollectT, tr util.Traceroute, ip netip.Addr) {
assert.NotNil(c, tr)
assert.True(c, tr.Success)
assert.NoError(c, tr.Err) //nolint:testifylint // using assert.CollectT
assert.NotEmpty(c, tr.Route)
// Since we're inside EventuallyWithT, we can't use require.Greater with t
// but assert.NotEmpty above ensures len(tr.Route) > 0
// Since we're inside [assert.EventuallyWithT], we can't use [require.Greater] with t
// but [assert.NotEmpty] above ensures len(tr.Route) > 0
if len(tr.Route) > 0 {
assert.Equal(c, tr.Route[0].IP.String(), ip.String())
}
@ -3219,7 +3219,7 @@ func TestSubnetRouteACLFiltering(t *testing.T) {
requireNoErrSync(t, err)
var routerNode, nodeNode *v1.Node
// Wait for route advertisements to propagate to NodeStore
// Wait for route advertisements to propagate to [state.NodeStore]
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
// List nodes and verify the router has 3 available routes
nodes, err := headscale.NodesByUser()
@ -3858,7 +3858,7 @@ func TestHASubnetRouterPingFailover(t *testing.T) {
//
// Two assertion sets split the failure surface:
// - R1: server-side primary route table restores after reconnect.
// If R1 fails, the bug is in state.Connect / primaryRoutes.
// If R1 fails, the bug is in [state.State.Connect] / primaryRoutes.
// - R2: client's view shows r2 online with the route in PrimaryRoutes.
// If R1 passes and R2 fails, the bug is in change broadcast /
// mapBatcher / multiChannelNodeConn.

View file

@ -459,7 +459,7 @@ func doSSHWithRetryAsUser(
)
if retry {
// Use assert.EventuallyWithT to retry SSH connections for success cases
// Use [assert.EventuallyWithT] to retry SSH connections for success cases
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
result, stderr, err = client.Execute(command)
@ -706,7 +706,7 @@ func findSSHCheckAuthID(t *testing.T, headscale ControlServer) string {
return authID
}
// sshCheckPolicy returns a policy with SSH "check" mode for group:integration-test
// sshCheckPolicy returns a [policyv2.Policy] with SSH "check" mode for group:integration-test
// targeting autogroup:member and autogroup:tagged destinations.
func sshCheckPolicy() *policyv2.Policy {
return &policyv2.Policy{
@ -739,7 +739,7 @@ func sshCheckPolicy() *policyv2.Policy {
}
}
// sshCheckPolicyWithPeriod returns a policy with SSH "check" mode and a
// sshCheckPolicyWithPeriod returns a [policyv2.Policy] with SSH "check" mode and a
// specified checkPeriod for session duration.
func sshCheckPolicyWithPeriod(period time.Duration) *policyv2.Policy {
return &policyv2.Policy{

View file

@ -1587,7 +1587,7 @@ func TestTagsUserLoginAddTagViaCLIReauth(t *testing.T) {
_, stderr, err := client.Execute(command)
t.Logf("CLI result: err=%v, stderr=%s", err, stderr)
// Check final state - EventuallyWithT handles waiting for propagation
// Check final state - [assert.EventuallyWithT] handles waiting for propagation
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)
@ -1678,7 +1678,7 @@ func TestTagsUserLoginRemoveTagViaCLIReauth(t *testing.T) {
_, stderr, err := client.Execute(command)
t.Logf("CLI result: err=%v, stderr=%s", err, stderr)
// Check final state - EventuallyWithT handles waiting for propagation
// Check final state - [assert.EventuallyWithT] handles waiting for propagation
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)
@ -3093,8 +3093,8 @@ func TestTagsAuthKeyWithoutUserRejectsAdvertisedTags(t *testing.T) {
// TestTagsAuthKeyConvertToUserViaCLIRegister reproduces the panic from
// issue #3038: register a node with a tags-only preauthkey (no user), then
// convert it to a user-owned node via "headscale auth register --auth-id <id> --user <user>".
// The crash happens in the mapper's generateUserProfiles when node.User is nil
// after the tag→user conversion in processReauthTags.
// The crash happens in the mapper's generateUserProfiles when [types.Node.User] is nil
// after the tag→user conversion in [State.processReauthTags].
//
// The key detail is using a tags-only PreAuthKey (User: nil). When created under
// a user, the node inherits User from the PreAuthKey and the bug is masked.
@ -3124,8 +3124,8 @@ func TestTagsAuthKeyConvertToUserViaCLIRegister(t *testing.T) {
requireNoErrGetHeadscale(t, err)
// Step 1: Create a tags-only preauthkey WITHOUT a user.
// This is the critical detail: when PreAuthKey.UserID is nil, the node
// enters the NodeStore with node.User == nil. The processReauthTags
// This is the critical detail: when [types.PreAuthKey.UserID] is nil, the node
// enters the [state.NodeStore] with [types.Node.User] == nil. The [state.State.processReauthTags]
// conversion then sets UserID but not User, leaving it nil for the mapper.
authKey, err := scenario.CreatePreAuthKeyWithOptions(hsic.AuthKeyOptions{
User: nil,
@ -3186,8 +3186,8 @@ func TestTagsAuthKeyConvertToUserViaCLIRegister(t *testing.T) {
require.NoError(t, err)
// Step 4: Verify node is now user-owned and the mapper didn't panic.
// The panic would occur when the mapper builds the MapResponse and calls
// node.Owner().Model().ID with a nil User pointer.
// The panic would occur when the mapper builds the [tailcfg.MapResponse] and calls
// [types.Node.Owner].Model().ID with a nil User pointer.
// ShutdownAssertNoPanics in the defer catches any panics in headscale logs.
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err := headscale.ListNodes()

View file

@ -65,7 +65,7 @@ func WithCACert(cert []byte) Option {
}
}
// WithNetwork sets the Docker container network.
// WithNetwork sets the Docker [dockertest.Network].
func WithNetwork(network *dockertest.Network) Option {
return func(t *TailscaleRustInContainer) {
t.network = network
@ -146,7 +146,7 @@ func (t *TailscaleRustInContainer) buildEntrypoint() []string {
return []string{"/bin/sh", "-c", strings.Join(commands, " ; ")}
}
// New creates and starts a new TailscaleRustInContainer instance.
// New creates and starts a new [TailscaleRustInContainer] instance.
func New(
pool *dockertest.Pool,
opts ...Option,
@ -280,7 +280,7 @@ func New(
return t, nil
}
// Hostname returns the hostname of the TailscaleRustInContainer instance.
// Hostname returns the hostname of the [TailscaleRustInContainer] instance.
func (t *TailscaleRustInContainer) Hostname() string {
return t.hostname
}
@ -310,7 +310,7 @@ func (t *TailscaleRustInContainer) SaveLog(path string) (string, string, error)
}
// WriteLogs writes the current stdout/stderr log of the container to
// the given io.Writers.
// the given [io.Writer]s.
func (t *TailscaleRustInContainer) WriteLogs(stdout, stderr io.Writer) error {
return dockertestutil.WriteLog(t.pool, t.container, stdout, stderr)
}

View file

@ -241,7 +241,7 @@ func TestTailscaleRustAxum(t *testing.T) {
// Fire several more POSTs and verify the counter advances.
// The axum handler returns {"count": N} where N is the pre-increment value.
// After the initial EventuallyWithT loop we don't know the exact counter,
// After the initial [assert.EventuallyWithT] loop we don't know the exact counter,
// but two back-to-back POSTs should return consecutive values.
t.Log("Verifying counter increments across multiple requests...")