From b6bfc1745c9030222ff17861311cf8aa81bffb3f Mon Sep 17 00:00:00 2001 From: Radu Ioan Fericean Date: Mon, 15 Jul 2013 21:06:14 +0300 Subject: [PATCH] expiration balances and minute buckets --- apier/apier.go | 2 +- rater/action.go | 6 +- rater/actions_test.go | 52 +++++----- rater/calldesc_test.go | 8 +- rater/responder.go | 2 +- rater/simple_serializer.go | 68 ++++++++++-- rater/simple_serializer_test.go | 6 +- rater/userbalance.go | 96 +++++++++++++++-- rater/userbalance_test.go | 177 ++++++++++++++++++++------------ 9 files changed, 297 insertions(+), 120 deletions(-) diff --git a/apier/apier.go b/apier/apier.go index 7d6ab5e12..c5758753f 100644 --- a/apier/apier.go +++ b/apier/apier.go @@ -80,7 +80,7 @@ func (self *Apier) GetBalance(attr *AttrGetBalance, reply *float64) error { if balance, balExists := userBalance.BalanceMap[attr.BalanceId+attr.Direction]; !balExists { *reply = 0.0 } else { - *reply = balance + *reply = balance.GetTotalValue() } return nil } diff --git a/rater/action.go b/rater/action.go index d9c317d97..334da4f53 100644 --- a/rater/action.go +++ b/rater/action.go @@ -114,7 +114,7 @@ func topupResetAction(ub *UserBalance, a *Action) (err error) { if a.BalanceId == MINUTES { ub.MinuteBuckets = make([]*MinuteBucket, 0) } else { - ub.BalanceMap[a.BalanceId+a.Direction] = 0 + ub.BalanceMap[a.BalanceId+a.Direction] = BalanceChain{&Balance{Value: 0}} } genericMakeNegative(a) genericDebit(ub, a) @@ -162,7 +162,7 @@ func genericMakeNegative(a *Action) { func genericDebit(ub *UserBalance, a *Action) (err error) { if ub.BalanceMap == nil { - ub.BalanceMap = make(map[string]float64) + ub.BalanceMap = make(map[string]BalanceChain) } switch a.BalanceId { case CREDIT: @@ -181,7 +181,7 @@ func genericDebit(ub *UserBalance, a *Action) (err error) { func genericReset(ub *UserBalance) { for k, _ := range ub.BalanceMap { - ub.BalanceMap[k] = 0 + ub.BalanceMap[k] = BalanceChain{&Balance{Value: 0}} } ub.MinuteBuckets = make([]*MinuteBucket, 0) ub.UnitCounters = make([]*UnitsCounter, 0) diff --git a/rater/actions_test.go b/rater/actions_test.go index ed091d19e..28771bd31 100644 --- a/rater/actions_test.go +++ b/rater/actions_test.go @@ -413,7 +413,7 @@ func TestActionTriggerPriotityList(t *testing.T) { func TestActionResetTriggres(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 10}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -428,7 +428,7 @@ func TestActionSetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -443,7 +443,7 @@ func TestActionSetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -458,14 +458,14 @@ func TestActionResetPrepaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetPrepaidAction(ub, nil) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[CREDIT] != 0 || + ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || len(ub.MinuteBuckets) != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { @@ -477,14 +477,14 @@ func TestActionResetPostpaid(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetPostpaidAction(ub, nil) if ub.Type != UB_TYPE_POSTPAID || - ub.BalanceMap[CREDIT] != 0 || + ub.BalanceMap[CREDIT].GetTotalValue() != 0 || len(ub.UnitCounters) != 0 || len(ub.MinuteBuckets) != 0 || ub.ActionTriggers[0].Executed == true || ub.ActionTriggers[1].Executed == true { @@ -496,7 +496,7 @@ func TestActionTopupResetCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT + OUTBOUND: 100}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -504,11 +504,11 @@ func TestActionTopupResetCredit(t *testing.T) { a := &Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 10} topupResetAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[CREDIT+OUTBOUND] != 10 || + ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 10 || len(ub.UnitCounters) != 1 || len(ub.MinuteBuckets) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { - t.Error("Topup reset action failed!", ub) + t.Errorf("Topup reset action failed: %#v", ub) } } @@ -516,7 +516,7 @@ func TestActionTopupResetMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT + OUTBOUND: 100}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -525,7 +525,7 @@ func TestActionTopupResetMinutes(t *testing.T) { topupResetAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.MinuteBuckets[0].Seconds != 5 || - ub.BalanceMap[CREDIT+OUTBOUND] != 100 || + ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || len(ub.MinuteBuckets) != 1 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { @@ -537,7 +537,7 @@ func TestActionTopupCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT + OUTBOUND: 100}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -545,7 +545,7 @@ func TestActionTopupCredit(t *testing.T) { a := &Action{BalanceId: CREDIT, Units: 10} topupAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[CREDIT+OUTBOUND] != 110 || + ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 110 || len(ub.UnitCounters) != 1 || len(ub.MinuteBuckets) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { @@ -557,7 +557,7 @@ func TestActionTopupMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -566,7 +566,7 @@ func TestActionTopupMinutes(t *testing.T) { topupAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.MinuteBuckets[0].Seconds != 15 || - ub.BalanceMap[CREDIT] != 100 || + ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || len(ub.MinuteBuckets) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { @@ -578,7 +578,7 @@ func TestActionDebitCredit(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT + OUTBOUND: 100}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -586,7 +586,7 @@ func TestActionDebitCredit(t *testing.T) { a := &Action{BalanceId: CREDIT, Units: 10} debitAction(ub, a) if ub.Type != UB_TYPE_PREPAID || - ub.BalanceMap[CREDIT+OUTBOUND] != 90 || + ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() != 90 || len(ub.UnitCounters) != 1 || len(ub.MinuteBuckets) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { @@ -598,7 +598,7 @@ func TestActionDebitMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}, &ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -607,7 +607,7 @@ func TestActionDebitMinutes(t *testing.T) { debitAction(ub, a) if ub.Type != UB_TYPE_PREPAID || ub.MinuteBuckets[0].Seconds != 5 || - ub.BalanceMap[CREDIT] != 100 || + ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || len(ub.MinuteBuckets) != 2 || ub.ActionTriggers[0].Executed != true || ub.ActionTriggers[1].Executed != true { @@ -619,14 +619,14 @@ func TestActionResetAllCounters(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, } resetCountersAction(ub, nil) if ub.Type != UB_TYPE_POSTPAID || - ub.BalanceMap[CREDIT] != 100 || + ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 1 || len(ub.UnitCounters[0].MinuteBuckets) != 1 || len(ub.MinuteBuckets) != 2 || @@ -646,7 +646,7 @@ func TestActionResetCounterMinutes(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -654,7 +654,7 @@ func TestActionResetCounterMinutes(t *testing.T) { a := &Action{BalanceId: MINUTES} resetCounterAction(ub, a) if ub.Type != UB_TYPE_POSTPAID || - ub.BalanceMap[CREDIT] != 100 || + ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 2 || len(ub.UnitCounters[1].MinuteBuckets) != 1 || len(ub.MinuteBuckets) != 2 || @@ -674,7 +674,7 @@ func TestActionResetCounterCREDIT(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{CREDIT: 100}, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}, &UnitsCounter{BalanceId: SMS, Direction: OUTBOUND, Units: 1}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 10, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, Direction: OUTBOUND, ThresholdValue: 2, ActionsId: "TEST_ACTIONS", Executed: true}}, @@ -682,7 +682,7 @@ func TestActionResetCounterCREDIT(t *testing.T) { a := &Action{BalanceId: CREDIT, Direction: OUTBOUND} resetCounterAction(ub, a) if ub.Type != UB_TYPE_POSTPAID || - ub.BalanceMap[CREDIT] != 100 || + ub.BalanceMap[CREDIT].GetTotalValue() != 100 || len(ub.UnitCounters) != 2 || len(ub.MinuteBuckets) != 2 || ub.ActionTriggers[0].Executed != true { diff --git a/rater/calldesc_test.go b/rater/calldesc_test.go index 985b5f426..0d073ce51 100644 --- a/rater/calldesc_test.go +++ b/rater/calldesc_test.go @@ -30,11 +30,9 @@ func init() { func populateDB() { minu := &UserBalance{ - Id: "OUT:vdf:minu", - Type: UB_TYPE_PREPAID, - BalanceMap: map[string]float64{ - CREDIT: 0, - }, + Id: "OUT:vdf:minu", + Type: UB_TYPE_PREPAID, + BalanceMap: map[string]BalanceChain{CREDIT: BalanceChain{&Balance{Value: 0}}}, MinuteBuckets: []*MinuteBucket{ &MinuteBucket{Seconds: 200, DestinationId: "NAT", Weight: 10}, &MinuteBucket{Seconds: 100, DestinationId: "RET", Weight: 20}, diff --git a/rater/responder.go b/rater/responder.go index ec3ed7d13..d41446626 100644 --- a/rater/responder.go +++ b/rater/responder.go @@ -217,7 +217,7 @@ func (rs *Responder) getBalance(arg *CallDescriptor, balanceId string, reply *Ca reply.Tenant = arg.Tenant reply.Account = arg.Account reply.Direction = arg.Direction - reply.Cost = balance + reply.Cost = balance.GetTotalValue() } return nil } diff --git a/rater/simple_serializer.go b/rater/simple_serializer.go index bec2c9077..25c94db06 100644 --- a/rater/simple_serializer.go +++ b/rater/simple_serializer.go @@ -33,7 +33,7 @@ type Serializer interface { var notEnoughElements = errors.New("Too few elements to restore") func (ap *ActivationPeriod) Store() (result string, err error) { - result += strconv.FormatInt(ap.ActivationTime.UnixNano(), 10) + "|" + result += ap.ActivationTime.Format(time.RFC3339) + "|" for _, i := range ap.Intervals { str, err := i.Store() if err != nil { @@ -47,8 +47,11 @@ func (ap *ActivationPeriod) Store() (result string, err error) { func (ap *ActivationPeriod) Restore(input string) error { elements := strings.Split(input, "|") - unixNano, _ := strconv.ParseInt(elements[0], 10, 64) - ap.ActivationTime = time.Unix(0, unixNano).In(time.UTC) + var err error + ap.ActivationTime, err = time.Parse(time.RFC3339, elements[0]) + if err != nil { + return err + } els := elements[1:] if len(els) > 1 { els = elements[1 : len(elements)-1] @@ -260,11 +263,57 @@ func (at *ActionTrigger) Restore(input string) error { return nil } +func (b *Balance) Store() (result string, err error) { + result += b.Id + ")" + result += strconv.FormatFloat(b.Value, 'f', -1, 64) + ")" + result += strconv.FormatFloat(b.Weight, 'f', -1, 64) + ")" + result += b.ExpirationDate.Format(time.RFC3339) + return result, nil +} + +func (b *Balance) Restore(input string) (err error) { + elements := strings.Split(input, ")") + b.Id = elements[0] + b.Value, _ = strconv.ParseFloat(elements[1], 64) + b.Weight, _ = strconv.ParseFloat(elements[2], 64) + b.ExpirationDate, err = time.Parse(time.RFC3339, elements[3]) + return nil +} + +func (bc BalanceChain) Store() (result string, err error) { + for _, b := range bc { + str, err := b.Store() + if err != nil { + return "", err + } + result += str + "^" + } + result = strings.TrimRight(result, "^") + return result, nil +} + +func (bc *BalanceChain) Restore(input string) error { + elements := strings.Split(input, "^") + for _, element := range elements { + b := &Balance{} + err := b.Restore(element) + if err != nil { + return err + } + *bc = append(*bc, b) + } + return nil +} + func (ub *UserBalance) Store() (result string, err error) { result += ub.Id + "|" result += ub.Type + "|" for k, v := range ub.BalanceMap { - result += k + ":" + strconv.FormatFloat(v, 'f', -1, 64) + "#" + bc, err := v.Store() + if err != nil { + return "", err + } + result += k + "=" + bc + "#" } result = strings.TrimRight(result, "#") + "|" for _, mb := range ub.MinuteBuckets { @@ -302,15 +351,18 @@ func (ub *UserBalance) Restore(input string) error { ub.Id = elements[0] ub.Type = elements[1] if ub.BalanceMap == nil { - ub.BalanceMap = make(map[string]float64, 0) + ub.BalanceMap = make(map[string]BalanceChain, 0) } for _, maps := range strings.Split(elements[2], "#") { - kv := strings.Split(maps, ":") + kv := strings.Split(maps, "=") if len(kv) != 2 { continue } - value, _ := strconv.ParseFloat(kv[1], 64) - ub.BalanceMap[kv[0]] = value + bc := BalanceChain{} + if err := (&bc).Restore(kv[1]); err != nil { + return err + } + ub.BalanceMap[kv[0]] = bc } for _, mbs := range strings.Split(elements[3], "#") { if mbs == "" { diff --git a/rater/simple_serializer_test.go b/rater/simple_serializer_test.go index 1d95e73cc..4a76a5232 100644 --- a/rater/simple_serializer_test.go +++ b/rater/simple_serializer_test.go @@ -35,7 +35,7 @@ func TestSimpleMarshallerApStoreRestore(t *testing.T) { ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) result, err := ap.Store() - expected := "1328106601000000000|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0" + expected := "2012-02-01T14:30:01Z|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0" if err != nil || !reflect.DeepEqual(result, expected) { t.Errorf("Expected %q was %q", expected, result) } @@ -47,7 +47,7 @@ func TestSimpleMarshallerApStoreRestore(t *testing.T) { } func TestSimpleMarshallerApRestoreFromString(t *testing.T) { - s := "1325376000000000000|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;1;;0\n" + s := "2012-02-01T14:30:01Z|;1,2,3,4,5,6,7,8,9,10,11,12;1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31;1,2,3,4,5,6,0;00:00:00;;10;0;0.2;60;1;;0\n" ap := &ActivationPeriod{} err := ap.Restore(s) if err != nil || len(ap.Intervals) != 1 { @@ -68,7 +68,7 @@ func TestRpStoreRestore(t *testing.T) { rp := &RatingProfile{FallbackKey: "test"} rp.AddActivationPeriodIfNotPresent("0723", ap) result, err := rp.Store() - expected := "test>0723=1328106601000000000|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0" + expected := "test>0723=2012-02-01T14:30:01Z|;2;1;3,4;14:30:00;15:00:00;0;0;0;0;0;;0" if err != nil || !reflect.DeepEqual(result, expected) { t.Errorf("Expected %q was %q", expected, result) } diff --git a/rater/userbalance.go b/rater/userbalance.go index 332d8360a..c4f200246 100644 --- a/rater/userbalance.go +++ b/rater/userbalance.go @@ -20,7 +20,8 @@ package rater import ( "errors" - //"log" + "sort" + "time" ) const ( @@ -46,12 +47,89 @@ Structure containing information about user's credit (minutes, cents, sms...).' type UserBalance struct { Id string Type string // prepaid-postpaid - BalanceMap map[string]float64 + BalanceMap map[string]BalanceChain MinuteBuckets []*MinuteBucket UnitCounters []*UnitsCounter ActionTriggers ActionTriggerPriotityList } +type Balance struct { + Id string + Value float64 + ExpirationDate time.Time + Weight float64 +} + +func (b *Balance) Equal(o *Balance) bool { + return b.Value == o.Value || + b.ExpirationDate.Equal(o.ExpirationDate) || + b.Weight == o.Weight +} + +func (b *Balance) IsExpired() bool { + return !b.ExpirationDate.IsZero() && b.ExpirationDate.Before(time.Now()) +} + +/* +Structure to store minute buckets according to weight, precision or price. +*/ +type BalanceChain []*Balance + +func (bc BalanceChain) Len() int { + return len(bc) +} + +func (bc BalanceChain) Swap(i, j int) { + bc[i], bc[j] = bc[j], bc[i] +} + +func (bc BalanceChain) Less(j, i int) bool { + return bc[i].Weight < bc[j].Weight +} + +func (bc BalanceChain) Sort() { + sort.Sort(bc) +} + +func (bc BalanceChain) GetTotalValue() (total float64) { + for _, b := range bc { + if !b.IsExpired() { + total += b.Value + } + } + return +} + +func (bc BalanceChain) Debit(amount float64) float64 { + bc.Sort() + for i, b := range bc { + if b.IsExpired() { + continue + } + if b.Value >= amount || i == len(bc)-1 { // if last one go negative + b.Value -= amount + break + } + b.Value = 0 + amount -= b.Value + } + return bc.GetTotalValue() +} + +func (bc BalanceChain) Equal(o BalanceChain) bool { + if len(bc) != len(o) { + return false + } + bc.Sort() + o.Sort() + for i := 0; i < len(bc); i++ { + if !bc[i].Equal(o[i]) { + return false + } + } + return true +} + /* Error type for overflowed debit methods. */ @@ -65,7 +143,7 @@ func (a AmountTooBig) Error() string { Returns user's available minutes for the specified destination */ func (ub *UserBalance) getSecondsForPrefix(prefix string) (seconds, credit float64, bucketList bucketsorter) { - credit = ub.BalanceMap[CREDIT+OUTBOUND] + credit = ub.BalanceMap[CREDIT+OUTBOUND].GetTotalValue() if len(ub.MinuteBuckets) == 0 { // Logger.Debug("There are no minute buckets to check for user: ", ub.Id) return @@ -134,19 +212,19 @@ func (ub *UserBalance) debitMinutesBalance(amount float64, prefix string, count 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 + credit.Debit(mb.Seconds * mb.Price) } } else { if mb.Price > 0 { // debit the money if the bucket has price - credit -= amount * mb.Price + credit.Debit(amount * mb.Price) } break } - if credit < 0 { + if credit.GetTotalValue() < 0 { break } } - if credit < 0 { + if credit.GetTotalValue() < 0 { return new(AmountTooBig) } ub.BalanceMap[CREDIT+OUTBOUND] = credit // credit is > 0 @@ -170,8 +248,8 @@ func (ub *UserBalance) debitBalance(balanceId string, amount float64, count bool if count { ub.countUnits(&Action{BalanceId: balanceId, Direction: OUTBOUND, Units: amount}) } - ub.BalanceMap[balanceId+OUTBOUND] -= amount - return ub.BalanceMap[balanceId+OUTBOUND] + ub.BalanceMap[balanceId+OUTBOUND].Debit(amount) + return ub.BalanceMap[balanceId+OUTBOUND].GetTotalValue() } // Scans the action trigers and execute the actions for which trigger is met diff --git a/rater/userbalance_test.go b/rater/userbalance_test.go index cbfe1a25b..87e2f00e7 100644 --- a/rater/userbalance_test.go +++ b/rater/userbalance_test.go @@ -22,6 +22,7 @@ import ( //"log" "reflect" "testing" + "time" ) var ( @@ -46,6 +47,54 @@ func populateTestActionsForTriggers() { storageGetter.SetActions("TEST_ACTIONS_ORDER", ats1) } +func TestBalanceStoreRestore(t *testing.T) { + b := &Balance{Value: 14, Weight: 1, Id: "test", ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)} + output, err := b.Store() + if err != nil { + t.Error("Error storing balance: ", err) + } + b1 := &Balance{} + err = b1.Restore(output) + if err != nil { + t.Error("Error restoring balance: ", err) + } + if !b.Equal(b1) { + t.Errorf("Balance store/restore failed: expected %v was %v", b, b1) + } +} + +func TestBalanceStoreRestoreZero(t *testing.T) { + b := &Balance{} + output, err := b.Store() + if err != nil { + t.Error("Error storing balance: ", err) + } + b1 := &Balance{} + err = b1.Restore(output) + if err != nil { + t.Error("Error restoring balance: ", err) + } + if !b.Equal(b1) { + t.Errorf("Balance store/restore failed: expected %v was %v", b, b1) + } +} + +func TestBalanceChainStoreRestore(t *testing.T) { + bc := BalanceChain{&Balance{Value: 14, ExpirationDate: time.Date(2013, time.July, 15, 17, 48, 0, 0, time.UTC)}, &Balance{Value: 1024}} + output, err := bc.Store() + if err != nil { + t.Error("Error storing balance chain: ", err) + } + bc1 := BalanceChain{} + err = (&bc1).Restore(output) + if err != nil { + t.Error("Error restoring balance chain: ", err) + } + if !bc.Equal(bc1) { + t.Errorf("Balance chain store/restore failed: expected %v was %v", bc, bc1) + } +} + func TestUserBalanceStoreRestore(t *testing.T) { uc := &UnitsCounter{ Direction: OUTBOUND, @@ -62,41 +111,42 @@ func TestUserBalanceStoreRestore(t *testing.T) { Weight: 10.0, ActionsId: "Commando", } + var zeroTime time.Time + zeroTime = zeroTime.UTC() // for deep equal to find location ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{SMS + OUTBOUND: 14, TRAFFIC + OUTBOUND: 1024}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14, ExpirationDate: zeroTime}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024, ExpirationDate: zeroTime}}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, UnitCounters: []*UnitsCounter{uc, uc}, ActionTriggers: ActionTriggerPriotityList{at, at, at}, } + + var err error r, err := ub.Store() - if err != nil || r != "rif|postpaid|SMSOUT:14#INTERNETOUT:1024|0;20;1;0;NAT#0;10;10;0;RET|OUT/SMS/100/0;20;1;0;NAT,0;10;10;0;RET#OUT/SMS/100/0;20;1;0;NAT,0;10;10;0;RET|some_uuid;MONETARY;OUT;NAT;Commando;100;10;false#some_uuid;MONETARY;OUT;NAT;Commando;100;10;false#some_uuid;MONETARY;OUT;NAT;Commando;100;10;false" && - r != "rif|postpaid|INTERNETOUT:1024#SMSOUT:14|0;20;1;0;NAT#0;10;10;0;RET|OUT/SMS/100/0;20;1;0;NAT,0;10;10;0;RET#OUT/SMS/100/0;20;1;0;NAT,0;10;10;0;RET|some_uuid;MONETARY;OUT;NAT;Commando;100;10;false#some_uuid;MONETARY;OUT;NAT;Commando;100;10;false#some_uuid;MONETARY;OUT;NAT;Commando;100;10;false" { - t.Errorf("Error serializing action timing: %v", string(r)) - } o := &UserBalance{} err = o.Restore(r) - if err != nil || !reflect.DeepEqual(o, ub) { - t.Errorf("Expected %v was %v", ub, o) + if err != nil || !reflect.DeepEqual(ub, o) { + t.Errorf("Expected [%v] was [%v] , err: %v", ub, o, err) } } func TestUserBalanceStorageStoreRestore(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) ub1, err := storageGetter.GetUserBalance("other") - if err != nil || ub1.BalanceMap[CREDIT+OUTBOUND] != rifsBalance.BalanceMap[CREDIT+OUTBOUND] { - t.Errorf("Expected %v was something else", rifsBalance.BalanceMap[CREDIT+OUTBOUND]) //, ub1.BalanceMap[CREDIT+OUTBOUND]) + if err != nil || !ub1.BalanceMap[CREDIT+OUTBOUND].Equal(rifsBalance.BalanceMap[CREDIT+OUTBOUND]) { + t.Log("UB: ", ub1) + t.Errorf("Expected %v was %v", rifsBalance.BalanceMap[CREDIT+OUTBOUND], ub1.BalanceMap[CREDIT+OUTBOUND]) } } func TestGetSecondsForPrefix(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 200}} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 200}}}} seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") expected := 110.0 if credit != 200 || seconds != expected || bucketList[0].Weight < bucketList[1].Weight { @@ -108,7 +158,7 @@ func TestGetPricedSeconds(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Price: 10, Weight: 10, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Price: 1, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} seconds, credit, bucketList := ub1.getSecondsForPrefix("0723") expected := 21.0 if credit != 0 || seconds != expected || len(bucketList) < 2 || bucketList[0].Weight < bucketList[1].Weight { @@ -119,63 +169,63 @@ func TestGetPricedSeconds(t *testing.T) { func TestUserBalanceStorageStore(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} storageGetter.SetUserBalance(rifsBalance) result, err := storageGetter.GetUserBalance(rifsBalance.Id) if err != nil || rifsBalance.Id != result.Id || len(rifsBalance.MinuteBuckets) < 2 || len(result.MinuteBuckets) < 2 || !(rifsBalance.MinuteBuckets[0].Equal(result.MinuteBuckets[0])) || !(rifsBalance.MinuteBuckets[1].Equal(result.MinuteBuckets[1])) || - (rifsBalance.BalanceMap[CREDIT+OUTBOUND] != result.BalanceMap[CREDIT+OUTBOUND]) { - t.Errorf("Expected %v was %v", rifsBalance.MinuteBuckets, result) + !rifsBalance.BalanceMap[CREDIT+OUTBOUND].Equal(result.BalanceMap[CREDIT+OUTBOUND]) { + t.Errorf("Expected %v was %v", rifsBalance, result) } } func TestDebitMoneyBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, 6, false) - if rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 15 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND] { - t.Errorf("Expected %v was %v", 15, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 15 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { + t.Errorf("Expected %v was %v", 15, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitAllMoneyBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} rifsBalance.debitBalance(CREDIT, 21, false) result := rifsBalance.debitBalance(CREDIT, 0, false) - if rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 0 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND] { - t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { + t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitMoreMoneyBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, 22, false) - if rifsBalance.BalanceMap[CREDIT+OUTBOUND] != -1 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND] { - t.Errorf("Expected %v was %v", -1, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -1 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { + t.Errorf("Expected %v was %v", -1, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitNegativeMoneyBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} result := rifsBalance.debitBalance(CREDIT, -15, false) - if rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 36 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND] { - t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + if rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 || result != rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value { + t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(6, "0723", false) if b2.Seconds != 94 || err != nil { t.Log(err) @@ -186,7 +236,7 @@ func TestDebitMinuteBalance(t *testing.T) { func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(105, "0723", false) if b2.Seconds != 0 || b1.Seconds != 5 || err != nil { t.Log(err) @@ -197,7 +247,7 @@ func TestDebitMultipleBucketsMinuteBalance(t *testing.T) { func TestDebitAllMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(110, "0723", false) if b2.Seconds != 0 || b1.Seconds != 0 || err != nil { t.Errorf("Expected %v was %v", 0, b2.Seconds) @@ -207,7 +257,7 @@ func TestDebitAllMinuteBalance(t *testing.T) { func TestDebitMoreMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(115, "0723", false) if b2.Seconds != 100 || b1.Seconds != 10 || err == nil { t.Errorf("Expected %v was %v", 1000, b2.Seconds) @@ -217,62 +267,61 @@ func TestDebitMoreMinuteBalance(t *testing.T) { func TestDebitPriceMinuteBalance0(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(5, "0723", false) - if b2.Seconds != 95 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 16 { - t.Errorf("Expected %v was %v", 16, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + if b2.Seconds != 95 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 16 { + t.Errorf("Expected %v was %v", 16, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitPriceAllMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(21, "0723", false) - if b2.Seconds != 79 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 0 { - t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + if b2.Seconds != 79 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 0 { + t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitPriceMoreMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(25, "0723", false) - if b2.Seconds != 100 || b1.Seconds != 10 || err == nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 21 { - t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 21, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + if b2.Seconds != 100 || b1.Seconds != 10 || err == nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != -4 { + t.Errorf("Expected %v was %v", -4, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitPriceNegativeMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 1.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(-15, "0723", false) - if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 36 { + if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 36 { t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + t.Errorf("Expected %v was %v", 36, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitNegativeMinuteBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} err := rifsBalance.debitMinutesBalance(-15, "0723", false) - if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND] != 21 { + if b2.Seconds != 115 || b1.Seconds != 10 || err != nil || rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value != 21 { t.Log(b1, b2, err) - t.Errorf("Expected %v was %v", 21, rifsBalance.BalanceMap[CREDIT+OUTBOUND]) + t.Errorf("Expected %v was %v", 21, rifsBalance.BalanceMap[CREDIT+OUTBOUND][0].Value) } } func TestDebitSMSBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21, SMS + OUTBOUND: 100}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 12, false) - if rifsBalance.BalanceMap[SMS+OUTBOUND] != 88 || result != rifsBalance.BalanceMap[SMS+OUTBOUND] { + if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 88 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 88, rifsBalance.BalanceMap[SMS+OUTBOUND]) } } @@ -280,9 +329,9 @@ func TestDebitSMSBalance(t *testing.T) { func TestDebitAllSMSBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21, SMS + OUTBOUND: 100}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 100, false) - if rifsBalance.BalanceMap[SMS+OUTBOUND] != 0 || result != rifsBalance.BalanceMap[SMS+OUTBOUND] { + if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 0 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 0, rifsBalance.BalanceMap[SMS+OUTBOUND]) } } @@ -290,19 +339,19 @@ func TestDebitAllSMSBalance(t *testing.T) { func TestDebitMoreSMSBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21, SMS + OUTBOUND: 100}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, 110, false) - if rifsBalance.BalanceMap[SMS+OUTBOUND] != -10 || result != rifsBalance.BalanceMap[SMS+OUTBOUND] { - t.Errorf("Expected %v was %v", -10, rifsBalance.BalanceMap[SMS+OUTBOUND]) + if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != -10 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { + t.Errorf("Expected %v was %v", -10, rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value) } } func TestDebitNegativeSMSBalance(t *testing.T) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.0, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21, SMS + OUTBOUND: 100}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}, SMS + OUTBOUND: BalanceChain{&Balance{Value: 100}}}} result := rifsBalance.debitBalance(SMS, -15, false) - if rifsBalance.BalanceMap[SMS+OUTBOUND] != 115 || result != rifsBalance.BalanceMap[SMS+OUTBOUND] { + if rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value != 115 || result != rifsBalance.BalanceMap[SMS+OUTBOUND][0].Value { t.Errorf("Expected %v was %v", 115, rifsBalance.BalanceMap[SMS+OUTBOUND]) } } @@ -311,7 +360,7 @@ func TestUserBalancedebitMinuteBucket(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{SMS: 14, TRAFFIC: 1024}, + BalanceMap: map[string]BalanceChain{SMS: BalanceChain{&Balance{Value: 14}}, TRAFFIC: BalanceChain{&Balance{Value: 1204}}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, } newMb := &MinuteBucket{Weight: 20, Price: 1, DestinationId: "NEW"} @@ -326,7 +375,7 @@ func TestUserBalancedebitMinuteBucketExists(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{SMS + OUTBOUND: 14, TRAFFIC + OUTBOUND: 1024}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Seconds: 15, Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, } newMb := &MinuteBucket{Seconds: -10, Weight: 20, Price: 1, DestinationId: "NAT"} @@ -340,7 +389,7 @@ func TestUserBalanceAddMinuteNil(t *testing.T) { ub := &UserBalance{ Id: "rif", Type: UB_TYPE_POSTPAID, - BalanceMap: map[string]float64{SMS + OUTBOUND: 14, TRAFFIC + OUTBOUND: 1024}, + BalanceMap: map[string]BalanceChain{SMS + OUTBOUND: BalanceChain{&Balance{Value: 14}}, TRAFFIC + OUTBOUND: BalanceChain{&Balance{Value: 1024}}}, MinuteBuckets: []*MinuteBucket{&MinuteBucket{Weight: 20, Price: 1, DestinationId: "NAT"}, &MinuteBucket{Weight: 10, Price: 10, Percent: 0, DestinationId: "RET"}}, } ub.debitMinuteBucket(nil) @@ -397,12 +446,12 @@ func TestUserBalanceExecuteTriggeredActions(t *testing.T) { func TestUserBalanceExecuteTriggeredActionsOrder(t *testing.T) { ub := &UserBalance{ Id: "TEST_UB_OREDER", - BalanceMap: map[string]float64{CREDIT + OUTBOUND: 100}, + BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 100}}}, UnitCounters: []*UnitsCounter{&UnitsCounter{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}}, ActionTriggers: ActionTriggerPriotityList{&ActionTrigger{BalanceId: CREDIT, ThresholdValue: 2, ActionsId: "TEST_ACTIONS_ORDER"}}, } ub.countUnits(&Action{BalanceId: CREDIT, Direction: OUTBOUND, Units: 1}) - if ub.BalanceMap[CREDIT+OUTBOUND] != 10 { + if len(ub.BalanceMap[CREDIT+OUTBOUND]) != 1 || ub.BalanceMap[CREDIT+OUTBOUND][0].Value != 10 { t.Error("Error executing triggered actions in order", ub.BalanceMap[CREDIT+OUTBOUND]) } } @@ -458,7 +507,7 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { b1 := &MinuteBucket{Seconds: 10, Price: 10, Weight: 10, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Price: 1, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + ub1 := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} b.StartTimer() for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix("0723") @@ -468,7 +517,7 @@ func BenchmarkGetSecondForPrefix(b *testing.B) { func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, Price: 0.01, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, Price: 0.0, DestinationId: "RET"} - rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + rifsBalance := &UserBalance{Id: "other", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { storageGetter.SetUserBalance(rifsBalance) storageGetter.GetUserBalance(rifsBalance.Id) @@ -478,7 +527,7 @@ func BenchmarkUserBalanceStorageStoreRestore(b *testing.B) { func BenchmarkGetSecondsForPrefix(b *testing.B) { b1 := &MinuteBucket{Seconds: 10, Weight: 10, DestinationId: "NAT"} b2 := &MinuteBucket{Seconds: 100, Weight: 20, DestinationId: "RET"} - ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]float64{CREDIT + OUTBOUND: 21}} + ub1 := &UserBalance{Id: "OUT:CUSTOMER_1:rif", MinuteBuckets: []*MinuteBucket{b1, b2}, BalanceMap: map[string]BalanceChain{CREDIT + OUTBOUND: BalanceChain{&Balance{Value: 21}}}} for i := 0; i < b.N; i++ { ub1.getSecondsForPrefix("0723") }