Skip to content

fix(cli): classify errored provider purchase results as failures - #2204

Merged
cristim merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-1900
Oct 16, 2026
Merged

cristim merged 1 commit into
LeanerCloud:mainfrom
IAmJSD:fix/issue-1900

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Root cause

The CLI decided whether a purchase succeeded using only PurchaseResult.Success. executePurchase keeps the provider's result as-is when the separate Go error is nil, so a provider result of {Success: true, Error: non-nil} was audited as success and printed with ✅, even though the provider had reported an error.

Fix

  • Add purchaseSucceeded(result), which is Success && Error == nil.
  • Use it in purchaseAuditStatus, which both paths share, and in the success/failure diagnostics in purchaseSingleRec (the recommendation path) and logPurchaseOutcome (the CSV path).
  • The returned PurchaseResult is not mutated, so provider data stays the same (per the issue).
  • Add a newServiceClient package-var seam (defaults to createServiceClient) so the test can drive the real purchaseSingleRec path with a fake provider. Nothing changes at runtime.

Closes #1900

Verification

New test file cmd/purchase_result_classification_test.go covers four cases on both purchaseSingleRec and runOnePurchase + logPurchaseOutcome: ordinary success, Success=true with a provider error, Success=false, and a separate Go error. Each case checks the audit status and the ✅/❌ diagnostic.

Fail-before: the seam was added, the fix was not yet applied, and the commands were run on macOS.

AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null go test ./cmd -count=1 -run 'ClassifiesErroredProviderResult' -v
--- FAIL: TestPurchaseSingleRec_ClassifiesErroredProviderResultAsFailure/success_flag_with_provider_error
    expected: "error"  actual: "success";  output: "✅ ri-err"
--- FAIL: TestRunOnePurchase_ClassifiesErroredProviderResultAsFailure/success_flag_with_provider_error
    expected: "error"  actual: "success";  output: "✅ Success: ri-err"
(the other three cases PASS: existing behavior is preserved)

Pass-after, with the fix applied:

go build -o /dev/null ./cmd                      # OK
go vet ./...                                     # OK
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null go test ./... -count=1   # ok .../cmd
golangci-lint run --timeout=5m ./...             # v2.10.1: 0 issues

AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null is set only because the dev machine's ~/.aws/config default profile is invalid, which breaks some existing tests. It is unrelated to this change.

Coverage gap: this was verified only with a mocked provider (MockServiceClient). No real purchase was made and the CLI was never run with --yes. None of the pinned cloud-commitments-go AWS providers is known to return Success=true with an error today, so this is a defensive fix.

Out of scope (not changed here)

Other places still read only result.Success. They would still treat such a result as successful:

  • cmd/multi_service_stats.go (calculateServiceStats successful/failed counts)
  • cmd/interrupt.go (printInterruptedSummary skips it from the "verify in AWS" list; auditStopMessage labels it with a commitment ID)
  • cmd/multi_service_csv.go (the CSV Success column prints the raw flag)

These could switch to purchaseSucceeded in a follow-up if maintainers want summaries to match the audit status.

Maintainer: please mirror the issue's triage labels on this PR (the author lacks label permissions): triaged, priority/p2, severity/medium, urgency/this-quarter, impact/few, effort/s, type/bug.

🤖 Generated with Claude Code

…nerCloud#1900)

A provider can return PurchaseResult{Success: true, Error: non-nil}.
purchaseAuditStatus, purchaseSingleRec and logPurchaseOutcome keyed only
on result.Success, so such a result was audited as "success" and printed
with a success marker. Add purchaseSucceeded (Success && Error == nil)
and use it for the audit status and the diagnostics on both the
recommendation and CSV purchase paths. The returned PurchaseResult is
left unchanged.

Add a newServiceClient seam so the regression test drives the real
purchaseSingleRec path with a fake provider.

Closes LeanerCloud#1900

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

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 2 billable files and costs up to $0.50.

Or wait 59 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: LeanerCloud/cloud-commitments-cli/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 589cdb28-8c21-42fa-a4ed-cc06b673985d
📥 Commits

Reviewing files that changed from the base of the PR and between 817ab4b and 55249c3.

📒 Files selected for processing: 2
  • cmd/multi_service.go
  • cmd/purchase_result_classification_test.go
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Independent review verdict: CLEAN for #1900 scope, head 55249c34fbb55eddbfb85b85b04d9f10eb23d83a

An independent adversarial reviewer (did not author the change) verified:

  • purchaseSucceeded is nil-safe and doesn't mutate provider data.
  • It is used on all three status/diagnostic sites: purchaseSingleRec, runOnePurchase and logPurchaseOutcome.
  • executePurchase can't misclassify a real success.
  • The newServiceClient seam is needed to drive the real purchaseSingleRec. It is safe today because no cmd/ test uses t.Parallel.
  • Merges cleanly with main and with fix(cli): only print "CSV report written to" after a complete CSV write #2203.

Local verification on macOS (mock evidence: MockServiceClient, no real purchase, no --yes):

  • make build, go test ./... -count=1 and go vet ./... all pass.
  • Mutation: changing purchaseSucceeded to return result.Success makes both *_ClassifiesErroredProviderResultAsFailure/success_flag_with_provider_error subtests FAIL.
  • With the fix restored, 10 tests pass.

The reviewer raised one more finding. The four sites the PR body lists as out of scope still read the raw .Success:

  • the interrupt.go Ctrl-C summary and auditStopMessage;
  • the multi_service_stats.go counts;
  • the CSV column.

For a Success=true, Error!=nil result, these now disagree with the audit record. The #1900 acceptance criteria cover only audit status and diagnostics, and no provider returns this shape today, so this is tracked as non-blocking follow-up #2205.

Labels mirrored from #1900.

Not yet mergeable:

  • (1) Fork CI runs 38822167183 and 38822167189 are awaiting maintainer approval.
  • (2) The PR is still a draft. @IAmJSD, mark it ready when you're done.
  • (3) CodeRabbit hasn't reviewed it because it's a draft.

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

@cristim

cristim commented Oct 16, 2026

Copy link
Copy Markdown
Member

Merge gate met at 55249c34fbb55eddbfb85b85b04d9f10eb23d83a

Squash-merging with --match-head-commit 55249c34.

@cristim
cristim merged commit 388c014 into LeanerCloud:main Oct 16, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/few Limited audience priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/bug Defect urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(cli): classify errored provider purchase results as failures

2 participants