Skip to content

Cover what happens to a request before a controller sees it - #715

Merged
blaipr merged 1 commit into
mainfrom
test/entry-point-guards
Aug 13, 2026
Merged

Cover what happens to a request before a controller sees it#715
blaipr merged 1 commit into
mainfrom
test/entry-point-guards

Conversation

@blaipr

@blaipr blaipr commented Aug 13, 2026

Copy link
Copy Markdown
Member

What

30 tests across the web and API entry points and the session bookkeeping behind them.

Why here

The web entry point was never run by upstream CI — only the mocked suites were — so its runtime contracts are the easy ones to break.

The dispatch contract

  • An action whose return type is not what the dispatch demands, and one missing its #[Action] attribute, are both rejected; a proper one is accepted and rendered. That check is what catches a whole class of breakage before it reaches a browser.
  • A plain-text response is rendered and a callback one invoked.

What a request that goes wrong becomes

  • An unknown controller is a 404, on both entry points.
  • An uncaught exception is a 500.
  • A session timeout is caught separately from everything else.
  • A redirect a guard has already sent — the not-installed and maintenance ones — is not overwritten by the error handler afterwards.
  • The top-level catch ends in die(), so it is exercised in a real subprocess rather than skipped.

The session bookkeeping the guards depend on

An expired session is restarted, first activity is recorded, an old session id is re-keyed, a failure to re-key restarts instead, and the lifetime falls back when nobody is signed in or the preset lookup fails. That needed #[RunClassInSeparateProcess] — the same technique the session tests already use — in a sibling class rather than restructuring the existing one.

Left uncovered, deliberately

  • Init::requireSessionContext()'s defensive throw (its own docblock says it is unreachable unless the DI wiring breaks).
  • buildResponse()'s default: throw — the ResponseType enum has exactly three cases and all three are handled.
  • The per-address session-timeout preset branch: a hand-built serialized preset for one narrow branch.

Two defects found, reported separately

Both are their own change; the first is the serious one:

  1. The web entry point sends the exception's stack trace to the browser. Bootstrap.php:140 builds ActionResponse::error($e->getMessage(), $e->getTrace()), and that trace lands in the JSON data field. The API entry point sends only the message. Reproduced live.
  2. Init.php:335's ini_set('session.gc_maxlifetime', …) always fails silently — the session has already been started by then, and PHP refuses that change on an active session, so the configured timeout never reaches PHP's own garbage collector.

Testing

Unit: 25 green across the three classes. Integration: 12 green.

The web entry point was never run by upstream CI, so its contracts are the easy
ones to break. Covered: an action whose return type is not what the dispatch
demands, and one missing its attribute, are both rejected — that check is what
catches a whole class of breakage before it reaches a browser — while a proper
one is accepted and rendered.

Then what a request that goes wrong becomes: an unknown controller is a 404, an
uncaught exception is a 500, a session timeout is caught separately from
everything else, and a redirect a guard has already sent — the not-installed and
maintenance ones — is not overwritten by the error handler afterwards. The
top-level catch ends in die(), so it is exercised in a real subprocess.

And the session bookkeeping the guards depend on: an expired session is
restarted, first activity is recorded, an old session id is re-keyed and a
failure to re-key restarts instead, and the lifetime falls back when nobody is
signed in or the preset lookup fails. That needed a separate process, which is
the same technique the session tests already use.
@blaipr
blaipr merged commit 99eb854 into main Aug 13, 2026
8 checks passed
@blaipr
blaipr deleted the test/entry-point-guards branch August 13, 2026 16:33
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