Skip to content

Fix race condition in test helpers where background requests steal login_as callbacks#220

Open
MatheusRich wants to merge 1 commit into
wardencommunity:masterfrom
MatheusRich:fix-test-helpers-race-condition
Open

Fix race condition in test helpers where background requests steal login_as callbacks#220
MatheusRich wants to merge 1 commit into
wardencommunity:masterfrom
MatheusRich:fix-test-helpers-race-condition

Conversation

@MatheusRich

Copy link
Copy Markdown

login_as and logout used Warden.on_next_request, a global FIFO queue consumed by the next non-asset request through the Warden middleware. This meant any background request (Turbo Frame fetches, ActionCable /cable reconnects) could race against the intended visit and consume the authentication callback, leaving the user silently unauthenticated.

The asset_paths mechanism (introduced in #45 for #44) partially addressed this by skipping asset requests, but it cannot cover application-level background requests like /cable or Turbo Frame endpoints — these are normal HTTP requests that go through the full middleware stack. Users had to work around this by extending asset_paths to match /cable and other paths (#182), which is fragile and requires knowledge of Warden internals.

This commit replaces the one-shot queue mechanism in login_as with a persistent _test_users hash keyed by scope. On each request, the on_request hook checks whether the scope already has a user stored in the Rack session before applying the test user.

This approach fixes the race condition because:

  • Background requests from an established session have stored?=true, so they are skipped — there is no callback to steal.
  • The intended browser's first request has stored?=false and gets the test user applied via set_user, which writes to the Rack session.
  • Subsequent requests from the same browser load the user from session normally — the test user is not "consumed" like on_next_request was.
  • on_next_request is preserved for backward compatibility and is still used by logout to clear the Rack session on the next request.

Fixes #163, fixes #182.

…gin_as callbacks

`login_as` and `logout` used `Warden.on_next_request`, a global FIFO queue
consumed by the next non-asset request through the Warden middleware. This
meant any background request (Turbo Frame fetches, ActionCable `/cable`
reconnects) could race against the intended `visit` and consume the
authentication callback, leaving the user silently unauthenticated.

The `asset_paths` mechanism (introduced in wardencommunity#45 for wardencommunity#44) partially addressed
this by skipping asset requests, but it cannot cover application-level
background requests like `/cable` or Turbo Frame endpoints — these are
normal HTTP requests that go through the full middleware stack. Users had to
work around this by extending `asset_paths` to match `/cable` and other
paths (wardencommunity#182), which is fragile and requires knowledge of Warden internals.

This commit replaces the one-shot queue mechanism in `login_as` with a
persistent `_test_users` hash keyed by scope. On each request, the
`on_request` hook checks whether the scope already has a user stored in
the Rack session before applying the test user.

This approach fixes the race condition because:

- Background requests from an established session have `stored?=true`, so
  they are skipped — there is no callback to steal.
- The intended browser's first request has `stored?=false` and gets the
  test user applied via `set_user`, which writes to the Rack session.
- Subsequent requests from the same browser load the user from session
  normally — the test user is not "consumed" like `on_next_request` was.
- `on_next_request` is preserved for backward compatibility and is still
  used by `logout` to clear the Rack session on the next request.

Fixes wardencommunity#163, fixes wardencommunity#182.
@MatheusRich

Copy link
Copy Markdown
Author

cc @jsmestad

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.

Ill-timed Rails ActionCable requests can prevent login_as from working in tests #logout isn't working in tests

1 participant