pricingset_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package pricing
  2. import (
  3. "testing"
  4. "github.com/opencost/opencost/core/pkg/model/shared"
  5. "github.com/opencost/opencost/core/pkg/unit"
  6. )
  7. func nodePricing(instanceType string, price float64) *NodePricing {
  8. return &NodePricing{
  9. Properties: NodePricingProperties{
  10. Provider: shared.Provider("AWS"),
  11. Region: "us-east-1",
  12. InstanceType: instanceType,
  13. },
  14. Prices: Prices{
  15. ResourceNode: {Unit: unit.Hour, Price: price},
  16. },
  17. }
  18. }
  19. func pvPricing(volumeType VolumeType, price float64) *PersistentVolumePricing {
  20. return &PersistentVolumePricing{
  21. Properties: PersistentVolumePricingProperties{
  22. Provider: shared.Provider("AWS"),
  23. Region: "us-east-1",
  24. VolumeType: volumeType,
  25. },
  26. Prices: Prices{
  27. ResourceStorage: {Unit: unit.GiBHour, Price: price},
  28. },
  29. }
  30. }
  31. // TestChecksumPriceSensitivity verifies that the checksum changes when only a
  32. // price value changes, even if all properties are identical.
  33. func TestChecksumPriceSensitivity(t *testing.T) {
  34. a := &PricingSet{NodePricing: []*NodePricing{nodePricing("m5.large", 0.096)}}
  35. b := &PricingSet{NodePricing: []*NodePricing{nodePricing("m5.large", 0.192)}}
  36. csA, err := a.Checksum()
  37. if err != nil {
  38. t.Fatalf("unexpected error: %v", err)
  39. }
  40. csB, err := b.Checksum()
  41. if err != nil {
  42. t.Fatalf("unexpected error: %v", err)
  43. }
  44. if csA == csB {
  45. t.Errorf("expected differing checksums for differing prices, got %q for both", csA)
  46. }
  47. }
  48. // TestChecksumOrderStability verifies that the checksum is independent of the
  49. // ordering of pricing slices.
  50. func TestChecksumOrderStability(t *testing.T) {
  51. n1 := nodePricing("m5.large", 0.096)
  52. n2 := nodePricing("m5.xlarge", 0.192)
  53. n3 := nodePricing("m5.2xlarge", 0.384)
  54. forward := &PricingSet{NodePricing: []*NodePricing{n1, n2, n3}}
  55. reverse := &PricingSet{NodePricing: []*NodePricing{n3, n2, n1}}
  56. csForward, err := forward.Checksum()
  57. if err != nil {
  58. t.Fatalf("unexpected error: %v", err)
  59. }
  60. csReverse, err := reverse.Checksum()
  61. if err != nil {
  62. t.Fatalf("unexpected error: %v", err)
  63. }
  64. if csForward != csReverse {
  65. t.Errorf("expected checksum to be order-independent, got %q vs %q", csForward, csReverse)
  66. }
  67. }
  68. // TestChecksumNilReceiver verifies that Checksum handles a nil receiver like
  69. // IsEmpty and Currencies do, rather than panicking.
  70. func TestChecksumNilReceiver(t *testing.T) {
  71. var ps *PricingSet
  72. if _, err := ps.Checksum(); err != nil {
  73. t.Errorf("unexpected error on nil receiver: %v", err)
  74. }
  75. }
  76. // TestIsEmptyAllKinds verifies that a set holding only Cluster/Network/Service
  77. // pricing is not reported empty.
  78. func TestIsEmptyAllKinds(t *testing.T) {
  79. if !(&PricingSet{}).IsEmpty() {
  80. t.Errorf("expected empty set to report empty")
  81. }
  82. cases := map[string]*PricingSet{
  83. "cluster": {ClusterPricing: []*ClusterPricing{{Properties: ClusterPricingProperties{Provider: shared.Provider("AWS")}}}},
  84. "network": {NetworkPricing: []*NetworkPricing{{Properties: NetworkPricingProperties{Provider: shared.Provider("AWS")}}}},
  85. "node": {NodePricing: []*NodePricing{nodePricing("m5.large", 0.096)}},
  86. "volume": {PersistentVolumePricing: []*PersistentVolumePricing{pvPricing(VolumeTypeGP3, 0.0001)}},
  87. "service": {ServicePricing: []*ServicePricing{{Properties: ServicePricingProperties{Provider: shared.Provider("AWS")}}}},
  88. }
  89. for name, ps := range cases {
  90. if ps.IsEmpty() {
  91. t.Errorf("set with only %s pricing should not be empty", name)
  92. }
  93. }
  94. }
  95. // TestMockGetPricingSetAllKinds verifies that the mock's GetPricingSet exposes
  96. // the same kinds as its readers, not just node + persistent volume.
  97. func TestMockGetPricingSetAllKinds(t *testing.T) {
  98. mpm, err := NewMockPricingModule()
  99. if err != nil {
  100. t.Fatalf("unexpected error: %v", err)
  101. }
  102. ps, err := mpm.GetPricingSet(t.Context())
  103. if err != nil {
  104. t.Fatalf("unexpected error: %v", err)
  105. }
  106. if len(ps.NodePricing) != len(mpm.NodePricing) {
  107. t.Errorf("expected %d node pricing, got %d", len(mpm.NodePricing), len(ps.NodePricing))
  108. }
  109. if len(ps.PersistentVolumePricing) != len(mpm.PersistentVolumePricing) {
  110. t.Errorf("expected %d volume pricing, got %d", len(mpm.PersistentVolumePricing), len(ps.PersistentVolumePricing))
  111. }
  112. if len(ps.ClusterPricing) != len(mpm.ClusterPricing) {
  113. t.Errorf("expected %d cluster pricing, got %d", len(mpm.ClusterPricing), len(ps.ClusterPricing))
  114. }
  115. if len(ps.NetworkPricing) != len(mpm.NetworkPricing) {
  116. t.Errorf("expected %d network pricing, got %d", len(mpm.NetworkPricing), len(ps.NetworkPricing))
  117. }
  118. if len(ps.ServicePricing) != len(mpm.ServicePricing) {
  119. t.Errorf("expected %d service pricing, got %d", len(mpm.ServicePricing), len(ps.ServicePricing))
  120. }
  121. }