diff --git a/engine/storage_mongo_datadb.go b/engine/storage_mongo_datadb.go index 29da3a312..d2e93bb00 100644 --- a/engine/storage_mongo_datadb.go +++ b/engine/storage_mongo_datadb.go @@ -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). diff --git a/sessions/basics_it_test.go b/sessions/basics_it_test.go index ce95405c9..33d517f4a 100644 --- a/sessions/basics_it_test.go +++ b/sessions/basics_it_test.go @@ -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, })