Files
yetone 06420a7121 tests: a fake program is a file macOS has already let run (testenv.Program), so a fake's first start no longer eats a test's wait under load
macOS checks a newly written executable before its first exec: 0.27s
alone, 2-5s while other new programs start (a go test ./... starting its
test binaries). The same file again takes 0.009s, and the pass is kept
across a rename and an in-place rewrite. 108852b4 worked round this for one
test with /bin/sh -c; the class was every test that writes a `#!/bin/sh`
fake and times its answer.

testenv.Program(t, path, script) replaces os.WriteFile(path, script, 0o755)
for fakes. On macOS it renames a file that has run once from a pool to path
and writes the script into it in place; at the test's cleanup the file goes
back to the pool (only a path the test created; a stand-in it wrote over
stays). Fakes still end with their tests: nothing about how they are
started or waited on changed. Run (and gateway's TestMain) remove the pool
at the end. TestMain's PATH stand-ins are run once in the background
(testenv.StandIns). Other systems write the file as before.

Converted every `#!/bin/sh` fake written by tests under internal/ (agent,
gateway, gui, library, plugin, proc, provider, update). Windows, binary
and node/bun fakes are unchanged.

Under the same load (3 loops each starting 8 freshly written scripts at a
time), 4 rounds each, before (108852b4 / origin/main) vs after:
- provider TestSavedButSignedOutClaudeServed: 4/4 fail (8.01s) -> 4/4 pass
- provider TestClaudeSignInOverStaleCredentials: 4/4 fail (5.00s) -> 4/4 pass
- provider TestDevinSignedInOnlyInMagpie: 3/4 fail ("CLI's own []") -> 4/4 pass
- gateway TestClaudeRunEndsWhenTheClientLeaves: 4/4 fail -> 4/4 pass
  (and 3/3 fail -> 3/3 pass at a third of that load)
- gateway TestClientGoneRecordedAsCanceled: 4/4 fail -> 4/4 pass
- gui TestProvidersAnswerWhileListsComeIn ("cursor found false" at 3.03s):
  4/4 fail -> 4/4 pass. The same class, not bfe8bcaf's test order.
- proc TestUserPath (login shell's PATH never came, 7.01s): 4/4 fail ->
  4/4 pass
Unloaded, every touched package passes; no pool directory or process is
left after a run.
2026-10-07 03:00:47 +08:00

93 lines
3.4 KiB
Go

package provider
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/yetone/magpie/internal/testenv"
)
// TestDevinUsage: magpie's built-in Devin account showed no usage while the
// Devin plugin showed its day, week and ACU windows; both read
// GetUserStatus now, and a key Devin refuses says to sign in again. The
// CLI's own account had no plan either, where the plugin's said its tier.
func TestDevinUsage(t *testing.T) {
home := claudeHome(t)
data := filepath.Join(home, "data")
t.Setenv("XDG_DATA_HOME", data)
os.MkdirAll(filepath.Join(data, "devin"), 0o700)
os.WriteFile(filepath.Join(data, "devin", "credentials.toml"), devinCredentials("devin-session-token$good", "", "", ""), 0o600)
exe := filepath.Join(home, "devin")
testenv.Program(t, exe, `#!/bin/sh
printf 'Logged in (via Devin).\n\nUser:\n Email: dev@example.com\n\nAccount:\n Tier: Devin Pro\n'
`)
fakeDevin(t, exe)
forgetDevinStatus()
old := firstAsk
firstAsk = time.Minute // a loaded machine's shell takes longer than a look's first wait
t.Cleanup(func() { firstAsk = old })
var asked map[string]any
fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/exa.seat_management_pb.SeatManagementService/GetUserStatus" || r.Header.Get("Connect-Protocol-Version") != "1" {
http.NotFound(w, r)
return
}
b, _ := io.ReadAll(r.Body)
json.Unmarshal(b, &asked)
if strings.Contains(string(b), "refused") {
w.WriteHeader(http.StatusUnauthorized)
io.WriteString(w, `{"code":"unauthenticated","message":"bad key"}`)
return
}
io.WriteString(w, `{"userStatus":{"planStatus":{"planInfo":{"planName":"Pro"},"planEnd":"2026-11-01T00:00:00Z",
"dailyQuotaRemainingPercent":75,"dailyQuotaResetAtUnix":"1790000000",
"weeklyQuotaRemainingPercent":"40","weeklyQuotaResetAtUnix":"1790500000",
"acuLimit":"250","acuConsumed":12.345,"overageBalanceMicros":"5000000"}}}`)
}))
defer fake.Close()
t.Setenv("WINDSURF_API_SERVER_URL", fake.URL)
ls, ok := builtinLogins("devin")
// the CLI's own account is named by its tier, as the plugin names it
if !ok || len(ls) != 1 || ls[0].User != "dev@example.com" || ls[0].Plan != "Devin Pro" {
t.Fatalf("logins %+v %v", ls, ok)
}
q := loginQuota(context.Background(), ls[0])
if q.Error != "" {
t.Fatal(q.Error)
}
if md, _ := asked["metadata"].(map[string]any); md["apiKey"] != "devin-session-token$good" || md["ideName"] != "devin-cli" {
t.Fatalf("asked %v", asked)
}
if len(q.Windows) != 3 {
t.Fatalf("windows %+v", q.Windows)
}
day, week, acu := q.Windows[0], q.Windows[1], q.Windows[2]
if day.Name != "1 day" || day.Used != 25 || day.ResetsAt == nil || day.ResetsAt.Unix() != 1790000000 {
t.Fatalf("day %+v", day)
}
if week.Name != "7 days" || week.Used != 60 || week.Span != 7*24*time.Hour {
t.Fatalf("week %+v", week)
}
if acu.Name != "ACUs" || !acu.Aside || acu.Display != "12.35 / 250 ACUs" || acu.ResetsAt == nil {
t.Fatalf("acu %+v", acu)
}
if q.Until == nil || q.Until.Format("2006-01-02") != "2026-11-01" || q.Balance != "$5.00" {
t.Fatalf("plan %+v", q)
}
os.WriteFile(filepath.Join(data, "devin", "credentials.toml"), devinCredentials("devin-session-token$refused", "", "", ""), 0o600)
if q := loginQuota(context.Background(), ls[0]); q.Error != "Devin's sign-in has expired — sign in again" {
t.Fatalf("refused %+v", q)
}
}