Skip to content

TEST-03: 19 Go test files register .On() expectations without mock.AssertExpectations, incl. money-path regression suite #35

Open

Description

@cristim

Severity: P3. Confidence: high. Informational.

Affected files:

  • internal/purchase/money_path_regression_test.go:232
  • internal/purchase/money_path_regression_test.go:241
  • internal/api/handler_test.go
  • internal/api/handler_config_test.go
  • internal/api/handler_coverage_test.go
  • internal/api/handler_apikeys_test.go:40
  • internal/api/coverage_extras_test.go
  • internal/email/coverage_test.go
    (19 files total)

Evidence:
A repo-wide scan finds 19 files with .On( but no AssertExpectations. TestSQSRedeliveryDoesNotDoubleExecute wires paired ordered .Once() expectations but never verifies both were consumed, so a regression where the second delivery short-circuits without attempting the CAS goes undetected at the mock layer (partially guarded by a purchaseCalls counter). The project's own feedback_mock_assert_expectations rule is applied consistently in internal/auth but not these files.

Impact:
Code paths can silently stop calling collaborators while tests stay green; the weakest guarantee sits precisely on the SQS-redelivery double-buy regression test.

Recommendation:
Add t.Cleanup(func() { m.AssertExpectations(t) }) at mock construction (or a shared newMockX(t) constructor), prioritizing money_path_regression_test.go and the api auth/permission handler tests.

Source: docs/reviews/codebase-review-2026-06-10.md (automated multi-dimension code review, adversarially verified for P1/P2)

Activity

  1. cristim commented on Jul 28, 2026

    @cristim
    MemberAuthor

    Reviewed commit: be11bdcb5. Note: origin/main moved to 3e9660d06 during the review; re-verify against current main before changing code, since a finding may have been fixed or moved.

    Confirmed still present. The 2026-07-28 full review re-derived this independently and adds the whole-file count for the money-path file plus a sharper failure scenario. Suggest treating internal/purchase/money_path_regression_test.go as its own first-class item rather than one of nineteen, and raising the priority accordingly.

    Where

    • internal/purchase/money_path_regression_test.go - whole file: 29 .On(...) registrations, zero AssertExpectations. The .Once() expectations at the TestSQSRedeliveryDoesNotDoubleExecute and TestMultiAccountPartialSuccessIsAcked setups are the load-bearing ones.

    What

    This is the double-spend guard file for LeanerCloud/cloud-commitments-cli#1012 / LeanerCloud/cloud-commitments-cli#1013 / LeanerCloud/cloud-commitments-cli#1014. It registers ordered .Once() expectations - two GetExecutionByID, two TransitionExecutionStatus with different outcomes - that encode the exact call sequence the test claims to prove. Nothing ever verifies they were consumed.

    Failure scenario

    The tests currently survive on side-channel assertions: purchaseCalls == 1 and savedRoot.Status == "partially_completed". Suppose a refactor made handleExecutePurchase return early before the compare-and-swap, never calling TransitionExecutionStatus at all. The unconsumed .Once() expectations go unreported, and both side-channel assertions can still hold. On the money path, the absence of the atomic claim is precisely the double-spend bug: without the CAS, a redelivered SQS message is free to execute a second purchase.

    So the file that exists to prove "a redelivery cannot double-execute" cannot detect the removal of the mechanism that prevents it. That is a different severity from the general hygiene case: for the other eighteen files a missing AssertExpectations weakens coverage; here it removes the only proof of the invariant the file is named after.

    Suggest splitting this file out at priority/p2 / severity/medium (or handling it first within this issue) and leaving the remaining eighteen at P3, rather than letting the money-path file inherit the batch's "informational" rating.

    Fix direction

    As already recommended here, with the specific placement:

    t.Cleanup(func() {
        mockStore.AssertExpectations(t)
        mockServiceClient.AssertExpectations(t)
    })

    in each test in the file. Mandatory for any test whose proof is "this call happened exactly once".

    Verify the right way round: delete the CAS from handleExecutePurchase, confirm the tests now fail, then restore. If they still pass, the fix is not done.

    Related

    • Two separate findings from the same review touch this issue's surface and are filed as their own issues, since TEST-03: 19 Go test files register .On() expectations without mock.AssertExpectations, incl. money-path regression suite #35's fix would not resolve them:
      • internal/api/handler_coverage_test.go (listed here) also asserts nothing about response bodies, so adding AssertExpectations alone leaves a handler returning a structurally wrong response passing.
      • internal/auth/service_apikeys_test.go:513-521 and service_apikeys_api_test.go:415-423 do call AssertExpectations, but .Maybe() makes it vacuous, so they need the opposite change (dropping .Maybe() and synchronising on a channel).
  2. IAmJSD commented on Oct 22, 2026

    @IAmJSD
    Collaborator

    Claiming: working on this

  3. self-assigned this
    on Oct 22, 2026
  4. IAmJSD commented on Oct 28, 2026

    @IAmJSD
    Collaborator

    Status update (2026-10-28): #1570 merged: internal/purchase tests now assert their mock expectations (a cleanup-based helper in the shared setups), with mutation probes showing dropped claims, history writes and emails now fail tests.

    Left: the same treatment for internal/api (the largest gap), internal/auth, internal/scheduler and internal/server.

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions