mirror of
https://github.com/gogrlx/snack.git
synced 2026-04-02 05:08:42 -07:00
- 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
458 lines
12 KiB
Go
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")
|
|
}
|
|
}
|