package apt import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // createTestSolver creates a test solver with default configuration func createTestSolver(t *testing.T) *Solver { tmpDir := t.TempDir() repos := []RepoConfig{ { BaseURL: "http://deb.debian.org/debian", Components: []string{"main"}, Priority: 500, }, } solver, err := NewSolver(tmpDir, "amd64", repos) require.NoError(t, err) return solver } func TestSolverCreation(t *testing.T) { // Test creating a new solver solver := createTestSolver(t) assert.NotNil(t, solver) assert.NotNil(t, solver.repos) } func TestMockDepsolve(t *testing.T) { solver := createTestSolver(t) // Test mock depsolve with empty package list result, err := solver.mockDepsolve([]string{}) assert.NoError(t, err) assert.NotNil(t, result) assert.Empty(t, result.Packages) // Test mock depsolve with some packages packages := []string{"base-files", "systemd", "linux-image-amd64"} result, err = solver.mockDepsolve(packages) assert.NoError(t, err) assert.NotNil(t, result) // Check that we got PackageSpec structs with correct names expectedNames := []string{"base-files", "systemd", "linux-image-amd64"} actualNames := make([]string, len(result.Packages)) for i, pkg := range result.Packages { actualNames[i] = pkg.Name } assert.Equal(t, expectedNames, actualNames) } func TestDepsolve(t *testing.T) { solver := createTestSolver(t) // Test depsolve with empty package list result, err := solver.Depsolve([]string{}, 0) assert.NoError(t, err) assert.NotNil(t, result) assert.Empty(t, result.Packages) // Test depsolve with some packages (should fall back to mock) packages := []string{"base-files", "systemd", "linux-image-amd64"} result, err = solver.Depsolve(packages, 0) assert.NoError(t, err) assert.NotNil(t, result) // Check that we got PackageSpec structs with correct names expectedNames := []string{"base-files", "systemd", "linux-image-amd64"} actualNames := make([]string, len(result.Packages)) for i, pkg := range result.Packages { actualNames[i] = pkg.Name } assert.Equal(t, expectedNames, actualNames) } func TestParseAptOutput(t *testing.T) { solver := createTestSolver(t) // Test with empty output result := solver.parseAptOutput("") assert.Empty(t, result) // Test with valid package names output := `base-files systemd linux-image-amd64 grub-common` result = solver.parseAptOutput(output) expected := []string{"base-files", "systemd", "linux-image-amd64", "grub-common"} assert.ElementsMatch(t, expected, result) // Test with dependency keywords (should be filtered out) output = `Depends: base-files PreDepends: systemd Breaks: old-package Replaces: old-package Conflicts: conflicting-package Recommends: recommended-package Suggests: suggested-package Enhances: enhanced-package |Depends: alternative-package |PreDepends: alternative-pre-depends Enhances: enhanced-package base-files systemd` result = solver.parseAptOutput(output) expected = []string{"base-files", "systemd"} assert.ElementsMatch(t, expected, result) // Test with package names containing special characters (should be filtered out) output = `base-files >version-constraint |alternative-package base-files:amd64 systemd` result = solver.parseAptOutput(output) expected = []string{"base-files", "systemd"} assert.ElementsMatch(t, expected, result) // Test with mixed content output = `Depends: base-files systemd Breaks: old-package linux-image-amd64 |Depends: alternative grub-common Enhances: enhanced bootc` result = solver.parseAptOutput(output) expected = []string{"systemd", "linux-image-amd64", "grub-common", "bootc"} assert.ElementsMatch(t, expected, result) } func TestCreateAptConf(t *testing.T) { solver := createTestSolver(t) // Test creating apt configuration aptConfPath, err := solver.createAptConf() assert.NoError(t, err) assert.NotEmpty(t, aptConfPath) // Check that apt.conf was created assert.FileExists(t, aptConfPath) // Check that sources.list was created in the same directory as apt.conf sourcesListPath := filepath.Join(filepath.Dir(aptConfPath), "sources.list") assert.FileExists(t, sourcesListPath) // Read and verify apt.conf content aptConfContent, err := os.ReadFile(aptConfPath) assert.NoError(t, err) aptConfStr := string(aptConfContent) assert.Contains(t, aptConfStr, "Dir::Etc::SourceList") assert.Contains(t, aptConfStr, "sources.list") // Read and verify sources.list content sourcesListContent, err := os.ReadFile(sourcesListPath) assert.NoError(t, err) sourcesListStr := string(sourcesListContent) assert.Contains(t, sourcesListStr, "deb.debian.org") assert.Contains(t, sourcesListStr, "main") } func TestGetPackageVersion(t *testing.T) { solver := createTestSolver(t) // Test getting version for a package (may get real version or fall back to mock) version, err := solver.getPackageVersion("base-files", "") // Don't assert specific version since it depends on system state assert.NoError(t, err) assert.NotEmpty(t, version) // Test getting version for non-existent package (should fall back to mock) version, err = solver.getPackageVersion("non-existent-package", "") // This might fail with real apt-cache, so we just check it doesn't panic _ = version _ = err } func TestRealDepsolve(t *testing.T) { solver := createTestSolver(t) // Test real depsolve (may fail if apt-cache is not available) result, err := solver.realDepsolve([]string{"base-files"}) // We don't assert success here because apt-cache may not be available in test environment // The important thing is that it doesn't panic // Result might be nil if apt-cache fails, which is acceptable _ = result _ = err // Suppress unused variable warning } func TestSolverWithCustomRepos(t *testing.T) { // Test creating solver with custom repositories repos := []RepoConfig{ { BaseURL: "http://deb.debian.org/debian", Components: []string{"main"}, Priority: 500, }, { BaseURL: "https://git.raines.xyz/api/packages/particle-os/debian", Components: []string{"trixie", "main"}, Priority: 400, }, } solver := &Solver{repos: repos} assert.NotNil(t, solver) assert.Equal(t, repos, solver.repos) } func TestDepsolveWithLargePackageList(t *testing.T) { solver := createTestSolver(t) // Test with a large package list packages := make([]string, 100) for i := 0; i < 100; i++ { packages[i] = "package-" + string(rune('a'+i%26)) } result, err := solver.Depsolve(packages, 0) assert.NoError(t, err) assert.NotNil(t, result) // Check that we got PackageSpec structs with correct names actualNames := make([]string, len(result.Packages)) for i, pkg := range result.Packages { actualNames[i] = pkg.Name } assert.Equal(t, packages, actualNames) } func TestParseAptOutputEdgeCases(t *testing.T) { solver := createTestSolver(t) // Test with only whitespace result := solver.parseAptOutput(" \n\t \n ") assert.Empty(t, result) // Test with only dependency keywords output := `Depends: package1 PreDepends: package2 Breaks: package3 Replaces: package4 Conflicts: package5 Recommends: package6 Suggests: package7 Enhances: package8 |Depends: package9 |PreDepends: package10` result = solver.parseAptOutput(output) assert.Empty(t, result) // Test with only special characters output = ` >constraint |alternative package:arch` result = solver.parseAptOutput(output) assert.Empty(t, result) // Test with mixed valid and invalid output = `valid-package Depends: invalid another-valid yet-another-valid` result = solver.parseAptOutput(output) expected := []string{"valid-package", "another-valid", "yet-another-valid"} assert.ElementsMatch(t, expected, result) }