From 65b0644027cbd9ede332982a5f6f57af38b4f62a Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 26 Nov 2025 14:17:58 +0000 Subject: [PATCH] test: add unit tests for internal/agentbinaries --- internal/agentbinaries/host_agent_test.go | 292 ++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 internal/agentbinaries/host_agent_test.go diff --git a/internal/agentbinaries/host_agent_test.go b/internal/agentbinaries/host_agent_test.go new file mode 100644 index 0000000..1f34ecd --- /dev/null +++ b/internal/agentbinaries/host_agent_test.go @@ -0,0 +1,292 @@ +package agentbinaries + +import ( + "os" + "path/filepath" + "testing" +) + +func TestNormalizeVersionTag(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"1.0.0", "v1.0.0"}, + {"v1.0.0", "v1.0.0"}, + {"2.3.4", "v2.3.4"}, + {"v2.3.4", "v2.3.4"}, + {" v1.2.3 ", "v1.2.3"}, + {" 1.2.3 ", "v1.2.3"}, + {"", ""}, + {" ", ""}, + } + + for _, tc := range tests { + t.Run(tc.input, func(t *testing.T) { + result := normalizeVersionTag(tc.input) + if result != tc.expected { + t.Errorf("normalizeVersionTag(%q) = %q, want %q", tc.input, result, tc.expected) + } + }) + } +} + +func TestNormalizeExecutableMode(t *testing.T) { + tests := []struct { + name string + input os.FileMode + expected os.FileMode + }{ + { + name: "already executable", + input: 0o755, + expected: 0o755, + }, + { + name: "not executable", + input: 0o644, + expected: 0o755, + }, + { + name: "partial executable", + input: 0o744, + expected: 0o744, // already has execute bit + }, + { + name: "read only", + input: 0o444, + expected: 0o755, + }, + { + name: "no permissions", + input: 0o000, + expected: 0o755, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := normalizeExecutableMode(tc.input) + if result != tc.expected { + t.Errorf("normalizeExecutableMode(%o) = %o, want %o", tc.input, result, tc.expected) + } + }) + } +} + +func TestHostAgentSearchPaths(t *testing.T) { + // Save and restore env var + originalEnv := os.Getenv("PULSE_BIN_DIR") + defer func() { + if originalEnv != "" { + os.Setenv("PULSE_BIN_DIR", originalEnv) + } else { + os.Unsetenv("PULSE_BIN_DIR") + } + }() + + // Test with default (no env var) + os.Unsetenv("PULSE_BIN_DIR") + paths := HostAgentSearchPaths() + + if len(paths) == 0 { + t.Error("HostAgentSearchPaths() returned empty slice") + } + if paths[0] != "/opt/pulse/bin" { + t.Errorf("First path = %q, want /opt/pulse/bin", paths[0]) + } + + // Test with custom env var + os.Setenv("PULSE_BIN_DIR", "/custom/path") + paths = HostAgentSearchPaths() + + if paths[0] != "/custom/path" { + t.Errorf("First path = %q, want /custom/path", paths[0]) + } + + // Verify deduplication + os.Setenv("PULSE_BIN_DIR", ".") + paths = HostAgentSearchPaths() + + // "." should only appear once + dotCount := 0 + for _, p := range paths { + if p == "." { + dotCount++ + } + } + if dotCount > 1 { + t.Errorf("'.' appears %d times, should only appear once", dotCount) + } +} + +func TestHostAgentBinary_Fields(t *testing.T) { + binary := HostAgentBinary{ + Platform: "linux", + Arch: "amd64", + Filenames: []string{"pulse-host-agent-linux-amd64"}, + } + + if binary.Platform != "linux" { + t.Errorf("Platform = %q, want linux", binary.Platform) + } + if binary.Arch != "amd64" { + t.Errorf("Arch = %q, want amd64", binary.Arch) + } + if len(binary.Filenames) != 1 { + t.Errorf("Filenames count = %d, want 1", len(binary.Filenames)) + } +} + +func TestRequiredHostAgentBinaries(t *testing.T) { + // Verify expected platforms are present + expectedPlatforms := map[string]bool{ + "linux-amd64": false, + "linux-arm64": false, + "linux-armv7": false, + "linux-armv6": false, + "linux-386": false, + "darwin-amd64": false, + "darwin-arm64": false, + "windows-amd64": false, + "windows-arm64": false, + "windows-386": false, + } + + for _, binary := range requiredHostAgentBinaries { + key := binary.Platform + "-" + binary.Arch + if _, ok := expectedPlatforms[key]; ok { + expectedPlatforms[key] = true + } + } + + for platform, found := range expectedPlatforms { + if !found { + t.Errorf("Expected platform %q not found in requiredHostAgentBinaries", platform) + } + } +} + +func TestHostAgentBinaryExists(t *testing.T) { + tmpDir := t.TempDir() + + // Create a test binary + testBinary := filepath.Join(tmpDir, "pulse-host-agent-linux-amd64") + if err := os.WriteFile(testBinary, []byte("test binary"), 0o755); err != nil { + t.Fatalf("Failed to create test binary: %v", err) + } + + // Test existing binary + exists := hostAgentBinaryExists([]string{tmpDir}, []string{"pulse-host-agent-linux-amd64"}) + if !exists { + t.Error("hostAgentBinaryExists should return true for existing binary") + } + + // Test non-existing binary + exists = hostAgentBinaryExists([]string{tmpDir}, []string{"pulse-host-agent-linux-arm64"}) + if exists { + t.Error("hostAgentBinaryExists should return false for non-existing binary") + } + + // Test with multiple search paths + tmpDir2 := t.TempDir() + exists = hostAgentBinaryExists([]string{tmpDir2, tmpDir}, []string{"pulse-host-agent-linux-amd64"}) + if !exists { + t.Error("hostAgentBinaryExists should find binary in second search path") + } + + // Test with directory (should not match) + dirPath := filepath.Join(tmpDir, "pulse-host-agent-linux-386") + if err := os.MkdirAll(dirPath, 0o755); err != nil { + t.Fatalf("Failed to create directory: %v", err) + } + exists = hostAgentBinaryExists([]string{tmpDir}, []string{"pulse-host-agent-linux-386"}) + if exists { + t.Error("hostAgentBinaryExists should return false for directories") + } +} + +func TestFindMissingHostAgentBinaries(t *testing.T) { + tmpDir := t.TempDir() + + // With no binaries present, all should be missing + missing := findMissingHostAgentBinaries([]string{tmpDir}) + if len(missing) != len(requiredHostAgentBinaries) { + t.Errorf("Missing count = %d, want %d", len(missing), len(requiredHostAgentBinaries)) + } + + // Create one binary + testBinary := filepath.Join(tmpDir, "pulse-host-agent-linux-amd64") + if err := os.WriteFile(testBinary, []byte("test"), 0o755); err != nil { + t.Fatalf("Failed to create test binary: %v", err) + } + + missing = findMissingHostAgentBinaries([]string{tmpDir}) + if len(missing) != len(requiredHostAgentBinaries)-1 { + t.Errorf("Missing count = %d, want %d", len(missing), len(requiredHostAgentBinaries)-1) + } + + // Verify linux-amd64 is not in missing + if _, ok := missing["linux-amd64"]; ok { + t.Error("linux-amd64 should not be in missing after creating binary") + } +} + +func TestDownloadAndInstallHostAgentBinaries_DevVersion(t *testing.T) { + tmpDir := t.TempDir() + + // Dev versions should fail + err := DownloadAndInstallHostAgentBinaries("dev", tmpDir) + if err == nil { + t.Error("DownloadAndInstallHostAgentBinaries should fail for 'dev' version") + } + + err = DownloadAndInstallHostAgentBinaries("vdev", tmpDir) + if err == nil { + t.Error("DownloadAndInstallHostAgentBinaries should fail for 'vdev' version") + } + + err = DownloadAndInstallHostAgentBinaries("", tmpDir) + if err == nil { + t.Error("DownloadAndInstallHostAgentBinaries should fail for empty version") + } +} + +func TestWindowsBinaryFilenames(t *testing.T) { + // Windows binaries should have both with and without .exe + for _, binary := range requiredHostAgentBinaries { + if binary.Platform != "windows" { + continue + } + + hasExe := false + hasNoExe := false + for _, filename := range binary.Filenames { + if filepath.Ext(filename) == ".exe" { + hasExe = true + } else { + hasNoExe = true + } + } + + if !hasExe || !hasNoExe { + t.Errorf("Windows binary %s-%s should have both .exe and non-.exe filenames", binary.Platform, binary.Arch) + } + } +} + +func TestNonWindowsBinaryFilenames(t *testing.T) { + // Non-Windows binaries should not have .exe + for _, binary := range requiredHostAgentBinaries { + if binary.Platform == "windows" { + continue + } + + for _, filename := range binary.Filenames { + if filepath.Ext(filename) == ".exe" { + t.Errorf("Non-Windows binary %s-%s should not have .exe extension: %s", + binary.Platform, binary.Arch, filename) + } + } + } +}