Skip to content

fix(tests): de-flake the ads short-code serving contract - #184

Closed
ralyodio wants to merge 1 commit into
masterfrom
fix/deflake-ads-short-code-serving
Closed

ralyodio wants to merge 1 commit into
masterfrom
fix/deflake-ads-short-code-serving

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Neither #179 nor #183 is broken. Both were red on the same file — tests/contract/ads-short-code-serving.test.ts — but on different tests, and neither PR touches ads. The file is flaky on master.

Cause

serveAd diverts HOUSE_AD_ROTATION_RATE (~10%) of otherwise-fillable requests to the house ad (lib/ads/serve.ts:213). A house fill is unmetered: no impression row, so no short code and no /a/ click URL.

Four of the five tests read fields off whatever came back, so each failed ~10% of the time:

PR Test that drew the house ad Symptom
#183 addresses the click by short code… .toMatch() expects a string, but got undefined
#179 records the publisher surface tag… Cannot read properties of undefined (reading 'src')

Measured on clean master: 6 of 15 runs failed, across four different tests — roughly one CI run in three.

Fix

These tests are about the click URL of a paid fill, so the draw is pinned above the threshold in beforeEach.

Fixing Math.random is safe precisely here, and the reasons are worth stating because they wouldn't hold elsewhere:

  • the auction has a single candidate, so a fixed draw picks the same winner a random one would;
  • short codes come from crypto.randomBytes, not Math.random — so "issues a distinct code per paid fill" still exercises real entropy, and now checks all 60 fills instead of a subset that silently shrank whenever the house ad turned up.

Pinning the draw would have deleted the only coverage of the rotation, so it's now asserted directly instead of statistically — one test for a draw under the rate, one for a draw exactly on it (the comparison is <, so the rate itself must stay paid). The boundary is the interesting part, and sampling the rate would just be a slower way to reintroduce a flaky test.

Verification

  • 40 consecutive runs of the file pass, against 9 of 15 before.
  • Full suite: 1259 passed, 7 skipped. tsc --noEmit clean.
  • Checked the other two files that call serveAd (ads-campaign-status, ads-self-deal) — stable across 30 runs, so this was the only one exposed.
  • Test count 5 → 7; the file also got ~3.5× faster (1295ms → 369ms), since it no longer retries through house fills.

The same commit is cherry-picked onto feature/careers-widget and fix/docs-index-stats-tracker so both builds go green now; it merges cleanly either way, since both sides carry an identical change.

🤖 Generated with Claude Code

serveAd diverts HOUSE_AD_ROTATION_RATE (~10%) of otherwise-fillable
requests to the house ad, which is unmetered: no impression row, so no
short code and no /a/ click URL. Four of the five tests in this file read
fields off whatever came back, so each failed on ~10% of runs — about one
CI run in three went red, on whichever test happened to draw the house ad.

Measured on master before this change: 6 of 15 runs failed, across four
different tests. That is what took down the builds on #179 and #183,
neither of which touches ads.

These tests are about the click URL of a *paid* fill, so the draw is
pinned above the threshold. Fixing Math.random is safe precisely here:
the auction has a single candidate either way, and short codes come from
crypto.randomBytes, so "issues a distinct code per paid fill" still
exercises real entropy — and now checks all 60 fills instead of a subset
that silently shrank whenever the house ad turned up.

Pinning the draw would have deleted the only coverage of the rotation, so
it is asserted directly rather than left to chance: one test for a draw
under the rate, one for a draw exactly on it. The boundary is the
interesting part, and sampling the rate would just be a slower way to
reintroduce a flaky test.

40 consecutive runs of the file now pass, against 9 of 15 before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

45 finding(s)

HIGH/CRITICAL: 10 | MEDIUM: 35

Severity Rule Location
HIGH secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
HIGH js-ssrf-outbound-request lib/sp/platforms/facebook.ts:115
HIGH secret-generic-credential lib/sp/platforms/linkedin.ts:25
HIGH js-ssrf-outbound-request lib/sp/platforms/telegram.ts:63
HIGH js-ssrf-outbound-request lib/sp/platforms/threads.ts:138
HIGH manifest-typosquat package.json:59
HIGH secret-generic-credential tests/contract/coinpay.test.ts:4
HIGH secret-generic-credential tests/contract/posthog-integration.test.ts:13
HIGH secret-generic-credential tests/lead-campaign.test.ts:16
MEDIUM js-unescaped-html-sink app/(app)/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM sql-template-interpolation app/(app)/projects/[id]/autoblog/actions.tsx:96
MEDIUM js-unescaped-html-sink app/(app)/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM sql-template-interpolation app/(app)/projects/[id]/autoblog/setup/form.tsx:504
MEDIUM sql-template-interpolation app/(app)/projects/[id]/uptime/monitor-actions.tsx:28
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM sql-template-interpolation app/actions/admin.ts:114
MEDIUM sql-template-interpolation app/actions/orgs.ts:328
MEDIUM sql-template-interpolation app/api/lx/keywords/regenerate/route.ts:59
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM sql-template-interpolation lib/audit/checks/security.ts:48
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:130
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:93
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:367
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:379
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:380
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:1340
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:1362
MEDIUM sql-template-interpolation lib/lx/guestPostGen.ts:109
MEDIUM tls-verification-disabled lib/onion.ts:47
MEDIUM sql-template-interpolation lib/sp/platforms/linkedin.ts:177
MEDIUM sql-template-interpolation scripts/delete-archived-projects.mjs:97
MEDIUM sql-template-interpolation scripts/delete-archived-projects.mjs:102
MEDIUM sql-template-interpolation scripts/lx-republish-todays-articles.mjs:102

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio

ralyodio commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded. #183 landed an equivalent de-flake (mocking HOUSE_AD_ROTATION_RATE to 0 for that file) while this was open, and it's now on master — so the fix here is redundant, and its version of the file would have conflicted.

The one piece worth keeping is the rotation coverage that pinning the rate removes; that's split out into its own PR against current master, which touches no shared lines.

@ralyodio ralyodio closed this Aug 3, 2026
@ralyodio
ralyodio deleted the fix/deflake-ads-short-code-serving branch August 3, 2026 15:12
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