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.108852b4worked 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.
93 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|