test(fleet): fix TestRouteRegistrationNoPanic compilation error
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run

- Use chi.Walk instead of len/range on chi.Routes interface
- chi.Routes returns an interface type, not a slice, which cannot
  be used with len() or range directly
- chi.Walk is the proper API for iterating registered routes

The test verifies that both Handler and FleetHandler can be registered
on the same router without chi panicking on duplicate routes.

Closes: bf-3o15x
This commit is contained in:
jedarden 2026-05-24 11:27:43 -04:00
parent 992368599d
commit 85a16e3152

View file

@ -15,10 +15,10 @@ import (
// mockNodeIdentifier is a mock implementation of NodeIdentifier for testing.
type mockNodeIdentifier struct {
sendIdentifyFunc func(mac string, durationMS int) bool
sendRebootFunc func(mac string, delayMS int) bool
getConnectedMACs func() []string
getUnpairedMACs func() []string
sendIdentifyFunc func(mac string, durationMS int) bool
sendRebootFunc func(mac string, delayMS int) bool
getConnectedMACs func() []string
getUnpairedMACs func() []string
}
func (m *mockNodeIdentifier) SendIdentifyToMAC(mac string, durationMS int) bool {
@ -162,13 +162,13 @@ func (m *mockRegistry) Close() error {
func TestHandlerIdentifyNode(t *testing.T) {
tests := []struct {
name string
mac string
reqBody string
nodeExists bool
nodeConnected bool
wantStatus int
wantResponse string
name string
mac string
reqBody string
nodeExists bool
nodeConnected bool
wantStatus int
wantResponse string
}{
{
name: "successful identify with default duration",
@ -292,28 +292,28 @@ func TestHandlerIdentifyNode(t *testing.T) {
func TestHandlerIdentifyNodeDurationParsing(t *testing.T) {
tests := []struct {
name string
reqBody string
name string
reqBody string
expectedDuration int
}{
{
name: "default duration when not specified",
reqBody: `{}`,
name: "default duration when not specified",
reqBody: `{}`,
expectedDuration: 5000,
},
{
name: "custom duration",
reqBody: `{"duration_ms": 10000}`,
name: "custom duration",
reqBody: `{"duration_ms": 10000}`,
expectedDuration: 10000,
},
{
name: "zero uses default",
reqBody: `{"duration_ms": 0}`,
name: "zero uses default",
reqBody: `{"duration_ms": 0}`,
expectedDuration: 5000,
},
{
name: "negative uses default",
reqBody: `{"duration_ms": -1000}`,
name: "negative uses default",
reqBody: `{"duration_ms": -1000}`,
expectedDuration: 5000,
},
}
@ -477,14 +477,14 @@ func TestHandlerSetSystemMode(t *testing.T) {
expectedMode: "away",
},
{
name: "invalid mode",
requestBody: `{"mode": "invalid"}`,
wantStatus: http.StatusBadRequest,
name: "invalid mode",
requestBody: `{"mode": "invalid"}`,
wantStatus: http.StatusBadRequest,
},
{
name: "invalid json",
requestBody: `invalid json`,
wantStatus: http.StatusBadRequest,
name: "invalid json",
requestBody: `invalid json`,
wantStatus: http.StatusBadRequest,
},
}
@ -745,12 +745,12 @@ func TestHandlerUpdateNodeLabel(t *testing.T) {
func TestHandlerSetNodeRole(t *testing.T) {
tests := []struct {
name string
mac string
requestBody string
nodeExists bool
wantStatus int
expectedRole string
name string
mac string
requestBody string
nodeExists bool
wantStatus int
expectedRole string
}{
{
name: "successful role change to tx",
@ -898,12 +898,12 @@ func TestHandlerDeleteNode(t *testing.T) {
func TestHandlerTriggerNodeOTA(t *testing.T) {
tests := []struct {
name string
mac string
requestBody string
nodeExists bool
otaAvailable bool
wantStatus int
name string
mac string
requestBody string
nodeExists bool
otaAvailable bool
wantStatus int
}{
{
name: "successful OTA trigger",
@ -1089,10 +1089,10 @@ func TestHandlerRebootNode(t *testing.T) {
func TestHandlerUpdateAllNodes(t *testing.T) {
tests := []struct {
name string
connectedMACs []string
wantStatus int
expectedCount int
name string
connectedMACs []string
wantStatus int
expectedCount int
}{
{
name: "update all connected nodes",
@ -1387,15 +1387,15 @@ func TestHandlerUpdateNodePosition(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reg := newTestRegistry(t);
reg.UpsertNode("AA:BB:CC:DD:EE:FF", "1.0.0", "ESP32-S3");
reg.SetNodeLabel("AA:BB:CC:DD:EE:FF", "Test Node");
reg.SetNodeRole("AA:BB:CC:DD:EE:FF", "rx");
reg.SetNodePosition("AA:BB:CC:DD:EE:FF", 0, 0, 0);
reg := newTestRegistry(t)
reg.UpsertNode("AA:BB:CC:DD:EE:FF", "1.0.0", "ESP32-S3")
reg.SetNodeLabel("AA:BB:CC:DD:EE:FF", "Test Node")
reg.SetNodeRole("AA:BB:CC:DD:EE:FF", "rx")
reg.SetNodePosition("AA:BB:CC:DD:EE:FF", 0, 0, 0)
mgr := NewManager(reg);
mgr := NewManager(reg)
h := &Handler{mgr: mgr}
h := &Handler{mgr: mgr}
req := httptest.NewRequest("PUT", "/api/nodes/"+tt.mac+"/position", bytes.NewBufferString(tt.requestBody))
req.Header.Set("Content-Type", "application/json")
@ -1410,17 +1410,17 @@ func TestHandlerUpdateNodePosition(t *testing.T) {
t.Errorf("updateNodePosition() status = %v, want %v", w.Code, tt.wantStatus)
}
if tt.wantStatus == http.StatusNoContent {
// Verify the position was updated
node, err := reg.GetNode(tt.mac)
if err != nil {
t.Errorf("Failed to get node: %v", err)
} else if node.PosX != tt.expectedX || node.PosY != tt.expectedY || node.PosZ != tt.expectedZ {
t.Errorf("Expected position to be (%v, %v, %v), got (%v, %v, %v)",
tt.expectedX, tt.expectedY, tt.expectedZ,
node.PosX, node.PosY, node.PosZ)
}
if tt.wantStatus == http.StatusNoContent {
// Verify the position was updated
node, err := reg.GetNode(tt.mac)
if err != nil {
t.Errorf("Failed to get node: %v", err)
} else if node.PosX != tt.expectedX || node.PosY != tt.expectedY || node.PosZ != tt.expectedZ {
t.Errorf("Expected position to be (%v, %v, %v), got (%v, %v, %v)",
tt.expectedX, tt.expectedY, tt.expectedZ,
node.PosX, node.PosY, node.PosZ)
}
}
})
}
}
@ -1434,14 +1434,14 @@ func TestFleetTableRendering(t *testing.T) {
// Create 4 test nodes with different configurations
testNodes := []struct {
mac string
name string
role string
firmware string
chip string
x, y, z float64
health float64
online bool
mac string
name string
role string
firmware string
chip string
x, y, z float64
health float64
online bool
}{
{
mac: "AA:BB:CC:DD:EE:01",
@ -2196,13 +2196,13 @@ func TestFleetListUnpairedNotInRegistry(t *testing.T) {
func TestHandlerDisableNode(t *testing.T) {
tests := []struct {
name string
mac string
initialRole string
nodeExists bool
wantStatus int
expectedRole string
expectedPrior string
name string
mac string
initialRole string
nodeExists bool
wantStatus int
expectedRole string
expectedPrior string
}{
{
name: "successful disable from tx",
@ -2308,14 +2308,14 @@ func TestHandlerDisableNode(t *testing.T) {
func TestHandlerEnableNode(t *testing.T) {
tests := []struct {
name string
mac string
initialRole string
savedPriorRole string
nodeExists bool
wantStatus int
expectedRole string
expectedNote string
name string
mac string
initialRole string
savedPriorRole string
nodeExists bool
wantStatus int
expectedRole string
expectedNote string
}{
{
name: "successful enable from idle with saved prior role",
@ -2533,32 +2533,32 @@ func TestRouteRegistrationNoPanic(t *testing.T) {
// From FleetHandler: /api/fleet/health, /api/fleet/history, /api/fleet/optimise, /api/fleet/simulate
// The key is no panic on duplicate routes like POST /api/nodes/{mac}/role
// Try to walk the routes to ensure they're registered
routes := chi.Routes(r)
if len(routes) == 0 {
t.Fatal("No routes registered")
}
// Walk the routes to ensure they're registered
expectedRoutes := make(map[string]bool)
expectedRoutes["/api/nodes"] = false
expectedRoutes["/api/fleet"] = false
expectedRoutes["/api/fleet/health"] = false
expectedRoutes["/api/fleet/history"] = false
expectedRoutes["/api/fleet/optimise"] = false
expectedRoutes["/api/fleet/simulate"] = false
// Verify some expected routes exist
expectedRoutes := []string{
"/api/nodes",
"/api/fleet",
"/api/fleet/health",
"/api/fleet/history",
"/api/fleet/optimise",
"/api/fleet/simulate",
}
for _, expected := range expectedRoutes {
found := false
for _, route := range routes {
if route.Pattern == expected {
found = true
break
// Use chi.Walk to iterate over all registered routes
err := chi.Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
for pattern := range expectedRoutes {
if route == pattern {
expectedRoutes[pattern] = true
}
}
return nil
})
if err != nil {
t.Fatalf("Failed to walk routes: %v", err)
}
// Check that all expected routes were found
for pattern, found := range expectedRoutes {
if !found {
t.Errorf("Expected route %s not found", expected)
t.Errorf("Expected route %s not found", pattern)
}
}
}