test(indexer): negative/zero limit regression coverage beyond /hooks (#284 follow-up) #317
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#317
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Current codebase
GitLab #284 fixed ungraceful 500 responses when list endpoints received
?limit=-1or?limit=0. Root cause: handlers used.min(MAX)onOption<i64>limits, so negative values passed through to Postgres as a negativeLIMITclause.Production code (fixed on
main, MR !749): All SQL-backed list handlers inindexer/src/api/now use.clamp(1, MAX)before bindingLIMIT $N. Representative sites:GET /api/v1/pairslist_pairspairs.rsGET /api/v1/pairs/{addr}/candlesget_pair_candlespairs.rsGET /api/v1/pairs/{addr}/tradesget_pair_tradespairs.rsGET /api/v1/pairs/{addr}/liquidity-eventsget_pair_liquidity_eventspairs.rsGET /api/v1/pairs/{addr}/limit-fillsget_pair_limit_fillspairs.rsGET /api/v1/pairs/{addr}/limit-placementsget_pair_limit_placementspairs.rsGET /api/v1/pairs/{addr}/limit-cancellationsget_pair_limit_cancellationspairs.rsGET /api/v1/pairs/{addr}/limit-bookget_pair_limit_bookpairs.rsGET /api/v1/pairs/{addr}/limit-book-shallowget_pair_limit_book_shallowdepth)pairs.rsGET /api/v1/tokenslist_tokenstokens.rsGET /api/v1/traders/leaderboardleaderboardtraders.rsGET /api/v1/traders/{addr}/tradesget_trader_tradestraders.rsGET /api/v1/traders/{addr}/limit-fillsget_trader_limit_fillstraders.rsGET /api/v1/traders/{addr}/limit-placementsget_trader_limit_placementstraders.rsGET /api/v1/traders/{addr}/limit-cancellationsget_trader_limit_cancellationstraders.rsGET /api/v1/hooksget_hook_eventshooks.rsGET /api/v1/oracle/historyget_oracle_historyoracle.rsGET /cg/pairscg_pairscg.rsGET /cg/historical_tradescg_historical_tradescg.rsGET /cg/orderbook,GET /cmc/orderbook/{pair}depth)orderbook_sim.rsRegression test coverage today:
/api/v1/hookshas explicit negative/zero coverage:hooks_negative_and_zero_limit_clamp_to_one_not_500inindexer/tests/api_hooks.rs.*_limit_capped*tests exist in:indexer/tests/security.rs— trades, candles, oracle history, trader trades, pair limit-fills/placements/cancellations (7 tests, alllimit=99999)indexer/tests/api_traders.rs—leaderboard_limit_capped(limit=999)indexer/tests/api_cg.rs—cg_historical_trades_limit_capped_at_500indexer/tests/api_hooks.rs—hooks_limit_capped_at_200/cg/orderbook?depth=-1) are clamped in code but only tested for upper bound inapi_orderbook_lcd_mock.rs/api_cmc.rs.Docs:
docs/indexer-invariants.mdnumeric query caps row documents.clamp(1, max)and links the hooks regression test (GitLab #284).Why this is needed
#284 regressed on
/api/v1/hooksbecause the original.min()→.clamp()sweep had no lower-bound test coverage. Every existing*_limit_cappedtest only asserts the upper cap (limit=99999→len <= MAX). A future refactor that reintroduces.min(MAX)on any non-hooks route would:LIMIT) instead of a clean 200 with clamped results — exactly the failure mode #284 reported.This is informational severity (no data exposure, no DoS), but it violates API hygiene and pollutes error logs/monitoring. Automated lower-bound tests close the gap Brouie called out in the #284 thread and in MR !766 verification follow-ups.
Constraints and guardrails
limit/depth→ clamp to 1, return 200 (not 400, unless the route already rejects bad query shapes for other reasons).dex_indexer_testwith-j 1 --test-threads=1(skills/AGENTS_LOCAL_POSTGRES_DEV.md). Assertions must be race-safe: preferassert_status_ok()+body.len() <= 1(or equivalent for wrapped JSON shapes) rather than exact row counts when siblings may seed data — mirrorhooks_negative_and_zero_limit_clamp_to_one_not_500.axum_test+common::seed_dbpatterns.limit-book,cg/orderbook) — use existing wiremock helpers inindexer/tests/common/lcd_mock.rswhere a handler touches LCD; negativedepthon orderbook routes should not require live chain.#[test]that fails ifindexer/src/api/**/*.rscontainslimit/depthclamps using.min(without.clamp(1,— complements runtime tests.docs/indexer-invariants.mdtest matrix column when new regression tests land.Relevant files
Handlers (reference — already clamped):
indexer/src/api/pairs.rsindexer/src/api/traders.rsindexer/src/api/tokens.rsindexer/src/api/hooks.rsindexer/src/api/oracle.rsindexer/src/api/cg.rsindexer/src/api/orderbook_sim.rsTests to extend:
indexer/tests/security.rs— primary home for cross-cutting limit abuse matrixindexer/tests/api_traders.rsindexer/tests/api_cg.rsindexer/tests/api_hooks.rs— reference implementationindexer/tests/api_pairs.rs— pair list, liquidity-events (if not centralized in security.rs)indexer/tests/api_orderbook_lcd_mock.rs— depth lower boundindexer/tests/common/mod.rs— shared helpersDocs:
docs/indexer-invariants.mddocs/testing.mdRelated issues: GitLab #284 (parent fix), MR !749 (hooks clamp), MR !766 (docs verification).
Recommended direction
indexer/tests/common/(e.g.assert_limit_clamps_low(path: &str, server: &TestServer)) that GETspath?limit=-1andpath?limit=0, asserts 200, and asserts response length<= 1(or route-specific shape for oracle/CG historical trades).security.rswith a parameterized table (or dedicated functions) covering all SQL list routes already tested for upper cap — addlimit=-1/limit=0for each.GET /api/v1/pairs,GET /api/v1/tokens, traderlimit-fills/limit-placements/limit-cancellations,GET /cg/pairs.depth=-1/depth=0cases for/cg/orderbookand/cmc/orderbook/{pair}(with LCD mock) asserting 200 and sane level counts (<= 1per side or total per Openware split rules).scripts/check_indexer_limit_clamps.shor unit test greppingapi/for.unwrap_or(...).min(on limit bindings.Prefer one consolidated test function per file (e.g.
list_endpoints_negative_zero_limit_never_500insecurity.rs) over 20 copy-pasted tests, as long as failure output names the failing route.Acceptance criteria
limit: Option<i64>has an integration test coveringlimit=-1andlimit=0that asserts HTTP 200 (never 500) and clamped row bound (<= 1row or route-equivalent).depthacceptdepth=-1anddepth=0with 200 and clamped depth (no panic, no 500).*_limit_capped*tests remain green (no regressions).cd indexer && cargo test --test security --test api_hooks --test api_traders --test api_cg -j 1 -- --test-threads=1passes with Postgres up.docs/indexer-invariants.mdnumeric query caps row lists the new regression coverage (not only hooks)..min(limit clamp pattern reappears inindexer/src/api/.Test plan — happy paths
limit=5(control)len <= 5(or default window)limitlimit=-1,limit=0len <= 1/cg/orderbook,/cmc/orderbook/...depth=10(control, mocked LCD)depth=-1,depth=0Run:
cd indexer && cargo test --tests -j 1 -- --test-threads=1(full integration suite) after changes.Test plan — attack, hack, and abuse vectors
limit=-1,limit=-9223372036854775808(i64::MIN)sqlx/postgres/Internal server errorfrom bad LIMITlimit=0limit=99999len <= MAXlimit=abclimit=-1&before=999999limit=-1&pair={valid}(trader routes)limit=-1on/api/v1/oracle/historyprices.len() <= 1limit=-1&ticker_id=LUNC_USTC<= 1depth=-1,depth=0error_responses_do_not_leak_internalsVerification criteria
Automated:
Manual smoke (optional QA):
Done when: CI indexer integration job green; reviewer can grep
limit=-1inindexer/tests/and find coverage for every clamp site inindexer/src/api/(hooks pattern generalized).mentioned in issue #284
mentioned in merge request !773
Took this — it's the lower-bound coverage gap I flagged in the #284 thread, so good to close it out properly. MR !773, tests only (no handler changes).
What landed:
Ran it: 16 + 2 + 1 new tests pass, and the existing upper-bound *_limit_capped suite (security 22, api_hooks 4, api_traders 15, api_cg 10) stays green. limit-book / limit-book-shallow are LCD-backed so they're out of this SQL-list scope; flag me if you want depth coverage on those via the wiremock harness too. @PlasticDigits
mentioned in commit
ede816ee1cmentioned in merge request !783
mentioned in merge request !785
Yes also need to check limit-book and limit-book-shallow, keeping in mind we need an up to date experience while minimizing lcd/rpc calls
mentioned in commit
1538ea8550mentioned in merge request !806
Implemented the follow-up from your comment (limit-book + limit-book-shallow).
MR: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/88
Adds
limit_book_negative_and_zero_limit_depth_clamp_not_500inapi_limit_book_lcd_mock.rs(wiremock LCD):GET .../limit-book-shallow?depth=-1|0→ 200,orders.len() <= 1GET .../limit-book?limit=-1|0→ 200,orders.len() <= 1Docs (
indexer-invariants.md) updated to cross-link the new test alongside the !773 SQL-list + CG/CMC coverage.Verification:
cargo test --test api_limit_book_lcd_mock limit_book_negative --test api_limit_lower_bound --test api_orderbook_lcd_mock --test limit_clamp_guardrail -j 1 -- --test-threads=1— all PASS.Issue left open until !806 merges.
mentioned in commit
c90c83b0bcmentioned in merge request !959