Skip to content

fix: eliminate the shard-8 sweep-test 500s on feature/ghcr-packages-integration - #95

Open
hongwei1 wants to merge 9 commits into
feature/ghcr-packages-integrationfrom
fix/ghcr-sweep-500s
Open

fix: eliminate the shard-8 sweep-test 500s on feature/ghcr-packages-integration#95
hongwei1 wants to merge 9 commits into
feature/ghcr-packages-integrationfrom
fix/ghcr-sweep-500s

Conversation

@hongwei1

Copy link
Copy Markdown
Owner

Summary

Fixes the shard 8 failures from this run on feature/ghcr-packages-integration. The triggering commit (08d68f5f2, GitHub Packages publishing) is unrelated CI/build config; these are pre-existing application bugs the endpoint sweep tests (AuthSweepTest, FailureSweepTest, SuccessSweepTest) happened to catch.

Each fix is its own commit with the reasoning in the message. In order:

  • createTransactionRequest (v4.0.0): a missing view id threw a raw NullPointerException (Lift's openOrThrowException convention) instead of a 4xx — routed through unboxFullOrFail.
  • getUserInvitation (v4.0.0): an unguarded secretLink.toLong threw NumberFormatException on a non-numeric secret link — added a format check.
  • getMyApiCollectionEndpoint / getApiCollectionEndpoints (v4.0.0): both declared AuthenticatedUserIsRequired in their error list but also embedded userAuthenticationMessage(false) in the description, which made ResourceDoc's own constructor silently strip that requirement back out — so the doc reported itself as public even though the handler (and the rest of the error list) disagreed. Runtime auth enforcement was never affected; this only corrects the doc's self-reported classification.
  • getSignalChannelInfo (v6.0.0): a bare throw new RuntimeException for an unknown channel — now a proper 404.
  • getConnectorTraces / getConfigProps / getAllApiProductsV600 / getAllProductsV600 (v6.0.0): same doc/handler mismatch as above — handlers call withUser unconditionally but the docs didn't declare it (two of them still carried Lift's prop-conditional public-access wording, which the http4s handlers never implemented — documented as intentional drift at the call site, matching the pattern already used for the rest of the api-products bucket).
  • createConsumerDynamicRegistration (v5.1.0): a missing PSD2-CERT header reached JwtUtil.verifyJwt unguarded, which throws JOSEException on empty PEM input — now rejected upfront with the existing X509GeneralError.
  • StarConnector / getConnectorMethodNames (v6.0.0): callableMethods is declared directly on the Connector trait with a real default body, so the ByteBuddy proxy's isInheritedMember check (introduced by the cglib→ByteBuddy swap for the Scala 2.13/JDK25 migration) didn't recognise it as non-routable — it was being treated as an ordinary connector call and NPEing on a null args array. InternalConnector already special-cases this exact method for the same reason; gave StarConnector the same treatment.
  • Two test-only commits: recognizing application-auth (OBP-20200) 401s as distinct from user-auth 401s in AuthSweepTest, a signed-off deviation list for two endpoints whose behaviour is deliberate (verifyRequestSignResponse's JWS auth, createTransactionRequestFreeForm's 400-not-403), and documenting getSignalChannelInfo as an expected non-2xx in SuccessSweepTest (its CHANNEL_NAME placeholder isn't caught by that suite's narrower regex).

Test plan

  • AuthSweepTest, FailureSweepTest, SuccessSweepTest, SweepCoverageTest — 56/56 passing (previously 44 failures)
  • Full local suite (run_tests_parallel.sh, 4 shards) — 3579 tests, 0 failures, 0 errors

Views.views.vend.systemView/customView both return Empty for a
nonexistent view id, and openOrThrowException throws a raw
NullPointerException that escapes as a 500 instead of the intended
4xx. Route it through unboxFullOrFail so a missing view answers 400
with ViewNotFound, matching every other lookup failure in this
handler.
secretLink.toLong ran directly against the path segment with no
format check, so a non-numeric secret_link threw an unhandled
NumberFormatException that surfaced as a 500. Validate it first and
fail with the existing InvalidNumber error instead.
getMyApiCollectionEndpoint and getApiCollectionEndpoints both call
userAuthenticationMessage(false) in their description while also
listing AuthenticatedUserIsRequired in errorResponseBodies and
requiring a user in the handler. ResourceDoc's constructor treats a
description containing the "optional" wording as authoritative when
roles are empty, so it silently strips AuthenticatedUserIsRequired
from the error list and the endpoint gets classified as public.
Runtime auth enforcement (withUser) was never affected -- this only
fixes the doc's self-reported classification, which is what tooling
built on ResourceDoc (including the endpoint sweep) reads. Both
sibling endpoints (getMyApiCollectionEndpoints,
getMyApiCollectionEndpointsById) already use the correct true value.
getSignalChannelInfo threw a bare RuntimeException when the channel
had no entry in Redis, which escaped as a 500. Fail through
booleanToFuture with the new SignalChannelNotFound error instead so a
missing channel answers 404.
…y enforce it

getConnectorTraces and getConfigProps call withUser but never listed
AuthenticatedUserIsRequired in their ResourceDoc, so the doc read as
public while the handler actually demanded a user.

getAllApiProductsV600 and getAllProductsV600 have the same gap by a
different route: their description still uses
userAuthenticationMessage(!getApiProductsIsPublic /
!getProductsIsPublic), a conditional Lift carried over from
APIMethods600.scala, but the http4s handlers always call withUser and
never branch on the prop -- the same simplification already noted for
the rest of the api-products bucket in this file. Fixed the
description and error list to match what the handler does, and
documented the drift from the Lift source-of-truth at the call site.

None of this changes runtime behaviour; it only corrects the doc's
self-reported classification, which the endpoint sweep (and any other
tooling built on ResourceDoc) relies on.
createConsumerDynamicRegistration passed pem.getOrElse("") straight
into JwtUtil.verifyJwt, which throws JOSEException ("No PEM-encoded
keys found") when there is no key material to parse, escaping as a
500. Lift's commented-out original has the same gap. Check the
header is present first and fail with the existing X509GeneralError
(400) instead.
callableMethods is declared directly on the Connector trait with a
real default body, so ConnectorProxy.isInheritedMember (which checks
declaringClass != classOf[Connector]) does not recognise it as a
non-routable member. The interceptor was therefore treating it as an
ordinary connector call: looking up a MethodRouting entry for
"callableMethods" and forwarding to method.invoke with a null args
array (ByteBuddy passes null, not empty, for a no-arg method), which
NPEs inside the zip used to build the routing lookup key.

InternalConnector already special-cases this exact method for the
same reason; give StarConnector the same treatment and answer it from
the empty stub connector instead of routing it.
AuthSweepTest's public-endpoint check treated any anonymous 401 as a
violation, but OBP-20200 (application/consumer authentication) is a
legitimate way to refuse an anonymous caller distinct from OBP-20001
(user authentication) -- createConsentRequest, getConsentRequest and
createVRPConsentRequest all authenticate the calling TPP via Client
Credentials rather than a logged-in user, exactly as their own docs
say. Treat an ApplicationNotIdentified-prefixed 401 as the doc being
right rather than the sweep.

Also add a signed-off expectedAuthDeviation list for two endpoints
whose behaviour is deliberate and already explained in their own
source comments: verifyRequestSignResponse authenticates by JWS
request signature (a third mechanism ResourceDoc's authMode has no
way to declare) and answers 401 with a different message than plain
user auth; createTransactionRequestFreeForm intentionally skips the
upfront role check and lets the connector's
checkAuthorisationToCreateTransactionRequest decide, answering 400
rather than 403.
CHANNEL_NAME is a placeholder to EndpointCatalog.isPlaceholder (it
matches the _NAME suffix) but not to hasNoPathVariable's narrower
regex, so getSignalChannelInfo lands in SuccessSweepTest's
no-setup-required bucket even though its path names an entity nothing
creates. A nonexistent channel correctly answers 404; add it to
expectedNon2xx with the reason rather than widening the regex for one
endpoint.
@sonarqubecloud

Copy link
Copy Markdown

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.

1 participant