Files
magpie/internal/provider/plugin_fetch_lapse_test.go
T
yetone 4f9dcb39e5 plugins: a plugin subscription goes through every path a built-in one does (the user: 要保证插件订阅 provider 跟内置的订阅 provider所有逻辑都要一模一样) — a SOCKS5 proxy on a plugin provider or account now carries its requests: Bun speaks http(s) proxies only, so magpie stands a loopback http proxy in front of the SOCKS one, and a proxy that refuses says so as Go's transport does. A plugin's model list and its loader are asked through the account's own proxy, as a built-in's list is fetched through it. The Grok plugin draws and makes videos at Grok's Imagine API through the plugin, and keeps Codex's namespaced tools, as the built-in does; an official plugin beside its built-in (kiro-plugin, zcode-plugin) counts tokens by estimate as the built-in and the moved plugin do. FetchNew no longer re-lists plugin providers every ten minutes, their list being told by the plugin host; a plugin account's usage is cached under one name whichever way it is asked for; and a fetch hook that throws on its sign-in (a refused refresh) marks the account lapsed, as a built-in's refused refresh does. TestPluginSOCKSProxy, TestPluginModelListThroughAccountProxy, TestGrokPluginDraws, TestPluginBesideBuiltinCountsByEstimate, TestFetchNewLeavesListedPlugins, TestPluginUsageReadOnceWhicheverName and TestPluginFetchSignInExpiredLapses fail without the change. go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass (the known load flakes pass alone); internal/plugin, netproxy and the plugin/proxy tests of provider and gateway pass on Linux and Windows.
2026-10-01 19:07:12 +08:00

67 lines
1.9 KiB
Go

package provider
import (
"context"
"net/http"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/yetone/magpie/internal/plugin"
)
// A plugin's fetch hook that throws on its sign-in (the vendor turned the
// refresh away) marks the account lapsed, as a built-in's refused refresh
// marked it; the other account is left alone.
func TestPluginFetchSignInExpiredLapses(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.Setenv("FAKE_BASE", "http://127.0.0.1:9/v1")
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)
}
keys := map[string]string{}
var rows []savedLogin
for user, refresh := range map[string]string{"revoked@fake": "r-revoked", "ok@fake": "r-ok"} {
k, err := plugin.Import(ctx, "fakeco", map[string]any{"type": "oauth", "refresh": refresh, "access": "a", "expires": 9e15, "accountId": user})
if err != nil {
t.Fatal(err)
}
keys[user] = k
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)
}
pp := mustPlugin(t)
req, _ := http.NewRequestWithContext(ctx, "POST", pluginBase+pp.ID+"/v1/chat/completions", strings.NewReader(`{"model":"fake-claude","messages":[]}`))
if _, err := pluginFetch(pp, keys["revoked@fake"], req); err == nil {
t.Fatal("the fetch went through")
}
deadline := time.Now().Add(10 * time.Second)
for {
lapsed := map[string]bool{}
for _, l := range pluginLogins(mustPlugin(t)) {
lapsed[l.User] = l.Lapsed != ""
}
if lapsed["revoked@fake"] && !lapsed["ok@fake"] {
return
}
if time.Now().After(deadline) {
t.Fatalf("lapsed: %v", lapsed)
}
time.Sleep(50 * time.Millisecond)
}
}