test(indexer): negative/zero limit regression coverage beyond /hooks (#284 follow-up) #317

Closed
opened 2026-06-05 04:11:57 +00:00 by PlasticDigits · 12 comments
PlasticDigits commented 2026-06-05 04:11:57 +00:00 (Migrated from gitlab.com)

Current codebase

GitLab #284 fixed ungraceful 500 responses when list endpoints received ?limit=-1 or ?limit=0. Root cause: handlers used .min(MAX) on Option<i64> limits, so negative values passed through to Postgres as a negative LIMIT clause.

Production code (fixed on main, MR !749): All SQL-backed list handlers in indexer/src/api/ now use .clamp(1, MAX) before binding LIMIT $N. Representative sites:

Route Handler Clamp max File
GET /api/v1/pairs list_pairs 100 pairs.rs
GET /api/v1/pairs/{addr}/candles get_pair_candles 1000 pairs.rs
GET /api/v1/pairs/{addr}/trades get_pair_trades 200 pairs.rs
GET /api/v1/pairs/{addr}/liquidity-events get_pair_liquidity_events 200 pairs.rs
GET /api/v1/pairs/{addr}/limit-fills get_pair_limit_fills 200 pairs.rs
GET /api/v1/pairs/{addr}/limit-placements get_pair_limit_placements 200 pairs.rs
GET /api/v1/pairs/{addr}/limit-cancellations get_pair_limit_cancellations 200 pairs.rs
GET /api/v1/pairs/{addr}/limit-book get_pair_limit_book 100 pairs.rs
GET /api/v1/pairs/{addr}/limit-book-shallow get_pair_limit_book_shallow 20 (depth) pairs.rs
GET /api/v1/tokens list_tokens 500 tokens.rs
GET /api/v1/traders/leaderboard leaderboard 200 traders.rs
GET /api/v1/traders/{addr}/trades get_trader_trades 200 traders.rs
GET /api/v1/traders/{addr}/limit-fills get_trader_limit_fills 200 traders.rs
GET /api/v1/traders/{addr}/limit-placements get_trader_limit_placements 200 traders.rs
GET /api/v1/traders/{addr}/limit-cancellations get_trader_limit_cancellations 200 traders.rs
GET /api/v1/hooks get_hook_events 200 hooks.rs
GET /api/v1/oracle/history get_oracle_history 1000 oracle.rs
GET /cg/pairs cg_pairs 1000 cg.rs
GET /cg/historical_trades cg_historical_trades 500 cg.rs
GET /cg/orderbook, GET /cmc/orderbook/{pair} orderbook sim 100 (depth) orderbook_sim.rs

Regression test coverage today:

  • Only /api/v1/hooks has explicit negative/zero coverage: hooks_negative_and_zero_limit_clamp_to_one_not_500 in indexer/tests/api_hooks.rs.
  • Upper-bound-only *_limit_capped* tests exist in:
  • No negative/zero tests for pair list, token list, liquidity-events, trader limit-* endpoints, limit-book, or CG pairs.
  • Depth params (/cg/orderbook?depth=-1) are clamped in code but only tested for upper bound in api_orderbook_lcd_mock.rs / api_cmc.rs.

Docs: docs/indexer-invariants.md numeric query caps row documents .clamp(1, max) and links the hooks regression test (GitLab #284).

Why this is needed

#284 regressed on /api/v1/hooks because the original .min() → .clamp() sweep had no lower-bound test coverage. Every existing *_limit_capped test only asserts the upper cap (limit=99999 → len <= MAX). A future refactor that reintroduces .min(MAX) on any non-hooks route would:

  1. Pass CI unchanged.
  2. Return 500 (Postgres rejects negative 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

  • Tests only — do not change handler clamp semantics unless a test proves a bug. Expected behavior remains: negative/zero limit/depth → clamp to 1, return 200 (not 400, unless the route already rejects bad query shapes for other reasons).
  • Shared test DB — integration tests use dex_indexer_test with -j 1 --test-threads=1 (skills/AGENTS_LOCAL_POSTGRES_DEV.md). Assertions must be race-safe: prefer assert_status_ok() + body.len() <= 1 (or equivalent for wrapped JSON shapes) rather than exact row counts when siblings may seed data — mirror hooks_negative_and_zero_limit_clamp_to_one_not_500.
  • No new dependencies — extend existing axum_test + common::seed_db patterns.
  • LCD-heavy routes (limit-book, cg/orderbook) — use existing wiremock helpers in indexer/tests/common/lcd_mock.rs where a handler touches LCD; negative depth on orderbook routes should not require live chain.
  • Static guardrail (optional but recommended): add a small script or #[test] that fails if indexer/src/api/**/*.rs contains limit/depth clamps using .min( without .clamp(1, — complements runtime tests.
  • Docs: update docs/indexer-invariants.md test matrix column when new regression tests land.

Relevant files

Handlers (reference — already clamped):

Tests to extend:

Docs:

Related issues: GitLab #284 (parent fix), MR !749 (hooks clamp), MR !766 (docs verification).

  1. Extract a small test helper in indexer/tests/common/ (e.g. assert_limit_clamps_low(path: &str, server: &TestServer)) that GETs path?limit=-1 and path?limit=0, asserts 200, and asserts response length <= 1 (or route-specific shape for oracle/CG historical trades).
  2. Extend security.rs with a parameterized table (or dedicated functions) covering all SQL list routes already tested for upper cap — add limit=-1 / limit=0 for each.
  3. Add missing routes not in security.rs today: GET /api/v1/pairs, GET /api/v1/tokens, trader limit-fills / limit-placements / limit-cancellations, GET /cg/pairs.
  4. Depth parity: add depth=-1 / depth=0 cases for /cg/orderbook and /cmc/orderbook/{pair} (with LCD mock) asserting 200 and sane level counts (<= 1 per side or total per Openware split rules).
  5. Optional static check: scripts/check_indexer_limit_clamps.sh or unit test grepping api/ for .unwrap_or(...).min( on limit bindings.
  6. Refactor hooks test to use the shared helper (avoid duplication) once helper exists.

Prefer one consolidated test function per file (e.g. list_endpoints_negative_zero_limit_never_500 in security.rs) over 20 copy-pasted tests, as long as failure output names the failing route.

Acceptance criteria

  • Every route in the table above that accepts limit: Option<i64> has an integration test covering limit=-1 and limit=0 that asserts HTTP 200 (never 500) and clamped row bound (<= 1 row or route-equivalent).
  • Orderbook routes with depth accept depth=-1 and depth=0 with 200 and clamped depth (no panic, no 500).
  • Existing upper-bound *_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=1 passes with Postgres up.
  • docs/indexer-invariants.md numeric query caps row lists the new regression coverage (not only hooks).
  • (Optional) Static guardrail fails if .min( limit clamp pattern reappears in indexer/src/api/.

Test plan — happy paths

Route Query Expected
Each SQL list route limit=5 (control) 200, len <= 5 (or default window)
Each SQL list route omit limit 200 (default limit applied)
Each SQL list route limit=-1, limit=0 200, len <= 1
/cg/orderbook, /cmc/orderbook/... depth=10 (control, mocked LCD) 200, bids/asks within depth budget
Orderbook routes depth=-1, depth=0 200, clamped shallow book

Run: cd indexer && cargo test --tests -j 1 -- --test-threads=1 (full integration suite) after changes.

Test plan — attack, hack, and abuse vectors

Vector Query Expected Rationale
Negative limit SQL abuse limit=-1, limit=-9223372036854775808 (i64::MIN) 200, clamped; body must not contain sqlx/postgres/Internal server error from bad LIMIT #284 core bug
Zero limit edge limit=0 200, clamp to 1 Zero also produced negative LIMIT before fix
Oversized limit (existing) limit=99999 200, len <= MAX Upper cap still enforced
Limit type confusion limit=abc 400 (Axum deserialize) Invalid type must not 500
Combined with cursor limit=-1&before=999999 200 Clamp applies before SQL bind
Combined with filters limit=-1&pair={valid} (trader routes) 200 Filters must not bypass clamp
Oracle wrapped JSON limit=-1 on /api/v1/oracle/history 200, prices.len() <= 1 Different response shape
CG historical trades limit=-1&ticker_id=LUNC_USTC 200, buy+sell total <= 1 Split buy/sell arrays
Negative depth on orderbook depth=-1, depth=0 200; no LCD amplification beyond clamped depth Prevents weird sim loops
Error body leak any failing route above Body must not echo SQL/LIMIT fragments Align with error_responses_do_not_leak_internals

Verification criteria

Automated:

docker compose up -d postgres
./scripts/setup-postgres-dev-databases.sh
cd indexer && cargo test --test security --test api_hooks --test api_traders --test api_cg --test api_pairs --test api_orderbook_lcd_mock -j 1 -- --test-threads=1
# Optional static guard:
rg 'unwrap_or\([^)]+\)\.min\(' indexer/src/api && exit 1 || true

Manual smoke (optional QA):

# With indexer running against local Postgres:
curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:3001/api/v1/pairs/{addr}/trades?limit=-1'   # expect 200
curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:3001/api/v1/traders/leaderboard?limit=0'      # expect 200

Done when: CI indexer integration job green; reviewer can grep limit=-1 in indexer/tests/ and find coverage for every clamp site in indexer/src/api/ (hooks pattern generalized).

## Current codebase GitLab **#284** fixed ungraceful **500** responses when list endpoints received `?limit=-1` or `?limit=0`. Root cause: handlers used `.min(MAX)` on `Option<i64>` limits, so negative values passed through to Postgres as a negative `LIMIT` clause. **Production code (fixed on `main`, MR !749):** All SQL-backed list handlers in `indexer/src/api/` now use `.clamp(1, MAX)` before binding `LIMIT $N`. Representative sites: | Route | Handler | Clamp max | File | |-------|---------|-----------|------| | `GET /api/v1/pairs` | `list_pairs` | 100 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/candles` | `get_pair_candles` | 1000 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/trades` | `get_pair_trades` | 200 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/liquidity-events` | `get_pair_liquidity_events` | 200 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/limit-fills` | `get_pair_limit_fills` | 200 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/limit-placements` | `get_pair_limit_placements` | 200 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/limit-cancellations` | `get_pair_limit_cancellations` | 200 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/limit-book` | `get_pair_limit_book` | 100 | `pairs.rs` | | `GET /api/v1/pairs/{addr}/limit-book-shallow` | `get_pair_limit_book_shallow` | 20 (`depth`) | `pairs.rs` | | `GET /api/v1/tokens` | `list_tokens` | 500 | `tokens.rs` | | `GET /api/v1/traders/leaderboard` | `leaderboard` | 200 | `traders.rs` | | `GET /api/v1/traders/{addr}/trades` | `get_trader_trades` | 200 | `traders.rs` | | `GET /api/v1/traders/{addr}/limit-fills` | `get_trader_limit_fills` | 200 | `traders.rs` | | `GET /api/v1/traders/{addr}/limit-placements` | `get_trader_limit_placements` | 200 | `traders.rs` | | `GET /api/v1/traders/{addr}/limit-cancellations` | `get_trader_limit_cancellations` | 200 | `traders.rs` | | `GET /api/v1/hooks` | `get_hook_events` | 200 | `hooks.rs` | | `GET /api/v1/oracle/history` | `get_oracle_history` | 1000 | `oracle.rs` | | `GET /cg/pairs` | `cg_pairs` | 1000 | `cg.rs` | | `GET /cg/historical_trades` | `cg_historical_trades` | 500 | `cg.rs` | | `GET /cg/orderbook`, `GET /cmc/orderbook/{pair}` | orderbook sim | 100 (`depth`) | `orderbook_sim.rs` | **Regression test coverage today:** - **Only `/api/v1/hooks`** has explicit negative/zero coverage: `hooks_negative_and_zero_limit_clamp_to_one_not_500` in [`indexer/tests/api_hooks.rs`](../indexer/tests/api_hooks.rs). - **Upper-bound-only** `*_limit_capped*` tests exist in: - [`indexer/tests/security.rs`](../indexer/tests/security.rs) — trades, candles, oracle history, trader trades, pair limit-fills/placements/cancellations (7 tests, all `limit=99999`) - [`indexer/tests/api_traders.rs`](../indexer/tests/api_traders.rs) — `leaderboard_limit_capped` (`limit=999`) - [`indexer/tests/api_cg.rs`](../indexer/tests/api_cg.rs) — `cg_historical_trades_limit_capped_at_500` - [`indexer/tests/api_hooks.rs`](../indexer/tests/api_hooks.rs) — `hooks_limit_capped_at_200` - **No negative/zero tests** for pair list, token list, liquidity-events, trader limit-* endpoints, limit-book, or CG pairs. - **Depth** params (`/cg/orderbook?depth=-1`) are clamped in code but only tested for **upper** bound in `api_orderbook_lcd_mock.rs` / `api_cmc.rs`. Docs: [`docs/indexer-invariants.md`](../docs/indexer-invariants.md) numeric query caps row documents `.clamp(1, max)` and links the hooks regression test (GitLab **#284**). ## Why this is needed #284 regressed on `/api/v1/hooks` because the original `.min()` → `.clamp()` sweep had **no lower-bound test coverage**. Every existing `*_limit_capped` test only asserts the **upper** cap (`limit=99999` → `len <= MAX`). A future refactor that reintroduces `.min(MAX)` on any non-hooks route would: 1. Pass CI unchanged. 2. Return **500** (Postgres rejects negative `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 - **Tests only** — do not change handler clamp semantics unless a test proves a bug. Expected behavior remains: negative/zero `limit`/`depth` → **clamp to 1**, return **200** (not **400**, unless the route already rejects bad query shapes for other reasons). - **Shared test DB** — integration tests use `dex_indexer_test` with `-j 1 --test-threads=1` ([`skills/AGENTS_LOCAL_POSTGRES_DEV.md`](../skills/AGENTS_LOCAL_POSTGRES_DEV.md)). Assertions must be **race-safe**: prefer `assert_status_ok()` + `body.len() <= 1` (or equivalent for wrapped JSON shapes) rather than exact row counts when siblings may seed data — mirror [`hooks_negative_and_zero_limit_clamp_to_one_not_500`](../indexer/tests/api_hooks.rs). - **No new dependencies** — extend existing `axum_test` + `common::seed_db` patterns. - **LCD-heavy routes** (`limit-book`, `cg/orderbook`) — use existing wiremock helpers in [`indexer/tests/common/lcd_mock.rs`](../indexer/tests/common/lcd_mock.rs) where a handler touches LCD; negative `depth` on orderbook routes should not require live chain. - **Static guardrail (optional but recommended):** add a small script or `#[test]` that fails if `indexer/src/api/**/*.rs` contains `limit`/`depth` clamps using `.min(` without `.clamp(1,` — complements runtime tests. - **Docs:** update [`docs/indexer-invariants.md`](../docs/indexer-invariants.md) test matrix column when new regression tests land. ## Relevant files **Handlers (reference — already clamped):** - [`indexer/src/api/pairs.rs`](../indexer/src/api/pairs.rs) - [`indexer/src/api/traders.rs`](../indexer/src/api/traders.rs) - [`indexer/src/api/tokens.rs`](../indexer/src/api/tokens.rs) - [`indexer/src/api/hooks.rs`](../indexer/src/api/hooks.rs) - [`indexer/src/api/oracle.rs`](../indexer/src/api/oracle.rs) - [`indexer/src/api/cg.rs`](../indexer/src/api/cg.rs) - [`indexer/src/api/orderbook_sim.rs`](../indexer/src/api/orderbook_sim.rs) **Tests to extend:** - [`indexer/tests/security.rs`](../indexer/tests/security.rs) — primary home for cross-cutting limit abuse matrix - [`indexer/tests/api_traders.rs`](../indexer/tests/api_traders.rs) - [`indexer/tests/api_cg.rs`](../indexer/tests/api_cg.rs) - [`indexer/tests/api_hooks.rs`](../indexer/tests/api_hooks.rs) — reference implementation - [`indexer/tests/api_pairs.rs`](../indexer/tests/api_pairs.rs) — pair list, liquidity-events (if not centralized in security.rs) - [`indexer/tests/api_orderbook_lcd_mock.rs`](../indexer/tests/api_orderbook_lcd_mock.rs) — depth lower bound - [`indexer/tests/common/mod.rs`](../indexer/tests/common/mod.rs) — shared helpers **Docs:** - [`docs/indexer-invariants.md`](../docs/indexer-invariants.md) - [`docs/testing.md`](../docs/testing.md) **Related issues:** GitLab **#284** (parent fix), MR !749 (hooks clamp), MR !766 (docs verification). ## Recommended direction 1. **Extract a small test helper** in `indexer/tests/common/` (e.g. `assert_limit_clamps_low(path: &str, server: &TestServer)`) that GETs `path?limit=-1` and `path?limit=0`, asserts **200**, and asserts response length `<= 1` (or route-specific shape for oracle/CG historical trades). 2. **Extend `security.rs`** with a parameterized table (or dedicated functions) covering all SQL list routes already tested for upper cap — add `limit=-1` / `limit=0` for each. 3. **Add missing routes** not in security.rs today: `GET /api/v1/pairs`, `GET /api/v1/tokens`, trader `limit-fills` / `limit-placements` / `limit-cancellations`, `GET /cg/pairs`. 4. **Depth parity:** add `depth=-1` / `depth=0` cases for `/cg/orderbook` and `/cmc/orderbook/{pair}` (with LCD mock) asserting **200** and sane level counts (`<= 1` per side or total per Openware split rules). 5. **Optional static check:** `scripts/check_indexer_limit_clamps.sh` or unit test grepping `api/` for `.unwrap_or(...).min(` on limit bindings. 6. **Refactor hooks test** to use the shared helper (avoid duplication) once helper exists. Prefer **one consolidated test function per file** (e.g. `list_endpoints_negative_zero_limit_never_500` in `security.rs`) over 20 copy-pasted tests, as long as failure output names the failing route. ## Acceptance criteria - [ ] Every route in the table above that accepts `limit: Option<i64>` has an integration test covering **`limit=-1`** and **`limit=0`** that asserts **HTTP 200** (never **500**) and clamped row bound (`<= 1` row or route-equivalent). - [ ] Orderbook routes with `depth` accept **`depth=-1`** and **`depth=0`** with **200** and clamped depth (no panic, no 500). - [ ] Existing upper-bound `*_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=1` passes with Postgres up. - [ ] `docs/indexer-invariants.md` numeric query caps row lists the new regression coverage (not only hooks). - [ ] (Optional) Static guardrail fails if `.min(` limit clamp pattern reappears in `indexer/src/api/`. ## Test plan — happy paths | Route | Query | Expected | |-------|-------|----------| | Each SQL list route | `limit=5` (control) | **200**, `len <= 5` (or default window) | | Each SQL list route | omit `limit` | **200** (default limit applied) | | Each SQL list route | `limit=-1`, `limit=0` | **200**, `len <= 1` | | `/cg/orderbook`, `/cmc/orderbook/...` | `depth=10` (control, mocked LCD) | **200**, bids/asks within depth budget | | Orderbook routes | `depth=-1`, `depth=0` | **200**, clamped shallow book | Run: `cd indexer && cargo test --tests -j 1 -- --test-threads=1` (full integration suite) after changes. ## Test plan — attack, hack, and abuse vectors | Vector | Query | Expected | Rationale | |--------|-------|----------|-----------| | Negative limit SQL abuse | `limit=-1`, `limit=-9223372036854775808` (i64::MIN) | **200**, clamped; body must not contain `sqlx`/`postgres`/`Internal server error` from bad LIMIT | #284 core bug | | Zero limit edge | `limit=0` | **200**, clamp to 1 | Zero also produced negative LIMIT before fix | | Oversized limit (existing) | `limit=99999` | **200**, `len <= MAX` | Upper cap still enforced | | Limit type confusion | `limit=abc` | **400** (Axum deserialize) | Invalid type must not 500 | | Combined with cursor | `limit=-1&before=999999` | **200** | Clamp applies before SQL bind | | Combined with filters | `limit=-1&pair={valid}` (trader routes) | **200** | Filters must not bypass clamp | | Oracle wrapped JSON | `limit=-1` on `/api/v1/oracle/history` | **200**, `prices.len() <= 1` | Different response shape | | CG historical trades | `limit=-1&ticker_id=LUNC_USTC` | **200**, buy+sell total `<= 1` | Split buy/sell arrays | | Negative depth on orderbook | `depth=-1`, `depth=0` | **200**; no LCD amplification beyond clamped depth | Prevents weird sim loops | | Error body leak | any failing route above | Body must not echo SQL/LIMIT fragments | Align with `error_responses_do_not_leak_internals` | ## Verification criteria **Automated:** ```bash docker compose up -d postgres ./scripts/setup-postgres-dev-databases.sh cd indexer && cargo test --test security --test api_hooks --test api_traders --test api_cg --test api_pairs --test api_orderbook_lcd_mock -j 1 -- --test-threads=1 # Optional static guard: rg 'unwrap_or\([^)]+\)\.min\(' indexer/src/api && exit 1 || true ``` **Manual smoke (optional QA):** ```bash # With indexer running against local Postgres: curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:3001/api/v1/pairs/{addr}/trades?limit=-1' # expect 200 curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:3001/api/v1/traders/leaderboard?limit=0' # expect 200 ``` **Done when:** CI indexer integration job green; reviewer can grep `limit=-1` in `indexer/tests/` and find coverage for every clamp site in `indexer/src/api/` (hooks pattern generalized).
Brouie commented 2026-06-05 06:14:43 +00:00 (Migrated from gitlab.com)

mentioned in issue #284

mentioned in issue #284
Brouie commented 2026-06-05 06:28:28 +00:00 (Migrated from gitlab.com)

mentioned in merge request !773

mentioned in merge request !773
Brouie commented 2026-06-05 06:28:41 +00:00 (Migrated from gitlab.com)

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:

  • api_limit_lower_bound.rs — limit=-1 and limit=0 → 200 (never 500) + clamp to <=1 row, for every SQL-backed list endpoint in the table: pairs list/candles/trades/liquidity-events/limit-fills/placements/cancellations, tokens, traders leaderboard/trades/limit-*, oracle history, cg pairs/historical_trades (16 tests). Mirrors hooks_negative_and_zero_limit_clamp_to_one_not_500; no #[serial] needed since the clamp makes len<=1 race-safe under the shared dex_indexer_test.
  • api_orderbook_lcd_mock.rs — depth=-1/0 on /cg/orderbook + /cmc/orderbook must not 500 (2 tests). Note: depth is usize, so depth=-1 fails query deserialization with 400 (handler never runs) and depth=0 clamps to 1 — so I assert "not 500" rather than strict 200 for the negative case.
  • limit_clamp_guardrail.rs — the optional static guard you suggested: a no-DB test that scans src/api/**.rs and fails if any line reintroduces the upper-only unwrap_or(...).min(...) idiom. It would have caught #284 directly. The 3 legit bare .min( uses (Vec capacity, page-slice bound, already-clamped page limit) lack unwrap_or on the same line, so they're not flagged.
  • docs/indexer-invariants.md numeric-caps row now lists the new coverage + #317.

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

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: - api_limit_lower_bound.rs — limit=-1 and limit=0 → 200 (never 500) + clamp to <=1 row, for every SQL-backed list endpoint in the table: pairs list/candles/trades/liquidity-events/limit-fills/placements/cancellations, tokens, traders leaderboard/trades/limit-*, oracle history, cg pairs/historical_trades (16 tests). Mirrors hooks_negative_and_zero_limit_clamp_to_one_not_500; no #[serial] needed since the clamp makes len<=1 race-safe under the shared dex_indexer_test. - api_orderbook_lcd_mock.rs — depth=-1/0 on /cg/orderbook + /cmc/orderbook must not 500 (2 tests). Note: depth is usize, so depth=-1 fails query deserialization with 400 (handler never runs) and depth=0 clamps to 1 — so I assert "not 500" rather than strict 200 for the negative case. - limit_clamp_guardrail.rs — the optional static guard you suggested: a no-DB test that scans src/api/**.rs and fails if any line reintroduces the upper-only unwrap_or(...).min(...) idiom. It would have caught #284 directly. The 3 legit bare .min( uses (Vec capacity, page-slice bound, already-clamped page limit) lack unwrap_or on the same line, so they're not flagged. - docs/indexer-invariants.md numeric-caps row now lists the new coverage + #317. 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
PlasticDigits commented 2026-06-05 07:03:55 +00:00 (Migrated from gitlab.com)

mentioned in commit ede816ee1c

mentioned in commit ede816ee1cd556c48378649cdfda7b460b371f4d
PlasticDigits commented 2026-06-05 09:55:27 +00:00 (Migrated from gitlab.com)

mentioned in merge request !783

mentioned in merge request !783
PlasticDigits commented 2026-06-05 10:01:03 +00:00 (Migrated from gitlab.com)

mentioned in merge request !785

mentioned in merge request !785
PlasticDigits commented 2026-06-05 10:03:51 +00:00 (Migrated from gitlab.com)

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

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
ghost1 commented 2026-06-05 12:43:36 +00:00 (Migrated from gitlab.com)

mentioned in commit 1538ea8550

mentioned in commit 1538ea855007381c10c5affe95bdbaa9f45bb4b1
PlasticDigits commented 2026-06-05 12:43:55 +00:00 (Migrated from gitlab.com)

mentioned in merge request !806

mentioned in merge request !806
PlasticDigits commented 2026-06-05 12:44:03 +00:00 (Migrated from gitlab.com)

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_500 in api_limit_book_lcd_mock.rs (wiremock LCD):

  • GET .../limit-book-shallow?depth=-1|0 → 200, orders.len() <= 1
  • GET .../limit-book?limit=-1|0 → 200, orders.len() <= 1

Docs (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.

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_500` in `api_limit_book_lcd_mock.rs` (wiremock LCD): - `GET .../limit-book-shallow?depth=-1|0` → **200**, `orders.len() <= 1` - `GET .../limit-book?limit=-1|0` → **200**, `orders.len() <= 1` Docs (`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.
PlasticDigits commented 2026-06-05 13:07:39 +00:00 (Migrated from gitlab.com)

mentioned in commit c90c83b0bc

mentioned in commit c90c83b0bc95a37d5bf16a2170bc7d8783b47f76
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-05 13:07:42 +00:00
PlasticDigits commented 2026-06-29 15:41:31 +00:00 (Migrated from gitlab.com)

mentioned in merge request !959

mentioned in merge request !959
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#317
No description provided.