Skip to content

Cleaned up ghost/core db (integration) test suite noise#28909

Merged
9larsons merged 16 commits into
mainfrom
logging-discipline-db
Jul 3, 2026
Merged

Cleaned up ghost/core db (integration) test suite noise#28909
9larsons merged 16 commits into
mainfrom
logging-discipline-db

Conversation

@9larsons

@9larsons 9larsons commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Removes the bulk of the error-level log noise from the DB-backed test suites (integration / e2e), where deliberate error paths were logging hundreds of lines per run and burying real failures.

Stripe boot registrationconfigure() calls webhookManager.start() + billingPortalManager.start() on every boot, both hitting Stripe endpoints the test mock doesn't implement (webhook_endpoints, billing_portal/configurations) → 500 → error-logged. Tests dispatch webhook events directly through the mocker and never use a registered endpoint or portal configuration, so both are skipped under testEnv; prod and dev register exactly as before.

4xx request-error logging — the suites deliberately exercise hundreds of 4xx responses (validation/auth/rate-limit, asserted via expectStatus), and the request logger logs every non-404 error at error level. Added a logging:logClientErrorsAsError flag (default true; false in the testing configs) so 4xx client errors demote to warn in the test env only — prod and dev keep 4xx at error (Elastic monitoring untouched). A new log-request unit test pins the contract (5xx → error; 4xx → error by default / warn when configured; 404 → info).

image-size fetch — frontend/email meta rendering fetches external image dimensions via the image lib cache; those images are nock-blocked in tests, so every render logged "Unknown Request error." disableNetwork() now captures the real cachedImageSizeFromUrl.getImageSizeFromUrl once and swaps in a no-op (dimensions omitted — the same outcome as the blocked fetch, minus the error); a couple of tests opt back in via allowImageSize() for the two that exercise the lookup itself.

webmention discovery — publishing fixture/example post content (which links to real ghost.org subdomains and example.com) triggers real webmention discovery on every publish; nock-blocked, so it error-logged each time. disableNetwork() now mocks those known domains to return a plain 200 page (no webmention link) — same "no endpoint found" outcome a real fetch would produce, without eating a connection error.

batch-sending / automations / gift-links / offers — several tests deliberately exercise already-handled catch blocks (guard rejections, DB write failures, invalid data being filtered out) without stubbing the logger. These now stub logging.error/warn/info and assert the expected call fired, instead of letting it print.

explore-ping — the explore "phone home" ping ran on every boot, including tests where no URL is configured. Gated to dev/prod only, matching the update-check service.

email-address-service.ts — two real over-eager logging bugs, not just test noise:

  • getAddress() error-logged an invalid replyTo even when it was noreply@<default-domain> — the same address validate() already special-cases as expected-but-invalid on IP-only installs. Now skips the error in that case.
  • error-logged (rather than warned) whenever a sender's from-address didn't match the configured sending domain — the designed, routine fallback for managed-email customers, not a fault.

Sentry captureMessage crash (PLA-197) — the disabled-Sentry no-op stub only exported captureException, not captureMessage. batch-sending-service.js calls captureMessage when a sent email's recipient count drifts from what was stored, so any Ghost instance without Sentry configured (the common case) crashed there instead of no-op'ing — marking otherwise-fine sends as failed. Fixed, with a test.

domain-warming test flakiness (PLA-198) — fixing the Sentry crash exposed that two domain-warming.test.js tests were passing by accident (the crash prevented a corrected recipient count from ever being saved). Root cause: the test's fake-clock baseline was anchored to "today at noon UTC," which lands before real member-creation timestamps whenever the suite runs in the afternoon — member ObjectIds then look like they were created after the (now backdated) email, so the deliberate id-based recipient cutoff in createBatches() excluded everyone. Anchored to the next UTC midnight instead, which is always safely in the future regardless of time of day.

Intermittent SQLITE_BUSY — knex's pool allows up to 10 connections, but SQLite only supports one writer; without a busy_timeout, a second connection's write while another is in-flight failed immediately instead of waiting. Added busy_timeout = 5000 to the connection setup.

Full DB suite (test:integration, test:e2e, test:unit) passes cleanly and repeatably; no snapshot changes.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The PR adds environment guards to the explore-ping and Stripe startup services so they skip side effects outside allowed environments or during tests. It adds a config option that changes 4xx request logging between error and warning, updates the middleware and its unit tests, switches several admin and email-related tests to expect warning logs, and adds test helper support for restoring image-size lookups.

Possibly related PRs

  • TryGhost/Ghost#27822: Both PRs modify ghost/core/test/integration/services/email-service/batch-sending.test.js, touching the same concurrent retry and guard-message scenarios.

Suggested reviewers

  • acburdine
  • EvanHahn
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title matches the main theme of reducing DB-backed test suite noise, though it is a bit narrower than the full changeset.
Description check ✅ Passed The description is directly related to the changeset and summarizes the main fixes accurately.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch logging-discipline-db

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.

@nx-cloud

nx-cloud Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 90a9aab

Command Status Duration Result
nx run ghost:test:integration ✅ Succeeded 2m 47s View ↗
nx run ghost:test:ci:integration ✅ Succeeded 3s View ↗
nx run ghost:test:legacy ✅ Succeeded 2m 13s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 22s View ↗
nx run-many --target=build --projects=tag:publi... ✅ Succeeded <1s View ↗
nx run @tryghost/admin:build ✅ Succeeded 5s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 3s View ↗
nx run ghost:build:assets ✅ Succeeded 2s View ↗
Additional runs (2) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-03 18:10:44 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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/integration/services/email-service/batch-sending.test.js (1)

188-218: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Concurrent-send test stubs logging.error but never restores it.

Unlike the retry test (which explicitly calls errorLog.restore() at Line 496), this block creates the stub at Line 191 with no matching restore. If the suite does not have a global afterEach(() => sinon.restore()), this stub leaks past the test and silently swallows all logging.error output for every subsequent test in the run — which could mask real failures and undermines the very goal of this PR.

Please confirm whether a global Sinon restore hook exists in this file. If it does, this is fine and you can disregard; if not, add an explicit restore.

#!/bin/bash
# Look for global sinon restore/sandbox teardown in the test file and its shared helpers
fd -t f 'batch-sending.test.js' ghost/core/test | while read -r f; do
  echo "== $f =="
  rg -nP 'afterEach|beforeEach|sinon\.(restore|createSandbox)|\.restore\s*\(' "$f"
done

# Also check shared test setup that might auto-restore
rg -nP 'afterEach|sinon\.(restore|createSandbox)' ghost/core/test/utils -g '*.js' -C2

If no auto-restore is present, scope the stub like the retry test does:

🔒 Suggested restore for the concurrent-send stub
         for (const call of errorLog.getCalls()) {
             assert.match(call.args[0], /Tried sending email that is not pending or failed/);
         }
+        errorLog.restore();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/test/integration/services/email-service/batch-sending.test.js`
around lines 188 - 218, The concurrent-send test stubs logging.error via
sinon.stub but never restores it, so scope cleanup is needed unless a shared
afterEach restore already exists in batch-sending.test.js or its helpers. Add an
explicit restore for the errorLog stub in the same test block, matching the
pattern used by the retryEmail test with errorLog.restore(), so the stub does
not leak into later tests and suppress logging.error globally.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@ghost/core/test/integration/services/email-service/batch-sending.test.js`:
- Around line 188-218: The concurrent-send test stubs logging.error via
sinon.stub but never restores it, so scope cleanup is needed unless a shared
afterEach restore already exists in batch-sending.test.js or its helpers. Add an
explicit restore for the errorLog stub in the same test block, matching the
pattern used by the retryEmail test with errorLog.restore(), so the stub does
not leak into later tests and suppress logging.error globally.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bdff0f0f-4aaf-4e9a-9c2f-10c03f695536

📥 Commits

Reviewing files that changed from the base of the PR and between 83e6dd9 and ddfd7a2.

📒 Files selected for processing (3)
  • ghost/core/core/server/services/explore-ping/index.js
  • ghost/core/core/server/services/stripe/stripe-service.js
  • ghost/core/test/integration/services/email-service/batch-sending.test.js

@9larsons 9larsons changed the title Cleaned up DB test-suite error logging (Stripe boot + batch-sending) Cleaned up DB test-suite error-log noise (Stripe boot, 4xx, batch-sending) Jun 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/server/web/parent/middleware/log-request.test.js (1)

43-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Solid coverage of the new branching. Consider optionally adding a case for a request with no req.err (the logging.info else-branch) to pin that contract too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/test/unit/server/web/parent/middleware/log-request.test.js` around
lines 43 - 78, The log-request middleware tests cover the status-based branches,
but they do not explicitly verify the no-error path. Add a test around the
log-request middleware’s main run path to assert that when req.err is absent,
the fallback branch uses logging.info, keeping the existing status-code cases
for logging.error and logging.warn intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ghost/core/test/unit/server/web/parent/middleware/log-request.test.js`:
- Around line 43-78: The log-request middleware tests cover the status-based
branches, but they do not explicitly verify the no-error path. Add a test around
the log-request middleware’s main run path to assert that when req.err is
absent, the fallback branch uses logging.info, keeping the existing status-code
cases for logging.error and logging.warn intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72698421-d64e-4d27-b9d6-0302fc5afd0d

📥 Commits

Reviewing files that changed from the base of the PR and between ddfd7a2 and 5180aa0.

📒 Files selected for processing (15)
  • ghost/core/core/server/web/parent/middleware/log-request.js
  • ghost/core/core/shared/config/defaults.json
  • ghost/core/core/shared/config/env/config.testing-mysql.json
  • ghost/core/core/shared/config/env/config.testing.json
  • ghost/core/test/e2e-api/admin/activity-feed.test.js
  • ghost/core/test/e2e-api/admin/custom-theme-settings.test.js
  • ghost/core/test/e2e-api/admin/email-previews.test.js
  • ghost/core/test/e2e-api/admin/images.test.js
  • ghost/core/test/e2e-api/admin/key-authentication.test.js
  • ghost/core/test/e2e-api/admin/labels.test.js
  • ghost/core/test/e2e-api/admin/media.test.js
  • ghost/core/test/e2e-api/admin/members.test.js
  • ghost/core/test/e2e-api/admin/posts-legacy.test.js
  • ghost/core/test/e2e-api/admin/settings.test.js
  • ghost/core/test/unit/server/web/parent/middleware/log-request.test.js
✅ Files skipped from review due to trivial changes (1)
  • ghost/core/test/e2e-api/admin/media.test.js

@9larsons 9larsons changed the title Cleaned up DB test-suite error-log noise (Stripe boot, 4xx, batch-sending) Cleaned up DB test-suite error-log noise (Stripe boot, 4xx, image-size, batch-sending) Jun 25, 2026
9larsons added 4 commits June 25, 2026 13:36
no ref

- the DB-backed test suites logged hundreds of expected errors per run, burying real failures in stdout
- stripe-service: webhookManager/billingPortalManager.start() hit Stripe endpoints the test mock doesn't implement (webhook_endpoints, billing_portal/configurations) -> 500 -> error-logged on every boot; skip both under testEnv (tests dispatch webhook events directly via the mocker; prod/dev unchanged) — ~266 fewer lines/run
- batch-sending.test.js: two tests deliberately trip the "not pending or failed" guards; stub logging.error and assert the expected message instead of printing — 206 -> 2 lines
- explore-ping: gate the background phone-home ping to dev/prod only (matching update-check) so it doesn't run in tests
no ref

- the DB test suites deliberately exercise hundreds of 4xx responses (validation/auth/rate-limit, asserted via expectStatus); the request logger logs every non-404 error at error level, so each produced a redundant error line — ~300 per run
- added a logging:logClientErrorsAsError flag (default true; false in the testing configs) so the request logger demotes 4xx client errors to warn in the test env only — production and dev keep 4xx at error (monitored in Elastic)
- moved the logging.error stubs to warn in the 27 tests that capture+assert their 4xx request log
- added a log-request unit test pinning the contract (5xx/no-status -> error; 4xx -> error by default, warn when configured; 404 -> info)
no ref

- frontend/email meta rendering fetches external image dimensions via the image lib cache; those images are nock-blocked in tests, so every render logged "Unknown Request error" (~250 lines per run)
- disableNetwork() now captures the real cachedImageSizeFromUrl.getImageSizeFromUrl once and swaps in a no-op (dimensions omitted — same outcome as the blocked fetch, minus the error); allowImageSize() restores it
- cards.test.js opts back in for the two tests that exercise the lookup itself
no ref

- #27822 (a1ba8ea, on main after this branch was first pushed) split the
  single "Tried sending email batch that is not pending or failed" error
  into two paths: already-submitted batches now log info ("already submitted
  on a prior run; skipping"), only stuck submitting orphans still error
- the retry test for "One failed batch marks the email as failed" still
  stubbed logging.error and asserted the old message, so the assertion
  failed on the GHA merge commit even though the branch passed locally
- repoint the stub at logging.info with the new message text; same scoped
  pattern (stub for the retry call only, restore before assert)
@9larsons 9larsons force-pushed the logging-discipline-db branch from b3d0f23 to 3148b38 Compare June 25, 2026 18:39
9larsons added 7 commits July 3, 2026 09:19
- offers.test.js and newsletters.test.js still stubbed logging.error
  around 4xx assertions, so the stub never intercepted the warn-level
  call log-request.js now makes for client errors in test env, and the
  real warn line printed to stdout on every run
- the image-size cache mock's capture guard compared against the noop
  function to avoid re-capturing it, but the guard is redundant: the
  first branch (!realCachedImageSizeFromUrl) already prevents re-entry
  before the noop is ever installed
- getAddress() error-logged an invalid replyTo on every send whose
  replyTo happened to equal the default from-address (e.g.
  noreply@127.0.0.1 on an IP-only/test install) — validate() already
  special-cases this exact address as expected-but-invalid, getAddress()
  didn't. This fired on most email-related tests and would do the same
  for any self-hoster running Ghost without a real domain.
- email-event-storage.test.js's 'Ignores invalid events' and
  resume-interrupted-sends.test.js's orphan-batch test deliberately
  trigger logging.error paths but never stubbed the logger, so both
  still printed on every run
- batch-sending.test.js's 'One failed batch...' test simulates a
  Mailgun 500 without stubbing the logger either, for the same reason
…t noise

- sentry.js's disabled-Sentry stub only exported captureException, not
  captureMessage. batch-sending-service.js calls captureMessage when a
  sent email's recipient count doesn't match what was stored, so any
  Ghost instance without Sentry configured (the common case) hit a
  TypeError there instead of the intended no-op — masking the real
  underlying signal with a crash.
- fixture/example post content (fixtures.json, golden-post.json) links
  to real ghost.org subdomains. Publishing that content in tests
  triggers real webmention discovery, which fetches every external
  link; nock blocks it, so mention-discovery-service.js error-logged on
  every publish. Mock the known ghost.org subdomains to return a plain
  200 page (no webmention link) instead of blocking the connection —
  same 'no endpoint found' outcome, without eating a connection error.

Note: fixing the captureMessage crash surfaced a separate, pre-existing
issue in domain-warming.test.js (2 tests now fail where they previously
passed by accident, since the crash prevented the corrected recipient
count from ever being saved). That needs its own investigation into why
createBatches() finds 0 recipients on some multi-day warmup scenarios —
tracked separately, not fixed here.
…mismatches

- getAddress() logged at error level whenever a sender's from-address
  didn't match the configured sending domain, but that's the designed,
  routine fallback path for managed-email customers (any staff/member/
  newsletter sender not on the apex sending domain) — demoted to warn
  and reworded, matching the earlier replyTo fix in the same file.
- welcome-email-automation-poll.test.js's two deliberate send/DB-failure
  tests, gift-links.test.ts's action-recording-failure test, and
  offer-bookshelf-repository.test.js's missing-tier test all exercise
  real, already-handled catch blocks that log and continue — none
  stubbed the logger, so all four printed on every run. Stubbed and
  asserted the log fired, matching the stub-and-assert pattern already
  used elsewhere in the suite for this class of test.
baseDate anchored to 'today at 12:00 UTC', computed once at file load.
createMembers() generates member ObjectIds under the real clock before
setDay() engages sinon.useFakeTimers (which fakes the global Date that
bson-objectid reads). Whenever the suite ran after 12:00 UTC, day 0's
faked 'now' landed before the real moment members were created, so
batch-sending-service.js's deliberate id-based recipient cutoff (only
include members created before the email — createBatches()'s comment
explains this guards against imported members gaming warmup limits via
manipulated created_at) excluded every member. Anchor to the next UTC
midnight instead, which is always safely after any real timestamp from
the same run regardless of what time of day the suite executes.

This was previously masked by the Sentry captureMessage crash (see
prior commit): the crash aborted createBatches() before the corrected
(zero) recipient count was ever saved, so the stale pre-crash value
coincidentally satisfied the test's assertions while sending silently
failed underneath.
…ry mock

- connection.js's better-sqlite3 pool defaults to up to 10 connections,
  but SQLite only ever allows one writer at a time. Without a
  busy_timeout, a second connection's write while another is in
  progress fails immediately with SQLITE_BUSY ('database is locked')
  instead of waiting. Set busy_timeout so it retries internally.
  Tried restricting the pool to a single connection first (min:1,
  max:1) as a more thorough fix, but that caused a cascading test-suite
  failure under load (something in the codebase genuinely needs 2+
  concurrent connections, e.g. a transaction plus a concurrent query) —
  reverted that in favor of the safer, bounded-wait busy_timeout alone.
  Verified clean across 6+ consecutive full integration-suite runs and
  a full unit-suite run (previously intermittently flaky both ways).
- extended the webmention-discovery domain mock (added for ghost.org
  subdomains previously) to also cover example.com/www.example.com,
  the RFC 2606 reserved placeholder domain used as a stand-in external
  link across many individual tests — same 'no endpoint found' outcome
  instead of a nock-blocked connection error on every publish.
@9larsons 9larsons changed the title Cleaned up DB test-suite error-log noise (Stripe boot, 4xx, image-size, batch-sending) Cleaned up ghost/core db (integration) test suite noise Jul 3, 2026
9larsons added 5 commits July 3, 2026 11:14
- image-size no-op broke LOCAL storage-path image lookups too, not just
  the nock-blocked external fetches it targeted: cachedImageSizeFromUrl's
  getImageSizeFromUrl already branches on isLocalImage internally, and
  the no-op replaced that whole function, silently dropping dimensions
  from every locally-hosted test image. Now only no-ops non-local URLs;
  local paths delegate to the real (network-free) lookup.
- batch-sending.test.js's concurrent-retry test asserted every captured
  logging.error call matched one specific guard message. With 50
  concurrent retries and maxRetries: 0 in test config, an unrelated
  transient error elsewhere in the same window would log an Error
  object instead of a string, and assert.match would throw a TypeError
  instead of failing the assertion cleanly. Filter for the expected
  message instead, matching the skipLogs pattern already used later in
  the same file.
- stripe-service.js gated both webhookManager.start() and
  billingPortalManager.start() behind !config.testEnv, with a comment
  claiming both hit Stripe endpoints the mock doesn't implement. Only
  the billing-portal call actually did — webhookManager.start() already
  self-guards via its own mode !== 'network' check, which is always
  true outside production (config.js defaults webhookSecret to a
  placeholder). Narrowed the gate to just billingPortalManager.start()
  and corrected the comment.
- log-request.test.js stubbed config.get wholesale instead of using the
  configUtils.set/restore convention every sibling web-middleware unit
  test uses, so the real defaults.json default was never exercised.
  Converted to configUtils.
…three env-detection idioms

stripe/service.js, explore-ping/index.js, and update-check/index.js each
had their own way of asking 'am I in test/dev/prod' — a Stripe-scoped
process.env.NODE_ENV.startsWith('test'), a hardcoded env array checked
against config.get('env'), and (in update-check) the same array checked
against raw process.env.NODE_ENV instead. The last one is why
explore-ping's comment claiming parity with update-check was wrong:
config.get('env') defaults to 'development' when NODE_ENV is unset,
process.env.NODE_ENV does not, so the two diverged on an unset NODE_ENV.

Added isTestEnv()/isProductionOrDevelopment() to shared/config/helpers.js,
following the same bind-to-nconf pattern already used there for
getContentPath/isPrivacyDisabled/etc, and pointed all three call sites
at it. update-check now shares config's normal env-defaulting semantics
instead of its own raw-NODE_ENV special case.
…t, other review fixes

- The webmention-discovery domain mock lived inside disableNetwork(),
  which the unit-test lane's vitest-setup.ts also calls directly in
  every beforeAll/afterEach — so the persist()'d ghost.org/example.com
  interceptors shadowed unit tests' own nock mocks against those exact
  hosts (nock 14 matches first-registered, persisted interceptors are
  never consumed). Broke 9 tests in oembed-service.test.js and
  external-media-inliner.test.js. Extracted into its own
  mockWebmentionDiscoveryDomains(), called only from the DB lane
  (vitest-setup-db.ts's beforeAll/afterEach and mockManager.restore()),
  guarded by a flag reset on nock.cleanAll() so it doesn't re-stack a
  fresh set of persisted interceptors on every single test.
- connection.js's busy_timeout pragma was a genuine no-op: better-sqlite3
  already defaults its own  option to 5000ms internally
  (verified in its Database constructor), independent of any pragma.
  Reverted; whatever reduced the SQLITE_BUSY rate I observed was just
  run-to-run variance on an already-rare, intermittent issue — the
  comment's explanation was wrong and would've misled the next debugger.
- config.isProductionOrDevelopment() defaults to true when NODE_ENV is
  unset (env defaults to 'development'), changing update-check's prior
  behavior of bailing on a literally-unset NODE_ENV. Documented why this
  is low-impact: ghost.js (the real CLI entry point) already sets
  process.env.NODE_ENV = process.env.NODE_ENV || 'development' before
  anything else loads, so the two only diverge for non-standard
  embedders that require core modules directly.
- email-address-service.ts's replyTo carve-out compared byte-exact,
  so a case variant of the default from-address (user-entered via
  members_support_address) defeated it and kept erroring, while any
  other malformed mail:from silently lost all log signal. Lowercased
  the comparison and demoted to warn instead of fully silencing.
- gift-links.test.ts and offer-bookshelf-repository.test.js called
  errorLog.restore() after the assertion it was guarding, so a failing
  assertion left logging.error stubbed for the rest of the file (and,
  in offer-bookshelf's case, the rest of its ~15 tests). Moved cleanup
  into each file's afterEach.
- domain-warming.test.js's day-0 anchor was only 1 day past file-load
  time; a load landing right at a UTC midnight boundary left a few-
  second window where createMembers() could still tick past it.
  Anchored 2 days out instead.
The module-level webmentionDomainsMocked flag was only reset in
restore(), but five DB-lane files (webhook-request, themes, webmentions,
milestones, mentions) call nock.cleanAll() directly, bypassing restore().
In that path the interceptors were wiped but the flag stayed true, so
the per-test re-registration in vitest-setup-db.ts's afterEach silently
no-op'd — exactly the case that afterEach exists to cover — and the
webmention mocks stayed gone for the rest of that worker until some
other file happened to call restore().

Replaced the flag with a check against nock.activeMocks() so the guard
reflects nock's actual live state instead of a boolean that can drift
out of sync with it.
@9larsons 9larsons enabled auto-merge (squash) July 3, 2026 18:03
@9larsons 9larsons disabled auto-merge July 3, 2026 18:03
@9larsons 9larsons enabled auto-merge (squash) July 3, 2026 18:03
@9larsons 9larsons merged commit 76d941c into main Jul 3, 2026
43 checks passed
@9larsons 9larsons deleted the logging-discipline-db branch July 3, 2026 18:15
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