Files
lazysecrets/internal/ui/app/update_test.go
T
keb a81fcaa5a6 fix: unambiguous repo key names, clearer config and key names, and a newer lazysecrets's files no longer stop an older one (#66)
Changes that would break configs or links once people use lazysecrets, made while nobody does yet.

- **Repo key names**: a dot or bracket inside a key is escaped (`app\.name`), so links to it resolve to that key and not to `app` → `name`. Dotenv files are flat, so their names stay as they are.
- **Config**: `updates.updateSources` is now `updates.sources`. A negative provider timeout, or `--timeout` of 0 or less, is an error instead of making every request fail.
- **Keybindings**: `lists.share` is now `lists.copyLink`, `lists.search` is `lists.goTo` and `lists.enter` is `lists.open`, matching what they do.
- **Version skew**, e.g. a lazysecrets.nvim theme file made for a newer lazysecrets:
  - an unknown theme role is skipped with a warning instead of stopping startup;
  - a state file from a newer version is read but not saved to, with a warning;
  - a link with an unknown provider suggests `lazysecrets update`.

Reviewed-on: #66
Co-authored-by: Karl-Einar Bergstrøm <karl-einarb@outlook.com>
2026-10-13 23:28:44 +02:00

133 lines
4.4 KiB
Go

package app
import (
"context"
"errors"
"path/filepath"
"strings"
"testing"
"time"
"git.kebcloud.no/keb/lazysecrets/internal/store"
"git.kebcloud.no/keb/lazysecrets/internal/update"
)
// fakeUpdater offers latest and records what it installs.
type fakeUpdater struct {
latest update.Release
err error
asked int
installed string
}
func (f *fakeUpdater) updater() *Updater {
return &Updater{
Latest: func(context.Context) (update.Release, error) {
f.asked++
return f.latest, f.err
},
Install: func(_ context.Context, r update.Release) error {
f.installed = r.Tag
return nil
},
}
}
var v020 = update.Release{
Tag: "v0.2.0",
Body: "### Added\n\n- Install with a one-liner (#7)\n\n## Install\n\ncurl …",
URL: "https://git.example.com/keb/lazysecrets/releases/tag/v0.2.0",
}
func TestUpdate(t *testing.T) {
t.Parallel()
f := &fakeUpdater{latest: v020}
mem, _ := store.Load(filepath.Join(t.TempDir(), "state.json"))
d := newDriverOpts(t, Options{Version: "0.1.1", Updater: f.updater(), UpdateCheck: true, Store: mem})
if f.asked != 1 {
t.Fatalf("asked %d times at startup", f.asked)
}
if scr := d.screen(); !strings.Contains(scr, "[0] lazysecrets v0.1.1") || !strings.Contains(scr, "↑ v0.2.0 U") {
t.Errorf("no update in the status pane:\n%s", scr)
}
// U: the changelog, without the install notes; u installs.
d.keys("U")
scr := d.screen()
for _, want := range []string{"lazysecrets v0.2.0", "You have v0.1.1.", "Install with a one-liner (#7)", "Update now", "Open the release page"} {
if !strings.Contains(scr, want) {
t.Errorf("missing %q:\n%s", want, scr)
}
}
if strings.Contains(scr, "curl") {
t.Errorf("install notes shown:\n%s", scr)
}
d.keys("u")
if f.installed != "v0.2.0" {
t.Fatalf("installed %q", f.installed)
}
if !strings.Contains(d.screen(), "updated to lazysecrets v0.2.0 · restart lazysecrets to use it") ||
!strings.Contains(d.screen(), "→ v0.2.0 (restart)") {
t.Errorf("after updating:\n%s", d.screen())
}
// Within a day, the next start uses what it found: no request.
f2 := &fakeUpdater{latest: v020}
d2 := newDriverOpts(t, Options{Version: "0.1.1", Updater: f2.updater(), UpdateCheck: true, Store: mem})
if f2.asked != 0 || !strings.Contains(d2.screen(), "↑ v0.2.0") {
t.Errorf("asked %d times within a day:\n%s", f2.asked, d2.screen())
}
// A day later it asks again.
mem.SetUpdateCheck(store.UpdateCheck{At: time.Now().Add(-25 * time.Hour), Tag: "v0.2.0"})
newDriverOpts(t, Options{Version: "0.1.1", Updater: f2.updater(), UpdateCheck: true, Store: mem})
if f2.asked != 1 {
t.Errorf("asked %d times a day later", f2.asked)
}
// With autoCheckInterval: 1h, two hours on is long enough.
mem.SetUpdateCheck(store.UpdateCheck{At: time.Now().Add(-2 * time.Hour), Tag: "v0.2.0"})
newDriverOpts(t, Options{Version: "0.1.1", Updater: f2.updater(), UpdateCheck: true, UpdateEvery: time.Hour, Store: mem})
if f2.asked != 2 {
t.Errorf("asked %d times, want a look after the hour", f2.asked)
}
}
func TestUpdateAsked(t *testing.T) {
t.Parallel()
mem, _ := store.Load(filepath.Join(t.TempDir(), "state.json"))
// Not looking on its own: U looks.
f := &fakeUpdater{latest: update.Release{Tag: "v0.1.1"}}
d := newDriverOpts(t, Options{Version: "0.1.1", Updater: f.updater(), Store: mem})
if f.asked != 0 {
t.Fatal("looked without updateCheck")
}
d.keys("U")
if f.asked != 1 || !strings.Contains(d.screen(), "lazysecrets v0.1.1 is the latest (v0.1.1)") {
t.Errorf("asked %d:\n%s", f.asked, d.screen())
}
// The daily check stays quiet when offline; U says why.
f.err = errors.New("no network")
d.keys("U")
if !strings.Contains(d.screen(), "couldn't check for a newer lazysecrets: no network") {
t.Errorf("no error:\n%s", d.screen())
}
// A development build isn't offered releases.
dev := newDriverOpts(t, Options{Version: "dev-2ba762c", Updater: (&fakeUpdater{latest: v020}).updater(), UpdateCheck: true, Store: mem})
if strings.Contains(dev.screen(), "↑") {
t.Errorf("dev build offered an update:\n%s", dev.screen())
}
dev.keys("U")
if !strings.Contains(dev.screen(), "development build") {
t.Errorf("U on a dev build:\n%s", dev.screen())
}
}
func TestStartupWarning(t *testing.T) {
t.Parallel()
d := newDriverOpts(t, Options{Warning: "theme: skipped unknown activeBorder"})
if !strings.Contains(d.screen(), "theme: skipped unknown activeBorder") {
t.Errorf("no warning:\n%s", d.screen())
}
}