Skip to content

137676bf - Guard every /compliance path, including ones no screen claims - #1307

Merged
TaprootFreak merged 3 commits into
developfrom
fix/compliance-guard-pending-chargebacks
Aug 10, 2026
Merged

137676bf - Guard every /compliance path, including ones no screen claims#1307
TaprootFreak merged 3 commits into
developfrom
fix/compliance-guard-pending-chargebacks

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #1306.

The defect

A URL below /compliance that matches no route — a typo, a screen not deployed yet, a screen removed —
makes React Router raise a 404 ErrorResponse and render the root errorElement (ErrorScreen). That
screen runs outside every guard, and an error boundary does not change the URL. An unauthorised role
therefore sits on a /compliance/… address and is never sent away.

That is what #1306 observed. The report named /compliance/pending-chargebacks, and the guard on that
screen turned out to be intact — the run had been made against a build without the route
(feat/e2e-stack branches off #1282, the screen landed with #1285, and the harness builds the bundle from
its own working tree). But the hole underneath is real and independent of that one route: the role check
for the compliance area only exists where a screen exists.

The fix

A guarded catch-all as the last compliance route:

  • src/screens/compliance-not-found.screen.tsx — calls useComplianceGuard() like every other compliance
    screen, then states plainly that the page does not exist and shows the requested path.
  • src/App.tsxpath: 'compliance/*', placed after all compliance routes. React Router ranks static
    segments above a splat, so it only ever matches what no screen claims.
  • src/__tests__/compliance-not-found.screen.test.tsx — pins both halves: the guard call (with an empty
    argument list) and the rendered message plus requested path.

The role check for /compliance no longer depends on whether a given screen exists, and an authorised
user gets "this compliance page does not exist" instead of "something went wrong".

Behaviour outside /compliance is untouched: an unknown path elsewhere still renders the generic error
screen exactly as before.

Second change: the guard assertion the screen tests were missing

src/__tests__/compliance-chargeback-list.screen.test.tsx mocked useComplianceGuard away and never
asserted the call — deleting the guard from the screen left all eight tests green. It now asserts the call
with an empty argument list, because useComplianceGuard(redirectPath = '/', isActive = true)
navigates only while isActive: useComplianceGuard(undefined, false) would keep the call and still leave
the guard inert.

Evidence

Measured against a running dev server, role taken from a JWT (the frontend decodes it client-side, so the
guard decision involves no backend), polling location.pathname:

Path Before After
/compliance/definitiv-nicht-existent (invented) stays on the path, "something went wrong" redirected to /
/compliance/pending-chargebacks redirected to / redirected to /
/compliance/recalls redirected to / redirected to /
/voellig-unbekannt-ausserhalb (outside /compliance) stays, generic error stays, generic error (unchanged)

The splat claims nothing that belongs to a real screen — verified by which lazy chunk each URL loads:

/compliance/kyc-stats                → compliance-kyc-stats
/compliance/recalls                  → compliance-recall-list
/compliance/pending-chargebacks      → compliance-chargeback-list
/compliance/call-queues/foo/1        → compliance-call-queue-detail
/compliance/mros/create              → compliance-mros-create
/compliance/definitiv-nicht-existent → compliance-not-found

Mutation checks on the guard assertion, each restored afterwards: removing useComplianceGuard(); and
changing it to useComplianceGuard(undefined, false); each fail that one test and only that test.

Full suite: 81 suites, 950 tests, all green. ESLint clean on both new files.

Coverage

  • src/screens/compliance-not-found.screen.tsx100 % statements / 100 % branches / 100 % functions /
    100 % lines
    .
  • src/screens/compliance-chargeback-list.screen.tsx (untouched, measured with the new assertion in
    place) — 100 % / 100 % / 100 % / 100 %.
  • src/App.tsx is touched by two insertions (a lazy import and a route entry). It is the routing table,
    has no test of its own in this repository, and is not brought to 100 % here — a declared deviation
    from the coverage rule in CONTRIBUTING.md, and the reviewer's call. The two added lines are exercised in
    the browser evidence above.

Note for #1288

Once that branch is rebased onto a develop containing #1285, /compliance/pending-chargebacks belongs in
ALL_COMPLIANCE_PATHS as a normal assertion and both test.fail() markers can go: the redirect assertion
now passes through this catch-all even while a screen is missing, and the mount assertion passes once the
screen is present.

…uard

The suite for this screen mocked useComplianceGuard away and never asserted the
call, so removing the guard from the screen left all eight tests green. That is
the blind spot #1306 suspected, and it survived review because nothing pointed
at it.

Verified by mutation: with the guard call commented out, this new test fails and
only this test fails; restored, the suite is green again.

Refs #1306
Review pointed out the quieter mutation the first version missed: the hook is
useComplianceGuard(redirectPath = '/', isActive = true) and its effect navigates
only while isActive, so useComplianceGuard(undefined, false) would keep the call
in place and still leave the guard inert — exactly the "guard present but
ineffective" shape #1306 started from.

The mock now forwards its arguments and the test asserts an empty argument list.
Verified by mutation on both shapes: removing the call and passing
(undefined, false) each fail this test and only this test.
@TaprootFreak

TaprootFreak commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

EN: Three review passes, the last two triggered by findings this branch fixed — a guard assertion that ignored its arguments, then the catch-all route that closes the underlying hole.
DE: Drei Review-Durchläufe, die letzten beiden ausgelöst durch Befunde, die dieser Branch behoben hat — eine Guard-Assertion, die ihre Argumente ignorierte, und danach die Catch-all-Route, die die eigentliche Lücke schliesst.

Details

Pass 1 — both dimensions clean on the first commit, which added the missing guard assertion to the chargeback screen test.

Pass 2 — the logic dimension pointed out that the guard mock discarded its arguments, so useComplianceGuard(undefined, false) would have kept the call in place, left the guard inert, and still passed. The mock now forwards its arguments and the test asserts an empty argument list. Both mutations (call removed, call made inert) fail that test and only that test. Both dimensions clean afterwards.

Pass 3 — covers the product fix: a guarded catch-all for /compliance/*, so an unknown compliance path can no longer park an unauthorised role on a /compliance URL. Both dimensions clean, including the check that the splat claims nothing belonging to a real screen (verified independently against the route table and, in the browser, against the lazy chunk each URL loads).

@TaprootFreak
TaprootFreak marked this pull request as ready for review August 10, 2026 18:35
@TaprootFreak
TaprootFreak marked this pull request as draft August 10, 2026 18:53
A path below /compliance that matches no route falls through to the router's
errorElement. That screen calls no guard, and an error boundary leaves the URL
alone — so an unauthorised role stays parked on a /compliance address, which is
what #1306 saw. The role check existed only where a screen existed.

A guarded catch-all closes that: 'compliance/*' as the last compliance route,
rendering a screen that runs useComplianceGuard() and otherwise says the page
does not exist. React Router ranks static segments above a splat, so it matches
only what no screen claims — verified per URL by the lazy chunk that loads.

Paths outside /compliance keep their current behaviour.
@TaprootFreak TaprootFreak changed the title 137676bf - Assert the pending-chargebacks screen calls its compliance guard 137676bf - Guard every /compliance path, including ones no screen claims Aug 10, 2026
@TaprootFreak
TaprootFreak marked this pull request as ready for review August 10, 2026 19:08
@TaprootFreak
TaprootFreak merged commit 58e9fb4 into develop Aug 10, 2026
6 checks passed
@TaprootFreak
TaprootFreak deleted the fix/compliance-guard-pending-chargebacks branch August 10, 2026 19:23
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.

Compliance guard does not redirect a plain User away from /compliance/pending-chargebacks

1 participant