官方 Gemini 平台配置的共享 HTTP Proxy 会拒绝香港出口的 CONNECT 请求,导致香港 Worker 的真实 VEO 与图片任务进入 upstream_submission_unknown。 新增仅按平台 UUID 生效的代理直连白名单,并只在香港 Worker 为官方 Gemini 平台启用;宁波与其他平台继续使用原代理策略。 验证:API 全量 go test、代理路由单测、bash -n、ShellCheck、cluster release helper、manual release 与差异检查均通过。
174 lines
5.6 KiB
Go
174 lines
5.6 KiB
Go
package runner
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/config"
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
|
)
|
|
|
|
func TestProviderHTTPClientTimeoutAllowsLongRunningMediaRequests(t *testing.T) {
|
|
client := newHTTPClient(nil)
|
|
if client.Timeout != 10*time.Minute {
|
|
t.Fatalf("unexpected provider HTTP timeout: got %s want %s", client.Timeout, 10*time.Minute)
|
|
}
|
|
transport, ok := client.Transport.(*http.Transport)
|
|
if !ok {
|
|
t.Fatalf("provider transport type = %T, want *http.Transport", client.Transport)
|
|
}
|
|
if transport.MaxIdleConns != providerHTTPMaxIdleConnections ||
|
|
transport.MaxIdleConnsPerHost != providerHTTPMaxIdleConnectionsPerHost {
|
|
t.Fatalf(
|
|
"provider idle connection pool = %d/%d, want %d/%d",
|
|
transport.MaxIdleConns,
|
|
transport.MaxIdleConnsPerHost,
|
|
providerHTTPMaxIdleConnections,
|
|
providerHTTPMaxIdleConnectionsPerHost,
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestPlatformProxyModeNoneIgnoresEnvironmentProxy(t *testing.T) {
|
|
var proxyHits int
|
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
proxyHits++
|
|
w.WriteHeader(http.StatusTeapot)
|
|
}))
|
|
defer proxy.Close()
|
|
t.Setenv("HTTP_PROXY", proxy.URL)
|
|
t.Setenv("http_proxy", proxy.URL)
|
|
|
|
var targetHits int
|
|
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
targetHits++
|
|
_, _ = w.Write([]byte("ok"))
|
|
}))
|
|
defer target.Close()
|
|
|
|
client, err := testProxyService(config.Config{}).httpClientForCandidate(store.RuntimeModelCandidate{
|
|
PlatformConfig: map[string]any{"networkProxy": map[string]any{"mode": "none"}},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatalf("build http client: %v", err)
|
|
}
|
|
resp, err := client.Get(target.URL)
|
|
if err != nil {
|
|
t.Fatalf("get target: %v", err)
|
|
}
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
_ = resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK || targetHits != 1 || proxyHits != 0 {
|
|
t.Fatalf("unexpected status=%d targetHits=%d proxyHits=%d", resp.StatusCode, targetHits, proxyHits)
|
|
}
|
|
}
|
|
|
|
func TestPlatformProxyModeCustomUsesConfiguredHTTPProxy(t *testing.T) {
|
|
var targetHits int
|
|
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
targetHits++
|
|
_, _ = w.Write([]byte("target"))
|
|
}))
|
|
defer target.Close()
|
|
|
|
var proxyHits int
|
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
proxyHits++
|
|
if r.URL.String() != target.URL && r.URL.String() != target.URL+"/" {
|
|
t.Fatalf("proxy received unexpected target URL %q", r.URL.String())
|
|
}
|
|
_, _ = w.Write([]byte("proxied"))
|
|
}))
|
|
defer proxy.Close()
|
|
|
|
client, err := testProxyService(config.Config{}).httpClientForCandidate(store.RuntimeModelCandidate{
|
|
PlatformConfig: map[string]any{"networkProxy": map[string]any{"mode": "custom", "httpProxy": proxy.URL}},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatalf("build http client: %v", err)
|
|
}
|
|
resp, err := client.Get(target.URL)
|
|
if err != nil {
|
|
t.Fatalf("get target through proxy: %v", err)
|
|
}
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
_ = resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK || proxyHits != 1 || targetHits != 0 {
|
|
t.Fatalf("unexpected status=%d proxyHits=%d targetHits=%d", resp.StatusCode, proxyHits, targetHits)
|
|
}
|
|
}
|
|
|
|
func TestPlatformProxyBypassIDUsesDirectConnection(t *testing.T) {
|
|
var targetHits int
|
|
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
targetHits++
|
|
_, _ = w.Write([]byte("target"))
|
|
}))
|
|
defer target.Close()
|
|
|
|
var proxyHits int
|
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
proxyHits++
|
|
w.WriteHeader(http.StatusProxyAuthRequired)
|
|
}))
|
|
defer proxy.Close()
|
|
|
|
client, err := testProxyService(config.Config{
|
|
PlatformProxyBypassIDs: "other-platform, official-gemini-platform ",
|
|
}).httpClientForCandidate(store.RuntimeModelCandidate{
|
|
PlatformID: "official-gemini-platform",
|
|
PlatformConfig: map[string]any{"networkProxy": map[string]any{"mode": "custom", "httpProxy": proxy.URL}},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatalf("build bypassed http client: %v", err)
|
|
}
|
|
resp, err := client.Get(target.URL)
|
|
if err != nil {
|
|
t.Fatalf("get target directly: %v", err)
|
|
}
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
_ = resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK || targetHits != 1 || proxyHits != 0 {
|
|
t.Fatalf("unexpected status=%d targetHits=%d proxyHits=%d", resp.StatusCode, targetHits, proxyHits)
|
|
}
|
|
}
|
|
|
|
func TestPlatformProxyModeGlobalUsesConfiguredGlobalHTTPProxy(t *testing.T) {
|
|
var targetHits int
|
|
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
targetHits++
|
|
_, _ = w.Write([]byte("target"))
|
|
}))
|
|
defer target.Close()
|
|
|
|
var proxyHits int
|
|
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
proxyHits++
|
|
_, _ = w.Write([]byte("proxied"))
|
|
}))
|
|
defer proxy.Close()
|
|
|
|
client, err := testProxyService(config.Config{GlobalHTTPProxy: proxy.URL}).httpClientForCandidate(store.RuntimeModelCandidate{
|
|
PlatformConfig: map[string]any{"networkProxy": map[string]any{"mode": "global"}},
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatalf("build http client: %v", err)
|
|
}
|
|
resp, err := client.Get(target.URL)
|
|
if err != nil {
|
|
t.Fatalf("get target through global proxy: %v", err)
|
|
}
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
_ = resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK || proxyHits != 1 || targetHits != 0 {
|
|
t.Fatalf("unexpected status=%d proxyHits=%d targetHits=%d", resp.StatusCode, proxyHits, targetHits)
|
|
}
|
|
}
|
|
|
|
func testProxyService(cfg config.Config) *Service {
|
|
return &Service{cfg: cfg, httpClients: newHTTPClientCache()}
|
|
}
|