Files
2026-05-27 17:26:53 -05:00

410 lines
10 KiB
Go

package main
import (
"encoding/json"
"os"
"path/filepath"
"sync"
"testing"
"time"
)
func TestCustomPackageResolvers(t *testing.T) {
defer resetMocks()
osName = "linux"
archName = "x86_64"
p := &CustomPackage{
Name: "my-pkg",
Version: "1.2.3",
URLTemplate: "http://example.com/download/{version}/{os}/{arch}/my-pkg.tar.gz",
SHA256URLTemplate: "http://example.com/download/{version}/{os}/{arch}/my-pkg.tar.gz.minisig",
SHA256Map: map[string]string{
"linux-x86_64": "aabbcc",
},
}
if p.resolveURL() != "http://example.com/download/1.2.3/linux/x86_64/my-pkg.tar.gz" {
t.Errorf("unexpected URL: %q", p.resolveURL())
}
if p.resolveSHA256URL() != "http://example.com/download/1.2.3/linux/x86_64/my-pkg.tar.gz.minisig" {
t.Errorf("unexpected SHA256 URL: %q", p.resolveSHA256URL())
}
if p.resolvedSHA256() != "aabbcc" {
t.Errorf("unexpected resolved SHA256: %q", p.resolvedSHA256())
}
if p.displayName() != "my-pkg-1.2.3" {
t.Errorf("unexpected display name: %q", p.displayName())
}
pNoVer := &CustomPackage{Name: "simple"}
if pNoVer.displayName() != "simple" {
t.Errorf("unexpected display name: %q", pNoVer.displayName())
}
}
func TestCmpSemver(t *testing.T) {
tests := []struct {
a, b string
expected int
}{
{"1.2.3", "1.2.3", 0},
{"1.2.3", "1.2.4", -1},
{"1.3.0", "1.2.9", 1},
{"2.0.0", "10.0.0", -1},
{"1.10.2", "1.2.3", 1},
}
for _, tt := range tests {
res := cmpSemver(tt.a, tt.b)
// Normalize to -1, 0, 1
actual := 0
if res < 0 {
actual = -1
} else if res > 0 {
actual = 1
}
if actual != tt.expected {
t.Errorf("cmpSemver(%q, %q) expected %d, got %d (raw %d)", tt.a, tt.b, tt.expected, actual, res)
}
}
}
func TestVerifyArchive(t *testing.T) {
defer resetMocks()
tmp := t.TempDir()
archive := filepath.Join(tmp, "archive.tar.gz")
os.WriteFile(archive, []byte("archive-bytes"), 0644)
// Hash of "archive-bytes" is 0c982986710a026635603031674053ca851fc0e3ea760094a34f59b84f7f6da6
p := &CustomPackage{
Name: "test",
SHA256: "0c982986710a026635603031674053ca851fc0e3ea760094a34f59b84f7f6da6",
}
if !verifyArchive(archive, p) {
t.Error("expected verification to pass")
}
p.SHA256 = "incorrect-hash"
if verifyArchive(archive, p) {
t.Error("expected verification to fail")
}
}
func TestResolveLatestGo(t *testing.T) {
defer resetMocks()
osName = "linux"
archName = "x86_64"
fetchJSON = func(url string, v any) bool {
// Mock Go releases API response
// This encodes mock data into v (raw JSON Message decoding)
data := `[
{
"version": "go1.21.3",
"files": [
{
"filename": "go1.21.3.linux-amd64.tar.gz",
"kind": "archive",
"sha256": "go-sha-value"
}
]
}
]`
json.Unmarshal([]byte(data), v)
return true
}
version, sha, ok := resolveLatestGo(nil)
if !ok || version != "1.21.3" || sha != "go-sha-value" {
t.Errorf("unexpected resolve latest Go result: version=%q, sha=%q, ok=%v", version, sha, ok)
}
}
func TestResolveLatestFirecracker(t *testing.T) {
defer resetMocks()
isMacOS = false
archName = "x86_64"
fetchJSON = func(url string, v any) bool {
rel := v.(*ghRelease)
rel.TagName = "v1.5.0"
rel.Assets = []ghAsset{
{Name: "firecracker-v1.5.0-x86_64.tgz.sha256.txt", BrowserDownloadURL: "http://sha-url"},
}
return true
}
fetchText = func(url string) string {
return "firecracker-sha-value firecracker-v1.5.0-x86_64.tgz"
}
version, sha, ok := resolveLatestFirecracker(nil)
if !ok || version != "1.5.0" || sha != "firecracker-sha-value" {
t.Errorf("unexpected resolve latest firecracker result: version=%q, sha=%q, ok=%v", version, sha, ok)
}
}
func TestResolveLatestZig(t *testing.T) {
defer resetMocks()
osName = "linux"
archName = "x86_64"
fetchJSON = func(url string, v any) bool {
data := `{
"0.11.0": {
"x86_64-linux": {
"shasum": "zig-sha-value"
}
}
}`
json.Unmarshal([]byte(data), v)
return true
}
version, sha, ok := resolveLatestZig(nil)
if !ok || version != "0.11.0" || sha != "zig-sha-value" {
t.Errorf("unexpected resolve latest zig result: version=%q, sha=%q, ok=%v", version, sha, ok)
}
}
func TestIsCustomPkgInstalled(t *testing.T) {
defer resetMocks()
// Pip installed check
hasCmd = func(name string) bool {
return name == "python3"
}
probe = func(argv []string, timeout time.Duration) (CmdResult, bool) {
return CmdResult{ExitCode: 0}, true
}
pPip := &CustomPackage{Name: "pip"}
installed, _ := isCustomPkgInstalled(pPip)
if !installed {
t.Error("expected pip to be installed")
}
// Go check (installed check via default install path)
osStat = func(name string) (os.FileInfo, error) {
if name == "/usr/local/go" {
return nil, nil // exists
}
return nil, os.ErrNotExist
}
pGo := &CustomPackage{Name: "go"}
installedGo, _ := isCustomPkgInstalled(pGo)
if !installedGo {
t.Error("expected go to be installed")
}
}
func TestExpandHomeAndDefaultInstallPath(t *testing.T) {
defer resetMocks()
// Test expandHome
t.Setenv("HOME", "/my/home")
expanded := expandHome("~/test")
if expanded != "/my/home/test" {
t.Errorf("expected /my/home/test, got %q", expanded)
}
notExpanded := expandHome("/other/path")
if notExpanded != "/other/path" {
t.Errorf("expected /other/path, got %q", notExpanded)
}
// Test defaultInstallPath
p := &CustomPackage{Name: "go"}
if defaultInstallPath(p) != "/usr/local/go" {
t.Errorf("expected /usr/local/go, got %q", defaultInstallPath(p))
}
pUnknown := &CustomPackage{Name: "unknown"}
if defaultInstallPath(pUnknown) != "" {
t.Errorf("expected empty path, got %q", defaultInstallPath(pUnknown))
}
}
func TestUrlArchOK(t *testing.T) {
defer resetMocks()
archName = "x86_64"
p := &CustomPackage{Name: "test", URLTemplate: "http://example.com/test-x86_64.tar.gz"}
if !urlArchOK(p) {
t.Error("expected urlArchOK to return true for matching arch")
}
pBad := &CustomPackage{Name: "test", URLTemplate: "http://example.com/test-aarch64.tar.gz"}
if urlArchOK(pBad) {
t.Error("expected urlArchOK to return false for mismatching arch")
}
}
func TestInstallCustomPackages(t *testing.T) {
defer resetMocks()
hasCmd = func(name string) bool { return true }
download = func(url, dest string) bool {
// Write valid checksum file so it passes verification
// SHA256 of "content" is 751a073f248535132b178652553f1f317b3f1f90be68c078021481e33d443224
os.WriteFile(dest, []byte("content"), 0644)
return true
}
var (
runCmdMu sync.Mutex
runCmdCalls [][]string
)
runCmd = func(argv []string, opts CmdOpts) CmdResult {
runCmdMu.Lock()
runCmdCalls = append(runCmdCalls, argv)
runCmdMu.Unlock()
return CmdResult{ExitCode: 0}
}
pkgs := []*CustomPackage{
{
Name: "go",
Version: "1.21.0",
URLTemplate: "http://example.com/go.tar.gz",
SHA256: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73",
},
{
Name: "zig",
Version: "0.11.0",
URLTemplate: "http://example.com/zig.tar.gz",
SHA256: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73",
},
}
installCustomPackages(pkgs)
runCmdMu.Lock()
defer runCmdMu.Unlock()
// Verify that we executed tar/mv/ln etc commands via runCmd
hasTar := false
for _, call := range runCmdCalls {
if call[0] == "tar" {
hasTar = true
}
}
if !hasTar {
t.Errorf("expected tar command to be executed, got: %v", runCmdCalls)
}
}
func TestInstallFirecracker(t *testing.T) {
defer resetMocks()
tmp := t.TempDir()
archive := filepath.Join(tmp, "firecracker.tgz")
runCmd = func(argv []string, opts CmdOpts) CmdResult {
if argv[0] == "tar" {
dummyBin := filepath.Join(tmp, "firecracker-v1.5.0")
os.WriteFile(dummyBin, []byte("binary-content"), 0755)
}
return CmdResult{ExitCode: 0}
}
installFirecracker(archive, tmp)
runCmd = func(argv []string, opts CmdOpts) CmdResult {
return CmdResult{ExitCode: 1}
}
installFirecracker(archive, tmp)
runCmd = func(argv []string, opts CmdOpts) CmdResult {
return CmdResult{ExitCode: 0}
}
installFirecracker(archive, tmp)
}
func TestInstallNeovim(t *testing.T) {
defer resetMocks()
tmp := t.TempDir()
osName = "linux"
archName = "x86_64"
fetchJSON = func(url string, v any) bool {
rel := v.(*ghRelease)
rel.Assets = []ghAsset{
{
Name: "nvim-linux-x86_64.tar.gz",
BrowserDownloadURL: "http://example.com/nvim.tar.gz",
Digest: "sha256:0c982986710a026635603031674053ca851fc0e3ea760094a34f59b84f7f6da6",
},
}
return true
}
download = func(url, dest string) bool {
os.WriteFile(dest, []byte("archive-bytes"), 0644)
return true
}
runCmd = func(argv []string, opts CmdOpts) CmdResult {
return CmdResult{ExitCode: 0}
}
installNeovim(nil, tmp)
fetchJSON = func(url string, v any) bool { return false }
installNeovim(nil, tmp)
fetchJSON = func(url string, v any) bool {
rel := v.(*ghRelease)
rel.Assets = []ghAsset{{Name: "other-name"}}
return true
}
installNeovim(nil, tmp)
fetchJSON = func(url string, v any) bool {
rel := v.(*ghRelease)
rel.Assets = []ghAsset{{Name: "nvim-linux-x86_64.tar.gz", Digest: "bad-digest"}}
return true
}
installNeovim(nil, tmp)
fetchJSON = func(url string, v any) bool {
rel := v.(*ghRelease)
rel.Assets = []ghAsset{{Name: "nvim-linux-x86_64.tar.gz", Digest: "sha256:0c982986710a026635603031674053ca851fc0e3ea760094a34f59b84f7f6da6"}}
return true
}
download = func(url, dest string) bool { return false }
installNeovim(nil, tmp)
download = func(url, dest string) bool {
os.WriteFile(dest, []byte("different-bytes"), 0644)
return true
}
installNeovim(nil, tmp)
}
func TestResolveLatestEdgeCases(t *testing.T) {
defer resetMocks()
pkg := &CustomPackage{Name: "test", FetchLatest: "nonexistent"}
resolveLatest(pkg)
latestResolvers["panic-resolver"] = func(pkg *CustomPackage) (string, string, bool) {
panic("simulated panic")
}
pkgPanic := &CustomPackage{Name: "test", FetchLatest: "panic-resolver", Version: "1.0.0"}
resolveLatest(pkgPanic)
latestResolvers["fail-resolver"] = func(pkg *CustomPackage) (string, string, bool) {
return "", "", false
}
pkgFail := &CustomPackage{Name: "test", FetchLatest: "fail-resolver", Version: "1.0.0"}
resolveLatest(pkgFail)
latestResolvers["same-resolver"] = func(pkg *CustomPackage) (string, string, bool) {
return "1.0.0", "hash", true
}
pkgSame := &CustomPackage{Name: "test", FetchLatest: "same-resolver", Version: "1.0.0"}
resolveLatest(pkgSame)
}