Upstream highlights:
- v0.1.127 release (150 commits): channel-monitor 协议管理、OpenAI
Responses 路由配置、模型定价 LiteLLM 默认、payment 强制扫码、
钉钉 OAuth、用户用量按平台拆分、Ops 错误分类 SLA 调整、
Anthropic passthrough keepalive、Gemini chat completions 路由 ...
- 91da8159 feat(risk-control): 内容审计新增关键词拦截
- 3d22dd34 feat: gemini-3.5-flash 模型支持
Conflicts resolved:
- Dockerfile: keep pnpm pin to 9.15.9 (upstream pinned generic v9 floating).
- wire_gen.go: combine upstream NewSettingHandler(+userAttributeService)
with local NewOpsHandler(opsService, requestEventBus, opsLogBroadcaster).
Verified by re-running wire generate.
- scheduler_cache.go: keep both upstream openai_responses_{mode,supported}
keys and local model_rate_limits key in filterSchedulerExtra().
- gateway_service.go: keep local context-compression block; drop now-dead
setOpsUpstreamRequestBody call (upstream removed ops retry replay).
- docker-compose.yml: keep local windsurf-ls service profile and named
volumes; keep local healthcheck start_period values.
Test mock signatures bumped to match current constructors:
- gateway_models_test.go: add nil for RPMTokenBucketService.
- account_handler_available_models_test.go: add nil for windsurfChatService.
103 lines
3.4 KiB
Go
103 lines
3.4 KiB
Go
//go:build unit
|
||
|
||
package repository
|
||
|
||
import (
|
||
"testing"
|
||
|
||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
func TestBuildSchedulerMetadataAccount_KeepsOpenAIWSFlags(t *testing.T) {
|
||
account := service.Account{
|
||
ID: 42,
|
||
Platform: service.PlatformOpenAI,
|
||
Type: service.AccountTypeOAuth,
|
||
Extra: map[string]any{
|
||
"openai_oauth_responses_websockets_v2_enabled": true,
|
||
"openai_oauth_responses_websockets_v2_mode": service.OpenAIWSIngressModePassthrough,
|
||
"openai_ws_force_http": true,
|
||
"openai_responses_mode": "force_chat_completions",
|
||
"openai_responses_supported": false,
|
||
"mixed_scheduling": true,
|
||
"unused_large_field": "drop-me",
|
||
},
|
||
}
|
||
|
||
got := buildSchedulerMetadataAccount(account)
|
||
|
||
require.Equal(t, true, got.Extra["openai_oauth_responses_websockets_v2_enabled"])
|
||
require.Equal(t, service.OpenAIWSIngressModePassthrough, got.Extra["openai_oauth_responses_websockets_v2_mode"])
|
||
require.Equal(t, true, got.Extra["openai_ws_force_http"])
|
||
require.Equal(t, "force_chat_completions", got.Extra["openai_responses_mode"])
|
||
require.Equal(t, false, got.Extra["openai_responses_supported"])
|
||
require.Equal(t, true, got.Extra["mixed_scheduling"])
|
||
require.Nil(t, got.Extra["unused_large_field"])
|
||
}
|
||
|
||
// 回归测试:model_rate_limits 必须透传到调度快照,否则选号阶段无法感知模型级限流,
|
||
// 会出现"限流账号被反复选中 → failover 切号 → 重复切号"的死循环(对应 windsurf 日志里的现象)。
|
||
func TestBuildSchedulerMetadataAccount_KeepsModelRateLimits(t *testing.T) {
|
||
modelLimits := map[string]any{
|
||
"claude-opus-4-7-medium": map[string]any{
|
||
"rate_limited_at": "2026-04-24T02:28:51Z",
|
||
"rate_limit_reset_at": "2026-04-24T02:58:51Z",
|
||
},
|
||
}
|
||
account := service.Account{
|
||
ID: 7,
|
||
Platform: service.PlatformWindsurf,
|
||
Type: service.AccountTypeSetupToken,
|
||
Extra: map[string]any{
|
||
"model_rate_limits": modelLimits,
|
||
"unused_large_field": "drop-me",
|
||
},
|
||
}
|
||
|
||
got := buildSchedulerMetadataAccount(account)
|
||
|
||
require.Equal(t, modelLimits, got.Extra["model_rate_limits"], "model_rate_limits must be carried into scheduler snapshot for rate-limit-aware selection")
|
||
require.Nil(t, got.Extra["unused_large_field"])
|
||
}
|
||
|
||
func TestBuildSchedulerMetadataAccount_KeepsSlimGroupMembership(t *testing.T) {
|
||
account := service.Account{
|
||
ID: 42,
|
||
Platform: service.PlatformAnthropic,
|
||
GroupIDs: []int64{7, 9, 7, 0},
|
||
AccountGroups: []service.AccountGroup{
|
||
{
|
||
AccountID: 42,
|
||
GroupID: 7,
|
||
Priority: 2,
|
||
Account: &service.Account{ID: 42, Name: "drop-from-metadata"},
|
||
Group: &service.Group{ID: 7, Name: "drop-from-metadata"},
|
||
},
|
||
{
|
||
AccountID: 42,
|
||
GroupID: 11,
|
||
Priority: 3,
|
||
Group: &service.Group{ID: 11, Name: "drop-from-metadata"},
|
||
},
|
||
{
|
||
AccountID: 42,
|
||
GroupID: 0,
|
||
Priority: 4,
|
||
},
|
||
},
|
||
}
|
||
|
||
got := buildSchedulerMetadataAccount(account)
|
||
|
||
require.Equal(t, []int64{7, 9, 11}, got.GroupIDs)
|
||
require.Len(t, got.AccountGroups, 2)
|
||
require.Equal(t, int64(42), got.AccountGroups[0].AccountID)
|
||
require.Equal(t, int64(7), got.AccountGroups[0].GroupID)
|
||
require.Equal(t, 2, got.AccountGroups[0].Priority)
|
||
require.Nil(t, got.AccountGroups[0].Account)
|
||
require.Nil(t, got.AccountGroups[0].Group)
|
||
require.Equal(t, int64(11), got.AccountGroups[1].GroupID)
|
||
require.Nil(t, got.Groups)
|
||
}
|