Files
yetone bc141ad834 update: install.sh and magpie update take --proxy and --mirror, and magpie update mirror keeps one for the app's updates too (akic404 on Discord: 安装和update如果没有梯子也是麻烦事,能否在install.sh和magpie update后加子选项?--proxy等可自定义或默认ghproxy) — install.sh had no options and fetched with curl alone, and magpie update took none; without a way out to GitHub both failed. Now both take --proxy <url> (http, https, socks5, socks5h; install.sh also MAGPIE_PROXY=) for the feed and the download, and --mirror <prefix> (or MAGPIE_MIRROR=), a GitHub download mirror the release file is fetched from as <prefix>https://github.com/…; only github.com / githubusercontent.com URLs are put through it. No mirror is used unless one is given: magpie names none and has none by default. The feed with each file's SHA-256 is always fetched from usemagpie.ai, never through the mirror, so a file the mirror changed is refused and deleted ("… from the mirror X does not match its checksum from https://usemagpie.ai; not installed"); macOS still checks the app's signing team. magpie update mirror <prefix>|off saves Settings' updateMirror (http(s) only), which the app's own background update follows too; magpie update --mirror off skips it once; the Settings page's save keeps it. The app's update already followed Settings → Proxy (#294); magpie update --proxy overrides it for one run. The environment's ALL_PROXY / all_proxy is now honoured when HTTPS_PROXY / HTTP_PROXY are unset (Go's ProxyFromEnvironment ignores it), with NO_PROXY as curl reads it. magpie update takes --flag value or --flag=value, and an unknown word is an error instead of an update. The help, README and the docs' FAQ (en/zh/ja) say how. install.test.mjs's proxy, mirror and tampered-mirror tests, TestAllProxyFromTheEnvironment, TestUpdateThroughAMirror, TestUpdateThroughAProxyGiven and TestUpdateFlags fail on the old code and pass; TestSettingsSaveKeepsWhatItDoesNotSend covers updateMirror. A dry run of install.sh as Linux amd64 into a temp HOME installed the real 0.1.773 with its SHA-256 checked, refused a local mirror serving a wrong file, and with --proxy to a closed port failed reaching the site through it. go vet, builds (darwin, GOOS=linux, GOOS=windows), sh -n, node --test site/install.test.mjs and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeIdentityServedWhileAsked, a timing test that passes alone. Not tried through a real proxy server or a real public mirror; no install.ps1 exists for Windows, and the app has no field for the mirror.
2026-10-04 00:19:47 +08:00

194 lines
6.4 KiB
Go

package netproxy
import (
"context"
"net/http"
"net/url"
"path/filepath"
"strings"
"testing"
"time"
"github.com/yetone/magpie/internal/settings"
)
func TestParseScutil(t *testing.T) {
out := `<dictionary> {
ExceptionsList : <array> {
0 : 127.0.0.1
1 : *.local
}
HTTPEnable : 1
HTTPPort : 7890
HTTPProxy : 127.0.0.1
HTTPSEnable : 1
HTTPSPort : 7891
HTTPSProxy : 127.0.0.1
SOCKSEnable : 1
SOCKSPort : 7892
SOCKSProxy : 127.0.0.1
}`
p := parseScutil(out)
if p.URL != "http://127.0.0.1:7891" || len(p.Bypass) != 2 || p.Bypass[1] != "*.local" {
t.Fatalf("%+v", p)
}
if p := parseScutil("<dictionary> {\n SOCKSEnable : 1\n SOCKSPort : 1080\n SOCKSProxy : 10.0.0.2\n}"); p.URL != "socks5://10.0.0.2:1080" {
t.Fatalf("socks: %+v", p)
}
if p := parseScutil("<dictionary> {\n HTTPEnable : 0\n HTTPProxy : x\n}"); p.URL != "" {
t.Fatalf("off: %+v", p)
}
}
func TestParseWindows(t *testing.T) {
for server, want := range map[string]string{
"127.0.0.1:7890": "http://127.0.0.1:7890",
"http=127.0.0.1:1;https=127.0.0.1:2": "http://127.0.0.1:2",
"socks=127.0.0.1:1080": "socks5://127.0.0.1:1080",
"": "",
} {
if got := parseWindows(server, "").URL; got != want {
t.Errorf("%q: %q, want %q", server, got, want)
}
}
p := parseWindows("127.0.0.1:7890", "*.corp;<local>")
if !bypassed("git.corp", p.Bypass) || !bypassed("intranet", p.Bypass) || bypassed("chatgpt.com", p.Bypass) {
t.Fatalf("bypass: %+v", p.Bypass)
}
}
func TestFor(t *testing.T) {
dir := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", dir)
for _, k := range []string{"HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy", "NO_PROXY", "no_proxy"} {
t.Setenv(k, "")
}
sysCache.at, sysCache.p = farFuture, Proxy{URL: "http://127.0.0.1:7890", Bypass: []string{"*.corp"}}
t.Cleanup(func() { sysCache.at = zero })
u, _ := url.Parse("https://chatgpt.com/backend-api/codex/responses")
if p, _ := For(u); p == nil || p.String() != "http://127.0.0.1:7890" {
t.Fatalf("system: %v", p)
}
corp, _ := url.Parse("https://git.corp/x")
if p, _ := For(corp); p != nil {
t.Fatalf("bypass: %v", p)
}
local := &http.Request{URL: &url.URL{Scheme: "http", Host: "127.0.0.1:3425"}}
if p, _ := Func(local); p != nil {
t.Fatalf("loopback: %v", p)
}
// magpie's own setting wins; "direct" turns it off
settings.Save(settings.Settings{Proxy: "socks5://127.0.0.1:1080"})
if p, _ := For(u); p == nil || p.String() != "socks5://127.0.0.1:1080" {
t.Fatalf("setting: %v", p)
}
settings.Save(settings.Settings{Proxy: "direct"})
if p, _ := For(u); p != nil {
t.Fatalf("direct: %v", p)
}
if err := settings.Save(settings.Settings{Proxy: "ftp://x"}); err == nil {
t.Fatal("ftp proxy accepted")
}
if settings.Save(settings.Settings{Proxy: "127.0.0.1:7890"}) != nil || filepath.Dir(settings.Path()) != filepath.Join(dir, "magpie") {
t.Fatal("host:port proxy refused")
}
}
func TestEnv(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
sysCache.at, sysCache.p = farFuture, Proxy{URL: "http://127.0.0.1:7890", Bypass: []string{"*.corp", "<local>"}}
t.Cleanup(func() { sysCache.at = zero })
get := func(env []string, k string) string {
v := ""
for _, e := range env {
if key, val, _ := strings.Cut(e, "="); key == k {
v = val
}
}
return v
}
// the system's proxy, where the environment names none
env := Env([]string{"PATH=/bin", "HOME=/h"})
if get(env, "HTTPS_PROXY") != "http://127.0.0.1:7890" || get(env, "all_proxy") != "http://127.0.0.1:7890" ||
get(env, "NO_PROXY") != "localhost,127.0.0.1,::1,.corp" || get(env, "PATH") != "/bin" {
t.Fatalf("system: %v", env)
}
// one the environment names is kept
env = Env([]string{"https_proxy=http://10.0.0.1:1", "NO_PROXY=x"})
if len(env) != 2 || get(env, "https_proxy") != "http://10.0.0.1:1" {
t.Fatalf("environment: %v", env)
}
// magpie's own replaces the environment's
settings.Save(settings.Settings{Proxy: "127.0.0.1:6152"})
env = Env([]string{"HTTPS_PROXY=http://10.0.0.1:1", "PATH=/bin"})
if get(env, "HTTPS_PROXY") != "http://127.0.0.1:6152" || get(env, "http_proxy") != "http://127.0.0.1:6152" ||
get(env, "NO_PROXY") != "localhost,127.0.0.1,::1" || len(env) != 9 {
t.Fatalf("setting: %v", env)
}
// and "direct" leaves none
settings.Save(settings.Settings{Proxy: "direct"})
if env = Env([]string{"HTTPS_PROXY=http://10.0.0.1:1", "PATH=/bin"}); len(env) != 1 {
t.Fatalf("direct: %v", env)
}
}
var farFuture = time.Now().Add(time.Hour)
var zero time.Time
// A provider's own proxy (#237) wins over the global one, "direct" over
// both, and each proxy is given one transport of its own, kept.
func TestWith(t *testing.T) {
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
settings.Save(settings.Settings{Proxy: "127.0.0.1:6152"})
u := &url.URL{Scheme: "https", Host: "api.openai.com"}
req := func(choice string) *http.Request {
return (&http.Request{URL: u}).WithContext(With(context.Background(), choice))
}
if p, _ := Func(req("")); p == nil || p.String() != "http://127.0.0.1:6152" {
t.Fatalf("follow: %v", p)
}
if p, _ := Func(req("socks5://127.0.0.1:1080")); p == nil || p.String() != "socks5://127.0.0.1:1080" {
t.Fatalf("own: %v", p)
}
if p, _ := Func(req("direct")); p != nil {
t.Fatalf("direct: %v", p)
}
local := (&http.Request{URL: &url.URL{Scheme: "http", Host: "127.0.0.1:3425"}}).WithContext(With(context.Background(), "127.0.0.1:1"))
if p, _ := Func(local); p != nil {
t.Fatalf("loopback: %v", p)
}
d := Dispatch(&http.Transport{Proxy: Func}).(*dispatch)
a, b := d.transport("127.0.0.1:1"), d.transport("direct")
if a == b || d.transport("127.0.0.1:1") != a || len(d.own) != 2 {
t.Fatalf("transports: %d kept", len(d.own))
}
if p, _ := a.Proxy(req("")); p == nil || p.Host != "127.0.0.1:1" {
t.Fatalf("own transport: %v", p)
}
if p, _ := b.Proxy(req("")); p != nil {
t.Fatalf("direct transport: %v", p)
}
get := func(env []string, k string) string {
for _, e := range env {
if key, val, _ := strings.Cut(e, "="); key == k {
return val
}
}
return ""
}
if env := EnvWith("socks5://127.0.0.1:1080", []string{"PATH=/bin"}); get(env, "HTTPS_PROXY") != "socks5://127.0.0.1:1080" {
t.Fatalf("own env: %v", env)
}
if env := EnvWith("direct", []string{"HTTPS_PROXY=http://x:1", "PATH=/bin"}); len(env) != 1 {
t.Fatalf("direct env: %v", env)
}
if env := EnvWith("", []string{"PATH=/bin"}); get(env, "HTTPS_PROXY") != "http://127.0.0.1:6152" {
t.Fatalf("follow env: %v", env)
}
}