Skip to content

Commit 3c01fee

Browse files
committed
Initial version of lib pricing
1 parent e153bc9 commit 3c01fee

File tree

6 files changed

+328
-0
lines changed

6 files changed

+328
-0
lines changed

pkg/pricing/basket.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package pricing
2+
3+
type Basket []Usage
4+
5+
func NewBasket() *Basket {
6+
return &Basket{}
7+
}
8+
9+
func (b *Basket) Add(usage Usage) error {
10+
*b = append(*b, usage)
11+
return nil
12+
}
13+
14+
func (b *Basket) Length() int {
15+
return len(*b)
16+
}

pkg/pricing/basket_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package pricing
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/smartystreets/goconvey/convey"
7+
)
8+
9+
func TestNewBasket(t *testing.T) {
10+
Convey("Testing NewBasket()", t, func() {
11+
basket := NewBasket()
12+
So(basket, ShouldNotBeNil)
13+
So(basket.Length(), ShouldEqual, 0)
14+
})
15+
}
16+
17+
func TestBasket_Add(t *testing.T) {
18+
Convey("Testing Basket.Add", t, func() {
19+
basket := NewBasket()
20+
So(basket, ShouldNotBeNil)
21+
So(basket.Length(), ShouldEqual, 0)
22+
23+
err := basket.Add(NewUsageByPath("/compute/c1/run", 1))
24+
So(err, ShouldBeNil)
25+
So(basket.Length(), ShouldEqual, 1)
26+
27+
err = basket.Add(NewUsageByPath("/compute/c1/run", 42))
28+
So(err, ShouldBeNil)
29+
So(basket.Length(), ShouldEqual, 2)
30+
})
31+
}

pkg/pricing/pricing.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package pricing
2+
3+
type PricingObject struct {
4+
Path string
5+
Identifier string
6+
Currency string
7+
UnitPrice float64
8+
UnitQuantity float64
9+
UnitPriceCap float64
10+
UsageUnit string
11+
UsageGranularity string
12+
}
13+
14+
type PricingList []PricingObject
15+
16+
// CurrentPricing tries to be up-to-date with the real pricing
17+
// we cannot guarantee of these values since we hardcode values for now
18+
// later, we should be able to call a dedicated pricing API
19+
var CurrentPricing PricingList
20+
21+
func init() {
22+
CurrentPricing = PricingList{
23+
{
24+
Path: "/compute/c1/run",
25+
Identifier: "aaaaaaaa-aaaa-4aaa-8aaa-111111111111",
26+
Currency: "EUR",
27+
UnitPrice: 0.012,
28+
UnitQuantity: 60,
29+
UnitPriceCap: 6,
30+
UsageGranularity: "minute",
31+
},
32+
{
33+
Path: "/ip/dynamic",
34+
Identifier: "467116bf-4631-49fb-905b-e07701c2db11",
35+
Currency: "EUR",
36+
UnitPrice: 0.004,
37+
UnitQuantity: 60,
38+
UnitPriceCap: 1.99,
39+
UsageGranularity: "minute",
40+
},
41+
{
42+
Path: "/ip/reserved",
43+
Identifier: "467116bf-4631-49fb-905b-e07701c2db22",
44+
Currency: "EUR",
45+
UnitPrice: 0.004,
46+
UnitQuantity: 60,
47+
UnitPriceCap: 1.99,
48+
UsageGranularity: "minute",
49+
},
50+
{
51+
Path: "/storage/local/ssd/storage",
52+
Identifier: "bbbbbbbb-bbbb-4bbb-8bbb-111111111113",
53+
Currency: "EUR",
54+
UnitPrice: 0.004,
55+
UnitQuantity: 50,
56+
UnitPriceCap: 2,
57+
UsageGranularity: "hour",
58+
},
59+
}
60+
}
61+
62+
// GetByPath returns an object matching a path
63+
func (pl *PricingList) GetByPath(path string) *PricingObject {
64+
for _, object := range *pl {
65+
if object.Path == path {
66+
return &object
67+
}
68+
}
69+
return nil
70+
}
71+
72+
// GetByIdentifier returns an object matching a identifier
73+
func (pl *PricingList) GetByIdentifier(identifier string) *PricingObject {
74+
for _, object := range *pl {
75+
if object.Identifier == identifier {
76+
return &object
77+
}
78+
}
79+
return nil
80+
}

pkg/pricing/pricing_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package pricing
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/smartystreets/goconvey/convey"
7+
)
8+
9+
func TestGetByPath(t *testing.T) {
10+
Convey("Testing GetByPath", t, func() {
11+
object := CurrentPricing.GetByPath("/compute/c1/run")
12+
So(object, ShouldNotBeNil)
13+
So(object.Path, ShouldEqual, "/compute/c1/run")
14+
15+
object = CurrentPricing.GetByPath("/ip/dynamic")
16+
So(object, ShouldNotBeNil)
17+
So(object.Path, ShouldEqual, "/ip/dynamic")
18+
19+
object = CurrentPricing.GetByPath("/dontexists")
20+
So(object, ShouldBeNil)
21+
})
22+
}
23+
24+
func TestGetByIdentifier(t *testing.T) {
25+
Convey("Testing GetByIdentifier", t, func() {
26+
object := CurrentPricing.GetByIdentifier("aaaaaaaa-aaaa-4aaa-8aaa-111111111111")
27+
So(object, ShouldNotBeNil)
28+
So(object.Path, ShouldEqual, "/compute/c1/run")
29+
30+
object = CurrentPricing.GetByIdentifier("dontexists")
31+
So(object, ShouldBeNil)
32+
})
33+
}

pkg/pricing/usage.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package pricing
2+
3+
import "math"
4+
5+
type Usage struct {
6+
PricingObject *PricingObject
7+
Quantity float64
8+
}
9+
10+
func NewUsageByPath(objectPath string, quantity float64) Usage {
11+
return NewUsage(CurrentPricing.GetByPath(objectPath), quantity)
12+
}
13+
14+
func NewUsage(object *PricingObject, quantity float64) Usage {
15+
return Usage{
16+
PricingObject: object,
17+
Quantity: quantity,
18+
}
19+
}
20+
21+
func (u *Usage) BillableQuantity() float64 {
22+
return math.Ceil(math.Max(u.Quantity, 0)/u.PricingObject.UnitQuantity) * u.PricingObject.UnitQuantity
23+
}
24+
25+
func (u *Usage) LostQuantity() float64 {
26+
return u.BillableQuantity() - math.Max(u.Quantity, 0)
27+
}
28+
29+
func (u *Usage) Total() float64 {
30+
total := u.PricingObject.UnitPrice * u.BillableQuantity()
31+
return math.Min(total, u.PricingObject.UnitPriceCap)
32+
}

pkg/pricing/usage_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package pricing
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/smartystreets/goconvey/convey"
7+
)
8+
9+
func TestNewUsageByPath(t *testing.T) {
10+
Convey("Testing NewUsageByPath()", t, func() {
11+
usage := NewUsageByPath("/compute/c1/run", 1)
12+
So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run")
13+
So(usage.Quantity, ShouldEqual, 1)
14+
})
15+
}
16+
17+
func TestNewUsage(t *testing.T) {
18+
Convey("Testing NewUsage()", t, func() {
19+
object := CurrentPricing.GetByPath("/compute/c1/run")
20+
usage := NewUsage(object, 1)
21+
So(usage.PricingObject.Path, ShouldEqual, "/compute/c1/run")
22+
So(usage.Quantity, ShouldEqual, 1)
23+
})
24+
}
25+
26+
func TestUsage_BillableQuantity(t *testing.T) {
27+
Convey("Testing Usage.BillableQuantity()", t, FailureContinues, func() {
28+
object := &PricingObject{
29+
UnitQuantity: 60,
30+
}
31+
usage := NewUsage(object, -1)
32+
So(usage.BillableQuantity(), ShouldEqual, 0)
33+
34+
usage = NewUsage(object, -1000)
35+
So(usage.BillableQuantity(), ShouldEqual, 0)
36+
37+
usage = NewUsage(object, 0)
38+
So(usage.BillableQuantity(), ShouldEqual, 0)
39+
40+
usage = NewUsage(object, 1)
41+
So(usage.BillableQuantity(), ShouldEqual, 60)
42+
43+
usage = NewUsage(object, 59)
44+
So(usage.BillableQuantity(), ShouldEqual, 60)
45+
46+
usage = NewUsage(object, 59.9999)
47+
So(usage.BillableQuantity(), ShouldEqual, 60)
48+
49+
usage = NewUsage(object, 60)
50+
So(usage.BillableQuantity(), ShouldEqual, 60)
51+
52+
usage = NewUsage(object, 60.00001)
53+
So(usage.BillableQuantity(), ShouldEqual, 60*2)
54+
55+
usage = NewUsage(object, 61)
56+
So(usage.BillableQuantity(), ShouldEqual, 60*2)
57+
58+
usage = NewUsage(object, 119)
59+
So(usage.BillableQuantity(), ShouldEqual, 60*2)
60+
61+
usage = NewUsage(object, 121)
62+
So(usage.BillableQuantity(), ShouldEqual, 60*3)
63+
64+
usage = NewUsage(object, 1000)
65+
So(usage.BillableQuantity(), ShouldEqual, 60*17)
66+
})
67+
}
68+
69+
func TestUsage_LostQuantity(t *testing.T) {
70+
Convey("Testing Usage.LostQuantity()", t, FailureContinues, func() {
71+
object := &PricingObject{
72+
UnitQuantity: 60,
73+
}
74+
usage := NewUsage(object, -1)
75+
So(usage.LostQuantity(), ShouldEqual, 0)
76+
77+
usage = NewUsage(object, -1000)
78+
So(usage.LostQuantity(), ShouldEqual, 0)
79+
80+
usage = NewUsage(object, 0)
81+
So(usage.LostQuantity(), ShouldEqual, 0)
82+
83+
usage = NewUsage(object, 1)
84+
So(usage.LostQuantity(), ShouldEqual, 60-1)
85+
86+
usage = NewUsage(object, 59)
87+
So(usage.LostQuantity(), ShouldEqual, 60-59)
88+
89+
// oops error, float64 precision isn't sufficient
90+
// usage = NewUsage(object, 59.9999)
91+
// So(usage.LostQuantity(), ShouldEqual, 60-59.9999)
92+
93+
usage = NewUsage(object, 60)
94+
So(usage.LostQuantity(), ShouldEqual, 0)
95+
96+
usage = NewUsage(object, 60.00001)
97+
So(usage.LostQuantity(), ShouldEqual, 60*2-60.00001)
98+
99+
usage = NewUsage(object, 61)
100+
So(usage.LostQuantity(), ShouldEqual, 60*2-61)
101+
102+
usage = NewUsage(object, 119)
103+
So(usage.LostQuantity(), ShouldEqual, 60*2-119)
104+
105+
usage = NewUsage(object, 121)
106+
So(usage.LostQuantity(), ShouldEqual, 60*3-121)
107+
108+
usage = NewUsage(object, 1000)
109+
So(usage.LostQuantity(), ShouldEqual, 60*17-1000)
110+
})
111+
}
112+
113+
func TestUsage_Total(t *testing.T) {
114+
Convey("Testing Usage.Total()", t, func() {
115+
object := PricingObject{
116+
UnitQuantity: 60,
117+
UnitPrice: 0.012,
118+
UnitPriceCap: 6,
119+
}
120+
121+
usage := NewUsage(&object, -1)
122+
So(usage.Total(), ShouldEqual, 0)
123+
124+
usage = NewUsage(&object, 0)
125+
So(usage.Total(), ShouldEqual, 0)
126+
127+
usage = NewUsage(&object, 1)
128+
So(usage.Total(), ShouldEqual, 0.012*60)
129+
130+
usage = NewUsage(&object, 61)
131+
So(usage.Total(), ShouldEqual, 0.012*120)
132+
133+
usage = NewUsage(&object, 1000)
134+
So(usage.Total(), ShouldEqual, 6)
135+
})
136+
}

0 commit comments

Comments
 (0)