Add unit tests for discovery package utility functions

Test coverage for pure utility functions: friendlyPhaseName,
defaultProductsForPort, cloneHeader, copyMetadata, and
ensurePolicyDefaults.
This commit is contained in:
rcourtman 2025-11-30 16:05:11 +00:00
parent 2620aa5db5
commit 426e583533

View file

@ -465,3 +465,371 @@ func TestPBSDiscoveryWithUnauthorizedVersion(t *testing.T) {
t.Errorf("Expected product %q, got %q", ProductPBS, probe.PrimaryProduct)
}
}
func TestFriendlyPhaseName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
phase string
expected string
}{
{"lxc container network", "lxc_container_network", "Container network"},
{"docker bridge network", "docker_bridge_network", "Docker bridge network"},
{"docker container network", "docker_container_network", "Docker container network"},
{"host local network", "host_local_network", "Local network"},
{"inferred gateway network", "inferred_gateway_network", "Gateway network"},
{"extra targets", "extra_targets", "Additional targets"},
{"proxmox cluster network", "proxmox_cluster_network", "Proxmox cluster network"},
{"unknown phase returns as-is", "some_unknown_phase", "some_unknown_phase"},
{"empty string returns empty", "", ""},
{"manual subnet passthrough", "manual_subnet", "manual_subnet"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
result := friendlyPhaseName(tc.phase)
if result != tc.expected {
t.Errorf("friendlyPhaseName(%q) = %q, want %q", tc.phase, result, tc.expected)
}
})
}
}
func TestDefaultProductsForPort(t *testing.T) {
t.Parallel()
tests := []struct {
name string
port int
expected []string
}{
{"port 8006 returns PVE and PMG", 8006, []string{productPVE, productPMG}},
{"port 8007 returns PBS", 8007, []string{productPBS}},
{"port 443 returns nil", 443, nil},
{"port 80 returns nil", 80, nil},
{"port 0 returns nil", 0, nil},
{"random port returns nil", 12345, nil},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
result := defaultProductsForPort(tc.port)
if tc.expected == nil {
if result != nil {
t.Errorf("defaultProductsForPort(%d) = %v, want nil", tc.port, result)
}
return
}
if len(result) != len(tc.expected) {
t.Errorf("defaultProductsForPort(%d) = %v, want %v", tc.port, result, tc.expected)
return
}
for i, v := range tc.expected {
if result[i] != v {
t.Errorf("defaultProductsForPort(%d)[%d] = %q, want %q", tc.port, i, result[i], v)
}
}
})
}
}
func TestCloneHeader(t *testing.T) {
t.Parallel()
t.Run("nil input returns nil", func(t *testing.T) {
result := cloneHeader(nil)
if result != nil {
t.Errorf("cloneHeader(nil) = %v, want nil", result)
}
})
t.Run("empty header returns empty header", func(t *testing.T) {
input := http.Header{}
result := cloneHeader(input)
if result == nil {
t.Fatal("cloneHeader(empty) returned nil")
}
if len(result) != 0 {
t.Errorf("cloneHeader(empty) has length %d, want 0", len(result))
}
})
t.Run("clones single-value headers", func(t *testing.T) {
input := http.Header{
"Content-Type": []string{"application/json"},
"Authorization": []string{"Bearer token123"},
}
result := cloneHeader(input)
if result.Get("Content-Type") != "application/json" {
t.Errorf("Content-Type = %q, want %q", result.Get("Content-Type"), "application/json")
}
if result.Get("Authorization") != "Bearer token123" {
t.Errorf("Authorization = %q, want %q", result.Get("Authorization"), "Bearer token123")
}
})
t.Run("clones multi-value headers", func(t *testing.T) {
input := http.Header{
"Set-Cookie": []string{"cookie1=val1", "cookie2=val2", "cookie3=val3"},
}
result := cloneHeader(input)
cookies := result.Values("Set-Cookie")
if len(cookies) != 3 {
t.Fatalf("Set-Cookie count = %d, want 3", len(cookies))
}
if cookies[0] != "cookie1=val1" || cookies[1] != "cookie2=val2" || cookies[2] != "cookie3=val3" {
t.Errorf("Set-Cookie values = %v, want [cookie1=val1 cookie2=val2 cookie3=val3]", cookies)
}
})
t.Run("clone is independent of original", func(t *testing.T) {
input := http.Header{
"X-Custom": []string{"original"},
}
result := cloneHeader(input)
// Modify original
input.Set("X-Custom", "modified")
// Clone should be unaffected
if result.Get("X-Custom") != "original" {
t.Errorf("clone was affected by original modification: got %q, want %q", result.Get("X-Custom"), "original")
}
})
t.Run("original is independent of clone", func(t *testing.T) {
input := http.Header{
"X-Custom": []string{"original"},
}
result := cloneHeader(input)
// Modify clone
result.Set("X-Custom", "modified")
// Original should be unaffected
if input.Get("X-Custom") != "original" {
t.Errorf("original was affected by clone modification: got %q, want %q", input.Get("X-Custom"), "original")
}
})
}
func TestCopyMetadata(t *testing.T) {
t.Parallel()
t.Run("nil input returns empty map", func(t *testing.T) {
result := copyMetadata(nil)
if result == nil {
t.Fatal("copyMetadata(nil) returned nil, want empty map")
}
if len(result) != 0 {
t.Errorf("copyMetadata(nil) has length %d, want 0", len(result))
}
})
t.Run("empty map returns empty map", func(t *testing.T) {
input := map[string]string{}
result := copyMetadata(input)
if result == nil {
t.Fatal("copyMetadata(empty) returned nil")
}
if len(result) != 0 {
t.Errorf("copyMetadata(empty) has length %d, want 0", len(result))
}
})
t.Run("copies all entries", func(t *testing.T) {
input := map[string]string{
"key1": "value1",
"key2": "value2",
"environment": "docker_bridge",
}
result := copyMetadata(input)
if len(result) != 3 {
t.Fatalf("copyMetadata returned %d entries, want 3", len(result))
}
if result["key1"] != "value1" {
t.Errorf("key1 = %q, want %q", result["key1"], "value1")
}
if result["key2"] != "value2" {
t.Errorf("key2 = %q, want %q", result["key2"], "value2")
}
if result["environment"] != "docker_bridge" {
t.Errorf("environment = %q, want %q", result["environment"], "docker_bridge")
}
})
t.Run("clone is independent of original", func(t *testing.T) {
input := map[string]string{"key": "original"}
result := copyMetadata(input)
// Modify original
input["key"] = "modified"
// Clone should be unaffected
if result["key"] != "original" {
t.Errorf("clone was affected by original modification: got %q, want %q", result["key"], "original")
}
})
t.Run("original is independent of clone", func(t *testing.T) {
input := map[string]string{"key": "original"}
result := copyMetadata(input)
// Modify clone
result["key"] = "modified"
// Original should be unaffected
if input["key"] != "original" {
t.Errorf("original was affected by clone modification: got %q, want %q", input["key"], "original")
}
})
}
func TestEnsurePolicyDefaults(t *testing.T) {
t.Parallel()
defaults := envdetect.DefaultScanPolicy()
t.Run("zero policy gets defaults for zero/negative fields", func(t *testing.T) {
input := envdetect.ScanPolicy{}
result := ensurePolicyDefaults(input)
if result.MaxConcurrent != defaults.MaxConcurrent {
t.Errorf("MaxConcurrent = %d, want %d", result.MaxConcurrent, defaults.MaxConcurrent)
}
if result.DialTimeout != defaults.DialTimeout {
t.Errorf("DialTimeout = %v, want %v", result.DialTimeout, defaults.DialTimeout)
}
if result.HTTPTimeout != defaults.HTTPTimeout {
t.Errorf("HTTPTimeout = %v, want %v", result.HTTPTimeout, defaults.HTTPTimeout)
}
// MaxHostsPerScan = 0 is preserved (means unlimited), only < 0 gets default
if result.MaxHostsPerScan != 0 {
t.Errorf("MaxHostsPerScan = %d, want 0 (zero is preserved as unlimited)", result.MaxHostsPerScan)
}
})
t.Run("negative MaxConcurrent gets default", func(t *testing.T) {
input := envdetect.ScanPolicy{MaxConcurrent: -1}
result := ensurePolicyDefaults(input)
if result.MaxConcurrent != defaults.MaxConcurrent {
t.Errorf("MaxConcurrent = %d, want %d", result.MaxConcurrent, defaults.MaxConcurrent)
}
})
t.Run("positive MaxConcurrent preserved", func(t *testing.T) {
input := envdetect.ScanPolicy{MaxConcurrent: 100}
result := ensurePolicyDefaults(input)
if result.MaxConcurrent != 100 {
t.Errorf("MaxConcurrent = %d, want 100", result.MaxConcurrent)
}
})
t.Run("negative DialTimeout gets default", func(t *testing.T) {
input := envdetect.ScanPolicy{DialTimeout: -time.Second}
result := ensurePolicyDefaults(input)
if result.DialTimeout != defaults.DialTimeout {
t.Errorf("DialTimeout = %v, want %v", result.DialTimeout, defaults.DialTimeout)
}
})
t.Run("positive DialTimeout preserved", func(t *testing.T) {
input := envdetect.ScanPolicy{DialTimeout: 5 * time.Second}
result := ensurePolicyDefaults(input)
if result.DialTimeout != 5*time.Second {
t.Errorf("DialTimeout = %v, want 5s", result.DialTimeout)
}
})
t.Run("negative HTTPTimeout gets default", func(t *testing.T) {
input := envdetect.ScanPolicy{HTTPTimeout: -time.Second}
result := ensurePolicyDefaults(input)
if result.HTTPTimeout != defaults.HTTPTimeout {
t.Errorf("HTTPTimeout = %v, want %v", result.HTTPTimeout, defaults.HTTPTimeout)
}
})
t.Run("positive HTTPTimeout preserved", func(t *testing.T) {
input := envdetect.ScanPolicy{HTTPTimeout: 10 * time.Second}
result := ensurePolicyDefaults(input)
if result.HTTPTimeout != 10*time.Second {
t.Errorf("HTTPTimeout = %v, want 10s", result.HTTPTimeout)
}
})
t.Run("zero MaxHostsPerScan preserved (unlimited)", func(t *testing.T) {
input := envdetect.ScanPolicy{MaxHostsPerScan: 0}
result := ensurePolicyDefaults(input)
// MaxHostsPerScan = 0 means unlimited, should be preserved (not replaced with default)
if result.MaxHostsPerScan != 0 {
t.Errorf("MaxHostsPerScan = %d, want 0 (zero should be preserved as unlimited)", result.MaxHostsPerScan)
}
})
t.Run("negative MaxHostsPerScan gets default", func(t *testing.T) {
input := envdetect.ScanPolicy{MaxHostsPerScan: -1}
result := ensurePolicyDefaults(input)
if result.MaxHostsPerScan != defaults.MaxHostsPerScan {
t.Errorf("MaxHostsPerScan = %d, want %d", result.MaxHostsPerScan, defaults.MaxHostsPerScan)
}
})
t.Run("positive MaxHostsPerScan preserved", func(t *testing.T) {
input := envdetect.ScanPolicy{MaxHostsPerScan: 500}
result := ensurePolicyDefaults(input)
if result.MaxHostsPerScan != 500 {
t.Errorf("MaxHostsPerScan = %d, want 500", result.MaxHostsPerScan)
}
})
t.Run("boolean fields preserved", func(t *testing.T) {
input := envdetect.ScanPolicy{
EnableReverseDNS: true,
ScanGateways: false,
}
result := ensurePolicyDefaults(input)
if result.EnableReverseDNS != true {
t.Errorf("EnableReverseDNS = %v, want true", result.EnableReverseDNS)
}
if result.ScanGateways != false {
t.Errorf("ScanGateways = %v, want false", result.ScanGateways)
}
})
t.Run("all custom values preserved", func(t *testing.T) {
input := envdetect.ScanPolicy{
MaxConcurrent: 25,
DialTimeout: 3 * time.Second,
HTTPTimeout: 5 * time.Second,
MaxHostsPerScan: 256,
EnableReverseDNS: false,
ScanGateways: true,
}
result := ensurePolicyDefaults(input)
if result.MaxConcurrent != 25 {
t.Errorf("MaxConcurrent = %d, want 25", result.MaxConcurrent)
}
if result.DialTimeout != 3*time.Second {
t.Errorf("DialTimeout = %v, want 3s", result.DialTimeout)
}
if result.HTTPTimeout != 5*time.Second {
t.Errorf("HTTPTimeout = %v, want 5s", result.HTTPTimeout)
}
if result.MaxHostsPerScan != 256 {
t.Errorf("MaxHostsPerScan = %d, want 256", result.MaxHostsPerScan)
}
if result.EnableReverseDNS != false {
t.Errorf("EnableReverseDNS = %v, want false", result.EnableReverseDNS)
}
if result.ScanGateways != true {
t.Errorf("ScanGateways = %v, want true", result.ScanGateways)
}
})
}