Skip to content

Added comprehensive E2E snapshot tests for post analytics CSV export#26969

Open
cmraible wants to merge 2 commits intomainfrom
post-analytics-export-snapshot-tests
Open

Added comprehensive E2E snapshot tests for post analytics CSV export#26969
cmraible wants to merge 2 commits intomainfrom
post-analytics-export-snapshot-tests

Conversation

@cmraible
Copy link
Copy Markdown
Collaborator

@cmraible cmraible commented Mar 25, 2026

refs https://linear.app/ghost/issue/NY-645/add-post-analytics-export-loaderdata-stream-in-settings

Added a dedicated test file with rich fixture data covering all export code paths: email analytics (sends/opens/clicks), multiple newsletters, member feedback, signup/conversion attribution, and all access types. Includes 6 settings variation tests that snapshot different column sets when features are toggled off.

This change is in preparation for a fairly significant refactor of the post analytics export to use data streaming.

Added a dedicated test file with rich fixture data covering all export
code paths: email analytics (sends/opens/clicks), multiple newsletters,
member feedback, signup/conversion attribution, and all access types.
Includes 6 settings variation tests that snapshot different column sets
when features are toggled off.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Walkthrough

The changes modify the posts exporter service and add comprehensive end-to-end tests for post analytics CSV export functionality. The exporter service is updated to apply a default sort order of published_at DESC, id DESC when none is specified. The new test file validates the Admin posts/export endpoint behavior through multiple test cases, including response status verification, header validation, CSV body snapshot comparisons with normalized timestamps and object IDs, and conditional column visibility based on feature configurations such as newsletter feedback, email tracking, Stripe integration, and source tracking settings.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding comprehensive E2E snapshot tests for post analytics CSV export functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description directly addresses the changeset, mentioning the addition of comprehensive E2E snapshot tests for post analytics CSV export with rich fixture data covering all export code paths and settings variation tests.

✏️ 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 post-analytics-export-snapshot-tests

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.

The export query relied on the Post model's default orderDefaultRaw which
uses a CASE expression that behaves differently across MySQL and SQLite.
Added an explicit default order (published_at DESC, id DESC) to the
exporter, and pinned published_at dates on test fixture posts to avoid
timing-dependent ordering between runs.
@cmraible cmraible marked this pull request as ready for review March 26, 2026 02:46
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 (2)
ghost/core/test/e2e-api/admin/post-analytics-export.test.js (2)

187-190: Redundant afterEach - outer block already covers this.

The afterEach at line 173-175 already runs after every test, including those in nested describe blocks. This inner afterEach is redundant.

♻️ Remove redundant afterEach
     describe('Settings variations', function () {
-        afterEach(function () {
-            mockManager.restore();
-        });
-
         it('Hides email columns when members disabled', async function () {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/e2e-api/admin/post-analytics-export.test.js` around lines 187
- 190, Remove the redundant inner afterEach inside the describe('Settings
variations', function () { block that calls mockManager.restore(); the outer
afterEach already restores the mock for all nested tests, so delete the inner
afterEach containing mockManager.restore() (leave the describe and its tests
intact) to avoid duplicate teardown calls.

48-81: Add defensive check for secondNewsletter.

Line 50 accesses newsletters.models[1] without verifying at least 2 newsletters exist. If the fixture changes or runs in a different context, secondNewsletter would be undefined, causing a TypeError at line 77.

🛡️ Proposed defensive check
         const newsletters = await models.Newsletter.findAll();
         const firstNewsletter = newsletters.models[0];
         const secondNewsletter = newsletters.models[1];
         await models.Newsletter.edit({feedback_enabled: true}, {id: firstNewsletter.id});
 
         // 2. Update existing email fixtures to have rich analytics data
         // Email[0] is linked to posts[0] (HTML Ipsum), Email[1] is linked to posts[1] (Ghostly Kitchen Sink)
         const emails = await models.Email.findAll();
         if (emails.models.length >= 1) {
             await models.Email.edit({
                 email_count: 256,
                 delivered_count: 240,
                 opened_count: 180,
                 track_opens: true,
                 track_clicks: true,
                 feedback_enabled: true,
                 newsletter_id: firstNewsletter.id,
                 status: 'submitted',
                 recipient_filter: 'all'
             }, {id: emails.models[0].id});
         }
-        if (emails.models.length >= 2) {
+        if (emails.models.length >= 2 && secondNewsletter) {
             await models.Email.edit({
                 email_count: 128,
                 delivered_count: 120,
                 opened_count: 90,
                 track_opens: true,
                 track_clicks: true,
                 feedback_enabled: true,
                 newsletter_id: secondNewsletter.id,
                 status: 'submitted',
                 recipient_filter: 'status:-free'
             }, {id: emails.models[1].id});
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/e2e-api/admin/post-analytics-export.test.js` around lines 48
- 81, The test assumes two newsletters but accesses newsletters.models[1]
without checking length; update the setup to defensively verify
newsletters.models.length >= 2 before assigning secondNewsletter (or set
secondNewsletter = null) and guard any use of secondNewsletter.id in the
Email.edit block (models.Email.edit calls) so the second-email update is skipped
when a second newsletter is missing; reference newsletters, firstNewsletter,
secondNewsletter and the models.Email.edit blocks when making the changes.
🤖 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/e2e-api/admin/post-analytics-export.test.js`:
- Around line 187-190: Remove the redundant inner afterEach inside the
describe('Settings variations', function () { block that calls
mockManager.restore(); the outer afterEach already restores the mock for all
nested tests, so delete the inner afterEach containing mockManager.restore()
(leave the describe and its tests intact) to avoid duplicate teardown calls.
- Around line 48-81: The test assumes two newsletters but accesses
newsletters.models[1] without checking length; update the setup to defensively
verify newsletters.models.length >= 2 before assigning secondNewsletter (or set
secondNewsletter = null) and guard any use of secondNewsletter.id in the
Email.edit block (models.Email.edit calls) so the second-email update is skipped
when a second newsletter is missing; reference newsletters, firstNewsletter,
secondNewsletter and the models.Email.edit blocks when making the changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e2c9b326-6c7b-48d7-b06c-edb08d99cd24

📥 Commits

Reviewing files that changed from the base of the PR and between a0bd354 and 2ade949.

⛔ Files ignored due to path filters (1)
  • ghost/core/test/e2e-api/admin/__snapshots__/post-analytics-export.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • ghost/core/core/server/services/posts/posts-exporter.js
  • ghost/core/test/e2e-api/admin/post-analytics-export.test.js

const posts = await this.#models.Post.findPage({
filter: filter ?? 'status:published,status:sent',
order,
order: order ?? 'published_at DESC, id DESC',
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added a fallback ordering so the sort order of the CSV file is deterministic for snapshot tests

@cmraible cmraible requested a review from 9larsons March 26, 2026 06:24
@sonarqubecloud
Copy link
Copy Markdown

1 similar comment
@sonarqubecloud
Copy link
Copy Markdown

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