Files
magpie/internal/gui/codex_daemon_test.go
yetone 4eb90b69f4 provider: after magpie switches Codex's account, Codex's accounts say the Codex app is still on the one before and to quit and reopen it (CavillZhang on X)
The Codex desktop app (ChatGPT.app 26.1007, bundled codex 0.162) reads
auth.json once, as its app-server starts. codex-rs AuthManager only
reloads it for the same account (reload_if_account_id_matches skips a
different one; a full reload is the app's own login/logout). So after
magpie's switch the running app keeps the spent account's token, its
webview reads /wham/usage with it (straight to chatgpt.com, which magpie
can't intercept), gets rate_limit.allowed=false, and hardBlocked stops
the composer in every thread with "You're out of Codex and Work usage".
Only quitting and reopening the app moves it.

magpie now notes, like the managed daemon, the app's own app-server
(…/CodexCLI.app/Contents/MacOS/codex … app-server, or the Windows Store
app's codex.exe) running at a switch and the account it holds, until that
process changes. Codex's accounts show "The Codex app is still signed in
as …; quit it and open it again" with Got it; `magpie accounts` and the
gateway log say the same.

Run: go test ./internal/provider ./internal/gui (TestCodexAppStale goes
red with the app not noted), the new GUI test in Chromium and WebKit, en
zh zh-TW ja de, 900 and 480px; findCodexApp against the owner's running
ChatGPT.app found its app-server; go vet darwin/linux(nogui)/windows,
go build.
2026-10-10 13:41:59 +08:00

61 lines
1.9 KiB
Go

//go:build !windows
package gui
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/yetone/magpie/internal/testenv"
)
// The Restart button runs `codex app-server daemon restart` with the codex
// magpie finds, for the Codex home it switches; a stand-in codex writes
// down how it was run, since the real one would restart the user's daemon.
func TestCodexDaemonRestartRoute(t *testing.T) {
h := sandboxHome(t)
bin := t.TempDir()
t.Setenv("PATH", bin)
t.Setenv("CODEX_HOME", "/somewhere/else")
rec := filepath.Join(bin, "ran")
script := "#!/bin/sh\necho \"$@|$CODEX_HOME\" > '" + rec + "'\n[ \"$FAIL\" = 1 ] && { echo 'Error: no daemon' >&2; exit 1; }\nexit 0\n"
testenv.Program(t, filepath.Join(bin, "codex"), script)
mux := http.NewServeMux()
providerRoutes(mux, nil)
post := func(action string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
mux.ServeHTTP(w, httptest.NewRequest("POST", "/api/codex/daemon/"+action, strings.NewReader("{}")))
return w
}
w := post("restart")
if w.Code != 200 {
t.Fatalf("%d %s", w.Code, w.Body)
}
var st providersJSON
if err := json.Unmarshal(w.Body.Bytes(), &st); err != nil || st.CodexDaemon != "" {
t.Fatalf("state %v %q", err, st.CodexDaemon)
}
b, _ := os.ReadFile(rec)
if got, want := strings.TrimSpace(string(b)), "app-server daemon restart|"+filepath.Join(h, ".codex"); got != want {
t.Fatalf("ran %q, want %q", got, want)
}
t.Setenv("FAIL", "1")
if w := post("restart"); w.Code != 400 || !strings.Contains(w.Body.String(), "no daemon") {
t.Fatalf("failing codex: %d %s", w.Code, w.Body)
}
if w := post("dismiss"); w.Code != 200 {
t.Fatalf("dismiss: %d %s", w.Code, w.Body)
}
if w := post("dismiss-app"); w.Code != 200 {
t.Fatalf("dismiss-app: %d %s", w.Code, w.Body)
}
if w := post("nope"); w.Code != 404 {
t.Fatalf("unknown action: %d", w.Code)
}
}