integration: port the new CLI command tests to the HTTP client

#3326 added per-resource CLI coverage decoding into the proto types; convert
the transport to the apiv1/ogen types, keeping every command and assertion.
Drop the now-duplicate health and node-routes tests #3326 covers more fully.
This commit is contained in:
Kristoffer Dalby
2026-06-18 13:45:46 +00:00
parent 2a1ae1a2fc
commit add794d190
7 changed files with 217 additions and 332 deletions
+40 -40
View File
@@ -5,7 +5,7 @@ import (
"testing"
"time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
apiv1 "github.com/juanfont/headscale/gen/api/v1"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/integrationutil"
"github.com/juanfont/headscale/integration/tsic"
@@ -55,7 +55,7 @@ func TestApiKeyCommand(t *testing.T) {
assert.Len(t, keys, 5)
var listedAPIKeys []v1.ApiKey
var listedAPIKeys []apiv1.ApiKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -73,43 +73,43 @@ func TestApiKeyCommand(t *testing.T) {
assert.Len(t, listedAPIKeys, 5)
assert.Equal(t, uint64(1), listedAPIKeys[0].GetId())
assert.Equal(t, uint64(2), listedAPIKeys[1].GetId())
assert.Equal(t, uint64(3), listedAPIKeys[2].GetId())
assert.Equal(t, uint64(4), listedAPIKeys[3].GetId())
assert.Equal(t, uint64(5), listedAPIKeys[4].GetId())
assert.Equal(t, uint64(1), listedAPIKeys[0].GetID().Or(0))
assert.Equal(t, uint64(2), listedAPIKeys[1].GetID().Or(0))
assert.Equal(t, uint64(3), listedAPIKeys[2].GetID().Or(0))
assert.Equal(t, uint64(4), listedAPIKeys[3].GetID().Or(0))
assert.Equal(t, uint64(5), listedAPIKeys[4].GetID().Or(0))
assert.NotEmpty(t, listedAPIKeys[0].GetPrefix())
assert.NotEmpty(t, listedAPIKeys[1].GetPrefix())
assert.NotEmpty(t, listedAPIKeys[2].GetPrefix())
assert.NotEmpty(t, listedAPIKeys[3].GetPrefix())
assert.NotEmpty(t, listedAPIKeys[4].GetPrefix())
assert.NotEmpty(t, listedAPIKeys[0].GetPrefix().Or(""))
assert.NotEmpty(t, listedAPIKeys[1].GetPrefix().Or(""))
assert.NotEmpty(t, listedAPIKeys[2].GetPrefix().Or(""))
assert.NotEmpty(t, listedAPIKeys[3].GetPrefix().Or(""))
assert.NotEmpty(t, listedAPIKeys[4].GetPrefix().Or(""))
assert.True(t, listedAPIKeys[0].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedAPIKeys[1].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedAPIKeys[2].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedAPIKeys[3].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedAPIKeys[4].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedAPIKeys[0].GetExpiration().Value.After(time.Now()))
assert.True(t, listedAPIKeys[1].GetExpiration().Value.After(time.Now()))
assert.True(t, listedAPIKeys[2].GetExpiration().Value.After(time.Now()))
assert.True(t, listedAPIKeys[3].GetExpiration().Value.After(time.Now()))
assert.True(t, listedAPIKeys[4].GetExpiration().Value.After(time.Now()))
assert.True(
t,
listedAPIKeys[0].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedAPIKeys[0].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
assert.True(
t,
listedAPIKeys[1].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedAPIKeys[1].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
assert.True(
t,
listedAPIKeys[2].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedAPIKeys[2].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
assert.True(
t,
listedAPIKeys[3].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedAPIKeys[3].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
assert.True(
t,
listedAPIKeys[4].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedAPIKeys[4].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
expiredPrefixes := make(map[string]bool)
@@ -122,15 +122,15 @@ func TestApiKeyCommand(t *testing.T) {
"apikeys",
"expire",
"--prefix",
listedAPIKeys[idx].GetPrefix(),
listedAPIKeys[idx].GetPrefix().Or(""),
},
)
require.NoError(t, err)
expiredPrefixes[listedAPIKeys[idx].GetPrefix()] = true
expiredPrefixes[listedAPIKeys[idx].GetPrefix().Or("")] = true
}
var listedAfterExpireAPIKeys []v1.ApiKey
var listedAfterExpireAPIKeys []apiv1.ApiKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -147,17 +147,17 @@ func TestApiKeyCommand(t *testing.T) {
}, 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 {
if _, ok := expiredPrefixes[listedAfterExpireAPIKeys[index].GetPrefix().Or("")]; ok {
// Expired
assert.True(
t,
listedAfterExpireAPIKeys[index].GetExpiration().AsTime().Before(time.Now()),
listedAfterExpireAPIKeys[index].GetExpiration().Value.Before(time.Now()),
)
} else {
// Not expired
assert.False(
t,
listedAfterExpireAPIKeys[index].GetExpiration().AsTime().Before(time.Now()),
listedAfterExpireAPIKeys[index].GetExpiration().Value.Before(time.Now()),
)
}
}
@@ -168,11 +168,11 @@ func TestApiKeyCommand(t *testing.T) {
"apikeys",
"delete",
"--prefix",
listedAPIKeys[0].GetPrefix(),
listedAPIKeys[0].GetPrefix().Or(""),
})
require.NoError(t, err)
var listedAPIKeysAfterDelete []v1.ApiKey
var listedAPIKeysAfterDelete []apiv1.ApiKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -197,11 +197,11 @@ func TestApiKeyCommand(t *testing.T) {
"apikeys",
"expire",
"--id",
strconv.FormatUint(listedAPIKeysAfterDelete[0].GetId(), 10),
strconv.FormatUint(listedAPIKeysAfterDelete[0].GetID().Or(0), 10),
})
require.NoError(t, err)
var listedAPIKeysAfterExpireByID []v1.ApiKey
var listedAPIKeysAfterExpireByID []apiv1.ApiKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -219,14 +219,14 @@ func TestApiKeyCommand(t *testing.T) {
// Verify the key was expired
for idx := range listedAPIKeysAfterExpireByID {
if listedAPIKeysAfterExpireByID[idx].GetId() == listedAPIKeysAfterDelete[0].GetId() {
assert.True(t, listedAPIKeysAfterExpireByID[idx].GetExpiration().AsTime().Before(time.Now()),
if listedAPIKeysAfterExpireByID[idx].GetID().Or(0) == listedAPIKeysAfterDelete[0].GetID().Or(0) {
assert.True(t, listedAPIKeysAfterExpireByID[idx].GetExpiration().Value.Before(time.Now()),
"Key expired by ID should have expiration in the past")
}
}
// Test delete by ID (using key at index 1)
deletedKeyID := listedAPIKeysAfterExpireByID[1].GetId()
deletedKeyID := listedAPIKeysAfterExpireByID[1].GetID().Or(0)
_, err = headscale.Execute(
[]string{
"headscale",
@@ -237,7 +237,7 @@ func TestApiKeyCommand(t *testing.T) {
})
require.NoError(t, err)
var listedAPIKeysAfterDeleteByID []v1.ApiKey
var listedAPIKeysAfterDeleteByID []apiv1.ApiKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -257,7 +257,7 @@ func TestApiKeyCommand(t *testing.T) {
// Verify the specific key was deleted
for idx := range listedAPIKeysAfterDeleteByID {
assert.NotEqual(t, deletedKeyID, listedAPIKeysAfterDeleteByID[idx].GetId(),
assert.NotEqual(t, deletedKeyID, listedAPIKeysAfterDeleteByID[idx].GetID().Or(0),
"Deleted key should not be present in the list")
}
}
@@ -277,7 +277,7 @@ func TestApiKeyCommandValidation(t *testing.T) {
_, err := headscale.Execute([]string{"headscale", "apikeys", "create", "--output", "json"})
require.NoError(t, err)
var listed []v1.ApiKey
var listed []apiv1.ApiKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err := executeAndUnmarshal(headscale,
@@ -288,8 +288,8 @@ func TestApiKeyCommandValidation(t *testing.T) {
assert.Len(c, listed, 1)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for API key list")
prefix := listed[0].GetPrefix()
id := strconv.FormatUint(listed[0].GetId(), 10)
prefix := listed[0].GetPrefix().Or("")
id := strconv.FormatUint(listed[0].GetID().Or(0), 10)
tests := []struct {
name string
+1 -1
View File
@@ -8,7 +8,7 @@ import (
)
// TestAuthCommandValidation exercises the validation permutations of the auth
// subcommands over the gRPC transport: `register` against a non-existent user
// subcommands over the HTTP transport: `register` against a non-existent user
// and a malformed auth-id, and `approve`/`reject` against malformed and unknown
// auth-ids.
//
+94 -94
View File
@@ -7,7 +7,7 @@ import (
"testing"
"time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
apiv1 "github.com/juanfont/headscale/gen/api/v1"
policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/integration/hsic"
@@ -43,7 +43,7 @@ func TestNodeCommand(t *testing.T) {
types.MustAuthID().String(),
types.MustAuthID().String(),
}
nodes := make([]*v1.Node, len(regIDs))
nodes := make([]*apiv1.Node, len(regIDs))
require.NoError(t, err)
@@ -65,7 +65,7 @@ func TestNodeCommand(t *testing.T) {
)
require.NoError(t, err)
var node v1.Node
var node apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -94,7 +94,7 @@ func TestNodeCommand(t *testing.T) {
}, integrationutil.ScaledTimeout(15*time.Second), 1*time.Second)
// Test list all nodes after added seconds
var listAll []v1.Node
var listAll []apiv1.Node
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
err := executeAndUnmarshal(
@@ -112,23 +112,23 @@ func TestNodeCommand(t *testing.T) {
assert.Len(ct, listAll, len(regIDs), "Should list all nodes after CLI operations")
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
assert.Equal(t, uint64(1), listAll[0].GetId())
assert.Equal(t, uint64(2), listAll[1].GetId())
assert.Equal(t, uint64(3), listAll[2].GetId())
assert.Equal(t, uint64(4), listAll[3].GetId())
assert.Equal(t, uint64(5), listAll[4].GetId())
assert.Equal(t, uint64(1), listAll[0].GetID().Or(0))
assert.Equal(t, uint64(2), listAll[1].GetID().Or(0))
assert.Equal(t, uint64(3), listAll[2].GetID().Or(0))
assert.Equal(t, uint64(4), listAll[3].GetID().Or(0))
assert.Equal(t, uint64(5), listAll[4].GetID().Or(0))
assert.Equal(t, "node-1", listAll[0].GetName())
assert.Equal(t, "node-2", listAll[1].GetName())
assert.Equal(t, "node-3", listAll[2].GetName())
assert.Equal(t, "node-4", listAll[3].GetName())
assert.Equal(t, "node-5", listAll[4].GetName())
assert.Equal(t, "node-1", listAll[0].GetName().Or(""))
assert.Equal(t, "node-2", listAll[1].GetName().Or(""))
assert.Equal(t, "node-3", listAll[2].GetName().Or(""))
assert.Equal(t, "node-4", listAll[3].GetName().Or(""))
assert.Equal(t, "node-5", listAll[4].GetName().Or(""))
otherUserRegIDs := []string{
types.MustAuthID().String(),
types.MustAuthID().String(),
}
otherUserMachines := make([]*v1.Node, len(otherUserRegIDs))
otherUserMachines := make([]*apiv1.Node, len(otherUserRegIDs))
require.NoError(t, err)
@@ -150,7 +150,7 @@ func TestNodeCommand(t *testing.T) {
)
require.NoError(t, err)
var node v1.Node
var node apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -179,7 +179,7 @@ func TestNodeCommand(t *testing.T) {
}, integrationutil.ScaledTimeout(15*time.Second), 1*time.Second)
// Test list all nodes after added otherUser
var listAllWithotherUser []v1.Node
var listAllWithotherUser []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -199,14 +199,14 @@ func TestNodeCommand(t *testing.T) {
// All nodes, nodes + otherUser
assert.Len(t, listAllWithotherUser, 7)
assert.Equal(t, uint64(6), listAllWithotherUser[5].GetId())
assert.Equal(t, uint64(7), listAllWithotherUser[6].GetId())
assert.Equal(t, uint64(6), listAllWithotherUser[5].GetID().Or(0))
assert.Equal(t, uint64(7), listAllWithotherUser[6].GetID().Or(0))
assert.Equal(t, "otheruser-node-1", listAllWithotherUser[5].GetName())
assert.Equal(t, "otheruser-node-2", listAllWithotherUser[6].GetName())
assert.Equal(t, "otheruser-node-1", listAllWithotherUser[5].GetName().Or(""))
assert.Equal(t, "otheruser-node-2", listAllWithotherUser[6].GetName().Or(""))
// Test list all nodes after added otherUser
var listOnlyotherUserMachineUser []v1.Node
var listOnlyotherUserMachineUser []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -227,18 +227,18 @@ func TestNodeCommand(t *testing.T) {
assert.Len(t, listOnlyotherUserMachineUser, 2)
assert.Equal(t, uint64(6), listOnlyotherUserMachineUser[0].GetId())
assert.Equal(t, uint64(7), listOnlyotherUserMachineUser[1].GetId())
assert.Equal(t, uint64(6), listOnlyotherUserMachineUser[0].GetID().Or(0))
assert.Equal(t, uint64(7), listOnlyotherUserMachineUser[1].GetID().Or(0))
assert.Equal(
t,
"otheruser-node-1",
listOnlyotherUserMachineUser[0].GetName(),
listOnlyotherUserMachineUser[0].GetName().Or(""),
)
assert.Equal(
t,
"otheruser-node-2",
listOnlyotherUserMachineUser[1].GetName(),
listOnlyotherUserMachineUser[1].GetName().Or(""),
)
// Delete a nodes
@@ -258,7 +258,7 @@ func TestNodeCommand(t *testing.T) {
require.NoError(t, err)
// Test: list main user after node is deleted
var listOnlyMachineUserAfterDelete []v1.Node
var listOnlyMachineUserAfterDelete []apiv1.Node
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
err := executeAndUnmarshal(
@@ -304,7 +304,7 @@ func TestNodeExpireCommand(t *testing.T) {
types.MustAuthID().String(),
types.MustAuthID().String(),
}
nodes := make([]*v1.Node, len(regIDs))
nodes := make([]*apiv1.Node, len(regIDs))
for index, regID := range regIDs {
_, err := headscale.Execute(
@@ -324,7 +324,7 @@ func TestNodeExpireCommand(t *testing.T) {
)
require.NoError(t, err)
var node v1.Node
var node apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -350,7 +350,7 @@ func TestNodeExpireCommand(t *testing.T) {
assert.Len(t, nodes, len(regIDs))
var listAll []v1.Node
var listAll []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -372,7 +372,7 @@ func TestNodeExpireCommand(t *testing.T) {
// With node.expiry defaulting to 0, non-tagged nodes have zero expiry
// (never expire unless explicitly expired).
for i := range 5 {
assert.True(t, listAll[i].GetExpiry().AsTime().IsZero(),
assert.True(t, listAll[i].GetExpiry().Or(time.Time{}).IsZero(),
"node %d should have zero expiry (no default node.expiry)", i)
}
@@ -383,13 +383,13 @@ func TestNodeExpireCommand(t *testing.T) {
"nodes",
"expire",
"--identifier",
strconv.FormatUint(listAll[idx].GetId(), 10),
strconv.FormatUint(listAll[idx].GetID().Or(0), 10),
},
)
require.NoError(t, err)
}
var listAllAfterExpiry []v1.Node
var listAllAfterExpiry []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -408,11 +408,11 @@ func TestNodeExpireCommand(t *testing.T) {
assert.Len(t, listAllAfterExpiry, 5)
assert.True(t, listAllAfterExpiry[0].GetExpiry().AsTime().Before(time.Now()))
assert.True(t, listAllAfterExpiry[1].GetExpiry().AsTime().Before(time.Now()))
assert.True(t, listAllAfterExpiry[2].GetExpiry().AsTime().Before(time.Now()))
assert.True(t, listAllAfterExpiry[3].GetExpiry().AsTime().IsZero())
assert.True(t, listAllAfterExpiry[4].GetExpiry().AsTime().IsZero())
assert.True(t, listAllAfterExpiry[0].GetExpiry().Or(time.Time{}).Before(time.Now()))
assert.True(t, listAllAfterExpiry[1].GetExpiry().Or(time.Time{}).Before(time.Now()))
assert.True(t, listAllAfterExpiry[2].GetExpiry().Or(time.Time{}).Before(time.Now()))
assert.True(t, listAllAfterExpiry[3].GetExpiry().Or(time.Time{}).IsZero())
assert.True(t, listAllAfterExpiry[4].GetExpiry().Or(time.Time{}).IsZero())
}
func TestNodeRenameCommand(t *testing.T) {
@@ -440,7 +440,7 @@ func TestNodeRenameCommand(t *testing.T) {
types.MustAuthID().String(),
types.MustAuthID().String(),
}
nodes := make([]*v1.Node, len(regIDs))
nodes := make([]*apiv1.Node, len(regIDs))
require.NoError(t, err)
@@ -462,7 +462,7 @@ func TestNodeRenameCommand(t *testing.T) {
)
require.NoError(t, err)
var node v1.Node
var node apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -488,7 +488,7 @@ func TestNodeRenameCommand(t *testing.T) {
assert.Len(t, nodes, len(regIDs))
var listAll []v1.Node
var listAll []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -507,11 +507,11 @@ func TestNodeRenameCommand(t *testing.T) {
assert.Len(t, listAll, 5)
assert.Contains(t, listAll[0].GetGivenName(), "node-1")
assert.Contains(t, listAll[1].GetGivenName(), "node-2")
assert.Contains(t, listAll[2].GetGivenName(), "node-3")
assert.Contains(t, listAll[3].GetGivenName(), "node-4")
assert.Contains(t, listAll[4].GetGivenName(), "node-5")
assert.Contains(t, listAll[0].GetGivenName().Or(""), "node-1")
assert.Contains(t, listAll[1].GetGivenName().Or(""), "node-2")
assert.Contains(t, listAll[2].GetGivenName().Or(""), "node-3")
assert.Contains(t, listAll[3].GetGivenName().Or(""), "node-4")
assert.Contains(t, listAll[4].GetGivenName().Or(""), "node-5")
for idx := range 3 {
res, err := headscale.Execute(
@@ -520,7 +520,7 @@ func TestNodeRenameCommand(t *testing.T) {
"nodes",
"rename",
"--identifier",
strconv.FormatUint(listAll[idx].GetId(), 10),
strconv.FormatUint(listAll[idx].GetID().Or(0), 10),
fmt.Sprintf("newnode-%d", idx+1),
},
)
@@ -529,7 +529,7 @@ func TestNodeRenameCommand(t *testing.T) {
assert.Contains(t, res, "Node renamed")
}
var listAllAfterRename []v1.Node
var listAllAfterRename []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -548,11 +548,11 @@ func TestNodeRenameCommand(t *testing.T) {
assert.Len(t, listAllAfterRename, 5)
assert.Equal(t, "newnode-1", listAllAfterRename[0].GetGivenName())
assert.Equal(t, "newnode-2", listAllAfterRename[1].GetGivenName())
assert.Equal(t, "newnode-3", listAllAfterRename[2].GetGivenName())
assert.Contains(t, listAllAfterRename[3].GetGivenName(), "node-4")
assert.Contains(t, listAllAfterRename[4].GetGivenName(), "node-5")
assert.Equal(t, "newnode-1", listAllAfterRename[0].GetGivenName().Or(""))
assert.Equal(t, "newnode-2", listAllAfterRename[1].GetGivenName().Or(""))
assert.Equal(t, "newnode-3", listAllAfterRename[2].GetGivenName().Or(""))
assert.Contains(t, listAllAfterRename[3].GetGivenName().Or(""), "node-4")
assert.Contains(t, listAllAfterRename[4].GetGivenName().Or(""), "node-5")
// Test failure for too long names
_, err = headscale.Execute(
@@ -561,13 +561,13 @@ func TestNodeRenameCommand(t *testing.T) {
"nodes",
"rename",
"--identifier",
strconv.FormatUint(listAll[4].GetId(), 10),
strconv.FormatUint(listAll[4].GetID().Or(0), 10),
strings.Repeat("t", 64),
},
)
require.ErrorContains(t, err, "is too long, max length is 63 bytes")
var listAllAfterRenameAttempt []v1.Node
var listAllAfterRenameAttempt []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -586,11 +586,11 @@ func TestNodeRenameCommand(t *testing.T) {
assert.Len(t, listAllAfterRenameAttempt, 5)
assert.Equal(t, "newnode-1", listAllAfterRenameAttempt[0].GetGivenName())
assert.Equal(t, "newnode-2", listAllAfterRenameAttempt[1].GetGivenName())
assert.Equal(t, "newnode-3", listAllAfterRenameAttempt[2].GetGivenName())
assert.Contains(t, listAllAfterRenameAttempt[3].GetGivenName(), "node-4")
assert.Contains(t, listAllAfterRenameAttempt[4].GetGivenName(), "node-5")
assert.Equal(t, "newnode-1", listAllAfterRenameAttempt[0].GetGivenName().Or(""))
assert.Equal(t, "newnode-2", listAllAfterRenameAttempt[1].GetGivenName().Or(""))
assert.Equal(t, "newnode-3", listAllAfterRenameAttempt[2].GetGivenName().Or(""))
assert.Contains(t, listAllAfterRenameAttempt[3].GetGivenName().Or(""), "node-4")
assert.Contains(t, listAllAfterRenameAttempt[4].GetGivenName().Or(""), "node-5")
}
func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
@@ -623,7 +623,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
u2, err := headscale.CreateUser(user2)
require.NoError(t, err)
var user2Key v1.PreAuthKey
var user2Key apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -632,7 +632,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
strconv.FormatUint(u2.GetId(), 10),
strconv.FormatUint(u2.GetID().Or(0), 10),
"create",
"--reusable",
"--expiration",
@@ -647,7 +647,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for user2 preauth key creation")
var listNodes []*v1.Node
var listNodes []*apiv1.Node
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var err error
@@ -655,7 +655,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err)
assert.Len(ct, listNodes, 1, "Should have exactly 1 node for user1")
assert.Equal(ct, user1, listNodes[0].GetUser().GetName(), "Node should belong to user1")
assert.Equal(ct, user1, listNodes[0].GetUser().Value.Name.Or(""), "Node should belong to user1")
}, integrationutil.ScaledTimeout(15*time.Second), 1*time.Second)
allClients, err := scenario.ListTailscaleClients()
@@ -679,7 +679,7 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
"Expected node to be logged out, backend state: %s", status.BackendState)
}, integrationutil.StatusReadyTimeout, 2*time.Second)
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey())
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey().Or(""))
require.NoError(t, err)
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
@@ -697,9 +697,9 @@ func TestPreAuthKeyCorrectUserLoggedInCommand(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err)
assert.Len(ct, listNodes, 2, "Should have 2 nodes after re-login")
assert.Equal(ct, user1, listNodes[0].GetUser().GetName(), "First node should belong to user1")
assert.Equal(ct, user1, listNodes[0].GetUser().Value.Name.Or(""), "First node should belong to user1")
// Second node is tagged (created with tagged PreAuthKey), so it shows as "tagged-devices"
assert.Equal(ct, "tagged-devices", listNodes[1].GetUser().GetName(), "Second node should be tagged-devices")
assert.Equal(ct, "tagged-devices", listNodes[1].GetUser().Value.Name.Or(""), "Second node should be tagged-devices")
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
}
@@ -731,7 +731,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
u2, err := headscale.CreateUser(user2)
require.NoError(t, err)
var user2Key v1.PreAuthKey
var user2Key apiv1.PreAuthKey
// Create a tagged PreAuthKey for user2
assert.EventuallyWithT(t, func(c *assert.CollectT) {
@@ -741,7 +741,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
"headscale",
"preauthkeys",
"--user",
strconv.FormatUint(u2.GetId(), 10),
strconv.FormatUint(u2.GetID().Or(0), 10),
"create",
"--reusable",
"--expiration",
@@ -778,7 +778,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
}, integrationutil.StatusReadyTimeout, 2*time.Second)
// Log in with the tagged PreAuthKey (from user2, with tags)
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey())
err = client.Login(headscale.GetEndpoint(), user2Key.GetKey().Or(""))
require.NoError(t, err)
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
@@ -790,7 +790,7 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
}, integrationutil.StatusReadyTimeout, 2*time.Second)
// Wait for the second node to appear
var listNodes []*v1.Node
var listNodes []*apiv1.Node
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var err error
@@ -798,8 +798,8 @@ func TestTaggedNodesCLIOutput(t *testing.T) {
listNodes, err = headscale.ListNodes()
assert.NoError(ct, err)
assert.Len(ct, listNodes, 2, "Should have 2 nodes after re-login with tagged key")
assert.Equal(ct, user1, listNodes[0].GetUser().GetName(), "First node should belong to user1")
assert.Equal(ct, "tagged-devices", listNodes[1].GetUser().GetName(), "Second node should be tagged-devices")
assert.Equal(ct, user1, listNodes[0].GetUser().Value.Name.Or(""), "First node should belong to user1")
assert.Equal(ct, "tagged-devices", listNodes[1].GetUser().Value.Name.Or(""), "Second node should be tagged-devices")
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
// Test: tailscale status output should show "tagged-devices" not "userid:2147455555"
@@ -838,7 +838,7 @@ func TestNodeExpireFlagsCommand(t *testing.T) {
})
require.NoError(t, err)
var node v1.Node
var node apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err := executeAndUnmarshal(headscale,
@@ -853,14 +853,14 @@ func TestNodeExpireFlagsCommand(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for node registration")
nodeID := strconv.FormatUint(node.GetId(), 10)
nodeID := strconv.FormatUint(node.GetID().Or(0), 10)
// listNodeByID returns the node with the given id from `nodes list`. The
// expire mutations are verified by reading the node back (authoritative,
// eventually-consistent) rather than trusting the mutation's immediate
// response.
listNodeByID := func(ct *assert.CollectT) *v1.Node {
var nodes []v1.Node
listNodeByID := func(ct *assert.CollectT) *apiv1.Node {
var nodes []apiv1.Node
err := executeAndUnmarshal(headscale,
[]string{"headscale", "nodes", "list", "--output", "json"},
@@ -869,7 +869,7 @@ func TestNodeExpireFlagsCommand(t *testing.T) {
require.NoError(ct, err)
for i := range nodes {
if nodes[i].GetId() == node.GetId() {
if nodes[i].GetID().Or(0) == node.GetID().Or(0) {
return &nodes[i]
}
}
@@ -896,8 +896,8 @@ func TestNodeExpireFlagsCommand(t *testing.T) {
return
}
assert.False(ct, n.GetExpiry().AsTime().IsZero(), "expiry should be set")
assert.True(ct, n.GetExpiry().AsTime().After(time.Now()), "expiry should be in the future")
assert.False(ct, n.GetExpiry().Or(time.Time{}).IsZero(), "expiry should be set")
assert.True(ct, n.GetExpiry().Or(time.Time{}).After(time.Now()), "expiry should be in the future")
}, integrationutil.ScaledTimeout(15*time.Second), 1*time.Second, "Waiting for future expiry to apply")
// Disable expiry entirely; the node should then report no expiry.
@@ -919,7 +919,7 @@ func TestNodeExpireFlagsCommand(t *testing.T) {
// future — it never expires. A nil expiry deserialises to the Unix
// epoch rather than the zero time, so assert "not in the future"
// rather than IsZero.
assert.False(ct, n.GetExpiry().AsTime().After(time.Now()),
assert.False(ct, n.GetExpiry().Or(time.Time{}).After(time.Now()),
"disabled node should not have a future expiry")
}, integrationutil.ScaledTimeout(15*time.Second), 1*time.Second, "Waiting for --disable to clear expiry")
}
@@ -943,7 +943,7 @@ func TestNodeCommandValidation(t *testing.T) {
})
require.NoError(t, err)
var node v1.Node
var node apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err := executeAndUnmarshal(headscale,
@@ -953,7 +953,7 @@ func TestNodeCommandValidation(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for node registration")
id := strconv.FormatUint(node.GetId(), 10)
id := strconv.FormatUint(node.GetID().Or(0), 10)
// wantErr is matched with ErrorContains; an empty wantErr only requires
// that the command fails (used where the exact message is not load-bearing).
@@ -1049,8 +1049,8 @@ func TestNodeTagCommand(t *testing.T) {
assert.Len(ct, nodes, 1)
if len(nodes) == 1 {
nodeID = nodes[0].GetId()
assert.Equal(ct, "user1", nodes[0].GetUser().GetName(), "node should start user-owned")
nodeID = nodes[0].GetID().Or(0)
assert.Equal(ct, "user1", nodes[0].GetUser().Value.Name.Or(""), "node should start user-owned")
}
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
@@ -1059,13 +1059,13 @@ func TestNodeTagCommand(t *testing.T) {
// Set two tags. The command response is round-tripped (transport check);
// the resulting tag state is asserted via the authoritative list read-back
// below rather than the immediate mutation response.
tagged := assertJSONRoundtrip[*v1.Node](t, headscale, []string{
tagged := assertJSONRoundtrip[*apiv1.Node](t, headscale, []string{
"headscale", "nodes", "tag",
"--identifier", idStr,
"--tags", "tag:test1,tag:test2",
"--output", "json",
})
assert.Equal(t, nodeID, tagged.GetId(), "tag response should be for the same node")
assert.Equal(t, nodeID, tagged.GetID().Or(0), "tag response should be for the same node")
// The node is now a tagged node, presented as the tagged-devices user.
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
@@ -1074,7 +1074,7 @@ func TestNodeTagCommand(t *testing.T) {
assert.Len(ct, nodes, 1)
if len(nodes) == 1 {
assert.Equal(ct, "tagged-devices", nodes[0].GetUser().GetName(), "tagged node shows as tagged-devices")
assert.Equal(ct, "tagged-devices", nodes[0].GetUser().Value.Name.Or(""), "tagged node shows as tagged-devices")
assert.ElementsMatch(ct, []string{"tag:test1", "tag:test2"}, nodes[0].GetTags())
}
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
@@ -1138,7 +1138,7 @@ func TestNodeRouteCommands(t *testing.T) {
var nodeID uint64
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var nodes []v1.Node
var nodes []apiv1.Node
err := executeAndUnmarshal(headscale,
[]string{"headscale", "nodes", "list-routes", "--output", "json"},
@@ -1148,7 +1148,7 @@ func TestNodeRouteCommands(t *testing.T) {
assert.Len(ct, nodes, 1, "list-routes should show the route-advertising node")
if len(nodes) == 1 {
nodeID = nodes[0].GetId()
nodeID = nodes[0].GetID().Or(0)
assert.Contains(ct, nodes[0].GetAvailableRoutes(), route)
assert.Empty(ct, nodes[0].GetApprovedRoutes())
}
@@ -1157,7 +1157,7 @@ func TestNodeRouteCommands(t *testing.T) {
idStr := strconv.FormatUint(nodeID, 10)
// Approve the route via the CLI.
approved := assertJSONRoundtrip[*v1.Node](t, headscale, []string{
approved := assertJSONRoundtrip[*apiv1.Node](t, headscale, []string{
"headscale", "nodes", "approve-routes",
"--identifier", idStr,
"--routes=" + route,
@@ -1168,7 +1168,7 @@ func TestNodeRouteCommands(t *testing.T) {
// list-routes filtered by the identifier should report the approved route
// as a primary subnet route.
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var nodes []v1.Node
var nodes []apiv1.Node
err := executeAndUnmarshal(headscale,
[]string{"headscale", "nodes", "list-routes", "--identifier", idStr, "--output", "json"},
@@ -1184,7 +1184,7 @@ func TestNodeRouteCommands(t *testing.T) {
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
// Remove all approved routes by passing an empty --routes value.
cleared := assertJSONRoundtrip[*v1.Node](t, headscale, []string{
cleared := assertJSONRoundtrip[*apiv1.Node](t, headscale, []string{
"headscale", "nodes", "approve-routes",
"--identifier", idStr,
"--routes=",
@@ -1205,7 +1205,7 @@ func TestNodeBackfillIPsCommand(t *testing.T) {
require.NoError(t, scenario.WaitForTailscaleSync())
var before []*v1.Node
var before []*apiv1.Node
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
var err error
+36 -36
View File
@@ -5,7 +5,7 @@ import (
"testing"
"time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
apiv1 "github.com/juanfont/headscale/gen/api/v1"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/integrationutil"
"github.com/juanfont/headscale/integration/tsic"
@@ -34,12 +34,12 @@ func TestPreAuthKeyCommand(t *testing.T) {
headscale, err := scenario.Headscale()
require.NoError(t, err)
keys := make([]*v1.PreAuthKey, count)
keys := make([]*apiv1.PreAuthKey, count)
require.NoError(t, err)
for index := range count {
var preAuthKey v1.PreAuthKey
var preAuthKey apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err := executeAndUnmarshal(
@@ -68,7 +68,7 @@ func TestPreAuthKeyCommand(t *testing.T) {
assert.Len(t, keys, 3)
var listedPreAuthKeys []v1.PreAuthKey
var listedPreAuthKeys []apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -90,34 +90,34 @@ func TestPreAuthKeyCommand(t *testing.T) {
assert.Equal(
t,
[]uint64{keys[0].GetId(), keys[1].GetId(), keys[2].GetId()},
[]uint64{keys[0].GetID().Or(0), keys[1].GetID().Or(0), keys[2].GetID().Or(0)},
[]uint64{
listedPreAuthKeys[1].GetId(),
listedPreAuthKeys[2].GetId(),
listedPreAuthKeys[3].GetId(),
listedPreAuthKeys[1].GetID().Or(0),
listedPreAuthKeys[2].GetID().Or(0),
listedPreAuthKeys[3].GetID().Or(0),
},
)
// New keys show prefix after listing, so check the created keys instead
assert.NotEmpty(t, keys[0].GetKey())
assert.NotEmpty(t, keys[1].GetKey())
assert.NotEmpty(t, keys[2].GetKey())
assert.NotEmpty(t, keys[0].GetKey().Or(""))
assert.NotEmpty(t, keys[1].GetKey().Or(""))
assert.NotEmpty(t, keys[2].GetKey().Or(""))
assert.True(t, listedPreAuthKeys[1].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedPreAuthKeys[2].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedPreAuthKeys[3].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedPreAuthKeys[1].GetExpiration().Value.After(time.Now()))
assert.True(t, listedPreAuthKeys[2].GetExpiration().Value.After(time.Now()))
assert.True(t, listedPreAuthKeys[3].GetExpiration().Value.After(time.Now()))
assert.True(
t,
listedPreAuthKeys[1].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedPreAuthKeys[1].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
assert.True(
t,
listedPreAuthKeys[2].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedPreAuthKeys[2].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
assert.True(
t,
listedPreAuthKeys[3].GetExpiration().AsTime().Before(time.Now().Add(time.Hour*26)),
listedPreAuthKeys[3].GetExpiration().Value.Before(time.Now().Add(time.Hour*26)),
)
for index := range listedPreAuthKeys {
@@ -139,12 +139,12 @@ func TestPreAuthKeyCommand(t *testing.T) {
"preauthkeys",
"expire",
"--id",
strconv.FormatUint(keys[0].GetId(), 10),
strconv.FormatUint(keys[0].GetID().Or(0), 10),
},
)
require.NoError(t, err)
var listedPreAuthKeysAfterExpire []v1.PreAuthKey
var listedPreAuthKeysAfterExpire []apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -161,9 +161,9 @@ func TestPreAuthKeyCommand(t *testing.T) {
assert.NoError(c, err)
}, 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()))
assert.True(t, listedPreAuthKeysAfterExpire[3].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedPreAuthKeysAfterExpire[1].GetExpiration().Value.Before(time.Now()))
assert.True(t, listedPreAuthKeysAfterExpire[2].GetExpiration().Value.After(time.Now()))
assert.True(t, listedPreAuthKeysAfterExpire[3].GetExpiration().Value.After(time.Now()))
}
func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
@@ -185,7 +185,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
headscale, err := scenario.Headscale()
require.NoError(t, err)
var preAuthKey v1.PreAuthKey
var preAuthKey apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -205,7 +205,7 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth key creation without expiry")
var listedPreAuthKeys []v1.PreAuthKey
var listedPreAuthKeys []apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -225,10 +225,10 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
// There is one key created by [Scenario.CreateHeadscaleEnv]
assert.Len(t, listedPreAuthKeys, 2)
assert.True(t, listedPreAuthKeys[1].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedPreAuthKeys[1].GetExpiration().Value.After(time.Now()))
assert.True(
t,
listedPreAuthKeys[1].GetExpiration().AsTime().Before(time.Now().Add(time.Minute*70)),
listedPreAuthKeys[1].GetExpiration().Value.Before(time.Now().Add(time.Minute*70)),
)
}
@@ -251,7 +251,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
headscale, err := scenario.Headscale()
require.NoError(t, err)
var preAuthReusableKey v1.PreAuthKey
var preAuthReusableKey apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -271,7 +271,7 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for reusable preauth key creation")
var preAuthEphemeralKey v1.PreAuthKey
var preAuthEphemeralKey apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -291,10 +291,10 @@ func TestPreAuthKeyCommandReusableEphemeral(t *testing.T) {
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for ephemeral preauth key creation")
assert.True(t, preAuthEphemeralKey.GetEphemeral())
assert.False(t, preAuthEphemeralKey.GetReusable())
assert.True(t, preAuthEphemeralKey.GetEphemeral().Or(false))
assert.False(t, preAuthEphemeralKey.GetReusable().Or(false))
var listedPreAuthKeys []v1.PreAuthKey
var listedPreAuthKeys []apiv1.PreAuthKey
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
@@ -325,7 +325,7 @@ func TestPreAuthKeyDeleteCommand(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t)
// Create a key to delete.
created := assertJSONRoundtrip[*v1.PreAuthKey](t, headscale, []string{
created := assertJSONRoundtrip[*apiv1.PreAuthKey](t, headscale, []string{
"headscale",
"preauthkeys",
"--user", "1",
@@ -333,7 +333,7 @@ func TestPreAuthKeyDeleteCommand(t *testing.T) {
"--reusable",
"--output", "json",
})
require.NotZero(t, created.GetId())
require.NotZero(t, created.GetID().Or(0))
// delete with no --id must be rejected.
_, err := headscale.Execute([]string{"headscale", "preauthkeys", "delete"})
@@ -342,13 +342,13 @@ func TestPreAuthKeyDeleteCommand(t *testing.T) {
// delete the created key by id.
_, err = headscale.Execute([]string{
"headscale", "preauthkeys", "delete",
"--id", strconv.FormatUint(created.GetId(), 10),
"--id", strconv.FormatUint(created.GetID().Or(0), 10),
})
require.NoError(t, err)
// The deleted key must be gone from the list.
assert.EventuallyWithT(t, func(c *assert.CollectT) {
var listed []v1.PreAuthKey
var listed []apiv1.PreAuthKey
err := executeAndUnmarshal(headscale,
[]string{"headscale", "preauthkeys", "list", "--output", "json"},
@@ -357,7 +357,7 @@ func TestPreAuthKeyDeleteCommand(t *testing.T) {
assert.NoError(c, err)
for i := range listed {
assert.NotEqual(c, created.GetId(), listed[i].GetId(), "deleted key should not be listed")
assert.NotEqual(c, created.GetID().Or(0), listed[i].GetID().Or(0), "deleted key should not be listed")
}
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for preauth key list after delete")
}
+3 -3
View File
@@ -3,7 +3,7 @@ package integration
import (
"testing"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
apiv1 "github.com/juanfont/headscale/gen/api/v1"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -20,10 +20,10 @@ func TestServerInfoCommands(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t)
t.Run("health", func(t *testing.T) {
health := assertJSONRoundtrip[*v1.HealthResponse](t, headscale, []string{
health := assertJSONRoundtrip[*apiv1.HealthOK](t, headscale, []string{
"headscale", "health", "--output", "json",
})
assert.True(t, health.GetDatabaseConnectivity(), "database should be reachable")
assert.True(t, health.GetDatabaseConnectivity().Or(false), "database should be reachable")
})
t.Run("version", func(t *testing.T) {
+8 -123
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"testing"
apiv1 "github.com/juanfont/headscale/gen/api/v1"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/require"
@@ -19,8 +20,8 @@ import (
//
// The whole point of the CLI test suite is to guard the transport: every
// command is invoked with `--output json` and the result is unmarshalled into
// the matching gen/go/headscale/v1 Go type, so a change to the gRPC handlers,
// proto definitions or output encoders that breaks a command is caught here.
// the matching gen/api/v1 Go type, so a change to the API handlers or output
// encoders that breaks a command is caught here.
func executeAndUnmarshal[T any](headscale ControlServer, command []string, result T) error {
str, err := headscale.Execute(command)
@@ -62,14 +63,13 @@ func assertJSONRoundtrip[T any](t require.TestingT, headscale ControlServer, com
return second
}
// Interface ensuring that we can sort structs from gRPC that
// have an ID field.
type GRPCSortable interface {
GetId() uint64
// hasID is implemented by API structs with an ID field, so they can be sorted.
type hasID interface {
GetID() apiv1.OptUint64
}
func sortWithID[T GRPCSortable](a, b T) int {
return cmp.Compare(a.GetId(), b.GetId())
func sortWithID[T hasID](a, b T) int {
return cmp.Compare(a.GetID().Or(0), b.GetID().Or(0))
}
// setupCLIScenario boots a scenario with the given users and nodes-per-user,
@@ -97,118 +97,3 @@ func setupCLIScenario(t *testing.T, testName string, users []string, nodesPerUse
return scenario, headscale
}
// TestHealthCommand exercises the `headscale health` CLI command end-to-end.
// Until now only the raw /health HTTP endpoint was hit (WaitForRunning); the
// CLI's ogen client.Health() path had no coverage.
func TestHealthCommand(t *testing.T) {
IntegrationSkip(t)
spec := ScenarioSpec{
Users: []string{"health-user"},
}
scenario, err := NewScenario(spec)
require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t)
err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("cli-health"))
require.NoError(t, err)
headscale, err := scenario.Headscale()
require.NoError(t, err)
// JSON output decodes and reports the database as reachable.
var health apiv1.HealthOK
err = executeAndUnmarshal(
headscale,
[]string{"headscale", "health", "--output", "json"},
&health,
)
require.NoError(t, err)
assert.True(t, health.GetDatabaseConnectivity().Or(false), "database should be reachable")
// Default (non-JSON) output path also succeeds.
_, err = headscale.Execute([]string{"headscale", "health"})
require.NoError(t, err)
}
// TestNodeRoutesCommand covers `nodes list-routes` and `nodes backfillips`,
// neither of which had any integration coverage. Both go through the ogen HTTP
// client; list-routes also renders via the table path on HTTP-decoded data.
func TestNodeRoutesCommand(t *testing.T) {
IntegrationSkip(t)
spec := ScenarioSpec{
Users: []string{"routes-user"},
}
scenario, err := NewScenario(spec)
require.NoError(t, err)
defer scenario.ShutdownAssertNoPanics(t)
err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("cli-routes"))
require.NoError(t, err)
headscale, err := scenario.Headscale()
require.NoError(t, err)
regIDs := []string{
types.MustAuthID().String(),
types.MustAuthID().String(),
}
for index, regID := range regIDs {
_, err := headscale.Execute(
[]string{
"headscale", "debug", "create-node",
"--name", fmt.Sprintf("route-node-%d", index+1),
"--user", "routes-user",
"--key", regID,
"--output", "json",
},
)
require.NoError(t, err)
var node apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(
headscale,
[]string{
"headscale", "auth", "register",
"--user", "routes-user",
"--auth-id", regID,
"--output", "json",
},
&node,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "registering node")
}
// list-routes decodes over HTTP (json output).
var routeNodes []apiv1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err := executeAndUnmarshal(
headscale,
[]string{"headscale", "nodes", "list-routes", "--output", "json"},
&routeNodes,
)
assert.NoError(c, err)
}, integrationutil.ScaledTimeout(15*time.Second), 1*time.Second)
// list-routes also renders as a table (exercises nodeRoutesToPtables).
_, err = headscale.Execute([]string{"headscale", "nodes", "list-routes"})
require.NoError(t, err)
// backfillips runs non-interactively and decodes its change list.
_, err = headscale.Execute(
[]string{"headscale", "nodes", "backfillips", "--force", "--output", "json"},
)
require.NoError(t, err)
}
+35 -35
View File
@@ -9,7 +9,7 @@ import (
tcmp "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
apiv1 "github.com/juanfont/headscale/gen/api/v1"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/integrationutil"
"github.com/juanfont/headscale/integration/tsic"
@@ -36,7 +36,7 @@ func TestUserCommand(t *testing.T) {
require.NoError(t, err)
var (
listUsers []*v1.User
listUsers []*apiv1.User
result []string
)
@@ -54,7 +54,7 @@ func TestUserCommand(t *testing.T) {
assert.NoError(ct, err)
slices.SortFunc(listUsers, sortWithID)
result = []string{listUsers[0].GetName(), listUsers[1].GetName()}
result = []string{listUsers[0].GetName().Or(""), listUsers[1].GetName().Or("")}
assert.Equal(
ct,
@@ -70,13 +70,13 @@ func TestUserCommand(t *testing.T) {
"users",
"rename",
"--output=json",
fmt.Sprintf("--identifier=%d", listUsers[1].GetId()),
fmt.Sprintf("--identifier=%d", listUsers[1].GetID().Or(0)),
"--new-name=newname",
},
)
require.NoError(t, err)
var listAfterRenameUsers []*v1.User
var listAfterRenameUsers []*apiv1.User
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
err := executeAndUnmarshal(headscale,
@@ -92,7 +92,7 @@ func TestUserCommand(t *testing.T) {
assert.NoError(ct, err)
slices.SortFunc(listAfterRenameUsers, sortWithID)
result = []string{listAfterRenameUsers[0].GetName(), listAfterRenameUsers[1].GetName()}
result = []string{listAfterRenameUsers[0].GetName().Or(""), listAfterRenameUsers[1].GetName().Or("")}
assert.Equal(
ct,
@@ -102,7 +102,7 @@ func TestUserCommand(t *testing.T) {
)
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
var listByUsername []*v1.User
var listByUsername []*apiv1.User
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -121,19 +121,19 @@ func TestUserCommand(t *testing.T) {
slices.SortFunc(listByUsername, sortWithID)
want := []*v1.User{
want := []*apiv1.User{
{
Id: 1,
Name: "user1",
Email: "user1@test.no",
ID: apiv1.NewOptUint64(1),
Name: apiv1.NewOptString("user1"),
Email: apiv1.NewOptString("user1@test.no"),
},
}
if diff := tcmp.Diff(want, listByUsername, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
if diff := tcmp.Diff(want, listByUsername, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" {
t.Errorf("unexpected users (-want +got):\n%s", diff)
}
var listByID []*v1.User
var listByID []*apiv1.User
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -152,15 +152,15 @@ func TestUserCommand(t *testing.T) {
slices.SortFunc(listByID, sortWithID)
want = []*v1.User{
want = []*apiv1.User{
{
Id: 1,
Name: "user1",
Email: "user1@test.no",
ID: apiv1.NewOptUint64(1),
Name: apiv1.NewOptString("user1"),
Email: apiv1.NewOptString("user1@test.no"),
},
}
if diff := tcmp.Diff(want, listByID, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
if diff := tcmp.Diff(want, listByID, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" {
t.Errorf("unexpected users (-want +got):\n%s", diff)
}
@@ -177,7 +177,7 @@ func TestUserCommand(t *testing.T) {
require.NoError(t, err)
assert.Contains(t, deleteResult, "User destroyed")
var listAfterIDDelete []*v1.User
var listAfterIDDelete []*apiv1.User
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
err := executeAndUnmarshal(headscale,
@@ -194,15 +194,15 @@ func TestUserCommand(t *testing.T) {
slices.SortFunc(listAfterIDDelete, sortWithID)
want := []*v1.User{
want := []*apiv1.User{
{
Id: 2,
Name: "newname",
Email: "user2@test.no",
ID: apiv1.NewOptUint64(2),
Name: apiv1.NewOptString("newname"),
Email: apiv1.NewOptString("user2@test.no"),
},
}
if diff := tcmp.Diff(want, listAfterIDDelete, cmpopts.IgnoreUnexported(v1.User{}), cmpopts.IgnoreFields(v1.User{}, "CreatedAt")); diff != "" {
if diff := tcmp.Diff(want, listAfterIDDelete, cmpopts.IgnoreUnexported(apiv1.User{}), cmpopts.IgnoreFields(apiv1.User{}, "CreatedAt")); diff != "" {
assert.Fail(ct, "unexpected users", "diff (-want +got):\n%s", diff)
}
}, integrationutil.ScaledTimeout(20*time.Second), 1*time.Second)
@@ -219,7 +219,7 @@ func TestUserCommand(t *testing.T) {
require.NoError(t, err)
assert.Contains(t, deleteResult, "User destroyed")
var listAfterNameDelete []v1.User
var listAfterNameDelete []apiv1.User
assert.EventuallyWithT(t, func(c *assert.CollectT) {
err = executeAndUnmarshal(headscale,
@@ -249,8 +249,8 @@ func TestUserCreateCommand(t *testing.T) {
defer scenario.ShutdownAssertNoPanics(t)
// Create a user populated with every optional field. The created user is
// returned on stdout and round-tripped through the v1.User type.
created := assertJSONRoundtrip[*v1.User](t, headscale, []string{
// returned on stdout and round-tripped through the apiv1.User type.
created := assertJSONRoundtrip[*apiv1.User](t, headscale, []string{
"headscale",
"users",
"create",
@@ -261,14 +261,14 @@ func TestUserCreateCommand(t *testing.T) {
"--output", "json",
})
assert.Equal(t, "cli-created", created.GetName())
assert.Equal(t, "CLI Created", created.GetDisplayName())
assert.Equal(t, "cli-created@example.com", created.GetEmail())
assert.Equal(t, "https://example.com/avatar.png", created.GetProfilePicUrl())
assert.Equal(t, "cli-created", created.GetName().Or(""))
assert.Equal(t, "CLI Created", created.GetDisplayName().Or(""))
assert.Equal(t, "cli-created@example.com", created.GetEmail().Or(""))
assert.Equal(t, "https://example.com/avatar.png", created.GetProfilePicUrl().Or(""))
// The created fields must survive a list query (read-after-write) and be
// filterable by email.
var byEmail []*v1.User
var byEmail []*apiv1.User
assert.EventuallyWithT(t, func(ct *assert.CollectT) {
err := executeAndUnmarshal(headscale,
@@ -286,8 +286,8 @@ func TestUserCreateCommand(t *testing.T) {
}, integrationutil.ScaledTimeout(10*time.Second), integrationutil.FastPoll, "Waiting for user list by email")
require.Len(t, byEmail, 1)
assert.Equal(t, "cli-created", byEmail[0].GetName())
assert.Equal(t, "CLI Created", byEmail[0].GetDisplayName())
assert.Equal(t, "cli-created", byEmail[0].GetName().Or(""))
assert.Equal(t, "CLI Created", byEmail[0].GetDisplayName().Or(""))
}
// TestUserCommandValidation exercises the validation and error permutations of
@@ -325,7 +325,7 @@ func TestUserCommandValidation(t *testing.T) {
case tt.wantEmptyList:
require.NoError(t, err)
var users []v1.User
var users []apiv1.User
require.NoError(t, json.Unmarshal([]byte(out), &users))
require.Empty(t, users)