mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-14 20:59:53 +05:00
424 lines
12 KiB
Go
424 lines
12 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 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 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) {
|
|
switch *utils.DBType {
|
|
case utils.MetaInternal:
|
|
case utils.MetaMySQL, utils.MetaMongo, utils.MetaPostgres:
|
|
t.SkipNow()
|
|
default:
|
|
t.Fatal("unsupported dbtype value")
|
|
}
|
|
|
|
ng := engine.TestEngine{
|
|
ConfigJSON: `{
|
|
"logger": {
|
|
"type": "*stdout"
|
|
},
|
|
"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: engine.InternalDBCfg,
|
|
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),
|
|
},
|
|
}, &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)
|
|
got = cd[costKey].(float64)
|
|
case utils.Cost:
|
|
cd := getCostDetails(t, cdr, utils.MetaRateSCost)
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
}
|
|
}
|
|
}
|