mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-15 23:22:59 +08:00
The zhipu adapter always appended the official /api/paas/v4/chat/completions path, so probes broke whenever the endpoint was not the official root: - OpenAI-compatible relay (e.g. https://relay.example.com): requested /api/paas/v4/chat/completions, which relays do not serve -> 404 - Coding Plan base (https://open.bigmodel.cn/api/coding/paas/v4): produced .../coding/paas/v4/api/paas/v4/chat/completions -> 404 Resolve the path from the endpoint instead: append /chat/completions when it already carries /paas/v4, keep the official path for bigmodel.cn / z.ai roots, and fall back to /v1/chat/completions (same as kimi/deepseek/minimax) for everything else. Fixes #7067 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
4.8 KiB
Go
116 lines
4.8 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestValidateMonitorEndpoint_BasePath(t *testing.T) {
|
|
for _, endpoint := range []string{
|
|
"https://8.8.8.8",
|
|
"https://8.8.8.8/anthropic",
|
|
"https://8.8.8.8/anthropic/",
|
|
"https://8.8.8.8/anthropic/v1",
|
|
"https://8.8.8.8/tenant%2Fone/anthropic",
|
|
} {
|
|
t.Run(endpoint, func(t *testing.T) {
|
|
require.NoError(t, validateEndpoint(endpoint))
|
|
})
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
endpoint string
|
|
want error
|
|
}{
|
|
{"http://8.8.8.8/anthropic", ErrChannelMonitorEndpointScheme},
|
|
{"https://8.8.8.8/anthropic?key=secret", ErrChannelMonitorEndpointPath},
|
|
{"https://8.8.8.8/anthropic#fragment", ErrChannelMonitorEndpointPath},
|
|
{"https://127.0.0.1/anthropic", ErrChannelMonitorEndpointPrivate},
|
|
{"https://10.0.0.1/anthropic", ErrChannelMonitorEndpointPrivate},
|
|
{"https://169.254.169.254/anthropic", ErrChannelMonitorEndpointPrivate},
|
|
{"https://[::1]/anthropic", ErrChannelMonitorEndpointPrivate},
|
|
{"https://[fd00::1]/anthropic", ErrChannelMonitorEndpointPrivate},
|
|
{"https:///anthropic", ErrChannelMonitorInvalidEndpoint},
|
|
} {
|
|
t.Run(tc.endpoint, func(t *testing.T) {
|
|
require.ErrorIs(t, validateEndpoint(tc.endpoint), tc.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJoinMonitorURL_DoesNotMatchHostOrEncodedSlash(t *testing.T) {
|
|
require.Equal(t, "https://v1/v1/messages", joinURL("https://v1", "/v1/messages"))
|
|
require.Equal(t, "https://relay.example/tenant%2Fv1/v1/messages",
|
|
joinURL("https://relay.example/tenant%2Fv1", "/v1/messages"))
|
|
}
|
|
|
|
func TestNormalizeMonitorEndpoint_PreservesBasePath(t *testing.T) {
|
|
require.Equal(t, "https://api.deepseek.com/anthropic",
|
|
normalizeEndpoint(" https://api.deepseek.com/anthropic/ "))
|
|
require.Equal(t, "https://relay.example/tenant%2Fone/anthropic/v1",
|
|
normalizeEndpoint("https://relay.example/tenant%2Fone/anthropic/v1/"))
|
|
}
|
|
|
|
func TestCallProvider_BasePath(t *testing.T) {
|
|
swapMonitorHTTPClient(t)
|
|
requests := make(chan string, 1)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
requests <- r.RequestURI
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte("{}"))
|
|
}))
|
|
t.Cleanup(server.Close)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
provider string
|
|
apiMode string
|
|
basePath string
|
|
wantPath string
|
|
}{
|
|
{"anthropic origin", MonitorProviderAnthropic, "", "", "/v1/messages"},
|
|
{"anthropic prefix", MonitorProviderAnthropic, "", "/anthropic", "/anthropic/v1/messages"},
|
|
{"anthropic trailing slash", MonitorProviderAnthropic, "", "/anthropic/", "/anthropic/v1/messages"},
|
|
{"anthropic version", MonitorProviderAnthropic, "", "/anthropic/v1", "/anthropic/v1/messages"},
|
|
{"version lookalike", MonitorProviderAnthropic, "", "/anthropic/v10", "/anthropic/v10/v1/messages"},
|
|
{"encoded prefix", MonitorProviderAnthropic, "", "/tenant%2Fone/anthropic", "/tenant%2Fone/anthropic/v1/messages"},
|
|
{"openai chat version", MonitorProviderOpenAI, "", "/relay/v1/", "/relay/v1/chat/completions"},
|
|
{"openai responses version", MonitorProviderOpenAI, MonitorAPIModeResponses, "/relay/v1", "/relay/v1/responses"},
|
|
{"gemini version", MonitorProviderGemini, "", "/relay/v1beta", "/relay/v1beta/models/test-model:generateContent"},
|
|
{"zhipu version", MonitorProviderZhipu, "", "/api/paas/v4", "/api/paas/v4/chat/completions"},
|
|
{"zhipu coding plan", MonitorProviderZhipu, "", "/api/coding/paas/v4", "/api/coding/paas/v4/chat/completions"},
|
|
{"zhipu relay origin", MonitorProviderZhipu, "", "", "/v1/chat/completions"},
|
|
{"zhipu relay version", MonitorProviderZhipu, "", "/relay/v1", "/relay/v1/chat/completions"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, status, err := callProvider(context.Background(), tc.provider,
|
|
server.URL+tc.basePath, "test-key", "test-model", "hello", &CheckOptions{APIMode: tc.apiMode})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, status)
|
|
require.Equal(t, tc.wantPath, <-requests)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMonitorRequestPath_Zhipu(t *testing.T) {
|
|
adapter := providerAdapters[MonitorProviderZhipu]
|
|
for endpoint, want := range map[string]string{
|
|
"https://open.bigmodel.cn": providerZhipuPath,
|
|
"https://api.z.ai": providerZhipuPath,
|
|
"https://open.bigmodel.cn/api/coding/paas/v4": "/chat/completions",
|
|
"https://birdapi.up.railway.app": providerOpenAIPath,
|
|
"https://notbigmodel.cn": providerOpenAIPath,
|
|
"https://relay.example.com/v1": providerOpenAIPath,
|
|
} {
|
|
require.Equal(t, want, monitorRequestPath(MonitorProviderZhipu, endpoint, adapter, "glm-5.3-flash"), endpoint)
|
|
}
|
|
require.Equal(t, providerOpenAIPath,
|
|
monitorRequestPath(MonitorProviderDeepseek, "https://birdapi.up.railway.app", providerAdapters[MonitorProviderDeepseek], "m"))
|
|
}
|