mirror of
https://github.com/juanfont/headscale.git
synced 2026-08-15 03:40:44 +09:00
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:
+8
-123
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user