headscale/integration/routes_cli_test.go
Kristoffer Dalby 8253d588c6 derp
2025-07-15 14:51:23 +00:00

310 lines
6.9 KiB
Go

package integration
import (
"encoding/json"
"fmt"
"testing"
"time"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/integration/hsic"
"github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRouteCommand(t *testing.T) {
IntegrationSkip(t)
spec := ScenarioSpec{
Users: []string{"route-user"},
NodesPerUser: 1,
}
scenario, err := NewScenario(spec)
assertNoErr(t, err)
defer scenario.ShutdownAssertNoPanics(t)
err = scenario.CreateHeadscaleEnv(
[]tsic.Option{tsic.WithAcceptRoutes()},
hsic.WithTestName("cliroutes"),
)
assertNoErr(t, err)
headscale, err := scenario.Headscale()
assertNoErr(t, err)
// Wait for setup to complete
err = scenario.WaitForTailscaleSync()
assertNoErr(t, err)
// Wait for node to be registered
assert.EventuallyWithT(t, func(c *assert.CollectT) {
var listNodes []*v1.Node
err := executeAndUnmarshal(headscale,
[]string{
"headscale",
"nodes",
"list",
"--output",
"json",
},
&listNodes,
)
assert.NoError(c, err)
assert.Len(c, listNodes, 1)
}, 30*time.Second, 1*time.Second)
// Get the node ID for route operations
var listNodes []*v1.Node
err = executeAndUnmarshal(headscale,
[]string{
"headscale",
"nodes",
"list",
"--output",
"json",
},
&listNodes,
)
assertNoErr(t, err)
require.Len(t, listNodes, 1)
nodeID := listNodes[0].GetId()
t.Run("test_route_advertisement", func(t *testing.T) {
// Get the first tailscale client
allClients, err := scenario.ListTailscaleClients()
assertNoErr(t, err)
require.NotEmpty(t, allClients, "should have at least one client")
client := allClients[0]
// Advertise a route
_, _, err = client.Execute([]string{
"tailscale",
"set",
"--advertise-routes=10.0.0.0/24",
})
assertNoErr(t, err)
// Wait for route to appear in Headscale
assert.EventuallyWithT(t, func(c *assert.CollectT) {
var updatedNodes []*v1.Node
err := executeAndUnmarshal(headscale,
[]string{
"headscale",
"nodes",
"list",
"--output",
"json",
},
&updatedNodes,
)
assert.NoError(c, err)
assert.Len(c, updatedNodes, 1)
assert.Greater(c, len(updatedNodes[0].GetAvailableRoutes()), 0, "node should have available routes")
}, 30*time.Second, 1*time.Second)
})
t.Run("test_route_approval", func(t *testing.T) {
// List available routes
_, err := headscale.Execute(
[]string{
"headscale",
"nodes",
"list-routes",
"--node",
fmt.Sprintf("%d", nodeID),
},
)
assertNoErr(t, err)
// Approve a route
_, err = headscale.Execute(
[]string{
"headscale",
"nodes",
"approve-routes",
"--node",
fmt.Sprintf("%d", nodeID),
"--routes",
"10.0.0.0/24",
},
)
assertNoErr(t, err)
// Verify route is approved
assert.EventuallyWithT(t, func(c *assert.CollectT) {
var updatedNodes []*v1.Node
err := executeAndUnmarshal(headscale,
[]string{
"headscale",
"nodes",
"list",
"--output",
"json",
},
&updatedNodes,
)
assert.NoError(c, err)
assert.Len(c, updatedNodes, 1)
assert.Contains(c, updatedNodes[0].GetApprovedRoutes(), "10.0.0.0/24", "route should be approved")
}, 30*time.Second, 1*time.Second)
})
t.Run("test_route_removal", func(t *testing.T) {
// Remove approved routes
_, err := headscale.Execute(
[]string{
"headscale",
"nodes",
"approve-routes",
"--node",
fmt.Sprintf("%d", nodeID),
"--routes",
"", // Empty string removes all routes
},
)
assertNoErr(t, err)
// Verify routes are removed
assert.EventuallyWithT(t, func(c *assert.CollectT) {
var updatedNodes []*v1.Node
err := executeAndUnmarshal(headscale,
[]string{
"headscale",
"nodes",
"list",
"--output",
"json",
},
&updatedNodes,
)
assert.NoError(c, err)
assert.Len(c, updatedNodes, 1)
assert.Empty(c, updatedNodes[0].GetApprovedRoutes(), "approved routes should be empty")
}, 30*time.Second, 1*time.Second)
})
t.Run("test_route_json_output", func(t *testing.T) {
// Test JSON output for route commands
result, err := headscale.Execute(
[]string{
"headscale",
"nodes",
"list-routes",
"--node",
fmt.Sprintf("%d", nodeID),
"--output",
"json",
},
)
assertNoErr(t, err)
// Verify JSON output is valid
var routes interface{}
err = json.Unmarshal([]byte(result), &routes)
assert.NoError(t, err, "route command should produce valid JSON output")
})
}
func TestRouteCommandEdgeCases(t *testing.T) {
IntegrationSkip(t)
spec := ScenarioSpec{
Users: []string{"route-test-user"},
}
scenario, err := NewScenario(spec)
assertNoErr(t, err)
defer scenario.ShutdownAssertNoPanics(t)
err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("cliroutesedge"))
assertNoErr(t, err)
headscale, err := scenario.Headscale()
assertNoErr(t, err)
t.Run("test_route_commands_with_invalid_node", func(t *testing.T) {
// Test route commands with non-existent node ID
_, err := headscale.Execute(
[]string{
"headscale",
"nodes",
"list-routes",
"--node",
"999999",
},
)
// Should handle error gracefully
assert.Error(t, err, "should fail for non-existent node")
})
t.Run("test_route_approval_invalid_routes", func(t *testing.T) {
// Test route approval with invalid CIDR
_, err := headscale.Execute(
[]string{
"headscale",
"nodes",
"approve-routes",
"--node",
"1",
"--routes",
"invalid-cidr",
},
)
// Should handle invalid CIDR gracefully
assert.Error(t, err, "should fail for invalid CIDR")
})
}
func TestRouteCommandHelp(t *testing.T) {
IntegrationSkip(t)
spec := ScenarioSpec{
Users: []string{"help-user"},
}
scenario, err := NewScenario(spec)
assertNoErr(t, err)
defer scenario.ShutdownAssertNoPanics(t)
err = scenario.CreateHeadscaleEnv([]tsic.Option{}, hsic.WithTestName("cliroutehelp"))
assertNoErr(t, err)
headscale, err := scenario.Headscale()
assertNoErr(t, err)
t.Run("test_list_routes_help", func(t *testing.T) {
result, err := headscale.Execute(
[]string{
"headscale",
"nodes",
"list-routes",
"--help",
},
)
assertNoErr(t, err)
// Verify help text contains expected information
assert.Contains(t, result, "list-routes", "help should mention list-routes command")
assert.Contains(t, result, "node", "help should mention node flag")
})
t.Run("test_approve_routes_help", func(t *testing.T) {
result, err := headscale.Execute(
[]string{
"headscale",
"nodes",
"approve-routes",
"--help",
},
)
assertNoErr(t, err)
// Verify help text contains expected information
assert.Contains(t, result, "approve-routes", "help should mention approve-routes command")
assert.Contains(t, result, "node", "help should mention node flag")
assert.Contains(t, result, "routes", "help should mention routes flag")
})
}