work in progress

This commit is contained in:
Radu Ioan Fericean
2012-06-07 21:41:06 +03:00
parent c70dc66848
commit 0a1f1afd75
17 changed files with 614 additions and 725 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ a.out
*workspace
docs/_*
bin
.idea

View File

@@ -19,46 +19,55 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
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
}

View File

@@ -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)

View File

@@ -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())
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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()))
}

View File

@@ -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)
}

View File

@@ -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())
}

View File

@@ -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)
}

View File

@@ -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
}

261
timespans/userbalance.go Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>
*/
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
}

View File

@@ -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")
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*/
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
}