Pulse/pkg/discovery/envdetect/envdetect_test.go
rcourtman c9ce98d679 test: add unit tests for pkg/discovery/envdetect
Add 20 tests covering:
- Environment enum String() method
- DefaultScanPolicy default values
- ScanPolicy struct fields
- SubnetPhase struct fields
- EnvironmentProfile struct fields
- parseHexIP little-endian IP parsing
- tryCommonSubnets fallback subnet list
- profileWithWarning helper
- addFallbackSubnets with confidence handling
2025-11-26 14:25:17 +00:00

509 lines
13 KiB
Go

package envdetect
import (
"net"
"testing"
"time"
)
func TestEnvironment_String(t *testing.T) {
tests := []struct {
env Environment
expected string
}{
{Unknown, "unknown"},
{Native, "native"},
{DockerHost, "docker_host"},
{DockerBridge, "docker_bridge"},
{LXCPrivileged, "lxc_privileged"},
{LXCUnprivileged, "lxc_unprivileged"},
{Environment(99), "unknown"}, // Invalid value
}
for _, tc := range tests {
t.Run(tc.expected, func(t *testing.T) {
result := tc.env.String()
if result != tc.expected {
t.Errorf("Environment(%d).String() = %q, want %q", tc.env, result, tc.expected)
}
})
}
}
func TestDefaultScanPolicy(t *testing.T) {
policy := DefaultScanPolicy()
if policy.MaxConcurrent != 50 {
t.Errorf("MaxConcurrent = %d, want 50", policy.MaxConcurrent)
}
if policy.DialTimeout != time.Second {
t.Errorf("DialTimeout = %v, want 1s", policy.DialTimeout)
}
if policy.HTTPTimeout != 2*time.Second {
t.Errorf("HTTPTimeout = %v, want 2s", policy.HTTPTimeout)
}
if policy.MaxHostsPerScan != 1024 {
t.Errorf("MaxHostsPerScan = %d, want 1024", policy.MaxHostsPerScan)
}
if !policy.EnableReverseDNS {
t.Error("EnableReverseDNS should be true by default")
}
if !policy.ScanGateways {
t.Error("ScanGateways should be true by default")
}
}
func TestScanPolicy_Fields(t *testing.T) {
policy := ScanPolicy{
MaxConcurrent: 100,
DialTimeout: 5 * time.Second,
HTTPTimeout: 10 * time.Second,
MaxHostsPerScan: 2048,
EnableReverseDNS: false,
ScanGateways: false,
}
if policy.MaxConcurrent != 100 {
t.Errorf("MaxConcurrent = %d, want 100", policy.MaxConcurrent)
}
if policy.DialTimeout != 5*time.Second {
t.Errorf("DialTimeout = %v, want 5s", policy.DialTimeout)
}
if policy.HTTPTimeout != 10*time.Second {
t.Errorf("HTTPTimeout = %v, want 10s", policy.HTTPTimeout)
}
if policy.MaxHostsPerScan != 2048 {
t.Errorf("MaxHostsPerScan = %d, want 2048", policy.MaxHostsPerScan)
}
if policy.EnableReverseDNS {
t.Error("EnableReverseDNS should be false")
}
if policy.ScanGateways {
t.Error("ScanGateways should be false")
}
}
func TestSubnetPhase_Fields(t *testing.T) {
_, subnet, _ := net.ParseCIDR("192.168.1.0/24")
phase := SubnetPhase{
Name: "test_phase",
Subnets: []net.IPNet{*subnet},
Confidence: 0.85,
Priority: 2,
}
if phase.Name != "test_phase" {
t.Errorf("Name = %q, want test_phase", phase.Name)
}
if len(phase.Subnets) != 1 {
t.Errorf("Subnets count = %d, want 1", len(phase.Subnets))
}
if phase.Confidence != 0.85 {
t.Errorf("Confidence = %f, want 0.85", phase.Confidence)
}
if phase.Priority != 2 {
t.Errorf("Priority = %d, want 2", phase.Priority)
}
}
func TestEnvironmentProfile_Fields(t *testing.T) {
_, subnet, _ := net.ParseCIDR("10.0.0.0/24")
extraIP := net.ParseIP("192.168.1.1")
profile := EnvironmentProfile{
Type: Native,
Phases: []SubnetPhase{
{Name: "local_networks", Subnets: []net.IPNet{*subnet}},
},
ExtraTargets: []net.IP{extraIP},
Policy: DefaultScanPolicy(),
Confidence: 0.95,
Warnings: []string{"test warning"},
Metadata: map[string]string{"key": "value"},
}
if profile.Type != Native {
t.Errorf("Type = %v, want Native", profile.Type)
}
if len(profile.Phases) != 1 {
t.Errorf("Phases count = %d, want 1", len(profile.Phases))
}
if len(profile.ExtraTargets) != 1 {
t.Errorf("ExtraTargets count = %d, want 1", len(profile.ExtraTargets))
}
if profile.Confidence != 0.95 {
t.Errorf("Confidence = %f, want 0.95", profile.Confidence)
}
if len(profile.Warnings) != 1 {
t.Errorf("Warnings count = %d, want 1", len(profile.Warnings))
}
if profile.Metadata["key"] != "value" {
t.Errorf("Metadata[key] = %q, want value", profile.Metadata["key"])
}
}
func TestParseHexIP(t *testing.T) {
tests := []struct {
name string
hexIP string
expected net.IP
expectErr bool
}{
{
name: "valid gateway 192.168.1.1",
hexIP: "0101A8C0", // 192.168.1.1 in little-endian hex
expected: net.IPv4(192, 168, 1, 1),
},
{
name: "valid gateway 10.0.0.1",
hexIP: "0100000A", // 10.0.0.1 in little-endian hex
expected: net.IPv4(10, 0, 0, 1),
},
{
name: "all zeros",
hexIP: "00000000",
expected: net.IPv4(0, 0, 0, 0),
},
{
name: "broadcast",
hexIP: "FFFFFFFF",
expected: net.IPv4(255, 255, 255, 255),
},
{
name: "too short",
hexIP: "0101A8",
expectErr: true,
},
{
name: "too long",
hexIP: "0101A8C0FF",
expectErr: true,
},
{
name: "invalid hex",
hexIP: "ZZZZZZZZ",
expectErr: true,
},
{
name: "empty",
hexIP: "",
expectErr: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := parseHexIP(tc.hexIP)
if tc.expectErr {
if err == nil {
t.Errorf("parseHexIP(%q) expected error, got nil", tc.hexIP)
}
return
}
if err != nil {
t.Errorf("parseHexIP(%q) unexpected error: %v", tc.hexIP, err)
return
}
if !result.Equal(tc.expected) {
t.Errorf("parseHexIP(%q) = %v, want %v", tc.hexIP, result, tc.expected)
}
})
}
}
func TestTryCommonSubnets(t *testing.T) {
subnets := tryCommonSubnets()
if len(subnets) == 0 {
t.Error("tryCommonSubnets() returned empty slice")
}
// Verify we get expected common subnets
expectedCIDRs := map[string]bool{
"192.168.1.0/24": true,
"192.168.0.0/24": true,
"10.0.0.0/24": true,
"172.16.0.0/24": true,
"192.168.2.0/24": true,
}
for _, subnet := range subnets {
cidr := subnet.String()
if !expectedCIDRs[cidr] {
t.Errorf("Unexpected subnet %s in fallback list", cidr)
}
delete(expectedCIDRs, cidr)
}
for cidr := range expectedCIDRs {
t.Errorf("Expected subnet %s not found in fallback list", cidr)
}
}
func TestProfileWithWarning(t *testing.T) {
profile := &EnvironmentProfile{
Warnings: []string{"existing warning"},
}
result := profileWithWarning(profile, "new warning")
// Should return same profile
if result != profile {
t.Error("profileWithWarning should return the same profile instance")
}
// Should have appended warning
if len(result.Warnings) != 2 {
t.Errorf("Warnings count = %d, want 2", len(result.Warnings))
}
if result.Warnings[1] != "new warning" {
t.Errorf("Warnings[1] = %q, want 'new warning'", result.Warnings[1])
}
}
func TestProfileWithWarning_EmptyWarning(t *testing.T) {
profile := &EnvironmentProfile{
Warnings: []string{"existing"},
}
result := profileWithWarning(profile, "")
// Empty warning should not be appended
if len(result.Warnings) != 1 {
t.Errorf("Warnings count = %d, want 1 (empty warning should not be appended)", len(result.Warnings))
}
}
func TestAddFallbackSubnets(t *testing.T) {
profile := &EnvironmentProfile{
Phases: []SubnetPhase{},
Confidence: 0.0,
Warnings: []string{},
}
result, err := addFallbackSubnets(profile)
if err != nil {
t.Errorf("addFallbackSubnets unexpected error: %v", err)
}
// Should have added a phase
if len(result.Phases) != 1 {
t.Errorf("Phases count = %d, want 1", len(result.Phases))
}
// Phase should have correct properties
phase := result.Phases[0]
if phase.Name != "fallback_common_subnets" {
t.Errorf("Phase name = %q, want fallback_common_subnets", phase.Name)
}
if phase.Confidence != 0.3 {
t.Errorf("Phase confidence = %f, want 0.3", phase.Confidence)
}
if phase.Priority != 10 {
t.Errorf("Phase priority = %d, want 10", phase.Priority)
}
if len(phase.Subnets) == 0 {
t.Error("Phase should have subnets")
}
// Confidence should be set if it was 0
if result.Confidence != 0.3 {
t.Errorf("Confidence = %f, want 0.3", result.Confidence)
}
// Should have warning
foundWarning := false
for _, w := range result.Warnings {
if w == "Using fallback private subnets; consider manual subnet configuration" {
foundWarning = true
break
}
}
if !foundWarning {
t.Error("Expected fallback warning not found")
}
}
func TestAddFallbackSubnets_PreservesExistingConfidence(t *testing.T) {
profile := &EnvironmentProfile{
Phases: []SubnetPhase{},
Confidence: 0.7,
Warnings: []string{},
}
result, _ := addFallbackSubnets(profile)
// Existing confidence should be preserved
if result.Confidence != 0.7 {
t.Errorf("Confidence = %f, want 0.7 (should preserve existing)", result.Confidence)
}
}
func TestEnvironmentConstants(t *testing.T) {
// Verify enum values are distinct
values := map[Environment]bool{}
envs := []Environment{Unknown, Native, DockerHost, DockerBridge, LXCPrivileged, LXCUnprivileged}
for _, env := range envs {
if values[env] {
t.Errorf("Duplicate environment value: %d", env)
}
values[env] = true
}
// Unknown should be 0 (default value)
if Unknown != 0 {
t.Errorf("Unknown = %d, want 0", Unknown)
}
}
func TestSubnetPhase_MultipleSubnets(t *testing.T) {
_, subnet1, _ := net.ParseCIDR("192.168.1.0/24")
_, subnet2, _ := net.ParseCIDR("10.0.0.0/8")
_, subnet3, _ := net.ParseCIDR("172.16.0.0/16")
phase := SubnetPhase{
Name: "multi_subnet_phase",
Subnets: []net.IPNet{*subnet1, *subnet2, *subnet3},
}
if len(phase.Subnets) != 3 {
t.Errorf("Subnets count = %d, want 3", len(phase.Subnets))
}
// Verify each subnet
if phase.Subnets[0].String() != "192.168.1.0/24" {
t.Errorf("Subnet[0] = %s, want 192.168.1.0/24", phase.Subnets[0].String())
}
if phase.Subnets[1].String() != "10.0.0.0/8" {
t.Errorf("Subnet[1] = %s, want 10.0.0.0/8", phase.Subnets[1].String())
}
if phase.Subnets[2].String() != "172.16.0.0/16" {
t.Errorf("Subnet[2] = %s, want 172.16.0.0/16", phase.Subnets[2].String())
}
}
func TestEnvironmentProfile_EmptyMetadata(t *testing.T) {
profile := EnvironmentProfile{
Type: Native,
Metadata: nil,
}
// Accessing nil map should not panic for reads
if profile.Metadata != nil {
t.Error("Metadata should be nil")
}
// Verify we can initialize and use metadata
profile.Metadata = make(map[string]string)
profile.Metadata["test"] = "value"
if profile.Metadata["test"] != "value" {
t.Errorf("Metadata[test] = %q, want value", profile.Metadata["test"])
}
}
func TestEnvironmentProfile_MultiplePhases(t *testing.T) {
_, subnet1, _ := net.ParseCIDR("192.168.1.0/24")
_, subnet2, _ := net.ParseCIDR("10.0.0.0/24")
profile := EnvironmentProfile{
Type: DockerBridge,
Phases: []SubnetPhase{
{Name: "container_network", Subnets: []net.IPNet{*subnet1}, Priority: 1, Confidence: 0.95},
{Name: "inferred_host", Subnets: []net.IPNet{*subnet2}, Priority: 2, Confidence: 0.7},
},
Confidence: 0.85,
}
if len(profile.Phases) != 2 {
t.Errorf("Phases count = %d, want 2", len(profile.Phases))
}
// Verify phase ordering by priority
if profile.Phases[0].Priority >= profile.Phases[1].Priority {
t.Error("First phase should have lower priority than second")
}
}
func TestParseHexIP_LittleEndian(t *testing.T) {
// Test that little-endian parsing is correct
// 0101A8C0 should parse to 192.168.1.1
// Bytes: 01 01 A8 C0
// Little-endian: C0 A8 01 01 = 192.168.1.1
ip, err := parseHexIP("0101A8C0")
if err != nil {
t.Fatalf("parseHexIP unexpected error: %v", err)
}
expected := net.IPv4(192, 168, 1, 1)
if !ip.Equal(expected) {
t.Errorf("parseHexIP(0101A8C0) = %v, want %v", ip, expected)
}
}
func TestScanPolicy_ZeroValues(t *testing.T) {
policy := ScanPolicy{}
// Verify zero values
if policy.MaxConcurrent != 0 {
t.Error("MaxConcurrent should be 0 by default")
}
if policy.DialTimeout != 0 {
t.Error("DialTimeout should be 0 by default")
}
if policy.HTTPTimeout != 0 {
t.Error("HTTPTimeout should be 0 by default")
}
if policy.MaxHostsPerScan != 0 {
t.Error("MaxHostsPerScan should be 0 by default")
}
if policy.EnableReverseDNS {
t.Error("EnableReverseDNS should be false by default")
}
if policy.ScanGateways {
t.Error("ScanGateways should be false by default")
}
}
func TestEnvironmentProfile_WithExtraTargets(t *testing.T) {
targets := []net.IP{
net.ParseIP("192.168.1.1"),
net.ParseIP("192.168.1.254"),
net.ParseIP("10.0.0.1"),
}
profile := EnvironmentProfile{
ExtraTargets: targets,
}
if len(profile.ExtraTargets) != 3 {
t.Errorf("ExtraTargets count = %d, want 3", len(profile.ExtraTargets))
}
// Verify IP addresses
if !profile.ExtraTargets[0].Equal(net.ParseIP("192.168.1.1")) {
t.Errorf("ExtraTargets[0] = %v, want 192.168.1.1", profile.ExtraTargets[0])
}
}
func TestEnvironmentProfile_WarningsSlice(t *testing.T) {
profile := EnvironmentProfile{
Warnings: []string{
"Warning 1",
"Warning 2",
"Warning 3",
},
}
if len(profile.Warnings) != 3 {
t.Errorf("Warnings count = %d, want 3", len(profile.Warnings))
}
// Append more warnings
profile.Warnings = append(profile.Warnings, "Warning 4")
if len(profile.Warnings) != 4 {
t.Errorf("Warnings count after append = %d, want 4", len(profile.Warnings))
}
}