Skip to content

fix(tests/redirection): check HTTPS redirects properly - #495

Open
caugner wants to merge 11 commits into
mainfrom
fix-https-redirects
Open

fix(tests/redirection): check HTTPS redirects properly#495
caugner wants to merge 11 commits into
mainfrom
fix-https-redirects

Conversation

@caugner

@caugner caugner commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes an assignment in the retriever, where the redirects from the HTTP session were assigned to the HTTPS redirects.

Motivation

With both chains identical, the redirection test logic produced wrong results for any site where the HTTP and HTTPS redirect chains actually differ.

Additional details

Correcting httpsRedirects revealed three latent bugs in the redirection tests:

  1. Wrong chain for pass/fail when httpRedirects is empty — the httpRedirects OR httpsRedirects fallback was used for both display and all pass/fail checks. With an empty httpRedirects, the off-host and initial-redirection checks silently evaluated against an https: URL for route[0], always falling through to a pass.

  2. Vacuous truth in allRedirectsPreloaded[].every(...) returns true, so an empty httpRedirects could produce RedirectionAllRedirectsPreloaded when the HTTP server didn't redirect at all.

  3. HTTPS chain redirecting back to HTTP went undetected — the "not to HTTPS" check only inspected the end of the HTTP chain. If the independent HTTPS session redirected back to HTTP, it passed.

All three are fixed and covered by new tests.

Related issues and pull requests

caugner added 6 commits April 10, 2026 17:32
…oute

After the retriever fix, `httpRedirects` and `httpsRedirects`
can differ. Unify both fields under a single `redirects` variable
(HTTP preferred, HTTPS fallback) so destination and route always
describe the same chain.
Two cases were not covered:
- HTTPS chain redirecting back to HTTP while the HTTP chain looked fine
- Empty httpRedirects with a live HTTP response producing a spurious pass
  (vacuous truth on .every() + wrong chain used for pass/fail checks)
Previously `redirects` was computed as httpRedirects OR httpsRedirects
(fallback), and then used for both display and all pass/fail logic.
This caused three bugs:

- When httpRedirects was empty but an HTTP response existed, the
  OR-fallback ran httpsRedirects through checks designed for an HTTP
  chain (e.g. route[0].protocol === "http:" is always false for an
  https: URL), producing a spurious pass.

- allRedirectsPreloaded called .every() on an empty httpRedirects
  array, which vacuously returns true and gave RedirectionAllRedirectsPreloaded
  for sites with no HTTP redirects.

- The "not to HTTPS" check only inspected the end of the HTTP chain.
  If the independent HTTPS session redirected back to HTTP, it went
  undetected.

Fix: separate httpRoute (used for all pass/fail checks) from
displayRoute (httpRoute with httpsRoute fallback, used only for
output.destination and output.route). Guard allRedirectsPreloaded
with httpRoute.length > 1. Extend the "not to HTTPS" check to also
test httpsRoute.at(-1).
@caugner caugner changed the title fix(retriever): assign HTTPS redirects properly fix(retriever): test HTTPS redirects properly Apr 16, 2026
@caugner caugner changed the title fix(retriever): test HTTPS redirects properly fix(tests/redirection): check HTTPS redirects properly Apr 16, 2026
@caugner
caugner marked this pull request as ready for review April 16, 2026 12:34
@caugner
caugner requested a review from a team as a code owner April 16, 2026 12:34
@caugner
caugner requested a review from LeoMcA April 16, 2026 12:34
// Check to see if every redirection was covered by the preload list.
// Guard httpRoute.length > 1 to avoid vacuous truth on an empty array.
const allRedirectsPreloaded =
httpRoute.length > 1 &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a test for the "route is 1" condition here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 2c43bbc, using a genuinely preloaded host (cloudflare.com) as a single-entry chain and asserting RedirectionMissing. The existing "redirection missing" test used mozilla.org, which isn't preloaded, so the httpRoute.length > 1 guard was never actually exercised.

Comment thread test/redirection.test.js Outdated
it("fails with redirection-missing when http redirects are empty but http response exists", function () {
// The OR fallback previously caused httpsRedirects to be used for pass/fail
// logic when httpRedirects is empty, which could produce a spurious pass.
reqs.responses.httpRedirects = [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this possible? Wouldn't a site returning a http response have at least one value in this array with a 200 status?

@caugner caugner Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, the interceptor records every response (including the final 200) and responses.http is only set on success, so a non-null responses.http always implies a non-empty httpRedirects. Removed in fd0484e.

// use the http redirect chain
retrievals.responses.httpRedirects = httpSession.redirectHistory;
retrievals.responses.httpsRedirects = httpSession.redirectHistory;
retrievals.responses.httpsRedirects = httpsSession.redirectHistory;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test for this change specifically?

@caugner caugner Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 38b5007: the live "test retrieve mdn" case now asserts httpsRedirects[0] starts on https:. Before the fix it reused the HTTP chain, which starts on http:.

Comment thread test/redirection.test.js Outdated
assert.isFalse(res.pass);
});

it("uses https redirects as fallback for destination and route when http redirects are empty", function () {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is possible in practice, if httpRedirects is empty, then responses.http should be undefined, and that's already covered by the first test in the file.

@caugner caugner Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, httpRedirects is empty only if responses.http is null. Rewrote in fd0484e to the real no-HTTP case: responses.http = null, expects RedirectionNotNeededNoHttp, and asserts destination falls back to the HTTPS chain end (the only reachable use of httpsRedirects here).

caugner added 4 commits July 15, 2026 10:58
…fallback

An HTTP response is only recorded together with at least one entry in
`httpRedirects`, so an empty `httpRedirects` implies `responses.http` is
`null`. The `displayRoute` fallback to `httpsRedirects` was therefore dead
for `route` (the `else` branch always had `httpRoute === displayRoute`);
only the destination fallback for the no-HTTP case is reachable.

Compute `destination` inline as `httpRoute.at(-1) ?? httpsRoute.at(-1)`,
assign `route` from `httpRoute` directly, and replace the test that relied
on the impossible empty-`httpRedirects`-with-HTTP-response state with one
covering the real no-HTTP destination fallback.
A redirect chain of length 1 means no redirection happened. Without the
`httpRoute.length > 1` guard, `[singlePreloadedHost].every(...)` returns
`true` and reports `RedirectionAllRedirectsPreloaded` instead of
`RedirectionMissing`. Use a genuinely preloaded host (`cloudflare.com`)
so the test fails if the guard is removed.
The retriever now assigns `httpsSession.redirectHistory` to
`httpsRedirects`. Assert the chain starts on `https:`; a regression that
reuses the HTTP session's history would start on `http:`.
Comment on lines +42 to +43
const httpRoute = requests.responses.httpRedirects;
const httpsRoute = requests.responses.httpsRedirects;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LeoMcA Should we rename http*Redirects and http*Route to avoid confusion altogether?

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.

3 participants