diff --git a/docs/tut_cgrates_usage.rst b/docs/tut_cgrates_usage.rst index 8697ac921..cde581441 100644 --- a/docs/tut_cgrates_usage.rst +++ b/docs/tut_cgrates_usage.rst @@ -4,9 +4,11 @@ Loading **CGRateS** Tariff Plans -------------------------------- -Before proceeding to this step, you should have **CGRateS** installed and started with custom configuration, depending on the tutorial you have followed. +Before proceeding to this step, you should have **CGRateS** installed and +started with custom configuration, depending on the tutorial you have followed. -For our tutorial we load again prepared data out of shared folder, containing following rules: +For our tutorial we load again prepared data out of shared folder, containing +following rules: - Create the necessary timings (always, asap, peak, offpeak). - Configure 3 destinations (1002, 1003 and 10 used as catch all rule). @@ -68,11 +70,11 @@ To verify that all actions successfully performed, we use following *cgr-console :: - cgr-console 'account Tenant="cgrates.org" Account="1001"' - cgr-console 'account Tenant="cgrates.org" Account="1002"' - cgr-console 'account Tenant="cgrates.org" Account="1003"' - cgr-console 'account Tenant="cgrates.org" Account="1004"' - cgr-console 'account Tenant="cgrates.org" Account="1005"' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1002"]' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1003"]' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1004"]' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1005"]' - Query call costs so we can see our calls will have expected costs (final cost will result as sum of *ConnectFee* and *Cost* fields): @@ -108,7 +110,7 @@ Check that 1001 balance is properly deducted, during the call, and moreover cons :: - cgr-console 'account Tenant="cgrates.org" Account="1001"' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]' 1002 -> 1001 @@ -120,7 +122,7 @@ To check that we had debits we use again console command, this time not during t :: - cgr-console 'account Tenant="cgrates.org" Account="1002"' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1002"]' 1003 -> 1001 @@ -132,7 +134,7 @@ To check that there are no debits during or by the end of the call, but when the :: - cgr-console 'account Tenant="cgrates.org" Account="1003"' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1003"]' 1004 -> 1001 @@ -150,8 +152,8 @@ Check that 1001 balance is properly debitted, during the call, and moreover cons :: - cgr-console 'account Tenant="cgrates.org" Account="1006"' - cgr-console 'account Tenant="cgrates.org" Account="1001"' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1006"]' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]' 1007 -> 1002 @@ -163,8 +165,8 @@ Check that call can proceed even if 1007 has no units left into his own balances :: - cgr-console 'account Tenant="cgrates.org" Account="1007"' - cgr-console 'account Tenant="cgrates.org" Account="1001"' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1007"]' + cgr-console 'accounts Tenant="cgrates.org" AccountIds=["1001"]' CDR Exporting @@ -192,4 +194,4 @@ To verify this mechanism simply add some random units into one account's balance cgr-console 'balance_set Tenant="cgrates.org" Account="1001" Direction="*out" Value=101' tail -f /var/log/syslog -n 20 -On the CDRs side we will be able to integrate CdrStats monitors as part of our Fraud Detection system (eg: the increase of average cost for 1001 and 1002 accounts will signal us abnormalities, hence we will be notified via syslog). \ No newline at end of file +On the CDRs side we will be able to integrate CdrStats monitors as part of our Fraud Detection system (eg: the increase of average cost for 1001 and 1002 accounts will signal us abnormalities, hence we will be notified via syslog). diff --git a/engine/cdrs.go b/engine/cdrs.go index 5967cff15..5a0ad538e 100644 --- a/engine/cdrs.go +++ b/engine/cdrs.go @@ -150,7 +150,8 @@ func (self *CdrServer) processCdr(storedCdr *StoredCdr) (err error) { if upData, err := LoadUserProfile(storedCdr, "ExtraFields"); err != nil { return err } else { - *storedCdr = upData.(StoredCdr) + cdrRcv := upData.(*StoredCdr) + *storedCdr = *cdrRcv } if storedCdr.ReqType == utils.META_NONE { return nil diff --git a/engine/responder.go b/engine/responder.go index 6cee095d1..7d89c10ae 100644 --- a/engine/responder.go +++ b/engine/responder.go @@ -54,6 +54,12 @@ func (rs *Responder) GetCost(arg *CallDescriptor, reply *CallCost) (err error) { if arg.Subject == "" { arg.Subject = arg.Account } + if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*CallDescriptor) + *arg = *udRcv + } if rs.Bal != nil { r, e := rs.getCallCost(arg, "Responder.GetCost") *reply, err = *r, e @@ -74,6 +80,12 @@ func (rs *Responder) Debit(arg *CallDescriptor, reply *CallCost) (err error) { if arg.Subject == "" { arg.Subject = arg.Account } + if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*CallDescriptor) + *arg = *udRcv + } if rs.Bal != nil { r, e := rs.getCallCost(arg, "Responder.Debit") *reply, err = *r, e @@ -92,6 +104,12 @@ func (rs *Responder) MaxDebit(arg *CallDescriptor, reply *CallCost) (err error) if arg.Subject == "" { arg.Subject = arg.Account } + if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*CallDescriptor) + *arg = *udRcv + } if rs.Bal != nil { r, e := rs.getCallCost(arg, "Responder.MaxDebit") *reply, err = *r, e @@ -110,6 +128,12 @@ func (rs *Responder) RefundIncrements(arg *CallDescriptor, reply *float64) (err if arg.Subject == "" { arg.Subject = arg.Account } + if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*CallDescriptor) + *arg = *udRcv + } if rs.Bal != nil { *reply, err = rs.callMethod(arg, "Responder.RefundIncrements") } else { @@ -125,6 +149,12 @@ func (rs *Responder) GetMaxSessionTime(arg *CallDescriptor, reply *float64) (err if arg.Subject == "" { arg.Subject = arg.Account } + if upData, err := LoadUserProfile(arg, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*CallDescriptor) + *arg = *udRcv + } if rs.Bal != nil { *reply, err = rs.callMethod(arg, "Responder.GetMaxSessionTime") } else { @@ -136,11 +166,17 @@ func (rs *Responder) GetMaxSessionTime(arg *CallDescriptor, reply *float64) (err // Returns MaxSessionTime for an event received in SessionManager, considering DerivedCharging for it func (rs *Responder) GetDerivedMaxSessionTime(ev *StoredCdr, reply *float64) error { + if rs.Bal != nil { + return errors.New("unsupported method on the balancer") + } if ev.Subject == "" { ev.Subject = ev.Account } - if rs.Bal != nil { - return errors.New("unsupported method on the balancer") + if upData, err := LoadUserProfile(ev, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*StoredCdr) + *ev = *udRcv } maxCallDuration := -1.0 attrsDC := &utils.AttrDerivedChargers{Tenant: ev.GetTenant(utils.META_DEFAULT), Category: ev.GetCategory(utils.META_DEFAULT), Direction: ev.GetDirection(utils.META_DEFAULT), @@ -205,11 +241,17 @@ func (rs *Responder) GetDerivedMaxSessionTime(ev *StoredCdr, reply *float64) err // Used by SM to get all the prepaid CallDescriptors attached to a session func (rs *Responder) GetSessionRuns(ev *StoredCdr, sRuns *[]*SessionRun) error { + if rs.Bal != nil { + return errors.New("Unsupported method on the balancer") + } if ev.Subject == "" { ev.Subject = ev.Account } - if rs.Bal != nil { - return errors.New("Unsupported method on the balancer") + if upData, err := LoadUserProfile(ev, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*StoredCdr) + *ev = *udRcv } attrsDC := &utils.AttrDerivedChargers{Tenant: ev.GetTenant(utils.META_DEFAULT), Category: ev.GetCategory(utils.META_DEFAULT), Direction: ev.GetDirection(utils.META_DEFAULT), Account: ev.GetAccount(utils.META_DEFAULT), Subject: ev.GetSubject(utils.META_DEFAULT)} @@ -257,6 +299,12 @@ func (rs *Responder) ProcessCdr(cdr *StoredCdr, reply *string) error { if rs.CdrSrv == nil { return errors.New("CDR_SERVER_NOT_RUNNING") } + if upData, err := LoadUserProfile(cdr, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*StoredCdr) + *cdr = *udRcv + } if err := rs.CdrSrv.ProcessCdr(cdr); err != nil { return err } @@ -279,6 +327,12 @@ func (rs *Responder) GetLCR(cd *CallDescriptor, reply *LCRCost) error { if cd.Subject == "" { cd.Subject = cd.Account } + if upData, err := LoadUserProfile(cd, "ExtraFields"); err != nil { + return err + } else { + udRcv := upData.(*CallDescriptor) + *cd = *udRcv + } lcrCost, err := cd.GetLCR(rs.Stats) if err != nil { return err diff --git a/engine/users.go b/engine/users.go index c0fe01f1c..84018ac9c 100644 --- a/engine/users.go +++ b/engine/users.go @@ -360,10 +360,7 @@ func LoadUserProfile(in interface{}, extraFields string) (interface{}, error) { if userService == nil { // no user service => no fun return in, nil } - m, err := utils.ToMapStringString(in) - if err != nil { - return nil, err - } + m := utils.ToMapStringString(in) var needsUsers bool for _, val := range m { if val == utils.USERS { @@ -391,12 +388,9 @@ func LoadUserProfile(in interface{}, extraFields string) (interface{}, error) { } // add extra fields if extraFields != "" { - extra, err := utils.GetMapExtraFields(in, extraFields) - if err != nil { - return nil, err - } + extra := utils.GetMapExtraFields(in, extraFields) for key, val := range extra { - if val != "" { + if val != "" && val != utils.USERS { up.Profile[key] = val } } @@ -409,7 +403,9 @@ func LoadUserProfile(in interface{}, extraFields string) (interface{}, error) { up = ups[0] m := up.Profile m["Tenant"] = up.Tenant - return utils.FromMapStringString(m, in) + utils.FromMapStringString(m, in) + utils.SetMapExtraFields(in, m, extraFields) + return in, nil } return nil, utils.ErrNotFound } diff --git a/engine/users_test.go b/engine/users_test.go index 12b14c21f..86699a9cb 100644 --- a/engine/users_test.go +++ b/engine/users_test.go @@ -3,6 +3,7 @@ package engine import ( "reflect" "testing" + "time" "github.com/cgrates/cgrates/utils" ) @@ -520,7 +521,7 @@ func TestUsersAddUpdateRemoveIndexes(t *testing.T) { } } -func TestUsersStoredCDRGetLoadUserProfile(t *testing.T) { +func TestUsersUsageRecordGetLoadUserProfile(t *testing.T) { userService = &UserMap{ table: map[string]map[string]string{ "test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"}, @@ -562,13 +563,13 @@ func TestUsersStoredCDRGetLoadUserProfile(t *testing.T) { AnswerTime: "t4", Usage: "13", } - *ur = out.(UsageRecord) + ur = out.(*UsageRecord) if !reflect.DeepEqual(ur, expected) { t.Errorf("Expected: %+v got: %+v", expected, ur) } } -func TestUsersStoredCDRGetLoadUserProfileExtraFields(t *testing.T) { +func TestUsersExternalCdrGetLoadUserProfileExtraFields(t *testing.T) { userService = &UserMap{ table: map[string]map[string]string{ "test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"}, @@ -612,14 +613,17 @@ func TestUsersStoredCDRGetLoadUserProfileExtraFields(t *testing.T) { SetupTime: "s4", AnswerTime: "t4", Usage: "13", + ExtraFields: map[string]string{ + "Test": "1", + }, } - *ur = out.(ExternalCdr) + ur = out.(*ExternalCdr) if !reflect.DeepEqual(ur, expected) { t.Errorf("Expected: %+v got: %+v", expected, ur) } } -func TestUsersStoredCDRGetLoadUserProfileExtraFieldsNotFound(t *testing.T) { +func TestUsersExternalCdrGetLoadUserProfileExtraFieldsNotFound(t *testing.T) { userService = &UserMap{ table: map[string]map[string]string{ "test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"}, @@ -652,3 +656,147 @@ func TestUsersStoredCDRGetLoadUserProfileExtraFieldsNotFound(t *testing.T) { t.Error("Error detecting err in loading user profile: ", err) } } + +func TestUsersExternalCdrGetLoadUserProfileExtraFieldsSet(t *testing.T) { + userService = &UserMap{ + table: map[string]map[string]string{ + "test:user": map[string]string{"TOR": "01", "ReqType": "1", "Direction": "*out", "Category": "c1", "Account": "dan", "Subject": "0723", "Destination": "+401", "SetupTime": "s1", "AnswerTime": "t1", "Usage": "10"}, + ":user": map[string]string{"TOR": "02", "ReqType": "2", "Direction": "*out", "Category": "c2", "Account": "ivo", "Subject": "0724", "Destination": "+402", "SetupTime": "s2", "AnswerTime": "t2", "Usage": "11"}, + "test:": map[string]string{"TOR": "03", "ReqType": "3", "Direction": "*out", "Category": "c3", "Account": "elloy", "Subject": "0725", "Destination": "+403", "SetupTime": "s3", "AnswerTime": "t3", "Usage": "12"}, + "test1:user1": map[string]string{"TOR": "04", "ReqType": "4", "Direction": "*out", "Category": "call", "Account": "rif", "Subject": "0726", "Destination": "+404", "SetupTime": "s4", "AnswerTime": "t4", "Usage": "13", "Test": "1", "Best": "BestValue"}, + }, + index: make(map[string]map[string]bool), + } + + ur := &ExternalCdr{ + TOR: utils.USERS, + ReqType: utils.USERS, + Direction: "*out", + Tenant: "", + Category: "call", + Account: utils.USERS, + Subject: utils.USERS, + Destination: utils.USERS, + SetupTime: utils.USERS, + AnswerTime: utils.USERS, + Usage: "13", + ExtraFields: map[string]string{ + "Test": "1", + "Best": utils.USERS, + }, + } + + out, err := LoadUserProfile(ur, "ExtraFields") + if err != nil { + t.Error("Error loading user profile: ", err) + } + expected := &ExternalCdr{ + TOR: "04", + ReqType: "4", + Direction: "*out", + Tenant: "", + Category: "call", + Account: "rif", + Subject: "0726", + Destination: "+404", + SetupTime: "s4", + AnswerTime: "t4", + Usage: "13", + ExtraFields: map[string]string{ + "Test": "1", + "Best": "BestValue", + }, + } + ur = out.(*ExternalCdr) + if !reflect.DeepEqual(ur, expected) { + t.Errorf("Expected: %+v got: %+v", expected, ur) + } +} + +func TestUsersCallDescLoadUserProfile(t *testing.T) { + userService = &UserMap{ + table: map[string]map[string]string{ + "cgrates.org:dan": map[string]string{"ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "dan", "Cli": "+4986517174963"}, + "cgrates.org:danvoice": map[string]string{"TOR": "*voice", "ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "0723"}, + "cgrates:rif": map[string]string{"ReqType": "*postpaid", "Direction": "*out", "Category": "call", "Account": "rif", "Subject": "0726"}, + }, + index: make(map[string]map[string]bool), + } + startTime := time.Now() + cd := &CallDescriptor{ + TOR: "*sms", + Tenant: utils.USERS, + Category: utils.USERS, + Subject: utils.USERS, + Account: utils.USERS, + Destination: "+4986517174963", + TimeStart: startTime, + TimeEnd: startTime.Add(time.Duration(1) * time.Minute), + ExtraFields: map[string]string{"Cli": "+4986517174963"}, + } + expected := &CallDescriptor{ + TOR: "*sms", + Tenant: "cgrates.org", + Category: "call1", + Account: "dan", + Subject: "dan", + Destination: "+4986517174963", + TimeStart: startTime, + TimeEnd: startTime.Add(time.Duration(1) * time.Minute), + ExtraFields: map[string]string{"Cli": "+4986517174963"}, + } + out, err := LoadUserProfile(cd, "ExtraFields") + if err != nil { + t.Error("Error loading user profile: ", err) + } + cdRcv := out.(*CallDescriptor) + if !reflect.DeepEqual(expected, cdRcv) { + t.Errorf("Expected: %+v got: %+v", expected, cdRcv) + } +} + +func TestUsersStoredCdrLoadUserProfile(t *testing.T) { + userService = &UserMap{ + table: map[string]map[string]string{ + "cgrates.org:dan": map[string]string{"ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "dan", "Cli": "+4986517174963"}, + "cgrates.org:danvoice": map[string]string{"TOR": "*voice", "ReqType": "*prepaid", "Category": "call1", "Account": "dan", "Subject": "0723"}, + "cgrates:rif": map[string]string{"ReqType": "*postpaid", "Direction": "*out", "Category": "call", "Account": "rif", "Subject": "0726"}, + }, + index: make(map[string]map[string]bool), + } + startTime := time.Now() + cdr := &StoredCdr{ + TOR: "*sms", + ReqType: utils.USERS, + Tenant: utils.USERS, + Category: utils.USERS, + Account: utils.USERS, + Subject: utils.USERS, + Destination: "+4986517174963", + SetupTime: startTime, + AnswerTime: startTime, + Usage: time.Duration(1) * time.Minute, + ExtraFields: map[string]string{"Cli": "+4986517174963"}, + } + expected := &StoredCdr{ + TOR: "*sms", + ReqType: "*prepaid", + Tenant: "cgrates.org", + Category: "call1", + Account: "dan", + Subject: "dan", + Destination: "+4986517174963", + SetupTime: startTime, + AnswerTime: startTime, + Usage: time.Duration(1) * time.Minute, + ExtraFields: map[string]string{"Cli": "+4986517174963"}, + } + out, err := LoadUserProfile(cdr, "ExtraFields") + if err != nil { + t.Error("Error loading user profile: ", err) + } + cdRcv := out.(*StoredCdr) + if !reflect.DeepEqual(expected, cdRcv) { + t.Errorf("Expected: %+v got: %+v", expected, cdRcv) + } +} diff --git a/utils/struct.go b/utils/struct.go index 5904341be..c59e574a6 100644 --- a/utils/struct.go +++ b/utils/struct.go @@ -78,7 +78,7 @@ func NonemptyStructFields(s interface{}) map[string]interface{} { }*/ // Converts a struct to map[string]interface{} -func ToMapMapStringInterface(in interface{}) (map[string]interface{}, error) { +func ToMapMapStringInterface(in interface{}) map[string]interface{} { out := make(map[string]interface{}) v := reflect.ValueOf(in) @@ -89,11 +89,11 @@ func ToMapMapStringInterface(in interface{}) (map[string]interface{}, error) { for i := 0; i < v.NumField(); i++ { out[typ.Field(i).Name] = v.Field(i).Interface() } - return out, nil + return out } // Converts a struct to map[string]string -func ToMapStringString(in interface{}) (map[string]string, error) { +func ToMapStringString(in interface{}) map[string]string { out := make(map[string]string) v := reflect.ValueOf(in) @@ -110,11 +110,10 @@ func ToMapStringString(in interface{}) (map[string]string, error) { out[typField.Name] = field.String() } } - return out, nil + return out } -// Converts a struct to map[string]string -func GetMapExtraFields(in interface{}, extraFields string) (map[string]string, error) { +func GetMapExtraFields(in interface{}, extraFields string) map[string]string { out := make(map[string]string) v := reflect.ValueOf(in) if v.Kind() == reflect.Ptr { @@ -128,21 +127,38 @@ func GetMapExtraFields(in interface{}, extraFields string) (map[string]string, e out[key.String()] = field.MapIndex(key).String() } } - return out, nil + return out } -func FromMapStringString(m map[string]string, in interface{}) (interface{}, error) { +func SetMapExtraFields(in interface{}, values map[string]string, extraFields string) { v := reflect.ValueOf(in) if v.Kind() == reflect.Ptr { v = v.Elem() in = v.Interface() } - st := reflect.TypeOf(in) - elem := reflect.New(st).Elem() - for fieldName, fieldValue := range m { - field := elem.FieldByName(fieldName) - if field.IsValid() { + efField := v.FieldByName(extraFields) + if efField.Kind() == reflect.Map { + keys := efField.MapKeys() + for _, key := range keys { + if efField.MapIndex(key).String() != "" { + if val, found := values[key.String()]; found { + efField.SetMapIndex(key, reflect.ValueOf(val)) + } + } + } + } + return +} +func FromMapStringString(m map[string]string, in interface{}) { + v := reflect.ValueOf(in) + if v.Kind() == reflect.Ptr { + v = v.Elem() + in = v.Interface() + } + for fieldName, fieldValue := range m { + field := v.FieldByName(fieldName) + if field.IsValid() { if field.Kind() == reflect.String { if v.FieldByName(fieldName).String() != "" { field.SetString(fieldValue) @@ -150,7 +166,7 @@ func FromMapStringString(m map[string]string, in interface{}) (interface{}, erro } } } - return elem.Interface(), nil + return } // Update struct with map fields, returns not matching map keys, s is a struct to be updated diff --git a/utils/struct_test.go b/utils/struct_test.go index 12723952f..50371ea36 100644 --- a/utils/struct_test.go +++ b/utils/struct_test.go @@ -18,16 +18,16 @@ func TestStructMapStruct(t *testing.T) { Address: "3", Other: "", } - m, err := ToMapStringString(ts) - if err != nil { - t.Error("Error converting to map: ", err) + nts := &TestStruct{ + Name: "1", + Surname: "2", + Address: "3", + Other: "", } - out, err := FromMapStringString(m, ts) - if err != nil { - t.Error("Error converting to struct: ", err) - } - nts := out.(TestStruct) - if !reflect.DeepEqual(ts, &nts) { + m := ToMapStringString(ts) + + FromMapStringString(m, ts) + if !reflect.DeepEqual(ts, nts) { t.Log(m) t.Errorf("Expected: %+v got: %+v", ts, nts) } @@ -46,17 +46,17 @@ func TestMapStructAddStructs(t *testing.T) { Address: "3", Other: "", } - m, err := ToMapStringString(ts) - if err != nil { - t.Error("Error converting to map: ", err) + nts := &TestStruct{ + Name: "1", + Surname: "2", + Address: "3", + Other: "", } + m := ToMapStringString(ts) m["Test"] = "4" - out, err := FromMapStringString(m, ts) - if err != nil { - t.Error("Error converting to struct: ", err) - } - nts := out.(TestStruct) - if !reflect.DeepEqual(ts, &nts) { + FromMapStringString(m, ts) + + if !reflect.DeepEqual(ts, nts) { t.Log(m) t.Errorf("Expected: %+v got: %+v", ts, nts) } @@ -78,10 +78,7 @@ func TestStructExtraFields(t *testing.T) { "k3": "v3", }, } - efMap, err := GetMapExtraFields(ts, "ExtraFields") - if err != nil { - t.Error("Error getting extra fields: ", err) - } + efMap := GetMapExtraFields(ts, "ExtraFields") if !reflect.DeepEqual(efMap, ts.ExtraFields) { t.Errorf("expected: %v got: %v", ts.ExtraFields, efMap)