Files
magpie/internal/provider/commandcode_plan_test.go
T
yetone 6546033109 kiro: removing kiro-cli's or the Kiro IDE's own sign-in hides it in magpie, and it shows again when Kiro signs in anew (Discord 碳碳双键: 本机没安装 kiro,但旧的 Kiro 数据还在,magpie 列出一个"Kiro account"(登录已失效,请重新添加此账号),添加新账号并设为首位后,旧的数据无法移除) — magpie reads Kiro's own sign-in from kiro-cli's database or ~/.aws/sso/cache and lists it among the accounts it signed in; Remove on it said "that is kiro-cli's or the Kiro IDE's own sign-in; run kiro-cli logout or sign out in the IDE", which with neither installed left a dead account there for good. The same refusal was shared by every agent whose own sign-in magpie only reads (forgetSideLogin: Kiro, Devin, Grok, Copilot, ZCode, WorkBuddy and WorkBuddy AI, Command Code, Gemini CLI, Antigravity). Now Remove on the agent's own sign-in hides it: logins.json's entry for it keeps a Hidden mark, and it is left out of the account list and so of the gateway's tries (the first and the ones behind it both come from that list); the agent's files are never deleted or written. It shows again when that sign-in changes: for Kiro, by a mark that is a digest of where the sign-in is (kiro-cli's row or the IDE's file) and its refresh token (never the token itself), so a new kiro-cli login or IDE sign-in, to any account, brings it back; for the others, which have no such mark, when it is another account. Removing it while it is first is allowed now, the next account being put first (saved, so it stays first when the own one comes back); removed when it was the only one, the agent has no account left. One of magpie's own accounts is removed with its home as before, and still can't be while it is first. The error and its per-agent sign-out hints are gone. Logins carry own, and the accounts list shows Remove on the agent's own sign-in also while it is first, its title "magpie stops showing and using Kiro's own sign-in; its files are left as they are, and it shows again when Kiro signs in anew" (magpie 不再显示和使用 Kiro 自己的登录;它的文件保持原样,Kiro 重新登录后会再次出现). Tested with a new internal/provider/side_logins_hide_test.go in a sandbox HOME: an expired kiro-cli sign-in ("Kiro account") behind a Google one magpie signed in is removed, stays gone across reads, isn't the account used nor among AlsoOn, kiro-cli's database byte-for-byte and mtime as it was, the refresh token nowhere in magpie's files; a new kiro-cli sign-in brings it back behind the one first; put first and removed, the next is first; one of magpie's removed takes its home and leaves the other's; the only account removed leaves Kiro with none; Grok's CLI sign-in hidden with auth.json untouched, back once it is another account. The agents' tests that expected the refusal (Command Code, Copilot, Devin, Grok, WorkBuddy, ZCode) no longer do. A new internal/gui/tests/own-signin-remove.test.cjs in Chromium and WebKit, en and zh: kiro-cli's sign-in behind another and alone first both have Remove with that title and post login/forget, magpie's first account has none. go build -tags nogui (and GOOS=linux, GOOS=windows), go test -tags nogui ./... and the whole GUI suite (138) pass. Not tried with a real Kiro, kiro-cli or the Kiro IDE.
2026-09-29 23:52:33 +08:00

251 lines
10 KiB
Go

package provider
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
"time"
)
func TestCommandCodePlan(t *testing.T) {
home := signIn(t)
writeFile(t, filepath.Join(home, ".commandcode", "auth.json"), map[string]any{
"apiKey": "own-key", "userId": "u1", "userName": "ownuser", "keyName": "cli", "authenticatedAt": "2026-09-01T00:00:00Z",
})
reset := time.Now().Add(2 * time.Hour).UnixMilli()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if key != "own-key" && key != "two-key" {
w.WriteHeader(401)
return
}
ok := func(v any) { _ = json.NewEncoder(w).Encode(v) }
switch r.URL.Path {
case "/alpha/whoami":
ok(map[string]any{"user": map[string]any{"id": "u2", "userName": "two", "email": "two@example.com"}})
case "/alpha/billing/subscriptions":
if key == "own-key" { // as for an account with no plan
ok(map[string]any{"success": true, "data": nil})
return
}
ok(map[string]any{"success": true, "data": map[string]any{"planId": "individual-max-monthly", "status": "active", "currentPeriodEnd": "2026-10-28T00:00:00Z", "cancelAtPeriodEnd": true}})
case "/alpha/billing/credits":
if key == "own-key" { // no plan: bought credits, no windows
ok(map[string]any{"credits": map[string]any{"monthlyCredits": 0, "purchasedCredits": 7.25, "freeCredits": 0}})
return
}
// as the live reply: no planId here, it is billing/subscriptions'
ok(map[string]any{
"credits": map[string]any{"monthlyCredits": 100.5, "purchasedCredits": 10, "freeCredits": 0},
"windowLimits": map[string]any{"limited": true,
"fiveHour": map[string]any{"used": 25, "cap": 100, "resetAt": reset},
"weekly": map[string]any{"used": 30, "cap": 300, "resetAt": time.Now().Add(72 * time.Hour).UnixMilli()},
},
})
default:
w.WriteHeader(404)
}
}))
defer srv.Close()
oldAPI := cmdAPI
cmdAPI = srv.URL
defer func() { cmdAPI = oldAPI }()
if who, a, ok := cmdOwn(); !ok || who != "ownuser" || a.APIKey != "own-key" {
t.Fatalf("own: %v %q", ok, who)
}
st, err := StartSignIn(CommandCodePlanID)
if err != nil {
t.Fatal(err)
}
u, _ := url.Parse(st.URL)
cb := u.Query().Get("callback")
if u.Host != "commandcode.ai" || u.Path != "/studio/auth/cli" || u.Query().Get("mode") != "redirect" ||
!strings.HasPrefix(cb, "http://127.0.0.1:") || !strings.HasSuffix(cb, "/callback") {
t.Fatalf("sign-in page: %s", st.URL)
}
state := u.Query().Get("state")
noFollow := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// the browser's preflight, from Studio
pre, _ := http.NewRequest(http.MethodOptions, cb, nil)
pre.Header.Set("Origin", "https://commandcode.ai")
pre.Header.Set("Access-Control-Request-Private-Network", "true")
if res, err := noFollow.Do(pre); err != nil || res.StatusCode != 204 ||
res.Header.Get("Access-Control-Allow-Origin") != "https://commandcode.ai" || res.Header.Get("Access-Control-Allow-Private-Network") != "true" {
t.Fatalf("preflight: %v %+v", err, res)
}
// someone else's state is turned away, and the sign-in goes on
if res, err := noFollow.PostForm(cb, url.Values{"apiKey": {"evil"}, "state": {"nope"}}); err != nil || res.StatusCode != 403 {
t.Fatalf("wrong state: %v %+v", err, res)
}
if res, _ := noFollow.Get(cb); res.StatusCode != 405 {
t.Fatalf("GET callback: %d", res.StatusCode)
}
res, err := noFollow.PostForm(cb, url.Values{"apiKey": {"two-key"}, "state": {state}, "userId": {"u2"}, "userName": {"two"}, "keyName": {"magpie"}})
if err != nil || res.StatusCode != 303 || !strings.HasPrefix(res.Header.Get("Location"), "/callback/complete?state=") {
t.Fatalf("callback: %v %+v", err, res)
}
page, err := http.Get(strings.TrimSuffix(cb, "/callback") + res.Header.Get("Location"))
if err != nil || page.StatusCode != 200 {
t.Fatalf("complete page: %v %+v", err, page)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
st, _ = WaitSignIn(ctx, st.ID)
if st.State != "done" || st.User != "two" || st.Plan != "Max" || st.Using {
t.Fatalf("done: %+v", st)
}
var users []string
for _, l := range Logins(CommandCodePlanID) {
users = append(users, l.User+map[bool]string{true: "*", false: ""}[l.Active]+map[bool]string{true: "+", false: ""}[l.On])
}
if got := strings.Join(users, " "); got != "ownuser*+ two+" {
t.Fatalf("logins: %s", got)
}
p, ok := find(All(), CommandCodePlanID)
if !ok || p.Account.User != "ownuser" || p.Chat != srv.URL+"/provider/v1" || p.Anthropic != srv.URL+"/provider" {
t.Fatalf("first: %+v", p)
}
also := p.AlsoOn()
if len(also) != 1 || also[0].Account.User != "two" {
t.Fatalf("also on: %+v", also)
}
req, _ := http.NewRequest("POST", also[0].Chat+"/chat/completions", nil)
req.Header.Set("Authorization", "Bearer magpie")
if err := also[0].Sign(context.Background(), req, Chat, []byte(`{}`)); err != nil ||
req.Header.Get("Authorization") != "Bearer two-key" || req.Header.Get("x-api-key") != "two-key" {
t.Fatalf("signs with its own key: %v %v", err, req.Header)
}
q := LoginUsage(context.Background(), CommandCodePlanID)["two"]
if q.Error != "" || q.Plan != "Max" || len(q.Windows) != 3 || q.Windows[0].Name != "5 hours" || q.Windows[0].Used != 25 ||
q.Windows[1].Name != "Weekly" || q.Windows[1].Used != 10 || q.Windows[0].ResetsAt == nil || q.Windows[1].ResetsAt == nil ||
q.Windows[0].ResetsAt.UnixMilli() != reset {
t.Fatalf("usage: %+v", q)
}
// the credits are a window too, not a Balance, which every view shows
// in place of the windows: Max's $150 a month, $100.50 of it left
if q.Balance != "" || q.Windows[2].Name != "Credits" || q.Windows[2].Display != "$49.50 / $160.00" {
t.Fatalf("credits: %q %+v", q.Balance, q.Windows[2])
}
if q.Until == nil || q.Until.Format("2006-01-02") != "2026-10-28" || q.Renew != "off" {
t.Fatalf("period: %v %q", q.Until, q.Renew)
}
// an account with no subscription says so, and shows what it bought
if q := LoginUsage(context.Background(), CommandCodePlanID)["ownuser"]; q.Error != "" || q.Plan != "No plan" || q.Balance != "$7.25" {
t.Fatalf("no plan: %+v", q)
}
if err := SwitchLogin(CommandCodePlanID, "two"); err != nil {
t.Fatal(err)
}
if p, _ := find(All(), CommandCodePlanID); p.Account.User != "two" {
t.Fatalf("after switch: %+v", p.Account)
}
if err := SwitchLogin(CommandCodePlanID, "ownuser"); err != nil {
t.Fatal(err)
}
if err := ForgetLogin(CommandCodePlanID, "two"); err != nil {
t.Fatal(err)
}
if ls := Logins(CommandCodePlanID); len(ls) != 1 {
t.Fatalf("after forget: %+v", ls)
}
// a sign-in the user turns down fails
st, _ = StartSignIn(CommandCodePlanID)
u, _ = url.Parse(st.URL)
body, _ := json.Marshal(map[string]any{"state": u.Query().Get("state"), "error": "access_denied"})
if res, err := http.Post(u.Query().Get("callback"), "application/json", strings.NewReader(string(body))); err != nil || res.StatusCode != 400 {
t.Fatalf("denied: %v %+v", err, res)
}
st, _ = WaitSignIn(ctx, st.ID)
if st.State != "failed" || st.Error != "the sign-in was denied" {
t.Fatalf("denied: %+v", st)
}
// signed out of the CLI, with none of magpie's: no Command Code subscription
os.Remove(filepath.Join(home, ".commandcode", "auth.json"))
if _, ok := find(All(), CommandCodePlanID); ok {
t.Fatal("command code without an account")
}
}
func TestCommandCodePlanName(t *testing.T) {
for id, want := range map[string]string{
"individual-go": "Go", "individual-goat": "GOAT", "individual-pro-v1": "Pro", "individual_ultra": "Ultra",
"teams-pro": "Teams Pro", "individual-provider": "Provider", "enterprise": "",
} {
if got := cmdPlanName(id); got != want {
t.Errorf("%s: %q, want %q", id, got, want)
}
}
if tm := cmdTime(float64(1_900_000_000_000)); tm == nil || tm.Unix() != 1_900_000_000 {
t.Fatalf("ms: %v", tm)
}
}
// A whoami that errs on a key just made doesn't fail the sign-in (tigger_ultra:
// "Command Code didn't take the new key: Internal Server Error"): it is asked
// again, then passed over for the name Studio sent; only a key it turns down
// fails.
func TestCommandCodeSignInWhoamiDown(t *testing.T) {
signIn(t)
var asked atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
switch {
case key == "bad-key":
w.WriteHeader(401)
case r.URL.Path == "/alpha/whoami":
asked.Add(1)
w.WriteHeader(500)
case r.URL.Path == "/alpha/billing/subscriptions":
_ = json.NewEncoder(w).Encode(map[string]any{"success": true, "data": map[string]any{"planId": "individual-pro-monthly", "status": "active"}})
default:
w.WriteHeader(404)
}
}))
defer srv.Close()
oldAPI, oldWait := cmdAPI, cmdWhoamiWait
cmdAPI, cmdWhoamiWait = srv.URL, 10*time.Millisecond
defer func() { cmdAPI, cmdWhoamiWait = oldAPI, oldWait }()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
noFollow := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
for _, c := range []struct{ key, user, state, errText string }{
{"new-key", "tigger", "done", ""},
{"bad-key", "evil", "failed", "Command Code didn't take the new key: Unauthorized"},
{"new-key", "", "failed", "Command Code couldn't say which account signed in: Internal Server Error"},
} {
asked.Store(0)
st, err := StartSignIn(CommandCodePlanID)
if err != nil {
t.Fatal(err)
}
u, _ := url.Parse(st.URL)
if _, err := noFollow.PostForm(u.Query().Get("callback"), url.Values{"apiKey": {c.key}, "state": {u.Query().Get("state")}, "userName": {c.user}}); err != nil {
t.Fatal(err)
}
st, _ = WaitSignIn(ctx, st.ID)
if st.State != c.state || st.Error != c.errText {
t.Fatalf("%s %q: %+v", c.key, c.user, st)
}
if c.state == "done" && (st.User != c.user || st.Plan != "Pro" || asked.Load() != 3) {
t.Fatalf("signed in: %+v, whoami asked %d times", st, asked.Load())
}
}
}