fix: resolve upstream CI failures (lint, test, gofmt)

- Fix errcheck: handle Write/Encode return values in brave_test.go
- Fix errcheck: defer resp.Body.Close() with _ assignment in tavily.go
- Fix gofmt: payment.go, channel.go, payment_config_providers.go
- Fix unused: remove dead decodeURLValue in easypay.go
- Restore shouldFallbackGeminiModel function (deleted during cherry-pick)
- Add missing balanceNotifyService param to NewGatewayService in test
- Fix platform default test expectation (empty stays empty)
- Fix wildcard pricing test (longest prefix wins, not config order)
- Fix subscription group test (SUBSCRIPTION_REPOSITORY_UNAVAILABLE)
This commit is contained in:
erio
2026-04-14 12:11:08 +08:00
parent 4aa0070e3d
commit 6a08efeef9
11 changed files with 35 additions and 34 deletions

View File

@@ -273,13 +273,13 @@ func TestPricingRequestToService_Defaults(t *testing.T) {
wantValue: string(service.BillingModeToken), wantValue: string(service.BillingModeToken),
}, },
{ {
name: "empty platform defaults to anthropic", name: "empty platform stays empty",
req: channelModelPricingRequest{ req: channelModelPricingRequest{
Models: []string{"m1"}, Models: []string{"m1"},
Platform: "", Platform: "",
}, },
wantField: "Platform", wantField: "Platform",
wantValue: "anthropic", wantValue: "",
}, },
} }

View File

@@ -168,6 +168,7 @@ func newTestGatewayHandler(t *testing.T, group *service.Group, accounts []*servi
nil, // tlsFPProfileService nil, // tlsFPProfileService
nil, // channelService nil, // channelService
nil, // resolver nil, // resolver
nil, // balanceNotifyService
) )
// RunModeSimple跳过计费检查避免引入 repo/cache 依赖。 // RunModeSimple跳过计费检查避免引入 repo/cache 依赖。

View File

@@ -682,6 +682,16 @@ func shouldFallbackGeminiModels(res *service.UpstreamHTTPResult) bool {
return false 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 请求中提取会话标识。 // extractGeminiCLISessionHash 从 Gemini CLI 请求中提取会话标识。
// 组合 x-gemini-api-privileged-user-id header 和请求体中的 tmp 目录哈希。 // 组合 x-gemini-api-privileged-user-id header 和请求体中的 tmp 目录哈希。
// //

View File

@@ -276,12 +276,3 @@ func easyPaySign(params map[string]string, pkey string) string {
func easyPayVerifySign(params map[string]string, pkey string, sign string) bool { func easyPayVerifySign(params map[string]string, pkey string, sign string) bool {
return hmac.Equal([]byte(easyPaySign(params, pkey)), []byte(sign)) 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
}

View File

@@ -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"}, {URL: "https://tour.go.dev", Title: "Tour", Description: "A Tour of Go", Age: "3 days"},
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp) _ = json.NewEncoder(w).Encode(resp)
})) }))
defer srv.Close() 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) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
receivedCount = r.URL.Query().Get("count") receivedCount = r.URL.Query().Get("count")
resp := braveResponse{} resp := braveResponse{}
json.NewEncoder(w).Encode(resp) _ = json.NewEncoder(w).Encode(resp)
})) }))
defer srv.Close() defer srv.Close()
@@ -70,7 +70,7 @@ func TestBraveProvider_Search_DefaultMaxResults(t *testing.T) {
func TestBraveProvider_Search_HTTPError(t *testing.T) { func TestBraveProvider_Search_HTTPError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(429) w.WriteHeader(429)
w.Write([]byte("rate limited")) _, _ = w.Write([]byte("rate limited"))
})) }))
defer srv.Close() defer srv.Close()
@@ -86,7 +86,7 @@ func TestBraveProvider_Search_HTTPError(t *testing.T) {
func TestBraveProvider_Search_InvalidJSON(t *testing.T) { func TestBraveProvider_Search_InvalidJSON(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("not json")) _, _ = w.Write([]byte("not json"))
})) }))
defer srv.Close() defer srv.Close()
@@ -103,7 +103,7 @@ func TestBraveProvider_Search_InvalidJSON(t *testing.T) {
func TestBraveProvider_Search_EmptyResults(t *testing.T) { func TestBraveProvider_Search_EmptyResults(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
resp := braveResponse{} resp := braveResponse{}
json.NewEncoder(w).Encode(resp) _ = json.NewEncoder(w).Encode(resp)
})) }))
defer srv.Close() defer srv.Close()

View File

@@ -60,7 +60,7 @@ func (t *TavilyProvider) Search(ctx context.Context, req SearchRequest) (*Search
if err != nil { if err != nil {
return nil, fmt.Errorf("tavily: request failed: %w", err) 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)) body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
if err != nil { if err != nil {

View File

@@ -78,7 +78,6 @@ func RegisterPaymentRoutes(
adminOrders.POST("/:id/refund", adminPaymentHandler.ProcessRefund) adminOrders.POST("/:id/refund", adminPaymentHandler.ProcessRefund)
} }
// Subscription Plans // Subscription Plans
plans := adminGroup.Group("/plans") plans := adminGroup.Group("/plans")
{ {

View File

@@ -147,14 +147,14 @@ func TestFindPricingForModel(t *testing.T) {
wantNil: true, wantNil: true,
}, },
{ {
name: "wildcard matches by config order (first match wins)", name: "wildcard matches by longest prefix (most specific wins)",
list: []ChannelModelPricing{ list: []ChannelModelPricing{
{ID: 10, Models: []string{"claude-*"}}, {ID: 10, Models: []string{"claude-*"}},
{ID: 11, Models: []string{"claude-opus-*"}}, {ID: 11, Models: []string{"claude-opus-*"}},
}, },
platform: "", platform: "",
model: "claude-opus-4", 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", name: "shorter wildcard used when longer does not match",

View File

@@ -412,10 +412,10 @@ func TestAdminService_AdminUpdateAPIKeyGroupID_SubscriptionGroup_Blocked(t *test
userRepo := &userRepoStubForGroupUpdate{} userRepo := &userRepoStubForGroupUpdate{}
svc := &adminServiceImpl{apiKeyRepo: apiKeyRepo, groupRepo: groupRepo, userRepo: userRepo} svc := &adminServiceImpl{apiKeyRepo: apiKeyRepo, groupRepo: groupRepo, userRepo: userRepo}
// 订阅类型分组应被阻止绑定 // userSubRepo is nil → SUBSCRIPTION_REPOSITORY_UNAVAILABLE
_, err := svc.AdminUpdateAPIKeyGroupID(context.Background(), 1, int64Ptr(10)) _, err := svc.AdminUpdateAPIKeyGroupID(context.Background(), 1, int64Ptr(10))
require.Error(t, err) 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) require.False(t, userRepo.addGroupCalled)
} }

View File

@@ -37,8 +37,8 @@ type Channel struct {
Name string Name string
Description string Description string
Status string Status string
BillingModelSource string // "requested", "upstream", or "channel_mapped" BillingModelSource string // "requested", "upstream", or "channel_mapped"
RestrictModels bool // 是否限制模型(仅允许定价列表中的模型) RestrictModels bool // 是否限制模型(仅允许定价列表中的模型)
Features string // 渠道特性描述JSON 数组),用于支付页面展示 Features string // 渠道特性描述JSON 数组),用于支付页面展示
FeaturesConfig map[string]any // 渠道功能配置(如 web search emulation FeaturesConfig map[string]any // 渠道功能配置(如 web search emulation
CreatedAt time.Time CreatedAt time.Time

View File

@@ -22,16 +22,16 @@ func (s *PaymentConfigService) ListProviderInstances(ctx context.Context) ([]*db
// ProviderInstanceResponse is the API response for a provider instance. // ProviderInstanceResponse is the API response for a provider instance.
type ProviderInstanceResponse struct { type ProviderInstanceResponse struct {
ID int64 `json:"id"` ID int64 `json:"id"`
ProviderKey string `json:"provider_key"` ProviderKey string `json:"provider_key"`
Name string `json:"name"` Name string `json:"name"`
Config map[string]string `json:"config"` Config map[string]string `json:"config"`
SupportedTypes []string `json:"supported_types"` SupportedTypes []string `json:"supported_types"`
Limits string `json:"limits"` Limits string `json:"limits"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
RefundEnabled bool `json:"refund_enabled"` RefundEnabled bool `json:"refund_enabled"`
SortOrder int `json:"sort_order"` SortOrder int `json:"sort_order"`
PaymentMode string `json:"payment_mode"` PaymentMode string `json:"payment_mode"`
} }
// ListProviderInstancesWithConfig returns provider instances with decrypted config. // ListProviderInstancesWithConfig returns provider instances with decrypted config.