test: Add tests for API helper functions

- isRecentlyAutoRegistered: expiry logic, cleanup behavior
- clearAutoRegistered: key deletion, empty input handling
- normalizeDockerAgentArch: all arch variants, case handling, trimming
This commit is contained in:
rcourtman 2025-12-01 21:33:45 +00:00
parent 80702a51bf
commit 3233c48986
2 changed files with 266 additions and 0 deletions

View file

@ -0,0 +1,196 @@
package api
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
func TestIsRecentlyAutoRegistered(t *testing.T) {
t.Parallel()
tests := []struct {
name string
nodeType string
nodeName string
setup func(*ConfigHandlers)
want bool
checkGone bool // verify entry was cleaned up
}{
{
name: "empty nodeType returns false",
nodeType: "",
nodeName: "node1",
setup: nil,
want: false,
},
{
name: "empty nodeName returns false",
nodeType: "pve",
nodeName: "",
setup: nil,
want: false,
},
{
name: "key not in map returns false",
nodeType: "pve",
nodeName: "nonexistent",
setup: nil,
want: false,
},
{
name: "recent registration returns true",
nodeType: "pve",
nodeName: "node1",
setup: func(h *ConfigHandlers) {
h.recentAutoRegistered["pve:node1"] = time.Now()
},
want: true,
},
{
name: "expired registration returns false and cleans up",
nodeType: "pve",
nodeName: "node1",
setup: func(h *ConfigHandlers) {
h.recentAutoRegistered["pve:node1"] = time.Now().Add(-3 * time.Minute)
},
want: false,
checkGone: true,
},
{
name: "different keys are independent",
nodeType: "pbs",
nodeName: "node2",
setup: func(h *ConfigHandlers) {
h.recentAutoRegistered["pve:node1"] = time.Now()
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
cfg := &config.Config{
DataPath: t.TempDir(),
ConfigPath: t.TempDir(),
}
h := newTestConfigHandlers(t, cfg)
if tt.setup != nil {
tt.setup(h)
}
got := h.isRecentlyAutoRegistered(tt.nodeType, tt.nodeName)
if got != tt.want {
t.Errorf("isRecentlyAutoRegistered(%q, %q) = %v, want %v", tt.nodeType, tt.nodeName, got, tt.want)
}
if tt.checkGone {
key := tt.nodeType + ":" + tt.nodeName
h.recentAutoRegMutex.Lock()
_, exists := h.recentAutoRegistered[key]
h.recentAutoRegMutex.Unlock()
if exists {
t.Errorf("expected key %q to be cleaned up, but it still exists", key)
}
}
})
}
}
func TestClearAutoRegistered(t *testing.T) {
t.Parallel()
tests := []struct {
name string
nodeType string
nodeName string
setup func(*ConfigHandlers)
check func(*testing.T, *ConfigHandlers)
}{
{
name: "empty nodeType is safe",
nodeType: "",
nodeName: "node1",
setup: func(h *ConfigHandlers) {
h.recentAutoRegistered["pve:node1"] = time.Now()
},
check: func(t *testing.T, h *ConfigHandlers) {
h.recentAutoRegMutex.Lock()
defer h.recentAutoRegMutex.Unlock()
if _, exists := h.recentAutoRegistered["pve:node1"]; !exists {
t.Error("expected pve:node1 to still exist")
}
},
},
{
name: "empty nodeName is safe",
nodeType: "pve",
nodeName: "",
setup: func(h *ConfigHandlers) {
h.recentAutoRegistered["pve:node1"] = time.Now()
},
check: func(t *testing.T, h *ConfigHandlers) {
h.recentAutoRegMutex.Lock()
defer h.recentAutoRegMutex.Unlock()
if _, exists := h.recentAutoRegistered["pve:node1"]; !exists {
t.Error("expected pve:node1 to still exist")
}
},
},
{
name: "successfully clears existing entry",
nodeType: "pve",
nodeName: "node1",
setup: func(h *ConfigHandlers) {
h.recentAutoRegistered["pve:node1"] = time.Now()
},
check: func(t *testing.T, h *ConfigHandlers) {
h.recentAutoRegMutex.Lock()
defer h.recentAutoRegMutex.Unlock()
if _, exists := h.recentAutoRegistered["pve:node1"]; exists {
t.Error("expected pve:node1 to be cleared")
}
},
},
{
name: "non-existent key does not error",
nodeType: "pve",
nodeName: "nonexistent",
setup: nil,
check: func(t *testing.T, h *ConfigHandlers) {
// Just verify no panic occurred and map is still valid
h.recentAutoRegMutex.Lock()
defer h.recentAutoRegMutex.Unlock()
if h.recentAutoRegistered == nil {
t.Error("map should not be nil")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
cfg := &config.Config{
DataPath: t.TempDir(),
ConfigPath: t.TempDir(),
}
h := newTestConfigHandlers(t, cfg)
if tt.setup != nil {
tt.setup(h)
}
h.clearAutoRegistered(tt.nodeType, tt.nodeName)
if tt.check != nil {
tt.check(t, h)
}
})
}
}

View file

@ -0,0 +1,70 @@
package api
import "testing"
func TestNormalizeDockerAgentArch(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
// Empty string
{"empty string", "", ""},
// amd64 variants
{"linux-amd64", "linux-amd64", "linux-amd64"},
{"amd64", "amd64", "linux-amd64"},
{"x86_64", "x86_64", "linux-amd64"},
{"x86-64", "x86-64", "linux-amd64"},
// arm64 variants
{"linux-arm64", "linux-arm64", "linux-arm64"},
{"arm64", "arm64", "linux-arm64"},
{"aarch64", "aarch64", "linux-arm64"},
// armv7 variants
{"linux-armv7", "linux-armv7", "linux-armv7"},
{"armv7", "armv7", "linux-armv7"},
{"armv7l", "armv7l", "linux-armv7"},
{"armhf", "armhf", "linux-armv7"},
// armv6 variants
{"linux-armv6", "linux-armv6", "linux-armv6"},
{"armv6", "armv6", "linux-armv6"},
{"armv6l", "armv6l", "linux-armv6"},
// 386 variants
{"linux-386", "linux-386", "linux-386"},
{"386", "386", "linux-386"},
{"i386", "i386", "linux-386"},
{"i686", "i686", "linux-386"},
// Unknown architecture
{"unknown arch", "unknown", ""},
{"invalid arch", "powerpc", ""},
{"mips", "mips", ""},
// Case insensitivity
{"AMD64 uppercase", "AMD64", "linux-amd64"},
{"X86_64 uppercase", "X86_64", "linux-amd64"},
{"AARCH64 uppercase", "AARCH64", "linux-arm64"},
{"ARMV7 uppercase", "ARMV7", "linux-armv7"},
{"mixed case LinuX-AmD64", "LinuX-AmD64", "linux-amd64"},
// Whitespace trimming
{"leading space", " amd64", "linux-amd64"},
{"trailing space", "amd64 ", "linux-amd64"},
{"both spaces", " amd64 ", "linux-amd64"},
{"tabs", "\tamd64\t", "linux-amd64"},
{"mixed whitespace", " \t arm64 \t ", "linux-arm64"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := normalizeDockerAgentArch(tt.input)
if result != tt.expected {
t.Errorf("normalizeDockerAgentArch(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}