From 48aacecf0df27bd17dbaa8e05dc44aa4ccfd6212 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 1 Dec 2025 11:06:50 +0000 Subject: [PATCH] test: Add unit tests for validateIPAddress, validatePort, defaultPortForNodeType Cover API validation functions that were previously untested: - validateIPAddress: IPv4/IPv6 validation with edge cases - validatePort: Port range validation (1-65535) - defaultPortForNodeType: Proxmox node type to default port mapping --- internal/api/config_handlers_host_test.go | 121 +++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/internal/api/config_handlers_host_test.go b/internal/api/config_handlers_host_test.go index aec19f994..2575afbd9 100644 --- a/internal/api/config_handlers_host_test.go +++ b/internal/api/config_handlers_host_test.go @@ -1,6 +1,8 @@ package api -import "testing" +import ( + "testing" +) func TestExtractHostAndPort(t *testing.T) { tests := []struct { @@ -203,6 +205,123 @@ func TestExtractHostAndPort(t *testing.T) { } } +func TestValidateIPAddress(t *testing.T) { + tests := []struct { + name string + ip string + want bool + }{ + // Valid IPv4 addresses + {"valid IPv4 localhost", "127.0.0.1", true}, + {"valid IPv4 private", "192.168.1.1", true}, + {"valid IPv4 public", "8.8.8.8", true}, + {"valid IPv4 zeros", "0.0.0.0", true}, + {"valid IPv4 broadcast", "255.255.255.255", true}, + {"valid IPv4 class A", "10.0.0.1", true}, + {"valid IPv4 class B", "172.16.0.1", true}, + + // Valid IPv6 addresses + {"valid IPv6 loopback", "::1", true}, + {"valid IPv6 unspecified", "::", true}, + {"valid IPv6 full", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", true}, + {"valid IPv6 compressed", "2001:db8:85a3::8a2e:370:7334", true}, + {"valid IPv6 link-local", "fe80::1", true}, + {"valid IPv6 multicast", "ff02::1", true}, + + // Invalid addresses + {"invalid empty", "", false}, + {"invalid hostname", "localhost", false}, + {"invalid domain", "example.com", false}, + {"invalid IPv4 with port", "192.168.1.1:8080", false}, + {"invalid IPv4 out of range", "256.256.256.256", false}, + {"invalid IPv4 too many octets", "192.168.1.1.1", false}, + {"invalid IPv4 too few octets", "192.168.1", false}, + {"invalid IPv4 negative", "-1.0.0.0", false}, + {"invalid IPv4 with letters", "192.168.a.1", false}, + {"invalid IPv6 with port", "[::1]:8080", false}, + {"invalid IPv6 brackets only", "[::1]", false}, + {"invalid random string", "not-an-ip", false}, + {"invalid whitespace", " 192.168.1.1 ", false}, + {"invalid URL", "https://192.168.1.1", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validateIPAddress(tt.ip) + if got != tt.want { + t.Errorf("validateIPAddress(%q) = %v, want %v", tt.ip, got, tt.want) + } + }) + } +} + +func TestValidatePort(t *testing.T) { + tests := []struct { + name string + portStr string + want bool + }{ + // Valid ports + {"valid minimum port", "1", true}, + {"valid maximum port", "65535", true}, + {"valid common HTTP", "80", true}, + {"valid HTTPS", "443", true}, + {"valid Proxmox PVE", "8006", true}, + {"valid Proxmox PBS", "8007", true}, + {"valid SSH", "22", true}, + {"valid high port", "49152", true}, + + // Invalid ports + {"invalid zero", "0", false}, + {"invalid negative", "-1", false}, + {"invalid too high", "65536", false}, + {"invalid way too high", "100000", false}, + {"invalid empty string", "", false}, + {"invalid non-numeric", "abc", false}, + {"invalid float", "80.5", false}, + {"invalid with spaces", " 80 ", false}, + {"invalid mixed", "80abc", false}, + {"invalid hex", "0x50", false}, + {"invalid with leading zeros that parse ok", "0080", true}, // strconv.Atoi handles leading zeros + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := validatePort(tt.portStr) + if got != tt.want { + t.Errorf("validatePort(%q) = %v, want %v", tt.portStr, got, tt.want) + } + }) + } +} + +func TestDefaultPortForNodeType(t *testing.T) { + tests := []struct { + name string + nodeType string + want string + }{ + {"PVE node", "pve", "8006"}, + {"PMG node", "pmg", "8006"}, + {"PBS node", "pbs", "8007"}, + {"docker node returns empty", "docker", ""}, + {"unknown type returns empty", "unknown", ""}, + {"empty type returns empty", "", ""}, + {"uppercase PVE returns empty", "PVE", ""}, // case sensitive + {"mixed case returns empty", "Pve", ""}, // case sensitive + {"similar but wrong returns empty", "pvee", ""}, // exact match only + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := defaultPortForNodeType(tt.nodeType) + if got != tt.want { + t.Errorf("defaultPortForNodeType(%q) = %q, want %q", tt.nodeType, got, tt.want) + } + }) + } +} + func TestNormalizeNodeHost(t *testing.T) { tests := []struct { name string