diff --git a/backend/internal/handler/sora_client_handler_test.go b/backend/internal/handler/sora_client_handler_test.go index 89dcd394..2d10875a 100644 --- a/backend/internal/handler/sora_client_handler_test.go +++ b/backend/internal/handler/sora_client_handler_test.go @@ -2258,7 +2258,8 @@ func TestProcessGeneration_SelectAccountError(t *testing.T) { } func TestProcessGeneration_SoraGatewayServiceNil(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") + // TODO: Re-enable after Sora process generation is stable + // t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") repo := newStubSoraGenRepo() repo.gens[1] = &service.SoraGeneration{ID: 1, UserID: 1, Status: "pending"} genService := service.NewSoraGenerationService(repo, nil, nil) @@ -2278,7 +2279,8 @@ func TestProcessGeneration_SoraGatewayServiceNil(t *testing.T) { } func TestProcessGeneration_ForwardError(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") + // TODO: Re-enable after Sora process generation is stable + // t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") repo := newStubSoraGenRepo() repo.gens[1] = &service.SoraGeneration{ID: 1, UserID: 1, Status: "pending"} genService := service.NewSoraGenerationService(repo, nil, nil) @@ -2337,7 +2339,8 @@ func TestProcessGeneration_ForwardErrorCancelled(t *testing.T) { } func TestProcessGeneration_ForwardSuccessNoMediaURL(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") + // TODO: Re-enable after Sora process generation is stable + // t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") repo := newStubSoraGenRepo() repo.gens[1] = &service.SoraGeneration{ID: 1, UserID: 1, Status: "pending"} genService := service.NewSoraGenerationService(repo, nil, nil) @@ -2399,7 +2402,8 @@ func TestProcessGeneration_ForwardSuccessCancelledBeforeStore(t *testing.T) { } func TestProcessGeneration_FullSuccessUpstream(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") + // TODO: Re-enable after Sora process generation is stable + // t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") repo := newStubSoraGenRepo() repo.gens[1] = &service.SoraGeneration{ID: 1, UserID: 1, Status: "pending"} genService := service.NewSoraGenerationService(repo, nil, nil) @@ -2430,7 +2434,8 @@ func TestProcessGeneration_FullSuccessUpstream(t *testing.T) { } func TestProcessGeneration_FullSuccessWithS3(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") + // TODO: Re-enable after Sora process generation is stable + // t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") sourceServer := newFakeSourceServer() defer sourceServer.Close() fakeS3 := newFakeS3Server("ok") @@ -2478,7 +2483,8 @@ func TestProcessGeneration_FullSuccessWithS3(t *testing.T) { } func TestProcessGeneration_MarkCompletedFails(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") + // TODO: Re-enable after Sora process generation is stable + // t.Skip("TODO: 临时屏蔽 Sora processGeneration 集成测试,待流程稳定后恢复") repo := newStubSoraGenRepo() repo.gens[1] = &service.SoraGeneration{ID: 1, UserID: 1, Status: "pending"} // 第 1 次 Update(MarkGenerating)成功,第 2 次(MarkCompleted)失败 @@ -2646,7 +2652,8 @@ func TestDeleteGeneration_DeleteError(t *testing.T) { // ==================== fetchUpstreamModels 测试 ==================== func TestFetchUpstreamModels_NilGateway(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") h := &SoraClientHandler{} _, err := h.fetchUpstreamModels(context.Background()) require.Error(t, err) @@ -2654,7 +2661,8 @@ func TestFetchUpstreamModels_NilGateway(t *testing.T) { } func TestFetchUpstreamModels_NoAccounts(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") accountRepo := &stubAccountRepoForHandler{accounts: nil} gatewayService := newMinimalGatewayService(accountRepo) h := &SoraClientHandler{gatewayService: gatewayService} @@ -2664,7 +2672,8 @@ func TestFetchUpstreamModels_NoAccounts(t *testing.T) { } func TestFetchUpstreamModels_NonAPIKeyAccount(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") accountRepo := &stubAccountRepoForHandler{ accounts: []service.Account{ {ID: 1, Type: "oauth", Platform: service.PlatformSora, Status: service.StatusActive, Schedulable: true}, @@ -2678,7 +2687,8 @@ func TestFetchUpstreamModels_NonAPIKeyAccount(t *testing.T) { } func TestFetchUpstreamModels_MissingAPIKey(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") accountRepo := &stubAccountRepoForHandler{ accounts: []service.Account{ {ID: 1, Type: service.AccountTypeAPIKey, Platform: service.PlatformSora, Status: service.StatusActive, Schedulable: true, @@ -2693,7 +2703,8 @@ func TestFetchUpstreamModels_MissingAPIKey(t *testing.T) { } func TestFetchUpstreamModels_MissingBaseURL_FallsBackToDefault(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") // GetBaseURL() 在缺少 base_url 时返回默认值 "https://api.anthropic.com" // 因此不会触发 "账号缺少 base_url" 错误,而是会尝试请求默认 URL 并失败 accountRepo := &stubAccountRepoForHandler{ @@ -2709,7 +2720,8 @@ func TestFetchUpstreamModels_MissingBaseURL_FallsBackToDefault(t *testing.T) { } func TestFetchUpstreamModels_UpstreamReturns500(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) })) @@ -2729,7 +2741,8 @@ func TestFetchUpstreamModels_UpstreamReturns500(t *testing.T) { } func TestFetchUpstreamModels_UpstreamReturnsInvalidJSON(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("not json")) @@ -2750,7 +2763,8 @@ func TestFetchUpstreamModels_UpstreamReturnsInvalidJSON(t *testing.T) { } func TestFetchUpstreamModels_UpstreamReturnsEmptyList(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"data":[]}`)) @@ -2771,7 +2785,8 @@ func TestFetchUpstreamModels_UpstreamReturnsEmptyList(t *testing.T) { } func TestFetchUpstreamModels_Success(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 验证请求头 require.Equal(t, "Bearer sk-test", r.Header.Get("Authorization")) @@ -2795,7 +2810,8 @@ func TestFetchUpstreamModels_Success(t *testing.T) { } func TestFetchUpstreamModels_UnrecognizedModels(t *testing.T) { - t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽 Sora 上游模型同步相关测试,待账号选择逻辑稳定后恢复") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"data":[{"id":"unknown-model-1"},{"id":"unknown-model-2"}]}`)) @@ -2830,7 +2846,8 @@ func TestGetModelFamilies_CachesLocalConfig(t *testing.T) { } func TestGetModelFamilies_CachesUpstreamResult(t *testing.T) { - t.Skip("TODO: 临时屏蔽依赖 Sora 上游模型同步的缓存测试,待账号选择逻辑稳定后恢复") + // TODO: Re-enable after Sora upstream model sync is stable + // t.Skip("TODO: 临时屏蔽依赖 Sora 上游模型同步的缓存测试,待账号选择逻辑稳定后恢复") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"data":[{"id":"sora2-landscape-10s"},{"id":"gpt-image"}]}`)) diff --git a/backend/internal/pkg/antigravity/request_transformer.go b/backend/internal/pkg/antigravity/request_transformer.go index 1b45e507..19967ec6 100644 --- a/backend/internal/pkg/antigravity/request_transformer.go +++ b/backend/internal/pkg/antigravity/request_transformer.go @@ -15,6 +15,8 @@ import ( "github.com/google/uuid" ) +// sessionRand 用于生成回退时的随机 session ID +// 注意:主要使用确定性方法(基于消息内容哈希),仅在无法提取消息时使用随机 var ( sessionRand = rand.New(rand.NewSource(time.Now().UnixNano())) sessionRandMutex sync.Mutex diff --git a/backend/internal/pkg/errors/types.go b/backend/internal/pkg/errors/types.go index 21dfbeb8..6d33a878 100644 --- a/backend/internal/pkg/errors/types.go +++ b/backend/internal/pkg/errors/types.go @@ -113,3 +113,14 @@ func ClientClosed(reason, message string) *ApplicationError { func IsClientClosed(err error) bool { return Code(err) == 499 } + +// NotImplemented new NotImplemented error that is mapped to an HTTP 501 response. +func NotImplemented(reason, message string) *ApplicationError { + return New(http.StatusNotImplemented, reason, message) +} + +// IsNotImplemented determines if err is an error which indicates a NotImplemented error. +// It supports wrapped errors. +func IsNotImplemented(err error) bool { + return Code(err) == http.StatusNotImplemented +} diff --git a/backend/internal/pkg/models/interface.go b/backend/internal/pkg/models/interface.go index 487dc0a1..d41ae3f2 100644 --- a/backend/internal/pkg/models/interface.go +++ b/backend/internal/pkg/models/interface.go @@ -219,7 +219,7 @@ type ModelError struct { func (e *ModelError) Error() string { if len(e.Args) > 0 { - return e.Message + ": " + fmt.Sprint(e.Args...) + return fmt.Sprintf(e.Message, e.Args...) } return e.Message } diff --git a/backend/internal/service/account_service.go b/backend/internal/service/account_service.go index 2e91db6b..b8a2bccd 100644 --- a/backend/internal/service/account_service.go +++ b/backend/internal/service/account_service.go @@ -124,8 +124,9 @@ type UpdateAccountRequest struct { // AccountService 账号管理服务 type AccountService struct { - accountRepo AccountRepository - groupRepo GroupRepository + accountRepo AccountRepository + groupRepo GroupRepository + accountTestSvc *AccountTestService // 用于凭证验证(凭证预警依赖此功能) } type groupExistenceBatchChecker interface { @@ -133,10 +134,12 @@ type groupExistenceBatchChecker interface { } // NewAccountService 创建账号服务实例 -func NewAccountService(accountRepo AccountRepository, groupRepo GroupRepository) *AccountService { +// accountTestSvc 可选,用于 TestCredentials 功能(凭证预警依赖此功能) +func NewAccountService(accountRepo AccountRepository, groupRepo GroupRepository, accountTestSvc *AccountTestService) *AccountService { return &AccountService{ - accountRepo: accountRepo, - groupRepo: groupRepo, + accountRepo: accountRepo, + groupRepo: groupRepo, + accountTestSvc: accountTestSvc, } } @@ -375,25 +378,31 @@ func (s *AccountService) GetCredential(ctx context.Context, id int64, key string return account.GetCredential(key), nil } -// TestCredentials 测试账号凭证是否有效(需要实现具体平台的测试逻辑) +// TestCredentials tests account credentials validity. +// Returns ErrNotImplemented as the credential testing feature is not yet implemented. +// TestCredentials 验证账号凭证有效性 +// 凭证预警功能依赖此方法 func (s *AccountService) TestCredentials(ctx context.Context, id int64) error { + if s.accountTestSvc == nil { + return infraerrors.NotImplemented("TEST_CREDENTIALS_NOT_CONFIGURED", "account test service not configured") + } + account, err := s.accountRepo.GetByID(ctx, id) if err != nil { return fmt.Errorf("get account: %w", err) } - // 根据平台执行不同的测试逻辑 - switch account.Platform { - case PlatformAnthropic: - // TODO: 测试Anthropic API凭证 - return nil - case PlatformOpenAI: - // TODO: 测试OpenAI API凭证 - return nil - case PlatformGemini: - // TODO: 测试Gemini API凭证 - return nil - default: - return fmt.Errorf("unsupported platform: %s", account.Platform) + _ = account // Account retrieved for logging purposes + + // 调用 AccountTestService.RunTestBackground 执行凭证验证 + result, err := s.accountTestSvc.RunTestBackground(ctx, id, "") + if err != nil { + return fmt.Errorf("credential test failed: %w", err) } + + if result.Status != "success" { + return fmt.Errorf("credential invalid: %s", result.ErrorMessage) + } + + return nil } diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index dfc790cf..eb543702 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -41,7 +41,7 @@ const ( claudeAPIURL = "https://api.anthropic.com/v1/messages?beta=true" claudeAPICountTokensURL = "https://api.anthropic.com/v1/messages/count_tokens?beta=true" stickySessionTTL = time.Hour // 粘性会话TTL - defaultMaxLineSize = 500 * 1024 * 1024 + defaultMaxLineSize = 10 * 1024 * 1024 // Canonical Claude Code banner. Keep it EXACT (no trailing whitespace/newlines) // to match real Claude CLI traffic as closely as possible. When we need a visual // separator between system blocks, we add "\n\n" at concatenation time. diff --git a/backend/internal/service/promo_service.go b/backend/internal/service/promo_service.go index 5ff63bdc..b75b43f9 100644 --- a/backend/internal/service/promo_service.go +++ b/backend/internal/service/promo_service.go @@ -151,6 +151,8 @@ func (s *PromoService) ApplyPromoCode(ctx context.Context, userID int64, code st s.invalidatePromoCaches(ctx, userID, promoCode.BonusAmount) // 失效余额缓存 + // Note: 使用 context.Background() 使缓存失效操作独立于请求生命周期 + // 即使请求取消也确保缓存被清理,5秒超时防止goroutine泄漏 if s.billingCacheService != nil { go func() { cacheCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) diff --git a/backend/internal/service/redeem_service.go b/backend/internal/service/redeem_service.go index 5b3310d6..84de4ee8 100644 --- a/backend/internal/service/redeem_service.go +++ b/backend/internal/service/redeem_service.go @@ -288,13 +288,6 @@ func (s *RedeemService) Redeem(ctx context.Context, userID int64, code string) ( return nil, infraerrors.BadRequest("REDEEM_CODE_INVALID", "invalid subscription redeem code: missing group_id") } - // 获取用户信息 - user, err := s.userRepo.GetByID(ctx, userID) - if err != nil { - return nil, fmt.Errorf("get user: %w", err) - } - _ = user // 使用变量避免未使用错误 - // 使用数据库事务保证兑换码标记与权益发放的原子性 tx, err := s.entClient.Tx(ctx) if err != nil { @@ -375,6 +368,7 @@ func (s *RedeemService) invalidateRedeemCaches(ctx context.Context, userID int64 if s.billingCacheService == nil { return } + // Note: 使用 context.Background() 使缓存失效独立于请求生命周期 go func() { cacheCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -396,6 +390,7 @@ func (s *RedeemService) invalidateRedeemCaches(ctx context.Context, userID int64 } if redeemCode.GroupID != nil { groupID := *redeemCode.GroupID + // Note: 使用 context.Background() 使缓存失效独立于请求生命周期 go func() { cacheCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() diff --git a/backend/internal/service/sticky_session_test.go b/backend/internal/service/sticky_session_test.go index e7ef8982..f33591ab 100644 --- a/backend/internal/service/sticky_session_test.go +++ b/backend/internal/service/sticky_session_test.go @@ -105,7 +105,7 @@ func TestShouldClearStickySession(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.want, shouldClearStickySession(tt.account, tt.requestedModel)) + require.Equal(t, tt.want, shouldClearStickySession(context.Background(), tt.account, tt.requestedModel)) }) } }