238 lines
5.8 KiB
Go
238 lines
5.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNormalizeVersion(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
want string
|
|
wantOK bool
|
|
}{
|
|
{"dev", "", false},
|
|
{"", "", false},
|
|
{" ", "", false},
|
|
{"abc", "", false},
|
|
{"v1.2.3", "v1.2.3", true},
|
|
{"1.2.3", "v1.2.3", true},
|
|
{"v1.2.3-rc1", "v1.2.3-rc1", true},
|
|
{" v0.10.0 ", "v0.10.0", true},
|
|
}
|
|
for _, tt := range tests {
|
|
got, ok := normalizeVersion(tt.in)
|
|
if ok != tt.wantOK || got != tt.want {
|
|
t.Errorf("normalizeVersion(%q) = (%q, %v), want (%q, %v)", tt.in, got, ok, tt.want, tt.wantOK)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestVerifyChecksum(t *testing.T) {
|
|
content := []byte("hello world")
|
|
sum := sha256.Sum256(content)
|
|
hash := hex.EncodeToString(sum[:])
|
|
|
|
t.Run("match", func(t *testing.T) {
|
|
checksumFile := []byte(fmt.Sprintf("%s reasonix-linux-amd64.tar.gz\n", hash))
|
|
if err := verifyChecksum(content, "reasonix-linux-amd64.tar.gz", checksumFile); err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("mismatch", func(t *testing.T) {
|
|
checksumFile := []byte(fmt.Sprintf("%s reasonix-linux-amd64.tar.gz\n", "0000000000000000000000000000000000000000000000000000000000000000"))
|
|
if err := verifyChecksum(content, "reasonix-linux-amd64.tar.gz", checksumFile); err == nil {
|
|
t.Error("expected checksum mismatch error")
|
|
}
|
|
})
|
|
|
|
t.Run("not found", func(t *testing.T) {
|
|
checksumFile := []byte(fmt.Sprintf("%s reasonix-darwin-arm64.tar.gz\n", hash))
|
|
if err := verifyChecksum(content, "reasonix-linux-amd64.tar.gz", checksumFile); err == nil {
|
|
t.Error("expected not-found error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpgradeSuccessMessageIncludesCurrentAndLatestVersions(t *testing.T) {
|
|
cur := "v1.10.0"
|
|
latest := "v1.11.0"
|
|
|
|
got := upgradeSuccessMessage(cur, latest)
|
|
if !strings.Contains(got, cur) {
|
|
t.Fatalf("success message %q does not include current version %q", got, cur)
|
|
}
|
|
if !strings.Contains(got, latest) {
|
|
t.Fatalf("success message %q does not include latest version %q", got, latest)
|
|
}
|
|
if strings.Index(got, cur) > strings.Index(got, latest) {
|
|
t.Fatalf("success message %q should report current version before latest version", got)
|
|
}
|
|
if strings.Contains(got, "%!") {
|
|
t.Fatalf("success message %q contains a missing fmt argument marker", got)
|
|
}
|
|
}
|
|
|
|
func TestExtractFromTarGz(t *testing.T) {
|
|
// Build a .tar.gz in memory containing a "reasonix" entry.
|
|
var buf bytes.Buffer
|
|
gw := gzip.NewWriter(&buf)
|
|
tw := tar.NewWriter(gw)
|
|
|
|
body := []byte("fake binary content")
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: "reasonix",
|
|
Mode: 0o755,
|
|
Size: int64(len(body)),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := tw.Write(body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := gw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, err := extractFromTarGz(buf.Bytes(), "reasonix")
|
|
if err != nil {
|
|
t.Fatalf("extractFromTarGz: %v", err)
|
|
}
|
|
if !bytes.Equal(got, body) {
|
|
t.Errorf("extracted body = %q, want %q", got, body)
|
|
}
|
|
}
|
|
|
|
func TestExtractFromTarGz_Nested(t *testing.T) {
|
|
// Archives from goreleaser have the binary at the root with its name.
|
|
var buf bytes.Buffer
|
|
gw := gzip.NewWriter(&buf)
|
|
tw := tar.NewWriter(gw)
|
|
|
|
body := []byte("nested binary")
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: "reasonix-linux-amd64/reasonix",
|
|
Mode: 0o755,
|
|
Size: int64(len(body)),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := tw.Write(body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := gw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got, err := extractFromTarGz(buf.Bytes(), "reasonix")
|
|
if err != nil {
|
|
t.Fatalf("extractFromTarGz: %v", err)
|
|
}
|
|
if !bytes.Equal(got, body) {
|
|
t.Errorf("extracted body = %q, want %q", got, body)
|
|
}
|
|
}
|
|
|
|
func TestExtractFromTarGz_NotFound(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
gw := gzip.NewWriter(&buf)
|
|
tw := tar.NewWriter(gw)
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: "other-file.txt",
|
|
Mode: 0o644,
|
|
Size: 3,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tw.Write([]byte("foo"))
|
|
tw.Close()
|
|
gw.Close()
|
|
|
|
_, err := extractFromTarGz(buf.Bytes(), "reasonix")
|
|
if err == nil {
|
|
t.Error("expected error for missing binary")
|
|
}
|
|
}
|
|
|
|
func TestIsCLITag(t *testing.T) {
|
|
tests := []struct {
|
|
tag string
|
|
want bool
|
|
}{
|
|
{"v1.6.0", true},
|
|
{"v0.1.0", true},
|
|
{"v2.0.0-rc.1", true},
|
|
{"desktop-v1.5.0", false},
|
|
{"npm-v1.4.0", false},
|
|
{"", false},
|
|
{"v", false},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := isCLITag(tt.tag); got != tt.want {
|
|
t.Errorf("isCLITag(%q) = %v, want %v", tt.tag, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHumanSize(t *testing.T) {
|
|
tests := []struct {
|
|
bytes int64
|
|
want string
|
|
}{
|
|
{500, "500 B"},
|
|
{2048, "2.0 KiB"},
|
|
{19_000_000, "18.1 MiB"},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := humanSize(tt.bytes); got != tt.want {
|
|
t.Errorf("humanSize(%d) = %q, want %q", tt.bytes, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPickCLIRelease(t *testing.T) {
|
|
pick := func(rels []ghRelease) string {
|
|
if r := pickCLIRelease(rels); r != nil {
|
|
return r.TagName
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Skips foreign namespaces (GitHub's "latest" can be a desktop-v release).
|
|
mixed := []ghRelease{
|
|
{TagName: "desktop-v1.6.0"},
|
|
{TagName: "npm-v1.4.0"},
|
|
{TagName: "v1.6.0"},
|
|
}
|
|
if got := pick(mixed); got != "v1.6.0" {
|
|
t.Errorf("foreign namespaces: got %q, want v1.6.0", got)
|
|
}
|
|
|
|
// The 1.x line ships as rc on npm @next, so a newer prerelease must be
|
|
// selected, not skipped — `reasonix upgrade` always moves to the newest 1.x.
|
|
withRC := []ghRelease{
|
|
{TagName: "v1.7.0-rc.1"},
|
|
{TagName: "v1.6.0"},
|
|
}
|
|
if got := pick(withRC); got != "v1.7.0-rc.1" {
|
|
t.Errorf("newest 1.x (incl. rc) must win: got %q, want v1.7.0-rc.1", got)
|
|
}
|
|
|
|
if got := pick([]ghRelease{{TagName: "desktop-v1.0.0"}}); got != "" {
|
|
t.Errorf("no CLI release should return nil, got %q", got)
|
|
}
|
|
}
|