源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
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
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
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user