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.
61 lines
1.9 KiB
Go
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)
|
|
}
|
|
}
|