diff --git a/tools/budget.go b/tools/budget.go index 4e9a6c2..f71f1ae 100644 --- a/tools/budget.go +++ b/tools/budget.go @@ -87,11 +87,15 @@ func (l *budgetLedger) committedUSD() float64 { // purchaseSpent reports whether a purchase that reached PurchaseCommitment // must stay counted. Only a clean no-op (nil error, no success, no error) or -// an adopted existing commitment is released; every error and any other +// an adopted existing commitment returned with no error is released; every +// error (err or result.Error), including one returned beside ExistingCommitment, and any other // result may have spent money, so it stays counted. func purchaseSpent(result common.PurchaseResult, err error) bool { + if err != nil || result.Error != nil { + return true + } if result.ExistingCommitment { return false } - return err != nil || result.Success || result.Error != nil + return result.Success } diff --git a/tools/budget_test.go b/tools/budget_test.go index c5f4a96..71ab036 100644 --- a/tools/budget_test.go +++ b/tools/budget_test.go @@ -2,6 +2,7 @@ package tools import ( "context" + "errors" "sync" "sync/atomic" "testing" @@ -114,6 +115,20 @@ func TestBudgetExistingCommitmentReleases(t *testing.T) { require.NoError(t, runWithNonce(fake, "a")) } +func TestBudgetExistingCommitmentWithErrorStaysCounted(t *testing.T) { + resetBudget(t, "100") + fake := pricedFake(60, common.PurchaseResult{ExistingCommitment: true}, errors.New("boom")) + require.Error(t, runWithNonce(fake, "a")) + assert.InDelta(t, 60, processBudget.committedUSD(), 1e-9) +} + +func TestBudgetExistingCommitmentWithResultErrorStaysCounted(t *testing.T) { + resetBudget(t, "100") + fake := pricedFake(60, common.PurchaseResult{ExistingCommitment: true, Error: errors.New("boom")}, nil) + _ = runWithNonce(fake, "a") + assert.InDelta(t, 60, processBudget.committedUSD(), 1e-9) +} + func TestBudgetUnsetRefusesBeforeResolve(t *testing.T) { resetBudget(t, "") fake := &fakeServiceClient{}