diff --git a/internal/provider/plugin_accounts.go b/internal/provider/plugin_accounts.go index f9c74b4a..cf53bfe0 100644 --- a/internal/provider/plugin_accounts.go +++ b/internal/provider/plugin_accounts.go @@ -380,11 +380,17 @@ func lapsedText(pp plugin.Provider, user string) string { // clearPluginLapse takes the mark off an account signed in again, and // brings back the agent's own account removed in magpie, as signing in to -// the built-in's did (#320). +// the built-in's did (#320). The account's kept usage reading goes too: it +// was read under the old sign-in, and for a minute it would answer the +// next reading with the old sign-in's "expired" without asking the plugin, +// and so without marking or clearing anything. func clearPluginLapse(saved plugin.Saved) { for _, pp := range plugin.Cached() { if pp.ID == saved.Provider { notePluginLapse(pp, saved.Account, http.StatusOK) + if user := pluginLabels(pp)[saved.Account]; user != "" { + StaleAllowance(pluginAgent(pp), user) + } } } loginsMu.Lock() diff --git a/plugin_login_cli_test.go b/plugin_login_cli_test.go index b3d396c5..5bc523d6 100644 --- a/plugin_login_cli_test.go +++ b/plugin_login_cli_test.go @@ -2,10 +2,14 @@ package main import ( "context" + "fmt" + "net/http" + "net/http/httptest" "os" "os/exec" "path/filepath" "runtime" + "sync/atomic" "testing" "time" @@ -64,6 +68,38 @@ func TestPluginLoginTakesTheLapseOff(t *testing.T) { } } +// 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 {