mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
795 lines
21 KiB
Go
795 lines
21 KiB
Go
//go:build integration
|
|
|
|
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
package sessions
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cgrates/birpc/context"
|
|
"github.com/cgrates/cgrates/engine"
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
func TestSessionBasics(t *testing.T) {
|
|
var dbcfg engine.DBCfg
|
|
switch *utils.DBType {
|
|
case utils.MetaInternal:
|
|
dbcfg = engine.InternalDBCfg
|
|
case utils.MetaRedis:
|
|
dbcfg = engine.DBCfg{
|
|
DB: &engine.DBParams{
|
|
DBConns: map[string]engine.DBConn{
|
|
utils.MetaDefault: {
|
|
Type: utils.StringPointer(utils.MetaRedis),
|
|
Host: utils.StringPointer("127.0.0.1"),
|
|
Port: utils.IntPointer(6379),
|
|
Name: utils.StringPointer("10"),
|
|
User: utils.StringPointer(utils.CGRateSLwr),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
case utils.MetaMySQL:
|
|
dbcfg = engine.MySQLDBCfg
|
|
case utils.MetaMongo:
|
|
dbcfg = engine.MongoDBCfg
|
|
case utils.MetaPostgres:
|
|
dbcfg = engine.PostgresDBCfg
|
|
default:
|
|
t.Fatal("unsupported dbtype value")
|
|
}
|
|
|
|
ng := engine.TestEngine{
|
|
ConfigJSON: `{
|
|
"logger": {
|
|
"type": "*stdout"
|
|
},
|
|
"db": {
|
|
"db_conns": {
|
|
"*default": {
|
|
"db_type": "*internal",
|
|
"opts":{
|
|
"internalDBRewriteInterval": "0s",
|
|
"internalDBDumpInterval": "0s"
|
|
}
|
|
}
|
|
},
|
|
},
|
|
"sessions": {
|
|
"enabled": true,
|
|
"accounts_conns": ["*internal"],
|
|
"rates_conns": ["*internal"],
|
|
"cdrs_conns": ["*internal"]
|
|
},
|
|
"cdrs": {
|
|
"enabled": true,
|
|
"accounts_conns": ["*internal"],
|
|
"rates_conns": ["*internal"]
|
|
},
|
|
"accounts": {
|
|
"enabled": true,
|
|
"rates_conns": ["*internal"]
|
|
},
|
|
"admins": {
|
|
"enabled": true
|
|
},
|
|
"rates": {
|
|
"enabled": true
|
|
}
|
|
}`,
|
|
TpFiles: map[string]string{
|
|
utils.RatesCsv: `
|
|
#Tenant,ID,FilterIDs,Weights,MinCost,MaxCost,MaxCostStrategy,RateID,RateFilterIDs,RateActivationStart,RateWeights,RateBlocker,RateIntervalStart,RateFixedFee,RateRecurrentFee,RateUnit,RateIncrement
|
|
cgrates.org,RP_STANDARD,,;10,,,,RT_STANDARD,*string:~*req.Destination:1002,"* * * * *",;10,false,0s,1,1,1m,1m
|
|
cgrates.org,RP_STANDARD,,,,,,RT_STANDARD,,,,,1m,0,0.6,1m,1s
|
|
cgrates.org,RP_FALLBACK,,;0,,,,RT_FALLBACK,*string:~*req.Destination:1002,"* * * * *",;0,false,0s,0,0.01,1s,1s`,
|
|
},
|
|
DBCfg: dbcfg,
|
|
Encoding: *utils.Encoding,
|
|
// LogBuffer: new(bytes.Buffer),
|
|
}
|
|
// t.Cleanup(func() { fmt.Println(ng.LogBuffer) })
|
|
client, _ := ng.Run(t)
|
|
|
|
// account helpers
|
|
setAccount := func(t *testing.T, id string, balances []*utils.Balance) {
|
|
t.Helper()
|
|
acnt := &utils.AccountWithAPIOpts{
|
|
Account: &utils.Account{
|
|
Tenant: "cgrates.org",
|
|
ID: id,
|
|
FilterIDs: []string{
|
|
fmt.Sprintf("*string:~*req.Account:%s", id),
|
|
},
|
|
},
|
|
}
|
|
acnt.Balances = make(map[string]*utils.Balance)
|
|
for _, bal := range balances {
|
|
acnt.Balances[bal.ID] = bal
|
|
}
|
|
|
|
var replySet string
|
|
if err := client.Call(context.Background(), utils.AdminSv1SetAccount,
|
|
acnt, &replySet); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
checkAccountBalances := func(t *testing.T, acntID string, wantBalances map[string]float64) {
|
|
t.Helper()
|
|
var acnt utils.Account
|
|
if err := client.Call(context.Background(), utils.AdminSv1GetAccount,
|
|
&utils.TenantIDWithAPIOpts{
|
|
TenantID: &utils.TenantID{
|
|
Tenant: "cgrates.org",
|
|
ID: "1001",
|
|
},
|
|
}, &acnt); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for blncID, val := range wantBalances {
|
|
gotUnits := acnt.Balances[blncID].Units
|
|
wantUnits := utils.NewDecimalFromFloat64(val)
|
|
if gotUnits.Compare(wantUnits) != 0 {
|
|
t.Errorf("acnt %q balance %q units=%s, want %s",
|
|
acntID, blncID, gotUnits.String(), wantUnits.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// cdr helpers
|
|
cdrNo := 0
|
|
processCDR := func(t *testing.T, acnt, dest, usage string, flags ...string) *utils.CDR {
|
|
t.Helper()
|
|
cdrNo++
|
|
originID := fmt.Sprintf("processCDR%d", cdrNo)
|
|
cgrEv := &utils.CGREvent{
|
|
Tenant: "cgrates.org",
|
|
Event: map[string]any{
|
|
utils.AccountField: acnt,
|
|
utils.Destination: dest,
|
|
utils.AnswerTime: "2018-01-07T17:00:00Z",
|
|
},
|
|
APIOpts: map[string]any{
|
|
utils.MetaOriginID: originID,
|
|
utils.MetaUsage: usage,
|
|
},
|
|
}
|
|
for _, flag := range flags {
|
|
cgrEv.APIOpts[flag] = true
|
|
}
|
|
var rplyProcCDR string
|
|
if err := client.Call(context.Background(), utils.SessionSv1ProcessCDR,
|
|
cgrEv, &rplyProcCDR); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
var cdrs []*utils.CDR
|
|
if err := client.Call(context.Background(), utils.AdminSv1GetCDRs,
|
|
&utils.CDRFilters{
|
|
FilterIDs: []string{
|
|
fmt.Sprintf("*string:~*opts.*originID:%s", originID),
|
|
"*exists:~*opts.*originID:",
|
|
"*notexists:~*req.NonExistentField:",
|
|
"*notempty:~*opts.*originID:",
|
|
},
|
|
}, &cdrs); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(cdrs) != 1 {
|
|
t.Fatalf("%s received %d cdrs, want exactly one", utils.AdminSv1GetCDRs, len(cdrs))
|
|
}
|
|
return cdrs[0]
|
|
}
|
|
|
|
getCostDetails := func(t *testing.T, cdr *utils.CDR, field string) map[string]any {
|
|
t.Helper()
|
|
v, has := cdr.Opts[field]
|
|
if !has {
|
|
t.Fatalf("missing %q field in CDR opts", field)
|
|
}
|
|
costDetails, ok := v.(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("cdr field %q of wrong type %T, want map[string]any", field, v)
|
|
}
|
|
return costDetails
|
|
}
|
|
|
|
checkCDR := func(t *testing.T, cdr *utils.CDR, wantCosts map[string]float64) {
|
|
t.Helper()
|
|
var got float64
|
|
for costKey, want := range wantCosts {
|
|
switch costKey {
|
|
case utils.Abstracts, utils.Concretes:
|
|
cd := getCostDetails(t, cdr, utils.MetaAccountsCost)
|
|
if cd == nil {
|
|
t.Fatalf("Nil costDetails")
|
|
}
|
|
var canCast bool
|
|
got, canCast = cd[costKey].(float64)
|
|
if !canCast {
|
|
t.Fatalf("Could not cast cdr.Opts[utils.MetaCost] to float64")
|
|
}
|
|
case utils.Cost:
|
|
cd := getCostDetails(t, cdr, utils.MetaRateSCost)
|
|
if cd == nil {
|
|
t.Fatalf("Nil costDetails")
|
|
}
|
|
var canCast bool
|
|
got, canCast = cd[costKey].(float64)
|
|
if !canCast {
|
|
t.Fatalf("Could not cast cdr.Opts[utils.MetaCost] to float64")
|
|
}
|
|
case utils.MetaCost:
|
|
var canCast bool
|
|
got, canCast = cdr.Opts[utils.MetaCost].(float64)
|
|
if !canCast {
|
|
t.Fatalf("Could not cast cdr.Opts[utils.MetaCost] to float64")
|
|
}
|
|
default:
|
|
t.Fatalf("invalid cdr cost key: %q", costKey)
|
|
}
|
|
if got != want {
|
|
t.Errorf("cdr %s = %g, want %g", costKey, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
checkCDRMongo := func(t *testing.T, cdr *utils.CDR, wantCosts map[string]float64) {
|
|
t.Helper()
|
|
var got float64
|
|
for costKey, want := range wantCosts {
|
|
switch costKey {
|
|
case "abstracts", "concretes":
|
|
cd := getCostDetails(t, cdr, utils.MetaAccountsCost)
|
|
if cd == nil {
|
|
t.Fatalf("Nil costDetails")
|
|
}
|
|
got = cd[costKey].(float64)
|
|
case "cost":
|
|
cd := getCostDetails(t, cdr, utils.MetaRateSCost)
|
|
if cd == nil {
|
|
t.Fatalf("Nil costDetails")
|
|
}
|
|
got = cd[costKey].(float64)
|
|
case utils.MetaCost:
|
|
got = cdr.Opts[utils.MetaCost].(float64)
|
|
default:
|
|
t.Fatalf("invalid cdr cost key: %q", costKey)
|
|
}
|
|
if got != want {
|
|
t.Errorf("cdr %s = %g, want %g", costKey, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
checkCDRRedis := func(t *testing.T, cdr *utils.CDR, wantCosts map[string]string) {
|
|
t.Helper()
|
|
var got string
|
|
for costKey, want := range wantCosts {
|
|
switch costKey {
|
|
case utils.Abstracts, utils.Concretes:
|
|
cd := getCostDetails(t, cdr, utils.MetaAccountsCost)
|
|
if cd == nil {
|
|
t.Fatalf("Nil costDetails")
|
|
}
|
|
var canCast bool
|
|
got, canCast = cd[costKey].(string)
|
|
if !canCast {
|
|
t.Fatalf("Could not cast cdr.Opts[utils.MetaCost] to string")
|
|
}
|
|
case utils.Cost:
|
|
cd := getCostDetails(t, cdr, utils.MetaRateSCost)
|
|
if cd == nil {
|
|
t.Fatalf("Nil costDetails")
|
|
}
|
|
var canCast bool
|
|
got, canCast = cd[costKey].(string)
|
|
if !canCast {
|
|
t.Fatalf("Could not cast cdr.Opts[utils.MetaCost] to string")
|
|
}
|
|
case utils.MetaCost:
|
|
var canCast bool
|
|
got, canCast = cdr.Opts[utils.MetaCost].(string)
|
|
if !canCast {
|
|
t.Fatalf("Could not cast cdr.Opts[utils.MetaCost] to string")
|
|
}
|
|
default:
|
|
t.Fatalf("invalid cdr cost key: %q", costKey)
|
|
}
|
|
if got != want {
|
|
t.Errorf("cdr %s = %v, want %v", costKey, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// session helpers
|
|
authEvent := func(t *testing.T, wantUsage, wantErr string) {
|
|
t.Helper()
|
|
var reply V1AuthorizeReply
|
|
err := client.Call(context.Background(), utils.SessionSv1AuthorizeEvent,
|
|
&utils.CGREvent{
|
|
Tenant: "cgrates.org",
|
|
APIOpts: map[string]any{
|
|
utils.MetaAccounts: true,
|
|
},
|
|
Event: map[string]any{
|
|
utils.AccountField: "1001",
|
|
utils.Destination: "1002",
|
|
utils.SetupTime: "2018-01-07T17:00:00Z",
|
|
},
|
|
}, &reply)
|
|
assertError(t, utils.SessionSv1AuthorizeEvent, err, wantErr)
|
|
if err == nil {
|
|
wantDecimal := utils.NewDecimalFromUsageIgnoreErr(wantUsage)
|
|
if reply.MaxUsage.Compare(wantDecimal) != 0 {
|
|
t.Errorf("%s reply.MaxUsage=%s, want %s",
|
|
utils.SessionSv1AuthorizeEvent, reply.MaxUsage, wantDecimal)
|
|
t.Logf("%s reply: %s", utils.SessionSv1AuthorizeEvent, utils.ToJSON(reply))
|
|
}
|
|
}
|
|
}
|
|
|
|
authEventWithDigest := func(t *testing.T, wantUsage time.Duration, wantErr string) {
|
|
t.Helper()
|
|
var reply V1AuthorizeReplyWithDigest
|
|
err := client.Call(context.Background(), utils.SessionSv1AuthorizeEventWithDigest,
|
|
&utils.CGREvent{
|
|
Tenant: "cgrates.org",
|
|
APIOpts: map[string]any{
|
|
utils.MetaAccounts: true,
|
|
},
|
|
Event: map[string]any{
|
|
utils.AccountField: "1001",
|
|
utils.Destination: "1002",
|
|
utils.SetupTime: "2018-01-07T17:00:00Z",
|
|
},
|
|
}, &reply)
|
|
assertError(t, utils.SessionSv1AuthorizeEventWithDigest, err, wantErr)
|
|
if err == nil {
|
|
if got := time.Duration(wantUsage).Nanoseconds(); got != reply.MaxUsage {
|
|
t.Errorf("%s reply.MaxUsage=%d, want %d", utils.SessionSv1AuthorizeEventWithDigest, reply.MaxUsage, got)
|
|
t.Logf("%s reply: %s", utils.SessionSv1AuthorizeEvent, utils.ToJSON(reply))
|
|
}
|
|
}
|
|
}
|
|
|
|
t.Run("auth and cdr", func(t *testing.T) {
|
|
// Account requested not found, should fail here with error
|
|
authEvent(t, "", "ACCOUNTS_ERROR:NOT_FOUND")
|
|
|
|
// Available less than requested(1m)
|
|
setAccount(t, "1001", []*utils.Balance{
|
|
{
|
|
ID: "ABSTRACT1",
|
|
Type: utils.MetaAbstract,
|
|
Weights: utils.DynamicWeights{&utils.DynamicWeight{Weight: 20.0}},
|
|
CostIncrements: []*utils.CostIncrement{
|
|
{
|
|
Increment: utils.NewDecimalFromUsageIgnoreErr("1s"),
|
|
RecurrentFee: utils.NewDecimalFromFloat64(0.01),
|
|
},
|
|
},
|
|
Units: utils.NewDecimalFromUsageIgnoreErr("1m"),
|
|
},
|
|
{
|
|
ID: "CONCRETE1",
|
|
Type: utils.MetaConcrete,
|
|
Weights: utils.DynamicWeights{&utils.DynamicWeight{Weight: 10.0}},
|
|
CostIncrements: []*utils.CostIncrement{
|
|
{
|
|
Increment: utils.NewDecimalFromUsageIgnoreErr("1s"),
|
|
RecurrentFee: utils.NewDecimalFromFloat64(0.01),
|
|
},
|
|
},
|
|
Units: utils.NewDecimalFromFloat64(0.5),
|
|
},
|
|
})
|
|
|
|
authEvent(t, "50s", "")
|
|
authEventWithDigest(t, 50*time.Second, "")
|
|
|
|
setAccount(t, "1001", []*utils.Balance{
|
|
{
|
|
ID: "CONCRETE1",
|
|
Type: utils.MetaConcrete,
|
|
Weights: utils.DynamicWeights{&utils.DynamicWeight{Weight: 10.0}},
|
|
CostIncrements: []*utils.CostIncrement{
|
|
{
|
|
Increment: utils.NewDecimalFromUsageIgnoreErr("1s"),
|
|
RecurrentFee: utils.NewDecimalFromFloat64(0.01),
|
|
},
|
|
},
|
|
Units: utils.NewDecimalFromFloat64(10),
|
|
},
|
|
})
|
|
authEvent(t, "1m", "")
|
|
authEventWithDigest(t, time.Minute, "")
|
|
|
|
// accounting via CostIncrements
|
|
cdr := processCDR(t, "1001", "1002", "1m30s", utils.MetaAccounts)
|
|
switch *utils.DBType {
|
|
case utils.MetaMongo: // field names are lowercase on mongo
|
|
checkCDRMongo(t, cdr,
|
|
map[string]float64{
|
|
"abstracts": 90000000000.0,
|
|
"concretes": 0.9,
|
|
utils.MetaCost: 0.9,
|
|
})
|
|
case utils.MetaRedis:
|
|
checkCDRRedis(t, cdr,
|
|
map[string]string{
|
|
utils.Abstracts: "90000000000",
|
|
utils.Concretes: "0.9",
|
|
utils.MetaCost: "0.9",
|
|
})
|
|
default:
|
|
checkCDR(t, cdr,
|
|
map[string]float64{
|
|
utils.Abstracts: 90000000000.0,
|
|
utils.Concretes: 0.9,
|
|
utils.MetaCost: 0.9,
|
|
})
|
|
}
|
|
|
|
checkAccountBalances(t, "1001", map[string]float64{
|
|
"CONCRETE1": 9.1,
|
|
})
|
|
})
|
|
|
|
t.Run("rates accounting", func(t *testing.T) {
|
|
setAccount(t, "1001", []*utils.Balance{
|
|
{
|
|
ID: "CONCRETE1",
|
|
Type: utils.MetaConcrete,
|
|
Weights: utils.DynamicWeights{&utils.DynamicWeight{Weight: 10.0}},
|
|
Units: utils.NewDecimalFromFloat64(10),
|
|
},
|
|
})
|
|
cdr := processCDR(t, "1001", "1002", "2m30s", utils.MetaAccounts)
|
|
switch *utils.DBType {
|
|
case utils.MetaMongo: // field names are lowercase on mongo
|
|
checkCDRMongo(t, cdr,
|
|
map[string]float64{
|
|
"abstracts": float64(150 * time.Second),
|
|
"concretes": 2.9,
|
|
utils.MetaCost: 2.9,
|
|
})
|
|
case utils.MetaRedis:
|
|
checkCDRRedis(t, cdr,
|
|
map[string]string{
|
|
utils.Abstracts: "150000000000",
|
|
utils.Concretes: "2.90",
|
|
utils.MetaCost: "2.90",
|
|
})
|
|
default:
|
|
checkCDR(t, cdr,
|
|
map[string]float64{
|
|
utils.Abstracts: float64(150 * time.Second),
|
|
utils.Concretes: 2.9,
|
|
utils.MetaCost: 2.9,
|
|
})
|
|
}
|
|
checkAccountBalances(t, "1001", map[string]float64{
|
|
"CONCRETE1": 7.1,
|
|
})
|
|
})
|
|
|
|
t.Run("rating", func(t *testing.T) {
|
|
setAccount(t, "1001", []*utils.Balance{
|
|
{
|
|
ID: "CONCRETE1",
|
|
Type: utils.MetaConcrete,
|
|
Weights: utils.DynamicWeights{&utils.DynamicWeight{Weight: 10.0}},
|
|
Units: utils.NewDecimalFromFloat64(10),
|
|
},
|
|
})
|
|
cdr := processCDR(t, "1001", "1002", "2m30s", utils.MetaRates)
|
|
switch *utils.DBType {
|
|
case utils.MetaMongo: // field names are lowercase on mongo
|
|
checkCDRMongo(t, cdr,
|
|
map[string]float64{
|
|
"cost": 2.9,
|
|
utils.MetaCost: 2.9,
|
|
})
|
|
case utils.MetaRedis:
|
|
checkCDRRedis(t, cdr,
|
|
map[string]string{
|
|
utils.Cost: "2.90",
|
|
utils.MetaCost: "2.90",
|
|
})
|
|
default:
|
|
checkCDR(t, cdr,
|
|
map[string]float64{
|
|
utils.Cost: 2.9,
|
|
utils.MetaCost: 2.9,
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("rates accounting with fallback", func(t *testing.T) {
|
|
t.Skip("looping through all max_increments inside maxDebitAbstractsFromConcretes")
|
|
setAccount(t, "1001", []*utils.Balance{
|
|
{
|
|
ID: "CONCRETE1",
|
|
Type: utils.MetaConcrete,
|
|
Weights: utils.DynamicWeights{&utils.DynamicWeight{Weight: 10.0}},
|
|
Units: utils.NewDecimalFromFloat64(2.9), // balance only enough for 2m30s usage
|
|
},
|
|
})
|
|
cdr := processCDR(t, "1001", "1002", "3m15s", utils.MetaAccounts)
|
|
if *utils.DBType == utils.MetaMongo { // field names are lowercase on mongo
|
|
checkCDRMongo(t, cdr,
|
|
map[string]float64{
|
|
"abstracts": float64(150 * time.Second),
|
|
"concretes": 2.9,
|
|
utils.MetaCost: 2.9,
|
|
})
|
|
} else {
|
|
checkCDR(t, cdr,
|
|
map[string]float64{
|
|
utils.Abstracts: float64(150 * time.Second),
|
|
utils.Concretes: 2.9,
|
|
utils.MetaCost: 2.9,
|
|
})
|
|
}
|
|
checkAccountBalances(t, "1001", map[string]float64{
|
|
"CONCRETE1": 7.1,
|
|
})
|
|
})
|
|
|
|
t.Run("rating and accounting", func(t *testing.T) {
|
|
setAccount(t, "1001", []*utils.Balance{
|
|
{
|
|
ID: "CONCRETE1",
|
|
Type: utils.MetaConcrete,
|
|
CostIncrements: []*utils.CostIncrement{
|
|
{
|
|
Increment: utils.NewDecimalFromUsageIgnoreErr("1s"),
|
|
RecurrentFee: utils.NewDecimalFromFloat64(0.01),
|
|
},
|
|
},
|
|
Units: utils.NewDecimalFromFloat64(10),
|
|
},
|
|
})
|
|
cdr := processCDR(t, "1001", "1002", "2m30s", utils.MetaAccounts, utils.MetaRates)
|
|
switch *utils.DBType {
|
|
case utils.MetaMongo: // field names are lowercase on mongo
|
|
checkCDRMongo(t, cdr,
|
|
map[string]float64{
|
|
"abstracts": float64(150 * time.Second),
|
|
"concretes": 1.5,
|
|
utils.MetaCost: 1.5,
|
|
"cost": 2.9,
|
|
})
|
|
case utils.MetaRedis:
|
|
checkCDRRedis(t, cdr,
|
|
map[string]string{
|
|
utils.Abstracts: "150000000000",
|
|
utils.Concretes: "1.5",
|
|
utils.MetaCost: "1.5",
|
|
utils.Cost: "2.90",
|
|
})
|
|
default:
|
|
checkCDR(t, cdr,
|
|
map[string]float64{
|
|
utils.Abstracts: float64(150 * time.Second),
|
|
utils.Concretes: 1.5,
|
|
utils.MetaCost: 1.5,
|
|
utils.Cost: 2.9,
|
|
})
|
|
}
|
|
checkAccountBalances(t, "1001", map[string]float64{
|
|
"CONCRETE1": 8.5,
|
|
})
|
|
})
|
|
}
|
|
|
|
func assertError(t *testing.T, method string, err error, wantErr string) {
|
|
t.Helper()
|
|
if wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("%s: unexpected error: got %v, want none", method, err)
|
|
}
|
|
} else {
|
|
if err == nil {
|
|
t.Fatalf("%s: expected error %q, got none", method, wantErr)
|
|
}
|
|
if err.Error() != wantErr {
|
|
t.Fatalf("%s: error mismatch: got %q, want %q", method, err.Error(), wantErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSessionLifecycle(t *testing.T) {
|
|
var dbcfg engine.DBCfg
|
|
switch *utils.DBType {
|
|
case utils.MetaInternal:
|
|
dbcfg = engine.InternalDBCfg
|
|
case utils.MetaRedis:
|
|
dbcfg = engine.RedisDBCfg
|
|
case utils.MetaMySQL:
|
|
dbcfg = engine.MySQLDBCfg
|
|
case utils.MetaMongo:
|
|
dbcfg = engine.MongoDBCfg
|
|
case utils.MetaPostgres:
|
|
dbcfg = engine.PostgresDBCfg
|
|
default:
|
|
t.Fatal("unsupported dbtype value")
|
|
}
|
|
|
|
ng := engine.TestEngine{
|
|
ConfigJSON: `{
|
|
"db": {
|
|
"db_conns": {
|
|
"*default": {
|
|
"db_type": "*internal",
|
|
"opts":{
|
|
"internalDBRewriteInterval": "0s",
|
|
"internalDBDumpInterval": "0s"
|
|
}
|
|
}
|
|
},
|
|
},
|
|
"sessions": {
|
|
"enabled": true,
|
|
"chargers_conns": ["*localhost"],
|
|
"alterable_fields": ["AlterableField"],
|
|
"terminate_attempts": 1
|
|
},
|
|
"chargers": {
|
|
"enabled": true
|
|
},
|
|
"admins": {
|
|
"enabled": true
|
|
}
|
|
}`,
|
|
DBCfg: dbcfg,
|
|
Encoding: *utils.Encoding,
|
|
// LogBuffer: new(bytes.Buffer),
|
|
}
|
|
// t.Cleanup(func() { fmt.Println(ng.LogBuffer) })
|
|
client, _ := ng.Run(t)
|
|
|
|
initSession := func(t *testing.T, originID string) {
|
|
t.Helper()
|
|
var reply V1InitSessionReply
|
|
err := client.Call(context.Background(), utils.SessionSv1InitiateSession,
|
|
&utils.CGREvent{
|
|
Tenant: "cgrates.org",
|
|
Event: map[string]any{
|
|
utils.AccountField: "1001",
|
|
utils.Destination: "1002",
|
|
utils.SetupTime: "2018-01-07T17:00:00Z",
|
|
"AlterableField": "test_val",
|
|
},
|
|
APIOpts: map[string]any{
|
|
utils.MetaOriginID: originID,
|
|
utils.MetaInitiate: true,
|
|
utils.MetaChargers: true,
|
|
},
|
|
}, &reply)
|
|
if err != nil {
|
|
t.Fatalf("failed to initiate session %s: %v", originID, err)
|
|
}
|
|
}
|
|
|
|
updateSession := func(t *testing.T, originID string) {
|
|
t.Helper()
|
|
var reply V1UpdateSessionReply
|
|
err := client.Call(context.Background(), utils.SessionSv1UpdateSession,
|
|
&utils.CGREvent{
|
|
Tenant: "cgrates.org",
|
|
Event: map[string]any{
|
|
utils.AccountField: "1001",
|
|
utils.Destination: "1002",
|
|
utils.SetupTime: "2018-01-07T17:00:00Z",
|
|
"AlterableField": "new_val",
|
|
},
|
|
APIOpts: map[string]any{
|
|
utils.MetaOriginID: originID,
|
|
utils.MetaUpdate: true,
|
|
},
|
|
}, &reply)
|
|
if err != nil {
|
|
t.Fatalf("failed to update session %s: %v", originID, err)
|
|
}
|
|
}
|
|
|
|
termSession := func(t *testing.T, originID string) {
|
|
t.Helper()
|
|
var reply string
|
|
err := client.Call(context.Background(), utils.SessionSv1TerminateSession,
|
|
&utils.CGREvent{
|
|
Tenant: "cgrates.org",
|
|
Event: map[string]any{
|
|
utils.AccountField: "1001",
|
|
utils.Destination: "1002",
|
|
utils.SetupTime: "2018-01-07T17:00:00Z",
|
|
},
|
|
APIOpts: map[string]any{
|
|
utils.MetaOriginID: originID,
|
|
utils.MetaTerminate: true,
|
|
},
|
|
}, &reply)
|
|
if err != nil {
|
|
t.Fatalf("failed to terminate session %s: %v", originID, err)
|
|
}
|
|
}
|
|
|
|
checkActiveSessions := func(t *testing.T, wantCount int, filters ...string) []*ExternalSession {
|
|
t.Helper()
|
|
var sessions []*ExternalSession
|
|
if err := client.Call(context.Background(), utils.SessionSv1GetActiveSessions,
|
|
&utils.SessionFilter{
|
|
Filters: filters,
|
|
}, &sessions); err != nil {
|
|
if wantCount == 0 && err.Error() == utils.ErrNotFound.Error() {
|
|
t.Logf("no active sessions found (expected)")
|
|
return nil
|
|
}
|
|
t.Fatalf("failed to get active sessions: %v", err)
|
|
}
|
|
if len(sessions) != wantCount {
|
|
t.Fatalf("%s received %d sessions, want exactly %d",
|
|
utils.SessionSv1GetActiveSessions, len(sessions), wantCount)
|
|
}
|
|
t.Logf("%s reply: %s", utils.SessionSv1GetActiveSessions, utils.ToIJSON(sessions))
|
|
return sessions
|
|
}
|
|
|
|
var replySetCharger string
|
|
if err := client.Call(context.Background(), utils.AdminSv1SetChargerProfile,
|
|
&utils.ChargerProfileWithAPIOpts{
|
|
ChargerProfile: &utils.ChargerProfile{
|
|
Tenant: "cgrates.org",
|
|
ID: "DEFAULT",
|
|
RunID: utils.MetaDefault,
|
|
AttributeIDs: []string{utils.MetaNone},
|
|
},
|
|
}, &replySetCharger); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
checkActiveSessions(t, 0)
|
|
|
|
sessionID := "test-session-123"
|
|
initSession(t, sessionID)
|
|
|
|
sessions := checkActiveSessions(t, 1)
|
|
want := "test_val"
|
|
if sessions[0].CGREvent.Event["AlterableField"] != want {
|
|
t.Errorf("after init, AlterableField = %v, want %s", sessions[0].CGREvent.Event["AlterableField"], want)
|
|
}
|
|
|
|
updateSession(t, sessionID)
|
|
|
|
sessions = checkActiveSessions(t, 1)
|
|
want = "new_val"
|
|
if sessions[0].CGREvent.Event["AlterableField"] != want {
|
|
t.Errorf("after update, AlterableField = %v, want %s", sessions[0].CGREvent.Event["AlterableField"], want)
|
|
}
|
|
|
|
termSession(t, sessionID)
|
|
checkActiveSessions(t, 0)
|
|
}
|