fix(smoke): reject an empty SMOKE_BROWSER; test the installer's argument logic

Two findings from Copilot's suppressed block.

1. resolveBrowserName treated a present-but-empty SMOKE_BROWSER as unset
   and returned chromium. The test that covered it even documented WHY
   that is dangerous — GitHub Actions renders an undefined matrix key as
   "" rather than omitting the variable — and then asserted the unsafe
   behavior. A misspelled matrix key would run chromium inside a job named
   for another engine and report green: the same false-coverage outcome
   the unrecognized-value branch exists to prevent, reached by a typo
   instead of a bad value.

   Only an absent variable now selects the default. Empty throws, naming
   the likely cause and the actual remedy ('unset it', which is not the
   same as setting it to nothing). The workflow always renders a real
   value, so nothing in CI is affected.

2. install-smoke-browser.mjs had no tests. Its I/O half runs on every
   smoke and fails loudly; its argument half does not — every caller
   passes nothing or the literal 'chromium', so the precedence rule is
   asserted by nothing at runtime. It matters: pack:verify passes chromium
   BECAUSE it launches chromium, and if the environment won,
   SMOKE_BROWSER=webkit npm run pack:verify would install one engine and
   launch another, surfacing as a misleading "Executable doesn't exist".

   resolveRequestedBrowser is now exported and pure; execution moved
   behind main() with the entrypoint guard verify-typecheck-coverage.mjs
   uses, so importing the module for tests cannot trigger an install.

Both mutation-verified: restoring either behavior fails the tests that
claim to cover it.

Addresses the suppressed comments on Copilot's fifth review of #2133.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-25 22:52:02 -04:00
commit 32cb4c63a7

View file

@ -35,13 +35,25 @@ describe("resolveBrowserName", () => {
assert.equal(resolveBrowserName({}), DEFAULT_BROWSER);
});
it("treats an empty or whitespace-only value as unset", () => {
// A CI expression that resolves to nothing (`SMOKE_BROWSER: ${{ … }}` with
// an undefined matrix key) sets the variable to "" rather than removing it.
for (const raw of ["", " "]) {
assert.equal(
resolveBrowserName({ [BROWSER_ENV_VAR]: raw }),
DEFAULT_BROWSER,
it("rejects a present-but-empty value instead of defaulting", () => {
// This is the case worth being strict about. A CI expression that resolves
// to nothing (`SMOKE_BROWSER: ${{ matrix.browsr }}`, an undefined key) sets
// the variable to "" rather than removing it — so defaulting here would run
// Chromium inside a job named for another engine, which is the very
// false-coverage outcome the unrecognized-value branch exists to stop. Only
// an ABSENT variable may select the default.
for (const raw of ["", " ", "\t\n"]) {
assert.throws(
() => resolveBrowserName({ [BROWSER_ENV_VAR]: raw }),
(err) => {
assert.match(err.message, /set but empty/);
// Names the likely cause and the actual remedy — "unset it", which is
// different from "set it to nothing" and is the whole point here.
assert.match(err.message, /matrix/);
assert.match(err.message, /Unset it/);
return true;
},
`expected ${JSON.stringify(raw)} to be rejected`,
);
}
});