Files
snack/brew/brew_test.go
Tai Groot 934c6610c5 feat: add Homebrew provider, implement NameNormalizer across all managers
- Add brew package for Homebrew support on macOS and Linux
- Implement NameNormalizer interface (NormalizeName, ParseArch) for all providers
- Add darwin platform detection with Homebrew as default
- Consolidate capabilities by removing separate *_linux.go/*_other.go files
- Update tests for new capability expectations
- Add comprehensive tests for AUR and brew providers
- Update README with capability matrix and modern Target API usage

💘 Generated with Crush

Assisted-by: AWS Claude Opus 4.5 via Crush <crush@charm.land>
2026-03-05 20:40:32 -05:00

224 lines
5.2 KiB
Go

//go:build darwin || linux
package brew
import (
"testing"
"github.com/gogrlx/snack"
)
// Compile-time interface checks
var (
_ snack.Manager = (*Brew)(nil)
_ snack.VersionQuerier = (*Brew)(nil)
_ snack.Cleaner = (*Brew)(nil)
_ snack.FileOwner = (*Brew)(nil)
_ snack.NameNormalizer = (*Brew)(nil)
)
func TestNew(t *testing.T) {
b := New()
if b == nil {
t.Fatal("New() returned nil")
}
}
func TestName(t *testing.T) {
b := New()
if b.Name() != "brew" {
t.Errorf("Name() = %q, want %q", b.Name(), "brew")
}
}
func TestParseBrewList(t *testing.T) {
input := `git 2.43.0
go 1.21.6
vim 9.1.0
`
pkgs := parseBrewList(input)
if len(pkgs) != 3 {
t.Fatalf("expected 3 packages, got %d", len(pkgs))
}
if pkgs[0].Name != "git" || pkgs[0].Version != "2.43.0" {
t.Errorf("unexpected first package: %+v", pkgs[0])
}
if !pkgs[0].Installed {
t.Error("expected Installed=true")
}
}
func TestParseBrewList_Empty(t *testing.T) {
pkgs := parseBrewList("")
if len(pkgs) != 0 {
t.Errorf("expected 0 packages, got %d", len(pkgs))
}
}
func TestParseBrewList_SinglePackage(t *testing.T) {
input := "curl 8.6.0\n"
pkgs := parseBrewList(input)
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Name != "curl" {
t.Errorf("expected name=curl, got %q", pkgs[0].Name)
}
}
func TestParseBrewSearch(t *testing.T) {
input := `==> Formulae
git
git-absorb
git-annex
==> Casks
git-credential-manager
`
pkgs := parseBrewSearch(input)
if len(pkgs) != 4 {
t.Fatalf("expected 4 packages, got %d", len(pkgs))
}
if pkgs[0].Name != "git" {
t.Errorf("unexpected first package: %+v", pkgs[0])
}
}
func TestParseBrewSearch_Empty(t *testing.T) {
pkgs := parseBrewSearch("")
if len(pkgs) != 0 {
t.Errorf("expected 0 packages, got %d", len(pkgs))
}
}
func TestParseBrewInfo(t *testing.T) {
input := `{"formulae":[{"name":"git","full_name":"git","desc":"Distributed revision control system","versions":{"stable":"2.43.0"},"installed":[{"version":"2.43.0"}]}],"casks":[]}`
pkg := parseBrewInfo(input)
if pkg == nil {
t.Fatal("expected non-nil package")
}
if pkg.Name != "git" {
t.Errorf("expected name 'git', got %q", pkg.Name)
}
if pkg.Version != "2.43.0" {
t.Errorf("unexpected version: %q", pkg.Version)
}
if !pkg.Installed {
t.Error("expected Installed=true")
}
}
func TestParseBrewInfo_NotInstalled(t *testing.T) {
input := `{"formulae":[{"name":"wget","full_name":"wget","desc":"Internet file retriever","versions":{"stable":"1.21"},"installed":[]}],"casks":[]}`
pkg := parseBrewInfo(input)
if pkg == nil {
t.Fatal("expected non-nil package")
}
if pkg.Installed {
t.Error("expected Installed=false")
}
}
func TestParseBrewInfo_Cask(t *testing.T) {
input := `{"formulae":[],"casks":[{"token":"visual-studio-code","name":["Visual Studio Code"],"desc":"Open-source code editor","version":"1.85.0"}]}`
pkg := parseBrewInfo(input)
if pkg == nil {
t.Fatal("expected non-nil package")
}
if pkg.Name != "visual-studio-code" {
t.Errorf("expected name 'visual-studio-code', got %q", pkg.Name)
}
}
func TestNormalizeName(t *testing.T) {
tests := []struct {
input string
want string
}{
{"git", "git"},
{"python@3.12", "python"},
{"node@18", "node"},
{"go", "go"},
{"ruby@3.2", "ruby"},
}
b := New()
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := b.NormalizeName(tt.input)
if got != tt.want {
t.Errorf("NormalizeName(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestParseArch(t *testing.T) {
tests := []struct {
input string
wantName string
wantArch string
}{
{"git", "git", ""},
// Homebrew doesn't embed arch in names - @ is version suffix
{"python@3.12", "python@3.12", ""},
{"node@18", "node@18", ""},
}
b := New()
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
gotName, gotArch := b.ParseArch(tt.input)
if gotName != tt.wantName || gotArch != tt.wantArch {
t.Errorf("ParseArch(%q) = (%q, %q), want (%q, %q)",
tt.input, gotName, gotArch, tt.wantName, tt.wantArch)
}
})
}
}
func TestCapabilities(t *testing.T) {
caps := snack.GetCapabilities(New())
tests := []struct {
name string
got bool
want bool
}{
{"VersionQuery", caps.VersionQuery, true},
{"Clean", caps.Clean, true},
{"FileOwnership", caps.FileOwnership, true},
{"NameNormalize", caps.NameNormalize, true},
// Homebrew does not support these
{"Hold", caps.Hold, false},
{"RepoManagement", caps.RepoManagement, false},
{"KeyManagement", caps.KeyManagement, false},
{"Groups", caps.Groups, false},
{"DryRun", caps.DryRun, false},
{"PackageUpgrade", caps.PackageUpgrade, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.got != tt.want {
t.Errorf("%s = %v, want %v", tt.name, tt.got, tt.want)
}
})
}
}
func TestInterfaceNonCompliance(t *testing.T) {
var m snack.Manager = New()
if _, ok := m.(snack.Holder); ok {
t.Error("Brew should not implement Holder")
}
if _, ok := m.(snack.RepoManager); ok {
t.Error("Brew should not implement RepoManager")
}
if _, ok := m.(snack.KeyManager); ok {
t.Error("Brew should not implement KeyManager")
}
if _, ok := m.(snack.Grouper); ok {
t.Error("Brew should not implement Grouper")
}
}