HTTP integration test: hybrid route cache tier isolation with seeded traders rows (#283) #306
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#306
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?
Summary
Add an HTTP-level integration test that seeds two
tradersrows with differenttier_idvalues and provesGET /api/v1/route/solve(global best execution withamount_in) does not serve a wrong-discount cached quote across discount tiers. This closes the regression gap left after GitLab #283 / MR !751: unit tests coverhybrid_cache_keyformat only; no test exercisesresolve_discount_tier→ in-memory cache → HTTP response end-to-end.Current codebase
Cache + tier resolution (shipped #283)
indexer/src/api/route_solver.rs:resolve_discount_tier(state, quote_trader)— async DB lookup: discount subject =trader.as_deref().or(sender.as_deref()); readstraders.tier_idviaget_trader; unknown → 0.execute_hybrid_route_solve— callsresolve_discount_tier, buildshybrid_cache_key(..., discount_tier), checkscache_get/cache_put.ROUTE_CACHE_TTL12s,ROUTE_CACHE_MAX_ENTRIES512, keyed bysolver_version|token_in|token_out|amount_bucket|max_maker_fills|trader_key|t{tier}.hybrid_cache_key_tests::{hybrid_cache_key_includes_trader_or_none, hybrid_cache_key_distinguishes_discount_tier}assert key string equality/inequality; they never hit Postgres or Ax6.Existing route-solve integration tests
indexer/tests/api_route_solve.rs— 19 tests; uses wiremock LCD viaindexer/tests/common/lcd_mock.rs.route_solve_get_with_trader_returns_higher_estimate— proves optionaltraderquery param changes LCD mock output (8888888vs9777776) but does not seedtraders.tier_idand does not assert cache behavior.traderis present on sim queries; it does not vary output bysenderor by resolved DB tier.Traders table + test seeds
indexer/migrations/20260310000001_initial_schema.sql—traders.tier_id SMALLINT NOT NULL DEFAULT 0.indexer/tests/common/mod.rs—seed_dbinserts one trader with default tier;seed_route_solve*helpers insert pairs/assets but no tier-specific trader rows.indexer/src/db/queries/traders.rs—get_traderused by route solver tier resolution.Precedent for cache assertions via wiremock call counts
indexer/tests/api_orderbook_lcd_mock.rs— usesmock.received_requests()to prove repeat GET hits cache (no extra LCD calls).Why this is needed
resolve_discount_tierwiring while unit tests on the purehybrid_cache_keyfn still pass — HTTP test catches the real failure mode (wrongestimated_amount_outserved from cache).skills/AGENTS_HYBRID_QUOTING.md,docs/indexer-invariants.md) now document tier-based cache keys; CI should enforce the behavior, not just the key format.Constraints and guardrails
traderunset). #283 keys on resolved tier for subjecttrader if set else sender. Trusted-router path (sender≠trader, dual-tier key) is out of scope — tracked in #279 cache phase.TestServer;#[serial]+ shared DB lock pattern fromindexer/tests/common/mod.rs. No LocalTerra deploy required.senderin sim queries; avoid coupling to production tier discount math (tiers table lives on-chain; indexer only stores syncedtier_id).traders.tier_id, nottrader_trackerLCD sync./route/solveis out of scope unless trivial — POST path does not use the hybrid GET result cache today; focus on GET global best execution.seed_route_solve_2hop) + single-path mock over multi-path global solver unless needed.Relevant files
indexer/src/api/route_solver.rsresolve_discount_tier, cache get/put, GET handlerindexer/tests/api_route_solve.rsindexer/tests/common/mod.rsseed_traders_with_tiers(pool, &[(addr, tier_id), …])indexer/tests/common/lcd_mock.rsstart_tier_aware_route_optimizer_mock()indexer/src/db/queries/traders.rsget_trader/TraderRow.tier_iddocs/indexer-invariants.mdskills/AGENTS_HYBRID_QUOTING.mdRecommended direction
common/mod.rsor inline in test): insert twoterra1…addresses intotraderswithtier_id = 0andtier_id = 5(or 9),registered = true.lcd_mock.rs):simulate_swap_operationsamounts for tier-0 vs tier-5 subjects (e.g. base8888888vs discounted9777776), keyed onsenderwhentraderis absent in the sim payload (matching howmaybe_simulateforwardssender).trader/senderappears in the wiremock request body.GET …/route/solve?token_in=…&token_out=…&amount_in=1000000&sender={tier0_addr}→ assertestimated_amount_out == tier0_amount.GET …&sender={tier5_addr}(same tokens/amount) → assertestimated_amount_out == tier5_amount(must differ from tier0 even though first response is cached under a different key).tier_id = 5.mock.received_requests()count unchanged (same pattern asapi_orderbook_lcd_mockcache test).cargo test route_solve_get_cache_tier(or chosen name).Acceptance criteria
api_route_solve.rsseed ≥2tradersrows with differenttier_idand pass against Postgres + wiremock.sender=A(tier 0) thensender=B(tier 5), same route params → differentestimated_amount_out; second request does not return first sender's cached body.tradersrow) resolves tier 0 and does not collide with tier-5 cache entry.cargo test --test api_route_solve(no new infra).Test plan — functional paths
tier_id=0GET /route/solve?…&amount_in=1M&sender=AGET …&sender=B(tier 5)tier_id=5GET …&sender=Csender=not-a-wallettraderset (smoke)GET …&trader=Ttraderas discount subject (tier from T's row); optional if timeboxed — not the primary #283 gapRun commands:
Test plan — attack, hack, and abuse vectors
estimated_amount_outfor Bsendervalues to probe cached quotessenderalone should not force full fanout within same tiersender='; DROP TABLE traders; --indexer/tests/security.rsif adding abuse case; at minimum no 500trader=T_tier5&sender=S_tier0#[ignore]documenting #279 — do not false-passVerification criteria
Agent/human sign-off when all pass:
cd indexer && cargo test --test api_route_solve -j 1 -- --test-threads=1— all tests green including new tier-cache test(s).cd indexer && cargo test hybrid_cache_key --lib— unit tests still green.discount_tiersegment removed fromhybrid_cache_keycall inexecute_hybrid_route_solve(optional: confirm by temporary local revert).route_solve.*tierinskills/AGENTS_HYBRID_QUOTING.mdordocs/indexer-invariants.md).Related issues
trader/senderforwardingLabels (suggested)
indexer,correctness,testing,hybridmentioned in issue #283
mentioned in issue #322
mentioned in issue #324
mentioned in commit
662cab6523mentioned in merge request !797
Implementation complete — MR !797
https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/79
Added HTTP integration tests for hybrid GET route cache tier isolation (#283 regression guard):
route_solve_get_cache_tier_isolation— tier-0 vs tier-5 senders get distinctestimated_amount_out; unknown sender resolves tier 0route_solve_get_cache_same_tier_reuses_lcd— same-tier senders share cache (wiremock LCD call count stable)route_solve_invalid_sender_returns_400— sender validation smoke testAll acceptance criteria PASS. Verification:
mentioned in commit
0583223a6amentioned in commit
1a2801a287mentioned in commit
0dd92913f8mentioned in merge request !798
mentioned in commit
abe16fc6a7