| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912 |
- package mcp
- import (
- "context"
- "io"
- "testing"
- "time"
- "github.com/opencost/opencost/core/pkg/clustercache"
- "github.com/opencost/opencost/core/pkg/opencost"
- models "github.com/opencost/opencost/pkg/cloud/models"
- "github.com/opencost/opencost/pkg/cloudcost"
- "github.com/opencost/opencost/pkg/costmodel"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func TestQueryTypeConstants(t *testing.T) {
- assert.Equal(t, QueryType("allocation"), AllocationQueryType)
- assert.Equal(t, QueryType("asset"), AssetQueryType)
- assert.Equal(t, QueryType("cloudcost"), CloudCostQueryType)
- }
- func TestAllocationQueryStruct(t *testing.T) {
- query := AllocationQuery{
- Step: 1 * time.Hour,
- Accumulate: true,
- ShareIdle: true,
- Aggregate: "namespace",
- IncludeIdle: true,
- IdleByNode: true,
- IncludeProportionalAssetResourceCosts: true,
- IncludeAggregatedMetadata: true,
- ShareLB: true,
- }
- assert.Equal(t, 1*time.Hour, query.Step)
- assert.True(t, query.Accumulate)
- assert.True(t, query.ShareIdle)
- assert.Equal(t, "namespace", query.Aggregate)
- assert.True(t, query.IncludeIdle)
- assert.True(t, query.IdleByNode)
- assert.True(t, query.IncludeProportionalAssetResourceCosts)
- assert.True(t, query.IncludeAggregatedMetadata)
- assert.True(t, query.ShareLB)
- }
- func TestAssetQueryStruct(t *testing.T) {
- query := AssetQuery{}
- // AssetQuery is currently empty, just test that it can be created
- assert.NotNil(t, query)
- }
- func TestCloudCostQueryStruct(t *testing.T) {
- query := CloudCostQuery{
- Aggregate: "provider,service",
- Accumulate: "day",
- Filter: "provider=aws",
- Provider: "aws",
- Service: "ec2",
- Category: "compute",
- Region: "us-east-1",
- AccountID: "123456789",
- }
- assert.Equal(t, "provider,service", query.Aggregate)
- assert.Equal(t, "day", query.Accumulate)
- assert.Equal(t, "provider=aws", query.Filter)
- assert.Equal(t, "aws", query.Provider)
- assert.Equal(t, "ec2", query.Service)
- assert.Equal(t, "compute", query.Category)
- assert.Equal(t, "us-east-1", query.Region)
- assert.Equal(t, "123456789", query.AccountID)
- }
- func TestMCPRequestStruct(t *testing.T) {
- request := MCPRequest{
- SessionID: "test-session-123",
- Query: &OpenCostQueryRequest{
- QueryType: AllocationQueryType,
- Window: "24h",
- AllocationParams: &AllocationQuery{
- Step: 1 * time.Hour,
- Accumulate: true,
- ShareIdle: true,
- },
- },
- }
- assert.Equal(t, "test-session-123", request.SessionID)
- assert.NotNil(t, request.Query)
- assert.Equal(t, AllocationQueryType, request.Query.QueryType)
- assert.Equal(t, "24h", request.Query.Window)
- assert.NotNil(t, request.Query.AllocationParams)
- assert.Equal(t, 1*time.Hour, request.Query.AllocationParams.Step)
- assert.True(t, request.Query.AllocationParams.Accumulate)
- assert.True(t, request.Query.AllocationParams.ShareIdle)
- }
- func TestMCPResponseStruct(t *testing.T) {
- response := MCPResponse{
- Data: "test-data",
- QueryInfo: QueryMetadata{
- QueryID: "query-123",
- Timestamp: time.Now(),
- ProcessingTime: 100 * time.Millisecond,
- },
- }
- assert.Equal(t, "test-data", response.Data)
- assert.Equal(t, "query-123", response.QueryInfo.QueryID)
- assert.NotZero(t, response.QueryInfo.Timestamp)
- assert.Equal(t, 100*time.Millisecond, response.QueryInfo.ProcessingTime)
- }
- func TestQueryMetadataStruct(t *testing.T) {
- metadata := QueryMetadata{
- QueryID: "query-456",
- Timestamp: time.Now(),
- ProcessingTime: 250 * time.Millisecond,
- }
- assert.Equal(t, "query-456", metadata.QueryID)
- assert.NotZero(t, metadata.Timestamp)
- assert.Equal(t, 250*time.Millisecond, metadata.ProcessingTime)
- }
- func TestOpenCostQueryRequestStruct(t *testing.T) {
- request := OpenCostQueryRequest{
- QueryType: AssetQueryType,
- Window: "7d",
- AssetParams: &AssetQuery{},
- }
- assert.Equal(t, AssetQueryType, request.QueryType)
- assert.Equal(t, "7d", request.Window)
- assert.NotNil(t, request.AssetParams)
- }
- // Test helper functions
- func createTestAllocation(name string) *Allocation {
- now := time.Now()
- return &Allocation{
- Name: name,
- CPUCost: 10.0,
- RAMCost: 5.0,
- GPUCost: 0.0,
- PVCost: 2.0,
- NetworkCost: 1.0,
- SharedCost: 0.5,
- ExternalCost: 0.0,
- TotalCost: 18.5,
- CPUCoreHours: 100.0,
- RAMByteHours: 5000000000.0,
- GPUHours: 0.0,
- PVByteHours: 2000000000.0,
- Start: now.Add(-24 * time.Hour),
- End: now,
- }
- }
- func createTestAsset(name string) *Asset {
- now := time.Now()
- return &Asset{
- Type: "node",
- Properties: AssetProperties{
- Category: "compute",
- Provider: "aws",
- Name: name,
- },
- CPUCost: 50.0,
- RAMCost: 25.0,
- GPUCost: 100.0,
- TotalCost: 175.0,
- CPUCoreHours: 500.0,
- RAMByteHours: 25000000000.0,
- GPUHours: 50.0,
- Start: now.Add(-24 * time.Hour),
- End: now,
- }
- }
- func createTestCloudCost(name string) *CloudCost {
- now := time.Now()
- return &CloudCost{
- Properties: CloudCostProperties{
- Provider: "aws",
- Service: "ec2",
- },
- Window: TimeWindow{
- Start: now.Add(-24 * time.Hour),
- End: now,
- },
- ListCost: CostMetric{
- Cost: 100.0,
- KubernetesPercent: 80.0,
- },
- NetCost: CostMetric{
- Cost: 95.0,
- KubernetesPercent: 80.0,
- },
- }
- }
- // Test MCP server response structures
- func TestAllocationResponseStruct(t *testing.T) {
- allocation := createTestAllocation("test-namespace")
- allocationSet := &AllocationSet{
- Name: "test-namespace",
- Properties: map[string]string{
- "namespace": "test-namespace",
- },
- Allocations: []*Allocation{allocation},
- }
- response := AllocationResponse{
- Allocations: map[string]*AllocationSet{
- "test-namespace": allocationSet,
- },
- }
- require.NotNil(t, response.Allocations)
- assert.Len(t, response.Allocations, 1)
- assert.Contains(t, response.Allocations, "test-namespace")
- allocSet := response.Allocations["test-namespace"]
- assert.Equal(t, "test-namespace", allocSet.Name)
- assert.Len(t, allocSet.Allocations, 1)
- alloc := allocSet.Allocations[0]
- assert.Equal(t, "test-namespace", alloc.Name)
- assert.Equal(t, 10.0, alloc.CPUCost)
- assert.Equal(t, 5.0, alloc.RAMCost)
- assert.Equal(t, 18.5, alloc.TotalCost)
- }
- func TestAssetResponseStruct(t *testing.T) {
- asset := createTestAsset("test-node")
- assetSet := &AssetSet{
- Name: "test-node",
- Assets: []*Asset{asset},
- }
- response := AssetResponse{
- Assets: map[string]*AssetSet{
- "test-node": assetSet,
- },
- }
- require.NotNil(t, response.Assets)
- assert.Len(t, response.Assets, 1)
- assert.Contains(t, response.Assets, "test-node")
- assetSetResult := response.Assets["test-node"]
- assert.Equal(t, "test-node", assetSetResult.Name)
- assert.Len(t, assetSetResult.Assets, 1)
- assetResult := assetSetResult.Assets[0]
- assert.Equal(t, "node", assetResult.Type)
- assert.Equal(t, 50.0, assetResult.CPUCost)
- assert.Equal(t, 25.0, assetResult.RAMCost)
- assert.Equal(t, 100.0, assetResult.GPUCost)
- assert.Equal(t, 175.0, assetResult.TotalCost)
- }
- func TestCloudCostResponseStruct(t *testing.T) {
- cloudCost := createTestCloudCost("aws-ec2")
- cloudCostSet := &CloudCostSet{
- Name: "aws-ec2",
- CloudCosts: []*CloudCost{cloudCost},
- Window: &TimeWindow{
- Start: time.Now().Add(-24 * time.Hour),
- End: time.Now(),
- },
- }
- response := CloudCostResponse{
- CloudCosts: map[string]*CloudCostSet{
- "aws-ec2": cloudCostSet,
- },
- Summary: &CloudCostSummary{
- TotalNetCost: 95.0,
- TotalAmortizedCost: 90.0,
- TotalInvoicedCost: 100.0,
- KubernetesPercent: 80.0,
- },
- }
- require.NotNil(t, response.CloudCosts)
- assert.Len(t, response.CloudCosts, 1)
- assert.Contains(t, response.CloudCosts, "aws-ec2")
- costSet := response.CloudCosts["aws-ec2"]
- assert.Equal(t, "aws-ec2", costSet.Name)
- assert.Len(t, costSet.CloudCosts, 1)
- cost := costSet.CloudCosts[0]
- assert.Equal(t, "aws", cost.Properties.Provider)
- assert.Equal(t, "ec2", cost.Properties.Service)
- assert.Equal(t, 100.0, cost.ListCost.Cost)
- assert.Equal(t, 95.0, cost.NetCost.Cost)
- require.NotNil(t, response.Summary)
- assert.Equal(t, 95.0, response.Summary.TotalNetCost)
- assert.Equal(t, 80.0, response.Summary.KubernetesPercent)
- }
- // Test allocation set functionality
- func TestAllocationSetTotalCost(t *testing.T) {
- alloc1 := createTestAllocation("alloc1")
- alloc1.TotalCost = 10.0
- alloc2 := createTestAllocation("alloc2")
- alloc2.TotalCost = 15.0
- allocSet := &AllocationSet{
- Name: "test-set",
- Allocations: []*Allocation{alloc1, alloc2},
- }
- totalCost := allocSet.TotalCost()
- assert.Equal(t, 25.0, totalCost)
- }
- // Test asset properties
- func TestAssetProperties(t *testing.T) {
- props := AssetProperties{
- Category: "compute",
- Provider: "aws",
- Account: "123456789",
- Project: "my-project",
- Service: "ec2",
- Cluster: "prod-cluster",
- Name: "worker-node-1",
- ProviderID: "i-1234567890abcdef0",
- }
- assert.Equal(t, "compute", props.Category)
- assert.Equal(t, "aws", props.Provider)
- assert.Equal(t, "123456789", props.Account)
- assert.Equal(t, "my-project", props.Project)
- assert.Equal(t, "ec2", props.Service)
- assert.Equal(t, "prod-cluster", props.Cluster)
- assert.Equal(t, "worker-node-1", props.Name)
- assert.Equal(t, "i-1234567890abcdef0", props.ProviderID)
- }
- // Test cloud cost properties
- func TestCloudCostProperties(t *testing.T) {
- props := CloudCostProperties{
- ProviderID: "i-1234567890abcdef0",
- Provider: "aws",
- AccountID: "123456789",
- AccountName: "my-account",
- InvoiceEntityID: "entity-123",
- InvoiceEntityName: "My Company",
- RegionID: "us-east-1",
- AvailabilityZone: "us-east-1a",
- Service: "ec2",
- Category: "compute",
- Labels: map[string]string{
- "environment": "production",
- "team": "platform",
- },
- }
- assert.Equal(t, "i-1234567890abcdef0", props.ProviderID)
- assert.Equal(t, "aws", props.Provider)
- assert.Equal(t, "123456789", props.AccountID)
- assert.Equal(t, "my-account", props.AccountName)
- assert.Equal(t, "entity-123", props.InvoiceEntityID)
- assert.Equal(t, "My Company", props.InvoiceEntityName)
- assert.Equal(t, "us-east-1", props.RegionID)
- assert.Equal(t, "us-east-1a", props.AvailabilityZone)
- assert.Equal(t, "ec2", props.Service)
- assert.Equal(t, "compute", props.Category)
- assert.Equal(t, "production", props.Labels["environment"])
- assert.Equal(t, "platform", props.Labels["team"])
- }
- // Test cost metric
- func TestCostMetric(t *testing.T) {
- metric := CostMetric{
- Cost: 100.0,
- KubernetesPercent: 80.0,
- }
- assert.Equal(t, 100.0, metric.Cost)
- assert.Equal(t, 80.0, metric.KubernetesPercent)
- }
- // Test time window
- func TestTimeWindow(t *testing.T) {
- now := time.Now()
- window := TimeWindow{
- Start: now.Add(-24 * time.Hour),
- End: now,
- }
- assert.True(t, window.Start.Before(window.End))
- assert.Equal(t, 24*time.Hour, window.End.Sub(window.Start))
- }
- // Test node overhead
- func TestNodeOverhead(t *testing.T) {
- overhead := NodeOverhead{
- RamOverheadFraction: 0.1,
- CpuOverheadFraction: 0.05,
- OverheadCostFraction: 0.15,
- }
- assert.Equal(t, 0.1, overhead.RamOverheadFraction)
- assert.Equal(t, 0.05, overhead.CpuOverheadFraction)
- assert.Equal(t, 0.15, overhead.OverheadCostFraction)
- }
- // Test asset breakdown
- func TestAssetBreakdown(t *testing.T) {
- breakdown := AssetBreakdown{
- Idle: 10.0,
- Other: 5.0,
- System: 15.0,
- User: 70.0,
- }
- assert.Equal(t, 10.0, breakdown.Idle)
- assert.Equal(t, 5.0, breakdown.Other)
- assert.Equal(t, 15.0, breakdown.System)
- assert.Equal(t, 70.0, breakdown.User)
- }
- // Test cloud cost summary
- func TestCloudCostSummary(t *testing.T) {
- summary := CloudCostSummary{
- TotalNetCost: 1000.0,
- TotalAmortizedCost: 950.0,
- TotalInvoicedCost: 1100.0,
- KubernetesPercent: 85.0,
- ProviderBreakdown: map[string]float64{
- "aws": 800.0,
- "gcp": 200.0,
- },
- ServiceBreakdown: map[string]float64{
- "ec2": 600.0,
- "s3": 200.0,
- "rds": 200.0,
- },
- RegionBreakdown: map[string]float64{
- "us-east-1": 600.0,
- "us-west-2": 400.0,
- },
- }
- assert.Equal(t, 1000.0, summary.TotalNetCost)
- assert.Equal(t, 950.0, summary.TotalAmortizedCost)
- assert.Equal(t, 1100.0, summary.TotalInvoicedCost)
- assert.Equal(t, 85.0, summary.KubernetesPercent)
- assert.Equal(t, 800.0, summary.ProviderBreakdown["aws"])
- assert.Equal(t, 200.0, summary.ProviderBreakdown["gcp"])
- assert.Equal(t, 600.0, summary.ServiceBreakdown["ec2"])
- assert.Equal(t, 200.0, summary.ServiceBreakdown["s3"])
- assert.Equal(t, 600.0, summary.RegionBreakdown["us-east-1"])
- assert.Equal(t, 400.0, summary.RegionBreakdown["us-west-2"])
- }
- // Test default values
- func TestAllocationQueryDefaultValues(t *testing.T) {
- query := AllocationQuery{}
- // Test default values
- assert.Equal(t, time.Duration(0), query.Step)
- assert.False(t, query.Accumulate)
- assert.False(t, query.ShareIdle)
- assert.Empty(t, query.Aggregate)
- assert.False(t, query.IncludeIdle)
- assert.False(t, query.IdleByNode)
- assert.False(t, query.IncludeProportionalAssetResourceCosts)
- assert.False(t, query.IncludeAggregatedMetadata)
- assert.False(t, query.ShareLB)
- }
- func TestCloudCostQueryDefaultValues(t *testing.T) {
- query := CloudCostQuery{}
- // Test default values
- assert.Empty(t, query.Aggregate)
- assert.Empty(t, query.Accumulate)
- assert.Empty(t, query.Filter)
- assert.Empty(t, query.Provider)
- assert.Empty(t, query.Service)
- assert.Empty(t, query.Category)
- assert.Empty(t, query.Region)
- assert.Empty(t, query.AccountID)
- }
- // Test edge cases
- func TestEdgeCases(t *testing.T) {
- t.Run("zero duration step", func(t *testing.T) {
- query := AllocationQuery{
- Step: 0,
- }
- assert.Equal(t, time.Duration(0), query.Step)
- })
- t.Run("negative duration step", func(t *testing.T) {
- query := AllocationQuery{
- Step: -1 * time.Hour,
- }
- assert.Equal(t, -1*time.Hour, query.Step)
- })
- t.Run("very large duration step", func(t *testing.T) {
- query := AllocationQuery{
- Step: 365 * 24 * time.Hour, // 1 year
- }
- assert.Equal(t, 365*24*time.Hour, query.Step)
- })
- t.Run("empty aggregate string", func(t *testing.T) {
- query := AllocationQuery{
- Aggregate: "",
- }
- assert.Empty(t, query.Aggregate)
- })
- t.Run("comma separated aggregate", func(t *testing.T) {
- query := AllocationQuery{
- Aggregate: "namespace,cluster,node",
- }
- assert.Equal(t, "namespace,cluster,node", query.Aggregate)
- })
- }
- // dummyQuerier captures the last QueryRequest it received
- type dummyQuerier struct {
- last cloudcost.QueryRequest
- }
- func (dq *dummyQuerier) Query(_ context.Context, req cloudcost.QueryRequest) (*opencost.CloudCostSetRange, error) {
- dq.last = req
- // Return empty set range
- ccsr, _ := opencost.NewCloudCostSetRange(time.Now().Add(-24*time.Hour), time.Now(), opencost.AccumulateOptionDay, "")
- return ccsr, nil
- }
- func TestBuildCloudCostQueryRequest_AccumulateParsing(t *testing.T) {
- s := &MCPServer{}
- req := cloudcost.QueryRequest{}
- params := &CloudCostQuery{
- Aggregate: "provider,service",
- Accumulate: "week",
- }
- out := s.buildCloudCostQueryRequest(req, params)
- assert.Equal(t, []string{"provider", "service"}, out.AggregateBy)
- assert.NotEqual(t, opencost.AccumulateOptionNone, out.Accumulate)
- }
- func TestBuildCloudCostQueryRequest_FilterString(t *testing.T) {
- s := &MCPServer{}
- req := cloudcost.QueryRequest{}
- params := &CloudCostQuery{
- Filter: `provider:"gcp" and service:"Compute Engine"`,
- }
- out := s.buildCloudCostQueryRequest(req, params)
- assert.NotNil(t, out.Filter)
- }
- func TestBuildFilterFromParams_SupportedFieldsOnly(t *testing.T) {
- s := &MCPServer{}
- params := &CloudCostQuery{
- Provider: "gcp",
- ProviderID: "cluster-1",
- Service: "Compute Engine",
- Category: "compute",
- AccountID: "acct-123",
- InvoiceEntityID: "inv-456",
- Region: "us-central1", // intentionally set; ignored by builder
- Labels: map[string]string{
- "goog-k8s-cluster-name": "cluster-1",
- },
- }
- f := s.buildFilterFromParams(params)
- assert.NotNil(t, f)
- }
- func TestBuildFilterFromParams_LabelOnly(t *testing.T) {
- s := &MCPServer{}
- params := &CloudCostQuery{
- Labels: map[string]string{"environment": "prod"},
- }
- f := s.buildFilterFromParams(params)
- assert.NotNil(t, f)
- }
- func TestQueryCloudCosts_QuerierCapture(t *testing.T) {
- dq := &dummyQuerier{}
- s := &MCPServer{cloudQuerier: dq}
- req := &OpenCostQueryRequest{
- QueryType: CloudCostQueryType,
- Window: "5d",
- CloudCostParams: &CloudCostQuery{
- Aggregate: "provider,service",
- Accumulate: "week",
- Provider: "gcp",
- },
- }
- _, err := s.QueryCloudCosts(req)
- require.NoError(t, err)
- assert.Equal(t, []string{"provider", "service"}, dq.last.AggregateBy)
- assert.NotEqual(t, opencost.AccumulateOptionNone, dq.last.Accumulate)
- }
- // ---- Tests for MCP server end-to-end behavior ----
- func TestProcessMCPRequest_CloudCostDispatch(t *testing.T) {
- dq := &dummyQuerier{}
- s := &MCPServer{cloudQuerier: dq}
- req := &MCPRequest{
- Query: &OpenCostQueryRequest{
- QueryType: CloudCostQueryType,
- Window: "3d",
- CloudCostParams: &CloudCostQuery{
- Aggregate: "provider",
- Accumulate: "day",
- Provider: "gcp",
- },
- },
- }
- resp, err := s.ProcessMCPRequest(req)
- require.NoError(t, err)
- require.NotNil(t, resp)
- require.NotNil(t, resp.Data)
- }
- func TestProcessMCPRequest_UnsupportedType(t *testing.T) {
- s := &MCPServer{}
- req := &MCPRequest{
- Query: &OpenCostQueryRequest{
- QueryType: QueryType("unknown"),
- Window: "1d",
- },
- }
- _, err := s.ProcessMCPRequest(req)
- require.Error(t, err)
- }
- func TestProcessMCPRequest_ValidationError(t *testing.T) {
- s := &MCPServer{}
- // Missing window
- req := &MCPRequest{
- Query: &OpenCostQueryRequest{
- QueryType: CloudCostQueryType,
- Window: "",
- },
- }
- _, err := s.ProcessMCPRequest(req)
- require.Error(t, err)
- }
- // ---- Additional comprehensive tests for missing functionality ----
- func TestNewMCPServer(t *testing.T) {
- costModel := &costmodel.CostModel{}
- provider := &mockProvider{}
- cloudQuerier := &dummyQuerier{}
- server := NewMCPServer(costModel, provider, cloudQuerier)
- require.NotNil(t, server)
- assert.Equal(t, costModel, server.costModel)
- assert.Equal(t, provider, server.provider)
- assert.Equal(t, cloudQuerier, server.cloudQuerier)
- }
- // Mock provider for testing
- type mockProvider struct{}
- func (mp *mockProvider) GetConfig() (*models.CustomPricing, error) { return nil, nil }
- func (mp *mockProvider) AllNodePricing() (interface{}, error) { return nil, nil }
- func (mp *mockProvider) ClusterInfo() (map[string]string, error) { return nil, nil }
- func (mp *mockProvider) GetAddresses() ([]byte, error) { return nil, nil }
- func (mp *mockProvider) GetDisks() ([]byte, error) { return nil, nil }
- func (mp *mockProvider) GetOrphanedResources() ([]models.OrphanedResource, error) { return nil, nil }
- func (mp *mockProvider) NodePricing(models.Key) (*models.Node, models.PricingMetadata, error) {
- return nil, models.PricingMetadata{}, nil
- }
- func (mp *mockProvider) GpuPricing(map[string]string) (string, error) { return "", nil }
- func (mp *mockProvider) PVPricing(models.PVKey) (*models.PV, error) { return nil, nil }
- func (mp *mockProvider) NetworkPricing() (*models.Network, error) { return nil, nil }
- func (mp *mockProvider) LoadBalancerPricing() (*models.LoadBalancer, error) { return nil, nil }
- func (mp *mockProvider) DownloadPricingData() error { return nil }
- func (mp *mockProvider) GetKey(map[string]string, *clustercache.Node) models.Key { return nil }
- func (mp *mockProvider) GetPVKey(*clustercache.PersistentVolume, map[string]string, string) models.PVKey {
- return nil
- }
- func (mp *mockProvider) UpdateConfig(io.Reader, string) (*models.CustomPricing, error) {
- return nil, nil
- }
- func (mp *mockProvider) UpdateConfigFromConfigMap(map[string]string) (*models.CustomPricing, error) {
- return nil, nil
- }
- func (mp *mockProvider) GetManagementPlatform() (string, error) { return "", nil }
- func (mp *mockProvider) ApplyReservedInstancePricing(map[string]*models.Node) {}
- func (mp *mockProvider) ServiceAccountStatus() *models.ServiceAccountStatus { return nil }
- func (mp *mockProvider) PricingSourceStatus() map[string]*models.PricingSource { return nil }
- func (mp *mockProvider) ClusterManagementPricing() (string, float64, error) { return "", 0, nil }
- func (mp *mockProvider) CombinedDiscountForNode(string, bool, float64, float64) float64 { return 0 }
- func (mp *mockProvider) Regions() []string { return nil }
- func (mp *mockProvider) PricingSourceSummary() interface{} { return nil }
- func TestGenerateQueryID(t *testing.T) {
- // Test that generateQueryID returns a non-empty string
- id1 := generateQueryID()
- id2 := generateQueryID()
- assert.NotEmpty(t, id1)
- assert.NotEmpty(t, id2)
- assert.NotEqual(t, id1, id2) // Should be different each time
- assert.Contains(t, id1, "query-")
- }
- func TestTransformAllocationSet_NilInput(t *testing.T) {
- result := transformAllocationSet(nil)
- require.NotNil(t, result)
- assert.NotNil(t, result.Allocations)
- assert.Len(t, result.Allocations, 0)
- }
- func TestTransformAllocationSet_EmptyInput(t *testing.T) {
- emptySet := &opencost.AllocationSet{
- Allocations: map[string]*opencost.Allocation{},
- }
- result := transformAllocationSet(emptySet)
- require.NotNil(t, result)
- assert.Contains(t, result.Allocations, "allocations")
- assert.Len(t, result.Allocations["allocations"].Allocations, 0)
- }
- func TestTransformAssetSet_NilInput(t *testing.T) {
- result := transformAssetSet(nil)
- require.NotNil(t, result)
- assert.NotNil(t, result.Assets)
- assert.Len(t, result.Assets, 0)
- }
- func TestTransformAssetSet_EmptyInput(t *testing.T) {
- emptySet := &opencost.AssetSet{
- Assets: map[string]opencost.Asset{},
- }
- result := transformAssetSet(emptySet)
- require.NotNil(t, result)
- assert.Contains(t, result.Assets, "assets")
- assert.Len(t, result.Assets["assets"].Assets, 0)
- }
- func TestBuildFilterFromParams_EmptyParams(t *testing.T) {
- s := &MCPServer{}
- params := &CloudCostQuery{}
- filter := s.buildFilterFromParams(params)
- assert.Nil(t, filter)
- }
- func TestBuildFilterFromParams_RegionIgnored(t *testing.T) {
- s := &MCPServer{}
- params := &CloudCostQuery{
- Region: "us-east-1", // Should be ignored
- }
- filter := s.buildFilterFromParams(params)
- assert.Nil(t, filter) // Should return nil since only region is set
- }
- func TestBuildFilterFromParams_EmptyLabelKey(t *testing.T) {
- s := &MCPServer{}
- params := &CloudCostQuery{
- Labels: map[string]string{
- "": "value1", // Empty key should be ignored
- "valid": "value2",
- },
- }
- filter := s.buildFilterFromParams(params)
- assert.NotNil(t, filter)
- }
- func TestBuildCloudCostQueryRequest_EmptyParams(t *testing.T) {
- s := &MCPServer{}
- req := cloudcost.QueryRequest{}
- params := &CloudCostQuery{}
- result := s.buildCloudCostQueryRequest(req, params)
- assert.Equal(t, req, result) // Should return unchanged request
- }
- func TestBuildCloudCostQueryRequest_InvalidFilterString(t *testing.T) {
- s := &MCPServer{}
- req := cloudcost.QueryRequest{}
- params := &CloudCostQuery{
- Filter: "invalid filter syntax !!!",
- }
- result := s.buildCloudCostQueryRequest(req, params)
- // Should not panic and should return request with nil filter
- assert.Nil(t, result.Filter)
- }
- func TestQueryCloudCosts_NilCloudQuerier(t *testing.T) {
- s := &MCPServer{cloudQuerier: nil}
- req := &OpenCostQueryRequest{
- QueryType: CloudCostQueryType,
- Window: "24h",
- }
- _, err := s.QueryCloudCosts(req)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "cloud cost querier not configured")
- }
- func TestQueryCloudCosts_InvalidWindow(t *testing.T) {
- s := &MCPServer{cloudQuerier: &dummyQuerier{}}
- req := &OpenCostQueryRequest{
- QueryType: CloudCostQueryType,
- Window: "invalid-window",
- }
- _, err := s.QueryCloudCosts(req)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to parse window")
- }
- func TestQueryAssets_InvalidWindow(t *testing.T) {
- s := &MCPServer{}
- req := &OpenCostQueryRequest{
- QueryType: AssetQueryType,
- Window: "invalid-window",
- }
- _, err := s.QueryAssets(req)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to parse window")
- }
- func TestQueryAllocations_InvalidWindow(t *testing.T) {
- s := &MCPServer{}
- req := &OpenCostQueryRequest{
- QueryType: AllocationQueryType,
- Window: "invalid-window",
- }
- _, err := s.QueryAllocations(req)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "failed to parse window")
- }
- func TestProcessMCPRequest_ResponseMetadata(t *testing.T) {
- dq := &dummyQuerier{}
- s := &MCPServer{cloudQuerier: dq}
- req := &MCPRequest{
- Query: &OpenCostQueryRequest{
- QueryType: CloudCostQueryType,
- Window: "1h",
- },
- }
- resp, err := s.ProcessMCPRequest(req)
- require.NoError(t, err)
- require.NotNil(t, resp)
- // Check response metadata
- assert.NotEmpty(t, resp.QueryInfo.QueryID)
- assert.NotZero(t, resp.QueryInfo.Timestamp)
- assert.Greater(t, resp.QueryInfo.ProcessingTime, time.Duration(0))
- }
- func TestCloudCostQuery_NewFields(t *testing.T) {
- query := CloudCostQuery{
- InvoiceEntityID: "entity-123",
- ProviderID: "provider-456",
- Labels: map[string]string{
- "environment": "prod",
- "team": "platform",
- },
- }
- assert.Equal(t, "entity-123", query.InvoiceEntityID)
- assert.Equal(t, "provider-456", query.ProviderID)
- assert.Equal(t, "prod", query.Labels["environment"])
- assert.Equal(t, "platform", query.Labels["team"])
- }
|