Revise mongo decoding for decimals in interfaces

This commit is contained in:
arberkatellari
2025-07-04 18:01:58 +02:00
committed by Dan Christian Bogos
parent ad5ec6c062
commit 09624ff660
2 changed files with 130 additions and 31 deletions

View File

@@ -150,6 +150,31 @@ func decimalDecoder(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val refle
return nil
}
// when trying to decode into map[string]any, range over the map and convert the primitive.Binary underlying types, back to decimal
func mapStringAnyDecoderWithDecimal(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
defaultDecoder, err := bson.NewRegistry().LookupDecoder(val.Type())
if err != nil {
return err
}
// decode map[string]any using default decoder
if err := defaultDecoder.DecodeValue(dc, vr, val); err != nil {
return err
}
// convert binary val values back to decimal
m := val.Interface().(map[string]any)
for k, v := range m {
if binary, ok := v.(primitive.Binary); ok {
// Convert binary to decimal
dBig := decimal.WithContext(utils.DecimalContext)
if err := dBig.UnmarshalText(binary.Data); err != nil {
return err
}
m[k] = &utils.Decimal{Big: dBig}
}
}
return nil
}
// NewMongoStorage initializes a new MongoDB storage instance with provided connection parameters and settings.
// Returns an error if the setup fails.
func NewMongoStorage(scheme, host, port, db, user, pass, mrshlerStr string, storageType string,
@@ -165,6 +190,7 @@ func NewMongoStorage(scheme, host, port, db, user, pass, mrshlerStr string, stor
decimalType := reflect.TypeOf(utils.Decimal{})
reg.RegisterTypeEncoder(decimalType, bsoncodec.ValueEncoderFunc(decimalEncoder))
reg.RegisterTypeDecoder(decimalType, bsoncodec.ValueDecoderFunc(decimalDecoder))
reg.RegisterTypeDecoder(reflect.TypeOf(map[string]any{}), bsoncodec.ValueDecoderFunc(mapStringAnyDecoderWithDecimal))
// serverAPI := options.ServerAPI(options.ServerAPIVersion1).SetStrict(true).SetDeprecationErrors(true)
opts := options.Client().
ApplyURI(uri).

View File

@@ -36,7 +36,6 @@ func TestSessionBasics(t *testing.T) {
dbcfg = engine.InternalDBCfg
case utils.MetaMySQL:
case utils.MetaMongo:
t.SkipNow() // unfinished look into errors with decimals in CDRs
dbcfg = engine.MongoDBCfg
case utils.MetaPostgres:
dbcfg = engine.PostgresDBCfg
@@ -229,6 +228,34 @@ cgrates.org,RP_FALLBACK,,;0,,,,RT_FALLBACK,*string:~*req.Destination:1002,"* * *
}
}
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)
}
}
}
// session helpers
authEvent := func(t *testing.T, wantUsage, wantErr string) {
t.Helper()
@@ -334,12 +361,22 @@ cgrates.org,RP_FALLBACK,,;0,,,,RT_FALLBACK,*string:~*req.Destination:1002,"* * *
// 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,
})
if *utils.DBType == utils.MetaMongo { // field names are lowercase on mongo
checkCDRMongo(t, cdr,
map[string]float64{
"abstracts": 90000000000.0,
"concretes": 0.9,
utils.MetaCost: 0.9,
})
} else {
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,
})
@@ -355,12 +392,21 @@ cgrates.org,RP_FALLBACK,,;0,,,,RT_FALLBACK,*string:~*req.Destination:1002,"* * *
},
})
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,
})
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,
})
@@ -376,11 +422,19 @@ cgrates.org,RP_FALLBACK,,;0,,,,RT_FALLBACK,*string:~*req.Destination:1002,"* * *
},
})
cdr := processCDR(t, "1001", "1002", "2m30s", utils.MetaRates)
checkCDR(t, cdr,
map[string]float64{
utils.Cost: 2.9,
utils.MetaCost: 2.9,
})
if *utils.DBType == utils.MetaMongo { // field names are lowercase on mongo
checkCDRMongo(t, cdr,
map[string]float64{
"cost": 2.9,
utils.MetaCost: 2.9,
})
} else {
checkCDR(t, cdr,
map[string]float64{
utils.Cost: 2.9,
utils.MetaCost: 2.9,
})
}
})
t.Run("rates accounting with fallback", func(t *testing.T) {
@@ -394,12 +448,21 @@ cgrates.org,RP_FALLBACK,,;0,,,,RT_FALLBACK,*string:~*req.Destination:1002,"* * *
},
})
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,
})
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,
})
@@ -420,13 +483,23 @@ cgrates.org,RP_FALLBACK,,;0,,,,RT_FALLBACK,*string:~*req.Destination:1002,"* * *
},
})
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,
})
if *utils.DBType == 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,
})
} else {
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,
})