Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUpstreamResponseModelObserverTerminalWinsAndRecordsConflict(t *testing.T) {
|
||||
observer := &upstreamResponseModelObserver{}
|
||||
|
||||
observer.ObserveOpenAI([]byte(`{"type":"response.created","response":{"model":"gpt-5.5"}}`), "response.created")
|
||||
observer.ObserveOpenAI([]byte(`{"type":"response.completed","response":{"model":"gpt-5.4"}}`), "response.completed")
|
||||
|
||||
require.Equal(t, "gpt-5.4", observer.Model())
|
||||
require.True(t, observer.Conflict())
|
||||
}
|
||||
|
||||
func TestUpstreamResponseModelObserverSupportsAnthropicAndGeminiShapes(t *testing.T) {
|
||||
t.Run("anthropic", func(t *testing.T) {
|
||||
observer := &upstreamResponseModelObserver{}
|
||||
observer.ObserveAnthropic([]byte(`{"type":"message_start","message":{"model":"claude-sonnet-4-20250514"}}`))
|
||||
require.Equal(t, "claude-sonnet-4-20250514", observer.Model())
|
||||
})
|
||||
|
||||
t.Run("gemini outer and nested", func(t *testing.T) {
|
||||
observer := &upstreamResponseModelObserver{}
|
||||
observer.ObserveGemini([]byte(`{"response":{"modelVersion":"gemini-2.5-pro"}}`))
|
||||
observer.ObserveGemini([]byte(`{"modelVersion":"gemini-2.5-pro-latest"}`))
|
||||
require.Equal(t, "gemini-2.5-pro-latest", observer.Model())
|
||||
require.True(t, observer.Conflict())
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpstreamResponseModelObservationAttemptReset(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
c, _ := gin.CreateTestContext(nil)
|
||||
|
||||
first := beginUpstreamResponseModelObservation(c)
|
||||
first.Observe("failed-attempt-model", false)
|
||||
second := beginUpstreamResponseModelObservation(c)
|
||||
second.Observe("successful-attempt-model", false)
|
||||
|
||||
require.Equal(t, "successful-attempt-model", observedUpstreamResponseModel(c))
|
||||
require.False(t, observedUpstreamResponseModelConflict(c))
|
||||
}
|
||||
|
||||
func TestUpstreamModelMismatchThreeStateAndCaseInsensitiveComparison(t *testing.T) {
|
||||
require.Nil(t, upstreamModelMismatch("gpt-5.5", ""))
|
||||
|
||||
matched := upstreamModelMismatch("gpt-5.5", "GPT-5.5")
|
||||
require.NotNil(t, matched)
|
||||
require.False(t, *matched)
|
||||
|
||||
mismatched := upstreamModelMismatch("gpt-5.5", "gpt-5.4")
|
||||
require.NotNil(t, mismatched)
|
||||
require.True(t, *mismatched)
|
||||
}
|
||||
|
||||
func TestUpstreamModelMismatchTreatsGrokBuildRuntimeIDsAsAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sentModel string
|
||||
responseModel string
|
||||
}{
|
||||
{
|
||||
name: "issue 5634 grok 4.6",
|
||||
sentModel: "grok-4.6",
|
||||
responseModel: "grok-4.6-build",
|
||||
},
|
||||
{
|
||||
name: "grok 4.6 latest",
|
||||
sentModel: "grok-4.6-latest",
|
||||
responseModel: "grok-4.6-build",
|
||||
},
|
||||
{
|
||||
name: "issue 5647 grok 4.5 latest",
|
||||
sentModel: "grok-4.5-latest",
|
||||
responseModel: "grok-4.5-build",
|
||||
},
|
||||
{
|
||||
name: "grok 4.5 canonical",
|
||||
sentModel: "grok-4.5",
|
||||
responseModel: "GROK-4.5-BUILD",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mismatch := upstreamModelMismatch(tt.sentModel, tt.responseModel)
|
||||
|
||||
require.NotNil(t, mismatch)
|
||||
require.False(t, *mismatch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpstreamModelMismatchDoesNotCollapseDifferentModels(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sentModel string
|
||||
responseModel string
|
||||
}{
|
||||
{
|
||||
name: "different grok versions",
|
||||
sentModel: "grok-4.5",
|
||||
responseModel: "grok-4.6-build",
|
||||
},
|
||||
{
|
||||
name: "unrelated build suffix",
|
||||
sentModel: "gpt-5.5",
|
||||
responseModel: "gpt-5.5-build",
|
||||
},
|
||||
{
|
||||
name: "different grok runtime",
|
||||
sentModel: "grok-build-0.1",
|
||||
responseModel: "grok-4.5-build",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mismatch := upstreamModelMismatch(tt.sentModel, tt.responseModel)
|
||||
|
||||
require.NotNil(t, mismatch)
|
||||
require.True(t, *mismatch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestObserveOpenAISSEBodyIgnoresMalformedPayload(t *testing.T) {
|
||||
observer := &upstreamResponseModelObserver{}
|
||||
observeOpenAISSEBody(observer, "data: not-json\n\ndata: {\"type\":\"response.completed\",\"response\":{\"model\":\"gpt-5.4\"}}\n\n")
|
||||
|
||||
require.Equal(t, "gpt-5.4", observer.Model())
|
||||
require.False(t, observer.Conflict())
|
||||
}
|
||||
|
||||
func TestObserveAntigravityGeminiSSELineReadsWrapperModelWithoutUnwrap(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "top-level sibling",
|
||||
payload: `{"modelVersion":"gemini-3-pro","response":{"candidates":[]}}`,
|
||||
want: "gemini-3-pro",
|
||||
},
|
||||
{
|
||||
name: "single wrapper",
|
||||
payload: `{"response":{"modelVersion":"gemini-3-pro","candidates":[]}}`,
|
||||
want: "gemini-3-pro",
|
||||
},
|
||||
{
|
||||
name: "nested response after one wrapper",
|
||||
payload: `{"response":{"response":{"modelVersion":"gemini-3-pro","candidates":[]}}}`,
|
||||
want: "gemini-3-pro",
|
||||
},
|
||||
{
|
||||
name: "outer declaration takes precedence",
|
||||
payload: `{"modelVersion":"gemini-outer","response":{"modelVersion":"gemini-inner","candidates":[]}}`,
|
||||
want: "gemini-outer",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
c, _ := gin.CreateTestContext(nil)
|
||||
beginUpstreamResponseModelObservation(c)
|
||||
|
||||
svc := &AntigravityGatewayService{}
|
||||
svc.observeAntigravityGeminiSSELine(c, "data: "+tt.payload)
|
||||
|
||||
require.Equal(t, tt.want, observedUpstreamResponseModel(c))
|
||||
require.False(t, observedUpstreamResponseModelConflict(c))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpstreamResponseModelObserverRejectsMalformedJSONWithModelField(t *testing.T) {
|
||||
observer := &upstreamResponseModelObserver{}
|
||||
observer.ObserveOpenAI([]byte(`{"response":{"model":"gpt-5.4"}`), "response.completed")
|
||||
|
||||
require.Empty(t, observer.Model())
|
||||
}
|
||||
|
||||
func TestUpstreamResponseModelObserverBoundsUntrustedModelName(t *testing.T) {
|
||||
observer := &upstreamResponseModelObserver{}
|
||||
observer.Observe(" "+strings.Repeat("模", upstreamResponseModelMaxLength+1)+" ", false)
|
||||
|
||||
require.Len(t, []rune(observer.Model()), upstreamResponseModelMaxLength)
|
||||
}
|
||||
Reference in New Issue
Block a user