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..4660042 --- /dev/null +++ b/test/integration/api/allocation/empty_window_test.go @@ -0,0 +1,93 @@ +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 + }{ + { + 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..3e44ae6 --- /dev/null +++ b/test/integration/api/asset/empty_window_test.go @@ -0,0 +1,78 @@ +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 + }{ + { + name: "FarPastWindow", + window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", + assetType: "node", + }, + { + name: "FutureWindow", + window: "2099-01-01T00:00:00Z,2099-01-02T00:00:00Z", + assetType: "node", + }, + { + name: "BeyondRetentionWindow", + window: "2000-01-01T00:00:00Z,2000-01-02T00:00:00Z", + assetType: "disk", + }, + { + name: "EmptyWindowPVC", + window: "1970-01-01T00:00:00Z,1970-01-02T00:00:00Z", + assetType: "pvc", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Logf("Testing: %s - %s", tc.name, 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