From fd004bdd8845532aa4b7d8a4ca012143f46f57f0 Mon Sep 17 00:00:00 2001 From: shaw Date: Wed, 1 Jul 2026 14:35:49 +0800 Subject: [PATCH] fix(account-repo): Clone query before Count to prevent state pollution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ListWithFilters reused the Ent query builder across Count() and All(), so interceptor-appended predicates (SoftDeleteMixin's deleted_at IS NULL) accumulated on the shared builder — the same pattern already fixed in group_repo/promo_code_repo/user_repo by commit 2588fa6a (P1-03 audit). The integration test now asserts pagination.Total matches len(items) on a single page, guarding the total-vs-items invariant reported in #3601. Fixes #3601 --- backend/internal/repository/account_repo.go | 6 +++++- .../internal/repository/account_repo_integration_test.go | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go index 314d7229e..2a2f5e057 100644 --- a/backend/internal/repository/account_repo.go +++ b/backend/internal/repository/account_repo.go @@ -575,7 +575,11 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati })) } - total, err := q.Count(ctx) + // Clone before Count so interceptor-appended predicates (SoftDeleteMixin's + // deleted_at IS NULL) don't accumulate on the shared builder and pollute the + // subsequent list query. Same pattern used in group_repo/promo_code_repo/user_repo + // (P1-03 audit fix, commit 2588fa6a). + total, err := q.Clone().Count(ctx) if err != nil { return nil, nil, err } diff --git a/backend/internal/repository/account_repo_integration_test.go b/backend/internal/repository/account_repo_integration_test.go index b962ba919..de7fd3a9f 100644 --- a/backend/internal/repository/account_repo_integration_test.go +++ b/backend/internal/repository/account_repo_integration_test.go @@ -445,9 +445,14 @@ func (s *AccountRepoSuite) TestListWithFilters() { tt.setup(client) - accounts, _, err := repo.ListWithFilters(ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, tt.platform, tt.accType, tt.status, tt.search, tt.groupID, tt.privacyMode) + accounts, page, err := repo.ListWithFilters(ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, tt.platform, tt.accType, tt.status, tt.search, tt.groupID, tt.privacyMode) s.Require().NoError(err) s.Require().Len(accounts, tt.wantCount) + // Regression guard for issue #3601: when the whole result set fits on a single page, + // pagination.Total must match len(items). A mismatch means the Count query was applied + // against different predicates than the list query — the exact symptom reported. + s.Require().NotNil(page) + s.Require().Equal(int64(tt.wantCount), page.Total, "total must match items on single page") if tt.validate != nil { tt.validate(accounts) }