Files
magpie/internal/plugin/updates_check_test.go
T
yetone 94bb3e2ca6 plugins: Check for updates asks npm now for each installed plugin's newest version, and each row says what it found (the owner: 插件的检查更新按钮还是得做) — the community's plugins update by themselves a little after magpie starts and every 6 hours, and the page knew of anyone else's new version only from what npm said within the hour (Info's cache), so a reader who heard a plugin had a new version had no way to have magpie look. The foot of Installed (beside Update all) now has Check for updates, shown when a plugin is npm's (a folder's or a git one's has no versions there; a git one keeps its own Update, which fetches it again): it says Checking… with a spinner while POST /api/plugins/check asks npm, a few at a time, past the hour's cache (plugin.CheckNow, through npmAsk, which is npmInfo telling why npm said nothing), and nothing is installed. Each row then says it: "v0.0.9 out" titled "v0.0.7 installed, v0.0.9 on npm" with its Update (the existing upgrade path, Upgrade → Add), "Up to date", or an amber "Couldn't check" chip with a dot, titled why — npm not reached (check your connection or proxy), npm turning requests away (429, try again in a few minutes), npm answering with an error, or npm not having the package (fetchJSON's answers other than 200/404 are now a statusError, so a 429 is told apart). The status line says how many have updates, or that every plugin is up to date, and how many couldn't be checked and why; npm not reached at all is "Couldn't check for updates: npm couldn't be reached — check your connection or proxy". A community plugin's Update is titled that magpie would update it by itself within a few hours and Update does it now. What npm said is kept as Info keeps it, so the rows and the market read it, and an update found for the reader (someone else's, or pinned) waits as the background look's do, putting the dot on Plugins; one npm didn't answer for keeps what waited before. Every new string has its Chinese; the click never scrolls the page. TestCheckNow (an httptest npm registry: the community's and someone else's updates found past an hour-old cache saying otherwise, up to date, 404 missing, 429 limited, 500 registry, git and folder not asked, nothing installed, the cache and the waiting list updated) and TestCheckNowOffline (npm not reached: offline, what waited still waits) are new, as is TestPluginsCheckRoute; plugin-check-updates.test.cjs (WebKit and Chromium, en and zh: Checking… while npm is asked, the rows' out / up to date / couldn't check chips and titles, the status line, Update after the check, offline said plainly, the view's scroll unmoved) fails without the change and passes with it. plugin-updates, plugin-market, plugin-progressive, plugin-git, plugin-local and click-scroll pass in WebKit and Chromium. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui -count=1 ./... under a temp HOME pass.
2026-10-02 21:58:22 +08:00

136 lines
4.7 KiB
Go

package plugin
import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// Check for updates asks npm now, whatever it said within the hour: each
// plugin says whether npm has a newer version, and why it couldn't be
// checked when npm didn't say; someone else's update found waits for the
// reader (the dot on Plugins), and nothing is installed.
func TestCheckNow(t *testing.T) {
dir := t.TempDir()
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config"))
const (
ours = "@magpie-community/opencode-zed-auth"
theirs = "opencode-gemini-auth"
current = "@magpie-community/opencode-kiro-auth"
gone = "opencode-gone-auth"
limited = "opencode-busy-auth"
broken = "opencode-broken-auth"
)
answers := map[string]string{ours: "0.1.4", theirs: "1.0.0", current: "0.2.0"}
fakeNPM(t, func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/downloads/") {
w.Write([]byte(`{"downloads":1}`))
return
}
name := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), "/latest")
switch name {
case limited:
http.Error(w, "slow down", http.StatusTooManyRequests)
case broken:
http.Error(w, "oops", http.StatusInternalServerError)
case gone:
http.NotFound(w, r)
default:
w.Write([]byte(`{"version":"` + answers[name] + `"}`))
}
})
folder := filepath.Join(dir, "myplugin")
if err := save(List{Plugins: []Entry{{Spec: ours}, {Spec: theirs + "@latest"}, {Spec: current}, {Spec: gone}, {Spec: limited}, {Spec: broken}, {Spec: "github:me/opencode-x-auth"}, {Spec: folder}}}); err != nil {
t.Fatal(err)
}
for _, p := range []string{ours, theirs, gone, limited, broken} {
installedAt(t, p, "0.1.2")
}
installedAt(t, current, "0.2.0")
// what npm said within the hour, before the new versions came out
npmMu.Lock()
for _, p := range []string{ours, theirs} {
npmCached()[p] = npmEntry{NPM{Version: "0.1.2"}, time.Now().Add(-time.Minute)}
}
npmMu.Unlock()
was := installLatest
t.Cleanup(func() { installLatest = was })
installLatest = func(_ context.Context, pkg string) error { t.Errorf("a check installed %s", pkg); return nil }
c := CheckNow(context.Background())
got := map[string]string{}
for _, p := range c.Plugins {
got[p.Package] = p.Status + " " + p.Why + " " + p.Version + ">" + p.Latest
}
want := map[string]string{
ours: "update 0.1.2>0.1.4",
theirs: "update 0.1.2>1.0.0",
current: "current 0.2.0>0.2.0",
gone: "unknown missing 0.1.2>",
limited: "unknown limited 0.1.2>",
broken: "unknown registry 0.1.2>",
"github:me/opencode-x-auth": "git >",
folder: "folder >",
}
for k, v := range want {
if got[k] != v {
t.Errorf("%s: %q, want %q", k, got[k], v)
}
}
if c.At.IsZero() || len(c.Plugins) != len(want) {
t.Fatalf("checked %+v", c)
}
for _, p := range c.Plugins {
if p.Auto != (p.Package == ours || p.Package == current) {
t.Errorf("%s: Auto %v", p.Package, p.Auto)
}
}
// what npm said now is what the page reads, not what it said before
if v := InfoCached([]string{ours})[ours].Version; v != "0.1.4" {
t.Errorf("kept %s, want 0.1.4", v)
}
// someone else's update waits for the reader; the community's comes
// by itself
if p := PendingUpdates(); len(p.Waiting) != 1 || p.Waiting[0].Package != theirs || p.Waiting[0].Latest != "1.0.0" || p.Checked.IsZero() {
t.Fatalf("waiting %+v", p)
}
}
// npm not reached: every plugin says it couldn't be checked, offline, and
// what waited for the reader before still waits.
func TestCheckNowOffline(t *testing.T) {
dir := t.TempDir()
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(dir, "config"))
fakeNPM(t, func(http.ResponseWriter, *http.Request) {})
down := httptest.NewServer(http.NotFoundHandler())
npmRegistry = down.URL
down.Close()
const theirs = "opencode-gemini-auth"
if err := save(List{Plugins: []Entry{{Spec: theirs}}}); err != nil {
t.Fatal(err)
}
installedAt(t, theirs, "0.1.2")
if err := os.MkdirAll(filepath.Dir(updatesPath()), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(updatesPath(), []byte(`{"waiting":[{"spec":"`+theirs+`","package":"`+theirs+`","version":"0.1.2","latest":"0.2.0"}]}`), 0o600); err != nil {
t.Fatal(err)
}
c := CheckNow(context.Background())
if len(c.Plugins) != 1 || c.Plugins[0].Status != "unknown" || c.Plugins[0].Why != "offline" || c.Plugins[0].Error == "" {
t.Fatalf("offline: %+v", c.Plugins)
}
if p := PendingUpdates(); len(p.Waiting) != 1 || p.Waiting[0].Latest != "0.2.0" {
t.Fatalf("waiting %+v", p.Waiting)
}
}