Skip to content

Pagination handler inconsistencies across paginated list endpoints #402

@daveharmswebdev

Description

@daveharmswebdev

Summary

Audit (Story 21.12, May 2026) found three different pagination contracts across the three paginated list query handlers. The expense handlers clamp; the maintenance-requests handler does not. Empty-list TotalPages differs across the two expense handlers. Edge inputs (page=0, pageSize=0) produce silently absurd values or 500 errors on the maintenance-requests endpoint.

This issue tracks unifying the contracts. The divergent shipped behavior is now pinned by integration tests in Story 21.12 — those tests will fail loudly when the handlers are unified, which is the desired signal to update the assertions.

Source-of-truth files

  • backend/src/PropertyManager.Application/Expenses/GetAllExpenses.cs (lines 122-127)
  • backend/src/PropertyManager.Application/Expenses/GetExpensesByProperty.cs (lines 74-78)
  • backend/src/PropertyManager.Application/MaintenanceRequests/GetMaintenanceRequests.cs (lines 14-15, 78-108)

Divergence table

Behavior GetAllExpenses GetExpensesByProperty GetMaintenanceRequests
Math.Clamp(PageSize, 1, 100) YES YES NO — uses raw request.PageSize
Math.Max(1, Page) YES YES NO — uses raw request.Page
Empty-list TotalPages value 0 (Math.Ceiling(0/pageSize)) 1 (explicit totalCount == 0 ? 1 : ...) 0 (same Ceiling math, but only when pageSize > 0)
pageSize=0 request clamped to 1 clamped to 1 divides by zero in Math.Ceiling((double)totalCount / 0)+Infinity cast to int → int.MaxValue (2147483647) AND returns 0 items because Take(0) returns empty
page=0 (or negative) clamped to 1 clamped to 1 Skip((0 - 1) * pageSize) = Skip(-pageSize) → PostgreSQL rejects with OFFSET must not be negative (SQLSTATE 2201X) → HTTP 500 Internal Server Error

Captured probe values (Story 21.12 Task 1)

  • GET /api/v1/maintenance-requests?pageSize=0 (5 seeded requests): 200 OK, items=[], pageSize=0, totalCount=5, totalPages=2147483647 (int.MaxValue).
  • GET /api/v1/maintenance-requests?page=0&pageSize=20: HTTP 500 with PostgreSQL error OFFSET must not be negative.
  • GET /api/v1/maintenance-requests?page=-N&pageSize=20: same as above for any N > 0.

Proposed unified contract

All paginated handlers should:

  1. pageSize = Math.Clamp(request.PageSize, 1, MaxPageSize) with a single shared MaxPageSize constant (suggest 100).
  2. page = Math.Max(1, request.Page).
  3. Empty-list TotalPages should be 0 OR 1 consistently across all three handlers (recommend 0, matching GetAllExpenses and GetMaintenanceRequests and the obvious math).
  4. Returning HTTP 500 for client-side input is a server bug — fix this by either clamping at the handler (Bug: Expired Email Verification Token Leads to Dead-End UX #2) or rejecting early with HTTP 400 from a FluentValidation rule on the query.

Out of scope

This issue is a tracking issue only — no code changes here. A future story will:

  • Refactor handler clamping into a small shared helper (or update each handler in place).
  • Update Story 21.12 tests' broken-behavior assertions to reflect the new fixed behavior.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions