Skip to content

test(node-4020): config-agnostic assertions + generic org-creation seam isolation - #4021

Merged
PierreBrisorgueil merged 4 commits into
masterfrom
feat/4020-test-homogeneity
Aug 5, 2026
Merged

test(node-4020): config-agnostic assertions + generic org-creation seam isolation#4021
PierreBrisorgueil merged 4 commits into
masterfrom
feat/4020-test-homogeneity

Conversation

@PierreBrisorgueil

@PierreBrisorgueil PierreBrisorgueil commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • What changed: applies the test-homogeneity epic's 4 rules to 5 test files (modules/billing/tests/billing.quota.unit.tests.js, modules/billing/tests/billing.unit.tests.js, modules/invitations/tests/invitations.integration.tests.js, modules/organizations/tests/organizations.emailVerification.unit.tests.js, modules/organizations/tests/organizations.service.silent.catch.unit.tests.js) and adds a new organizationAbilities unit suite (modules/organizations/tests/organizations.abilities.unit.tests.js).
  • Why: these test files currently assert hardcoded stack defaults, ride an ambient env var, or (implicitly) depend on a real consumer registering something on the org-creation event seam — a consumer running its own test suite against a different config would have to hand-adapt them. The fix makes each one config/env-agnostic by construction.
  • Related issues: Closes 🔧 Node test suite — config-agnostic assertions + generic extension-point isolation #4020

Scope

  • Module(s) impacted: billing, invitations, organizations (tests only, no production code changed)
  • Cross-module impact: none
  • Risk level: low

Validation

  • npm run lint
  • npm test
  • Manual checks done (if applicable)

Guardrails check

  • No secrets or credentials introduced (.env*, secrets/**, keys, tokens)
  • No risky rename/move of core stack paths
  • Changes remain merge-friendly for downstream projects
  • Tests added or updated when behavior changed

Notes for reviewers

Per-rule breakdown:

  • rule 1 (never assert a hardcoded default): billing.unit.tests.js's SubscriptionUpdate tests now derive the plan id from the loaded config instead of a hardcoded devkit-default plan id; a new nested block rebuilds the schema against a synthetic plan catalogue to prove it's genuinely config-driven, not a re-labeled default.
  • rule 2 (isolate extension points generically): organizations.emailVerification.unit.tests.js and organizations.service.silent.catch.unit.tests.js now mock the org-creation event emitter (a plain hook/registry any consumer module can subscribe to) with a bare double, rather than letting the real singleton run — the same convention already used by organizations.service.signup.unit.tests.js, which already covers the emitted-payload contract.
  • rule 3 (env prerequisites are explicit): billing.quota.unit.tests.js now sets/restores an explicit dev-grade NODE_ENV for the suite instead of depending on whichever value a consumer's own test runner happens to use — the response helper this middleware calls only serializes a debug field in a dev-grade env.
  • rules 1+3 combined: invitations.integration.tests.js's one test that implicitly rode the ambient config.invitations.userFacing default now installs its own value (the true/false split is already covered elsewhere in the file).
  • New coverage: organizations.abilities.unit.tests.js — admin/user × owner/admin/member/no-membership matrix over the CASL ability builder, generic policy coverage the stack lacked.

Verified both on the bare devkit-default config and against synthetic config overrides (different plan catalogue, non-dev-grade NODE_ENV) — details in the first PR comment.

  • Security considerations: none — test-file-only diff, no production code touched.
  • Mergeability considerations: none.
  • Follow-up tasks (optional): two discoveries surfaced during this batch (out of scope here, no fix applied) — noted in the first PR comment.

Summary by CodeRabbit

  • Tests
    • Expanded coverage for organization permissions across roles, memberships, and scoped actions.
    • Improved billing tests to validate configured subscription plans and quota responses reliably.
    • Strengthened invitation tests for open-signup behavior and cleanup.
    • Isolated organization creation tests from event listeners for more consistent results.

…am isolation

Applies the epic's 4 homogeneity rules to the test files a consumer currently
has to adapt:
- billing quota: force an explicit dev-grade NODE_ENV (set/restore) instead of
  riding whatever ambient value a consumer's own test runner uses.
- billing subscription schema: derive plan ids from the loaded config instead
  of a hardcoded devkit default; add a synthetic-catalogue block proving the
  schema is config-driven, not a re-labeled default.
- organizations email-verification + silent-catch: mock the org-creation event
  emitter generically (the hook mechanism itself) instead of leaving it real,
  so no consumer-named listener is ever needed to isolate these tests; add
  coverage proving a generically-registered listener receives the documented
  payload.
- invitations open-signup: the one assertion riding the ambient
  config.invitations.userFacing default now installs its own value
  (set/restore) — the true/false split is already covered elsewhere in the
  file.
- new organizationAbilities unit suite: admin/user × owner/admin/member/
  no-membership matrix over the CASL ability builder, generic policy coverage
  the stack lacked.

Closes #4020
Simplify pass over the Node#4020 test-homogeneity batch:
- organizations.emailVerification: drop the new real-EventEmitter seam block
  and switch to the same bare-double lib/events.js mock already used
  elsewhere in the module — organizations.service.signup.unit.tests.js
  already covers the organization.created/organization.provisioned payload
  shape and ordering generically, so this file only needed isolation.
- organizations.abilities: extract a callAbilities(user, membership) helper
  to remove the repeated can/cannot spy boilerplate across the matrix.
- invitations.integration: fold the userFacing save/restore into the
  describe block's existing beforeEach/afterEach (same pattern already used
  for sign.up/sign.cap), dropping the test's bespoke try/finally.
- billing.quota: hoist the NODE_ENV save/restore to beforeAll/afterAll — the
  value never changes per test.

Re-verified: full test:coverage suite green, plus the config/NODE_ENV-
independent files re-checked under a synthetic non-dev NODE_ENV.
@PierreBrisorgueil PierreBrisorgueil added the Tests Adding missing tests or correcting existing label Aug 5, 2026
@PierreBrisorgueil PierreBrisorgueil self-assigned this Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PierreBrisorgueil, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 15a4774d-c8a3-4fef-ac26-dba100984ce5

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4d76c and 5b9004a.

📒 Files selected for processing (1)
  • modules/billing/tests/billing.unit.tests.js

Walkthrough

The PR makes billing and invitation tests configuration-independent, isolates organization event seams, and adds comprehensive organizationAbilities coverage.

Changes

Test hermeticity and policy coverage

Layer / File(s) Summary
Config-aware billing tests
modules/billing/tests/billing.quota.unit.tests.js, modules/billing/tests/billing.unit.tests.js
Billing tests control NODE_ENV, derive plans from configuration, and validate synthetic billing catalogues.
Invitation configuration lifecycle
modules/invitations/tests/invitations.integration.tests.js
Invitation tests set and restore userFacing configuration and use describe-level cleanup for the open-signup account.
Organization policy and event isolation
modules/organizations/tests/organizations.abilities.unit.tests.js, modules/organizations/tests/organizations.emailVerification.unit.tests.js, modules/organizations/tests/organizations.service.silent.catch.unit.tests.js
Organization ability tests cover administrator and membership roles. Organization creation tests mock the event seam generically.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

Suggested labels: Chore, billing

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: config-agnostic test assertions and generic organization-creation seam isolation.
Description check ✅ Passed The description covers the required summary, scope, validation, guardrails, affected modules, issue link, and reviewer notes.
Linked Issues check ✅ Passed The changes satisfy #4020 by removing configuration assumptions, isolating seams, setting environment prerequisites, covering branches, and adding ability tests.
Out of Scope Changes check ✅ Passed All changes are test-only and align with #4020; the noted production findings are explicitly documented as out of scope and unchanged.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/4020-test-homogeneity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@PierreBrisorgueil

Copy link
Copy Markdown
Contributor Author

Verification detail

Full suite (bare devkit-default config) — `npm run test:coverage`:
```
Test Suites: 220 passed, 220 total
Tests: 2909 passed, 2909 total
```

Synthetic-consumer profile — the 4 files whose fix depends on config/NODE_ENV independence (`billing.quota.unit.tests.js`, `billing.unit.tests.js`, `organizations.service.silent.catch.unit.tests.js`, `organizations.abilities.unit.tests.js`), re-run under a non-dev-grade `NODE_ENV` unknown to this repo's config (no override file exists for it — matching how a downstream consumer's own project name would behave) + a strong JWT secret so the app config still boots:
```
Test Suites: 4 passed, 4 total
Tests: 94 passed, 94 total
```
This exercises rule 3 for real (an ambient NODE_ENV outside `development/test/local`) and re-confirms rule 1's config-derived plan id + the new `organizationAbilities` matrix are unaffected by it.

The remaining two files' config-agnosticism (rule 2's org-creation-seam isolation, rule 1+3's `userFacing` set/restore) is proved via in-file synthetic-config blocks (a rebuilt schema against a synthetic plan catalogue; the dedicated userFacing:true/false block) — both already included in the 220/2909 full-suite run above, since they don't depend on the outer process's ambient `NODE_ENV`.

Discoveries (out of scope for this PR, no fix applied)

  1. `billing.subscription.schema.js`'s Zod `Subscription` schema hardcodes `.default('free')` for the `plan` field instead of deriving it from `config.billing.defaultPlan` — under the bare stack these coincide, but a consumer with a different `defaultPlan` would get a schema default that silently doesn't match their configured default. Production code, so left untouched here.
  2. `billing.webhook.hardening.unit.tests.js` has the same rule-3 shaped issue as `billing.quota.unit.tests.js` (asserts `JSON.parse(payload.error)` with no NODE_ENV handling) — not one of the 5 files named in 🔧 Node test suite — config-agnostic assertions + generic extension-point isolation #4020's scope, so left untouched here.

@PierreBrisorgueil
PierreBrisorgueil marked this pull request as ready for review August 5, 2026 12:41
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.01%. Comparing base (174f3b1) to head (5b9004a).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4021      +/-   ##
==========================================
+ Coverage   93.80%   94.01%   +0.20%     
==========================================
  Files         170      170              
  Lines        5810     5810              
  Branches     1860     1859       -1     
==========================================
+ Hits         5450     5462      +12     
+ Misses        290      285       -5     
+ Partials       70       63       -7     
Flag Coverage Δ
integration 62.25% <ø> (ø)
unit 76.85% <ø> (+0.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 174f3b1...5b9004a. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…logue

Pre-push panel P1: rule 1 was applied to the two SubscriptionUpdate tests
only, but the 'Subscription schema' and 'Subscription schema — meter fields'
blocks' shared beforeEach fixtures still hardcoded plan: 'free'. Since the
schema builds its plan enum from config.billing.plans, every one of the ~15
tests riding that fixture would fail for a consumer whose catalogue lacks
'free'.

Derive a fixturePlan from the loaded config instead (defaultPlan when it's
actually a member of the catalogue, else plans[0]) and use it in both
fixtures. 'should default plan to free' is untouched — it explicitly deletes
subscription.plan before parsing, so it tests the schema's own Zod default
(currently hardcoded 'free' in production code, unrelated to this fixture)
rather than the fixture value.

Extends the synthetic-consumer-catalogue block with a test that applies the
same fixturePlan derivation to a catalogue containing no devkit-shipped plan
id at all, proving the mechanism the fixtures now rely on survives it.
@PierreBrisorgueil

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 35 minutes.

…ue fix

CodeRabbit was rate-limited on the P1 fix push, so the fallback Claude
reviewer covered it (verdict: OK with nits). Two nits addressed:
- Document the degenerate single-distinct-plan-catalogue case for
  nonDefaultPlan's `?? plans[0]` fallback — there's no code fix possible
  (no other plan exists to pick in that case), so this clarifies the
  round-trip guarantee still holds either way rather than adding branching
  that can't actually produce a better value.
- Drop the synthetic-catalogue block's afterAll(jest.resetModules()) — dead
  cleanup, it's the last describe block in the file.
@PierreBrisorgueil

Copy link
Copy Markdown
Contributor Author

Update — P1 fix from pre-push adversarial panel

The panel caught a real gap: rule 1 was applied to the two `SubscriptionUpdate` tests only, but the shared `beforeEach` fixtures in the `'Subscription schema'` and `'Subscription schema — meter fields'` blocks in `billing.unit.tests.js` still hardcoded `plan: 'free'` — ~15 tests riding that fixture would fail for any consumer whose plan catalogue lacks `'free'`.

Fixed by deriving a `fixturePlan` from the loaded config (`defaultPlan` when it's actually in the catalogue, else `plans[0]`) and using it in both fixtures. `'should default plan to free'` is untouched on purpose — it deletes `subscription.plan` before parsing, so it's testing the schema's own Zod `.default('free')` (a production-code fact, unrelated to the fixture) — flagged as a discovery above, not fixed here (no production code touched in this PR).

Extended the synthetic-catalogue block with a test that applies the exact same `fixturePlan`-style derivation to a catalogue with zero devkit-shipped plan ids, proving the mechanism survives it.

CodeRabbit was rate-limited on both the fix push and the follow-up nit push (re-prompted once, still rate-limited after another backoff wait) — per this repo's reviewer-fallback policy, a Claude-backed reviewer covered the full PR diff instead (different model family from the kimi pre-push gate). Verdict: OK with nits — both nits addressed in the follow-up commit (documented the degenerate single-plan-catalogue edge case for the `nonDefaultPlan` fallback; dropped a dead `afterAll(jest.resetModules())` cleanup).

Re-verified: full `test:coverage` green (220 suites / 2910 tests), 0 review threads, CI green.

@PierreBrisorgueil
PierreBrisorgueil merged commit 36d37cc into master Aug 5, 2026
8 checks passed
@PierreBrisorgueil
PierreBrisorgueil deleted the feat/4020-test-homogeneity branch August 5, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Tests Adding missing tests or correcting existing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🔧 Node test suite — config-agnostic assertions + generic extension-point isolation

1 participant