Skip to content

Removed unnecessary dones from several tests#27580

Merged
EvanHahn merged 31 commits intomainfrom
remove-useless-dones
Apr 27, 2026
Merged

Removed unnecessary dones from several tests#27580
EvanHahn merged 31 commits intomainfrom
remove-useless-dones

Conversation

@EvanHahn
Copy link
Copy Markdown
Contributor

no ref

This is a test-only change. I think it's useful on its own, but will make upcoming changes (like migrating to Vitest) a bit easier.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Walkthrough

The changes replace callback- and Promise-chain test patterns across the repo with async/await. Many unit and integration tests remove done, .then/.catch(done), and .end(done) in favor of awaiting async calls and using assert.rejects for error cases. Several controller tests now use next stubs to assert error propagation. In integration routing tests a callback-based doEnd(done) header validator was removed and centralized as assertCorrectHeaders(headers), which is called with res.headers after awaited requests. Multiple before hooks were also converted to async/await fixture setup.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: removing unnecessary done callbacks from multiple test files across the codebase.
Description check ✅ Passed The description is directly related to the changeset, explaining that this is a test-only change that removes unnecessary done callbacks to enable future migrations like Vitest.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 remove-useless-dones

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 and usage tips.

@EvanHahn EvanHahn marked this pull request as draft April 27, 2026 18:52
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ghost/core/test/unit/server/models/post.test.js (1)

635-792: 🛠️ Refactor suggestion | 🟠 Major

Destroy-action tests are now grouped under describe('Adding').

Lines 736-791 cover models.Post.permissible(..., 'destroy', ...) but live inside the describe('Adding') block. Looks like the previous describe('Destroying', ...) wrapper was removed but the inner it(...)s were kept and folded into Adding. This makes the test suite output misleading (a "destroy" failure will report under "Adding").

Please either restore the dedicated describe('Destroying', function () { ... }) wrapper or move these two tests to wherever destroy-permissions belong.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/unit/server/models/post.test.js` around lines 635 - 792, The
destroy-permission tests that call models.Post.permissible(..., 'destroy', ...)
are incorrectly nested inside describe('Adding'); restore a separate
describe('Destroying', function () { ... }) block (or move the two it(...) cases
named 'rejects if destroying a published post' and 'resolves if none of the
above cases are true') so those tests are grouped under a dedicated "Destroying"
describe; ensure the two tests retain their existing mock setup (mockPostObj,
context) and assertions around models.Post.permissible, mockPostObj.get, and
mockPostObj.related.
🧹 Nitpick comments (4)
ghost/core/test/legacy/site/dynamic-routing.test.js (1)

105-118: Redundant .expect(200) added.

Line 107 already asserts 200; the new .expect(200) on line 111 duplicates it. Drop one of them.

♻️ Proposed fix
             const res = await request.get('/tag/getting-started/')
-                .expect(200)
                 .expect('Content-Type', /html/)
                 .expect('Content-Type', /html/)
                 .expect('Cache-Control', testUtils.cacheRules.public)
                 .expect(200);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/legacy/site/dynamic-routing.test.js` around lines 105 - 118,
The test "should return HTML for valid route" contains a duplicated status
assertion: remove the redundant .expect(200) in the request chain started by
request.get('/tag/getting-started/') so the chain only asserts status 200 once
(keep the first or the last .expect(200) and delete the other) in the test
function to eliminate the duplicate assertion.
ghost/core/test/unit/server/lib/package-json/read.test.js (1)

33-33: Remove unnecessary await on synchronous removeCallback calls.

tmp.dirSync({unsafeCleanup: true}).removeCallback is a synchronous function returning void. The await is unnecessary and misleading — await packagePath.removeCallback() simply resolves to undefined immediately. Use packagePath.removeCallback(); instead, or switch to tmp.dir(...) (async API) if async cleanup is actually needed.

Applies to lines: 33, 65, 93, 129, 157, 186, 218, 246, 275, 289, 303

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/unit/server/lib/package-json/read.test.js` at line 33, The
tests call the synchronous tmp.dirSync().removeCallback via expressions like
packagePath.removeCallback() but currently use unnecessary awaits; remove the
await keywords from all calls to removeCallback (e.g.,
packagePath.removeCallback()) in the test file so the synchronous cleanup is
invoked without awaiting, or if you intended async cleanup switch to
tmp.dir(...) and await its async callback; update every occurrence listed (lines
with packagePath.removeCallback and similar tempDir.removeCallback calls)
accordingly.
ghost/core/test/unit/server/lib/image/image-size.test.js (1)

629-649: Could use assert.rejects here for consistency.

The other failure tests in this file were converted to assert.rejects(...). This try/catch block could follow the same pattern, with the additional benefit that an unexpected resolution is reported automatically:

♻️ Proposed refactor
-            let thrownError;
-            try {
-                await imageSize.getImageSizeFromStoragePath(url);
-            } catch (err) {
-                thrownError = err;
-                assertExists(err);
-                assert.equal((err instanceof errors.NotFoundError), true);
-            }
-            assert(thrownError);
+            await assert.rejects(
+                imageSize.getImageSizeFromStoragePath(url),
+                errors.NotFoundError
+            );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/unit/server/lib/image/image-size.test.js` around lines 629 -
649, Replace the manual try/catch in the "[failure] returns error if storage
adapter errors" test with assert.rejects to mirror other failure tests: call
await assert.rejects(() => imageSize.getImageSizeFromStoragePath(url), {
instanceOf: errors.NotFoundError }) (or the equivalent predicate) so the test
fails automatically on unexpected resolution and removes the
thrownError/assertExists boilerplate; keep the createImageSize setup and the
same url and storage stub, just change the assertion to use assert.rejects
against imageSize.getImageSizeFromStoragePath.
ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js (1)

129-178: Consider simplifying to match the channel.test.js pattern.

The other refactored controller tests in this PR (e.g. channel.test.js "page param too big") simply pass a sinon.stub() as next, await the controller, and assert with sinon.assert.calledWith(next, sinon.match.instanceOf(errors.NotFoundError)). That avoids the manual Promise plumbing here.

♻️ Proposed simplification
-    it('page param too big', async function () {
-        req.params.page = 6;
-
-        fetchDataStub.withArgs({page: 6, slug: undefined, limit: postsPerPage}, res.routerOptions)
-            .resolves({
-                posts: posts,
-                meta: {
-                    pagination: {
-                        pages: 5
-                    }
-                }
-            });
-
-        let called = false;
-
-        await new Promise((resolve, reject) => {
-            const next = (err) => {
-                if (called) {
-                    return;
-                }
-
-                called = true;
-
-                try {
-                    assert.equal((err instanceof errors.NotFoundError), true);
-                    sinon.assert.calledOnce(themeEngine.getActive);
-                    sinon.assert.notCalled(security.string.safe);
-                    sinon.assert.calledOnce(fetchDataStub);
-                    sinon.assert.notCalled(renderStub);
-                    sinon.assert.notCalled(ownsStub);
-                    resolve();
-                } catch (error) {
-                    reject(error);
-                }
-            };
-
-            controllers.collection(req, res, next)
-                .then(() => {
-                    if (!called) {
-                        reject(new Error('Expected callback to be called with NotFoundError.'));
-                    }
-                })
-                .catch((error) => {
-                    if (!called) {
-                        reject(error);
-                    }
-                });
-        });
-        assert.equal(called, true);
-    });
+    it('page param too big', async function () {
+        req.params.page = 6;
+
+        fetchDataStub.withArgs({page: 6, slug: undefined, limit: postsPerPage}, res.routerOptions)
+            .resolves({
+                posts: posts,
+                meta: {
+                    pagination: {
+                        pages: 5
+                    }
+                }
+            });
+
+        const next = sinon.stub();
+        await controllers.collection(req, res, next);
+
+        sinon.assert.calledOnce(themeEngine.getActive);
+        sinon.assert.notCalled(security.string.safe);
+        sinon.assert.calledOnce(fetchDataStub);
+        sinon.assert.notCalled(renderStub);
+        sinon.assert.notCalled(ownsStub);
+        sinon.assert.calledWith(next, sinon.match.instanceOf(errors.NotFoundError));
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js`
around lines 129 - 178, Replace the manual Promise plumbing in the "page param
too big" test for controllers.collection with the simpler pattern used in
channel.test.js: create a sinon.stub() for next, await
controllers.collection(req, res, nextStub), then assert that nextStub was called
with sinon.match.instanceOf(errors.NotFoundError)
(sinon.assert.calledWith(nextStub,
sinon.match.instanceOf(errors.NotFoundError))); keep the other assertions
(sinon.assert.calledOnce(themeEngine.getActive),
sinon.assert.notCalled(security.string.safe),
sinon.assert.calledOnce(fetchDataStub), sinon.assert.notCalled(renderStub),
sinon.assert.notCalled(ownsStub)) unchanged. This removes the manual called flag
and Promise resolution/rejection logic and uses the next stub to verify the
error was forwarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghost/core/test/unit/server/lib/image/image-size.test.js`:
- Line 464: Remove the unnecessary backslash escapes from the test titles that
wrap module names in backticks so the backticks render correctly; specifically,
update the three failing "it" descriptions that currently read like '[failure]
returns error if \`probe-image-size`\ module throws error' (and the similar ones
for `image-size`) by deleting the backslashes before the backticks in those
it(...) strings so they become '[failure] returns error if `probe-image-size`
module throws error' (and `image-size`) within the test file's describe/it
blocks.
- Around line 695-697: The test currently asserts only that
imageSize.getImageSizeFromStoragePath(url) rejects; update the assert.rejects
call to validate the error shape so the failure is from the expected source —
e.g., pass a second argument to assert.rejects that checks the error.message or
errorType (for example {message: /expected error text/} or {errorType:
'ExpectedErrorType'}) to the existing call to
imageSize.getImageSizeFromStoragePath in the test so it mirrors other failure
tests in this file.

---

Outside diff comments:
In `@ghost/core/test/unit/server/models/post.test.js`:
- Around line 635-792: The destroy-permission tests that call
models.Post.permissible(..., 'destroy', ...) are incorrectly nested inside
describe('Adding'); restore a separate describe('Destroying', function () { ...
}) block (or move the two it(...) cases named 'rejects if destroying a published
post' and 'resolves if none of the above cases are true') so those tests are
grouped under a dedicated "Destroying" describe; ensure the two tests retain
their existing mock setup (mockPostObj, context) and assertions around
models.Post.permissible, mockPostObj.get, and mockPostObj.related.

---

Nitpick comments:
In `@ghost/core/test/legacy/site/dynamic-routing.test.js`:
- Around line 105-118: The test "should return HTML for valid route" contains a
duplicated status assertion: remove the redundant .expect(200) in the request
chain started by request.get('/tag/getting-started/') so the chain only asserts
status 200 once (keep the first or the last .expect(200) and delete the other)
in the test function to eliminate the duplicate assertion.

In
`@ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js`:
- Around line 129-178: Replace the manual Promise plumbing in the "page param
too big" test for controllers.collection with the simpler pattern used in
channel.test.js: create a sinon.stub() for next, await
controllers.collection(req, res, nextStub), then assert that nextStub was called
with sinon.match.instanceOf(errors.NotFoundError)
(sinon.assert.calledWith(nextStub,
sinon.match.instanceOf(errors.NotFoundError))); keep the other assertions
(sinon.assert.calledOnce(themeEngine.getActive),
sinon.assert.notCalled(security.string.safe),
sinon.assert.calledOnce(fetchDataStub), sinon.assert.notCalled(renderStub),
sinon.assert.notCalled(ownsStub)) unchanged. This removes the manual called flag
and Promise resolution/rejection logic and uses the next stub to verify the
error was forwarded.

In `@ghost/core/test/unit/server/lib/image/image-size.test.js`:
- Around line 629-649: Replace the manual try/catch in the "[failure] returns
error if storage adapter errors" test with assert.rejects to mirror other
failure tests: call await assert.rejects(() =>
imageSize.getImageSizeFromStoragePath(url), { instanceOf: errors.NotFoundError
}) (or the equivalent predicate) so the test fails automatically on unexpected
resolution and removes the thrownError/assertExists boilerplate; keep the
createImageSize setup and the same url and storage stub, just change the
assertion to use assert.rejects against imageSize.getImageSizeFromStoragePath.

In `@ghost/core/test/unit/server/lib/package-json/read.test.js`:
- Line 33: The tests call the synchronous tmp.dirSync().removeCallback via
expressions like packagePath.removeCallback() but currently use unnecessary
awaits; remove the await keywords from all calls to removeCallback (e.g.,
packagePath.removeCallback()) in the test file so the synchronous cleanup is
invoked without awaiting, or if you intended async cleanup switch to
tmp.dir(...) and await its async callback; update every occurrence listed (lines
with packagePath.removeCallback and similar tempDir.removeCallback calls)
accordingly.
🪄 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: CHILL

Plan: Pro

Run ID: fc531627-09e5-42d9-ab27-ac94fd415aa9

📥 Commits

Reviewing files that changed from the base of the PR and between 94f9b73 and 7621797.

📒 Files selected for processing (27)
  • ghost/core/test/legacy/site/dynamic-routing.test.js
  • ghost/core/test/unit/frontend/helpers/comments.test.js
  • ghost/core/test/unit/frontend/helpers/get.test.js
  • ghost/core/test/unit/frontend/meta/schema.test.js
  • ghost/core/test/unit/frontend/meta/structured-data.test.js
  • ghost/core/test/unit/frontend/services/data/fetch-data.test.js
  • ghost/core/test/unit/frontend/services/routing/controllers/channel.test.js
  • ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js
  • ghost/core/test/unit/frontend/services/routing/controllers/previews.test.js
  • ghost/core/test/unit/frontend/services/rss/cache.test.js
  • ghost/core/test/unit/frontend/services/rss/generate-feed.test.js
  • ghost/core/test/unit/frontend/services/rss/renderer.test.js
  • ghost/core/test/unit/frontend/web/routers/serve-favicon.test.js
  • ghost/core/test/unit/server/data/db/backup.test.js
  • ghost/core/test/unit/server/data/exporter/index.test.js
  • ghost/core/test/unit/server/data/importer/handlers/image.test.js
  • ghost/core/test/unit/server/data/importer/index.test.js
  • ghost/core/test/unit/server/data/schema/fixtures/fixture-manager.test.js
  • ghost/core/test/unit/server/lib/image/image-size.test.js
  • ghost/core/test/unit/server/lib/package-json/read.test.js
  • ghost/core/test/unit/server/models/post.test.js
  • ghost/core/test/unit/server/models/session.test.js
  • ghost/core/test/unit/server/models/user.test.js
  • ghost/core/test/unit/server/services/permissions/can-this.test.js
  • ghost/core/test/unit/server/services/permissions/index.test.js
  • ghost/core/test/unit/server/services/permissions/providers.test.js
  • ghost/core/test/unit/server/services/url/url-service.test.js

Comment thread ghost/core/test/unit/server/lib/image/image-size.test.js Outdated
Comment thread ghost/core/test/unit/server/lib/image/image-size.test.js
@EvanHahn EvanHahn marked this pull request as ready for review April 27, 2026 18:55
EvanHahn added 26 commits April 27, 2026 14:33
@EvanHahn EvanHahn force-pushed the remove-useless-dones branch from 586a195 to fecbe0b Compare April 27, 2026 19:45
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js (1)

125-128: Prefer asserting on a captured theme stub here.

Calling themeEngine.getActive() again in the assertion bumps the stub count after you've already checked it once, which makes the test a bit brittle.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js`
around lines 125 - 128, The test currently calls themeEngine.getActive() again
in the assertion which increments the stub call count; capture the active theme
stub once after setup and reuse it in assertions (e.g. assign
themeEngine.getActive() to a variable) and then assert
theme.updateTemplateOptions.withArgs({data: {config: {posts_per_page: 3}}}) was
called and that security.string.safe was not called; update references to use
the captured stub instead of calling themeEngine.getActive() again to avoid
changing call counts and brittle behavior in controllers.collection tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js`:
- Around line 125-128: The test currently calls themeEngine.getActive() again in
the assertion which increments the stub call count; capture the active theme
stub once after setup and reuse it in assertions (e.g. assign
themeEngine.getActive() to a variable) and then assert
theme.updateTemplateOptions.withArgs({data: {config: {posts_per_page: 3}}}) was
called and that security.string.safe was not called; update references to use
the captured stub instead of calling themeEngine.getActive() again to avoid
changing call counts and brittle behavior in controllers.collection tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 73db67b7-ef9b-49f0-829d-d2d4c25cf651

📥 Commits

Reviewing files that changed from the base of the PR and between 586a195 and fecbe0b.

📒 Files selected for processing (27)
  • ghost/core/test/legacy/site/dynamic-routing.test.js
  • ghost/core/test/unit/frontend/helpers/comments.test.js
  • ghost/core/test/unit/frontend/helpers/get.test.js
  • ghost/core/test/unit/frontend/meta/schema.test.js
  • ghost/core/test/unit/frontend/meta/structured-data.test.js
  • ghost/core/test/unit/frontend/services/data/fetch-data.test.js
  • ghost/core/test/unit/frontend/services/routing/controllers/channel.test.js
  • ghost/core/test/unit/frontend/services/routing/controllers/collection.test.js
  • ghost/core/test/unit/frontend/services/routing/controllers/previews.test.js
  • ghost/core/test/unit/frontend/services/rss/cache.test.js
  • ghost/core/test/unit/frontend/services/rss/generate-feed.test.js
  • ghost/core/test/unit/frontend/services/rss/renderer.test.js
  • ghost/core/test/unit/frontend/web/routers/serve-favicon.test.js
  • ghost/core/test/unit/server/data/db/backup.test.js
  • ghost/core/test/unit/server/data/exporter/index.test.js
  • ghost/core/test/unit/server/data/importer/handlers/image.test.js
  • ghost/core/test/unit/server/data/importer/index.test.js
  • ghost/core/test/unit/server/data/schema/fixtures/fixture-manager.test.js
  • ghost/core/test/unit/server/lib/image/image-size.test.js
  • ghost/core/test/unit/server/lib/package-json/read.test.js
  • ghost/core/test/unit/server/models/post.test.js
  • ghost/core/test/unit/server/models/session.test.js
  • ghost/core/test/unit/server/models/user.test.js
  • ghost/core/test/unit/server/services/permissions/can-this.test.js
  • ghost/core/test/unit/server/services/permissions/index.test.js
  • ghost/core/test/unit/server/services/permissions/providers.test.js
  • ghost/core/test/unit/server/services/url/url-service.test.js
✅ Files skipped from review due to trivial changes (9)
  • ghost/core/test/unit/server/data/db/backup.test.js
  • ghost/core/test/unit/frontend/meta/schema.test.js
  • ghost/core/test/unit/frontend/meta/structured-data.test.js
  • ghost/core/test/unit/server/models/session.test.js
  • ghost/core/test/unit/frontend/web/routers/serve-favicon.test.js
  • ghost/core/test/unit/server/data/importer/handlers/image.test.js
  • ghost/core/test/unit/frontend/services/routing/controllers/channel.test.js
  • ghost/core/test/unit/server/services/permissions/providers.test.js
  • ghost/core/test/unit/frontend/helpers/get.test.js
🚧 Files skipped from review as they are similar to previous changes (10)
  • ghost/core/test/unit/frontend/helpers/comments.test.js
  • ghost/core/test/unit/server/services/permissions/index.test.js
  • ghost/core/test/unit/server/data/schema/fixtures/fixture-manager.test.js
  • ghost/core/test/unit/server/data/exporter/index.test.js
  • ghost/core/test/unit/server/models/user.test.js
  • ghost/core/test/unit/frontend/services/rss/generate-feed.test.js
  • ghost/core/test/unit/server/lib/package-json/read.test.js
  • ghost/core/test/unit/server/services/permissions/can-this.test.js
  • ghost/core/test/unit/server/data/importer/index.test.js
  • ghost/core/test/legacy/site/dynamic-routing.test.js

@EvanHahn EvanHahn enabled auto-merge (squash) April 27, 2026 19:57
@EvanHahn EvanHahn merged commit db6bfab into main Apr 27, 2026
42 checks passed
@EvanHahn EvanHahn deleted the remove-useless-dones branch April 27, 2026 20:10
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