diff --git a/backend/internal/handler/admin/account_handler_mixed_channel_test.go b/backend/internal/handler/admin/account_handler_mixed_channel_test.go index 24ec5bcf..929dc240 100644 --- a/backend/internal/handler/admin/account_handler_mixed_channel_test.go +++ b/backend/internal/handler/admin/account_handler_mixed_channel_test.go @@ -196,3 +196,29 @@ func TestAccountHandlerBulkUpdateMixedChannelConfirmSkips(t *testing.T) { require.Equal(t, float64(2), data["success"]) require.Equal(t, float64(0), data["failed"]) } + +func TestBulkUpdateAcceptsFilterTargetRequest(t *testing.T) { + adminSvc := newStubAdminService() + router := setupAccountMixedChannelRouter(adminSvc) + + body, _ := json.Marshal(map[string]any{ + "filters": map[string]any{ + "platform": "openai", + "type": "oauth", + "status": "active", + "group": "12", + "privacy_mode": "blocked", + "search": "bulk-target", + }, + "schedulable": true, + }) + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + var resp map[string]any + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.Equal(t, float64(0), resp["code"]) +} diff --git a/backend/internal/service/admin_service_bulk_update_test.go b/backend/internal/service/admin_service_bulk_update_test.go index 4845d87c..df415295 100644 --- a/backend/internal/service/admin_service_bulk_update_test.go +++ b/backend/internal/service/admin_service_bulk_update_test.go @@ -5,8 +5,10 @@ package service import ( "context" "errors" + "reflect" "testing" + "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/stretchr/testify/require" ) @@ -25,6 +27,19 @@ type accountRepoStubForBulkUpdate struct { getByIDCalled []int64 listByGroupData map[int64][]Account listByGroupErr map[int64]error + listData []Account + listResult *pagination.PaginationResult + listErr error + listCalled bool + lastListParams pagination.PaginationParams + lastListFilters struct { + platform string + accountType string + status string + search string + groupID int64 + privacyMode string + } } func (s *accountRepoStubForBulkUpdate) BulkUpdate(_ context.Context, ids []int64, _ AccountBulkUpdate) (int64, error) { @@ -73,6 +88,24 @@ func (s *accountRepoStubForBulkUpdate) ListByGroup(_ context.Context, groupID in return nil, nil } +func (s *accountRepoStubForBulkUpdate) ListWithFilters(_ context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) { + s.listCalled = true + s.lastListParams = params + s.lastListFilters.platform = platform + s.lastListFilters.accountType = accountType + s.lastListFilters.status = status + s.lastListFilters.search = search + s.lastListFilters.groupID = groupID + s.lastListFilters.privacyMode = privacyMode + if s.listErr != nil { + return nil, nil, s.listErr + } + if s.listResult != nil { + return s.listData, s.listResult, nil + } + return s.listData, &pagination.PaginationResult{Total: int64(len(s.listData))}, nil +} + // TestAdminService_BulkUpdateAccounts_AllSuccessIDs 验证批量更新成功时返回 success_ids/failed_ids。 func TestAdminService_BulkUpdateAccounts_AllSuccessIDs(t *testing.T) { repo := &accountRepoStubForBulkUpdate{} @@ -170,3 +203,46 @@ func TestAdminService_BulkUpdateAccounts_MixedChannelPreCheckBlocksOnExistingCon // No BindGroups should have been called since the check runs before any write. require.Empty(t, repo.bindGroupsCalls) } + +func TestAdminServiceBulkUpdateAccounts_ResolvesIDsFromFilters(t *testing.T) { + repo := &accountRepoStubForBulkUpdate{ + listData: []Account{ + {ID: 7}, + {ID: 11}, + }, + listResult: &pagination.PaginationResult{Total: 2}, + } + svc := &adminServiceImpl{accountRepo: repo} + + schedulable := true + input := &BulkUpdateAccountsInput{ + Schedulable: &schedulable, + } + + filtersField := reflect.ValueOf(input).Elem().FieldByName("Filters") + require.True(t, filtersField.IsValid(), "BulkUpdateAccountsInput should expose Filters for filter-target bulk update") + require.Equal(t, reflect.Ptr, filtersField.Kind(), "BulkUpdateAccountsInput.Filters should be a pointer field") + + filtersValue := reflect.New(filtersField.Type().Elem()) + filtersValue.Elem().FieldByName("Platform").SetString(PlatformOpenAI) + filtersValue.Elem().FieldByName("Type").SetString(AccountTypeOAuth) + filtersValue.Elem().FieldByName("Status").SetString(StatusActive) + filtersValue.Elem().FieldByName("Group").SetString("12") + filtersValue.Elem().FieldByName("PrivacyMode").SetString(PrivacyModeCFBlocked) + filtersValue.Elem().FieldByName("Search").SetString("bulk-target") + filtersField.Set(filtersValue) + + result, err := svc.BulkUpdateAccounts(context.Background(), input) + require.NoError(t, err) + require.True(t, repo.listCalled, "expected filter-target bulk update to resolve matching IDs via account list filters") + require.Equal(t, PlatformOpenAI, repo.lastListFilters.platform) + require.Equal(t, AccountTypeOAuth, repo.lastListFilters.accountType) + require.Equal(t, StatusActive, repo.lastListFilters.status) + require.Equal(t, "bulk-target", repo.lastListFilters.search) + require.Equal(t, int64(12), repo.lastListFilters.groupID) + require.Equal(t, PrivacyModeCFBlocked, repo.lastListFilters.privacyMode) + require.Equal(t, []int64{7, 11}, repo.bulkUpdateIDs) + require.Equal(t, 2, result.Success) + require.Equal(t, 0, result.Failed) + require.Equal(t, []int64{7, 11}, result.SuccessIDs) +}