From 2f763c7d45f8bbbb1557be5073fe976bcd287233 Mon Sep 17 00:00:00 2001 From: HardingEthan15 Date: Mon, 15 Jun 2026 16:32:23 -0500 Subject: [PATCH 1/2] Added empty window tests for Assets and Allocations api Signed-off-by: HardingEthan15 --- .../api/allocation/empty_window_test.go | 94 +++++++++++++++++++ test/integration/api/allocation/test.bats | 4 + .../api/asset/empty_window_test.go | 83 ++++++++++++++++ test/integration/api/asset/test.bats | 4 + 4 files changed, 185 insertions(+) create mode 100644 test/integration/api/allocation/empty_window_test.go create mode 100644 test/integration/api/asset/empty_window_test.go diff --git a/test/integration/api/allocation/empty_window_test.go b/test/integration/api/allocation/empty_window_test.go new file mode 100644 index 0000000..c78dfa7 --- /dev/null +++ b/test/integration/api/allocation/empty_window_test.go @@ -0,0 +1,94 @@ +package allocation + +import ( + "testing" + + "github.com/opencost/opencost-integration-tests/pkg/api" +) + +// TestAllocationEmptyWindow validates that allocation API returns a clean, well-formed +// empty response for valid but data-less windows. +// +// Expected response shape for empty windows: +// - HTTP Code: 200 (success) +// - Data: empty slice [] or list of empty allocations +// - No panic or 5xx errors +func TestAllocationEmptyWindow(t *testing.T) { + apiObj := api.NewAPI() + + testCases := []struct { + name string + window string + aggregate string + describe string + }{ + { + name: "FarPastWindow", + window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", + aggregate: "namespace", + }, + { + name: "FutureWindow", + window: "2099-01-01T00:00:00Z,2099-01-02T00:00:00Z", + aggregate: "namespace", + }, + { + name: "BeyondRetentionWindow", + window: "2000-01-01T00:00:00Z,2000-01-02T00:00:00Z", + aggregate: "cluster", + }, + { + name: "TestPodEmptyWindow", + window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", + aggregate: "pod", + }, + { + name: "TestContainerEmptyWindow", + window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", + aggregate: "container", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Logf("Testing: %s - %s", tc.name, tc.window) + + response, err := apiObj.GetAllocation(api.AllocationRequest{ + Window: tc.window, + Aggregate: tc.aggregate, + }) + + // Validate no errors occurred + if err != nil { + t.Fatalf("Unexpected error calling allocation API: %v", err) + } + + // Validate HTTP 200 + if response.Code != 200 { + t.Fatalf("Expected HTTP 200, got %d", response.Code) + } + + // Validate response structure is well-formed + if response.Data == nil { + t.Fatalf("Expected non-nil data slice, got nil") + } + + // Empty window should have empty data or a single empty map + // The exact format depends on the step parameter, but we should + // have at least one "window" entry even if no allocations + if len(response.Data) == 0 { + t.Logf("Data is empty slice (expected for empty window): %v", response.Data) + } else { + // If data has entries, each should be a map + for i, dataMap := range response.Data { + if len(dataMap) == 0 { + t.Logf("Data[%d] is empty map (expected for empty window)", i) + } else { + t.Fatalf("expected empty allocation data for empty window, data[%d] contained allocations: %v", i, dataMap) + } + } + } + }) + } +} + diff --git a/test/integration/api/allocation/test.bats b/test/integration/api/allocation/test.bats index 2d41bd7..429f807 100644 --- a/test/integration/api/allocation/test.bats +++ b/test/integration/api/allocation/test.bats @@ -45,3 +45,7 @@ teardown() { @test "validate_api: validate if all of idle costs are spread" { go test share_idle_shares_test.go } + +@test "validate_api: validate if api handles empty windows correctly" { + go test empty_window_test.go +} \ No newline at end of file diff --git a/test/integration/api/asset/empty_window_test.go b/test/integration/api/asset/empty_window_test.go new file mode 100644 index 0000000..55e0aa5 --- /dev/null +++ b/test/integration/api/asset/empty_window_test.go @@ -0,0 +1,83 @@ +package assets + +import ( + "testing" + + "github.com/opencost/opencost-integration-tests/pkg/api" +) + +// TestAssetsEmptyWindow validates that assets API returns a clean, well-formed +// empty response for valid but data-less windows. +// +// Expected response shape for empty windows: +// - HTTP Code: 200 (success) +// - Data: empty map {} (no assets in the window) +// - No panic or 5xx errors +func TestAssetsEmptyWindow(t *testing.T) { + apiObj := api.NewAPI() + + testCases := []struct { + name string + window string + assetType string + describe string + }{ + { + name: "FarPastWindow", + window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", + assetType: "node", + describe: "Window from 1970, before any cluster existed", + }, + { + name: "FutureWindow", + window: "2099-01-01T00:00:00Z,2099-01-02T00:00:00Z", + assetType: "node", + describe: "Window from 2099, in the future", + }, + { + name: "BeyondRetentionWindow", + window: "2000-01-01T00:00:00Z,2000-01-02T00:00:00Z", + assetType: "disk", + describe: "Window from 2000, beyond Prometheus retention", + }, + { + name: "EmptyWindowPVC", + window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", + assetType: "pvc", + describe: "PVC assets in empty window", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Logf("Testing: %s - %s", tc.describe, tc.window) + + response, err := apiObj.GetAssets(api.AssetsRequest{ + Window: tc.window, + Filter: tc.assetType, + }) + + // Validate no errors occurred + if err != nil { + t.Fatalf("Unexpected error calling assets API: %v", err) + } + + // Validate HTTP 200 + if response.Code != 200 { + t.Fatalf("Expected HTTP 200, got %d", response.Code) + } + + // Validate response structure is well-formed + if response.Data == nil { + t.Fatalf("Expected non-nil data map, got nil") + } + + // Empty window should return empty map + if len(response.Data) == 0 { + t.Logf("Data is empty map (expected for empty window)") + } else { + t.Fatalf("Data has %d entries (filter: %s)", len(response.Data), tc.assetType) + } + }) + } +} \ No newline at end of file diff --git a/test/integration/api/asset/test.bats b/test/integration/api/asset/test.bats index a642c16..6d9b8f8 100644 --- a/test/integration/api/asset/test.bats +++ b/test/integration/api/asset/test.bats @@ -14,3 +14,7 @@ teardown() { @test "asset: Spot Node" { go test spot_nodes_test.go } + +@test "asset: Empty Window" { + go test empty_window_test.go +} \ No newline at end of file From ec5fd9c98261d3f66f95ad0b2f3b07132cd75b69 Mon Sep 17 00:00:00 2001 From: HardingEthan15 Date: Tue, 16 Jun 2026 11:58:03 -0500 Subject: [PATCH 2/2] Removed describe from test cases Signed-off-by: HardingEthan15 --- test/integration/api/allocation/empty_window_test.go | 1 - test/integration/api/asset/empty_window_test.go | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/test/integration/api/allocation/empty_window_test.go b/test/integration/api/allocation/empty_window_test.go index c78dfa7..4660042 100644 --- a/test/integration/api/allocation/empty_window_test.go +++ b/test/integration/api/allocation/empty_window_test.go @@ -20,7 +20,6 @@ func TestAllocationEmptyWindow(t *testing.T) { name string window string aggregate string - describe string }{ { name: "FarPastWindow", diff --git a/test/integration/api/asset/empty_window_test.go b/test/integration/api/asset/empty_window_test.go index 55e0aa5..3e44ae6 100644 --- a/test/integration/api/asset/empty_window_test.go +++ b/test/integration/api/asset/empty_window_test.go @@ -20,37 +20,32 @@ func TestAssetsEmptyWindow(t *testing.T) { name string window string assetType string - describe string }{ { name: "FarPastWindow", window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", assetType: "node", - describe: "Window from 1970, before any cluster existed", }, { name: "FutureWindow", window: "2099-01-01T00:00:00Z,2099-01-02T00:00:00Z", assetType: "node", - describe: "Window from 2099, in the future", }, { name: "BeyondRetentionWindow", window: "2000-01-01T00:00:00Z,2000-01-02T00:00:00Z", assetType: "disk", - describe: "Window from 2000, beyond Prometheus retention", }, { name: "EmptyWindowPVC", window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", assetType: "pvc", - describe: "PVC assets in empty window", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - t.Logf("Testing: %s - %s", tc.describe, tc.window) + t.Logf("Testing: %s - %s", tc.name, tc.window) response, err := apiObj.GetAssets(api.AssetsRequest{ Window: tc.window,