diff --git a/README b/README index 62ae63565..69108b6f8 100644 --- a/README +++ b/README @@ -1,10 +1,9 @@ Rating system designed to be used in VoIP Carriers World. Features -- Fast RatingEngine, holds all rating information into memory -- Unlimited spanned timebased rating: considers both ActiveFrom/RateStartsAt -- Unlimited rate profiles chanining for fallback -- Rating Fields: ConnectFee, RateIn, RateOut -- High accuracy rating: configurable to miliseconds -- Flexible rates storage - plugin based rates loading -- Flexibility to rate various information from the cdrs as rating subject. +* Fast RatingEngine, holds all rating information into memory +* Unlimited spanned timebased rating: considers both ActiveFrom/RateStartsAt +* Unlimited rate profiles chanining for fallback +* Rating Fields: ConnectFee, RateIn, RateOut +* High accuracy rating: configurable to miliseconds +* Flexible rates storage - plugin based rates loading diff --git a/cmd/rater/rater.go b/cmd/rater/rater.go index 39bdb480f..fe6bb6cc6 100644 --- a/cmd/rater/rater.go +++ b/cmd/rater/rater.go @@ -28,7 +28,7 @@ RPC method providing the rating information from the storage. */ func (s *Storage) GetCost(cd timespans.CallDescriptor, reply *timespans.CallCost) (err error) { descriptor := &cd - descriptor.storageGetter = s.sg + descriptor.StorageGetter = s.sg r, e := descriptor.GetCost() *reply, err = *r, e return nil diff --git a/cmd/stress/spansstress/spansstress.go b/cmd/stress/spansstress/spansstress.go index 2e315f2e7..44b542dbe 100644 --- a/cmd/stress/spansstress/spansstress.go +++ b/cmd/stress/spansstress/spansstress.go @@ -34,12 +34,14 @@ func main() { getter, _ := timespans.NewRedisStorage("", 10) defer getter.Close() - + + cd.StorageGetter = getter + i := 0 log.Printf("Runnning %d cycles...", *runs) for j := 0; j < *runs; j++ { - result, _ = cd.GetCost(getter) + result, _ = cd.GetCost() } log.Print(result) diff --git a/timespans/calldesc.go b/timespans/calldesc.go index 399ca8693..cc6307a43 100644 --- a/timespans/calldesc.go +++ b/timespans/calldesc.go @@ -37,7 +37,7 @@ type CallDescriptor struct { CstmId, Subject, DestinationPrefix string TimeStart, TimeEnd time.Time ActivationPeriods []*ActivationPeriod - storageGetter StorageGetter + StorageGetter StorageGetter } /* @@ -57,10 +57,10 @@ func (cd *CallDescriptor) RestoreFromStorage() (destPrefix string, err error) { base := fmt.Sprintf("%s:%s:", cd.CstmId, cd.Subject) destPrefix = cd.DestinationPrefix key := base + destPrefix - values, err := cd.storageGetter.GetActivationPeriods(key) + values, err := cd.StorageGetter.GetActivationPeriods(key) //get for a smaller prefix if the orignal one was not found - for i := len(cd.DestinationPrefix); err != nil && i >= MinPrefixLength; values, err = cd.storageGetter.GetActivationPeriods(key) { + for i := len(cd.DestinationPrefix); err != nil && i >= MinPrefixLength; values, err = cd.StorageGetter.GetActivationPeriods(key) { i-- destPrefix = cd.DestinationPrefix[:i] key = base + destPrefix @@ -92,9 +92,10 @@ Splits the received timespan into sub time spans according to the activation per */ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeSpan) { timespans = append(timespans, firstSpan) - // split on (free) minute buckets - if userBudget, err := cd.storageGetter.GetUserBudget(cd.Subject); err == nil && userBudget != nil { - _, bucketList := userBudget.getSecondsForPrefix(cd.storageGetter, cd.DestinationPrefix) + // split on (free) minute buckets + if userBudget, err := cd.StorageGetter.GetUserBudget(cd.Subject); err == nil && userBudget != nil { + userBudget.mux.RLock() + _, bucketList := userBudget.getSecondsForPrefix(cd.StorageGetter, cd.DestinationPrefix) for _, mb := range bucketList { for i := 0; i < len(timespans); i++ { if timespans[i].MinuteInfo != nil { @@ -108,7 +109,9 @@ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeS } } } + userBudget.mux.RUnlock() } + if firstSpan.MinuteInfo != nil { return // all the timespans are on minutes } @@ -206,7 +209,7 @@ Returns the cost of a second in the present time conditions. func (cd *CallDescriptor) GetMaxSessionTime(maxSessionSeconds int) (seconds int, err error) { _, err = cd.RestoreFromStorage() now := time.Now() - maxDuration, _ := time.ParseDuration(fmt.Sprintf("%ds", maxSessionSeconds)) + maxDuration, _ := time.ParseDuration(fmt.Sprintf("%vs", maxSessionSeconds)) ts := &TimeSpan{TimeStart: now, TimeEnd: now.Add(maxDuration)} timespans := cd.splitTimeSpan(ts) diff --git a/timespans/calldesc_test.go b/timespans/calldesc_test.go index f6fe1103a..2238bc6f8 100644 --- a/timespans/calldesc_test.go +++ b/timespans/calldesc_test.go @@ -12,7 +12,7 @@ func TestKyotoSplitSpans(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} cd.RestoreFromStorage() timespans := cd.splitInTimeSpans() @@ -27,7 +27,7 @@ func TestRedisSplitSpans(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} cd.RestoreFromStorage() timespans := cd.splitInTimeSpans() @@ -43,13 +43,13 @@ func TestKyotoGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Errorf("Expected %v was %v", expected, result) } - cd = &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd = &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ = cd.GetCost() expected = &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 540, ConnectFee: 0} } @@ -60,7 +60,7 @@ func TestRedisGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -74,7 +74,7 @@ func TestMongoGetCost(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -88,7 +88,7 @@ func TestFullDestNotFound(t *testing.T) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256308200", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -102,7 +102,7 @@ func TestMultipleActivationPeriods(t *testing.T) { t1 := time.Date(2012, time.February, 8, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 330, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -118,7 +118,7 @@ func TestSpansMultipleActivationPeriods(t *testing.T) { t1 := time.Date(2012, time.February, 7, 23, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 0, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 360, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -132,7 +132,7 @@ func TestLessThanAMinute(t *testing.T) { t1 := time.Date(2012, time.February, 8, 23, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 23, 50, 30, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 0.5, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -146,7 +146,7 @@ func TestUniquePrice(t *testing.T) { t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 23, 50, 21, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723045326", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723045326", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723", Cost: 60.35, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -160,7 +160,7 @@ func TestPresentSecodCost(t *testing.T) { t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 23, 50, 21, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.getPresentSecondCost() expected := 0.016 if result != expected { @@ -174,7 +174,7 @@ func TestMinutesCost(t *testing.T) { t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 8, 23, 50, 21, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "minutosu", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "minutosu", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "minutosu", DestinationPrefix: "0723", Cost: 0, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { @@ -204,7 +204,7 @@ func BenchmarkRedisRestoring(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.RestoreFromStorage() @@ -218,7 +218,7 @@ func BenchmarkRedisGetCost(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetCost() @@ -232,7 +232,7 @@ func BenchmarkKyotoGetting(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { key := cd.GetKey() @@ -247,7 +247,7 @@ func BenchmarkKyotoRestoring(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.RestoreFromStorage() @@ -261,7 +261,7 @@ func BenchmarkSplitting(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} cd.RestoreFromStorage() b.StartTimer() for i := 0; i < b.N; i++ { @@ -276,7 +276,7 @@ func BenchmarkKyotoGetCost(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetCost() @@ -304,7 +304,7 @@ func BenchmarkMongoGetCost(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) - cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, StorageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetCost() diff --git a/timespans/userbudget.go b/timespans/userbudget.go index f1449c760..6d2140de4 100644 --- a/timespans/userbudget.go +++ b/timespans/userbudget.go @@ -5,6 +5,7 @@ import ( "sort" "strconv" "strings" + "sync" ) /* @@ -18,22 +19,29 @@ type UserBudget struct { TariffPlanId string tariffPlan *TariffPlan MinuteBuckets []*MinuteBucket + mux sync.RWMutex +} + +type AmountTooBig byte + +func (a AmountTooBig) Error() string { + return "Amount excedes budget!" } /* Structure to store minute buckets according to priority, precision or price. */ -type BucketSorter []*MinuteBucket +type bucketsorter []*MinuteBucket -func (bs BucketSorter) Len() int { +func (bs bucketsorter) Len() int { return len(bs) } -func (bs BucketSorter) Swap(i, j int) { +func (bs bucketsorter) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] } -func (bs BucketSorter) Less(j, i int) bool { +func (bs bucketsorter) Less(j, i int) bool { return bs[i].Priority < bs[j].Priority || bs[i].precision < bs[j].precision || bs[i].Price > bs[j].Price @@ -92,14 +100,14 @@ func (ub *UserBudget) getTariffPlan(storage StorageGetter) (tp *TariffPlan) { /* Returns user's avaliable minutes for the specified destination */ -func (ub *UserBudget) getSecondsForPrefix(storage StorageGetter, prefix string) (seconds float64, bucketList BucketSorter) { +func (ub *UserBudget) getSecondsForPrefix(sg StorageGetter, prefix string) (seconds float64, bucketList bucketsorter) { if len(ub.MinuteBuckets) == 0 { log.Print("There are no minute buckets to check for user", ub.Id) return } for _, mb := range ub.MinuteBuckets { - d := mb.getDestination(storage) + d := mb.getDestination(sg) if d == nil { continue } @@ -115,8 +123,60 @@ func (ub *UserBudget) getSecondsForPrefix(storage StorageGetter, prefix string) credit := ub.Credit for _, mb := range bucketList { s := mb.GetSecondsForCredit(credit) - credit -= s + credit -= s * mb.Price seconds += s } return } + +/* +Debits some amount of user's money credit. Returns the remaining credit in user's budget. +*/ +func (ub *UserBudget) debitMoneyBudget(sg StorageGetter, amount float64) float64 { + ub.mux.Lock() + defer ub.mux.Unlock() + ub.Credit -= amount + sg.SetUserBudget(ub) + return ub.Credit +} + +/* +Debits the recived amount of seconds from user's minute buckets. +All the appropriate buckets will be debited until all amount of minutes is consumed. +If the amount is bigger than the sum of all seconds in the minute buckets than nothing will be +debited and an error will be returned. +*/ +func (ub *UserBudget) debitMinutesBudget(sg StorageGetter, amount float64, prefix string) error { + ub.mux.Lock() + defer ub.mux.Unlock() + avaliableNbSeconds, bucketList := ub.getSecondsForPrefix(sg, prefix) + if avaliableNbSeconds < amount { + return new(AmountTooBig) + } + for _, mb := range bucketList { + if mb.Seconds < amount { + amount -= mb.Seconds + mb.Seconds = 0 + } else { + mb.Seconds -= amount + break + } + } + sg.SetUserBudget(ub) + return nil +} + +/* +Debits some amount of user's SMS budget. Returns the remaining SMS in user's budget. +If the amount is bigger than the budget than nothing wil be debited and an error will be returned +*/ +func (ub *UserBudget) debitSMSBuget(sg StorageGetter, amount int) (int, error) { + ub.mux.Lock() + defer ub.mux.Unlock() + if ub.SmsCredit < amount { + return ub.SmsCredit, new(AmountTooBig) + } + ub.SmsCredit -= amount + sg.SetUserBudget(ub) + return ub.SmsCredit, nil +} diff --git a/timespans/userbudget_test.go b/timespans/userbudget_test.go index c123dfafb..d6ebbda78 100644 --- a/timespans/userbudget_test.go +++ b/timespans/userbudget_test.go @@ -91,6 +91,130 @@ func TestUserBudgetMongoStore(t *testing.T) { } } +func TestDebitMoneyBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "o4her", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + result := rifsBudget.debitMoneyBudget(getter, 6) + if rifsBudget.Credit != 15 || result != rifsBudget.Credit { + t.Errorf("Expected %v was %v", 15, rifsBudget.Credit) + } +} + +func TestDebitAllMoneyBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + rifsBudget.debitMoneyBudget(getter, 21) + result := rifsBudget.debitMoneyBudget(getter, 0) + if rifsBudget.Credit != 0 || result != rifsBudget.Credit { + t.Errorf("Expected %v was %v", 0, rifsBudget.Credit) + } +} + +func TestDebitMoreMoneyBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + result := rifsBudget.debitMoneyBudget(getter, 22) + if rifsBudget.Credit != -1 || result != rifsBudget.Credit { + t.Errorf("Expected %v was %v", -1, rifsBudget.Credit) + } +} + +func TestDebitMinuteBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBudget.debitMinutesBudget(getter, 6, "0723") + if b2.Seconds != 94 || err != nil { + t.Log(err) + t.Errorf("Expected %v was %v", 94, b2.Seconds) + } +} + +func TestDebitMultipleBucketsMinuteBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBudget.debitMinutesBudget(getter, 105, "0723") + if b2.Seconds != 0 || b1.Seconds != 5 || err != nil { + t.Log(err) + t.Errorf("Expected %v was %v", 0, b2.Seconds) + } +} + +func TestDebitAllMinuteBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBudget.debitMinutesBudget(getter, 110, "0723") + if b2.Seconds != 0 || b1.Seconds != 0 || err != nil { + t.Log(err) + t.Errorf("Expected %v was %v", 0, b2.Seconds) + } +} + +func TestDebitMoreMinuteBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBudget.debitMinutesBudget(getter, 115, "0723") + if b2.Seconds != 100 || b1.Seconds != 10 || err == nil { + t.Errorf("Expected %v was %v", 1000, b2.Seconds) + } +} + +func TestDebitSMSBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10} + result, err := rifsBudget.debitSMSBuget(getter, 12) + if rifsBudget.SmsCredit != 88 || result != rifsBudget.SmsCredit || err != nil { + t.Errorf("Expected %v was %v", 88, rifsBudget.SmsCredit) + } +} + +func TestDebitAllSMSBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10} + result, err := rifsBudget.debitSMSBuget(getter, 100) + if rifsBudget.SmsCredit != 0 || result != rifsBudget.SmsCredit || err != nil { + t.Errorf("Expected %v was %v", 0, rifsBudget.SmsCredit) + } +} + +func TestDebitMoreSMSBudget(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.0, DestinationId: "nationale"} + b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} + rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10} + result, err := rifsBudget.debitSMSBuget(getter, 110) + if rifsBudget.SmsCredit != 100 || result != rifsBudget.SmsCredit || err == nil { + t.Errorf("Expected %v was %v", 100, rifsBudget.SmsCredit) + } +} + /*********************************** Benchmarks *******************************/ func BenchmarkGetSecondForPrefix(b *testing.B) {