Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

301 lines
9.4 KiB
Go

package runtimecmd
import (
"net/http"
"net/url"
"runtime"
"strings"
"testing"
"time"
"golang.org/x/net/http/httpproxy"
)
// Real-world `scutil --proxy` output with Clash Verge system proxy enabled
// (HTTP/HTTPS/SOCKS all pointing at 127.0.0.1:7890).
const scutilProxyEnabled = `<dictionary> {
ExceptionsList : <array> {
0 : 127.0.0.1
1 : localhost
2 : *.local
}
HTTPEnable : 1
HTTPPort : 7890
HTTPProxy : 127.0.0.1
HTTPSEnable : 1
HTTPSPort : 7890
HTTPSProxy : 127.0.0.1
ProxyAutoConfigEnable : 0
SOCKSEnable : 1
SOCKSPort : 7890
SOCKSProxy : 127.0.0.1
}`
const scutilProxyDisabled = `<dictionary> {
ExceptionsList : <array> {
0 : 127.0.0.1
1 : localhost
}
HTTPEnable : 0
HTTPSEnable : 0
ProxyAutoConfigEnable : 0
SOCKSEnable : 0
}`
func TestParseScutilProxyEnabled(t *testing.T) {
got := parseScutilProxy(scutilProxyEnabled)
want := map[string]string{
"HTTPS_PROXY": "http://127.0.0.1:7890",
"HTTP_PROXY": "http://127.0.0.1:7890",
"NO_PROXY": noProxyDefault,
}
for k, v := range want {
if got[k] != v {
t.Fatalf("parseScutilProxy()[%q] = %q, want %q", k, got[k], v)
}
}
}
func TestParseScutilProxyDisabledReturnsNil(t *testing.T) {
if got := parseScutilProxy(scutilProxyDisabled); got != nil {
t.Fatalf("parseScutilProxy() = %v, want nil", got)
}
}
func TestParseScutilProxyHTTPOnly(t *testing.T) {
out := `<dictionary> {
HTTPEnable : 1
HTTPProxy : 10.0.0.2
HTTPPort : 3128
HTTPSEnable : 0
SOCKSEnable : 0
}`
got := parseScutilProxy(out)
if got["HTTP_PROXY"] != "http://10.0.0.2:3128" || got["HTTPS_PROXY"] != "http://10.0.0.2:3128" {
t.Fatalf("parseScutilProxy() = %v, want HTTP(S)_PROXY=http://10.0.0.2:3128", got)
}
}
func TestParseScutilProxySOCKSOnlyIgnored(t *testing.T) {
out := `<dictionary> {
HTTPEnable : 0
HTTPSEnable : 0
SOCKSEnable : 1
SOCKSProxy : 127.0.0.1
SOCKSPort : 7890
}`
if got := parseScutilProxy(out); got != nil {
t.Fatalf("parseScutilProxy() with SOCKS only = %v, want nil (SOCKS skipped)", got)
}
}
func TestParseWindowsProxyServerSingleProxy(t *testing.T) {
got := parseWindowsProxyServer("127.0.0.1:7890", "<local>;*.corp.example;localhost")
if got["HTTPS_PROXY"] != "http://127.0.0.1:7890" || got["HTTP_PROXY"] != "http://127.0.0.1:7890" {
t.Fatalf("parseWindowsProxyServer() proxy = %v", got)
}
if got["NO_PROXY"] != "localhost,127.0.0.1,::1,.local,.corp.example" {
t.Fatalf("parseWindowsProxyServer() NO_PROXY = %q", got["NO_PROXY"])
}
}
func TestParseWindowsProxyServerProtocolMap(t *testing.T) {
got := parseWindowsProxyServer("http=proxy.internal:8080;https=secure.internal:8443;socks=127.0.0.1:1080", "")
if got["HTTP_PROXY"] != "http://proxy.internal:8080" || got["HTTPS_PROXY"] != "http://secure.internal:8443" {
t.Fatalf("parseWindowsProxyServer() = %v", got)
}
}
func TestParseWindowsProxyServerIgnoresSocksOnly(t *testing.T) {
if got := parseWindowsProxyServer("socks=127.0.0.1:1080", ""); got != nil {
t.Fatalf("parseWindowsProxyServer() = %v, want nil", got)
}
}
func TestEnvInjectsSystemProxy(t *testing.T) {
resolver := Resolver{
Environ: func() []string { return []string{"PATH=/usr/bin:/bin"} },
HomeDir: func() (string, error) { return t.TempDir(), nil },
ScutilProxy: func() (string, bool) { return scutilProxyEnabled, true },
}
env := resolver.Env(nil)
if got := envValue(env, "HTTPS_PROXY"); got != "http://127.0.0.1:7890" {
t.Fatalf("HTTPS_PROXY = %q, want http://127.0.0.1:7890", got)
}
if got := envValue(env, "HTTP_PROXY"); got != "http://127.0.0.1:7890" {
t.Fatalf("HTTP_PROXY = %q, want http://127.0.0.1:7890", got)
}
if got := envValue(env, "NO_PROXY"); got != noProxyDefault {
t.Fatalf("NO_PROXY = %q, want %q", got, noProxyDefault)
}
}
func TestEnvDoesNotOverrideExplicitProxy(t *testing.T) {
resolver := Resolver{
// User already exported a (lowercase) proxy — must be preserved.
Environ: func() []string {
return []string{"PATH=/usr/bin:/bin", "https_proxy=http://user-set:1080"}
},
HomeDir: func() (string, error) { return t.TempDir(), nil },
ScutilProxy: func() (string, bool) { return scutilProxyEnabled, true },
}
env := resolver.Env(nil)
if got := envValue(env, "https_proxy"); got != "http://user-set:1080" {
t.Fatalf("https_proxy = %q, want it preserved as http://user-set:1080", got)
}
// And we must not have appended a conflicting upper-case HTTPS_PROXY.
count := 0
for _, item := range env {
if k, _, _ := strings.Cut(item, "="); strings.EqualFold(k, "HTTPS_PROXY") {
count++
}
}
if count != 1 {
t.Fatalf("found %d HTTPS_PROXY entries, want exactly 1 (no override)", count)
}
}
func TestEnvNoProxyWhenScutilUnavailable(t *testing.T) {
resolver := Resolver{
Environ: func() []string { return []string{"PATH=/usr/bin:/bin"} },
HomeDir: func() (string, error) { return t.TempDir(), nil },
ScutilProxy: func() (string, bool) { return "", false },
}
env := resolver.Env(nil)
if got := envValue(env, "HTTPS_PROXY"); got != "" {
t.Fatalf("HTTPS_PROXY = %q, want empty when scutil unavailable", got)
}
}
func proxyFor(t *testing.T, cfg *httpproxy.Config, rawURL string) *url.URL {
t.Helper()
target, err := url.Parse(rawURL)
if err != nil {
t.Fatalf("url.Parse(%q): %v", rawURL, err)
}
proxy, err := cfg.ProxyFunc()(target)
if err != nil {
t.Fatalf("ProxyFunc(%q): %v", rawURL, err)
}
return proxy
}
func TestMergeSystemProxyEnvWins(t *testing.T) {
cfg := &httpproxy.Config{HTTPSProxy: "http://env-proxy:8080"}
mergeSystemProxy(cfg, map[string]string{
"HTTPS_PROXY": "http://system-proxy:7890",
"HTTP_PROXY": "http://system-proxy:7890",
"NO_PROXY": noProxyDefault,
})
if cfg.HTTPSProxy != "http://env-proxy:8080" {
t.Fatalf("HTTPSProxy = %q, want env value preserved", cfg.HTTPSProxy)
}
if cfg.HTTPProxy != "http://system-proxy:7890" {
t.Fatalf("HTTPProxy = %q, want system value filling the blank", cfg.HTTPProxy)
}
if got := proxyFor(t, cfg, "https://api.anthropic.com/v1"); got == nil || got.Host != "env-proxy:8080" {
t.Fatalf("proxy = %v, want env proxy env-proxy:8080", got)
}
}
func TestMergeSystemProxyFallsBackToSystem(t *testing.T) {
cfg := &httpproxy.Config{}
mergeSystemProxy(cfg, map[string]string{
"HTTPS_PROXY": "http://system-proxy:7890",
"HTTP_PROXY": "http://system-proxy:7890",
"NO_PROXY": noProxyDefault,
})
if got := proxyFor(t, cfg, "https://api.anthropic.com/v1"); got == nil || got.Host != "system-proxy:7890" {
t.Fatalf("proxy = %v, want system proxy when env has none", got)
}
}
func TestMergeSystemProxyDirectWhenNoneConfigured(t *testing.T) {
cfg := &httpproxy.Config{}
mergeSystemProxy(cfg, nil)
if got := proxyFor(t, cfg, "https://api.anthropic.com/v1"); got != nil {
t.Fatalf("proxy = %v, want nil (direct) when nothing configured", got)
}
}
func TestMergeSystemProxyBypassesLoopbackAndNoProxy(t *testing.T) {
cfg := &httpproxy.Config{}
mergeSystemProxy(cfg, map[string]string{
"HTTPS_PROXY": "http://system-proxy:7890",
"HTTP_PROXY": "http://system-proxy:7890",
"NO_PROXY": noProxyDefault,
})
for _, target := range []string{
"http://127.0.0.1:4545/v1/health",
"http://localhost:4545/v1/health",
"https://printer.local/status",
} {
if got := proxyFor(t, cfg, target); got != nil {
t.Fatalf("proxy for %q = %v, want nil (local bypass)", target, got)
}
}
if got := proxyFor(t, cfg, "https://api.anthropic.com/v1"); got == nil {
t.Fatalf("proxy for external target = nil, want system proxy")
}
}
func TestMergeSystemProxyDefaultsNoProxyForEnvOnlyProxy(t *testing.T) {
// Env sets a proxy but no NO_PROXY, and no system proxy exists: the
// default exclusions still apply so .local names never hit the proxy.
cfg := &httpproxy.Config{HTTPSProxy: "http://env-proxy:8080"}
mergeSystemProxy(cfg, nil)
if cfg.NoProxy != noProxyDefault {
t.Fatalf("NoProxy = %q, want default %q", cfg.NoProxy, noProxyDefault)
}
}
func TestProxyAutodetectDisabledSkipsInjection(t *testing.T) {
t.Setenv(disableProxyAutodetectEnvKey, "1")
resolver := Resolver{
Environ: func() []string { return []string{"PATH=/usr/bin:/bin"} },
HomeDir: func() (string, error) { return t.TempDir(), nil },
ScutilProxy: func() (string, bool) { return scutilProxyEnabled, true },
}
env := resolver.Env(nil)
if got := envValue(env, "HTTPS_PROXY"); got != "" {
t.Fatalf("HTTPS_PROXY = %q, want empty when autodetect disabled", got)
}
}
func TestProxyAutodetectDisabledKeepsEnvProxy(t *testing.T) {
t.Setenv(disableProxyAutodetectEnvKey, "true")
t.Setenv("HTTPS_PROXY", "http://env-proxy:8080")
fn := DynamicProxyFunc()
request, err := http.NewRequest(http.MethodGet, "https://api.anthropic.com/v1", nil)
if err != nil {
t.Fatalf("NewRequest: %v", err)
}
got, err := fn(request)
if err != nil {
t.Fatalf("proxy func err = %v", err)
}
if got == nil || got.Host != "env-proxy:8080" {
t.Fatalf("proxy = %v, want explicit env proxy to keep working", got)
}
}
func TestSystemProxyEnvCachesScutil(t *testing.T) {
if runtime.GOOS != "darwin" {
t.Skip("scutil cache path is darwin-only")
}
systemProxyCache.Lock()
systemProxyCache.env = map[string]string{"HTTPS_PROXY": "http://cached-proxy:7890"}
systemProxyCache.expiresAt = time.Now().Add(time.Minute)
systemProxyCache.Unlock()
t.Cleanup(func() {
systemProxyCache.Lock()
systemProxyCache.env = nil
systemProxyCache.expiresAt = time.Time{}
systemProxyCache.Unlock()
})
got := Resolver{}.systemProxyEnv()
if got["HTTPS_PROXY"] != "http://cached-proxy:7890" {
t.Fatalf("systemProxyEnv() = %v, want cached value within TTL", got)
}
}