144 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|