You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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
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:
pageSize = Math.Clamp(request.PageSize, 1, MaxPageSize) with a single shared MaxPageSize constant (suggest 100).
page = Math.Max(1, request.Page).
Empty-list TotalPages should be 0 OR 1 consistently across all three handlers (recommend 0, matching GetAllExpenses and GetMaintenanceRequests and the obvious math).
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
TotalPagesdiffers 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
GetAllExpensesGetExpensesByPropertyGetMaintenanceRequestsMath.Clamp(PageSize, 1, 100)request.PageSizeMath.Max(1, Page)request.PageTotalPagesvalue0(Math.Ceiling(0/pageSize))1(explicittotalCount == 0 ? 1 : ...)0(same Ceiling math, but only whenpageSize > 0)pageSize=0requestMath.Ceiling((double)totalCount / 0)→+Infinitycast to int →int.MaxValue(2147483647) AND returns 0 items becauseTake(0)returns emptypage=0(or negative)Skip((0 - 1) * pageSize) = Skip(-pageSize)→ PostgreSQL rejects withOFFSET must not be negative(SQLSTATE 2201X) → HTTP 500 Internal Server ErrorCaptured 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 errorOFFSET must not be negative.GET /api/v1/maintenance-requests?page=-N&pageSize=20: same as above for anyN > 0.Proposed unified contract
All paginated handlers should:
pageSize = Math.Clamp(request.PageSize, 1, MaxPageSize)with a single sharedMaxPageSizeconstant (suggest 100).page = Math.Max(1, request.Page).TotalPagesshould be0OR1consistently across all three handlers (recommend0, matchingGetAllExpensesandGetMaintenanceRequestsand the obvious math).Out of scope
This issue is a tracking issue only — no code changes here. A future story will:
Related
docs/project/stories/epic-21/21-12-pagination-edge-case-tests.md) — pinned the broken behavior with integration tests.