From 187df65b166bce0dd7141d975d2d2a807df79d92 Mon Sep 17 00:00:00 2001 From: yetone Date: Fri, 2 Oct 2026 02:38:33 +0800 Subject: [PATCH] =?UTF-8?q?provider:=20a=20plugin=20provider's=20accounts?= =?UTF-8?q?=20can=20be=20dragged=20into=20a=20new=20order=20too=20(#216)?= =?UTF-8?q?=20=E2=80=94=20dragging=20them=20failed=20with=20"accounts=20ch?= =?UTF-8?q?anged;=20reopen=20the=20provider=20and=20try=20again":=20the=20?= =?UTF-8?q?account=20a=20plugin's=20provider=20holds=20names=20its=20agent?= =?UTF-8?q?=20"plugin",=20which=20no=20list=20goes=20by,=20while=20the=20p?= =?UTF-8?q?age=20lists=20and=20switches=20its=20accounts=20by=20the=20prov?= =?UTF-8?q?ider's=20id=20and=20logins.json=20keeps=20them=20as=20plugin:.=20SetAccountOrder=20now=20lists=20and=20switches=20a=20plug?= =?UTF-8?q?in's=20accounts=20by=20the=20provider's=20id=20and=20writes=20t?= =?UTF-8?q?heir=20ranks=20on=20its=20plugin:=20rows,=20so=20the=20orde?= =?UTF-8?q?r=20dragged=20is=20the=20order=20shown=20and=20tried.=20TestAcc?= =?UTF-8?q?ountOrderPluginAccounts=20fails=20without=20the=20change.=20go?= =?UTF-8?q?=20vet,=20go=20build=20-tags=20nogui=20(darwin,=20GOOS=3Dlinux)?= =?UTF-8?q?,=20GOOS=3Dwindows=20go=20build,=20go=20test=20-tags=20nogui=20?= =?UTF-8?q?./...=20pass=20(TestAccountProxy=20flaked=20once=20under=20load?= =?UTF-8?q?,=20passes=20alone).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/provider/account_order.go | 15 ++++- .../provider/account_order_plugin_test.go | 57 +++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 internal/provider/account_order_plugin_test.go diff --git a/internal/provider/account_order.go b/internal/provider/account_order.go index c81059a2..a1aaf821 100644 --- a/internal/provider/account_order.go +++ b/internal/provider/account_order.go @@ -10,6 +10,11 @@ func SetAccountOrder(id string, order []string) error { if err != nil { return err } + if p.IsPlugin() { + // a plugin's accounts are listed and switched by the provider's id, + // and kept in logins.json as plugin: + return arrangeLoginsAs(p.ID, pluginAgent(*p.Account.plugin), order) + } if p.Account != nil { return arrangeLogins(p.Account.Agent, order) } @@ -39,6 +44,12 @@ func SetAccountOrder(id string, order []string) error { } func arrangeLogins(agent string, order []string) error { + return arrangeLoginsAs(agent, agent, order) +} + +// arrangeLoginsAs arranges the accounts agent lists and switches, saved in +// logins.json under stored. +func arrangeLoginsAs(agent, stored string, order []string) error { var available []string var first Login for _, l := range Logins(agent) { @@ -64,7 +75,7 @@ func arrangeLogins(agent string, order []string) error { rank[user] = i + 1 } for i := range ls { - if ls[i].Agent == agent { + if ls[i].Agent == stored { before[ls[i].User] = ls[i].Order ls[i].Order = rank[ls[i].User] } @@ -86,7 +97,7 @@ func arrangeLogins(agent string, order []string) error { loginsMu.Lock() ls = readLogins() for i := range ls { - if ls[i].Agent == agent { + if ls[i].Agent == stored { ls[i].Order = before[ls[i].User] } } diff --git a/internal/provider/account_order_plugin_test.go b/internal/provider/account_order_plugin_test.go new file mode 100644 index 00000000..8227ab6b --- /dev/null +++ b/internal/provider/account_order_plugin_test.go @@ -0,0 +1,57 @@ +package provider + +import ( + "context" + "os/exec" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/yetone/magpie/internal/plugin" +) + +// A plugin provider's accounts are arranged like a built-in's: the page +// names them by the provider's id, magpie keeps them as plugin:, and +// the gateway tries them in the order dragged. +func TestAccountOrderPluginAccounts(t *testing.T) { + bun, err := exec.LookPath("bun") + if err != nil { + t.Skip("no bun on PATH") + } + claudeHome(t) + t.Setenv("MAGPIE_BUN", bun) + t.Cleanup(plugin.Settle) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + abs, _ := filepath.Abs("../plugin/testdata/fake/index.js") + if _, err := plugin.Add(ctx, abs); err != nil { + t.Fatal(err) + } + var rows []savedLogin + users := []string{"a@fake", "b@fake", "c@fake"} + for _, user := range users { + k, err := plugin.Import(ctx, "fakeco", map[string]any{"type": "oauth", "refresh": "r-" + user, "access": "a", "expires": 9e15, "accountId": user}) + if err != nil { + t.Fatal(err) + } + rows = append(rows, savedLogin{Agent: "plugin:fakeco", User: user, Home: k, On: true}) + } + saveLogins(t, rows...) + if _, err := plugin.Providers(ctx); err != nil { + t.Fatal(err) + } + id := PluginID("fakeco") + order := []string{"c@fake", "a@fake", "b@fake"} + if err := SetAccountOrder(id, order); err != nil { + t.Fatal(err) + } + shown, _ := loginUsers(Logins(id)) + p, err := Find(id) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(shown, order) || !reflect.DeepEqual(loginRoutingUsers(p), order) { + t.Fatalf("display=%v routing=%v", shown, loginRoutingUsers(p)) + } +}