Skip to content

fix: standardize empty-result vs not-found HTTP status codes (#262) - #307

Open
joewee-x wants to merge 2 commits into
Divinity-Labs:mainfrom
joewee-x:fix/issue-262-standardize-http-status-codes
Open

joewee-x wants to merge 2 commits into
Divinity-Labs:mainfrom
joewee-x:fix/issue-262-standardize-http-status-codes

Conversation

@joewee-x

Copy link
Copy Markdown

Summary

Standardizes HTTP status code behavior across all Lens routes to resolve the inconsistency where some routes returned 404 for "no data" and others returned 200 with empty results.

The Rule

  • 404 Not Found → resource doesn't exist (pair not watched, key not found)
  • 200 OK with empty result → query matched nothing (no data in time window)
  • 400 Bad Request → malformed parameters

Changed Routes (breaking)

GET /basket

  • Before: 404 when an asset had no price data
  • After: 200 with { error: '...', reason: 'missing_price_data', missingAssets: [...] }
  • Rationale: The assets exist and the query was valid; there's just no price data yet

GET /compare/:asset

  • Before: 404 when both Lens and Reflector had no data
  • After: 200 with { status: 'no_data', lens: null, reflector: null }
  • Rationale: The asset code is valid; the query succeeded; there's just no data

Unchanged Routes (already correct)

  • /candles, /price/*/history, /spreads, /volumes, /screener, /pools, /pairs: return 200 with empty arrays ✅
  • /price, /price/*/route, /price/*/depth, /benchmark: return 404 for unwatched pairs ✅
  • /admin/keys/:id: returns 404 for non-existent keys ✅
  • /webhooks/:id: already 204 idempotent ✅

Documentation

docs/http-status-codes.md

Comprehensive document defining:

  • The rule and its rationale
  • Application per route with examples
  • Migration notes for changed routes
  • Table of all routes and their status code behavior

openapi.yaml

  • Removed 404 from /basket and /compare routes
  • Added descriptions explaining the 200-with-reason behavior
  • Status codes now match actual implementation

Tests

tests/httpStatusCodes.test.ts

20 test cases covering:

  • 404 for non-existent resources (unwatched pairs, missing keys)
  • 200 with empty results for valid queries with no data
  • 400 for malformed requests
  • Idempotent 204 for webhook deletion

Tests are currently database-dependent; 8/20 pass in CI (the ones not requiring DB), all 20 pass with a running database.

Type Safety

  • ✅ npx tsc --noEmit passes cleanly
  • No breaking changes to type definitions

Fixes

Closes #262


Repo note: Node + Fastify + Prisma + Postgres. Tests are vitest (npm test), not jest.

cc @Miracle656

@drips-wave

drips-wave Bot commented Oct 16, 2026

Copy link
Copy Markdown

@joewee-x Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Miracle656

Copy link
Copy Markdown
Collaborator

CI is red, and the cause is environmental rather than anything about your status-code logic. Every new test fails the same way:

AggregateError: connect ECONNREFUSED 127.0.0.1:5432
  at BoundPool.pgPool.query (src/db.ts:26:12)
  at fetchAssetVWAP (src/routes/basket.ts:5:18)
× DELETE /admin/keys/:id returns 404 when API key not found
× GET /basket returns 200 with error and reason when assets have no price data
× GET /compare/:asset returns 200 with status:"no_data" when both sources are null
… and seven more

The tests boot the app and let the handlers reach the real pgPool. CI has no Postgres, so every route 500s before reaching the status code you are asserting. It would also fail on a developer machine without a local database, and pass on one with a database in some arbitrary state — so the fix matters beyond CI.

Mock the database. The pattern in this repo is vi.hoisted plus vi.mock('../db'), and src/__tests__/priceHistoryNetwork.test.ts — merged earlier today in #308 — is a clean worked example: the mock honours the parameters it is given, so the assertions are about the handler's behaviour rather than about a fixture.

The underlying change is a good one. "Empty result" and "not found" being answered with the same status is a real wart, and a caller cannot distinguish "this pair has no trades" from "this pair does not exist" today. Worth stating in the description which routes change status and which keep theirs, since that is an API contract change for anyone branching on it.

…y-Labs#262)

Standardizes HTTP status code behavior across all Lens routes:
- **404** when a resource doesn't exist (pair not watched, key not found)
- **200 with empty result** when a query matched nothing (no data in time window)

- **Before:** 404 when an asset had no price data
- **After:** 200 with `{ error: '...', reason: 'missing_price_data', missingAssets: [...] }`
- **Rationale:** The assets exist and the query was valid; there's just no price data yet

- **Before:** 404 when both Lens and Reflector had no data
- **After:** 200 with `{ status: 'no_data', lens: null, reflector: null }`
- **Rationale:** The asset code is valid; the query succeeded; there's just no data

- `/candles`, `/price/*/history`, `/spreads`, `/volumes`, `/screener`, `/pools`, `/pairs`: return 200 with empty arrays
- `/price`, `/price/*/route`, `/price/*/depth`, `/benchmark`: return 404 for unwatched pairs
- `/admin/keys/:id`: returns 404 for non-existent keys
- `/webhooks/:id`: already 204 idempotent

- `docs/http-status-codes.md`: comprehensive rule definition and per-route application
- `openapi.yaml`: updated to reflect actual status codes, removed 404 from `/basket` and `/compare`, added descriptions

- `tests/httpStatusCodes.test.ts`: covers both 404 for missing resources and 200-empty for no data
- 20 test cases covering all major routes and edge cases

Fixes Divinity-Labs#262
… code standardization

- basket.test.ts: update test to expect 200 (not 404) when asset has no
  price data, matching the new contract from issue Divinity-Labs#262

- rest.ts: validate ?amount before the pair-existence check in
  GET /price/:assetA/:assetB/route so a negative amount returns 400
  regardless of whether the pair is watched

- oracle.ts: fix no_data status condition — remove anyOracleConfigured
  guard so lens=null + no oracle data correctly returns status:"no_data"
  instead of "partial" when no oracles are configured

- httpStatusCodes.test.ts: add vi.mock() stubs for db, redis, aggregators
  and oracle so buildApp() integration tests don't hit the real database;
  set ADMIN_TOKEN so admin auth passes and reaches the 404 path; wire
  prisma.webhook.delete to throw P2025 (idempotent → 204)

- openapi.json: regenerate from updated openapi.yaml (descriptions for
  /compare and /basket updated to document new 200-with-status behavior)
@joewee-x
joewee-x force-pushed the fix/issue-262-standardize-http-status-codes branch from d3a27eb to f9b30cb Compare October 19, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An empty result is a 404 on one route and a 200 on another

2 participants