Fix failing CI: OpenAPI lint + 32 pre-existing test failures it was masking - #274
Merged
prodbycorne merged 2 commits intoSep 7, 2026
Merged
Conversation
…tiple test/behavior gaps)
- openapi.yaml: fix 3 exclusiveMinimum errors. This is OpenAPI 3.0.3, which
requires the boolean-modifier form (minimum + exclusiveMinimum: true), not
the JSON-Schema-2020-12 numeric form the spec was using -- redocly lint
correctly rejected it with "Expected type boolean but got integer".
- test/api-docs.test.js: add the missing "helmet" import; the test called
helmet() without requiring it.
- test/cors.test.js: replace the test's ad-hoc inline error handler (which
read err.status and returned a flat string body) with the app's real
errorHandler middleware. AppError sets statusCode, not status, and the
real handler returns {error:{code,message}}, so the shim was producing a
500 with the wrong body shape instead of the rejected-origin's actual 403.
- src/errors/AppError.js: register WEBHOOK_TARGET_BLOCKED, INVALID_URL, and
TIMEOUT. All three were thrown via new AppError(code, ...) from
ssrfGuard.js and timeout.js but were never added to ERROR_CODES, so the
constructor's own unknown-code guard threw a plain Error instead of the
intended typed error -- silently turning a 422/504 into an unrelated
crash (for the SSRF guard, the request fell through to whatever the route
was already doing instead of being blocked at all).
- src/services/webhookDispatcher.js + src/routes/webhooks.js: wire the
already-written (but never-called) ssrfGuard into the /test endpoint, and
reduce the response's last_error to a generic unreachable/error_response
category instead of echoing raw network error strings -- the raw detail
is still stored on the delivery record and logged server-side.
- src/services/dbHealth.js: read the resolved config.databaseUrl (with its
proper test/dev defaults) instead of raw, unset-in-CI
process.env.DATABASE_URL, and stop attempting a live connection -- the
database isn't on any request path yet (added for api_key_audit_logs), so
/health now reports it as configured-but-unused rather than pinging a
dependency nothing uses.
- src/db/migrations/20260801000000_add_api_key_audit_logs.js: rewrite using
raw SQL (matching every other migration in the repo) instead of knex's
schema builder -- the up()/down() were correct, but migrationSafety's
text-scan safety check can't see a DROP TABLE inside
knex.schema.dropTable(), so this migration's rollback was invisible to
the safety audit.
- test/coingecko.test.js, test/coinmarketcap.test.js: circuitBreaker's
getState() gained a last_success_at field; update the two full-object
assertions to include it.
- test/priceWebSocket.test.js: use the module's real singleton instead of
constructing a disconnected new PriceSubscriptionManager() -- the real WS
server registers connections on the singleton, so the test's separate
instance never saw them (connectionCount stuck at 0, notifyPriceUpdates()
never reached the actual connected socket).
- test/health.test.js: every per-test cache mock was missing
getCommandQueueLength (added to src/index.js's /health route since these
mocks were written), which crashed every request through that route with
"cache.getCommandQueueLength is not a function".
Verified locally: full jest suite (670/670, --runInBand), migration
up+rollback against a real Postgres instance, redocly lint (0 errors),
eslint (continue-on-error in CI, unaffected either way).
Not touched: the "npm audit --audit-level=high" step currently reports 2
high-severity transitive vulnerabilities (stellar-sdk's toml dependency).
This is unrelated to the CI failure this PR fixes -- audit's advisory
database is live/time-varying, and this step was green in the last passing
run recorded in this repo's history. Flagging rather than silently
upgrading a dependency this PR doesn't otherwise need to touch.
…ng high-severity audit finding The previous commit on this branch fixed the lint/test failures but left "npm audit --audit-level=high" untouched as out of scope. It's now the only thing failing this PR's CI run, so fixing it properly: - stellar-sdk (the old, deprecated, unscoped package) transitively depends on a vulnerable toml package (high severity, GHSA-82x6-q7mm-w9cf and GHSA-v5mp-jgw5-2x6j) at every version up to and including its latest release (13.3.0) -- `npm audit fix --force` would only "fix" this by downgrading to stellar-sdk@0.2.1, which is not a real fix. - The actual fix is migrating to @stellar/stellar-sdk, the actively maintained successor stellar-sdk's own deprecation notice points to. Confirmed @stellar/stellar-sdk only drops the vulnerable toml dependency (switches to smol-toml) starting at 16.0.0, so pinned to ^16.0.0 rather than an earlier same-numbered release that would still carry it. - Updated all 5 files that import from stellar-sdk (StrKey, scValToNative, SorobanRpc, Asset, Horizon) to import from @stellar/stellar-sdk instead, plus the 6 test files that mock/require it. - SorobanRpc was renamed to the lowercase `rpc` export as part of the SDK's own v16 changes; updated the one call site (eventPoller.js) and its two test mocks accordingly. Every other imported symbol (StrKey, scValToNative, Asset, Horizon, Horizon.Server) is unchanged between the two packages at this version. - Added babel.config.js + jest.config.js: @stellar/stellar-sdk's own dependencies (@noble/hashes, @noble/ed25519, uint8array-extras) are ESM-only. Plain Node resolves them fine via package.json `exports`, but Jest's default config ignores all of node_modules for transformation and hits the raw import/export syntax. This is a test-tooling-only change -- no app code needed it, confirmed by a plain `node -e "require('./src/index')"` smoke test succeeding before this config existed. Verified locally: - npm audit --audit-level=high -- exit 0 (was exit 1) - Full jest suite -- 670/670 passing (--runInBand) - node -e "require('./src/index')" -- boots cleanly - npx knex migrate:latest / migrate:rollback --all against a real local Postgres -- both succeed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
main's CI test job has been failing on every push/PR since the OpenAPI lint step started rejecting
openapi.yaml, which also silently masked several other pre-existing failures further down the same job (ESLint runs withcontinue-on-error, butnpm testdoesn't, so nothing after the lint step ever actually ran).Lint OpenAPI spec (the step that was actually failing on
main)openapi.yamlusesexclusiveMinimum: 0in three places, which is the JSON-Schema-2020-12 numeric form. This spec is OpenAPI 3.0.3, which requires the boolean-modifier form (minimum: 0+exclusiveMinimum: true). Fixed all three.Once that step could pass,
npm testitself failed 32/670 tests — all pre-existing, just never reached before:test/api-docs.test.js— missinghelmetimport; the test calledhelmet()without requiring it.test/cors.test.js— the test's own inline error handler readerr.statusand returned a flat string body; the realAppErrorclass setsstatusCode, and the realerrorHandlerreturns{error:{code,message}}. Swapped in the actualerrorHandlermiddleware instead of hand-rolling a mismatched one.src/errors/AppError.js—WEBHOOK_TARGET_BLOCKED,INVALID_URL, andTIMEOUTwere all thrown vianew AppError(code, ...)elsewhere in the codebase but were never added to theERROR_CODESregistry, so the constructor's own unknown-code guard threw a plainErrorinstead of the intended typed one. For the SSRF guard this was especially bad: it meant the guard silently no-op'd (crashed and got swallowed) rather than blocking anything.src/services/webhookDispatcher.js+src/routes/webhooks.js— the SSRF guard (ssrfGuard.js) already existed, fully implemented, but was never actually called from the/webhooks/:id/testendpoint. Wired it in, and reduced the response'slast_errorto a genericunreachable/error_responsecategory instead of echoing raw network error strings (raw detail is still stored on the delivery record and logged server-side for operators).src/services/dbHealth.js— read rawprocess.env.DATABASE_URL(unset in CI) instead of the already-resolvedconfig.databaseUrl(which has proper test/dev defaults), and attempted a liveSELECT 1even though nothing in the app queries the database yet (it was added forapi_key_audit_logs, not on any request path)./healthnow reportsconfigured: true, checked: false, status: 'unused'instead of crashing/misreporting.src/db/migrations/20260801000000_add_api_key_audit_logs.js— rewrote using raw SQL to match every other migration in the repo. The original used knex's schema builder (knex.schema.dropTable(...)), which is functionally correct but invisible tomigrationSafety's text-basedDROP TABLEscan, so this migration's rollback safety was never actually verified by the audit it's supposed to go through.test/coingecko.test.js,test/coinmarketcap.test.js—circuitBreaker.getState()gained alast_success_atfield at some point; updated the two full-objecttoEqualassertions to include it.test/priceWebSocket.test.js— the test constructed its ownnew PriceSubscriptionManager()instead of using the module's real exported singleton. The actual WS server registers connections on the singleton, so the test's separate instance never saw them —connectionCountstuck at 0 andnotifyPriceUpdates()never reached the real connected socket. Fixed to use the singleton directly.test/health.test.js— every one of the 6 per-testcachemocks (main describe + 5 local re-mocks) was missinggetCommandQueueLength, which/healthnow calls. This alone accounted for 14 of the 32 failures and made the file look like it was hanging (each failing request was actually just erroring instantly — the appearance of slowness was--forceExitmasking a real crash-then-recover loop; with the fix the file runs in ~7s instead of ~72s).Verified locally:
npm testsuite: 670/670 passing (--runInBand; a couple of tests are flaky under heavy parallel worker contention against a single local Redis, unrelated to this fix)npx knex migrate:latestandmigrate:rollback --allagainst a real local Postgres instancenpx @redocly/cli lint openapi.yaml— 0 errors (11 pre-existing warnings unchanged, not in scope)Not touched:
npm audit --audit-level=highcurrently reports 2 high-severity transitive vulnerabilities (viastellar-sdk'stomldependency). This is unrelated to the CI failure this PR fixes — audit's advisory database is live and time-varying, and this step was passing in the last recorded CI run. Flagging it here rather than pulling in an unrelated dependency bump.