From 3233c48986b996e68a4110b1bf68ac39a069378c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 1 Dec 2025 21:33:45 +0000 Subject: [PATCH] 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 --- internal/api/config_handlers_auto_reg_test.go | 196 ++++++++++++++++++ internal/api/router_arch_test.go | 70 +++++++ 2 files changed, 266 insertions(+) create mode 100644 internal/api/config_handlers_auto_reg_test.go create mode 100644 internal/api/router_arch_test.go diff --git a/internal/api/config_handlers_auto_reg_test.go b/internal/api/config_handlers_auto_reg_test.go new file mode 100644 index 0000000..7e5545e --- /dev/null +++ b/internal/api/config_handlers_auto_reg_test.go @@ -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) + } + }) + } +} diff --git a/internal/api/router_arch_test.go b/internal/api/router_arch_test.go new file mode 100644 index 0000000..fbb04e7 --- /dev/null +++ b/internal/api/router_arch_test.go @@ -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) + } + }) + } +}