Skip to content

test(purchase,email): assert mock expectations in the money-path regression suite - #1249

Draft
IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-35
Draft

IAmJSD wants to merge 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-35

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

First slice for #35: the money-path regression suite (which the maintainer's comment asked to treat first) plus internal/email/coverage_test.go. The internal/api files are left for follow-up commits. They are listed below.

Refs #35

Re-count on current main (c8bdd46)

The full list of 19 files came from a review doc that isn't in the repo. I rebuilt it by running the review's scan (".On( but no AssertExpectations in the file") against the tree as of 2026-06-06, then adding money_path_regression_test.go. That gives exactly 19 files.

  • File-level (the issue's original criterion): 11 of the 19 still have no AssertExpectations at all. The other 8 now have some.
  • Test-level (the criterion that matters): a file-level hit hides gaps. money_path_regression_test.go gained some AssertExpectations calls, but TestSQSRedeliveryDoesNotDoubleExecute and TestMultiAccountPartialSuccessIsAcked still never checked theirs. To count these gaps I used a scope-aware AST scan: a test function counts when it registers a non-.Maybe() .On(...) on a local mock that is never AssertExpectations'd in its scope. That scan found 161 such test functions across 17 of the 19 files. handler_accounts_router_test.go and api/mocks_test.go have none, because their .On calls are in helpers or are .Maybe(). After this PR, 152 remain, all in internal/api.

What changed

  • internal/purchase/money_path_regression_test.go: every test (and the captureIdempotencyTokens helper) now registers t.Cleanup(func() { m.AssertExpectations(t) }) for each mock with required expectations. This covers the ordered .Once() claim and read pairs in TestSQSRedeliveryDoesNotDoubleExecute and the claim in TestMultiAccountPartialSuccessIsAcked / TestExecuteForAccount_CommittedButUnsavedIsAcked.
  • internal/email/coverage_test.go: the same change in the 3 tests that lacked it.

Expectations that never fired (test fixes, no production change)

  1. captureIdempotencyTokens (used by TestRetryReusesIdempotencyToken and TestLegacyRowFallsBackToExecutionID) registered SendPurchaseConfirmation. The code under test, processPurchaseRecommendations, never sends it; its callers do. I removed the expectation, so any call now fails the test.
  2. TestExecuteForAccount_CommittedButUnsavedIsAcked registered SendPurchaseConfirmation. executeForAccount returns AUDIT LOSS before sendPurchaseNotification when the per-account row fails to save, and the other account never commits, so no confirmation goes out on this run. I removed the expectation and added a comment explaining why.

Verification

All of this is unit/mock evidence; nothing touches a real cloud. Tests ran with AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null because the local default AWS profile is invalid.

Mutation probe (fail-before/pass-after). This is the maintainer's scenario. I patched claimAndExecute to drop the TransitionExecutionStatusPinned CAS and use an in-memory dedupe instead, so the purchase still runs exactly once and the purchaseCalls == 1 / partially_completed side-channel assertions still hold:

go test ./internal/purchase/ -run 'TestSQSRedeliveryDoesNotDoubleExecute|TestMultiAccountPartialSuccessIsAcked|TestExecuteForAccount_CommittedButUnsavedIsAcked' -count=1
  • main's test file + probe: ok (the missing CAS goes undetected)
  • this PR + probe: all 3 FAIL with FAIL: TransitionExecutionStatus(...), 6 out of 8 expectation(s) were met (Redelivery) and 6 out of 7 (the other two)
  • this PR, no probe: pass

Simply deleting the CAS (so both deliveries execute) was already caught by the existing purchaseCalls == 1 assertion. The probe above covers the case that counter misses.

Other checks:

  • go test -count=1 ./internal/purchase/ ./internal/email/ ./internal/mocks/: ok
  • go test -short -race -count=1 ./internal/purchase/ ./internal/email/: ok
  • go vet ./internal/purchase/ ./internal/email/: clean
  • gofmt -l: clean
  • golangci-lint v2.10.1 (--allow-parallel-runners) on both packages: 0 issues
  • gocyclo: only _test.go files changed, so not applicable
  • git-secrets: ran scripts/setup-git-secrets.sh in a throwaway repo with an isolated HOME and staged the two changed files plus a planted fake key. The planted key was flagged; with it removed the scan is clean. scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.

Remaining for #35 (follow-up commits/PR)

Test functions still missing AssertExpectations, by file:

File Tests
internal/api/handler_test.go 29
internal/api/handler_apikeys_test.go 24
internal/api/handler_config_test.go 22
internal/api/handler_analytics_test.go 15
internal/api/coverage_extras_test.go 11
internal/api/handler_coverage_test.go 11
internal/api/handler_users_test.go 10
internal/api/handler_groups_test.go 8
internal/api/health_test.go 6
internal/api/handler_router_test.go 4
internal/api/handler_federation_test.go 3
internal/api/handler_registrations_recipients_test.go 3
internal/api/router_authuser_test.go 3
internal/api/handler_accounts_test.go 2
internal/api/handler_purchases_guards_test.go 1

Some internal/api expectations are registered inside helpers that take no *testing.T (setupRouterForDispatch, setupAdminAuth, setupStandardUserAuth, setupScopedAuth, adminAnalyticsReq, federationHandler). Asserting those means giving the helper a t. Together they have about 140 call sites, some in files outside the 19 (handler_accounts_controlchar_test.go, handler_accounts_external_id_test.go, exchange_helpers_test.go). That should be its own commit.

Out of scope (noted, not changed)

  • Repo-wide, the same scan finds about 775 test functions with this gap outside the 19 listed files. A guard in internal/mocks (like TestNoUnfailableMockAssertions) would only be practical once those are cleared, so I didn't add one here.
  • The two related findings from the maintainer's comment are filed separately and untouched: handler_coverage_test.go asserting nothing about response bodies, and .Maybe() making AssertExpectations vacuous in internal/auth/service_apikeys*_test.go.
  • Known unrelated CI failure: govulncheck (GO-2026-6809) until fix(deps): bump Go to 1.26.10 for GO-2026-6809 #1236 merges.

Maintainer: please mirror the issue's triage labels onto this PR: impact/internal, type/chore, triaged, priority/p3, severity/low, urgency/eventually, effort/s.

🤖 Generated with Claude Code

…ession suite

money_path_regression_test.go registered ordered .Once() expectations
(two TransitionExecutionStatus claims with different outcomes, two
GetExecutionByID reads) that nothing ever verified were consumed. Its
redelivery and partial-success guards survived only on side-channel
counters, so a change that dropped the CAS claim while still running the
purchase once went unnoticed. Register t.Cleanup(AssertExpectations) for
every mock with required expectations in the file, and in the three
email/coverage_test.go tests that lacked it.

Two SendPurchaseConfirmation expectations never fired and are removed:
processPurchaseRecommendations never sends the confirmation (its callers
do), and executeForAccount returns AUDIT LOSS before notifying when the
per-account row fails to save.

Refs LeanerCloud#35

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

@IAmJSD IAmJSD added impact/internal Team-internal only type/chore Maintenance / non-user-visible triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline effort/s Hours labels Oct 22, 2026

This branch has not been deployed

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

Labels

effort/s Hours impact/internal Team-internal only priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant