Files
snack/snap/snap_test.go
Tai Groot 1fa7de6d66 feat: add PackageUpgrade to Capabilities, exhaustive detect + CLI tests
- Add PackageUpgrade field to Capabilities struct and GetCapabilities
- Add PackageUpgrade to all 11 provider capability tests
- Add pkg-upgrade to CLI detect command output
- Expand detect tests: ByName for all managers, concurrent Reset,
  HasBinary, candidates/allManagers coverage
- Add cmd/snack unit tests: targets, opts, getManager, version
- 838 tests passing, 0 failures
2026-03-06 01:07:35 +00:00

458 lines
12 KiB
Go

package snap
import (
"testing"
"github.com/gogrlx/snack"
)
func TestParseSnapList(t *testing.T) {
input := `Name Version Rev Tracking Publisher Notes
core22 20240111 1122 latest/stable canonical✓ base
firefox 131.0 4647 latest/stable mozilla✓ -
`
pkgs := parseSnapList(input)
if len(pkgs) != 2 {
t.Fatalf("expected 2 packages, got %d", len(pkgs))
}
if pkgs[0].Name != "core22" || pkgs[0].Version != "20240111" {
t.Errorf("unexpected first package: %+v", pkgs[0])
}
if pkgs[1].Name != "firefox" || pkgs[1].Version != "131.0" {
t.Errorf("unexpected second package: %+v", pkgs[1])
}
if !pkgs[0].Installed {
t.Error("expected Installed=true")
}
}
func TestParseSnapListEmpty(t *testing.T) {
input := `Name Version Rev Tracking Publisher Notes
`
pkgs := parseSnapList(input)
if len(pkgs) != 0 {
t.Fatalf("expected 0 packages, got %d", len(pkgs))
}
}
func TestParseSnapListEdgeCases(t *testing.T) {
t.Run("single entry", func(t *testing.T) {
input := `Name Version Rev Tracking Publisher Notes
core22 20240111 1122 latest/stable canonical✓ base
`
pkgs := parseSnapList(input)
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Name != "core22" {
t.Errorf("expected core22, got %q", pkgs[0].Name)
}
})
t.Run("header only no trailing newline", func(t *testing.T) {
input := "Name Version Rev Tracking Publisher Notes"
pkgs := parseSnapList(input)
if len(pkgs) != 0 {
t.Errorf("expected 0 packages, got %d", len(pkgs))
}
})
t.Run("single field line skipped", func(t *testing.T) {
input := "Name Version Rev Tracking Publisher Notes\nsinglefield\n"
pkgs := parseSnapList(input)
if len(pkgs) != 0 {
t.Errorf("expected 0 packages (need >=2 fields), got %d", len(pkgs))
}
})
t.Run("extra whitespace lines", func(t *testing.T) {
input := "Name Version Rev Tracking Publisher Notes\n \n\n"
pkgs := parseSnapList(input)
if len(pkgs) != 0 {
t.Errorf("expected 0 packages, got %d", len(pkgs))
}
})
t.Run("many columns", func(t *testing.T) {
input := "Name Version Rev Tracking Publisher Notes\nsnap1 2.0 100 latest/stable pub note extra more\n"
pkgs := parseSnapList(input)
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Name != "snap1" || pkgs[0].Version != "2.0" {
t.Errorf("unexpected package: %+v", pkgs[0])
}
})
}
func TestParseSnapFind(t *testing.T) {
input := `Name Version Publisher Notes Summary
firefox 131.0 mozilla✓ - Mozilla Firefox web browser
chromium 129.0 nickvdp - Chromium web browser
`
pkgs := parseSnapFind(input)
if len(pkgs) != 2 {
t.Fatalf("expected 2 packages, got %d", len(pkgs))
}
if pkgs[0].Name != "firefox" || pkgs[0].Version != "131.0" {
t.Errorf("unexpected first package: %+v", pkgs[0])
}
if pkgs[0].Description != "Mozilla Firefox web browser" {
t.Errorf("unexpected description: %q", pkgs[0].Description)
}
}
func TestParseSnapFindEdgeCases(t *testing.T) {
t.Run("single result", func(t *testing.T) {
input := "Name Version Publisher Notes Summary\nfirefox 131.0 mozilla - Web browser\n"
pkgs := parseSnapFind(input)
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Name != "firefox" {
t.Errorf("expected firefox, got %q", pkgs[0].Name)
}
if pkgs[0].Description != "Web browser" {
t.Errorf("expected 'Web browser', got %q", pkgs[0].Description)
}
})
t.Run("header only", func(t *testing.T) {
input := "Name Version Publisher Notes Summary\n"
pkgs := parseSnapFind(input)
if len(pkgs) != 0 {
t.Errorf("expected 0 packages, got %d", len(pkgs))
}
})
t.Run("too few fields skipped", func(t *testing.T) {
input := "Name Version Publisher Notes Summary\nfoo 1.0 pub\n"
pkgs := parseSnapFind(input)
if len(pkgs) != 0 {
t.Errorf("expected 0 packages (need >=4 fields), got %d", len(pkgs))
}
})
t.Run("exactly four fields no summary", func(t *testing.T) {
input := "Name Version Publisher Notes Summary\nfoo 1.0 pub note\n"
pkgs := parseSnapFind(input)
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Description != "" {
t.Errorf("expected empty description, got %q", pkgs[0].Description)
}
})
t.Run("multi-word summary", func(t *testing.T) {
input := "Name Version Publisher Notes Summary\nmysnap 2.0 me - A very long description with many words\n"
pkgs := parseSnapFind(input)
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Description != "A very long description with many words" {
t.Errorf("unexpected description: %q", pkgs[0].Description)
}
})
t.Run("empty input", func(t *testing.T) {
pkgs := parseSnapFind("")
if len(pkgs) != 0 {
t.Errorf("expected 0 packages, got %d", len(pkgs))
}
})
}
func TestParseSnapInfo(t *testing.T) {
input := `name: firefox
summary: Mozilla Firefox web browser
publisher: Mozilla✓ (mozilla✓)
snap-id: 3wdHCAVyZEmYsCMFDE9qt92UV8rC8Wdk
installed: 131.0 (4647) 283MB -
`
pkg := parseSnapInfo(input)
if pkg == nil {
t.Fatal("expected non-nil package")
}
if pkg.Name != "firefox" {
t.Errorf("expected name 'firefox', got %q", pkg.Name)
}
if pkg.Version != "131.0" {
t.Errorf("expected version '131.0', got %q", pkg.Version)
}
if pkg.Description != "Mozilla Firefox web browser" {
t.Errorf("unexpected description: %q", pkg.Description)
}
if !pkg.Installed {
t.Error("expected Installed=true")
}
}
func TestParseSnapInfoEmpty(t *testing.T) {
pkg := parseSnapInfo("")
if pkg != nil {
t.Error("expected nil for empty input")
}
}
func TestParseSnapInfoEdgeCases(t *testing.T) {
t.Run("not installed snap", func(t *testing.T) {
input := `name: hello-world
summary: A simple hello world snap
publisher: Canonical✓ (canonical✓)
snap-id: buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ
`
pkg := parseSnapInfo(input)
if pkg == nil {
t.Fatal("expected non-nil")
}
if pkg.Name != "hello-world" {
t.Errorf("expected hello-world, got %q", pkg.Name)
}
if pkg.Installed {
t.Error("expected Installed=false for snap without installed field")
}
if pkg.Version != "" {
t.Errorf("expected empty version, got %q", pkg.Version)
}
})
t.Run("name only", func(t *testing.T) {
pkg := parseSnapInfo("name: test\n")
if pkg == nil {
t.Fatal("expected non-nil")
}
if pkg.Name != "test" {
t.Errorf("expected test, got %q", pkg.Name)
}
})
t.Run("no name returns nil", func(t *testing.T) {
pkg := parseSnapInfo("summary: something\ninstalled: 1.0 (1) 10MB\n")
if pkg != nil {
t.Error("expected nil when no name field")
}
})
t.Run("no colon lines ignored", func(t *testing.T) {
input := "name: mysnap\nrandom text without colon\nsummary: A snap\n"
pkg := parseSnapInfo(input)
if pkg == nil {
t.Fatal("expected non-nil")
}
if pkg.Description != "A snap" {
t.Errorf("unexpected description: %q", pkg.Description)
}
})
}
func TestParseSnapInfoVersion(t *testing.T) {
input := `name: firefox
channels:
latest/stable: 131.0 2024-10-01 (4647) 283MB -
latest/candidate: 132.0b5 2024-10-05 (4650) 285MB -
latest/beta: 132.0b5 2024-10-05 (4650) 285MB -
latest/edge: 133.0a1 2024-10-06 (4655) 290MB -
`
ver := parseSnapInfoVersion(input)
if ver != "131.0" {
t.Errorf("expected '131.0', got %q", ver)
}
}
func TestParseSnapInfoVersionMissing(t *testing.T) {
ver := parseSnapInfoVersion("name: test\n")
if ver != "" {
t.Errorf("expected empty, got %q", ver)
}
}
func TestParseSnapInfoVersionEdgeCases(t *testing.T) {
t.Run("empty input", func(t *testing.T) {
ver := parseSnapInfoVersion("")
if ver != "" {
t.Errorf("expected empty, got %q", ver)
}
})
t.Run("stable channel with dashes", func(t *testing.T) {
input := " latest/stable: 2024.01.15 2024-01-15 (100) 50MB -\n"
ver := parseSnapInfoVersion(input)
if ver != "2024.01.15" {
t.Errorf("expected 2024.01.15, got %q", ver)
}
})
t.Run("closed channel marked --", func(t *testing.T) {
input := " latest/stable: --\n"
ver := parseSnapInfoVersion(input)
if ver != "" {
t.Errorf("expected empty for closed channel, got %q", ver)
}
})
t.Run("closed channel marked ^", func(t *testing.T) {
input := " latest/stable: ^ \n"
ver := parseSnapInfoVersion(input)
if ver != "" {
t.Errorf("expected empty for ^ channel, got %q", ver)
}
})
}
func TestParseSnapRefreshList(t *testing.T) {
input := `Name Version Rev Publisher Notes
firefox 132.0 4650 mozilla✓ -
`
pkgs := parseSnapRefreshList(input)
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Name != "firefox" || pkgs[0].Version != "132.0" {
t.Errorf("unexpected package: %+v", pkgs[0])
}
}
func TestParseSnapRefreshListUpToDate(t *testing.T) {
input := `Name Version Rev Publisher Notes
All snaps up to date.
`
pkgs := parseSnapRefreshList(input)
if len(pkgs) != 0 {
t.Fatalf("expected 0 packages, got %d", len(pkgs))
}
}
func TestParseSnapRefreshListEdgeCases(t *testing.T) {
t.Run("multiple upgrades", func(t *testing.T) {
input := "Name Version Rev Publisher Notes\nfoo 2.0 10 pub -\nbar 3.0 20 pub -\n"
pkgs := parseSnapRefreshList(input)
if len(pkgs) != 2 {
t.Fatalf("expected 2 packages, got %d", len(pkgs))
}
if pkgs[0].Name != "foo" || pkgs[1].Name != "bar" {
t.Errorf("unexpected packages: %+v, %+v", pkgs[0], pkgs[1])
}
})
t.Run("header only", func(t *testing.T) {
input := "Name Version Rev Publisher Notes\n"
pkgs := parseSnapRefreshList(input)
if len(pkgs) != 0 {
t.Errorf("expected 0 packages, got %d", len(pkgs))
}
})
}
func TestSemverCmp(t *testing.T) {
tests := []struct {
name string
a, b string
want int
}{
{"equal", "1.0.0", "1.0.0", 0},
{"less major", "1.0.0", "2.0.0", -1},
{"greater major", "2.0.0", "1.0.0", 1},
{"less patch", "1.2.3", "1.2.4", -1},
{"multi-digit minor", "1.10.0", "1.9.0", 1},
{"short vs long equal", "1.0", "1.0.0", 0},
{"real versions", "131.0", "132.0", -1},
// Edge cases
{"single component", "5", "3", 1},
{"single equal", "3", "3", 0},
{"empty vs empty", "", "", 0},
{"empty vs version", "", "1.0", -1},
{"version vs empty", "1.0", "", 1},
{"non-numeric suffix", "1.0.0-beta", "1.0.0-rc1", 0},
{"four components", "1.2.3.4", "1.2.3.5", -1},
{"different lengths padded", "1.0.0.0", "1.0.0", 0},
{"short less", "1.0", "1.0.1", -1},
{"short greater", "1.1", "1.0.9", 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := semverCmp(tt.a, tt.b)
if got != tt.want {
t.Errorf("semverCmp(%q, %q) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
func TestStripNonNumeric(t *testing.T) {
tests := []struct {
input string
want string
}{
{"123", "123"},
{"123abc", "123"},
{"abc", ""},
{"0beta", "0"},
{"", ""},
{"42-rc1", "42"},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := stripNonNumeric(tt.input)
if got != tt.want {
t.Errorf("stripNonNumeric(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestInterfaceCompliance(t *testing.T) {
var _ snack.Manager = (*Snap)(nil)
var _ snack.VersionQuerier = (*Snap)(nil)
var _ snack.Cleaner = (*Snap)(nil)
var _ snack.PackageUpgrader = (*Snap)(nil)
}
// Compile-time interface checks
var (
_ snack.Cleaner = (*Snap)(nil)
_ snack.PackageUpgrader = (*Snap)(nil)
)
func TestCapabilities(t *testing.T) {
caps := snack.GetCapabilities(New())
if !caps.VersionQuery {
t.Error("expected VersionQuery=true")
}
if !caps.Clean {
t.Error("expected Clean=true")
}
// Should be false
if caps.FileOwnership {
t.Error("expected FileOwnership=false")
}
if caps.DryRun {
t.Error("expected DryRun=false")
}
if caps.Hold {
t.Error("expected Hold=false")
}
if caps.RepoManagement {
t.Error("expected RepoManagement=false")
}
if caps.KeyManagement {
t.Error("expected KeyManagement=false")
}
if caps.Groups {
t.Error("expected Groups=false")
}
if caps.NameNormalize {
t.Error("expected NameNormalize=false")
}
if !caps.PackageUpgrade {
t.Error("expected PackageUpgrade=true")
}
}
func TestName(t *testing.T) {
s := New()
if s.Name() != "snap" {
t.Errorf("Name() = %q, want %q", s.Name(), "snap")
}
}