|
@@ -8,6 +8,7 @@ import (
|
|
|
"github.com/opencost/opencost/core/pkg/pricing"
|
|
"github.com/opencost/opencost/core/pkg/pricing"
|
|
|
"github.com/opencost/opencost/core/pkg/reader"
|
|
"github.com/opencost/opencost/core/pkg/reader"
|
|
|
"github.com/opencost/opencost/core/pkg/storage"
|
|
"github.com/opencost/opencost/core/pkg/storage"
|
|
|
|
|
+ "github.com/opencost/opencost/core/pkg/unit"
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/require"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
@@ -55,6 +56,34 @@ func testPricingModuleWithStore(store pricing.PricingStore) func(t *testing.T) {
|
|
|
testDefaultPricing(t, ctx, pm)
|
|
testDefaultPricing(t, ctx, pm)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ t.Run("Metadata", func(t *testing.T) {
|
|
|
|
|
+ testMetadata(t, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("GetPricingSet", func(t *testing.T) {
|
|
|
|
|
+ testGetPricingSet(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("PublicGetters", func(t *testing.T) {
|
|
|
|
|
+ testPublicGetters(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("SetClusterPricePerHour", func(t *testing.T) {
|
|
|
|
|
+ testSetClusterPricePerHour(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("SetNetworkPrices", func(t *testing.T) {
|
|
|
|
|
+ testSetNetworkPrices(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("SetServicePricePerHour", func(t *testing.T) {
|
|
|
|
|
+ testSetServicePricePerHour(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("Checksum", func(t *testing.T) {
|
|
|
|
|
+ testChecksum(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
t.Run("SetNodePricePerCPUCoreHour", func(t *testing.T) {
|
|
t.Run("SetNodePricePerCPUCoreHour", func(t *testing.T) {
|
|
|
testSetNodePricePerCPUCoreHour(t, ctx, pm)
|
|
testSetNodePricePerCPUCoreHour(t, ctx, pm)
|
|
|
})
|
|
})
|
|
@@ -83,20 +112,43 @@ func testPricingModuleWithStore(store pricing.PricingStore) func(t *testing.T) {
|
|
|
testNewVolumePricingReader(t, ctx, pm)
|
|
testNewVolumePricingReader(t, ctx, pm)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ t.Run("NewClusterPricingReader", func(t *testing.T) {
|
|
|
|
|
+ testNewClusterPricingReader(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("NewNetworkPricingReader", func(t *testing.T) {
|
|
|
|
|
+ testNewNetworkPricingReader(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ t.Run("NewServicePricingReader", func(t *testing.T) {
|
|
|
|
|
+ testNewServicePricingReader(t, ctx, pm)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
t.Run("ModulePersistence", func(t *testing.T) {
|
|
t.Run("ModulePersistence", func(t *testing.T) {
|
|
|
// Create a new PricingModule with the same store
|
|
// Create a new PricingModule with the same store
|
|
|
pm2, err := NewBasicPricingModule(store)
|
|
pm2, err := NewBasicPricingModule(store)
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
- // Verify that pricing persists
|
|
|
|
|
|
|
+ // Verify that pricing persists across all resource kinds.
|
|
|
np, err := pm2.getNodePricing(ctx)
|
|
np, err := pm2.getNodePricing(ctx)
|
|
|
- if err != nil {
|
|
|
|
|
- t.Fatalf("Failed to get node pricing: %v", err)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ require.NoError(t, err, "getting node pricing")
|
|
|
|
|
+ require.NotNil(t, np, "expected node pricing to be persisted")
|
|
|
|
|
|
|
|
- if np == nil {
|
|
|
|
|
- t.Fatal("Expected node pricing to be persisted")
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ cp, err := pm2.getClusterPricing(ctx)
|
|
|
|
|
+ require.NoError(t, err, "getting cluster pricing")
|
|
|
|
|
+ require.NotNil(t, cp, "expected cluster pricing to be persisted")
|
|
|
|
|
+
|
|
|
|
|
+ netp, err := pm2.getNetworkPricing(ctx)
|
|
|
|
|
+ require.NoError(t, err, "getting network pricing")
|
|
|
|
|
+ require.NotNil(t, netp, "expected network pricing to be persisted")
|
|
|
|
|
+
|
|
|
|
|
+ vp, err := pm2.getPersistentVolumePricing(ctx)
|
|
|
|
|
+ require.NoError(t, err, "getting volume pricing")
|
|
|
|
|
+ require.NotNil(t, vp, "expected volume pricing to be persisted")
|
|
|
|
|
+
|
|
|
|
|
+ sp, err := pm2.getServicePricing(ctx)
|
|
|
|
|
+ require.NoError(t, err, "getting service pricing")
|
|
|
|
|
+ require.NotNil(t, sp, "expected service pricing to be persisted")
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -152,6 +204,50 @@ func testDefaultPricing(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
if volumePrice.Price != DefaultPersistentVolumePricePerGiBHour {
|
|
if volumePrice.Price != DefaultPersistentVolumePricePerGiBHour {
|
|
|
t.Errorf("Expected volume price to be %f, got %f", DefaultPersistentVolumePricePerGiBHour, volumePrice.Price)
|
|
t.Errorf("Expected volume price to be %f, got %f", DefaultPersistentVolumePricePerGiBHour, volumePrice.Price)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // Test default cluster pricing
|
|
|
|
|
+ cp, err := pm.getClusterPricing(ctx)
|
|
|
|
|
+ require.NoError(t, err, "getting cluster pricing")
|
|
|
|
|
+ require.NotNil(t, cp, "expected cluster pricing to exist")
|
|
|
|
|
+
|
|
|
|
|
+ clusterPrice, ok := cp.Prices[pricing.ResourceCluster]
|
|
|
|
|
+ require.True(t, ok, "expected to find cluster pricing")
|
|
|
|
|
+ require.Equal(t, DefaultClusterPricePerHour, clusterPrice.Price)
|
|
|
|
|
+ require.Equal(t, unit.Hour, clusterPrice.Unit)
|
|
|
|
|
+
|
|
|
|
|
+ // Test default network pricing. RAM, egress types, etc. all share the GiB
|
|
|
|
|
+ // unit, so the Resource key distinguishes them.
|
|
|
|
|
+ netp, err := pm.getNetworkPricing(ctx)
|
|
|
|
|
+ require.NoError(t, err, "getting network pricing")
|
|
|
|
|
+ require.NotNil(t, netp, "expected network pricing to exist")
|
|
|
|
|
+
|
|
|
|
|
+ networkChecks := []struct {
|
|
|
|
|
+ resource pricing.Resource
|
|
|
|
|
+ want float64
|
|
|
|
|
+ }{
|
|
|
|
|
+ {pricing.ResourceLocalEgress, DefaultNetworkLocalEgressPricePerGiB},
|
|
|
|
|
+ {pricing.ResourceCrossZoneEgress, DefaultNetworkCrossZoneEgressPricePerGiB},
|
|
|
|
|
+ {pricing.ResourceCrossRegionEgress, DefaultNetworkCrossRegionEgressPricePerGiB},
|
|
|
|
|
+ {pricing.ResourceInternetEgress, DefaultNetworkInternetEgressPricePerGiB},
|
|
|
|
|
+ {pricing.ResourceNATGatewayEgress, DefaultNetworkNATGatewayEgressPricePerGiB},
|
|
|
|
|
+ {pricing.ResourceNATGatewayIngress, DefaultNetworkNATGatewayIngressPricePerGiB},
|
|
|
|
|
+ }
|
|
|
|
|
+ for _, c := range networkChecks {
|
|
|
|
|
+ price, ok := netp.Prices[c.resource]
|
|
|
|
|
+ require.Truef(t, ok, "expected to find %s pricing", c.resource)
|
|
|
|
|
+ require.Equalf(t, c.want, price.Price, "price for %s", c.resource)
|
|
|
|
|
+ require.Equalf(t, unit.GiB, price.Unit, "unit for %s", c.resource)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Test default service pricing
|
|
|
|
|
+ sp, err := pm.getServicePricing(ctx)
|
|
|
|
|
+ require.NoError(t, err, "getting service pricing")
|
|
|
|
|
+ require.NotNil(t, sp, "expected service pricing to exist")
|
|
|
|
|
+
|
|
|
|
|
+ servicePrice, ok := sp.Prices[pricing.ResourceService]
|
|
|
|
|
+ require.True(t, ok, "expected to find service pricing")
|
|
|
|
|
+ require.Equal(t, DefaultServicePricePerHour, servicePrice.Price)
|
|
|
|
|
+ require.Equal(t, unit.Hour, servicePrice.Unit)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// testSetNodePricePerCPUCoreHour tests the SetNodePricePerCPUCoreHour function
|
|
// testSetNodePricePerCPUCoreHour tests the SetNodePricePerCPUCoreHour function
|
|
@@ -254,7 +350,7 @@ func testSetNodePricePerLocalDiskGiBHour(t *testing.T, ctx context.Context, pm *
|
|
|
func testSetVolumePricePerStorageGiBHour(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
func testSetVolumePricePerStorageGiBHour(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
newPrice := 0.0003
|
|
newPrice := 0.0003
|
|
|
|
|
|
|
|
- err := pm.SetVolumePricePerStorageGiBHour(ctx, newPrice)
|
|
|
|
|
|
|
+ err := pm.SetPersistentVolumePricePerStorageGiBHour(ctx, newPrice)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
t.Fatalf("Failed to set volume storage price: %v", err)
|
|
t.Fatalf("Failed to set volume storage price: %v", err)
|
|
|
}
|
|
}
|
|
@@ -364,6 +460,222 @@ func testNewVolumePricingReader(t *testing.T, ctx context.Context, pm *PricingMo
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// testMetadata verifies the source identity accessors.
|
|
|
|
|
+func testMetadata(t *testing.T, pm *PricingModule) {
|
|
|
|
|
+ require.Equal(t, SourceKind, pm.SourceKind())
|
|
|
|
|
+ require.Equal(t, SourceName, pm.SourceName())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testGetPricingSet verifies GetPricingSet returns a populated set.
|
|
|
|
|
+func testGetPricingSet(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ ps, err := pm.GetPricingSet(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, ps)
|
|
|
|
|
+ require.False(t, ps.IsEmpty())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testPublicGetters exercises the public Get*Pricing wrappers, which apply a
|
|
|
|
|
+// nil-check around the private getters.
|
|
|
|
|
+func testPublicGetters(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ cp, err := pm.GetClusterPricing(ctx, pricing.ClusterPricingProperties{})
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, cp)
|
|
|
|
|
+
|
|
|
|
|
+ netp, err := pm.GetNetworkPricing(ctx, pricing.NetworkPricingProperties{})
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, netp)
|
|
|
|
|
+
|
|
|
|
|
+ np, err := pm.GetNodePricing(ctx, pricing.NodePricingProperties{})
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, np)
|
|
|
|
|
+
|
|
|
|
|
+ vp, err := pm.GetPersistentVolumePricing(ctx, pricing.PersistentVolumePricingProperties{})
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, vp)
|
|
|
|
|
+
|
|
|
|
|
+ sp, err := pm.GetServicePricing(ctx, pricing.ServicePricingProperties{})
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, sp)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testSetClusterPricePerHour tests the SetClusterPricePerHour function.
|
|
|
|
|
+func testSetClusterPricePerHour(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ newPrice := 1.25
|
|
|
|
|
+
|
|
|
|
|
+ err := pm.SetClusterPricePerHour(ctx, newPrice)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ cp, err := pm.getClusterPricing(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ price, ok := cp.Prices[pricing.ResourceCluster]
|
|
|
|
|
+ require.True(t, ok, "expected to find cluster pricing")
|
|
|
|
|
+ require.Equal(t, newPrice, price.Price)
|
|
|
|
|
+ require.Equal(t, unit.Hour, price.Unit)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testSetNetworkPrices tests each network setter. Each case verifies that only
|
|
|
|
|
+// the targeted Resource changes and that the other network prices are left
|
|
|
|
|
+// intact, guarding against a setter writing the wrong Resource key.
|
|
|
|
|
+func testSetNetworkPrices(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ cases := []struct {
|
|
|
|
|
+ name string
|
|
|
|
|
+ resource pricing.Resource
|
|
|
|
|
+ newPrice float64
|
|
|
|
|
+ set func(context.Context, float64) error
|
|
|
|
|
+ }{
|
|
|
|
|
+ {"LocalEgress", pricing.ResourceLocalEgress, 0.002, pm.SetNetworkLocalEgressPricePerGiB},
|
|
|
|
|
+ {"CrossZoneEgress", pricing.ResourceCrossZoneEgress, 0.012, pm.SetNetworkCrossZoneEgressPricePerGiB},
|
|
|
|
|
+ {"CrossRegionEgress", pricing.ResourceCrossRegionEgress, 0.022, pm.SetNetworkCrossRegionEgressPricePerGiB},
|
|
|
|
|
+ {"InternetEgress", pricing.ResourceInternetEgress, 0.111, pm.SetNetworkInternetEgressPricePerGiB},
|
|
|
|
|
+ {"NATGatewayEgress", pricing.ResourceNATGatewayEgress, 0.055, pm.SetNetworkNATGatewayEgressPricePerGiB},
|
|
|
|
|
+ {"NATGatewayIngress", pricing.ResourceNATGatewayIngress, 0.066, pm.SetNetworkNATGatewayIngressPricePerGiB},
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for _, c := range cases {
|
|
|
|
|
+ t.Run(c.name, func(t *testing.T) {
|
|
|
|
|
+ // Snapshot existing prices so we can verify non-targeted
|
|
|
|
|
+ // resources are untouched.
|
|
|
|
|
+ before, err := pm.getNetworkPricing(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ prev := make(map[pricing.Resource]float64, len(before.Prices))
|
|
|
|
|
+ for r, p := range before.Prices {
|
|
|
|
|
+ prev[r] = p.Price
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ require.NoError(t, c.set(ctx, c.newPrice))
|
|
|
|
|
+
|
|
|
|
|
+ after, err := pm.getNetworkPricing(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ price, ok := after.Prices[c.resource]
|
|
|
|
|
+ require.Truef(t, ok, "expected to find %s pricing", c.resource)
|
|
|
|
|
+ require.Equal(t, c.newPrice, price.Price)
|
|
|
|
|
+ require.Equal(t, unit.GiB, price.Unit)
|
|
|
|
|
+
|
|
|
|
|
+ // All other network resources must be unchanged.
|
|
|
|
|
+ for r, want := range prev {
|
|
|
|
|
+ if r == c.resource {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+ require.Equalf(t, want, after.Prices[r].Price, "unexpected change to %s", r)
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testSetServicePricePerHour tests the SetServicePricePerHour function.
|
|
|
|
|
+func testSetServicePricePerHour(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ newPrice := 0.05
|
|
|
|
|
+
|
|
|
|
|
+ err := pm.SetServicePricePerHour(ctx, newPrice)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ sp, err := pm.getServicePricing(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ price, ok := sp.Prices[pricing.ResourceService]
|
|
|
|
|
+ require.True(t, ok, "expected to find service pricing")
|
|
|
|
|
+ require.Equal(t, newPrice, price.Price)
|
|
|
|
|
+ require.Equal(t, unit.Hour, price.Unit)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testChecksum verifies the checksum is non-empty, deterministic, and sensitive
|
|
|
|
|
+// to pricing mutations.
|
|
|
|
|
+func testChecksum(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ sum1, err := pm.Checksum(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotEmpty(t, sum1)
|
|
|
|
|
+
|
|
|
|
|
+ // Deterministic: recomputing without mutation yields the same checksum.
|
|
|
|
|
+ sum2, err := pm.Checksum(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.Equal(t, sum1, sum2)
|
|
|
|
|
+
|
|
|
|
|
+ // Sensitive: mutating pricing changes the checksum.
|
|
|
|
|
+ require.NoError(t, pm.SetServicePricePerHour(ctx, sum1Sentinel))
|
|
|
|
|
+
|
|
|
|
|
+ sum3, err := pm.Checksum(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotEqual(t, sum1, sum3, "expected checksum to change after a price mutation")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// sum1Sentinel is an arbitrary price unlikely to match the existing service
|
|
|
|
|
+// price, used to force a checksum change.
|
|
|
|
|
+const sum1Sentinel = 0.0419
|
|
|
|
|
+
|
|
|
|
|
+// testNewClusterPricingReader verifies the cluster pricing reader yields exactly
|
|
|
|
|
+// one non-nil element.
|
|
|
|
|
+func testNewClusterPricingReader(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ rdr, err := pm.NewClusterPricingReader(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, rdr)
|
|
|
|
|
+
|
|
|
|
|
+ dst := make([]*pricing.ClusterPricing, 10)
|
|
|
|
|
+ count := 0
|
|
|
|
|
+ for {
|
|
|
|
|
+ n, err := rdr.Read(ctx, dst)
|
|
|
|
|
+ count += n
|
|
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
|
|
+ require.NotNil(t, dst[i], "expected non-nil ClusterPricing")
|
|
|
|
|
+ }
|
|
|
|
|
+ if err == reader.Done {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ require.Equal(t, 1, count, "expected exactly 1 ClusterPricing")
|
|
|
|
|
+ require.NoError(t, rdr.Close())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testNewNetworkPricingReader verifies the network pricing reader yields exactly
|
|
|
|
|
+// one non-nil element.
|
|
|
|
|
+func testNewNetworkPricingReader(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ rdr, err := pm.NewNetworkPricingReader(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, rdr)
|
|
|
|
|
+
|
|
|
|
|
+ dst := make([]*pricing.NetworkPricing, 10)
|
|
|
|
|
+ count := 0
|
|
|
|
|
+ for {
|
|
|
|
|
+ n, err := rdr.Read(ctx, dst)
|
|
|
|
|
+ count += n
|
|
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
|
|
+ require.NotNil(t, dst[i], "expected non-nil NetworkPricing")
|
|
|
|
|
+ }
|
|
|
|
|
+ if err == reader.Done {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ require.Equal(t, 1, count, "expected exactly 1 NetworkPricing")
|
|
|
|
|
+ require.NoError(t, rdr.Close())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// testNewServicePricingReader verifies the service pricing reader yields exactly
|
|
|
|
|
+// one non-nil element.
|
|
|
|
|
+func testNewServicePricingReader(t *testing.T, ctx context.Context, pm *PricingModule) {
|
|
|
|
|
+ rdr, err := pm.NewServicePricingReader(ctx)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotNil(t, rdr)
|
|
|
|
|
+
|
|
|
|
|
+ dst := make([]*pricing.ServicePricing, 10)
|
|
|
|
|
+ count := 0
|
|
|
|
|
+ for {
|
|
|
|
|
+ n, err := rdr.Read(ctx, dst)
|
|
|
|
|
+ count += n
|
|
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
|
|
+ require.NotNil(t, dst[i], "expected non-nil ServicePricing")
|
|
|
|
|
+ }
|
|
|
|
|
+ if err == reader.Done {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ require.Equal(t, 1, count, "expected exactly 1 ServicePricing")
|
|
|
|
|
+ require.NoError(t, rdr.Close())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func newFileStorage(t *testing.T) storage.Storage {
|
|
func newFileStorage(t *testing.T) storage.Storage {
|
|
|
tempDir, err := os.MkdirTemp("", "pricing-test-*")
|
|
tempDir, err := os.MkdirTemp("", "pricing-test-*")
|
|
|
if err != nil {
|
|
if err != nil {
|