diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d7652..0b47ea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## [Unreleased] +## [0.4.1] _2021-07-15_ + +### Fixed + +- Correct calculation for planned cost in resource diff + ([Issue #51](https://github.com/cycloidio/terracost/issues/51)) + ## [0.4.0] _2021-07-13_ ### Added diff --git a/cost/cost.go b/cost/cost.go index 8657d32..e4d2de8 100644 --- a/cost/cost.go +++ b/cost/cost.go @@ -44,6 +44,11 @@ func (c Cost) Hourly() decimal.Decimal { // Add adds the values of two Cost structs. // If the currency of both costs doesn't match, error is returned. func (c Cost) Add(c2 Cost) (Cost, error) { + // if cost addition iz Zero, ignore it + if c2 == Zero { + return c, nil + } + // If there is no currency, use the currency of the addition if c.Currency == "" { c.Currency = c2.Currency diff --git a/cost/resource.go b/cost/resource.go index 7d5065f..f0dd7a1 100644 --- a/cost/resource.go +++ b/cost/resource.go @@ -42,7 +42,7 @@ func (rd ResourceDiff) PriorCost() (Cost, error) { for _, cd := range rd.ComponentDiffs { total, err = total.Add(cd.PriorCost()) if err != nil { - return Zero, fmt.Errorf("failed calculating prior cost : %w", err) + return Zero, fmt.Errorf("failed calculating prior cost: %w", err) } } return total, nil @@ -54,7 +54,7 @@ func (rd ResourceDiff) PlannedCost() (Cost, error) { total := Zero var err error for _, cd := range rd.ComponentDiffs { - total, err = total.Add(cd.PriorCost()) + total, err = total.Add(cd.PlannedCost()) if err != nil { return Zero, fmt.Errorf("failed calculating planned cost: %w", err) } diff --git a/cost/resource_test.go b/cost/resource_test.go index cd26d51..00d7be2 100644 --- a/cost/resource_test.go +++ b/cost/resource_test.go @@ -1,9 +1,12 @@ package cost_test import ( + "fmt" "testing" + "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/cycloidio/terracost/cost" ) @@ -103,3 +106,130 @@ func TestResourceDiff_Valid(t *testing.T) { }) } } + +func TestResourceDiff_Cost(t *testing.T) { + t.Run("Success", func(t *testing.T) { + rd := &cost.ResourceDiff{ + ComponentDiffs: map[string]*cost.ComponentDiff{ + "comp1": { + Prior: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(104.6), "USD"), + Quantity: decimal.NewFromInt(1), + }, + Planned: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(208.8), "USD"), + Quantity: decimal.NewFromInt(1), + }, + }, + "comp2": { + Prior: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(10.5), "USD"), + Quantity: decimal.NewFromInt(2), + }, + Planned: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(20.7), "USD"), + Quantity: decimal.NewFromInt(2), + }, + }, + }, + } + + prior, err := rd.PriorCost() + require.NoError(t, err) + assertDecimalEqual(t, decimal.NewFromFloat(125.6), prior.Monthly()) + assert.Equal(t, "USD", prior.Currency) + + planned, err := rd.PlannedCost() + require.NoError(t, err) + assertDecimalEqual(t, decimal.NewFromFloat(250.2), planned.Decimal) + assert.Equal(t, "USD", planned.Currency) + }) + t.Run("PriorWithError", func(t *testing.T) { + rd := &cost.ResourceDiff{ + ComponentDiffs: map[string]*cost.ComponentDiff{ + "comp1": { + Prior: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(104.6), "USD"), + Quantity: decimal.NewFromInt(1), + }, + }, + "comp2": { + Prior: &cost.Component{ + Error: fmt.Errorf("prior error"), + }, + }, + }, + } + + prior, err := rd.PriorCost() + require.NoError(t, err) + assertDecimalEqual(t, decimal.NewFromFloat(104.6), prior.Monthly()) + assert.Equal(t, "USD", prior.Currency) + }) + t.Run("PlannedWithError", func(t *testing.T) { + rd := &cost.ResourceDiff{ + ComponentDiffs: map[string]*cost.ComponentDiff{ + "comp1": { + Planned: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(208.8), "USD"), + Quantity: decimal.NewFromInt(1), + }, + }, + "comp2": { + Planned: &cost.Component{ + Error: fmt.Errorf("planned error"), + }, + }, + }, + } + + planned, err := rd.PlannedCost() + require.NoError(t, err) + assertDecimalEqual(t, decimal.NewFromFloat(208.8), planned.Decimal) + assert.Equal(t, "USD", planned.Currency) + }) + t.Run("PriorCurrencyMismatch", func(t *testing.T) { + rd := &cost.ResourceDiff{ + ComponentDiffs: map[string]*cost.ComponentDiff{ + "comp1": { + Prior: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(104.6), "USD"), + Quantity: decimal.NewFromInt(1), + }, + }, + "comp2": { + Prior: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(10.5), "EUR"), + Quantity: decimal.NewFromInt(2), + }, + }, + }, + } + + _, err := rd.PriorCost() + require.Error(t, err) + assert.Equal(t, "failed calculating prior cost: currency mismatch: expected USD, got EUR", err.Error()) + }) + t.Run("PlannedCurrencyMismatch", func(t *testing.T) { + rd := &cost.ResourceDiff{ + ComponentDiffs: map[string]*cost.ComponentDiff{ + "comp1": { + Planned: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(208.8), "USD"), + Quantity: decimal.NewFromInt(1), + }, + }, + "comp2": { + Planned: &cost.Component{ + Rate: cost.NewMonthly(decimal.NewFromFloat(20.7), "EUR"), + Quantity: decimal.NewFromInt(2), + }, + }, + }, + } + + _, err := rd.PlannedCost() + require.Error(t, err) + assert.Equal(t, "failed calculating planned cost: currency mismatch: expected USD, got EUR", err.Error()) + }) +} diff --git a/e2e/aws_estimation_test.go b/e2e/aws_estimation_test.go index 75122cc..6d37e4b 100644 --- a/e2e/aws_estimation_test.go +++ b/e2e/aws_estimation_test.go @@ -161,11 +161,27 @@ func TestAWSEstimation(t *testing.T) { assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(3.6), "USD"), compute.PriorCost()) assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(3.6), "USD"), compute.PlannedCost()) + priorCost, err := diff.PriorCost() + require.NoError(t, err) + assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(91.2), "USD"), priorCost) + + plannedCost, err := diff.PlannedCost() + require.NoError(t, err) + assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(901.5), "USD"), plannedCost) + case "aws_lb.example": lb := diff.ComponentDiffs["Application Load Balancer"] require.NotNil(t, lb) assert.False(t, diff.Valid()) assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(0), ""), lb.Planned.Cost()) + + priorCost, err := diff.PriorCost() + require.NoError(t, err) + assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(0), ""), priorCost) + + plannedCost, err := diff.PlannedCost() + require.NoError(t, err) + assertCostEqual(t, cost.NewMonthly(decimal.NewFromFloat(0), ""), plannedCost) } } })