diff --git a/backend/internal/service/admin_group.go b/backend/internal/service/admin_group.go index fd28f681cb..d719e4d501 100644 --- a/backend/internal/service/admin_group.go +++ b/backend/internal/service/admin_group.go @@ -469,7 +469,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn // 先归一化(非订阅分组清空高峰配置、清洗停用状态下的脏字段)再校验,与 UpdateGroup 同一收口。 peakRateEnabled, peakStart, peakEnd, peakRateMultiplier := NormalizePeakRateConfig(subscriptionType, input.PeakRateEnabled, input.PeakStart, input.PeakEnd, peakRateMultiplier) if err := ValidatePeakRateConfig(subscriptionType, peakRateEnabled, peakStart, peakEnd, peakRateMultiplier); err != nil { - return nil, err + return nil, infraerrors.BadRequest("INVALID_PEAK_RATE_CONFIG", err.Error()) } profitMinMargin := 0.0 @@ -867,7 +867,7 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd // 防止单独修改 start/end 导致最终 start>=end 等非法配置入库。与 CreateGroup 同一收口。 group.PeakRateEnabled, group.PeakStart, group.PeakEnd, group.PeakRateMultiplier = NormalizePeakRateConfig(group.SubscriptionType, group.PeakRateEnabled, group.PeakStart, group.PeakEnd, group.PeakRateMultiplier) if err := ValidatePeakRateConfig(group.SubscriptionType, group.PeakRateEnabled, group.PeakStart, group.PeakEnd, group.PeakRateMultiplier); err != nil { - return nil, err + return nil, infraerrors.BadRequest("INVALID_PEAK_RATE_CONFIG", err.Error()) } if input.ProfitControlEnabled != nil { group.ProfitControlEnabled = *input.ProfitControlEnabled diff --git a/backend/internal/service/admin_service_group_test.go b/backend/internal/service/admin_service_group_test.go index 7d5774d794..1f7a71f9f3 100644 --- a/backend/internal/service/admin_service_group_test.go +++ b/backend/internal/service/admin_service_group_test.go @@ -1145,6 +1145,66 @@ func TestAdminService_UpdateGroup_ClearsReasoningPolicyForUnsupportedPlatform(t require.Empty(t, repo.updated.ReasoningEffortMappings) } +func TestAdminService_CreateGroup_InvalidPeakRateReturnsBadRequest(t *testing.T) { + repo := &groupRepoStubForAdmin{} + svc := &adminServiceImpl{groupRepo: repo} + + _, err := svc.CreateGroup(context.Background(), &CreateGroupInput{ + Name: "subscription-group", + Platform: PlatformOpenAI, + SubscriptionType: SubscriptionTypeSubscription, + PeakRateEnabled: true, + PeakStart: "20:00", + PeakEnd: "08:30", + }) + + require.ErrorContains(t, err, "peak_end") + require.Equal(t, http.StatusBadRequest, infraerrors.Code(err)) + require.Equal(t, "INVALID_PEAK_RATE_CONFIG", infraerrors.Reason(err)) + require.Nil(t, repo.created) +} + +func TestAdminService_UpdateGroup_PeakRateValidation(t *testing.T) { + tests := []struct { + name string + input UpdateGroupInput + wantErr bool + }{ + {"cross-day window", UpdateGroupInput{PeakStart: ptrString("20:00"), PeakEnd: ptrString("08:30")}, true}, + {"partial update invalidates window", UpdateGroupInput{PeakEnd: ptrString("08:30")}, true}, + {"partial update keeps valid window", UpdateGroupInput{PeakEnd: ptrString("19:00")}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repo := &groupRepoStubForAdmin{getByID: &Group{ + ID: 1, + Name: "subscription-group", + Platform: PlatformOpenAI, + Status: StatusActive, + SubscriptionType: SubscriptionTypeSubscription, + PeakRateEnabled: true, + PeakStart: "14:00", + PeakEnd: "18:00", + PeakRateMultiplier: 3, + }} + svc := &adminServiceImpl{groupRepo: repo} + + _, err := svc.UpdateGroup(context.Background(), 1, &tt.input) + + if tt.wantErr { + require.ErrorContains(t, err, "peak_end") + require.Equal(t, http.StatusBadRequest, infraerrors.Code(err)) + require.Equal(t, "INVALID_PEAK_RATE_CONFIG", infraerrors.Reason(err)) + require.Nil(t, repo.updated) + } else { + require.NoError(t, err) + require.NotNil(t, repo.updated) + require.Equal(t, "19:00", repo.updated.PeakEnd) + } + }) + } +} + func TestAdminService_UpdateGroup_ClearsPeakRateWhenChangingToStandard(t *testing.T) { existingGroup := &Group{ ID: 1,