Skip to content

Add a ratchet lint for full-module vi.mock calls - #412

Merged
haksungjang merged 1 commit into
mainfrom
mock-audit-lint-390
Sep 6, 2026
Merged

Add a ratchet lint for full-module vi.mock calls#412
haksungjang merged 1 commit into
mainfrom
mock-audit-lint-390

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

Summary

vi.mock("@/lib/api", () => ({...})) hand-writes the mocked module's shape. When the real module gains an export, every test that already mocks it that way keeps compiling and silently hands the new export undefined, and the test that breaks is rarely the one whose PR added the export. This is what happened when postLogin's MFA union return type shipped: unrelated tests failed for a reason invisible from their own diff (#390).

This does not migrate the existing full mocks; that is a larger change requiring each one to be checked against what its component actually renders. Instead:

  • apps/frontend/scripts/mock-audit.mjs statically detects full-module vi.mock calls (a factory is present and it doesn't call importOriginal/vi.importActual), resolves the mocked specifier to its source file, and diffs the factory's declared keys against the module's real value-level exports (type-only exports are excluded, since they don't exist at runtime).
  • apps/frontend/scripts/mock-audit-baseline.json freezes the current count of full-module mocks per file (151 across 88 files), same ratchet shape as token-lint.mjs and problem-detail-lint.mjs: a new full mock, or a rise in an existing file's count, fails; a drop must be re-recorded with --update so debt paid down can't be quietly re-spent.
  • Wired into npm run lint (mock:lint script) so CI enforces it without a workflow change.

87 of the 151 existing full mocks already have an undeclared-export gap (most visibly the two heaviest offenders, @/lib/api and @/lib/projectsApi). The tool reports these prominently but does not fail on them, since they predate the gate; it exists to stop the count from growing, and the gap detail is there so the actual bug shape is visible to whoever touches that mock next.

Test plan

  • npm run lint (eslint + mock:lint) green
  • npm run typecheck green
  • npm run test (235 files / 2624 tests) green
  • node scripts/i18n-check.cjs green (no new UI strings)
  • node tools/em-dash/lint.mjs --base origin/main green
  • Added a full mock with a declared-export gap as a temporary fixture; confirmed the lint fails with the exact missing-export list, then removed it
  • Manually inflated a baseline entry to simulate a hand-edited widening; confirmed the gate reports it as a mismatch requiring --update (which only succeeds once it matches the real count), so an unjustified increase can't silently land

Refs #390 (audit + lint only, per the issue's decided scope; migrating the 51 existing mocks to importOriginal is explicitly out of scope for this change)

shape, so a new export in the real module silently becomes undefined
in every test that already mocks it that way, breaking a test that
never touched the change. Add scripts/mock-audit.mjs, which detects
full-module mocks (a factory present, no importOriginal/importActual),
diffs the factory's declared keys against the real module's exports,
and reports the gap. Freeze the current 151 full-module mocks in a
baseline that can only shrink, same shape as token-lint.mjs and
problem-detail-lint.mjs, and wire it into npm run lint.
@haksungjang
haksungjang merged commit 79803a3 into main Sep 6, 2026
25 checks passed
@haksungjang
haksungjang deleted the mock-audit-lint-390 branch September 6, 2026 21:57
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