sub2api/backend/internal/service/bootstrap_preflight_test.go
win 435ae221bc
Some checks failed
CI / test (push) Failing after 1m32s
CI / golangci-lint (push) Failing after 31s
Security Scan / backend-security (push) Failing after 1m32s
Security Scan / frontend-security (push) Failing after 9s
x
2026-04-16 19:11:47 +08:00

144 lines
4.4 KiB
Go

package service
import (
"context"
"io"
"net/http"
"strings"
"testing"
claude "github.com/Wei-Shaw/sub2api/internal/pkg/claude"
"github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
)
type bootstrapHTTPUpstreamStub struct {
doCalled bool
doWithTLSCalled bool
lastProxyURL string
lastAccountID int64
lastConcurrency int
lastTLSProfile *tlsfingerprint.Profile
lastRequestMethod string
}
func (s *bootstrapHTTPUpstreamStub) Do(req *http.Request, proxyURL string, accountID int64, accountConcurrency int) (*http.Response, error) {
s.doCalled = true
s.lastProxyURL = proxyURL
s.lastAccountID = accountID
s.lastConcurrency = accountConcurrency
if req != nil {
s.lastRequestMethod = req.Method
}
return &http.Response{
StatusCode: http.StatusNoContent,
Body: io.NopCloser(strings.NewReader("")),
Header: make(http.Header),
}, nil
}
func (s *bootstrapHTTPUpstreamStub) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, profile *tlsfingerprint.Profile) (*http.Response, error) {
s.doWithTLSCalled = true
s.lastProxyURL = proxyURL
s.lastAccountID = accountID
s.lastConcurrency = accountConcurrency
s.lastTLSProfile = profile
if req != nil {
s.lastRequestMethod = req.Method
}
return &http.Response{
StatusCode: http.StatusNoContent,
Body: io.NopCloser(strings.NewReader("")),
Header: make(http.Header),
}, nil
}
func TestBackgroundSimulatorApplyBackgroundHeaders_UsesClaudeCodeBackgroundProfile(t *testing.T) {
bg := &backgroundSimulator{}
state := &accountBackgroundState{
accessToken: "token-123",
accountID: 42,
instanceSalt: "salt-123",
}
req, err := http.NewRequest(http.MethodGet, "https://api.anthropic.com/api/claude_cli/bootstrap", nil)
if err != nil {
t.Fatalf("NewRequest() error = %v", err)
}
bg.applyBackgroundHeaders(req, state, "application/json")
if got := req.Header.Get("Accept"); got != "application/json, text/plain, */*" {
t.Fatalf("Accept = %q", got)
}
if got := req.Header.Get("Content-Type"); got != "application/json" {
t.Fatalf("Content-Type = %q", got)
}
if got := req.Header.Get("User-Agent"); got != claude.DefaultCodeUserAgent() {
t.Fatalf("User-Agent = %q, want %q", got, claude.DefaultCodeUserAgent())
}
if got := req.Header.Get("Authorization"); got != "Bearer token-123" {
t.Fatalf("Authorization = %q", got)
}
if got := req.Header.Get("anthropic-beta"); got != claude.BetaOAuth {
t.Fatalf("anthropic-beta = %q", got)
}
if got := req.Header.Get("anthropic-version"); got != "" {
t.Fatalf("anthropic-version = %q, want empty", got)
}
if got := req.Header.Get("x-app"); got != "" {
t.Fatalf("x-app = %q, want empty", got)
}
if got := req.Header.Get("X-Claude-Code-Session-Id"); got != "" {
t.Fatalf("X-Claude-Code-Session-Id = %q, want empty", got)
}
if got := req.Header.Get("x-client-request-id"); got != "" {
t.Fatalf("x-client-request-id = %q, want empty", got)
}
}
func TestBackgroundSimulatorDoRequest_UsesSharedUpstreamForHTTPS(t *testing.T) {
stub := &bootstrapHTTPUpstreamStub{}
bg := &backgroundSimulator{}
profile := &tlsfingerprint.Profile{Name: "test-profile"}
state := &accountBackgroundState{
accountID: 7,
proxyURL: "http://127.0.0.1:8080",
httpUpstream: stub,
tlsProfile: profile,
useSharedUpstream: true,
}
req, err := http.NewRequest(http.MethodGet, "https://api.anthropic.com/api/claude_code/policy_limits", nil)
if err != nil {
t.Fatalf("NewRequest() error = %v", err)
}
resp, err := bg.doRequest(context.Background(), state, req)
if err != nil {
t.Fatalf("doRequest() error = %v", err)
}
defer resp.Body.Close()
if !stub.doWithTLSCalled {
t.Fatal("expected DoWithTLS to be called for https background requests")
}
if stub.doCalled {
t.Fatal("did not expect Do to be called for https background requests")
}
if stub.lastProxyURL != state.proxyURL {
t.Fatalf("proxyURL = %q, want %q", stub.lastProxyURL, state.proxyURL)
}
if stub.lastAccountID != state.accountID {
t.Fatalf("accountID = %d, want %d", stub.lastAccountID, state.accountID)
}
if stub.lastConcurrency != 0 {
t.Fatalf("accountConcurrency = %d, want 0", stub.lastConcurrency)
}
if stub.lastTLSProfile != profile {
t.Fatalf("TLS profile pointer mismatch")
}
if stub.lastRequestMethod != http.MethodGet {
t.Fatalf("request method = %q, want %q", stub.lastRequestMethod, http.MethodGet)
}
}