integration: replace ad-hoc test timeouts with named constants

Categorised timeouts in integrationutil/timeouts.go remove the drift
opportunity between same-purpose budgets repeated across the suite.
The auth, cli, dns, derp, ssh, and tags tests are swept; acl, route,
and general tests follow in later commits alongside their other
ergonomic fixes.
This commit is contained in:
Kristoffer Dalby
2026-05-13 13:18:52 +00:00
parent eec3844f24
commit 78fd6efb38
9 changed files with 221 additions and 181 deletions

View File

@@ -88,7 +88,7 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
for _, node := range listNodes {
assertLastSeenSetWithCollect(c, node)
}
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for expected node list before logout")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected node list before logout")
nodeCountBeforeLogout = len(listNodes)
t.Logf("node count before logout: %d", nodeCountBeforeLogout)
@@ -115,7 +115,7 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes after logout")
assert.Len(ct, listNodes, nodeCountBeforeLogout, "Node count should match before logout count - expected %d nodes, got %d", nodeCountBeforeLogout, len(listNodes))
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second, "validating node persistence after logout (nodes should remain in database)")
}, integrationutil.StatusReadyTimeout, 2*time.Second, "validating node persistence after logout (nodes should remain in database)")
for _, node := range listNodes {
assertLastSeenSet(t, node)
@@ -153,7 +153,7 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes after relogin")
assert.Len(ct, listNodes, nodeCountBeforeLogout, "Node count should remain unchanged after relogin - expected %d nodes, got %d", nodeCountBeforeLogout, len(listNodes))
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating node count stability after same-user auth key relogin")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating node count stability after same-user auth key relogin")
for _, node := range listNodes {
assertLastSeenSet(t, node)
@@ -210,7 +210,7 @@ func TestAuthKeyLogoutAndReloginSameUser(t *testing.T) {
for _, node := range listNodes {
assertLastSeenSetWithCollect(c, node)
}
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for node list after relogin")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for node list after relogin")
})
}
}
@@ -266,7 +266,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, listNodes, len(allClients))
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for expected node list before logout")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected node list before logout")
nodeCountBeforeLogout = len(listNodes)
t.Logf("node count before logout: %d", nodeCountBeforeLogout)
@@ -313,7 +313,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) {
user1Nodes, err = headscale.ListNodes("user1")
assert.NoError(ct, err, "Failed to list nodes for user1 after relogin")
assert.Len(ct, user1Nodes, len(allClients), "User1 should have all %d clients after relogin, got %d nodes", len(allClients), len(user1Nodes))
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating user1 has all client nodes after auth key relogin")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating user1 has all client nodes after auth key relogin")
// Collect expected node IDs for user1 after relogin
expectedUser1Nodes := make([]types.NodeID, 0, len(user1Nodes))
@@ -337,7 +337,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) {
user2Nodes, err = headscale.ListNodes("user2")
assert.NoError(ct, err, "Failed to list nodes for user2 after user1 relogin")
assert.Len(ct, user2Nodes, len(allClients)/2, "User2 should still have %d clients after user1 relogin, got %d nodes", len(allClients)/2, len(user2Nodes))
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second, "validating user2 nodes persist after user1 relogin (should not be affected)")
}, integrationutil.StatusReadyTimeout, 2*time.Second, "validating user2 nodes persist after user1 relogin (should not be affected)")
t.Logf("Validating client login states after user switch at %s", time.Now().Format(TimestampFormat))
@@ -346,7 +346,7 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) {
status, err := client.Status()
assert.NoError(ct, err, "Failed to get status for client %s", client.Hostname())
assert.Equal(ct, "user1@test.no", status.User[status.Self.UserID].LoginName, "Client %s should be logged in as user1 after user switch, got %s", client.Hostname(), status.User[status.Self.UserID].LoginName)
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second, "validating %s is logged in as user1 after auth key user switch", client.Hostname())
}, integrationutil.StatusReadyTimeout, 2*time.Second, "validating %s is logged in as user1 after auth key user switch", client.Hostname())
}
}
@@ -412,7 +412,7 @@ func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, listNodes, len(allClients))
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for expected node list before logout")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected node list before logout")
nodeCountBeforeLogout = len(listNodes)
t.Logf("node count before logout: %d", nodeCountBeforeLogout)
@@ -527,7 +527,7 @@ func TestAuthKeyDeleteKey(t *testing.T) {
user1Nodes, err = headscale.ListNodes("user1")
assert.NoError(c, err)
assert.Len(c, user1Nodes, 1)
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for node to be registered")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for node to be registered")
nodeID := user1Nodes[0].GetId()
nodeName := user1Nodes[0].GetName()
@@ -554,7 +554,7 @@ func TestAuthKeyDeleteKey(t *testing.T) {
status, err := client.Status()
assert.NoError(c, err)
assert.Equal(c, "Stopped", status.BackendState)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "client should be stopped")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "client should be stopped")
err = client.Up()
require.NoError(t, err)
@@ -659,7 +659,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) {
assert.Contains(c, initialNode.GetSubnetRoutes(), advertiseRoute,
"Subnet routes should contain %s", advertiseRoute)
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "initial route should be serving")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "initial route should be serving")
require.NotNil(t, initialNode, "Initial node should be found")
initialNodeID := initialNode.GetId()
@@ -677,7 +677,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) {
status, err := client.Status()
assert.NoError(ct, err)
assert.Equal(ct, "NeedsLogin", status.BackendState, "Expected NeedsLogin state after logout")
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for logout to complete")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for logout to complete")
t.Logf("Logout completed, node should still exist in database")
@@ -686,7 +686,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, nodes, 1, "Node should persist in database after logout")
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "node should persist after logout")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "node should persist after logout")
// Step 3: Re-authenticate with the SAME user (using auth key)
t.Logf("Step 3: Re-authenticating with same user at %s", time.Now().Format(TimestampFormat))
@@ -707,7 +707,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) {
status, err := client.Status()
assert.NoError(ct, err)
assert.Equal(ct, "Running", status.BackendState, "Expected Running state after relogin")
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for relogin to complete")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for relogin to complete")
t.Logf("Re-authentication completed at %s", time.Now().Format(TimestampFormat))
@@ -741,7 +741,7 @@ func TestAuthKeyLogoutAndReloginRoutesPreserved(t *testing.T) {
assert.Equal(c, initialNodeID, node.GetId(),
"Node ID should be preserved after same-user relogin")
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond,
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll,
"BUG #2896: routes should remain SERVING after logout/relogin with same user")
t.Logf("Test completed - verifying issue #2896 fix")

View File

@@ -526,7 +526,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
ct.Errorf("User validation failed after first login - unexpected users: %s", diff)
}
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating user1 creation after initial OIDC login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user1 creation after initial OIDC login")
t.Logf("Validating initial node creation at %s", time.Now().Format(TimestampFormat))
@@ -538,7 +538,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes during initial validation")
assert.Len(ct, listNodes, 1, "Expected exactly 1 node after first login, got %d", len(listNodes))
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating initial node creation for user1 after OIDC login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating initial node creation for user1 after OIDC login")
// Collect expected node IDs for validation after user1 initial login
expectedNodes := make([]types.NodeID, 0, 1)
@@ -553,7 +553,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
nodeID, err = strconv.ParseUint(string(status.Self.ID), 10, 64)
assert.NoError(ct, err, "Failed to parse node ID from status")
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for node ID to be populated in status after initial login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for node ID to be populated in status after initial login")
expectedNodes = append(expectedNodes, types.NodeID(nodeID))
@@ -579,7 +579,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err, "Failed to get client status during logout validation")
assert.Equal(ct, "NeedsLogin", status.BackendState, "Expected NeedsLogin state after logout, got %s", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for user1 logout to complete before user2 login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for user1 logout to complete before user2 login")
u, err = ts.LoginWithURL(headscale.GetEndpoint())
require.NoError(t, err)
@@ -617,7 +617,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
ct.Errorf("User validation failed after user2 login - expected both user1 and user2: %s", diff)
}
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating both user1 and user2 exist after second OIDC login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating both user1 and user2 exist after second OIDC login")
var listNodesAfterNewUserLogin []*v1.Node
// First, wait for the new node to be created
@@ -627,7 +627,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
assert.NoError(ct, err, "Failed to list nodes after user2 login")
// We might temporarily have more than 2 nodes during cleanup, so check for at least 2
assert.GreaterOrEqual(ct, len(listNodesAfterNewUserLogin), 2, "Should have at least 2 nodes after user2 login, got %d (may include temporary nodes during cleanup)", len(listNodesAfterNewUserLogin))
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for user2 node creation (allowing temporary extra nodes during cleanup)")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for user2 node creation (allowing temporary extra nodes during cleanup)")
// Then wait for cleanup to stabilize at exactly 2 nodes
t.Logf("Waiting for node cleanup stabilization at %s", time.Now().Format(TimestampFormat))
@@ -644,7 +644,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
assert.Equal(ct, listNodesAfterNewUserLogin[0].GetMachineKey(), listNodesAfterNewUserLogin[1].GetMachineKey(), "Both nodes should share the same machine key")
assert.NotEqual(ct, listNodesAfterNewUserLogin[0].GetNodeKey(), listNodesAfterNewUserLogin[1].GetNodeKey(), "Node keys should be different between user1 and user2 nodes")
}
}, integrationutil.ScaledTimeout(90*time.Second), 2*time.Second, "waiting for node count stabilization at exactly 2 nodes after user2 login")
}, integrationutil.PolicyPropagationTimeout, 2*time.Second, "waiting for node count stabilization at exactly 2 nodes after user2 login")
// Security validation: Only user2's node should be active after user switch
var activeUser2NodeID types.NodeID
@@ -674,7 +674,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
} else {
assert.Fail(c, "User2 node not found in nodestore")
}
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating only user2 node is online after user switch")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating only user2 node is online after user switch")
// Before logging out user2, validate we have exactly 2 nodes and both are stable
t.Logf("Pre-logout validation: checking node stability at %s", time.Now().Format(TimestampFormat))
@@ -689,7 +689,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
assert.NotEmpty(ct, node.GetMachineKey(), "Node %d should have a valid machine key before logout", i)
t.Logf("Pre-logout node %d: User=%s, MachineKey=%s", i, node.GetUser().GetName(), node.GetMachineKey()[:16]+"...")
}
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating stable node count and integrity before user2 logout")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating stable node count and integrity before user2 logout")
// Log out user2, and log into user1, no new node should be created,
// the node should now "become" node1 again
@@ -715,7 +715,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err, "Failed to get client status during user2 logout validation")
assert.Equal(ct, "NeedsLogin", status.BackendState, "Expected NeedsLogin state after user2 logout, got %s", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for user2 logout to complete before user1 relogin")
}, 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,
@@ -734,7 +734,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
assert.NotEmpty(ct, node.GetMachineKey(), "Node %d should still have a valid machine key after user2 logout", i)
t.Logf("Post-logout node %d: User=%s, MachineKey=%s", i, node.GetUser().GetName(), node.GetMachineKey()[:16]+"...")
}
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating node persistence and integrity after user2 logout")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating node persistence and integrity after user2 logout")
// We do not actually "change" the user here, it is done by logging in again
// as the OIDC mock server is kind of like a stack, and the next user is
@@ -750,7 +750,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err, "Failed to get client status during user1 relogin validation")
assert.Equal(ct, "Running", status.BackendState, "Expected Running state after user1 relogin, got %s", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for user1 relogin to complete (final login)")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for user1 relogin to complete (final login)")
t.Logf("Logged back in")
t.Log("timestamp: " + time.Now().Format(TimestampFormat) + "\n")
@@ -785,7 +785,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
ct.Errorf("Final user validation failed - both users should persist after relogin cycle: %s", diff)
}
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating user persistence after complete relogin cycle (user1->user2->user1)")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user persistence after complete relogin cycle (user1->user2->user1)")
var listNodesAfterLoggingBackIn []*v1.Node
// Wait for login to complete and nodes to stabilize
@@ -826,7 +826,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
assert.NotEqual(ct, listNodesAfterLoggingBackIn[0].GetNodeKey(), listNodesAfterLoggingBackIn[1].GetNodeKey(), "Final nodes should have different node keys for different users")
t.Logf("Final validation complete - node counts and key relationships verified at %s", time.Now().Format(TimestampFormat))
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating final node state after complete user1->user2->user1 relogin cycle with detailed key validation")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating final node state after complete user1->user2->user1 relogin cycle with detailed key validation")
// Security validation: Only user1's node should be active after relogin
var activeUser1NodeID types.NodeID
@@ -856,7 +856,7 @@ func TestOIDCReloginSameNodeNewUser(t *testing.T) {
} else {
assert.Fail(c, "User1 node not found in nodestore after relogin")
}
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating only user1 node is online after final relogin")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating only user1 node is online after final relogin")
}
// TestOIDCFollowUpUrl validates the follow-up login flow
@@ -933,7 +933,7 @@ func TestOIDCFollowUpUrl(t *testing.T) {
assert.NoError(c, err)
assert.NotEqual(c, u.String(), st.AuthURL, "AuthURL should change")
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for registration cache to expire and status to reflect NeedsLogin")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for registration cache to expire and status to reflect NeedsLogin")
_, err = doLoginURL(ts.Hostname(), newUrl)
require.NoError(t, err)
@@ -971,7 +971,7 @@ func TestOIDCFollowUpUrl(t *testing.T) {
listNodes, err := headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, listNodes, 1)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for expected node list after OIDC login")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected node list after OIDC login")
}
// TestOIDCMultipleOpenedLoginUrls tests the scenario:
@@ -1081,7 +1081,7 @@ func TestOIDCMultipleOpenedLoginUrls(t *testing.T) {
listNodes, err := headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, listNodes, 1)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for expected node list after OIDC login",
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for expected node list after OIDC login",
)
}
@@ -1176,7 +1176,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
ct.Errorf("User validation failed after first login - unexpected users: %s", diff)
}
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating user1 creation after initial OIDC login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user1 creation after initial OIDC login")
t.Logf("Validating initial node creation at %s", time.Now().Format(TimestampFormat))
@@ -1188,7 +1188,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
initialNodes, err = headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes during initial validation")
assert.Len(ct, initialNodes, 1, "Expected exactly 1 node after first login, got %d", len(initialNodes))
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating initial node creation for user1 after OIDC login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating initial node creation for user1 after OIDC login")
// Collect expected node IDs for validation after user1 initial login
expectedNodes := make([]types.NodeID, 0, 1)
@@ -1203,7 +1203,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
nodeID, err = strconv.ParseUint(string(status.Self.ID), 10, 64)
assert.NoError(ct, err, "Failed to parse node ID from status")
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for node ID to be populated in status after initial login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for node ID to be populated in status after initial login")
expectedNodes = append(expectedNodes, types.NodeID(nodeID))
@@ -1232,7 +1232,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err, "Failed to get client status during logout validation")
assert.Equal(ct, "NeedsLogin", status.BackendState, "Expected NeedsLogin state after logout, got %s", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for user1 logout to complete before same-user relogin")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for user1 logout to complete before same-user relogin")
// Validate node persistence during logout (node should remain in DB)
t.Logf("Validating node persistence during logout at %s", time.Now().Format(TimestampFormat))
@@ -1240,7 +1240,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
listNodes, err := headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes during logout validation")
assert.Len(ct, listNodes, 1, "Should still have exactly 1 node during logout (node should persist in DB), got %d", len(listNodes))
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating node persistence in database during same-user logout")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating node persistence in database during same-user logout")
// Login again as the same user (user1)
u, err = ts.LoginWithURL(headscale.GetEndpoint())
@@ -1254,7 +1254,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err, "Failed to get client status during relogin validation")
assert.Equal(ct, "Running", status.BackendState, "Expected Running state after user1 relogin, got %s", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for user1 relogin to complete (same user)")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for user1 relogin to complete (same user)")
t.Logf("Final validation: checking user persistence after same-user relogin at %s", time.Now().Format(TimestampFormat))
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
@@ -1279,7 +1279,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
if diff := cmp.Diff(wantUsers, listUsers, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
ct.Errorf("Final user validation failed - user1 should persist after same-user relogin: %s", diff)
}
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating user1 persistence after same-user OIDC relogin cycle")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating user1 persistence after same-user OIDC relogin cycle")
var finalNodes []*v1.Node
@@ -1302,7 +1302,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
assert.NotEqual(ct, initialNodeKey, finalNode.GetNodeKey(), "Node key should be regenerated after logout/relogin even for same user")
t.Logf("Final validation complete - same user relogin key relationships verified at %s", time.Now().Format(TimestampFormat))
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating final node state after same-user OIDC relogin cycle with key preservation validation")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating final node state after same-user OIDC relogin cycle with key preservation validation")
// Security validation: user1's node should be active after relogin
activeUser1NodeID := types.NodeID(finalNodes[0].GetId())
@@ -1322,7 +1322,7 @@ func TestOIDCReloginSameNodeSameUser(t *testing.T) {
} else {
assert.Fail(c, "User1 node not found in nodestore after same-user relogin")
}
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating user1 node is online after same-user OIDC relogin")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating user1 node is online after same-user OIDC relogin")
}
// TestOIDCExpiryAfterRestart validates that node expiry is preserved
@@ -1398,7 +1398,7 @@ func TestOIDCExpiryAfterRestart(t *testing.T) {
initialExpiry = expiryTime
t.Logf("Initial expiry set to: %v (expires in %v)", expiryTime, time.Until(expiryTime))
}
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating initial expiry after OIDC login")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating initial expiry after OIDC login")
// Now restart the tailscaled container
t.Logf("Restarting tailscaled container at %s", time.Now().Format(TimestampFormat))
@@ -1420,7 +1420,7 @@ func TestOIDCExpiryAfterRestart(t *testing.T) {
}
assert.Equal(ct, "Running", status.BackendState)
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "waiting for tailscale to reconnect after restart")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "waiting for tailscale to reconnect after restart")
// THE CRITICAL TEST: Verify expiry is still set correctly after restart
t.Logf("Validating expiry preservation after restart at %s", time.Now().Format(TimestampFormat))
@@ -1448,7 +1448,7 @@ func TestOIDCExpiryAfterRestart(t *testing.T) {
t.Logf("SUCCESS: Expiry preserved after restart: %v (expires in %v)",
expiryTime, time.Until(expiryTime))
}
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "validating expiry preservation after restart")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "validating expiry preservation after restart")
}
// TestOIDCACLPolicyOnJoin validates that ACL policies are correctly applied
@@ -1570,7 +1570,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
gatewayNodeID = gatewayNode.GetId()
assert.Len(ct, gatewayNode.GetAvailableRoutes(), 1)
assert.Contains(ct, gatewayNode.GetAvailableRoutes(), advertiseRoute)
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "route advertisement should propagate to headscale")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "route advertisement should propagate to headscale")
// Approve the advertised route
_, err = headscale.ApproveRoutes(
@@ -1588,7 +1588,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
gatewayNode := nodes[0]
assert.Len(ct, gatewayNode.GetApprovedRoutes(), 1)
assert.Contains(ct, gatewayNode.GetApprovedRoutes(), advertiseRoute)
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "route approval should propagate to headscale")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "route approval should propagate to headscale")
// NOW create the OIDC user by having them join
// This is where issue #2888 manifests - the new OIDC node should immediately
@@ -1658,7 +1658,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
t.Logf("Gateway peer AllowedIPs: %v", allowedIPs)
}
}
}, integrationutil.ScaledTimeout(15*time.Second), 500*time.Millisecond,
}, integrationutil.ScaledTimeout(15*time.Second), integrationutil.SlowPoll,
"OIDC user should immediately see gateway's advertised route without client restart (issue #2888)")
// Verify that the Gateway node sees the OIDC node's advertised route (AutoApproveRoutes check)
@@ -1690,7 +1690,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
"Gateway user should immediately see OIDC's advertised route %s in PrimaryRoutes", oidcAdvertiseRoute)
}
}
}, integrationutil.ScaledTimeout(15*time.Second), 500*time.Millisecond,
}, integrationutil.ScaledTimeout(15*time.Second), integrationutil.SlowPoll,
"Gateway user should immediately see OIDC's advertised route (AutoApproveRoutes check)")
// Additional validation: Verify nodes in headscale match expectations
@@ -1738,7 +1738,7 @@ func TestOIDCACLPolicyOnJoin(t *testing.T) {
assert.Equal(ct, "oidcuser", oidcUserFound.GetName())
assert.Equal(ct, "oidcuser@headscale.net", oidcUserFound.GetEmail())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "headscale should have correct users and nodes")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "headscale should have correct users and nodes")
t.Logf("Test completed successfully - issue #2888 fix validated")
}
@@ -1829,7 +1829,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err)
assert.Equal(ct, "Running", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for initial login to complete")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for initial login to complete")
// Step 1: Verify initial route is advertised, approved, and SERVING
t.Logf("Step 1: Verifying initial route is advertised, approved, and SERVING at %s", time.Now().Format(TimestampFormat))
@@ -1853,7 +1853,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
assert.Contains(c, initialNode.GetSubnetRoutes(), advertiseRoute,
"Subnet routes should contain %s", advertiseRoute)
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "initial route should be serving")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "initial route should be serving")
require.NotNil(t, initialNode, "Initial node should be found")
initialNodeID := initialNode.GetId()
@@ -1871,7 +1871,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err)
assert.Equal(ct, "NeedsLogin", status.BackendState, "Expected NeedsLogin state after logout")
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for logout to complete")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for logout to complete")
t.Logf("Logout completed, node should still exist in database")
@@ -1880,7 +1880,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, nodes, 1, "Node should persist in database after logout")
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "node should persist after logout")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "node should persist after logout")
// Step 3: Re-authenticate via OIDC as the same user
t.Logf("Step 3: Re-authenticating with same user via OIDC at %s", time.Now().Format(TimestampFormat))
@@ -1896,7 +1896,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
status, err := ts.Status()
assert.NoError(ct, err)
assert.Equal(ct, "Running", status.BackendState, "Expected Running state after relogin")
}, integrationutil.ScaledTimeout(30*time.Second), 1*time.Second, "waiting for relogin to complete")
}, integrationutil.StatusReadyTimeout, 1*time.Second, "waiting for relogin to complete")
t.Logf("Re-authentication completed at %s", time.Now().Format(TimestampFormat))
@@ -1930,7 +1930,7 @@ func TestOIDCReloginSameUserRoutesPreserved(t *testing.T) {
assert.Equal(c, initialNodeID, node.GetId(),
"Node ID should be preserved after same-user relogin")
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond,
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll,
"BUG #2896: routes should remain SERVING after OIDC logout/relogin with same user")
t.Logf("Test completed - verifying issue #2896 fix for OIDC")

View File

@@ -107,7 +107,7 @@ func TestAuthWebFlowLogoutAndReloginSameUser(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes after web authentication")
assert.Len(ct, listNodes, len(allClients), "Expected %d nodes after web auth, got %d", len(allClients), len(listNodes))
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second, "validating node count matches client count after web authentication")
}, integrationutil.StatusReadyTimeout, 2*time.Second, "validating node count matches client count after web authentication")
nodeCountBeforeLogout := len(listNodes)
t.Logf("node count before logout: %d", nodeCountBeforeLogout)
@@ -154,7 +154,7 @@ func TestAuthWebFlowLogoutAndReloginSameUser(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes after web flow logout")
assert.Len(ct, listNodes, nodeCountBeforeLogout, "Node count should remain unchanged after logout - expected %d nodes, got %d", nodeCountBeforeLogout, len(listNodes))
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating node persistence in database after web flow logout")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating node persistence in database after web flow logout")
t.Logf("node count first login: %d, after relogin: %d", nodeCountBeforeLogout, len(listNodes))
// Validate connection state after relogin
@@ -265,7 +265,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err, "Failed to list nodes after initial web authentication")
assert.Len(ct, listNodes, len(allClients), "Expected %d nodes after web auth, got %d", len(allClients), len(listNodes))
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second, "validating node count matches client count after initial web authentication")
}, integrationutil.StatusReadyTimeout, 2*time.Second, "validating node count matches client count after initial web authentication")
nodeCountBeforeLogout := len(listNodes)
t.Logf("node count before logout: %d", nodeCountBeforeLogout)
@@ -325,7 +325,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) {
user1Nodes, err = headscale.ListNodes("user1")
assert.NoError(ct, err, "Failed to list nodes for user1 after web flow relogin")
assert.Len(ct, user1Nodes, len(allClients), "User1 should have all %d clients after web flow relogin, got %d nodes", len(allClients), len(user1Nodes))
}, integrationutil.ScaledTimeout(60*time.Second), 2*time.Second, "validating user1 has all client nodes after web flow user switch relogin")
}, integrationutil.HAConvergeTimeout, 2*time.Second, "validating user1 has all client nodes after web flow user switch relogin")
// Collect expected node IDs for user1 after relogin
expectedUser1Nodes := make([]types.NodeID, 0, len(user1Nodes))
@@ -347,7 +347,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) {
user2Nodes, err = headscale.ListNodes("user2")
assert.NoError(ct, err, "Failed to list nodes for user2 after CLI registration to user1")
assert.Len(ct, user2Nodes, len(allClients)/2, "User2 should still have %d old nodes (likely expired) after CLI registration to user1, got %d nodes", len(allClients)/2, len(user2Nodes))
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second, "validating user2 old nodes remain in database after CLI registration to user1")
}, integrationutil.StatusReadyTimeout, 2*time.Second, "validating user2 old nodes remain in database after CLI registration to user1")
t.Logf("Validating client login states after web flow user switch at %s", time.Now().Format(TimestampFormat))
@@ -356,7 +356,7 @@ func TestAuthWebFlowLogoutAndReloginNewUser(t *testing.T) {
status, err := client.Status()
assert.NoError(ct, err, "Failed to get status for client %s", client.Hostname())
assert.Equal(ct, "user1@test.no", status.User[status.Self.UserID].LoginName, "Client %s should be logged in as user1 after web flow user switch, got %s", client.Hostname(), status.User[status.Self.UserID].LoginName)
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second, "validating %s is logged in as user1 after web flow user switch", client.Hostname())
}, integrationutil.StatusReadyTimeout, 2*time.Second, "validating %s is logged in as user1 after web flow user switch", client.Hostname())
}
// Test connectivity after user switch

View File

@@ -147,7 +147,7 @@ func TestUserCommand(t *testing.T) {
&listByUsername,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for user list by username")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for user list by username")
slices.SortFunc(listByUsername, sortWithID)
@@ -178,7 +178,7 @@ func TestUserCommand(t *testing.T) {
&listByID,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for user list by ID")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for user list by ID")
slices.SortFunc(listByID, sortWithID)
@@ -264,7 +264,7 @@ func TestUserCommand(t *testing.T) {
)
assert.NoError(c, err)
assert.Empty(c, listAfterNameDelete)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for user list after name delete")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for user list after name delete")
}
func TestPreAuthKeyCommand(t *testing.T) {
@@ -315,7 +315,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
&preAuthKey,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for preauth key creation")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth key creation")
keys[index] = &preAuthKey
}
@@ -337,7 +337,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
&listedPreAuthKeys,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for preauth keys list")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth keys list")
// There is one key created by "scenario.CreateHeadscaleEnv"
assert.Len(t, listedPreAuthKeys, 4)
@@ -413,7 +413,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
&listedPreAuthKeysAfterExpire,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for preauth keys list after expire")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth keys list after expire")
assert.True(t, listedPreAuthKeysAfterExpire[1].GetExpiration().AsTime().Before(time.Now()))
assert.True(t, listedPreAuthKeysAfterExpire[2].GetExpiration().AsTime().After(time.Now()))
@@ -457,7 +457,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
&preAuthKey,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for preauth key creation without expiry")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth key creation without expiry")
var listedPreAuthKeys []v1.PreAuthKey
@@ -474,7 +474,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
&listedPreAuthKeys,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for preauth keys list")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth keys list")
// There is one key created by "scenario.CreateHeadscaleEnv"
assert.Len(t, listedPreAuthKeys, 2)
@@ -523,7 +523,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
&preAuthReusableKey,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for reusable preauth key creation")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for reusable preauth key creation")
var preAuthEphemeralKey v1.PreAuthKey
@@ -543,7 +543,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
&preAuthEphemeralKey,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for ephemeral preauth key creation")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for ephemeral preauth key creation")
assert.True(t, preAuthEphemeralKey.GetEphemeral())
assert.False(t, preAuthEphemeralKey.GetReusable())
@@ -563,7 +563,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
&listedPreAuthKeys,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for preauth keys list after reusable/ephemeral creation")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth keys list after reusable/ephemeral creation")
// There is one key created by "scenario.CreateHeadscaleEnv"
assert.Len(t, listedPreAuthKeys, 3)
@@ -621,7 +621,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
&user2Key,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for user2 preauth key creation")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for user2 preauth key creation")
var listNodes []*v1.Node
@@ -653,7 +653,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
assert.NoError(ct, err)
assert.NotContains(ct, []string{"Starting", "Running"}, status.BackendState,
"Expected node to be logged out, backend state: %s", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey())
require.NoError(t, err)
@@ -665,7 +665,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
// With tags-as-identity model, tagged nodes show as 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.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var err error
@@ -730,7 +730,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
&user2Key,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for user2 tagged preauth key creation")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for user2 tagged preauth key creation")
allClients, err := scenario.ListTailscaleClients()
requireNoErrListClients(t, err)
@@ -751,7 +751,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
assert.NoError(ct, err)
assert.NotContains(ct, []string{"Starting", "Running"}, status.BackendState,
"Expected node to be logged out, backend state: %s", status.BackendState)
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
// Log in with the tagged PreAuthKey (from user2, with tags)
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey())
@@ -763,7 +763,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
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)
assert.Equal(ct, "userid:2147455555", status.Self.UserID.String(), "Expected node to be logged in as tagged-devices user")
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
// Wait for the second node to appear
var listNodes []*v1.Node
@@ -850,7 +850,7 @@ func TestApiKeyCommand(t *testing.T) {
&listedAPIKeys,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for API keys list")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for API keys list")
assert.Len(t, listedAPIKeys, 5)
@@ -925,7 +925,7 @@ func TestApiKeyCommand(t *testing.T) {
&listedAfterExpireAPIKeys,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for API keys list after expire")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for API keys list after expire")
for index := range listedAfterExpireAPIKeys {
if _, ok := expiredPrefixes[listedAfterExpireAPIKeys[index].GetPrefix()]; ok {
@@ -967,7 +967,7 @@ func TestApiKeyCommand(t *testing.T) {
&listedAPIKeysAfterDelete,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for API keys list after delete")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for API keys list after delete")
assert.Len(t, listedAPIKeysAfterDelete, 4)
@@ -996,7 +996,7 @@ func TestApiKeyCommand(t *testing.T) {
&listedAPIKeysAfterExpireByID,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for API keys list after expire by ID")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for API keys list after expire by ID")
// Verify the key was expired
for idx := range listedAPIKeysAfterExpireByID {
@@ -1032,7 +1032,7 @@ func TestApiKeyCommand(t *testing.T) {
&listedAPIKeysAfterDeleteByID,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for API keys list after delete by ID")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for API keys list after delete by ID")
assert.Len(t, listedAPIKeysAfterDeleteByID, 3)
@@ -1109,7 +1109,7 @@ func TestNodeCommand(t *testing.T) {
&node,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for node registration")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for node registration")
nodes[index] = &node
}
@@ -1194,7 +1194,7 @@ func TestNodeCommand(t *testing.T) {
&node,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for other-user node registration")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for other-user node registration")
otherUserMachines[index] = &node
}
@@ -1219,7 +1219,7 @@ func TestNodeCommand(t *testing.T) {
&listAllWithotherUser,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for nodes list after adding other-user nodes")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for nodes list after adding other-user nodes")
// All nodes, nodes + otherUser
assert.Len(t, listAllWithotherUser, 7)
@@ -1248,7 +1248,7 @@ func TestNodeCommand(t *testing.T) {
&listOnlyotherUserMachineUser,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for nodes list filtered by other-user")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for nodes list filtered by other-user")
assert.Len(t, listOnlyotherUserMachineUser, 2)
@@ -1368,7 +1368,7 @@ func TestNodeExpireCommand(t *testing.T) {
&node,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for node-expire-user node registration")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for node-expire-user node registration")
nodes[index] = &node
}
@@ -1390,7 +1390,7 @@ func TestNodeExpireCommand(t *testing.T) {
&listAll,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for nodes list in expire test")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for nodes list in expire test")
assert.Len(t, listAll, 5)
@@ -1429,7 +1429,7 @@ func TestNodeExpireCommand(t *testing.T) {
&listAllAfterExpiry,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for nodes list after expiry")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for nodes list after expiry")
assert.Len(t, listAllAfterExpiry, 5)
@@ -1506,7 +1506,7 @@ func TestNodeRenameCommand(t *testing.T) {
&node,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for node-rename-command node registration")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for node-rename-command node registration")
nodes[index] = &node
}
@@ -1528,7 +1528,7 @@ func TestNodeRenameCommand(t *testing.T) {
&listAll,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for nodes list in rename test")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for nodes list in rename test")
assert.Len(t, listAll, 5)
@@ -1569,7 +1569,7 @@ func TestNodeRenameCommand(t *testing.T) {
&listAllAfterRename,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for nodes list after rename")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for nodes list after rename")
assert.Len(t, listAllAfterRename, 5)
@@ -1607,7 +1607,7 @@ func TestNodeRenameCommand(t *testing.T) {
&listAllAfterRenameAttempt,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for nodes list after failed rename attempt")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for nodes list after failed rename attempt")
assert.Len(t, listAllAfterRenameAttempt, 5)
@@ -1696,7 +1696,7 @@ func TestPolicyCommand(t *testing.T) {
&output,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond, "Waiting for policy get command")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for policy get command")
assert.Len(t, output.TagOwners, 1)
assert.Len(t, output.ACLs, 1)

View File

@@ -67,7 +67,7 @@ func TestResolveMagicDNS(t *testing.T) {
for _, ip := range ips {
assert.Contains(ct, result, ip.String(), "IP %s should be found in DNS resolution result from %s to %s", ip.String(), client.Hostname(), peer.Hostname())
}
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
}
}
}

View File

@@ -153,7 +153,7 @@ func derpServerScenario(
assert.NotContains(ct, health, "could not connect to the 'Headscale Embedded DERP' relay server.",
"Client %s should be connected to Headscale Embedded DERP", client.Hostname())
}
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
}
success := pingDerpAllHelper(t, allClients, allHostnames)
@@ -174,7 +174,7 @@ func derpServerScenario(
assert.NotContains(ct, health, "could not connect to the 'Headscale Embedded DERP' relay server.",
"Client %s should be connected to Headscale Embedded DERP after first run", client.Hostname())
}
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
}
t.Logf("Run 1: %d successful pings out of %d", success, len(allClients)*len(allHostnames))
@@ -200,7 +200,7 @@ func derpServerScenario(
assert.NotContains(ct, health, "could not connect to the 'Headscale Embedded DERP' relay server.",
"Client %s should be connected to Headscale Embedded DERP after second run", client.Hostname())
}
}, integrationutil.ScaledTimeout(30*time.Second), 2*time.Second)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
}
t.Logf("Run2: %d successful pings out of %d", success, len(allClients)*len(allHostnames))

View File

@@ -0,0 +1,40 @@
package integrationutil
import "time"
// CI-scaled convergence budgets. ScaledTimeout doubles each on CI.
var (
// HAConvergeTimeout: routes / ACL / policy propagation to reach
// every node and show up in status, traceroute, or curl.
HAConvergeTimeout = ScaledTimeout(60 * time.Second)
// HASlowConvergeTimeout: multi-step failover sequences that need
// at least one HA prober cycle plus a data-plane settle.
HASlowConvergeTimeout = ScaledTimeout(120 * time.Second)
// PolicyPropagationTimeout: post-SetPolicy filter rules and peer
// reachability to reflect the change. Sized for wgengine's
// rule-reload lag on contended CI runners (~2 min observed).
PolicyPropagationTimeout = ScaledTimeout(180 * time.Second)
// AuthFlowTimeout: OIDC / web-auth / preauth-key flows to reach
// Running.
AuthFlowTimeout = ScaledTimeout(30 * time.Second)
// StatusReadyTimeout: post-event read to reflect the event
// (created node visible in list, set tags visible on node).
StatusReadyTimeout = ScaledTimeout(30 * time.Second)
)
// Polling intervals for EventuallyWithT.
const (
// FastPoll: in-process reads (HA state, route table snapshots).
FastPoll = 200 * time.Millisecond
// SlowPoll: cross-container reads (tailscale status, curl,
// headscale API) where each tick pays a docker exec round-trip.
SlowPoll = 500 * time.Millisecond
// PingPoll: gap between full ping-matrix sweeps.
PingPoll = 2 * time.Second
)

View File

@@ -471,7 +471,7 @@ func doSSHWithRetryAsUser(
// For all other errors, assert no error to trigger retry
assert.NoError(ct, err)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll)
} else {
// For failure cases, just execute once
result, stderr, err = client.Execute(command)
@@ -701,7 +701,7 @@ func findSSHCheckAuthID(t *testing.T, headscale ControlServer) string {
}
assert.NotEmpty(c, authID, "auth-id not found in headscale logs")
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "waiting for SSH check auth-id in headscale logs")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "waiting for SSH check auth-id in headscale logs")
return authID
}
@@ -810,7 +810,7 @@ func findNewSSHCheckAuthID(
}
assert.NotEmpty(c, authID, "new auth-id not found in headscale logs")
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "waiting for new SSH check auth-id")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "waiting for new SSH check auth-id")
return authID
}

View File

@@ -182,7 +182,7 @@ func TestTagsAuthKeyWithTagRequestDifferentTag(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Node registered with tags: %v (expected rejection)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node state")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node state")
t.Fail()
}
@@ -252,7 +252,7 @@ func TestTagsAuthKeyWithTagNoAdvertiseFlag(t *testing.T) {
t.Logf("Node registered with tags: %v", node.GetTags())
assertNodeHasTagsWithCollect(c, node, []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying node inherited tags from auth key")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying node inherited tags from auth key")
t.Logf("Test 2.2 completed - node inherited tags from auth key")
}
@@ -320,7 +320,7 @@ func TestTagsAuthKeyWithTagCannotAddViaCLI(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
t.Logf("Node registered with tag:valid-owned, now attempting to add tag:second via CLI")
@@ -353,7 +353,7 @@ func TestTagsAuthKeyWithTagCannotAddViaCLI(t *testing.T) {
assert.Fail(c, "Tags should not have changed")
}
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying tags unchanged")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying tags unchanged")
}
}
@@ -416,7 +416,7 @@ func TestTagsAuthKeyWithTagCannotChangeViaCLI(t *testing.T) {
nodes, err := headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, nodes, 1)
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
t.Logf("Node registered, now attempting to change to different tag via CLI")
@@ -448,7 +448,7 @@ func TestTagsAuthKeyWithTagCannotChangeViaCLI(t *testing.T) {
assert.Fail(c, "Tags should not have changed")
}
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying tags unchanged")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying tags unchanged")
}
}
@@ -519,7 +519,7 @@ func TestTagsAuthKeyWithTagAdminOverrideReauthPreserves(t *testing.T) {
nodeID = nodes[0].GetId()
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
t.Logf("Step 1 complete: Node %d registered with tag:valid-owned", nodeID)
@@ -536,12 +536,12 @@ func TestTagsAuthKeyWithTagAdminOverrideReauthPreserves(t *testing.T) {
t.Logf("After admin assignment, server tags are: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying admin tag assignment on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying admin tag assignment on server")
// Verify admin assignment propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying admin tag assignment propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying admin tag assignment propagated to node self")
t.Logf("Step 2 complete: Admin assigned tag:second (verified on both server and node self)")
@@ -569,12 +569,12 @@ func TestTagsAuthKeyWithTagAdminOverrideReauthPreserves(t *testing.T) {
// Expected: admin-assigned tags are preserved through reauth
assertNodeHasTagsWithCollect(c, node, []string{"tag:second"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved after reauth on server")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved after reauth on server")
// Verify admin tags are preserved in node's self view after reauth (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved after reauth in node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved after reauth in node self")
t.Logf("Test 2.5 PASS: Admin tags preserved through reauth (admin decisions are authoritative)")
}
@@ -645,7 +645,7 @@ func TestTagsAuthKeyWithTagCLICannotModifyAdminTags(t *testing.T) {
if len(nodes) == 1 {
nodeID = nodes[0].GetId()
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
// Step 2: Admin assigns multiple tags via headscale CLI
err = headscale.SetNodeTags(nodeID, []string{"tag:valid-owned", "tag:second"})
@@ -659,12 +659,12 @@ func TestTagsAuthKeyWithTagCLICannotModifyAdminTags(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned", "tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying admin tag assignment on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying admin tag assignment on server")
// Verify admin assignment propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned", "tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying admin tag assignment propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying admin tag assignment propagated to node self")
t.Logf("Admin assigned both tags, now attempting to reduce via CLI")
@@ -691,12 +691,12 @@ func TestTagsAuthKeyWithTagCLICannotModifyAdminTags(t *testing.T) {
// Expected: tags should remain unchanged (admin wins)
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned", "tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "admin tags should be preserved after CLI attempt on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "admin tags should be preserved after CLI attempt on server")
// Verify admin tags are preserved in node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned", "tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved after CLI attempt in node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved after CLI attempt in node self")
t.Logf("Test 2.6 PASS: Admin tags preserved - CLI cannot modify admin-assigned tags")
}
@@ -770,7 +770,7 @@ func TestTagsAuthKeyWithoutTagCannotRequestTags(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Node registered with tags: %v (expected rejection)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node state")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node state")
t.Fail()
}
@@ -837,7 +837,7 @@ func TestTagsAuthKeyWithoutTagRegisterNoTags(t *testing.T) {
t.Logf("Node registered with tags: %v", nodes[0].GetTags())
assertNodeHasNoTagsWithCollect(c, nodes[0])
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying node has no tags")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying node has no tags")
t.Logf("Test 3.2 completed - node registered without tags")
}
@@ -905,7 +905,7 @@ func TestTagsAuthKeyWithoutTagCannotAddViaCLI(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasNoTagsWithCollect(c, nodes[0])
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
t.Logf("Node registered without tags, attempting to add via CLI")
@@ -936,7 +936,7 @@ func TestTagsAuthKeyWithoutTagCannotAddViaCLI(t *testing.T) {
assert.Fail(c, "Tags should not have changed")
}
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying tags unchanged")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying tags unchanged")
}
}
@@ -1007,7 +1007,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset(t *testing.T) {
nodeID = nodes[0].GetId()
assertNodeHasNoTagsWithCollect(c, nodes[0])
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
// Step 2: Admin assigns tags
err = headscale.SetNodeTags(nodeID, []string{"tag:valid-owned"})
@@ -1021,12 +1021,12 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying admin tag assignment on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying admin tag assignment on server")
// Verify admin assignment propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying admin tag assignment propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying admin tag assignment propagated to node self")
t.Logf("Admin assigned tag, now running CLI with --reset")
@@ -1050,12 +1050,12 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset(t *testing.T) {
t.Logf("After --reset, server tags are: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "admin tags should be preserved after --reset on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "admin tags should be preserved after --reset on server")
// Verify admin tags are preserved in node's self view after --reset (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved after --reset in node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved after --reset in node self")
t.Logf("Test 3.4 PASS: Admin tags preserved after --reset")
}
@@ -1126,7 +1126,7 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise(t *testing.T)
if len(nodes) == 1 {
nodeID = nodes[0].GetId()
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
// Step 2: Admin assigns tags
err = headscale.SetNodeTags(nodeID, []string{"tag:valid-owned"})
@@ -1140,12 +1140,12 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise(t *testing.T)
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying admin tag assignment on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying admin tag assignment on server")
// Verify admin assignment propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying admin tag assignment propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying admin tag assignment propagated to node self")
t.Logf("Admin assigned tag, now running CLI with empty --advertise-tags")
@@ -1169,12 +1169,12 @@ func TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise(t *testing.T)
t.Logf("After empty --advertise-tags, server tags are: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "admin tags should be preserved after empty --advertise-tags on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "admin tags should be preserved after empty --advertise-tags on server")
// Verify admin tags are preserved in node's self view after empty --advertise-tags (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved after empty --advertise-tags in node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved after empty --advertise-tags in node self")
t.Logf("Test 3.5 PASS: Admin tags preserved after empty --advertise-tags")
}
@@ -1245,7 +1245,7 @@ func TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag(t *testing.T) {
if len(nodes) == 1 {
nodeID = nodes[0].GetId()
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
// Step 2: Admin assigns multiple tags
err = headscale.SetNodeTags(nodeID, []string{"tag:valid-owned", "tag:second"})
@@ -1259,12 +1259,12 @@ func TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned", "tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying admin tag assignment on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying admin tag assignment on server")
// Verify admin assignment propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned", "tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying admin tag assignment propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying admin tag assignment propagated to node self")
t.Logf("Admin assigned both tags, now attempting to reduce via CLI")
@@ -1288,12 +1288,12 @@ func TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag(t *testing.T) {
t.Logf("After CLI reduce attempt, server tags are: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned", "tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "admin tags should be preserved after CLI reduce attempt on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "admin tags should be preserved after CLI reduce attempt on server")
// Verify admin tags are preserved in node's self view after CLI reduce attempt (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned", "tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved after CLI reduce attempt in node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved after CLI reduce attempt in node self")
t.Logf("Test 3.6 PASS: Admin tags preserved - CLI cannot reduce admin-assigned multi-tag set")
}
@@ -1369,7 +1369,7 @@ func TestTagsUserLoginOwnedTagAtRegistration(t *testing.T) {
t.Logf("Node registered with tags: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying node has advertised tag")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying node has advertised tag")
t.Logf("Test 1.1 completed - web auth with owned tag succeeded")
}
@@ -1442,7 +1442,7 @@ func TestTagsUserLoginNonExistentTagAtRegistration(t *testing.T) {
"Non-existent tag should not be applied to node")
t.Logf("Test 1.2: Node registered with tags: %v (non-existent tag correctly rejected)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node registration result")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node registration result")
}
}
@@ -1510,7 +1510,7 @@ func TestTagsUserLoginUnownedTagAtRegistration(t *testing.T) {
"Unowned tag should not be applied to node (tag:valid-unowned is owned by other-user)")
t.Logf("Test 1.3: Node registered with tags: %v (unowned tag correctly rejected)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node registration result")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node registration result")
}
// TestTagsUserLoginAddTagViaCLIReauth tests that a user can add tags via CLI reauthentication.
@@ -1574,7 +1574,7 @@ func TestTagsUserLoginAddTagViaCLIReauth(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Initial tags: %v", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "checking initial tags")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "checking initial tags")
// Step 2: Try to add second tag via CLI
t.Logf("Attempting to add second tag via CLI reauth")
@@ -1601,7 +1601,7 @@ func TestTagsUserLoginAddTagViaCLIReauth(t *testing.T) {
t.Logf("Test 1.4: Tags are %v (may require manual reauth completion)", nodes[0].GetTags())
}
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "checking tags after CLI")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "checking tags after CLI")
}
// TestTagsUserLoginRemoveTagViaCLIReauth tests that a user can remove tags via CLI reauthentication.
@@ -1665,7 +1665,7 @@ func TestTagsUserLoginRemoveTagViaCLIReauth(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Initial tags: %v", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "checking initial tags")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "checking initial tags")
// Step 2: Try to remove second tag via CLI
t.Logf("Attempting to remove tag via CLI reauth")
@@ -1690,7 +1690,7 @@ func TestTagsUserLoginRemoveTagViaCLIReauth(t *testing.T) {
t.Logf("Test 1.5 PASS: Only one tag after removal")
}
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "checking tags after CLI")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "checking tags after CLI")
}
// TestTagsUserLoginCLINoOpAfterAdminAssignment tests that CLI advertise-tags becomes
@@ -1760,7 +1760,7 @@ func TestTagsUserLoginCLINoOpAfterAdminAssignment(t *testing.T) {
nodeID = nodes[0].GetId()
t.Logf("Step 1: Node %d registered with tags: %v", nodeID, nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
// Step 2: Admin assigns different tag
err = headscale.SetNodeTags(nodeID, []string{"tag:second"})
@@ -1775,12 +1775,12 @@ func TestTagsUserLoginCLINoOpAfterAdminAssignment(t *testing.T) {
t.Logf("Step 2: After admin assignment, server tags: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying admin assignment on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying admin assignment on server")
// Verify admin assignment propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying admin assignment propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying admin assignment propagated to node self")
// Step 3: Try to change tags via CLI
command := []string{
@@ -1801,12 +1801,12 @@ func TestTagsUserLoginCLINoOpAfterAdminAssignment(t *testing.T) {
t.Logf("Step 3: After CLI, server tags are: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "admin tags should be preserved - CLI advertise-tags should be no-op on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "admin tags should be preserved - CLI advertise-tags should be no-op on server")
// Verify admin tags are preserved in node's self view after CLI attempt (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved - CLI advertise-tags should be no-op in node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved - CLI advertise-tags should be no-op in node self")
t.Logf("Test 1.6 PASS: Admin tags preserved (CLI was no-op)")
}
@@ -1876,7 +1876,7 @@ func TestTagsUserLoginCLICannotRemoveAdminTags(t *testing.T) {
if len(nodes) == 1 {
nodeID = nodes[0].GetId()
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
// Step 2: Admin assigns both tags
err = headscale.SetNodeTags(nodeID, []string{"tag:valid-owned", "tag:second"})
@@ -1891,12 +1891,12 @@ func TestTagsUserLoginCLICannotRemoveAdminTags(t *testing.T) {
t.Logf("After admin assignment, server tags: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned", "tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying admin assignment on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying admin assignment on server")
// Verify admin assignment propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned", "tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying admin assignment propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying admin assignment propagated to node self")
// Step 3: Try to reduce tags via CLI
command := []string{
@@ -1917,12 +1917,12 @@ func TestTagsUserLoginCLICannotRemoveAdminTags(t *testing.T) {
t.Logf("Test 1.7: After CLI, server tags are: %v", nodes[0].GetTags())
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned", "tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "admin tags should be preserved - CLI cannot remove them on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "admin tags should be preserved - CLI cannot remove them on server")
// Verify admin tags are preserved in node's self view after CLI attempt (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned", "tag:second"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "admin tags should be preserved - CLI cannot remove them in node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "admin tags should be preserved - CLI cannot remove them in node self")
t.Logf("Test 1.7 PASS: Admin tags preserved (CLI cannot remove)")
}
@@ -1995,7 +1995,7 @@ func TestTagsAuthKeyWithTagRequestNonExistentTag(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Node registered with tags: %v (expected rejection)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node state")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node state")
t.Fail()
}
@@ -2065,7 +2065,7 @@ func TestTagsAuthKeyWithTagRequestUnownedTag(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Node registered with tags: %v (expected rejection)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node state")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node state")
t.Fail()
}
@@ -2139,7 +2139,7 @@ func TestTagsAuthKeyWithoutTagRequestNonExistentTag(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Node registered with tags: %v (expected rejection)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node state")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node state")
t.Fail()
}
@@ -2209,7 +2209,7 @@ func TestTagsAuthKeyWithoutTagRequestUnownedTag(t *testing.T) {
if len(nodes) == 1 {
t.Logf("Node registered with tags: %v (expected rejection)", nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "checking node state")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "checking node state")
t.Fail()
}
@@ -2281,7 +2281,7 @@ func TestTagsAdminAPICannotSetNonExistentTag(t *testing.T) {
nodeID = nodes[0].GetId()
t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration")
// Try to set a non-existent tag via admin API - should fail
err = headscale.SetNodeTags(nodeID, []string{"tag:nonexistent"})
@@ -2353,7 +2353,7 @@ func TestTagsAdminAPICanSetUnownedTag(t *testing.T) {
nodeID = nodes[0].GetId()
t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration")
// Admin sets an "unowned" tag - should SUCCEED because admin has full authority
// (tag:valid-unowned is owned by other-user, but admin can assign it)
@@ -2369,12 +2369,12 @@ func TestTagsAdminAPICanSetUnownedTag(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-unowned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying unowned tag was applied on server")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying unowned tag was applied on server")
// Verify the tag was propagated to node's self view (issue #2978)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-unowned"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying unowned tag propagated to node self")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying unowned tag propagated to node self")
t.Logf("Test 4.2 PASS: Admin API correctly allowed setting unowned tag")
}
@@ -2441,7 +2441,7 @@ func TestTagsAdminAPICannotRemoveAllTags(t *testing.T) {
nodeID = nodes[0].GetId()
t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration")
// Try to remove all tags - should fail
err = headscale.SetNodeTags(nodeID, []string{})
@@ -2458,7 +2458,7 @@ func TestTagsAdminAPICannotRemoveAllTags(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying original tags preserved")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying original tags preserved")
}
// assertNetmapSelfHasTagsWithCollect asserts that the client's netmap self node has expected tags.
@@ -2563,12 +2563,12 @@ func TestTagsIssue2978ReproTagReplacement(t *testing.T) {
nodeID = nodes[0].GetId()
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for initial registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for initial registration")
// Verify client initially sees tag:valid-owned
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-owned"})
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "client should see initial tag")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "client should see initial tag")
t.Logf("Step 1: Node %d registered via web auth with --advertise-tags=tag:valid-owned, client sees it", nodeID)
@@ -2588,7 +2588,7 @@ func TestTagsIssue2978ReproTagReplacement(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:second"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "server should show tag:second after first call")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "server should show tag:second after first call")
t.Log("Step 2a: Server shows tag:second after first call")
@@ -2638,11 +2638,11 @@ func TestTagsIssue2978ReproTagReplacement(t *testing.T) {
t.Log("Step 3a: Verifying client self view updates after SECOND call")
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:second"})
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "client status.Self should update to tag:second after SECOND call")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "client status.Self should update to tag:second after SECOND call")
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNetmapSelfHasTagsWithCollect(c, client, []string{"tag:second"})
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "client netmap.SelfNode should update to tag:second after SECOND call")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "client netmap.SelfNode should update to tag:second after SECOND call")
t.Log("Step 3b: Client self view updated to tag:second after SECOND call")
@@ -2660,7 +2660,7 @@ func TestTagsIssue2978ReproTagReplacement(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-unowned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "server should show tag:valid-unowned")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "server should show tag:valid-unowned")
t.Log("Step 4a: Server shows tag:valid-unowned after first call")
@@ -2692,11 +2692,11 @@ func TestTagsIssue2978ReproTagReplacement(t *testing.T) {
t.Log("Step 5a: Verifying client self view updates after SECOND call")
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNodeSelfHasTagsWithCollect(c, client, []string{"tag:valid-unowned"})
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "client status.Self should update to tag:valid-unowned after SECOND call")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "client status.Self should update to tag:valid-unowned after SECOND call")
assert.EventuallyWithT(t, func(c *assert.CollectT) {
assertNetmapSelfHasTagsWithCollect(c, client, []string{"tag:valid-unowned"})
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "client netmap.SelfNode should update to tag:valid-unowned after SECOND call")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "client netmap.SelfNode should update to tag:valid-unowned after SECOND call")
t.Log("Test complete - see logs for bug reproduction details")
}
@@ -2763,7 +2763,7 @@ func TestTagsAdminAPICannotSetInvalidFormat(t *testing.T) {
nodeID = nodes[0].GetId()
t.Logf("Node %d registered with tags: %v", nodeID, nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "waiting for registration")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "waiting for registration")
// Try to set a tag without the "tag:" prefix - should fail
err = headscale.SetNodeTags(nodeID, []string{"invalid-no-prefix"})
@@ -2780,7 +2780,7 @@ func TestTagsAdminAPICannotSetInvalidFormat(t *testing.T) {
if len(nodes) == 1 {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond, "verifying original tags preserved")
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.SlowPoll, "verifying original tags preserved")
}
// =============================================================================
@@ -2871,7 +2871,7 @@ func TestTagsUserLoginReauthWithEmptyTagsRemovesAllTags(t *testing.T) {
// Verify node has the expected tags
assertNodeHasTagsWithCollect(c, node, []string{"tag:valid-owned", "tag:second"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "checking initial tags")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "checking initial tags")
// Step 2: Reauth with empty tags to remove all tags
t.Logf("Step 2: Reauthenticating with empty tag list to untag device (%s)", tc.name)
@@ -2947,7 +2947,7 @@ func TestTagsUserLoginReauthWithEmptyTagsRemovesAllTags(t *testing.T) {
tc.name, tagTestUser, node.GetTags(), node.GetUser().GetName())
}
}
}, integrationutil.ScaledTimeout(60*time.Second), 1*time.Second, "verifying tags removed and ownership returned")
}, integrationutil.HAConvergeTimeout, 1*time.Second, "verifying tags removed and ownership returned")
})
}
@@ -3020,7 +3020,7 @@ func TestTagsAuthKeyWithoutUserInheritsTags(t *testing.T) {
t.Logf("Node registered with tags: %v", node.GetTags())
assertNodeHasTagsWithCollect(c, node, []string{"tag:valid-owned"})
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "verifying node inherited tags from auth key")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "verifying node inherited tags from auth key")
t.Logf("Test 5.1 PASS: Node inherited tags from tags-only auth key")
}
@@ -3158,7 +3158,7 @@ func TestTagsAuthKeyConvertToUserViaCLIRegister(t *testing.T) {
assertNodeHasTagsWithCollect(c, nodes[0], []string{"tag:valid-owned"})
t.Logf("Initial state - Node ID: %d, Tags: %v", nodes[0].GetId(), nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(30*time.Second), 500*time.Millisecond, "node should be tagged initially")
}, integrationutil.StatusReadyTimeout, integrationutil.SlowPoll, "node should be tagged initially")
// Step 2: Force reauth with empty tags (triggers web auth flow)
command := []string{
@@ -3201,5 +3201,5 @@ func TestTagsAuthKeyConvertToUserViaCLIRegister(t *testing.T) {
t.Logf("After conversion - Node ID: %d, Tags: %v, User: %s",
nodes[0].GetId(), nodes[0].GetTags(), nodes[0].GetUser().GetName())
}
}, integrationutil.ScaledTimeout(60*time.Second), 1*time.Second, "node should be user-owned after conversion via CLI register")
}, integrationutil.HAConvergeTimeout, 1*time.Second, "node should be user-owned after conversion via CLI register")
}