Files
magpie/plugin_login_cli_test.go
T
yetone 8c0ffcc640 provider: a plugin account signed in again is read afresh, not answered by the reading kept from the refused sign-in (TestPluginLoginTakesTheLapseOff red after its first run in a process)
clearPluginLapse took the lapsed mark off a plugin account signed in
again, but its usage reading from before stayed in loginUsageCache for a
minute. The next LoginUsage was answered from it: the old sign-in's
"expired" shown for the renewed account, and no plugin asked, so nothing
marked or cleared. TestPluginLoginTakesTheLapseOff hit it from its second
run in one process (-count=3: pass, fail, fail): the run before's last
reading, still under a minute old, answered the "gone" account's read
under the new HOME, and the vendor's refusal never marked it.

A sign-in now drops the account's kept reading (StaleAllowance), on both
paths that finish one (pluginDone and PluginSignedIn).

TestPluginLoginReadsUsageAfresh fails without the change at -count=1
("signed in again, but the usage is the old sign-in's: ... Error:the
FakeCo sign-in has expired"). With it, -run TestPluginLogin passes at
-count=5 and with -race -count=5; go test -tags nogui . ./internal/provider
./internal/plugin pass under a temp HOME with pinned caches; go vet and
GOOS=linux/windows go vet -tags nogui pass on the two packages.
2026-10-08 13:52:50 +08:00

144 lines
4.6 KiB
Go

package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync/atomic"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
"github.com/yetone/magpie/internal/provider"
"github.com/yetone/magpie/internal/testenv"
)
// magpie accounts add, signing in to a plugin's provider removed from
// magpie, brings it back, as the window's sign-in and a built-in's do.
func TestAccountsAddBringsBackARemovedPlugin(t *testing.T) {
ctx := fakeCoHome(t)
if _, err := provider.PluginAPIKey(ctx, "fakeco", 0, nil, "sk-one-key-1111"); err != nil {
t.Fatal(err)
}
if err := provider.Delete("fakeco"); err != nil {
t.Fatal(err)
}
if _, err := provider.Find("fakeco"); err == nil {
t.Fatal("removed provider still listed")
}
// the API key way (1), then the key
piped(t, "1", "sk-two-key-2222")
if out, err := said(t, func() error { return accountsCmd([]string{"accounts", "add", "fakeco"}) }); err != nil {
t.Fatalf("%v: %s", err, out)
}
if _, err := provider.Find("fakeco"); err != nil {
t.Fatalf("signed in again, but not listed: %v", err)
}
}
// magpie plugin login, signing in again to an account whose sign-in the
// vendor turned away, takes its lapsed mark off, as the window's sign-in
// does, rather than the account saying "sign in again" until a request or
// a usage read goes through.
func TestPluginLoginTakesTheLapseOff(t *testing.T) {
ctx := fakeCoHome(t)
noBrowser(t)
// the browser's way (2), at work (2) on the team "gone", whose usage
// says its sign-in has expired, then the code the page shows
login := func() {
t.Helper()
piped(t, "2", "2", "gone", "good")
if out, err := said(t, func() error { return pluginCmd([]string{"plugin", "login", "fakeco"}) }); err != nil {
t.Fatalf("%v: %s", err, out)
}
}
login()
provider.LoginUsage(ctx, "fakeco")
if ls := provider.Logins("fakeco"); len(ls) != 1 || ls[0].Lapsed == "" {
t.Fatalf("the vendor's refusal didn't mark the account: %+v", ls)
}
login()
if ls := provider.Logins("fakeco"); len(ls) != 1 || ls[0].Lapsed != "" {
t.Fatalf("signed in again, but still lapsed: %+v", ls)
}
}
// An account signed in again is read afresh: the reading kept from the
// sign-in the vendor refused doesn't answer for the new one, saying its
// sign-in has expired for up to a minute after it was renewed.
func TestPluginLoginReadsUsageAfresh(t *testing.T) {
var status atomic.Int32
status.Store(http.StatusUnauthorized)
vendor := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(int(status.Load()))
fmt.Fprint(w, "Fake Pro")
}))
defer vendor.Close()
t.Setenv("FAKE_USAGE", vendor.URL)
ctx := fakeCoHome(t)
noBrowser(t)
login := func() {
t.Helper()
piped(t, "2", "2", "team", "good")
if out, err := said(t, func() error { return pluginCmd([]string{"plugin", "login", "fakeco"}) }); err != nil {
t.Fatalf("%v: %s", err, out)
}
}
login()
if q := provider.LoginUsage(ctx, "fakeco")["team@fake"]; q.Error == "" {
t.Fatalf("the vendor's 401 read clean: %+v", q)
}
status.Store(http.StatusOK)
login()
if q := provider.LoginUsage(ctx, "fakeco")["team@fake"]; q.Error != "" || q.Plan != "Fake Pro" {
t.Fatalf("signed in again, but the usage is the old sign-in's: %+v", q)
}
}
// fakeCoHome is a HOME of the test's own with the plugin host's test
// plugin (FakeCo) added, as TestAccountsOfAPlugin sets one up.
func fakeCoHome(t *testing.T) context.Context {
t.Helper()
bun, err := exec.LookPath("bun")
if err != nil {
t.Skip("no bun on PATH")
}
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
t.Setenv("CLAUDE_CONFIG_DIR", filepath.Join(home, ".claude"))
t.Setenv("MAGPIE_BUN", bun)
t.Cleanup(plugin.Settle)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
t.Cleanup(cancel)
abs, _ := filepath.Abs("internal/plugin/testdata/fake/index.js")
if _, err := plugin.Add(ctx, abs); err != nil {
t.Fatal(err)
}
if _, err := plugin.Providers(ctx); err != nil {
t.Fatal(err)
}
return ctx
}
// noBrowser has the sign-in pages magpie opens go nowhere: an open (macOS)
// and an xdg-open (Linux) that do nothing come first on PATH.
func noBrowser(t *testing.T) {
t.Helper()
if runtime.GOOS == "windows" {
t.Skip("rundll32 opens the browser on Windows")
}
dir := t.TempDir()
for _, name := range []string{"open", "xdg-open"} {
testenv.Program(t, filepath.Join(dir, name), "#!/bin/sh\nexit 0\n")
}
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
}