From 20a755f33070c9c066b4e5fe820f108a204e5673 Mon Sep 17 00:00:00 2001 From: Vladimir Sakac Date: Wed, 14 Jul 2021 23:40:05 +0200 Subject: [PATCH 1/3] cost: fix resource diff planned cost fix Fixed issue with prior cost used to calculate planned cost in resource diff. Added tests to cover prior and planned cost calculation for resource diff. Skip cost addition for zero costs. --- cost/cost.go | 5 ++ cost/resource.go | 4 +- cost/resource_test.go | 130 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 2 deletions(-) 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()) + }) +} From 39b1e06ef7662f0f33cbb3cf68602ebba89ed2dc Mon Sep 17 00:00:00 2001 From: Vladimir Sakac Date: Wed, 14 Jul 2021 23:40:49 +0200 Subject: [PATCH 2/3] e2e: check prior and planned cost calculation Check prior and planned cost calculation for resource diff. --- e2e/aws_estimation_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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) } } }) From 03f66060d7b1cc0f213d0c8df9127ab1cbc6ef8c Mon Sep 17 00:00:00 2001 From: Vladimir Sakac Date: Wed, 14 Jul 2021 23:41:21 +0200 Subject: [PATCH 3/3] CHANGELOG: bump to version 0.4.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) 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