feat(sora): 强制Sora走curl_cffi sidecar并完善校验测试

This commit is contained in:
yangjianbo
2026-02-19 20:29:31 +08:00
parent 40498aac9d
commit 36a1a7998b
6 changed files with 486 additions and 12 deletions

View File

@@ -4,6 +4,7 @@ package service
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"io"
@@ -639,3 +640,144 @@ func TestSoraDirectClient_PostVideoForWatermarkFree(t *testing.T) {
require.Equal(t, "/backend-api/sentinel/req", upstream.calls[0].Path)
require.Equal(t, "/backend/project_y/post", upstream.calls[1].Path)
}
type soraClientFallbackUpstream struct {
doWithTLSCalls int32
respBody string
respStatusCode int
err error
}
func (u *soraClientFallbackUpstream) Do(_ *http.Request, _ string, _ int64, _ int) (*http.Response, error) {
return nil, errors.New("unexpected Do call")
}
func (u *soraClientFallbackUpstream) DoWithTLS(_ *http.Request, _ string, _ int64, _ int, _ bool) (*http.Response, error) {
atomic.AddInt32(&u.doWithTLSCalls, 1)
if u.err != nil {
return nil, u.err
}
statusCode := u.respStatusCode
if statusCode <= 0 {
statusCode = http.StatusOK
}
body := u.respBody
if body == "" {
body = `{"ok":true}`
}
return newSoraClientMockResponse(statusCode, body), nil
}
func TestSoraDirectClient_DoHTTP_UsesCurlCFFISidecarWhenEnabled(t *testing.T) {
var captured soraCurlCFFISidecarRequest
sidecar := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "/request", r.URL.Path)
raw, err := io.ReadAll(r.Body)
require.NoError(t, err)
require.NoError(t, json.Unmarshal(raw, &captured))
_ = json.NewEncoder(w).Encode(map[string]any{
"status_code": http.StatusOK,
"headers": map[string]any{
"Content-Type": "application/json",
"X-Sidecar": []string{"yes"},
},
"body_base64": base64.StdEncoding.EncodeToString([]byte(`{"ok":true}`)),
})
}))
defer sidecar.Close()
upstream := &soraClientFallbackUpstream{}
cfg := &config.Config{
Sora: config.SoraConfig{
Client: config.SoraClientConfig{
BaseURL: "https://sora.chatgpt.com/backend",
CurlCFFISidecar: config.SoraCurlCFFISidecarConfig{
Enabled: true,
BaseURL: sidecar.URL,
Impersonate: "chrome131",
TimeoutSeconds: 15,
},
},
},
}
client := NewSoraDirectClient(cfg, upstream, nil)
req, err := http.NewRequest(http.MethodPost, "https://sora.chatgpt.com/backend/me", strings.NewReader("hello-sidecar"))
require.NoError(t, err)
req.Header.Set("User-Agent", "test-ua")
resp, err := client.doHTTP(req, "http://127.0.0.1:18080", &Account{ID: 1})
require.NoError(t, err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.JSONEq(t, `{"ok":true}`, string(body))
require.Equal(t, int32(0), atomic.LoadInt32(&upstream.doWithTLSCalls))
require.Equal(t, "http://127.0.0.1:18080", captured.ProxyURL)
require.Equal(t, "chrome131", captured.Impersonate)
require.Equal(t, "https://sora.chatgpt.com/backend/me", captured.URL)
decodedReqBody, err := base64.StdEncoding.DecodeString(captured.BodyBase64)
require.NoError(t, err)
require.Equal(t, "hello-sidecar", string(decodedReqBody))
}
func TestSoraDirectClient_DoHTTP_CurlCFFISidecarFailureReturnsError(t *testing.T) {
sidecar := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
_, _ = w.Write([]byte(`{"error":"boom"}`))
}))
defer sidecar.Close()
upstream := &soraClientFallbackUpstream{respBody: `{"fallback":true}`}
cfg := &config.Config{
Sora: config.SoraConfig{
Client: config.SoraClientConfig{
BaseURL: "https://sora.chatgpt.com/backend",
CurlCFFISidecar: config.SoraCurlCFFISidecarConfig{
Enabled: true,
BaseURL: sidecar.URL,
},
},
},
}
client := NewSoraDirectClient(cfg, upstream, nil)
req, err := http.NewRequest(http.MethodGet, "https://sora.chatgpt.com/backend/me", nil)
require.NoError(t, err)
_, err = client.doHTTP(req, "", &Account{ID: 2})
require.Error(t, err)
require.Contains(t, err.Error(), "sora curl_cffi sidecar")
require.Equal(t, int32(0), atomic.LoadInt32(&upstream.doWithTLSCalls))
}
func TestSoraDirectClient_DoHTTP_CurlCFFISidecarDisabledUsesLegacyStack(t *testing.T) {
upstream := &soraClientFallbackUpstream{respBody: `{"legacy":true}`}
cfg := &config.Config{
Sora: config.SoraConfig{
Client: config.SoraClientConfig{
BaseURL: "https://sora.chatgpt.com/backend",
CurlCFFISidecar: config.SoraCurlCFFISidecarConfig{
Enabled: false,
BaseURL: "http://127.0.0.1:18080",
},
},
},
}
client := NewSoraDirectClient(cfg, upstream, nil)
req, err := http.NewRequest(http.MethodGet, "https://sora.chatgpt.com/backend/me", nil)
require.NoError(t, err)
resp, err := client.doHTTP(req, "", &Account{ID: 3})
require.NoError(t, err)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.JSONEq(t, `{"legacy":true}`, string(body))
require.Equal(t, int32(1), atomic.LoadInt32(&upstream.doWithTLSCalls))
}
func TestConvertSidecarHeaderValue_NilAndSlice(t *testing.T) {
require.Nil(t, convertSidecarHeaderValue(nil))
require.Equal(t, []string{"a", "b"}, convertSidecarHeaderValue([]any{"a", " ", "b"}))
}