//go:build unit package service import ( "io" "net/http" "strings" "testing" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" ) func adaptiveCNAccountTestAccount(id int64, platform string) *Account { return &Account{ ID: id, Name: "adaptive-cn-test", Platform: platform, Type: AccountTypeAPIKey, Status: StatusActive, Concurrency: 1, Credentials: map[string]any{ "api_key": "sk-adaptive-test", "api_protocol": APIProtocolAdaptive, "api_base_urls": map[string]any{ APIProtocolChatCompletions: "http://chat.example/v1", APIProtocolAnthropic: "http://anthropic.example", APIProtocolResponses: "http://responses.example", }, }, } } func adaptiveCNAccountTestService(account *Account, responses ...*http.Response) (*AccountTestService, *httpUpstreamRecorder) { repo := &openAIAccountTestRepo{ mockAccountRepoForGemini: mockAccountRepoForGemini{ accountsByID: map[int64]*Account{account.ID: account}, }, } upstream := &httpUpstreamRecorder{responses: responses} return &AccountTestService{ accountRepo: repo, httpUpstream: upstream, cfg: rawChatCompletionsTestConfig(), }, upstream } func adaptiveCNChatTestResponse() *http.Response { return &http.Response{ StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"text/event-stream"}}, Body: io.NopCloser(strings.NewReader(`data: {"choices":[{"delta":{"content":"chat ok"},"finish_reason":"stop"}]} data: [DONE] `)), } } func adaptiveCNAnthropicTestResponse() *http.Response { return &http.Response{ StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"text/event-stream"}}, Body: io.NopCloser(strings.NewReader(`data: {"type":"content_block_delta","delta":{"text":"anthropic ok"}} data: {"type":"message_stop"} `)), } } func adaptiveCNResponsesTestResponse() *http.Response { return &http.Response{ StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"text/event-stream"}}, Body: io.NopCloser(strings.NewReader(`data: {"type":"response.output_text.delta","delta":"responses ok"} data: {"type":"response.completed"} `)), } } func TestAccountTestService_AdaptiveChatOnlyProvidersTestChatAndAnthropicEndpoints(t *testing.T) { for index, testCase := range []struct { name string platform string model string }{ {name: "Kimi", platform: PlatformKimi, model: "kimi-k2.5"}, {name: "Zhipu", platform: PlatformZhipu, model: "glm-4.7"}, } { t.Run(testCase.name, func(t *testing.T) { account := adaptiveCNAccountTestAccount(int64(301+index), testCase.platform) svc, upstream := adaptiveCNAccountTestService( account, adaptiveCNChatTestResponse(), adaptiveCNAnthropicTestResponse(), ) c, recorder := newTestContext() err := svc.TestAccountConnection(c, account.ID, testCase.model, "hello", AccountTestModeDefault) require.NoError(t, err) require.Len(t, upstream.requests, 2) require.Equal(t, "http://chat.example/v1/chat/completions", upstream.requests[0].URL.String()) require.Equal(t, "http://anthropic.example/v1/messages", upstream.requests[1].URL.String()) require.Equal(t, "Bearer sk-adaptive-test", upstream.requests[0].Header.Get("Authorization")) require.Equal(t, "sk-adaptive-test", upstream.requests[1].Header.Get("x-api-key")) require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_start"`)) require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_complete"`)) require.Contains(t, recorder.Body.String(), "已通过原生 /v1/messages 验证") }) } } func TestAccountTestService_AdaptiveDeepSeekAlsoTestsResponsesEndpoint(t *testing.T) { account := adaptiveCNAccountTestAccount(302, PlatformDeepseek) svc, upstream := adaptiveCNAccountTestService( account, adaptiveCNChatTestResponse(), adaptiveCNAnthropicTestResponse(), adaptiveCNResponsesTestResponse(), ) c, recorder := newTestContext() err := svc.TestAccountConnection(c, account.ID, "deepseek-chat", "", AccountTestModeDefault) require.NoError(t, err) require.Len(t, upstream.requests, 3) require.Equal(t, "http://responses.example/responses", upstream.requests[2].URL.String()) require.Equal(t, HTTPUpstreamProfileOpenAI, HTTPUpstreamProfileFromContext(upstream.requests[2].Context())) require.Equal(t, "Bearer sk-adaptive-test", upstream.requests[2].Header.Get("Authorization")) require.True(t, gjson.GetBytes(upstream.bodies[2], "stream").Bool()) require.False(t, gjson.GetBytes(upstream.bodies[2], "store").Bool()) require.False(t, gjson.GetBytes(upstream.bodies[2], "instructions").Exists()) require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_complete"`)) require.Contains(t, recorder.Body.String(), "已通过原生 /responses 验证") } func TestAccountTestService_AdaptiveStopsAndNamesFailingEndpoint(t *testing.T) { account := adaptiveCNAccountTestAccount(303, PlatformDeepseek) svc, upstream := adaptiveCNAccountTestService( account, adaptiveCNChatTestResponse(), newJSONResponse(http.StatusNotFound, `{"error":{"message":"missing messages route"}}`), ) c, recorder := newTestContext() err := svc.TestAccountConnection(c, account.ID, "deepseek-chat", "", AccountTestModeDefault) require.Error(t, err) require.Contains(t, err.Error(), "Adaptive Anthropic endpoint returned 404") require.Len(t, upstream.requests, 2) require.Contains(t, recorder.Body.String(), `"type":"error"`) require.NotContains(t, recorder.Body.String(), `"type":"test_complete"`) } func TestAccountTestService_AdaptiveRejectsInvalidAnthropicSuccessBody(t *testing.T) { account := adaptiveCNAccountTestAccount(305, PlatformKimi) svc, upstream := adaptiveCNAccountTestService( account, adaptiveCNChatTestResponse(), newJSONResponse(http.StatusOK, `not an Anthropic stream`), ) c, recorder := newTestContext() err := svc.TestAccountConnection(c, account.ID, "kimi-k2.5", "", AccountTestModeDefault) require.Error(t, err) require.Contains(t, err.Error(), "Adaptive Anthropic stream ended before message_stop") require.Len(t, upstream.requests, 2) require.Contains(t, recorder.Body.String(), `"type":"error"`) require.NotContains(t, recorder.Body.String(), `"type":"test_complete"`) } func TestAccountTestService_FixedCNChatProtocolStillTestsOnlyChatEndpoint(t *testing.T) { account := adaptiveCNAccountTestAccount(304, PlatformZhipu) account.Credentials["api_protocol"] = APIProtocolChatCompletions account.Credentials["base_url"] = "http://fixed-chat.example/v1" svc, upstream := adaptiveCNAccountTestService(account, adaptiveCNChatTestResponse()) c, recorder := newTestContext() err := svc.TestAccountConnection(c, account.ID, "glm-4.7", "", AccountTestModeDefault) require.NoError(t, err) require.Len(t, upstream.requests, 1) require.Equal(t, "http://fixed-chat.example/v1/chat/completions", upstream.requests[0].URL.String()) require.Equal(t, 1, strings.Count(recorder.Body.String(), `"type":"test_complete"`)) }