Files
lazysecrets/internal/update/update_test.go
T
keb 4e4014a043
CI / Checks (pull_request) Successful in 39s
PR title / Conventional title (pull_request) Successful in 2s
feat: the update changelog covers every version you missed
Latest reads the last 10 releases in one request instead of only the
newest, so U and lazysecrets update --check show the changes of every
release since the running version, each under its version. Drafts and
pre-releases are skipped. A changelog taller than the screen is cut
with a pointer to the release pages.
2026-10-13 19:44:26 +02:00

211 lines
7.4 KiB
Go

package update
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestNewer(t *testing.T) {
for _, c := range []struct {
current, latest string
want bool
}{
{"0.1.1", "v0.2.0", true},
{"v0.2.0", "v0.2.0", false},
{"0.3.0", "v0.2.0", false},
{"v0.1.2-0.20261009185704-2ba762ce2224", "v0.1.1", false}, // go install of a later commit
{"v0.1.2-0.20261009185704-2ba762ce2224", "v0.1.2", true},
{"dev-2ba762c", "v0.2.0", false},
{"dev", "v0.2.0", false},
{"", "v0.2.0", false},
} {
if got := Newer(c.current, c.latest); got != c.want {
t.Errorf("Newer(%q, %q) = %v", c.current, c.latest, got)
}
}
}
func TestNotes(t *testing.T) {
r := Release{Body: "### Added\n\n- Install with a one-liner (#7)\n\n## Install\n\nOn macOS…"}
if got := r.Notes("0.1.1"); got != "### Added\n\n- Install with a one-liner (#7)" {
t.Errorf("notes %q", got)
}
}
// Latest reads the recent releases, so the notes cover every version
// since the one running.
func TestNotesSince(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/keb/lazysecrets/releases" {
http.NotFound(w, r)
return
}
fmt.Fprint(w, `[
{"tag_name":"v0.4.0-rc1","body":"### Added\n\n- rc","prerelease":true},
{"tag_name":"v0.2.0","body":"### Added\n\n- two\n\n## Install\n\ncurl"},
{"tag_name":"v0.3.0","body":"### Fixed\n\n- three\n\n## Install\n\ncurl","html_url":"https://example.com/v0.3.0"},
{"tag_name":"v0.1.1","body":"### Added\n\n- one"},
{"tag_name":"v9.0.0","body":"draft","draft":true}]`)
}))
t.Cleanup(srv.Close)
r, err := (&Client{Servers: []string{srv.URL + "/keb/lazysecrets"}}).Latest(context.Background())
if err != nil || r.Tag != "v0.3.0" || r.URL != "https://example.com/v0.3.0" {
t.Fatalf("latest %+v %v", r, err)
}
for current, want := range map[string]string{
"0.1.1": "## v0.3.0\n\n### Fixed\n\n- three\n\n## v0.2.0\n\n### Added\n\n- two",
"0.2.0": "### Fixed\n\n- three",
"dev-2ba762c": "### Fixed\n\n- three",
} {
if got := r.Notes(current); got != want {
t.Errorf("notes since %s: %q, want %q", current, got, want)
}
}
}
func TestManaged(t *testing.T) {
if Managed("/opt/homebrew/Cellar/lazysecrets/0.2.0/bin/lazysecrets") == "" || Managed("/nix/store/abc-lazysecrets/bin/lazysecrets") == "" {
t.Error("package managers not recognised")
}
if m := Managed("/Users/x/.local/bin/lazysecrets"); m != "" {
t.Errorf("~/.local/bin is ours: %q", m)
}
}
// server publishes v0.2.0 with a "binary" (a script) printing its version.
func server(t *testing.T, tamper bool) *httptest.Server {
t.Helper()
name := fmt.Sprintf("lazysecrets_0.2.0_%s_%s", runtime.GOOS, runtime.GOARCH)
var archive bytes.Buffer
gz := gzip.NewWriter(&archive)
tw := tar.NewWriter(gz)
script := []byte("#!/bin/sh\necho lazysecrets 0.2.0\n")
tw.WriteHeader(&tar.Header{Name: name + "/lazysecrets", Mode: 0o755, Size: int64(len(script)), Typeflag: tar.TypeReg})
tw.Write(script)
tw.Close()
gz.Close()
sum := sha256.Sum256(archive.Bytes())
if tamper {
sum[0] ^= 0xff
}
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/repos/keb/lazysecrets/releases", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `[{"tag_name":"v0.2.0","body":"### Added\n\n- x\n\n## Install\n","html_url":"https://example.com/r"}]`)
})
mux.HandleFunc("/keb/lazysecrets/releases/download/v0.2.0/checksums.txt", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintf(w, "%s %s.tar.gz\n", hex.EncodeToString(sum[:]), name)
})
mux.HandleFunc("/keb/lazysecrets/releases/download/v0.2.0/"+name+".tar.gz", func(w http.ResponseWriter, _ *http.Request) {
w.Write(archive.Bytes())
})
srv := httptest.NewServer(mux)
t.Cleanup(srv.Close)
return srv
}
func TestInstall(t *testing.T) {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
t.Skip("releases are for macOS and Linux")
}
srv := server(t, false)
c := &Client{Servers: []string{srv.URL + "/keb/lazysecrets"}}
r, err := c.Latest(context.Background())
if err != nil || r.Tag != "v0.2.0" {
t.Fatalf("latest %+v %v", r, err)
}
exe := filepath.Join(t.TempDir(), "lazysecrets")
os.WriteFile(exe, []byte("old"), 0o755)
if err := c.Install(context.Background(), r, exe); err != nil {
t.Fatal(err)
}
if b, _ := os.ReadFile(exe); !strings.Contains(string(b), "echo lazysecrets 0.2.0") {
t.Errorf("not replaced: %q", b)
}
if left, _ := filepath.Glob(filepath.Join(filepath.Dir(exe), ".lazysecrets-update-*")); len(left) > 0 {
t.Errorf("left behind: %v", left)
}
}
// A server that's down, or doesn't have the release, is skipped.
func TestFallsBackToTheNextServer(t *testing.T) {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
t.Skip("releases are for macOS and Linux")
}
down := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "down", http.StatusBadGateway)
}))
t.Cleanup(down.Close)
srv := server(t, false)
c := &Client{Servers: []string{down.URL + "/keb/lazysecrets", srv.URL + "/keb/lazysecrets"}}
r, err := c.Latest(context.Background())
if err != nil || r.Tag != "v0.2.0" {
t.Fatalf("latest %+v %v", r, err)
}
exe := filepath.Join(t.TempDir(), "lazysecrets")
os.WriteFile(exe, []byte("old"), 0o755)
if err := c.Install(context.Background(), r, exe); err != nil {
t.Fatal(err)
}
if b, _ := os.ReadFile(exe); !strings.Contains(string(b), "echo lazysecrets 0.2.0") {
t.Errorf("not replaced: %q", b)
}
// Every server down: both say why.
c = &Client{Servers: []string{down.URL + "/a/b", down.URL + "/c/d"}}
if _, err := c.Latest(context.Background()); err == nil || strings.Count(err.Error(), "502") != 2 {
t.Errorf("all down: %v", err)
}
}
func TestAPI(t *testing.T) {
for repo, want := range map[string]string{
"https://github.com/karleinarb/lazysecrets": "https://api.github.com/repos/karleinarb/lazysecrets",
"https://git.kebcloud.no/keb/lazysecrets/": "https://git.kebcloud.no/api/v1/repos/keb/lazysecrets",
"https://codeberg.org/someone/lazysecrets": "https://codeberg.org/api/v1/repos/someone/lazysecrets",
"https://git.kebcloud.no/keb": "",
"git.kebcloud.no/keb/lazysecrets": "",
"https://github.com/karleinarb/lazysecrets/x": "",
} {
got, err := API(repo)
if got != want || (err != nil) != (want == "") {
t.Errorf("API(%q) = %q, %v", repo, got, err)
}
}
}
func TestInstallRefusesATamperedArchive(t *testing.T) {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
t.Skip("releases are for macOS and Linux")
}
srv := server(t, true)
c := &Client{Servers: []string{srv.URL + "/keb/lazysecrets"}}
exe := filepath.Join(t.TempDir(), "lazysecrets")
os.WriteFile(exe, []byte("old"), 0o755)
err := c.Install(context.Background(), Release{Tag: "v0.2.0"}, exe)
if err == nil || !strings.Contains(err.Error(), "doesn't match checksums.txt") {
t.Fatalf("err %v", err)
}
if b, _ := os.ReadFile(exe); string(b) != "old" {
t.Error("replaced anyway")
}
}
func TestNotesShowLinksAsText(t *testing.T) {
r := Release{Body: "### Added\n\n- Search ([#11](https://git.example.com/pulls/11)) ([65dc2d7](https://git.example.com/commit/65dc2d7abc))\n\n## Install\n\ncurl …"}
if got, want := r.Notes("0.1.1"), "### Added\n\n- Search (#11) (65dc2d7)"; got != want {
t.Errorf("notes %q, want %q", got, want)
}
}