Skip to content

Fix backend test suite failures#2137

Open
trillium wants to merge 7 commits intochore/backend-esm-migrationfrom
fix/backend-test-suite
Open

Fix backend test suite failures#2137
trillium wants to merge 7 commits intochore/backend-esm-migrationfrom
fix/backend-test-suite

Conversation

@trillium
Copy link
Copy Markdown
Member

Fixes #2136

Depends on: #2134 (ESM migration) must be merged first. This PR is based on that branch.

What changes did you make and why did you make them ?

The ESM migration exposed several test failures. Here is every failure that was addressed:

Test Failure Root cause Fix
models/event.test.js — "Cannot save simple data" Error: No test found in suite Empty describe block with no it() calls. Jest silently skipped it; Vitest fails on it. Deleted the empty describe block (leftover scaffolding).
models/user.test.js — "Create a simple user" TypeError: Cannot read properties of undefined Migration agent rewrote the test incorrectly: used User.find()[0] instead of User.findOne({ email }), and asserted on fields that weren't set. Combined with an un-awaited deleteMany() in setup-test.js causing a race condition. Aligned test logic with the development branch version. Added await to deleteMany() in setup-test.js.
models/user.test.js — "should serialize user data correctly" expected undefined to be true Test asserted isActive, which doesn't exist on the User model schema. Removed the isActive assertion and property from test data.
models/user.test.js — "should fail if accessLevel is not in enum" promise resolved instead of rejecting Test expected accessLevel to have an enum constraint, but the User model defines it as { type: String } with no enum. Deleted the invalid test.
routers/auth.router.test.js — all 7 tests Invalid value "undefined" for header "x-customrequired-header" process.env.CUSTOM_REQUEST_HEADER is undefined without a .env file. Under CJS/Jest, the authorizationUtils ESM import error crashed these tests before they reached this point, masking the issue. Set the env var in each test file using vi.hoisted() so it's available before ESM imports evaluate.
routers/events.router.test.js — all 5 tests Same as above Same as above Same fix.
routers/projects.router.test.js — all 4 tests Same as above Same as above Same fix.
routers/users.router.test.js — all 6 tests Same as above Same as above Same fix.
controllers/project.controller.test.js MongoNetworkError: connection closed (unhandled rejection) mongoose.connect() mixed await with a callback (making await a no-op), and mongoose.connection.close() didn't force-kill pending operations. Removed callback from mongoose.connect(), changed to mongoose.connection.close(true).
config/auth.config.test.js Skipped (test.skip) Test only checked that REACT_APP_PROXY matched BACKEND_PORT from .env — no code logic tested. assert-env already validates required env vars at startup. Deleted the tautological test.

Result: 40 tests passing, 0 skipped, 0 failures (up from 70 passing, 4 failing, 1 skipped under Jest on development).

Screenshots of Proposed Changes Of The Website (if any, please do not screen shot code changes)

N/A — no visual changes, backend only.

Vitest fails on empty test suites, unlike Jest which silently skips them.
This block had no test cases and was leftover scaffolding.
…y in setup

The ESM migration incorrectly rewrote user.test.js test logic.
Restored findOne queries and correct assertions from development branch.
Added missing await to deleteMany() in setup-test.js to prevent race conditions.
…ting

ESM evaluates all imports before setup files run, so assertEnv() in app.js
throws before dotenv can populate process.env. Mock it like the worker modules.
Remove callback from mongoose.connect() so await works correctly.
Force-close mongoose connection in afterAll to prevent teardown errors.
Router tests need this env var to test the custom header middleware.
Set it directly in each test file rather than relying on a global .env file.
Remove isActive assertion (field doesn't exist on User model) and
accessLevel enum test (model has no enum constraint on this field).
This test only checked that REACT_APP_PROXY matched BACKEND_PORT from
the .env file — no code logic tested. assert-env already validates
required env vars at startup.
@trillium trillium added Bug role: Back End housekeeping Non-user story development task labels Apr 28, 2026
@trillium trillium added this to the 06.01 Infrastructure milestone Apr 28, 2026
@trillium trillium added size: 2pt Can be done in 7-12 hours complexity: medium labels Apr 28, 2026
@trillium
Copy link
Copy Markdown
Member Author

Code Review: Fix backend test suite failures

1. setup-test.js Changes

await collection.deleteMany() (line 14) -- Correct and important fix. Without await, deleteMany() returns a promise that resolves asynchronously, creating race conditions where data from a previous test leaks into the next one. This was likely the root cause of the user.test.js "Create a simple user" flakiness.

mongoose.connection.close(true) (line 66) -- Correct. The true parameter forces the connection closed even if there are pending operations. Without it, afterAll can hang waiting for in-flight operations to complete, which is exactly what the MongoNetworkError: connection closed symptom in the PR description suggests. Good fix.

Callback removal from mongoose.connect() -- Correct and critical. When mongoose.connect() receives a callback, it returns undefined instead of a Promise, making the await a no-op. The connection would start but beforeAll would resolve before it finished, causing tests to run against an unconnected database. The try/catch replacement preserves error handling while making await work as intended.

2. Router Test vi.hoisted() Pattern

Using vi.hoisted() to set process.env.CUSTOM_REQUEST_HEADER before ESM imports is the correct Vitest pattern. In ESM, all import statements are hoisted and evaluated before any other module-level code. vi.hoisted() runs before those hoisted imports, so process.env.CUSTOM_REQUEST_HEADER is available when app.js evaluates. No timing issues here -- this is the idiomatic Vitest solution.

The change from CONFIG.CUSTOM_REQUEST_HEADER / CONFIG_AUTH.CUSTOM_REQUEST_HEADER to process.env.CUSTOM_REQUEST_HEADER is also correct. The config module (auth.config.js) just reads process.env.CUSTOM_REQUEST_HEADER itself, so this removes an unnecessary indirection and avoids importing a module that exposes a hardcoded SECRET in test contexts.

3. assert-env Mock

vi.mock('assert-env', () => ({ default: () => {} })) -- Correct. The assert-env package uses module.exports = assertion (CJS). When imported via import assertEnv from 'assert-env' in ESM, Vitest maps the CJS export to { default: assertion }. The mock correctly provides a default property with a no-op function. This prevents assertEnv() in app.js from throwing when env vars like SLACK_OAUTH_TOKEN etc. are missing in the test environment.

4. Deleted Tests

config/auth.config.test.js -- Already test.skip'd. It only verified that REACT_APP_PROXY contained BACKEND_PORT, which is a .env file tautology, not a code logic test. No value lost.

accessLevel enum test (removed from user.test.js) -- Correct deletion. The User model defines accessLevel as { type: String, default: "user" } with no enum constraint. The test was asserting behavior the model doesn't implement. If enum validation is desired in the future, it should be added to the model first, then tested.

5. user.test.js Rewrite

findOne vs find -- Using User.findOne({ email }) instead of User.find()[0] is more reliable and doesn't depend on insertion order. Good improvement.

Removed githubHandle from test data -- Acceptable. The field is optional and defined (twice, actually -- line 22 and 29 of user.model.js, which is a pre-existing issue). Not testing it here is fine since the "Create and Read" tests focus on core fields.

Serialization test -- The assertions align with the serialize() method defined in user.model.js. One subtlety to note: the serialize() method has what appears to be a pre-existing bug where currentJobTitle returns this.currentRole and desiredJobTitle returns this.desiredRole instead of this.currentJobTitle and this.desiredJobTitle. The test wisely avoids asserting on currentJobTitle and desiredJobTitle from the serialized output, which means it won't break, but it also won't catch this existing bug. Consider filing a separate issue for that model bug.

Unique email validation test -- Correct. Uses rejects.toMatchObject({ code: 11000 }) which is the MongoDB duplicate key error code. This is a valid integration test.

6. Potential Issues

Minor: users.router.test.js env var timing -- The diff only adds vi.hoisted() but doesn't change how the header value is used. Line 9 reads const backendHeaders = process.env.CUSTOM_REQUEST_HEADER at module scope. This works correctly because vi.hoisted() runs before all imports AND before module-level code in the test file. Just confirming this is fine.

No issues found with flaky test potential. The await deleteMany() fix specifically addresses the most common source of test flakiness (inter-test data leakage), and the mongoose.connection.close(true) prevents hanging teardown.

Overall Assessment

This is a well-structured PR with clear commit messages and an excellent diagnostic table in the description. Every fix addresses a real, identified root cause rather than papering over symptoms. The changes are minimal and surgical. The test rewrites align with the actual model schemas. No concerns -- this is ready to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug complexity: medium housekeeping Non-user story development task role: Back End size: 2pt Can be done in 7-12 hours

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant