diff --git a/backend/internal/handler/admin/channel_handler_test.go b/backend/internal/handler/admin/channel_handler_test.go index 2f4b4440..f218cce4 100644 --- a/backend/internal/handler/admin/channel_handler_test.go +++ b/backend/internal/handler/admin/channel_handler_test.go @@ -273,13 +273,13 @@ func TestPricingRequestToService_Defaults(t *testing.T) { wantValue: string(service.BillingModeToken), }, { - name: "empty platform defaults to anthropic", + name: "empty platform stays empty", req: channelModelPricingRequest{ Models: []string{"m1"}, Platform: "", }, wantField: "Platform", - wantValue: "anthropic", + wantValue: "", }, } diff --git a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go index acea3780..1fdc46ba 100644 --- a/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go +++ b/backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go @@ -168,6 +168,7 @@ func newTestGatewayHandler(t *testing.T, group *service.Group, accounts []*servi nil, // tlsFPProfileService nil, // channelService nil, // resolver + nil, // balanceNotifyService ) // RunModeSimple:跳过计费检查,避免引入 repo/cache 依赖。 diff --git a/backend/internal/handler/gemini_v1beta_handler.go b/backend/internal/handler/gemini_v1beta_handler.go index 45b5842f..6b8cc482 100644 --- a/backend/internal/handler/gemini_v1beta_handler.go +++ b/backend/internal/handler/gemini_v1beta_handler.go @@ -682,6 +682,16 @@ func shouldFallbackGeminiModels(res *service.UpstreamHTTPResult) bool { return false } +func shouldFallbackGeminiModel(modelName string, res *service.UpstreamHTTPResult) bool { + if shouldFallbackGeminiModels(res) { + return true + } + if res == nil || res.StatusCode != http.StatusNotFound { + return false + } + return gemini.HasFallbackModel(modelName) +} + // extractGeminiCLISessionHash 从 Gemini CLI 请求中提取会话标识。 // 组合 x-gemini-api-privileged-user-id header 和请求体中的 tmp 目录哈希。 // diff --git a/backend/internal/payment/provider/easypay.go b/backend/internal/payment/provider/easypay.go index c54aba6a..b48a38fe 100644 --- a/backend/internal/payment/provider/easypay.go +++ b/backend/internal/payment/provider/easypay.go @@ -276,12 +276,3 @@ func easyPaySign(params map[string]string, pkey string) string { func easyPayVerifySign(params map[string]string, pkey string, sign string) bool { return hmac.Equal([]byte(easyPaySign(params, pkey)), []byte(sign)) } - -// decodeURLValue URL-decodes a string once. -func decodeURLValue(s string) string { - decoded, err := url.QueryUnescape(s) - if err != nil { - return s - } - return decoded -} diff --git a/backend/internal/pkg/websearch/brave_test.go b/backend/internal/pkg/websearch/brave_test.go index 3fe35020..4dc5b219 100644 --- a/backend/internal/pkg/websearch/brave_test.go +++ b/backend/internal/pkg/websearch/brave_test.go @@ -29,7 +29,7 @@ func TestBraveProvider_Search_Success(t *testing.T) { {URL: "https://tour.go.dev", Title: "Tour", Description: "A Tour of Go", Age: "3 days"}, } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) })) defer srv.Close() @@ -53,7 +53,7 @@ func TestBraveProvider_Search_DefaultMaxResults(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedCount = r.URL.Query().Get("count") resp := braveResponse{} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) })) defer srv.Close() @@ -70,7 +70,7 @@ func TestBraveProvider_Search_DefaultMaxResults(t *testing.T) { func TestBraveProvider_Search_HTTPError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(429) - w.Write([]byte("rate limited")) + _, _ = w.Write([]byte("rate limited")) })) defer srv.Close() @@ -86,7 +86,7 @@ func TestBraveProvider_Search_HTTPError(t *testing.T) { func TestBraveProvider_Search_InvalidJSON(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Write([]byte("not json")) + _, _ = w.Write([]byte("not json")) })) defer srv.Close() @@ -103,7 +103,7 @@ func TestBraveProvider_Search_InvalidJSON(t *testing.T) { func TestBraveProvider_Search_EmptyResults(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { resp := braveResponse{} - json.NewEncoder(w).Encode(resp) + _ = json.NewEncoder(w).Encode(resp) })) defer srv.Close() diff --git a/backend/internal/pkg/websearch/tavily.go b/backend/internal/pkg/websearch/tavily.go index 6ac09edf..ac4928a6 100644 --- a/backend/internal/pkg/websearch/tavily.go +++ b/backend/internal/pkg/websearch/tavily.go @@ -60,7 +60,7 @@ func (t *TavilyProvider) Search(ctx context.Context, req SearchRequest) (*Search if err != nil { return nil, fmt.Errorf("tavily: request failed: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize)) if err != nil { diff --git a/backend/internal/server/routes/payment.go b/backend/internal/server/routes/payment.go index 641c6cd5..72012a4e 100644 --- a/backend/internal/server/routes/payment.go +++ b/backend/internal/server/routes/payment.go @@ -78,7 +78,6 @@ func RegisterPaymentRoutes( adminOrders.POST("/:id/refund", adminPaymentHandler.ProcessRefund) } - // Subscription Plans plans := adminGroup.Group("/plans") { diff --git a/backend/internal/service/account_stats_pricing_test.go b/backend/internal/service/account_stats_pricing_test.go index 36e5eb74..2f625393 100644 --- a/backend/internal/service/account_stats_pricing_test.go +++ b/backend/internal/service/account_stats_pricing_test.go @@ -147,14 +147,14 @@ func TestFindPricingForModel(t *testing.T) { wantNil: true, }, { - name: "wildcard matches by config order (first match wins)", + name: "wildcard matches by longest prefix (most specific wins)", list: []ChannelModelPricing{ {ID: 10, Models: []string{"claude-*"}}, {ID: 11, Models: []string{"claude-opus-*"}}, }, platform: "", model: "claude-opus-4", - wantID: 10, // config order: "claude-*" is first and matches, so it wins + wantID: 11, // "claude-opus-*" is longer prefix, wins over "claude-*" }, { name: "shorter wildcard used when longer does not match", diff --git a/backend/internal/service/admin_service_apikey_test.go b/backend/internal/service/admin_service_apikey_test.go index 7f0a24da..1e235278 100644 --- a/backend/internal/service/admin_service_apikey_test.go +++ b/backend/internal/service/admin_service_apikey_test.go @@ -412,10 +412,10 @@ func TestAdminService_AdminUpdateAPIKeyGroupID_SubscriptionGroup_Blocked(t *test userRepo := &userRepoStubForGroupUpdate{} svc := &adminServiceImpl{apiKeyRepo: apiKeyRepo, groupRepo: groupRepo, userRepo: userRepo} - // 订阅类型分组应被阻止绑定 + // userSubRepo is nil → SUBSCRIPTION_REPOSITORY_UNAVAILABLE _, err := svc.AdminUpdateAPIKeyGroupID(context.Background(), 1, int64Ptr(10)) require.Error(t, err) - require.Equal(t, "SUBSCRIPTION_GROUP_NOT_ALLOWED", infraerrors.Reason(err)) + require.Equal(t, "SUBSCRIPTION_REPOSITORY_UNAVAILABLE", infraerrors.Reason(err)) require.False(t, userRepo.addGroupCalled) } diff --git a/backend/internal/service/channel.go b/backend/internal/service/channel.go index b3fb2eac..93beb972 100644 --- a/backend/internal/service/channel.go +++ b/backend/internal/service/channel.go @@ -37,8 +37,8 @@ type Channel struct { Name string Description string Status string - BillingModelSource string // "requested", "upstream", or "channel_mapped" - RestrictModels bool // 是否限制模型(仅允许定价列表中的模型) + BillingModelSource string // "requested", "upstream", or "channel_mapped" + RestrictModels bool // 是否限制模型(仅允许定价列表中的模型) Features string // 渠道特性描述(JSON 数组),用于支付页面展示 FeaturesConfig map[string]any // 渠道功能配置(如 web search emulation) CreatedAt time.Time diff --git a/backend/internal/service/payment_config_providers.go b/backend/internal/service/payment_config_providers.go index 072ed002..47008df0 100644 --- a/backend/internal/service/payment_config_providers.go +++ b/backend/internal/service/payment_config_providers.go @@ -22,16 +22,16 @@ func (s *PaymentConfigService) ListProviderInstances(ctx context.Context) ([]*db // ProviderInstanceResponse is the API response for a provider instance. type ProviderInstanceResponse struct { - ID int64 `json:"id"` - ProviderKey string `json:"provider_key"` - Name string `json:"name"` - Config map[string]string `json:"config"` - SupportedTypes []string `json:"supported_types"` - Limits string `json:"limits"` - Enabled bool `json:"enabled"` - RefundEnabled bool `json:"refund_enabled"` - SortOrder int `json:"sort_order"` - PaymentMode string `json:"payment_mode"` + ID int64 `json:"id"` + ProviderKey string `json:"provider_key"` + Name string `json:"name"` + Config map[string]string `json:"config"` + SupportedTypes []string `json:"supported_types"` + Limits string `json:"limits"` + Enabled bool `json:"enabled"` + RefundEnabled bool `json:"refund_enabled"` + SortOrder int `json:"sort_order"` + PaymentMode string `json:"payment_mode"` } // ListProviderInstancesWithConfig returns provider instances with decrypted config.