diff --git a/pkg/plugins/mongodb-atlas/cmd/main/main.go b/pkg/plugins/mongodb-atlas/cmd/main/main.go index a58c8e2..9738051 100644 --- a/pkg/plugins/mongodb-atlas/cmd/main/main.go +++ b/pkg/plugins/mongodb-atlas/cmd/main/main.go @@ -31,7 +31,7 @@ var handshakeConfig = plugin.HandshakeConfig{ MagicCookieValue: "mongodb-atlas", } -const costExplorerPendingInvoices = "https://cloud.mongodb.com/api/atlas/v2/orgs/%s/invoices/pending" +const costExplorerPendingInvoicesURL = "https://cloud.mongodb.com/api/atlas/v2/orgs/%s/invoices/pending" func main() { log.Debug("Initializing Mongo plugin") @@ -155,15 +155,10 @@ func (a *AtlasCostSource) GetCustomCosts(req *pb.CustomCostRequest) []*pb.Custom } log.Debugf("fetching atlas costs for window %v", target) - result, err := a.getAtlasCostsForWindow(&target, lineItems) - if err != nil { - log.Errorf("error getting costs for window %v: %v", target, err) - errResp := pb.CustomCostResponse{} - errResp.Errors = append(errResp.Errors, fmt.Sprintf("error getting costs for window %v: %v", target, err)) - results = append(results, &errResp) - } else { - results = append(results, result) - } + result := a.getAtlasCostsForWindow(&target, lineItems) + + results = append(results, result) + } return results @@ -183,12 +178,15 @@ func filterLineItemsByWindow(win *opencost.Window, lineItems []atlasplugin.LineI if err1 != nil || err2 != nil { // If parsing fails, skip this item + if err1 != nil { + log.Warnf("%s", err1) + } + if err2 != nil { + log.Warnf("%s", err2) + } continue } - // // Iterate over the UsageDetails in CostResponse - // for _, lineItem := range pendingInvoicesResponse.LineItems { - // Create a new pb.CustomCost for each LineItem - //log.Debugf("Line item %v", item) + customCost := &pb.CustomCost{ AccountName: item.GroupName, @@ -217,9 +215,9 @@ func filterLineItemsByWindow(win *opencost.Window, lineItems []atlasplugin.LineI } -func (a *AtlasCostSource) getAtlasCostsForWindow(win *opencost.Window, lineItems []atlasplugin.LineItem) (*pb.CustomCostResponse, error) { +func (a *AtlasCostSource) getAtlasCostsForWindow(win *opencost.Window, lineItems []atlasplugin.LineItem) *pb.CustomCostResponse { - //filter responses between + //filter responses between the win start and win end dates costsInWindow := filterLineItemsByWindow(win, lineItems) @@ -234,11 +232,11 @@ func (a *AtlasCostSource) getAtlasCostsForWindow(win *opencost.Window, lineItems Errors: []string{}, Costs: costsInWindow, } - return &resp, nil + return &resp } func GetPendingInvoices(org string, client HTTPClient) ([]atlasplugin.LineItem, error) { - request, _ := http.NewRequest("GET", fmt.Sprintf(costExplorerPendingInvoices, org), nil) + request, _ := http.NewRequest("GET", fmt.Sprintf(costExplorerPendingInvoicesURL, org), nil) request.Header.Set("Accept", "application/vnd.atlas.2023-01-01+json") request.Header.Set("Content-Type", "application/vnd.atlas.2023-01-01+json") diff --git a/pkg/plugins/mongodb-atlas/cmd/main/main_test.go b/pkg/plugins/mongodb-atlas/cmd/main/main_test.go index 0cbf719..d79db79 100644 --- a/pkg/plugins/mongodb-atlas/cmd/main/main_test.go +++ b/pkg/plugins/mongodb-atlas/cmd/main/main_test.go @@ -124,7 +124,7 @@ func TestGetCostsPendingInvoices(t *testing.T) { if req.Method != http.MethodGet { t.Errorf("expected GET request, got %s", req.Method) } - expectedURL := fmt.Sprintf(costExplorerPendingInvoices, "myOrg") + expectedURL := fmt.Sprintf(costExplorerPendingInvoicesURL, "myOrg") if req.URL.String() != expectedURL { t.Errorf("expected URL %s, got %s", expectedURL, req.URL.String()) } @@ -154,10 +154,7 @@ func TestGetCostErrorFromServer(t *testing.T) { DoFunc: func(req *http.Request) (*http.Response, error) { // Return a mock response with status 200 and mock JSON body - return &http.Response{ - StatusCode: http.StatusInternalServerError, - Body: io.NopCloser(bytes.NewBufferString("")), - }, nil + return nil, fmt.Errorf("mock error: failed to execute request") }, } costs, err := GetPendingInvoices("myOrg", mockClient) @@ -226,8 +223,7 @@ func TestGetAtlasCostsForWindow(t *testing.T) { // Create a new Window instance window := opencost.NewWindow(&day2, &day3) - resp, error := atlasCostSource.getAtlasCostsForWindow(&window, lineItems) - assert.Nil(t, error) + resp := atlasCostSource.getAtlasCostsForWindow(&window, lineItems) assert.True(t, resp != nil) assert.Equal(t, "data_storage", resp.CostSource) assert.Equal(t, "mongodb-atlas", resp.Domain) @@ -414,3 +410,82 @@ func TestFilterInvoicesOnWindow(t *testing.T) { assert.Equal(t, lineItems[0].Quantity, filteredItems[0].UsageQuantity) assert.Equal(t, filteredItems[0].UsageUnit, lineItems[0].Unit) } + +func TestFilterInvoicesOnWindowBadResponse(t *testing.T) { + //setup a window between october 1st and october 31st 2024 + windowStart := time.Date(2024, time.October, 1, 0, 0, 0, 0, time.UTC) + windowEnd := time.Date(2024, time.October, 31, 0, 0, 0, 0, time.UTC) + window := opencost.NewWindow(&windowStart, &windowEnd) + + //lineItems has bad startdate and bad endDate + lineItems := []atlasplugin.LineItem{ + {StartDate: "Bar", EndDate: "Foo", UnitPriceDollars: 1.0, GroupName: "kubecost0", + SKU: "0", ClusterName: "cluster-0", GroupId: "A", TotalPriceCents: 45, Quantity: 2, Unit: "GB"}, // Within window + // Partially in window + } + + filteredItems := filterLineItemsByWindow(&window, lineItems) + assert.Equal(t, 0, len(filteredItems)) +} + +func TestBadWindow(t *testing.T) { + pendingInvoiceResponse := atlasplugin.PendingInvoice{} + + mockResponseJson, _ := json.Marshal(pendingInvoiceResponse) + mockClient := &MockHTTPClient{ + DoFunc: func(req *http.Request) (*http.Response, error) { + + //return costs + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBuffer(mockResponseJson)), + }, nil + + }, + } + atlasCostSource := AtlasCostSource{ + orgID: "myOrg", + atlasClient: mockClient, + } + now := time.Now() + currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC) + customCostRequest := pb.CustomCostRequest{ + Start: timestamppb.New(currentMonthStart), // Start in current month + End: timestamppb.New(currentMonthStart.Add(5 * time.Hour)), // End in 5 hours + Resolution: durationpb.New(24 * time.Hour), // 1 day resolution + + } + //this window should throw an error in the opencost.GetWindows method + resp := atlasCostSource.GetCustomCosts(&customCostRequest) + assert.True(t, len(resp[0].Errors) > 0) +} + +func TestGetCostsReturnsErrorForPendingInvoices(t *testing.T) { + + mockClient := &MockHTTPClient{ + DoFunc: func(req *http.Request) (*http.Response, error) { + + //return costs + return nil, fmt.Errorf("mock error: failed to execute request") + + }, + } + atlasCostSource := AtlasCostSource{ + orgID: "myOrg", + atlasClient: mockClient, + } + // Define the start and end time for the window + now := time.Now() + currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC) + + customCostRequest := pb.CustomCostRequest{ + Start: timestamppb.New(currentMonthStart), // Start in current month + End: timestamppb.New(currentMonthStart.Add(48 * time.Hour)), // End in current month + Resolution: durationpb.New(24 * time.Hour), // 1 day resolution + + } + + resp := atlasCostSource.GetCustomCosts(&customCostRequest) + assert.True(t, len(resp[0].Errors) > 0) + +}