fix(test): skip per-suite migration re-runs via globalSetup pre-warm#3549
fix(test): skip per-suite migration re-runs via globalSetup pre-warm#3549PierreBrisorgueil merged 3 commits intomasterfrom
Conversation
Run migrations once in jest.globalSetup (before any vm context) and set process.env.DEVKIT_MIGRATIONS_RAN='1'. bootstrap() checks this flag and skips migrations.run() on 2nd+ suite bootstraps. Why process.env set in globalSetup works: jest --experimental-vm-modules resets any process.env key that was set INSIDE a test-file vm context on teardown. Keys present BEFORE the first vm.createContext() call are included in jest's protectProperties() sweep and survive all per-suite GlobalProxy teardowns unchanged. globalSetup runs before any vm context is created — exactly that window. Each test suite still gets a fresh Express app and mongoose connection so per-suite module state (e.g. config.sign.up mutations) stays isolated. Impact: - Migrations runs: 21 → 1 per full test run (18 skips × ~100ms ≈ 1.8s saved) - Heap peak devkit: 1.43 GB (unchanged — jest module-registry accumulation dominates; migrations overhead is timing, not heap) - All 1234 devkit tests green Fixes the bootstrap accumulation root cause for downstream projects with larger test suites (trawl: 89 suites × full migrations scan each = 8.9s and additional file-read heap accumulation under --experimental-vm-modules).
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
WalkthroughThe changes implement a two-phase migration strategy where Jest's global setup runs migrations once during test initialization and signals completion via an environment variable, enabling per-suite bootstrap calls to skip redundant migrations. Changes
Sequence Diagram(s)sequenceDiagram
participant Jest Global Setup
participant Database
participant Migrations
participant Bootstrap (per suite)
Note over Jest Global Setup,Bootstrap (per suite): Jest Initialization (Global Setup)
Jest Global Setup->>Database: Phase 1: connect
Jest Global Setup->>Database: dropDatabase
Jest Global Setup->>Database: disconnect
Jest Global Setup->>Database: Phase 2: connect
Jest Global Setup->>Migrations: Load model files
Jest Global Setup->>Migrations: migrations.run()
Jest Global Setup->>Jest Global Setup: Set DEVKIT_MIGRATIONS_RAN='1'
Jest Global Setup->>Database: disconnect
Note over Jest Global Setup,Bootstrap (per suite): Per-Suite Execution
Bootstrap (per suite)->>Bootstrap (per suite): Check DEVKIT_MIGRATIONS_RAN
alt Flag is set
Bootstrap (per suite)->>Bootstrap (per suite): Skip migrations.run()
else Flag not set
Bootstrap (per suite)->>Migrations: migrations.run()
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 0/1 reviews remaining, refill in 31 minutes and 52 seconds.Comment |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Security | 2 critical |
🟢 Metrics -2 duplication
Metric Results Duplication -2
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
The PR is currently not up to standards due to new security issues and missing verification for core logic changes. While the architectural move to optimize migrations in globalSetup is beneficial, the implementation in scripts/jest.globalSetup.js introduces potential path traversal vulnerabilities and contains redundant database operations. Additionally, there are significant gaps in testing; specifically, the logic in lib/app.js that skips or executes migrations based on the new environment flag is completely unverified. These security and reliability concerns should be resolved prior to approval.
About this PR
- This PR is currently flagged as not up to standards. The introduction of unsafe dynamic imports and the lack of unit tests for the conditional migration logic in lib/app.js are the primary blockers.
Test suggestions
- Verify that globalSetup runs migrations and sets the persistent environment flag.
- Verify that globalSetup handles migration failures gracefully and still disconnects from the database.
- Verify that bootstrap() skips migrations when the DEVKIT_MIGRATIONS_RAN flag is set.
- Verify that bootstrap() runs migrations as a fallback when the flag is absent.
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify that bootstrap() skips migrations when the DEVKIT_MIGRATIONS_RAN flag is set.
2. Verify that bootstrap() runs migrations as a fallback when the flag is absent.
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3549 +/- ##
==========================================
+ Coverage 87.79% 87.87% +0.08%
==========================================
Files 128 128
Lines 3589 3589
Branches 1054 1054
==========================================
+ Hits 3151 3154 +3
+ Misses 347 345 -2
+ Partials 91 90 -1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Optimizes Jest integration test runtime under --experimental-vm-modules --runInBand by pre-running DB migrations once in globalSetup and letting per-suite bootstrap() skip redundant migration runs via an env flag.
Changes:
- Add a migrations pre-run phase to
scripts/jest.globalSetup.jsand setprocess.env.DEVKIT_MIGRATIONS_RAN='1'on success. - Update
lib/app.jsbootstrap()to skipmigrations.run()when the env flag is present. - Update unit tests to reflect the new two-phase globalSetup behavior (drop DB + migration pre-run).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| scripts/jest.globalSetup.js | Adds a second globalSetup phase to connect/load models/run migrations once and set the env flag. |
| lib/app.js | Skips per-suite migrations when the env flag indicates they were already run in globalSetup. |
| scripts/tests/jest.globalSetup.unit.tests.js | Adjusts assertions/docs for the new two-phase globalSetup behavior. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/app.js`:
- Around line 73-75: The current check skips migrations whenever
process.env.DEVKIT_MIGRATIONS_RAN === '1', which can inadvertently bypass
migrations in non-test environments; update the conditional in lib/app.js to
only short-circuit migrations when both process.env.DEVKIT_MIGRATIONS_RAN ===
'1' and process.env.NODE_ENV === 'test' so that migrations.run() is still
executed in normal starts (reference the DEVKIT_MIGRATIONS_RAN check and the
migrations.run() call to locate the change).
In `@scripts/tests/jest.globalSetup.unit.tests.js`:
- Around line 113-117: The test assertion for the second connect attempt is too
weak; update the expectation on the mocked connect to assert at least two calls
so the retry path is verified. In the test around globalSetup(), replace the
loose check using connect.mock.calls.length >= 1 with a stricter assertion
(e.g., expect(connect.mock.calls.length).toBeGreaterThanOrEqual(2) or
toHaveBeenCalledTimes(2)) to prove the Phase 2 reconnect actually ran; leave the
other assertions for dropDatabase and resolves as-is.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c1e7c6d6-4a5c-4aea-8b8f-3d103d957cd3
📒 Files selected for processing (3)
lib/app.jsscripts/jest.globalSetup.jsscripts/tests/jest.globalSetup.unit.tests.js
- Scope DEVKIT_MIGRATIONS_RAN skip to NODE_ENV=test in bootstrap() to prevent accidental migration bypass in prod/dev (CodeRabbit major) - Update JSDoc @returns to Promise<{db, app}> (Copilot) - Add explicit Array.isArray guard for config.files.mongooseModels in globalSetup Phase 2 instead of relying on TypeError for control flow - Pass config.db.options to mongoose.connect() in Phase 2 for SSL/auth parity with mongooseService.connect (Copilot) - Use pathToFileURL for ESM dynamic imports (Codacy path-traversal fix) - Tighten Phase 2 reconnect assertion from >=1 to >=2 + assert disconnect called once (CodeRabbit minor + Copilot) - Add files.mongooseModels to mocks so Phase 2 is exercised in tests
Bootstrap no longer re-runs migrations.run() per suite (DEVKIT_MIGRATIONS_RAN short-circuits it after globalSetup), so the claim path, duplicate-key handling, and unclaim-on-error branches lost coverage. Add unit tests that spy on migrationRepository to exercise: - claimMigration E11000 → returns false (concurrent runner already claimed) - claimMigration non-duplicate error → rethrows - runMigration: import failure → unclaim + rethrow - runMigration: missing up() export → unclaim + rethrow - run(): all migrations already executed branch Restores migrations.js coverage from ~55% back to ~82%.
…gistry growth (#3550) Adds `workerIdleMemoryLimit: '512MB'` to jest.config.js. Jest forks worker child processes that accumulate per-suite vm-context module registry under `--experimental-vm-modules` (~14 MB retained per loaded suite). On large downstream test suites this leaks until the worker OOMs. The flag tells jest to inspect each worker's RSS between suites and respawn when it exceeds the threshold. Respawn cost (~150-300 ms) is negligible vs. the OOM crash it avoids on long-running coverage runs. This complements PR #3549 (migration pre-warm) which addressed migration overhead but not the vm-context module registry growth.
Summary
--experimental-vm-modules --runInBand, jest creates a freshvm.createContext()per test file. Every suite callingbootstrap()re-ranmigrations.run()(glob scan + DB round-trip, ~100ms × 18 suites = 1.8s wasted). For downstream projects with 89 suites (trawl), this multiplies further.globalThissingleton,processproperty singleton, andprocess.envflag set inside vm contexts — all cleaned up by jest'sGlobalProxy.clear()on suite teardown. The only anchor that survives isprocess.envkeys set before any vm context is created (i.e., inglobalSetup), which land in jest'sprotectProperties()protected set.jest.globalSetup.js(before vm contexts exist) and setprocess.env.DEVKIT_MIGRATIONS_RAN='1'.bootstrap()checks this flag and skipsmigrations.run()on 2nd+ calls. Each suite still gets a fresh Express app + mongoose connection for full test isolation.Impact
Why not a full bootstrap singleton?
Explored and rejected:
globalThisper vm context — each test file gets a freshglobalThis; property isundefinedfor next suiteprocess.__propertyset inside vm context — cleaned byGlobalProxy.clear()on teardownprocess.envkey set inside vm context — also cleaned (jest'sdeletePropertiesonprocess.env)globalSetupbootstrap + shared Express app — breaks tests that mutateconfig.*module-level state (e.g.config.sign.up = falsein auth tests) because the route handlers use globalSetup's module instance ofconfig, not the test vm context's instanceTest plan
jest.globalSetup.unit.tests.jsupdated to reflect 2-phase connect/disconnect behavior--logHeapUsageSummary by CodeRabbit
Tests
Chores