From 0a1f1afd758ccee578381533fe3454858bb43cf3 Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Thu, 7 Jun 2012 21:41:06 +0300 Subject: [PATCH] work in progress --- .gitignore | 1 + timespans/{volumediscounts.go => actions.go} | 80 +++-- timespans/activationperiod_test.go | 12 +- timespans/callcost.go | 4 +- timespans/callcost_test.go | 16 +- timespans/calldesc.go | 141 ++++---- timespans/calldesc_test.go | 90 ++--- timespans/minute_buckets.go | 11 +- timespans/storage_interface.go | 4 +- timespans/storage_kyoto.go | 6 +- timespans/storage_mongo.go | 12 +- timespans/storage_redis.go | 6 +- timespans/tariff_plans.go | 73 +--- timespans/timespans.go | 14 +- timespans/userbalance.go | 261 ++++++++++++++ ...userbudget_test.go => userbalance_test.go} | 282 +++++++-------- timespans/userbudget.go | 326 ------------------ 17 files changed, 614 insertions(+), 725 deletions(-) rename timespans/{volumediscounts.go => actions.go} (52%) create mode 100644 timespans/userbalance.go rename timespans/{userbudget_test.go => userbalance_test.go} (54%) delete mode 100644 timespans/userbudget.go diff --git a/.gitignore b/.gitignore index 3a16c4fb4..f4c81506b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ a.out *workspace docs/_* bin +.idea diff --git a/timespans/volumediscounts.go b/timespans/actions.go similarity index 52% rename from timespans/volumediscounts.go rename to timespans/actions.go index dbb02a6d3..84ca0fdaa 100644 --- a/timespans/volumediscounts.go +++ b/timespans/actions.go @@ -19,46 +19,55 @@ along with this program. If not, see package timespans import ( -//"log" + "bytes" + "encoding/gob" ) // Amount of a trafic of a certain type (TOR) -type TrafficVolume struct { +type UnitsCounter struct { + Direction string TOR string Units float64 + Weight float64 DestinationId string + destination *Destination } -// Volume discount to be applyed after the Units are reached -// in a certain time period. -type VolumeDiscount struct { - TOR string - DestinationsId string - Units float64 - AbsoulteValue float64 // either this or the procentage below - DiscountProcentage float64 // use only one - Weight float64 +// Structure to store actions according to weight +type countersorter []*UnitsCounter + +func (s countersorter) Len() int { + return len(s) +} + +func (s countersorter) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s countersorter) Less(j, i int) bool { + return s[i].Weight < s[j].Weight } /* Returns the destination loading it from the storage if necessary. */ -func (vd *VolumeDiscount) getDestination(storage StorageGetter) (dest *Destination) { - if vd.destination == nil { - vd.destination, _ = storage.GetDestination(vd.DestinationId) +func (uc *UnitsCounter) getDestination() (dest *Destination) { + if uc.destination == nil { + uc.destination, _ = storageGetter.GetDestination(uc.DestinationId) } - return vd.destination + return uc.destination } /* Structure to be filled for each tariff plan with the bonus value for received calls minutes. */ -type Bonus struct { +type Action struct { Direction string TOR string Units float64 balanceMap map[string]float64 MinuteBuckets []*MinuteBucket + Weight float64 DestinationsId string destination *Destination } @@ -66,27 +75,30 @@ type Bonus struct { /* Serializes the tariff plan for the storage. Used for key-value storages. */ -func (rcb *Bonus) store() (result string) { - result += strconv.FormatFloat(rcb.Credit, 'f', -1, 64) + "," - result += strconv.FormatFloat(rcb.SmsCredit, 'f', -1, 64) + "," - result += strconv.FormatFloat(rcb.Traffic, 'f', -1, 64) - if rcb.MinuteBucket != nil { - result += "," - result += rcb.MinuteBucket.store() - } - return +func (a *Action) store() (result string) { + buf := new(bytes.Buffer) + gob.NewEncoder(buf).Encode(a) + return buf.String() } /* De-serializes the tariff plan for the storage. Used for key-value storages. */ -func (rcb *Bonus) restore(input string) { - elements := strings.Split(input, ",") - rcb.Credit, _ = strconv.ParseFloat(elements[0], 64) - rcb.SmsCredit, _ = strconv.ParseFloat(elements[1], 64) - rcb.Traffic, _ = strconv.ParseFloat(elements[2], 64) - if len(elements) > 3 { - rcb.MinuteBucket = &MinuteBucket{} - rcb.MinuteBucket.restore(elements[3]) - } +func (a *Action) restore(input string) { + gob.NewDecoder(bytes.NewBuffer([]byte(input))).Decode(a) +} + +// Structure to store actions according to weight +type actionsorter []*Action + +func (s actionsorter) Len() int { + return len(s) +} + +func (s actionsorter) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s actionsorter) Less(j, i int) bool { + return s[i].Weight < s[j].Weight } diff --git a/timespans/activationperiod_test.go b/timespans/activationperiod_test.go index c14e8cd7f..5026a066c 100644 --- a/timespans/activationperiod_test.go +++ b/timespans/activationperiod_test.go @@ -31,7 +31,7 @@ func TestApRestoreKyoto(t *testing.T) { cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", - DestinationPrefix: "0257", + Destination: "0257", storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 2 { @@ -45,7 +45,7 @@ func TestApRestoreRedis(t *testing.T) { cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", - DestinationPrefix: "0257", + Destination: "0257", storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 2 { @@ -78,7 +78,7 @@ func TestFallbackDirect(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0745", storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0745", storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 1 { t.Error("Error restoring activation periods: ", cd.ActivationPeriods) @@ -89,7 +89,7 @@ func TestFallbackWithBackTrace(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0745121", storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0745121", storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 1 { t.Error("Error restoring activation periods: ", cd.ActivationPeriods) @@ -100,7 +100,7 @@ func TestFallbackDefault(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "00000", storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "00000", storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 1 { t.Error("Error restoring activation periods: ", cd.ActivationPeriods) @@ -111,7 +111,7 @@ func TestFallbackNoInfiniteLoop(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0721", storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0721", storageGetter: getter} cd.SearchStorageForPrefix() if len(cd.ActivationPeriods) != 0 { t.Error("Error restoring activation periods: ", cd.ActivationPeriods) diff --git a/timespans/callcost.go b/timespans/callcost.go index 9ed423119..3c55516bb 100644 --- a/timespans/callcost.go +++ b/timespans/callcost.go @@ -26,14 +26,14 @@ import ( The output structure that will be returned with the call cost information. */ type CallCost struct { - TOR, Tenant, Subject, DestinationPrefix string + TOR, Tenant, Subject, Destination string Cost, ConnectFee float64 Timespans []*TimeSpan } // Pretty printing for call cost func (cc *CallCost) String() (r string) { - r = fmt.Sprintf("%v[%v] : %s -> %s (", cc.Cost, cc.ConnectFee, cc.Subject, cc.DestinationPrefix) + r = fmt.Sprintf("%v[%v] : %s -> %s (", cc.Cost, cc.ConnectFee, cc.Subject, cc.Destination) for _, ts := range cc.Timespans { r += fmt.Sprintf(" %v,", ts.GetDuration()) } diff --git a/timespans/callcost_test.go b/timespans/callcost_test.go index 9bb203f8e..25e338be5 100644 --- a/timespans/callcost_test.go +++ b/timespans/callcost_test.go @@ -29,14 +29,14 @@ func TestSingleResultMerge(t *testing.T) { defer getter.Close() t1 := time.Date(2012, time.February, 2, 17, 00, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 17, 01, 0, 0, time.UTC) - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc1, _ := cd.GetCost() if cc1.Cost != 12 { t.Errorf("expected 12 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 17, 01, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 17, 02, 0, 0, time.UTC) - cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc2, _ := cd.GetCost() if cc2.Cost != 12 { t.Errorf("expected 12 was %v", cc2.Cost) @@ -55,14 +55,14 @@ func TestMultipleResultMerge(t *testing.T) { defer getter.Close() t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC) - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc1, _ := cd.GetCost() if cc1.Cost != 12 { t.Errorf("expected 12 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 18, 00, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) - cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc2, _ := cd.GetCost() if cc2.Cost != 6 { t.Errorf("expected 6 was %v", cc2.Cost) @@ -81,14 +81,14 @@ func TestMultipleInputLeftMerge(t *testing.T) { defer getter.Close() t1 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc1, _ := cd.GetCost() if cc1.Cost != 18 { t.Errorf("expected 12 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 02, 0, 0, time.UTC) - cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc2, _ := cd.GetCost() if cc2.Cost != 6 { t.Errorf("expected 6 was %v", cc2.Cost) @@ -107,14 +107,14 @@ func TestMultipleInputRightMerge(t *testing.T) { defer getter.Close() t1 := time.Date(2012, time.February, 2, 17, 58, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc1, _ := cd.GetCost() if cc1.Cost != 12 { t.Errorf("expected 12 was %v", cc1.Cost) } t1 = time.Date(2012, time.February, 2, 17, 59, 0, 0, time.UTC) t2 = time.Date(2012, time.February, 2, 18, 01, 0, 0, time.UTC) - cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cc2, _ := cd.GetCost() if cc2.Cost != 18 { t.Errorf("expected 18 was %v", cc2.Cost) diff --git a/timespans/calldesc.go b/timespans/calldesc.go index ac38b38af..dbbbab063 100644 --- a/timespans/calldesc.go +++ b/timespans/calldesc.go @@ -53,15 +53,14 @@ func round(val float64, prec int) float64 { The input stucture that contains call information. */ type CallDescriptor struct { - TOR string - Tenant, Subject, DestinationPrefix string - TimeStart, TimeEnd time.Time - Amount float64 - FallbackSubject string // the subject to check for destination if not found on primary subject - ActivationPeriods []*ActivationPeriod - FallbackKey string - storageGetter StorageGetter - userBudget *UserBudget + TOR string + Tenant, Subject, Destination string + TimeStart, TimeEnd time.Time + Amount float64 + FallbackSubject string // the subject to check for destination if not found on primary subject + ActivationPeriods []*ActivationPeriod + FallbackKey string + userBalance *UserBalance } /* @@ -74,20 +73,20 @@ func (cd *CallDescriptor) AddActivationPeriod(aps ...*ActivationPeriod) { } /* -Gets and caches the user budget information. +Gets and caches the user balance information. */ -func (cd *CallDescriptor) getUserBudget() (ub *UserBudget, err error) { - if cd.userBudget == nil { - cd.userBudget, err = cd.storageGetter.GetUserBudget(cd.Subject) +func (cd *CallDescriptor) getUserBalance() (ub *UserBalance, err error) { + if cd.userBalance == nil { + cd.userBalance, err = storageGetter.GetUserBalance(cd.Subject) } - return cd.userBudget, err + return cd.userBalance, err } /* Exported method to set the storage getter. */ -func (cd *CallDescriptor) SetStorageGetter(sg StorageGetter) { - cd.storageGetter = sg +func SetStorageGetter(sg StorageGetter) { + storageGetter = sg } /* @@ -96,7 +95,7 @@ Restores the activation periods for the specified prefix from storage. func (cd *CallDescriptor) SearchStorageForPrefix() (destPrefix string, err error) { cd.ActivationPeriods = make([]*ActivationPeriod, 0) base := fmt.Sprintf("%s:%s:", cd.Tenant, cd.Subject) - destPrefix = cd.DestinationPrefix + destPrefix = cd.Destination key := base + destPrefix values, err := cd.getActivationPeriodsOrFallback(key, base, destPrefix, 1) if err != nil { @@ -115,7 +114,7 @@ func (cd *CallDescriptor) getActivationPeriodsOrFallback(key, base, destPrefix s err = errors.New("Max fallback recursion depth reached!" + key) return } - values, fallbackKey, err := cd.storageGetter.GetActivationPeriodsOrFallback(key) + values, fallbackKey, err := storageGetter.GetActivationPeriodsOrFallback(key) if fallbackKey != "" { base = fallbackKey + ":" key = base + destPrefix @@ -123,7 +122,7 @@ func (cd *CallDescriptor) getActivationPeriodsOrFallback(key, base, destPrefix s return cd.getActivationPeriodsOrFallback(key, base, destPrefix, recursionDepth) } //get for a smaller prefix if the orignal one was not found - for i := len(cd.DestinationPrefix); err != nil || fallbackKey != ""; { + for i := len(cd.Destination); err != nil || fallbackKey != ""; { if fallbackKey != "" { base = fallbackKey + ":" key = base + destPrefix @@ -132,12 +131,12 @@ func (cd *CallDescriptor) getActivationPeriodsOrFallback(key, base, destPrefix s } i-- if i >= MinPrefixLength { - destPrefix = cd.DestinationPrefix[:i] + destPrefix = cd.Destination[:i] key = base + destPrefix } else { break } - values, fallbackKey, err = cd.storageGetter.GetActivationPeriodsOrFallback(key) + values, fallbackKey, err = storageGetter.GetActivationPeriodsOrFallback(key) } return } @@ -147,7 +146,7 @@ Constructs the key for the storage lookup. The prefixLen is limiting the length of the destination prefix. */ func (cd *CallDescriptor) GetKey() string { - return fmt.Sprintf("%s:%s:%s", cd.Tenant, cd.Subject, cd.DestinationPrefix) + return fmt.Sprintf("%s:%s:%s", cd.Tenant, cd.Subject, cd.Destination) } /* @@ -163,9 +162,9 @@ 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.getUserBudget(); err == nil && userBudget != nil { - userBudget.mux.RLock() - _, bucketList := userBudget.getSecondsForPrefix(cd.storageGetter, cd.DestinationPrefix) + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + userBalance.mux.RLock() + _, bucketList := userBalance.getSecondsForPrefix(cd.Destination) for _, mb := range bucketList { for i := 0; i < len(timespans); i++ { if timespans[i].MinuteInfo != nil { @@ -179,7 +178,7 @@ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeS } } } - userBudget.mux.RUnlock() + userBalance.mux.RUnlock() } if firstSpan.MinuteInfo != nil { @@ -247,12 +246,12 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { cost += ts.getCost(cd) } cc := &CallCost{TOR: cd.TOR, - Tenant: cd.Tenant, - Subject: cd.Subject, - DestinationPrefix: destPrefix, - Cost: cost, - ConnectFee: connectionFee, - Timespans: timespans} + Tenant: cd.Tenant, + Subject: cd.Subject, + Destination: destPrefix, + Cost: cost, + ConnectFee: connectionFee, + Timespans: timespans} return cc, err } @@ -275,7 +274,7 @@ func (cd *CallDescriptor) getPresentSecondCost() (cost float64, err error) { } /* -Returns the approximate max allowed session for user budget. It will try the max amount received in the call descriptor +Returns the approximate max allowed session for user balance. It will try the max amount received in the call descriptor and will decrease it by 10% for nine times. So if the user has little credit it will still allow 10% of the initial amount. If the user has no credit then it will return 0. */ @@ -283,19 +282,19 @@ func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) { _, err = cd.SearchStorageForPrefix() now := time.Now() availableCredit, availableSeconds := 0.0, 0.0 - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - if userBudget.Type == UB_TYPE_POSTPAID { + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + if userBalance.Type == UB_TYPE_POSTPAID { return -1, nil } else { - userBudget.mux.RLock() - availableCredit = userBudget.BalanceMap[CREDIT] - availableSeconds, _ = userBudget.getSecondsForPrefix(cd.storageGetter, cd.DestinationPrefix) - userBudget.mux.RUnlock() + userBalance.mux.RLock() + availableCredit = userBalance.BalanceMap[CREDIT] + availableSeconds, _ = userBalance.getSecondsForPrefix(cd.Destination) + userBalance.mux.RUnlock() } } else { return cd.Amount, err } - // check for zero budget + // check for zero balance if availableCredit == 0 { return availableSeconds, nil } @@ -323,19 +322,19 @@ func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) { } // Interface method used to add/substract an amount of cents or bonus seconds (as returned by GetCost method) -// from user's money budget. +// from user's money balance. func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { cc, err = cd.GetCost() if err != nil { log.Printf("error getting cost %v", err) } - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { if cc.Cost != 0 || cc.ConnectFee != 0 { - userBudget.debitMoneyBudget(cd.storageGetter, cc.Cost+cc.ConnectFee) + userBalance.debitMoneyBalance(cc.Cost + cc.ConnectFee) } for _, ts := range cc.Timespans { if ts.MinuteInfo != nil { - userBudget.debitMinutesBudget(cd.storageGetter, ts.MinuteInfo.Quantity, cd.DestinationPrefix) + userBalance.debitMinutesBalance(ts.MinuteInfo.Quantity, cd.Destination) } } } @@ -343,79 +342,57 @@ func (cd *CallDescriptor) Debit() (cc *CallCost, err error) { } /* -Interface method used to add/substract an amount of cents from user's money budget. +Interface method used to add/substract an amount of cents from user's money balance. The amount filed has to be filled in call descriptor. */ func (cd *CallDescriptor) DebitCents() (left float64, err error) { - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - return userBudget.debitMoneyBudget(cd.storageGetter, cd.Amount), nil + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + return userBalance.debitMoneyBalance(cd.Amount), nil } return 0.0, err } /* -Interface method used to add/substract an amount of units from user's sms budget. +Interface method used to add/substract an amount of units from user's sms balance. The amount filed has to be filled in call descriptor. */ func (cd *CallDescriptor) DebitSMS() (left float64, err error) { - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - return userBudget.debitSMSBuget(cd.storageGetter, cd.Amount) + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + return userBalance.debitSMSBuget(cd.Amount) } return 0, err } /* -Interface method used to add/substract an amount of seconds from user's minutes budget. +Interface method used to add/substract an amount of seconds from user's minutes balance. The amount filed has to be filled in call descriptor. */ func (cd *CallDescriptor) DebitSeconds() (err error) { - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - return userBudget.debitMinutesBudget(cd.storageGetter, cd.Amount, cd.DestinationPrefix) - } - return err -} - -/* -Interface method used to add an amount to the accumulated placed call seconds -to be used for volume discount. -The amount filed has to be filled in call descriptor. -*/ -func (cd *CallDescriptor) AddVolumeDiscountSeconds() (err error) { - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - return userBudget.addVolumeDiscountSeconds(cd.storageGetter, cd.Amount) - } - return err -} - -/* -Resets the accumulated volume discount seconds (to zero). -*/ -func (cd *CallDescriptor) ResetVolumeDiscountSeconds() (err error) { - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - return userBudget.resetVolumeDiscountSeconds(cd.storageGetter) + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + return userBalance.debitMinutesBalance(cd.Amount, cd.Destination) } return err } /* Adds the specified amount of seconds to the received call seconds. When the threshold specified -in the user's tariff plan is reached then the received call budget is reseted and the bonus +in the user's tariff plan is reached then the received call balance is reseted and the bonus specified in the tariff plan is applied. The amount filed has to be filled in call descriptor. */ func (cd *CallDescriptor) AddRecievedCallSeconds() (err error) { - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - return userBudget.addReceivedCallSeconds(cd.storageGetter, cd.Amount) + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + return userBalance.addReceivedCallSeconds(INBOUND, cd.TOR, cd.Destination, cd.Amount) } return err } /* -Resets user budgets value to the amounts specified in the tariff plan. +Resets user balances value to the amounts specified in the tariff plan. */ -func (cd *CallDescriptor) ResetUserBudget() (err error) { - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - return userBudget.resetUserBudget(cd.storageGetter) +func (cd *CallDescriptor) ResetUserBalance() (err error) { + if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + return userBalance.resetUserBalance() } return err } diff --git a/timespans/calldesc_test.go b/timespans/calldesc_test.go index 46b9e09b3..88c767131 100644 --- a/timespans/calldesc_test.go +++ b/timespans/calldesc_test.go @@ -40,7 +40,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cd.SearchStorageForPrefix() timespans := cd.splitInTimeSpans() @@ -56,7 +56,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cd.SearchStorageForPrefix() timespans := cd.splitInTimeSpans() @@ -72,15 +72,15 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Errorf("Expected %v was %v", expected, result) } - cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd = &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ = cd.GetCost() - expected = &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 540, ConnectFee: 0} + expected = &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 540, ConnectFee: 0} } func TestRedisGetCost(t *testing.T) { @@ -89,9 +89,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Errorf("Expected %v was %v", expected, result) } @@ -106,9 +106,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Errorf("Expected %v was %v", expected, result) } @@ -120,9 +120,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Log(cd.ActivationPeriods) t.Errorf("Expected %v was %v", expected, result) @@ -135,9 +135,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 330, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 330, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Log(result.Timespans) t.Errorf("Expected %v was %v", expected, result) @@ -150,9 +150,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 360, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 360, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Errorf("Expected %v was %v", expected, result) } @@ -164,9 +164,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0257308200", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 0.5, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0257", Cost: 0.5, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Errorf("Expected %v was %v", expected, result) } @@ -178,9 +178,9 @@ 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723045326", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723045326", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", Cost: 60.35, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "rif", Destination: "0723", Cost: 60.35, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Errorf("Expected %v was %v", expected, result) } @@ -192,7 +192,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.getPresentSecondCost() expected := 0.016 if result != expected { @@ -206,29 +206,29 @@ 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, 22, 51, 50, 0, time.UTC) - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter} result, _ := cd.GetCost() - expected := &CallCost{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", Cost: 0.1, ConnectFee: 0} + expected := &CallCost{Tenant: "vdf", Subject: "minutosu", Destination: "0723", Cost: 0.1, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { t.Log(result.Timespans[0]) t.Errorf("Expected %v was %v", expected, result) } } -func TestMaxSessionTimeNoUserBudget(t *testing.T) { +func TestMaxSessionTimeNoUserBalance(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", storageGetter: getter, Amount: 1000} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723", storageGetter: getter, Amount: 1000} result, err := cd.GetMaxSessionTime() if result != 1000 || err != nil { t.Errorf("Expected %v was %v", 1000, result) } } -func TestMaxSessionTimeWithUserBudget(t *testing.T) { +func TestMaxSessionTimeWithUserBalance(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400} result, err := cd.GetMaxSessionTime() if result != 1080 || err != nil { t.Errorf("Expected %v was %v", 1080, result) @@ -238,7 +238,7 @@ func TestMaxSessionTimeWithUserBudget(t *testing.T) { func TestMaxSessionTimeNoCredit(t *testing.T) { getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "broker", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400} + cd := &CallDescriptor{Tenant: "vdf", Subject: "broker", Destination: "0723", storageGetter: getter, Amount: 5400} result, err := cd.GetMaxSessionTime() if result != 100 || err != nil { t.Errorf("Expected %v was %v", 100, result) @@ -251,10 +251,10 @@ func TestGetCostWithVolumeDiscount(t *testing.T) { vd1 := &VolumeDiscount{100, 10} vd2 := &VolumeDiscount{500, 20} seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd1, vd2}} - rifsBudget := &UserBudget{Id: "rif", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 105} + rifsBalance := &UserBalance{Id: "rif", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 105} 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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter, userBudget: rifsBudget} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0723", TimeStart: t1, TimeEnd: t2, storageGetter: getter, userBalance: rifsBalance} callCost, err := cd.GetCost() if callCost.Cost != 54.0 || err != nil { t.Errorf("Expected %v was %v", 54.0, callCost) @@ -269,7 +269,7 @@ func BenchmarkRedisGetting(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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { getter.GetActivationPeriodsOrFallback(cd.GetKey()) @@ -283,7 +283,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.SearchStorageForPrefix() @@ -297,7 +297,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetCost() @@ -311,7 +311,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { key := cd.GetKey() @@ -326,7 +326,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.SearchStorageForPrefix() @@ -340,7 +340,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} cd.SearchStorageForPrefix() b.StartTimer() for i := 0; i < b.N; i++ { @@ -355,7 +355,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetCost() @@ -369,7 +369,7 @@ func BenchmarkMongoGetting(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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2} b.StartTimer() for i := 0; i < b.N; i++ { getter.GetActivationPeriodsOrFallback(cd.GetKey()) @@ -383,7 +383,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{Tenant: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} + cd := &CallDescriptor{Tenant: "vdf", Subject: "rif", Destination: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetCost() @@ -394,7 +394,7 @@ func BenchmarkKyotoSingleGetSessionTime(b *testing.B) { b.StopTimer() getter, _ := NewKyotoStorage("../data/test.kch") defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 100} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 100} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetMaxSessionTime() @@ -405,7 +405,7 @@ func BenchmarkKyotoMultipleGetSessionTime(b *testing.B) { b.StopTimer() getter, _ := NewKyotoStorage("../data/test.kch") defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetMaxSessionTime() @@ -416,7 +416,7 @@ func BenchmarkRedisSingleGetSessionTime(b *testing.B) { b.StopTimer() getter, _ := NewRedisStorage("", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 100} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 100} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetMaxSessionTime() @@ -427,7 +427,7 @@ func BenchmarkRedisMultipleGetSessionTime(b *testing.B) { b.StopTimer() getter, _ := NewRedisStorage("", 10) defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetMaxSessionTime() @@ -438,7 +438,7 @@ func BenchmarkMongoSingleGetSessionTime(b *testing.B) { b.StopTimer() getter, _ := NewMongoStorage("127.0.0.1", "test") defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 100} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 100} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetMaxSessionTime() @@ -449,7 +449,7 @@ func BenchmarkMongoMultipleGetSessionTime(b *testing.B) { b.StopTimer() getter, _ := NewMongoStorage("127.0.0.1", "test") defer getter.Close() - cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", DestinationPrefix: "0723", storageGetter: getter, Amount: 5400} + cd := &CallDescriptor{Tenant: "vdf", Subject: "minutosu", Destination: "0723", storageGetter: getter, Amount: 5400} b.StartTimer() for i := 0; i < b.N; i++ { cd.GetMaxSessionTime() diff --git a/timespans/minute_buckets.go b/timespans/minute_buckets.go index 7ef8aab42..eec7bbe76 100644 --- a/timespans/minute_buckets.go +++ b/timespans/minute_buckets.go @@ -27,8 +27,9 @@ import ( type MinuteBucket struct { Seconds float64 - Priority int + Weight float64 Price float64 + Percentage float64 // percentage from standard price DestinationId string destination *Destination precision int @@ -39,7 +40,7 @@ Serializes the minute bucket for the storage. Used for key-value storages. */ func (mb *MinuteBucket) store() (result string) { result += strconv.Itoa(int(mb.Seconds)) + "|" - result += strconv.Itoa(int(mb.Priority)) + "|" + result += strconv.FormatFloat(mb.Weight, 'f', -1, 64) + "|" result += strconv.FormatFloat(mb.Price, 'f', -1, 64) + "|" result += mb.DestinationId return @@ -51,7 +52,7 @@ De-serializes the minute bucket for the storage. Used for key-value storages. func (mb *MinuteBucket) restore(input string) { elements := strings.Split(input, "|") mb.Seconds, _ = strconv.ParseFloat(elements[0], 64) - mb.Priority, _ = strconv.Atoi(elements[1]) + mb.Weight, _ = strconv.ParseFloat(elements[1], 64) mb.Price, _ = strconv.ParseFloat(elements[2], 64) mb.DestinationId = elements[3] } @@ -59,9 +60,9 @@ func (mb *MinuteBucket) restore(input string) { /* Returns the destination loading it from the storage if necessary. */ -func (mb *MinuteBucket) getDestination(storage StorageGetter) (dest *Destination) { +func (mb *MinuteBucket) getDestination() (dest *Destination) { if mb.destination == nil { - mb.destination, _ = storage.GetDestination(mb.DestinationId) + mb.destination, _ = storageGetter.GetDestination(mb.DestinationId) } return mb.destination } diff --git a/timespans/storage_interface.go b/timespans/storage_interface.go index 4eac4cc5d..fb031b284 100644 --- a/timespans/storage_interface.go +++ b/timespans/storage_interface.go @@ -29,6 +29,6 @@ type StorageGetter interface { SetDestination(*Destination) error GetTariffPlan(string) (*TariffPlan, error) SetTariffPlan(*TariffPlan) error - GetUserBudget(string) (*UserBudget, error) - SetUserBudget(*UserBudget) error + GetUserBalance(string) (*UserBalance, error) + SetUserBalance(*UserBalance) error } diff --git a/timespans/storage_kyoto.go b/timespans/storage_kyoto.go index 054e562ce..ab011dda4 100644 --- a/timespans/storage_kyoto.go +++ b/timespans/storage_kyoto.go @@ -97,14 +97,14 @@ func (ks *KyotoStorage) SetTariffPlan(tp *TariffPlan) error { return ks.db.Set([]byte(tp.Id), []byte(tp.store())) } -func (ks *KyotoStorage) GetUserBudget(key string) (ub *UserBudget, err error) { +func (ks *KyotoStorage) GetUserBalance(key string) (ub *UserBalance, err error) { if values, err := ks.db.Get([]byte(key)); err == nil { - ub = &UserBudget{Id: key} + ub = &UserBalance{Id: key} ub.restore(string(values)) } return } -func (ks *KyotoStorage) SetUserBudget(ub *UserBudget) error { +func (ks *KyotoStorage) SetUserBalance(ub *UserBalance) error { return ks.db.Set([]byte(ub.Id), []byte(ub.store())) } diff --git a/timespans/storage_mongo.go b/timespans/storage_mongo.go index 0d0dbc1c0..4e314c33d 100644 --- a/timespans/storage_mongo.go +++ b/timespans/storage_mongo.go @@ -42,7 +42,7 @@ func NewMongoStorage(address, db string) (*MongoStorage, error) { index = mgo.Index{Key: []string{"id"}, Unique: true, DropDups: true, Background: true} err = session.DB(db).C("destinations").EnsureIndex(index) err = session.DB(db).C("tariffPlans").EnsureIndex(index) - err = session.DB(db).C("userBudget").EnsureIndex(index) + err = session.DB(db).C("userBalance").EnsureIndex(index) return &MongoStorage{db: session.DB(db), session: session}, nil } @@ -96,14 +96,14 @@ func (ms *MongoStorage) SetTariffPlan(tp *TariffPlan) error { return ndb.Insert(&tp) } -func (ms *MongoStorage) GetUserBudget(key string) (result *UserBudget, err error) { - ndb := ms.db.C("userBudget") - result = &UserBudget{} +func (ms *MongoStorage) GetUserBalance(key string) (result *UserBalance, err error) { + ndb := ms.db.C("userBalance") + result = &UserBalance{} err = ndb.Find(bson.M{"id": key}).One(result) return } -func (ms *MongoStorage) SetUserBudget(ub *UserBudget) error { - ndb := ms.db.C("userBudget") +func (ms *MongoStorage) SetUserBalance(ub *UserBalance) error { + ndb := ms.db.C("userBalance") return ndb.Insert(&ub) } diff --git a/timespans/storage_redis.go b/timespans/storage_redis.go index a3990ca9d..6e62953ce 100644 --- a/timespans/storage_redis.go +++ b/timespans/storage_redis.go @@ -99,16 +99,16 @@ func (rs *RedisStorage) SetTariffPlan(tp *TariffPlan) error { return rs.db.Set(tp.Id, tp.store()) } -func (rs *RedisStorage) GetUserBudget(key string) (ub *UserBudget, err error) { +func (rs *RedisStorage) GetUserBalance(key string) (ub *UserBalance, err error) { //rs.db.Select(rs.dbNb + 3) if values, err := rs.db.Get(key); err == nil { - ub = &UserBudget{Id: key} + ub = &UserBalance{Id: key} ub.restore(values.String()) } return } -func (rs *RedisStorage) SetUserBudget(ub *UserBudget) error { +func (rs *RedisStorage) SetUserBalance(ub *UserBalance) error { //rs.db.Select(rs.dbNb + 3) return rs.db.Set(ub.Id, ub.store()) } diff --git a/timespans/tariff_plans.go b/timespans/tariff_plans.go index 34368f5dd..ef08a41a5 100644 --- a/timespans/tariff_plans.go +++ b/timespans/tariff_plans.go @@ -20,80 +20,43 @@ package timespans import ( // "log" - "strconv" - "strings" + "bytes" + "encoding/gob" ) const ( - CREDIT = "CREDIT" - SMS = "SMS" - TRAFFIC = "TRAFFIC" + // Direction type + INBOUND = "IN" + OUTBOUND = "OUT" + // Balance types + CREDIT = "MONETARY" + SMS = "SMS" + TRAFFIC = "INTERNET" ) /* Structure describing a tariff plan's number of bonus items. It is uset to restore -these numbers to the user budget every month. +these numbers to the user balance every month. */ type TariffPlan struct { - Id string - balanceMap map[string]float64 - Bonuses []*Bonus - MinuteBuckets []*MinuteBucket - VolumeDiscountThresholds []*VolumeDiscount + Id string + balanceMap map[string]float64 + Actions []*Action + MinuteBuckets []*MinuteBucket } /* Serializes the tariff plan for the storage. Used for key-value storages. */ func (tp *TariffPlan) store() (result string) { - result += strconv.FormatFloat(tp.SmsCredit, 'f', -1, 64) + ";" - result += strconv.FormatFloat(tp.Traffic, 'f', -1, 64) + ";" - result += strconv.FormatFloat(tp.ReceivedCallSecondsLimit, 'f', -1, 64) + ";" - if tp.RecivedCallBonus == nil { - tp.RecivedCallBonus = &RecivedCallBonus{} - } - result += tp.RecivedCallBonus.store() + ";" - for i, mb := range tp.MinuteBuckets { - if i > 0 { - result += "," - } - result += mb.store() - } - if tp.VolumeDiscountThresholds != nil { - result += ";" - } - for i, vd := range tp.VolumeDiscountThresholds { - if i > 0 { - result += "," - } - result += strconv.FormatFloat(vd.Volume, 'f', -1, 64) + "|" + strconv.FormatFloat(vd.Discount, 'f', -1, 64) - } - result = strings.TrimRight(result, ";") - return + buf := new(bytes.Buffer) + gob.NewEncoder(buf).Encode(tp) + return buf.String() } /* De-serializes the tariff plan for the storage. Used for key-value storages. */ func (tp *TariffPlan) restore(input string) { - elements := strings.Split(input, ";") - tp.SmsCredit, _ = strconv.ParseFloat(elements[0], 64) - tp.Traffic, _ = strconv.ParseFloat(elements[1], 64) - tp.ReceivedCallSecondsLimit, _ = strconv.ParseFloat(elements[2], 64) - tp.RecivedCallBonus = &RecivedCallBonus{} - tp.RecivedCallBonus.restore(elements[3]) - for _, mbs := range strings.Split(elements[4], ",") { - mb := &MinuteBucket{} - mb.restore(mbs) - tp.MinuteBuckets = append(tp.MinuteBuckets, mb) - } - if len(elements) > 5 { - for _, vdss := range strings.Split(elements[5], ",") { - vd := &VolumeDiscount{} - vds := strings.Split(vdss, "|") - vd.Volume, _ = strconv.ParseFloat(vds[0], 64) - vd.Discount, _ = strconv.ParseFloat(vds[1], 64) - tp.VolumeDiscountThresholds = append(tp.VolumeDiscountThresholds, vd) - } - } + gob.NewDecoder(bytes.NewBuffer([]byte(input))).Decode(tp) } diff --git a/timespans/timespans.go b/timespans/timespans.go index b4152ed8d..f56fa4749 100644 --- a/timespans/timespans.go +++ b/timespans/timespans.go @@ -64,13 +64,13 @@ func (ts *TimeSpan) getCost(cd *CallDescriptor) (cost float64) { } else { cost = ts.GetDuration().Seconds() * ts.Interval.Price } - if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { - userBudget.mux.RLock() - if percentageDiscount, err := userBudget.getVolumeDiscount(cd.storageGetter); err == nil && percentageDiscount > 0 { - cost *= (100 - percentageDiscount) / 100 - } - userBudget.mux.RUnlock() - } + // if userBalance, err := cd.getUserBalance(); err == nil && userBalance != nil { + // userBalance.mux.RLock() + // if percentageDiscount, err := userBalance.getVolumeDiscount(cd.Destination, INBOUND); err == nil && percentageDiscount > 0 { + // cost *= (100 - percentageDiscount) / 100 + // } + // userBalance.mux.RUnlock() + // } ts.Cost = cost return } diff --git a/timespans/userbalance.go b/timespans/userbalance.go new file mode 100644 index 000000000..b3db40925 --- /dev/null +++ b/timespans/userbalance.go @@ -0,0 +1,261 @@ +/* +Rating system designed to be used in VoIP Carriers World +Copyright (C) 2012 Radu Ioan Fericean + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see +*/ + +package timespans + +import ( + // "log" + "sort" + "bytes" + "encoding/gob" + "sync" +) + +const ( + UB_TYPE_POSTPAID = "postpaid" + UB_TYPE_PREPAID = "prepaid" +) + +var ( + storageGetter StorageGetter +) + +/* +Structure containing information about user's credit (minutes, cents, sms...).' +*/ +type UserBalance struct { + Id string + Type string // prepaid-postpaid + BalanceMap map[string]float64 + UnitsCounters []*UnitsCounter + TariffPlanId string + tariffPlan *TariffPlan + MinuteBuckets []*MinuteBucket + mux sync.RWMutex +} + +/* +Error type for overflowed debit methods. +*/ +type AmountTooBig byte + +func (a AmountTooBig) Error() string { + return "Amount excedes balance!" +} + +/* +Structure to store minute buckets according to weight, precision or price. +*/ +type bucketsorter []*MinuteBucket + +func (bs bucketsorter) Len() int { + return len(bs) +} + +func (bs bucketsorter) Swap(i, j int) { + bs[i], bs[j] = bs[j], bs[i] +} + +func (bs bucketsorter) Less(j, i int) bool { + return bs[i].Weight < bs[j].Weight || + bs[i].precision < bs[j].precision || + bs[i].Price > bs[j].Price +} + +/* +Serializes the user balance for the storage. Used for key-value storage. +*/ +func (ub *UserBalance) store() (result string) { + buf := new(bytes.Buffer) + gob.NewEncoder(buf).Encode(ub) + return buf.String() +} + +/* +De-serializes the user balance for the storage. Used for key-value storage. +*/ +func (ub *UserBalance) restore(input string) { + gob.NewDecoder(bytes.NewBuffer([]byte(input))).Decode(ub) +} + +/* +Returns the tariff plan loading it from the storage if necessary. +*/ +func (ub *UserBalance) getTariffPlan() (tp *TariffPlan, err error) { + if ub.tariffPlan == nil && ub.TariffPlanId != "" { + ub.tariffPlan, err = storageGetter.GetTariffPlan(ub.TariffPlanId) + } + return ub.tariffPlan, err +} + +/* +Returns user's available minutes for the specified destination +*/ +func (ub *UserBalance) getSecondsForPrefix(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() + if d == nil { + continue + } + contains, precision := d.containsPrefix(prefix) + if contains { + mb.precision = precision + if mb.Seconds > 0 { + bucketList = append(bucketList, mb) + } + } + } + sort.Sort(bucketList) // sorts the buckets according to priority, precision or price + credit := ub.BalanceMap[CREDIT] + for _, mb := range bucketList { + s := mb.GetSecondsForCredit(credit) + credit -= s * mb.Price + seconds += s + } + return +} + +/* +Debits some amount of user's money credit. Returns the remaining credit in user's balance. +*/ +func (ub *UserBalance) debitMoneyBalance(amount float64) float64 { + ub.mux.Lock() + defer ub.mux.Unlock() + ub.BalanceMap[CREDIT] -= amount + storageGetter.SetUserBalance(ub) + return ub.BalanceMap[CREDIT] +} + +/* +Debits the received 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 *UserBalance) debitMinutesBalance(amount float64, prefix string) error { + ub.mux.Lock() + defer ub.mux.Unlock() + avaliableNbSeconds, bucketList := ub.getSecondsForPrefix(prefix) + if avaliableNbSeconds < amount { + return new(AmountTooBig) + } + credit := ub.BalanceMap[CREDIT] + // calculating money debit + // this is needed because if the credit is less then the amount needed to be debited + // we need to keep everything in place and return an error + for _, mb := range bucketList { + if mb.Seconds < amount { + if mb.Price > 0 { // debit the money if the bucket has price + credit -= mb.Seconds * mb.Price + } + } else { + if mb.Price > 0 { // debit the money if the bucket has price + credit -= amount * mb.Price + } + break + } + if credit < 0 { + break + } + } + if credit < 0 { + return new(AmountTooBig) + } + ub.BalanceMap[CREDIT] = credit // credit is > 0 + for _, mb := range bucketList { + if mb.Seconds < amount { + amount -= mb.Seconds + mb.Seconds = 0 + } else { + mb.Seconds -= amount + break + } + } + storageGetter.SetUserBalance(ub) + return nil +} + +/* +Debits some amount of user's SMS balance. Returns the remaining SMS in user's balance. +If the amount is bigger than the balance than nothing wil be debited and an error will be returned +*/ +func (ub *UserBalance) debitSMSBuget(amount float64) (float64, error) { + ub.mux.Lock() + defer ub.mux.Unlock() + if ub.BalanceMap[SMS] < amount { + return ub.BalanceMap[SMS], new(AmountTooBig) + } + ub.BalanceMap[SMS] -= amount + + storageGetter.SetUserBalance(ub) + return ub.BalanceMap[SMS], nil +} + +/* +Adds the specified amount of seconds. +*/ +func (ub *UserBalance) addReceivedCallSeconds(direction, tor, destination string, amount float64) error { + ub.mux.Lock() + defer ub.mux.Unlock() + for + ub.ReceivedCallSeconds += amount + if tariffPlan, err := ub.getTariffPlan(); tariffPlan != nil && err == nil { + if ub.ReceivedCallSeconds >= tariffPlan.ReceivedCallSecondsLimit { + ub.ReceivedCallSeconds -= tariffPlan.ReceivedCallSecondsLimit + if tariffPlan.RecivedCallBonus != nil { // apply the bonus + ub.BalanceMap[CREDIT] += tariffPlan.RecivedCallBonus.Credit + ub.BalanceMap[SMS] += tariffPlan.RecivedCallBonus.SmsCredit + ub.BalanceMap[TRAFFIC] += tariffPlan.RecivedCallBonus.Traffic + if tariffPlan.RecivedCallBonus.MinuteBucket != nil { + for _, mb := range ub.MinuteBuckets { + if mb.DestinationId == tariffPlan.RecivedCallBonus.MinuteBucket.DestinationId { + mb.Seconds += tariffPlan.RecivedCallBonus.MinuteBucket.Seconds + } + } + } + } + } + } + return storageGetter.SetUserBalance(ub) +} + +/* +Resets the user balance items to their tariff plan values. +*/ +func (ub *UserBalance) resetUserBalance() (err error) { + ub.mux.Lock() + defer ub.mux.Unlock() + if tp, err := ub.getTariffPlan(storageGetter); err == nil { + ub.SmsCredit = tp.SmsCredit + ub.Traffic = tp.Traffic + ub.MinuteBuckets = make([]*MinuteBucket, 0) + for _, bucket := range tp.MinuteBuckets { + mb := &MinuteBucket{Seconds: bucket.Seconds, + Priority: bucket.Priority, + Price: bucket.Price, + DestinationId: bucket.DestinationId} + ub.MinuteBuckets = append(ub.MinuteBuckets, mb) + } + err = storageGetter.SetUserBalance(ub) + } + return +} diff --git a/timespans/userbudget_test.go b/timespans/userbalance_test.go similarity index 54% rename from timespans/userbudget_test.go rename to timespans/userbalance_test.go index 64ac9fc87..62cf4471c 100644 --- a/timespans/userbudget_test.go +++ b/timespans/userbalance_test.go @@ -29,13 +29,13 @@ var ( retea = &Destination{Id: "retea", Prefixes: []string{"0723", "0724"}} ) -func TestUserBudgetStoreRestore(t *testing.T) { +func TestUserBalanceStoreRestore(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Priority: 10, Price: 0.01, DestinationId: "nationale"} b2 := &MinuteBucket{Seconds: 100, Priority: 20, Price: 0.0, DestinationId: "retea"} seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} - s := rifsBudget.store() - ub1 := &UserBudget{Id: "other"} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} + s := rifsBalance.store() + ub1 := &UserBalance{Id: "other"} ub1.restore(s) if ub1.store() != s { t.Errorf("Expected %q was %q", s, ub1.store()) @@ -47,7 +47,7 @@ func TestGetSecondsForPrefix(t *testing.T) { b2 := &MinuteBucket{Seconds: 100, Priority: 20, destination: retea} tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} - ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10} + ub1 := &UserBalance{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10} seconds, bucketList := ub1.getSecondsForPrefix(nil, "0723") expected := 110.0 if seconds != expected || bucketList[0].Priority < bucketList[1].Priority { @@ -60,7 +60,7 @@ func TestGetPricedSeconds(t *testing.T) { b2 := &MinuteBucket{Seconds: 100, Price: 1, Priority: 20, destination: retea} tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} - ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10} + ub1 := &UserBalance{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10} seconds, bucketList := ub1.getSecondsForPrefix(nil, "0723") expected := 21.0 if seconds != expected || bucketList[0].Priority < bucketList[1].Priority { @@ -68,35 +68,35 @@ func TestGetPricedSeconds(t *testing.T) { } } -func TestUserBudgetKyotoStore(t *testing.T) { +func TestUserBalanceKyotoStore(t *testing.T) { getter, _ := NewKyotoStorage("../data/test.kch") 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} - getter.SetUserBudget(rifsBudget) - result, _ := getter.GetUserBudget(rifsBudget.Id) - if !reflect.DeepEqual(rifsBudget, result) { - t.Log(rifsBudget) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + getter.SetUserBalance(rifsBalance) + result, _ := getter.GetUserBalance(rifsBalance.Id) + if !reflect.DeepEqual(rifsBalance, result) { + t.Log(rifsBalance) t.Log(result) - t.Errorf("Expected %q was %q", rifsBudget, result) + t.Errorf("Expected %q was %q", rifsBalance, result) } } -func TestUserBudgetRedisStore(t *testing.T) { +func TestUserBalanceRedisStore(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} - getter.SetUserBudget(rifsBudget) - result, _ := getter.GetUserBudget(rifsBudget.Id) - if !reflect.DeepEqual(rifsBudget, result) { - t.Errorf("Expected %q was %q", rifsBudget, result) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + getter.SetUserBalance(rifsBalance) + result, _ := getter.GetUserBalance(rifsBalance.Id) + if !reflect.DeepEqual(rifsBalance, result) { + t.Errorf("Expected %q was %q", rifsBalance, result) } } -func TestUserBudgetMongoStore(t *testing.T) { +func TestUserBalanceMongoStore(t *testing.T) { getter, err := NewMongoStorage("127.0.0.1", "test") if err != nil { return @@ -104,244 +104,244 @@ func TestUserBudgetMongoStore(t *testing.T) { 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} - getter.SetUserBudget(rifsBudget) - result, _ := getter.GetUserBudget(rifsBudget.Id) - if !reflect.DeepEqual(rifsBudget, result) { - t.Errorf("Expected %q was %q", rifsBudget, result) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + getter.SetUserBalance(rifsBalance) + result, _ := getter.GetUserBalance(rifsBalance.Id) + if !reflect.DeepEqual(rifsBalance, result) { + t.Errorf("Expected %q was %q", rifsBalance, result) } } -func TestDebitMoneyBudget(t *testing.T) { +func TestDebitMoneyBalance(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) + rifsBalance := &UserBalance{Id: "o4her", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + result := rifsBalance.debitMoneyBalance(getter, 6) + if rifsBalance.Credit != 15 || result != rifsBalance.Credit { + t.Errorf("Expected %v was %v", 15, rifsBalance.Credit) } } -func TestDebitAllMoneyBudget(t *testing.T) { +func TestDebitAllMoneyBalance(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) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + rifsBalance.debitMoneyBalance(getter, 21) + result := rifsBalance.debitMoneyBalance(getter, 0) + if rifsBalance.Credit != 0 || result != rifsBalance.Credit { + t.Errorf("Expected %v was %v", 0, rifsBalance.Credit) } } -func TestDebitMoreMoneyBudget(t *testing.T) { +func TestDebitMoreMoneyBalance(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) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + result := rifsBalance.debitMoneyBalance(getter, 22) + if rifsBalance.Credit != -1 || result != rifsBalance.Credit { + t.Errorf("Expected %v was %v", -1, rifsBalance.Credit) } } -func TestDebitNegativeMoneyBudget(t *testing.T) { +func TestDebitNegativeMoneyBalance(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, -15) - if rifsBudget.Credit != 36 || result != rifsBudget.Credit { - t.Errorf("Expected %v was %v", 36, rifsBudget.Credit) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + result := rifsBalance.debitMoneyBalance(getter, -15) + if rifsBalance.Credit != 36 || result != rifsBalance.Credit { + t.Errorf("Expected %v was %v", 36, rifsBalance.Credit) } } -func TestDebitMinuteBudget(t *testing.T) { +func TestDebitMinuteBalance(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") + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(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) { +func TestDebitMultipleBucketsMinuteBalance(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") + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(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) { +func TestDebitAllMinuteBalance(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") + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(getter, 110, "0723") if b2.Seconds != 0 || b1.Seconds != 0 || err != nil { t.Errorf("Expected %v was %v", 0, b2.Seconds) } } -func TestDebitMoreMinuteBudget(t *testing.T) { +func TestDebitMoreMinuteBalance(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") + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(getter, 115, "0723") if b2.Seconds != 100 || b1.Seconds != 10 || err == nil { t.Errorf("Expected %v was %v", 1000, b2.Seconds) } } -func TestDebitPriceMinuteBudget(t *testing.T) { +func TestDebitPriceMinuteBalance(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: 1.0, DestinationId: "retea"} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} - err := rifsBudget.debitMinutesBudget(getter, 5, "0723") - if b2.Seconds != 95 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 16 { - t.Log(rifsBudget.Credit) - t.Errorf("Expected %v was %v", 16, rifsBudget.Credit) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(getter, 5, "0723") + if b2.Seconds != 95 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 16 { + t.Log(rifsBalance.Credit) + t.Errorf("Expected %v was %v", 16, rifsBalance.Credit) } } -func TestDebitPriceAllMinuteBudget(t *testing.T) { +func TestDebitPriceAllMinuteBalance(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: 1.0, DestinationId: "retea"} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} - err := rifsBudget.debitMinutesBudget(getter, 21, "0723") - if b2.Seconds != 79 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 0 { - t.Errorf("Expected %v was %v", 0, rifsBudget.Credit) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(getter, 21, "0723") + if b2.Seconds != 79 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 0 { + t.Errorf("Expected %v was %v", 0, rifsBalance.Credit) } } -func TestDebitPriceMoreMinuteBudget(t *testing.T) { +func TestDebitPriceMoreMinuteBalance(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: 1.0, DestinationId: "retea"} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} - err := rifsBudget.debitMinutesBudget(getter, 25, "0723") - if b2.Seconds != 100 || b1.Seconds != 10 || err == nil || rifsBudget.Credit != 21 { + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(getter, 25, "0723") + if b2.Seconds != 100 || b1.Seconds != 10 || err == nil || rifsBalance.Credit != 21 { t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 21, rifsBudget.Credit) + t.Errorf("Expected %v was %v", 21, rifsBalance.Credit) } } -func TestDebitPriceNegativeMinuteBudget(t *testing.T) { +func TestDebitPriceNegativeMinuteBalance(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: 1.0, DestinationId: "retea"} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} - err := rifsBudget.debitMinutesBudget(getter, -15, "0723") - if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 36 { + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(getter, -15, "0723") + if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 36 { t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 36, rifsBudget.Credit) + t.Errorf("Expected %v was %v", 36, rifsBalance.Credit) } } -func TestDebitNegativeMinuteBudget(t *testing.T) { +func TestDebitNegativeMinuteBalance(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, -15, "0723") - if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBudget.Credit != 21 { + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, ResetDayOfTheMonth: 10} + err := rifsBalance.debitMinutesBalance(getter, -15, "0723") + if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.Credit != 21 { t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 21, rifsBudget.Credit) + t.Errorf("Expected %v was %v", 21, rifsBalance.Credit) } } -func TestDebitSMSBudget(t *testing.T) { +func TestDebitSMSBalance(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) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10} + result, err := rifsBalance.debitSMSBuget(getter, 12) + if rifsBalance.SmsCredit != 88 || result != rifsBalance.SmsCredit || err != nil { + t.Errorf("Expected %v was %v", 88, rifsBalance.SmsCredit) } } -func TestDebitAllSMSBudget(t *testing.T) { +func TestDebitAllSMSBalance(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) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10} + result, err := rifsBalance.debitSMSBuget(getter, 100) + if rifsBalance.SmsCredit != 0 || result != rifsBalance.SmsCredit || err != nil { + t.Errorf("Expected %v was %v", 0, rifsBalance.SmsCredit) } } -func TestDebitMoreSMSBudget(t *testing.T) { +func TestDebitMoreSMSBalance(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) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10} + result, err := rifsBalance.debitSMSBuget(getter, 110) + if rifsBalance.SmsCredit != 100 || result != rifsBalance.SmsCredit || err == nil { + t.Errorf("Expected %v was %v", 100, rifsBalance.SmsCredit) } } -func TestDebitNegativeSMSBudget(t *testing.T) { +func TestDebitNegativeSMSBalance(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, -15) - if rifsBudget.SmsCredit != 115 || result != rifsBudget.SmsCredit || err != nil { - t.Errorf("Expected %v was %v", 115, rifsBudget.SmsCredit) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, SmsCredit: 100, ResetDayOfTheMonth: 10} + result, err := rifsBalance.debitSMSBuget(getter, -15) + if rifsBalance.SmsCredit != 115 || result != rifsBalance.SmsCredit || err != nil { + t.Errorf("Expected %v was %v", 115, rifsBalance.SmsCredit) } } -func TestResetUserBudget(t *testing.T) { +func TestResetUserBalance(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"} seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} - rifsBudget.MinuteBuckets[0].Seconds, rifsBudget.MinuteBuckets[1].Seconds = 0.0, 0.0 - err := rifsBudget.resetUserBudget(getter) + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} + rifsBalance.MinuteBuckets[0].Seconds, rifsBalance.MinuteBuckets[1].Seconds = 0.0, 0.0 + err := rifsBalance.resetUserBalance(getter) if err != nil || - rifsBudget.MinuteBuckets[0] == b1 || - rifsBudget.MinuteBuckets[0].Seconds != seara.MinuteBuckets[0].Seconds || - rifsBudget.MinuteBuckets[1].Seconds != seara.MinuteBuckets[1].Seconds || - rifsBudget.SmsCredit != seara.SmsCredit { - t.Log(rifsBudget.MinuteBuckets[0]) - t.Log(rifsBudget.MinuteBuckets[1]) - t.Log(rifsBudget.SmsCredit) - t.Log(rifsBudget.Traffic) - t.Errorf("Expected %v was %v", seara, rifsBudget) + rifsBalance.MinuteBuckets[0] == b1 || + rifsBalance.MinuteBuckets[0].Seconds != seara.MinuteBuckets[0].Seconds || + rifsBalance.MinuteBuckets[1].Seconds != seara.MinuteBuckets[1].Seconds || + rifsBalance.SmsCredit != seara.SmsCredit { + t.Log(rifsBalance.MinuteBuckets[0]) + t.Log(rifsBalance.MinuteBuckets[1]) + t.Log(rifsBalance.SmsCredit) + t.Log(rifsBalance.Traffic) + t.Errorf("Expected %v was %v", seara, rifsBalance) } } @@ -349,8 +349,8 @@ func TestResetUserBudget(t *testing.T) { func TestGetVolumeDiscountHaving(t *testing.T) { vd := &VolumeDiscount{100, 11} seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd}} - rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 100} - result, err := rifsBudget.getVolumeDiscount(nil) + rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 100} + result, err := rifsBalance.getVolumeDiscount(nil) if err != nil || result != 11 { t.Errorf("Expected %v was %v", 11, result) } @@ -359,8 +359,8 @@ func TestGetVolumeDiscountHaving(t *testing.T) { func TestGetVolumeDiscountNotHaving(t *testing.T) { vd := &VolumeDiscount{100, 11} seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd}} - rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 99} - result, err := rifsBudget.getVolumeDiscount(nil) + rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 99} + result, err := rifsBalance.getVolumeDiscount(nil) if err != nil || result != 0 { t.Errorf("Expected %v was %v", 0, result) } @@ -370,8 +370,8 @@ func TestGetVolumeDiscountSteps(t *testing.T) { vd1 := &VolumeDiscount{100, 11} vd2 := &VolumeDiscount{500, 20} seara := &TariffPlan{Id: "seara", SmsCredit: 100, VolumeDiscountThresholds: []*VolumeDiscount{vd1, vd2}} - rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 551} - result, err := rifsBudget.getVolumeDiscount(nil) + rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, VolumeDiscountSeconds: 551} + result, err := rifsBalance.getVolumeDiscount(nil) if err != nil || result != 20 { t.Errorf("Expected %v was %v", 20, result) } @@ -382,10 +382,10 @@ func TestRecivedCallsBonus(t *testing.T) { defer getter.Close() rcb := &RecivedCallBonus{Credit: 100} seara := &TariffPlan{Id: "seara_voo", SmsCredit: 100, ReceivedCallSecondsLimit: 10, RecivedCallBonus: rcb} - rifsBudget := &UserBudget{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, ReceivedCallSeconds: 1} - err := rifsBudget.addReceivedCallSeconds(getter, 12) - if err != nil || rifsBudget.Credit != 121 || rifsBudget.ReceivedCallSeconds != 3 { - t.Error("Wrong Received call bonus procedure: ", rifsBudget) + rifsBalance := &UserBalance{Id: "other", Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10, ReceivedCallSeconds: 1} + err := rifsBalance.addReceivedCallSeconds(getter, 12) + if err != nil || rifsBalance.Credit != 121 || rifsBalance.ReceivedCallSeconds != 3 { + t.Error("Wrong Received call bonus procedure: ", rifsBalance) } } @@ -397,49 +397,49 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { b2 := &MinuteBucket{Seconds: 100, Price: 1, Priority: 20, destination: retea} tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} - ub1 := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10} + ub1 := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: tf1, ResetDayOfTheMonth: 10} b.StartTimer() for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix(nil, "0723") } } -func BenchmarkUserBudgetKyotoStoreRestore(b *testing.B) { +func BenchmarkUserBalanceKyotoStoreRestore(b *testing.B) { getter, _ := NewKyotoStorage("../data/test.kch") 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"} seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} for i := 0; i < b.N; i++ { - getter.SetUserBudget(rifsBudget) - getter.GetUserBudget(rifsBudget.Id) + getter.SetUserBalance(rifsBalance) + getter.GetUserBalance(rifsBalance.Id) } } -func BenchmarkUserBudgetRedisStoreRestore(b *testing.B) { +func BenchmarkUserBalanceRedisStoreRestore(b *testing.B) { 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"} seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} for i := 0; i < b.N; i++ { - getter.SetUserBudget(rifsBudget) - getter.GetUserBudget(rifsBudget.Id) + getter.SetUserBalance(rifsBalance) + getter.GetUserBalance(rifsBalance.Id) } } -func BenchmarkUserBudgetMongoStoreRestore(b *testing.B) { +func BenchmarkUserBalanceMongoStoreRestore(b *testing.B) { getter, _ := NewMongoStorage("127.0.0.1", "test") 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"} seara := &TariffPlan{Id: "seara", SmsCredit: 100, MinuteBuckets: []*MinuteBucket{b1, b2}} - rifsBudget := &UserBudget{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 21, tariffPlan: seara, ResetDayOfTheMonth: 10} for i := 0; i < b.N; i++ { - getter.SetUserBudget(rifsBudget) - getter.GetUserBudget(rifsBudget.Id) + getter.SetUserBalance(rifsBalance) + getter.GetUserBalance(rifsBalance.Id) } } @@ -448,7 +448,7 @@ func BenchmarkGetSecondsForPrefix(b *testing.B) { b2 := &MinuteBucket{Seconds: 100, Priority: 20, destination: retea} tf1 := &TariffPlan{MinuteBuckets: []*MinuteBucket{b1, b2}} - ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10} + ub1 := &UserBalance{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10} for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix(nil, "0723") } diff --git a/timespans/userbudget.go b/timespans/userbudget.go deleted file mode 100644 index 73a117ff0..000000000 --- a/timespans/userbudget.go +++ /dev/null @@ -1,326 +0,0 @@ -/* -Rating system designed to be used in VoIP Carriers World -Copyright (C) 2012 Radu Ioan Fericean - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see -*/ - -package timespans - -import ( - // "log" - "sort" - "strconv" - "strings" - "sync" -) - -const ( - UB_TYPE_POSTPAID = "postpaid" - UB_TYPE_PREPAID = "prepaid" -) - -/* -Structure conatining information about user's credit (minutes, cents, sms...).' -*/ -type UserBudget struct { - Id string - Type string // prepaid-postpaid - BalanceMap map[string]float64 - OutboundVolumes []*TrafficVolume - InboundVolumes []*TrafficVolume - ResetDayOfTheMonth int - TariffPlanId string - tariffPlan *TariffPlan - MinuteBuckets []*MinuteBucket - mux sync.RWMutex -} - -/* -Error type for overflowed debit methods. -*/ -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 - -func (bs bucketsorter) Len() int { - return len(bs) -} - -func (bs bucketsorter) Swap(i, j int) { - bs[i], bs[j] = bs[j], bs[i] -} - -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 -} - -/* -Serializes the user budget for the storage. Used for key-value storages. -*/ -func (ub *UserBudget) store() (result string) { - result += ub.Type + ";" - result += strconv.FormatFloat(ub.Credit, 'f', -1, 64) + ";" - result += strconv.FormatFloat(ub.SmsCredit, 'f', -1, 64) + ";" - result += strconv.FormatFloat(ub.Traffic, 'f', -1, 64) + ";" - result += strconv.FormatFloat(ub.VolumeDiscountSeconds, 'f', -1, 64) + ";" - result += strconv.FormatFloat(ub.ReceivedCallSeconds, 'f', -1, 64) + ";" - result += strconv.Itoa(ub.ResetDayOfTheMonth) + ";" - result += ub.TariffPlanId - if ub.MinuteBuckets != nil { - result += ";" - } - for i, mb := range ub.MinuteBuckets { - if i > 0 { - result += "," - } - result += mb.store() - } - return -} - -/* -De-serializes the user budget for the storage. Used for key-value storages. -*/ -func (ub *UserBudget) restore(input string) { - elements := strings.Split(input, ";") - ub.Type = elements[0] - ub.Credit, _ = strconv.ParseFloat(elements[1], 64) - ub.SmsCredit, _ = strconv.ParseFloat(elements[2], 64) - ub.Traffic, _ = strconv.ParseFloat(elements[3], 64) - ub.VolumeDiscountSeconds, _ = strconv.ParseFloat(elements[4], 64) - ub.ReceivedCallSeconds, _ = strconv.ParseFloat(elements[5], 64) - ub.ResetDayOfTheMonth, _ = strconv.Atoi(elements[6]) - ub.TariffPlanId = elements[7] - if len(elements) > 8 { - for _, mbs := range strings.Split(elements[8], ",") { - mb := &MinuteBucket{} - mb.restore(mbs) - ub.MinuteBuckets = append(ub.MinuteBuckets, mb) - } - } -} - -/* -Returns the tariff plan loading it from the storage if necessary. -*/ -func (ub *UserBudget) getTariffPlan(storage StorageGetter) (tp *TariffPlan, err error) { - if ub.tariffPlan == nil && ub.TariffPlanId != "" { - ub.tariffPlan, err = storage.GetTariffPlan(ub.TariffPlanId) - } - return ub.tariffPlan, err -} - -/* -Returns thevolume discount procentage according to the nuber of acumulated volume discount seconds. -*/ -func (ub *UserBudget) getVolumeDiscount(storage StorageGetter) (float64, error) { - tariffPlan, err := ub.getTariffPlan(storage) - if err != nil || tariffPlan == nil { - return 0.0, err - } - thresholds := len(tariffPlan.VolumeDiscountThresholds) - for i, vd := range tariffPlan.VolumeDiscountThresholds { - if ub.VolumeDiscountSeconds >= vd.Volume && - (i > thresholds-2 || ub.VolumeDiscountSeconds < tariffPlan.VolumeDiscountThresholds[i+1].Volume) { - return vd.Discount, nil - } - } - return 0, nil -} - -/* -Returns user's avaliable minutes for the specified destination -*/ -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(sg) - if d == nil { - continue - } - contains, precision := d.containsPrefix(prefix) - if contains { - mb.precision = precision - if mb.Seconds > 0 { - bucketList = append(bucketList, mb) - } - } - } - sort.Sort(bucketList) // sorts the buckets according to priority, precision or price - credit := ub.Credit - for _, mb := range bucketList { - s := mb.GetSecondsForCredit(credit) - 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) - } - credit := ub.Credit - // calculating money debit - // this is needed because if the credit is less then the amount needed to be debited - // we need to keep everithing in place and return an error - for _, mb := range bucketList { - if mb.Seconds < amount { - if mb.Price > 0 { // debit the money if the bucket has price - credit -= mb.Seconds * mb.Price - } - } else { - if mb.Price > 0 { // debit the money if the bucket has price - credit -= amount * mb.Price - } - break - } - if credit < 0 { - break - } - } - if credit < 0 { - return new(AmountTooBig) - } - ub.Credit = credit // credit is > 0 - 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 float64) (float64, 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 -} - -/* -Adds the the specified amount to volume discount seconds budget. -*/ -func (ub *UserBudget) addVolumeDiscountSeconds(sg StorageGetter, amount float64) error { - ub.mux.Lock() - defer ub.mux.Unlock() - ub.VolumeDiscountSeconds += amount - return sg.SetUserBudget(ub) -} - -/* -Resets the volume discounts seconds (sets zero value). -*/ -func (ub *UserBudget) resetVolumeDiscountSeconds(sg StorageGetter) error { - ub.mux.Lock() - defer ub.mux.Unlock() - ub.VolumeDiscountSeconds = 0 - return sg.SetUserBudget(ub) -} - -/* -Adds the spcifeied amount of seconds to the reci. -*/ -func (ub *UserBudget) addReceivedCallSeconds(sg StorageGetter, amount float64) error { - ub.mux.Lock() - defer ub.mux.Unlock() - ub.ReceivedCallSeconds += amount - if tariffPlan, err := ub.getTariffPlan(sg); tariffPlan != nil && err == nil { - if ub.ReceivedCallSeconds >= tariffPlan.ReceivedCallSecondsLimit { - ub.ReceivedCallSeconds -= tariffPlan.ReceivedCallSecondsLimit - if tariffPlan.RecivedCallBonus != nil { // apply the bonus - ub.Credit += tariffPlan.RecivedCallBonus.Credit - ub.SmsCredit += tariffPlan.RecivedCallBonus.SmsCredit - ub.Traffic += tariffPlan.RecivedCallBonus.Traffic - if tariffPlan.RecivedCallBonus.MinuteBucket != nil { - for _, mb := range ub.MinuteBuckets { - if mb.DestinationId == tariffPlan.RecivedCallBonus.MinuteBucket.DestinationId { - mb.Seconds += tariffPlan.RecivedCallBonus.MinuteBucket.Seconds - } - } - } - } - } - } - return sg.SetUserBudget(ub) -} - -/* -Resets the user budget items to their tariff plan values. -*/ -func (ub *UserBudget) resetUserBudget(sg StorageGetter) (err error) { - ub.mux.Lock() - defer ub.mux.Unlock() - if tp, err := ub.getTariffPlan(sg); err == nil { - ub.SmsCredit = tp.SmsCredit - ub.Traffic = tp.Traffic - ub.MinuteBuckets = make([]*MinuteBucket, 0) - for _, bucket := range tp.MinuteBuckets { - mb := &MinuteBucket{Seconds: bucket.Seconds, - Priority: bucket.Priority, - Price: bucket.Price, - DestinationId: bucket.DestinationId} - ub.MinuteBuckets = append(ub.MinuteBuckets, mb) - } - err = sg.SetUserBudget(ub) - } - return -} \ No newline at end of file