diff --git a/agents/libdmt.go b/agents/libdmt.go index 6a3033a4e..e4b518ecc 100644 --- a/agents/libdmt.go +++ b/agents/libdmt.go @@ -417,7 +417,7 @@ func fieldOutVal(m *diam.Message, cfgFld *config.CfgCdrField, extraParam interfa case utils.MetaGrouped: // GroupedAVP outVal = composedFieldvalue(m, cfgFld.Value, passAtIndex, processorVars) } - if fmtValOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + if fmtValOut, err = utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Warning(fmt.Sprintf(" Error when processing field template with tag: %s, error: %s", cfgFld.Tag, err.Error())) return "", err } diff --git a/agents/librad.go b/agents/librad.go index 2ef0d9336..821e25796 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -126,7 +126,7 @@ func radFieldOutVal(pkt *radigo.Packet, processorVars map[string]string, default: return "", fmt.Errorf("unsupported configuration field type: <%s>", cfgFld.Type) } - if outVal, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + if outVal, err = utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { return "", err } return diff --git a/agents/librad_test.go b/agents/librad_test.go index 700b62075..6f5eb8daa 100644 --- a/agents/librad_test.go +++ b/agents/librad_test.go @@ -45,6 +45,7 @@ ATTRIBUTE Password 2 string # Alias values VALUE Framed-Protocol PPP 1 +VALUE Service-Type SIP-Caller-AVPs 30 # Proprietary, avp_radius # Vendors VENDOR Cisco 9 diff --git a/agents/radagent.go b/agents/radagent.go index dbfb42e4e..98cedb69f 100644 --- a/agents/radagent.go +++ b/agents/radagent.go @@ -73,9 +73,8 @@ type RadiusAgent struct { // handleAuth handles RADIUS Authorization request func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err error) { req.SetAVPValues() // populate string values in AVPs - utils.Logger.Debug(fmt.Sprintf("RadiusAgent handleAuth, received request: %+v", req)) procVars := map[string]string{ - MetaRadAuth: "true", + MetaRadReqType: MetaRadAuth, } rpl = req.Reply() rpl.Code = radigo.AccessAccept @@ -103,16 +102,15 @@ func (ra *RadiusAgent) handleAuth(req *radigo.Packet) (rpl *radigo.Packet, err e // supports: Acct-Status-Type = Start, Interim-Update, Stop func (ra *RadiusAgent) handleAcct(req *radigo.Packet) (rpl *radigo.Packet, err error) { req.SetAVPValues() // populate string values in AVPs - utils.Logger.Debug(fmt.Sprintf("Received request: %s", utils.ToJSON(req))) procVars := make(map[string]string) if avps := req.AttributesWithName("Acct-Status-Type", ""); len(avps) != 0 { // populate accounting type switch avps[0].GetStringValue() { // first AVP found will give out the type of accounting case "Start": - procVars[MetaRadAcctStart] = "true" + procVars[MetaRadReqType] = MetaRadAcctStart case "Interim-Update": - procVars[MetaRadAcctUpdate] = "true" + procVars[MetaRadReqType] = MetaRadAcctUpdate case "Stop": - procVars[MetaRadAcctStop] = "true" + procVars[MetaRadReqType] = MetaRadAcctStop } } rpl = req.Reply() @@ -144,6 +142,7 @@ func (ra *RadiusAgent) processRequest(reqProcessor *config.RARequestProcessor, for _, fldFilter := range reqProcessor.RequestFilter { if !radPassesFieldFilter(req, processorVars, fldFilter) { passesAllFilters = false + break } } if !passesAllFilters { // Not going with this processor further diff --git a/agents/radagent_it_test.go b/agents/radagent_it_test.go new file mode 100644 index 000000000..67225235a --- /dev/null +++ b/agents/radagent_it_test.go @@ -0,0 +1,138 @@ +// +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 +*/ +package agents + +import ( + "net/rpc" + "net/rpc/jsonrpc" + "path" + "reflect" + "testing" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/engine" + "github.com/cgrates/cgrates/utils" + "github.com/cgrates/radigo" +) + +var raCfgPath string +var raCfg *config.CGRConfig +var raSMGrpc *rpc.Client +var raAuthClnt, raAcctClnt *radigo.Client + +func TestRAitInitCfg(t *testing.T) { + raCfgPath = path.Join(*dataDir, "conf", "samples", "radagent") + // Init config first + var err error + raCfg, err = config.NewCGRConfigFromFolder(raCfgPath) + if err != nil { + t.Error(err) + } + raCfg.DataFolderPath = *dataDir // Share DataFolderPath through config towards StoreDb for Flush() + config.SetCgrConfig(raCfg) +} + +// Remove data in both rating and accounting db +func TestRAitResetDataDb(t *testing.T) { + if err := engine.InitDataDb(raCfg); err != nil { + t.Fatal(err) + } +} + +// Wipe out the cdr database +func TestRAitResetStorDb(t *testing.T) { + if err := engine.InitStorDb(raCfg); err != nil { + t.Fatal(err) + } +} + +// Start CGR Engine +func TestRAitStartEngine(t *testing.T) { + if _, err := engine.StopStartEngine(raCfgPath, *waitRater); err != nil { + t.Fatal(err) + } +} + +// Connect rpc client to rater +func TestRAitApierRpcConn(t *testing.T) { + var err error + raSMGrpc, err = jsonrpc.Dial("tcp", raCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed + if err != nil { + t.Fatal(err) + } +} + +// Load the tariff plan, creating accounts and their balances +func TestRAitTPFromFolder(t *testing.T) { + attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutorial")} + var loadInst utils.LoadInstance + if err := raSMGrpc.Call("ApierV2.LoadTariffPlanFromFolder", attrs, &loadInst); err != nil { + t.Error(err) + } + time.Sleep(time.Duration(*waitRater) * time.Millisecond) // Give time for scheduler to execute topups +} + +func TestRAitAuth(t *testing.T) { + if raAuthClnt, err = radigo.NewClient("udp", "127.0.0.1:1812", "CGRateS.org", dictRad, 1, nil); err != nil { + t.Fatal(err) + } + authReq := raAuthClnt.NewRequest(radigo.AccessRequest, 1) // emulates Kamailio packet out of radius_load_caller_avps() + if err := authReq.AddAVPWithName("User-Name", "1001", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Service-Type", "SIP-Caller-AVPs", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Called-Station-Id", "1002", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Acct-Session-Id", "2ca13afce9b2d76e15de7e1ec6568fc8@0:0:0:0:0:0:0:0", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Sip-From-Tag", "7f30055f", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("NAS-IP-Address", "127.0.0.1", ""); err != nil { + t.Error(err) + } + if err := authReq.AddAVPWithName("Event-Timestamp", "1497106115", ""); err != nil { + t.Error(err) + } + + reply, err := raAuthClnt.SendRequest(authReq) + if err != nil { + t.Error(err) + } + if reply.Code != radigo.AccessAccept { + t.Errorf("Received reply: %+v", reply) + } + if len(reply.AVPs) != 1 { // make sure max duration is received + t.Errorf("Received AVPs: %+v", reply.AVPs) + } else if !reflect.DeepEqual([]byte("session_max_time#10800"), reply.AVPs[0].RawValue) { + t.Errorf("Received: %s", string(reply.AVPs[0].RawValue)) + } +} + +func TestRAitStopCgrEngine(t *testing.T) { + if err := engine.KillEngine(100); err != nil { + t.Error(err) + } +} diff --git a/config/config_defaults.go b/config/config_defaults.go index 5a592006a..314bf62ac 100644 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -372,20 +372,7 @@ const CGRATES_CFG_JSON = ` "create_cdr": true, // create CDR out of Accounting-Stop and send it to SMG component "cdr_requires_session": false, // only create CDR if there is an active session at terminate "timezone": "", // timezone for timestamps where not specified, empty for general defaults <""|UTC|Local|$IANA_TZ_DB> - "request_processors": [ - { - "id": "*default", // formal identifier of this processor - "dry_run": false, // do not send the events to SMG, just log them - "request_filter": "", // filter requests processed by this processor - "flags": [], // flags to influence processing behavior - "continue_on_success": false, // continue to the next template if executed - "append_reply": true, // when continuing will append reply fields to the next template - "request_fields":[ // import content_fields template, tag will match internally CDR field - ], - "reply_fields":[ // fields returned in radius reply - ], - }, - ], + "request_processors": [], }, diff --git a/config/config_json_test.go b/config/config_json_test.go index 9dfdcc9e7..a5ca676ad 100644 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -583,24 +583,13 @@ func TestRadiusAgentJsonCfg(t *testing.T) { Create_cdr: utils.BoolPointer(true), Cdr_requires_session: utils.BoolPointer(false), Timezone: utils.StringPointer(""), - Request_processors: &[]*RAReqProcessorJsnCfg{ - &RAReqProcessorJsnCfg{ - Id: utils.StringPointer("*default"), - Dry_run: utils.BoolPointer(false), - Request_filter: utils.StringPointer(""), - Flags: utils.StringSlicePointer([]string{}), - Continue_on_success: utils.BoolPointer(false), - Append_reply: utils.BoolPointer(true), - Request_fields: &[]*CdrFieldJsonCfg{}, - Reply_fields: &[]*CdrFieldJsonCfg{}, - }, - }, + Request_processors: &[]*RAReqProcessorJsnCfg{}, } if cfg, err := dfCgrJsonCfg.RadiusAgentJsonCfg(); err != nil { t.Error(err) } else if !reflect.DeepEqual(eCfg, cfg) { rcv := *cfg.Request_processors - t.Errorf("Received: %+v", rcv[0].Reply_fields) + t.Errorf("Received: %+v", rcv) } } diff --git a/data/conf/samples/radagent/cgrates.json b/data/conf/samples/radagent/cgrates.json index b88f74bcb..73839a7cd 100644 --- a/data/conf/samples/radagent/cgrates.json +++ b/data/conf/samples/radagent/cgrates.json @@ -78,28 +78,48 @@ "radius_agent": { "enabled": true, + "sm_generic_conns": [ + {"address": "127.0.0.1:2012", "transport": "*json"} // connection towards SMG component for session management + ], "request_processors": [ { "id": "KamailioAuth", - "request_filter": "*radReqType(*radAuth)", + "request_filter": "*radReqType(*radAuthReq)", "request_fields":[ - {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", "value": "*prepaid", "mandatory": true}, - {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", "value": "Acct-Session-Id;^-;Sip-From-Tag", "mandatory": true}, - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "User-Name", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Called-Station-Id", "mandatory": true}, - {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, - {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", "value": "Event-Timestamp", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", + "value": "*prepaid", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "Acct-Session-Id;^-;Sip-From-Tag", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", + "value": "User-Name", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", + "value": "Called-Station-Id", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "Event-Timestamp", "mandatory": true}, ], "reply_fields":[ - {"tag": "MaxUsage", "field_id": "Acct-Session-Time", "type": "*composed", "value": "~*cgrMaxUsage:s/(\\d*)\\d{9}$/$1/", "mandatory": true}, + {"tag": "MaxUsage", "field_id": "SIP-AVP", "type": "*composed", + "value": "^session_max_time#;~*cgrMaxUsage:s/(\\d*)\\d{9}$/$1/", "mandatory": true}, ], }, { "id": "KamailioAccountingStart", "request_filter": "*radReqType(*radAcctStart)", "request_fields":[ - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "User-Name", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Called-Station-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", + "value": "*prepaid", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "Acct-Session-Id;^-;Sip-From-Tag;^-;Sip-To-Tag", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", + "value": "User-Name", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", + "value": "Called-Station-Id", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "Event-Timestamp", "mandatory": true}, ], "reply_fields":[], }, @@ -107,8 +127,22 @@ "id": "KamailioAccountingStop", "request_filter": "*radReqType(*radAcctStop)", "request_fields":[ - {"tag": "Account", "field_id": "Account", "type": "*composed", "value": "User-Name", "mandatory": true}, - {"tag": "Destination", "field_id": "Destination", "type": "*composed", "value": "Called-Station-Id", "mandatory": true}, + {"tag": "RequestType", "field_id": "RequestType", "type": "*constant", + "value": "*prepaid", "mandatory": true}, + {"tag": "OriginID", "field_id": "OriginID", "type": "*composed", + "value": "Acct-Session-Id;^-;Sip-From-Tag;^-;Sip-To-Tag", "mandatory": true}, + {"tag": "OriginHost", "field_id": "OriginHost", "type": "*composed", + "value": "NAS-IP-Address", "mandatory": true}, + {"tag": "Account", "field_id": "Account", "type": "*composed", + "value": "User-Name", "mandatory": true}, + {"tag": "Destination", "field_id": "Destination", "type": "*composed", + "value": "Called-Station-Id", "mandatory": true}, + {"tag": "SetupTime", "field_id": "SetupTime", "type": "*composed", + "value": "Event-Timestamp", "mandatory": true}, + {"tag": "AnswerTime", "field_id": "AnswerTime", "type": "*composed", + "value": "Event-Timestamp", "mandatory": true}, + {"tag": "Usage", "field_id": "AnswerTime", "type": "*handler", "handler_id": "*usage_difference", + "value": "Event-Timestamp;^|;Ascend-User-Acct-Time", "mandatory": true}, ], "reply_fields":[], }, diff --git a/engine/cdr.go b/engine/cdr.go index 9b0e6f8a6..0df10b469 100644 --- a/engine/cdr.go +++ b/engine/cdr.go @@ -788,7 +788,7 @@ func (cdr *CDR) formatField(cfgFld *config.CfgCdrField, httpSkipTlsCheck bool, g if err != nil { return "", err } - return utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory) + return utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory) } diff --git a/engine/cdre.go b/engine/cdre.go index 663c11514..30ba80219 100644 --- a/engine/cdre.go +++ b/engine/cdre.go @@ -160,7 +160,7 @@ func (cdre *CDRExporter) composeHeader() (err error) { return err } fmtOut := outVal - if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + if fmtOut, err = utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Err(fmt.Sprintf(" Cannot export CDR header, field %s, error: %s", cfgFld.Tag, err.Error())) return err } @@ -191,7 +191,7 @@ func (cdre *CDRExporter) composeTrailer() (err error) { return err } fmtOut := outVal - if fmtOut, err = utils.FmtFieldWidth(outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { + if fmtOut, err = utils.FmtFieldWidth(cfgFld.Tag, outVal, cfgFld.Width, cfgFld.Strip, cfgFld.Padding, cfgFld.Mandatory); err != nil { utils.Logger.Err(fmt.Sprintf(" Cannot export CDR trailer, field: %s, error: %s", cfgFld.Tag, err.Error())) return err } diff --git a/glide.lock b/glide.lock index 02d1042cf..ebd002ee0 100644 --- a/glide.lock +++ b/glide.lock @@ -18,7 +18,7 @@ imports: - name: github.com/cgrates/osipsdagram version: 3d6beed663452471dec3ca194137a30d379d9e8f - name: github.com/cgrates/radigo - version: 57ecc46930a17563f6ba6d3a46280b102e9366d4 + version: b3ea6cb087f3ecdc1b4e611df206521d96af1472 - name: github.com/cgrates/rpcclient version: dddae42e9344e877627cd4b7aba075d63b452c0b - name: github.com/ChrisTrenkamp/goxpath diff --git a/utils/coreutils.go b/utils/coreutils.go index e1d5ecbc8..9c52d08ae 100644 --- a/utils/coreutils.go +++ b/utils/coreutils.go @@ -513,9 +513,9 @@ type Cloner interface { // width - the field width // strip - if present it will specify the strip strategy, when missing strip will not be allowed // padding - if present it will specify the padding strategy to use, left, right, zeroleft, zeroright -func FmtFieldWidth(source string, width int, strip, padding string, mandatory bool) (string, error) { +func FmtFieldWidth(fieldID, source string, width int, strip, padding string, mandatory bool) (string, error) { if mandatory && len(source) == 0 { - return "", errors.New("Empty source value") + return "", fmt.Errorf("Empty source value for fieldID: <%s>", fieldID) } if width == 0 { // Disable width processing if not defined return source, nil @@ -525,7 +525,7 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo } if len(source) > width { //the source is bigger than allowed if len(strip) == 0 { - return "", fmt.Errorf("Source %s is bigger than the width %d, no strip defied", source, width) + return "", fmt.Errorf("Source %s is bigger than the width %d, no strip defied, fieldID: <%s>", source, width, fieldID) } if strip == "right" { return source[:width], nil @@ -540,7 +540,7 @@ func FmtFieldWidth(source string, width int, strip, padding string, mandatory bo } } else { //the source is smaller as the maximum allowed if len(padding) == 0 { - return "", fmt.Errorf("Source %s is smaller than the width %d, no padding defined", source, width) + return "", fmt.Errorf("Source %s is smaller than the width %d, no padding defined, fieldID: <%s>", source, width, fieldID) } var paddingFmt string switch padding { diff --git a/utils/coreutils_test.go b/utils/coreutils_test.go index fb3d89729..a9ea152d2 100644 --- a/utils/coreutils_test.go +++ b/utils/coreutils_test.go @@ -429,14 +429,14 @@ func TestConvertIfaceToString(t *testing.T) { } func TestMandatory(t *testing.T) { - _, err := FmtFieldWidth("", 0, "", "", true) + _, err := FmtFieldWidth("", "", 0, "", "", true) if err == nil { t.Errorf("Failed to detect mandatory value") } } func TestMaxLen(t *testing.T) { - result, err := FmtFieldWidth("test", 4, "", "", false) + result, err := FmtFieldWidth("", "test", 4, "", "", false) expected := "test" if err != nil || result != expected { t.Errorf("Expected \"test\" was \"%s\"", result) @@ -444,7 +444,7 @@ func TestMaxLen(t *testing.T) { } func TestRPadding(t *testing.T) { - result, err := FmtFieldWidth("test", 8, "", "right", false) + result, err := FmtFieldWidth("", "test", 8, "", "right", false) expected := "test " if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -452,7 +452,7 @@ func TestRPadding(t *testing.T) { } func TestPaddingFiller(t *testing.T) { - result, err := FmtFieldWidth("", 8, "", "right", false) + result, err := FmtFieldWidth("", "", 8, "", "right", false) expected := " " if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -460,7 +460,7 @@ func TestPaddingFiller(t *testing.T) { } func TestLPadding(t *testing.T) { - result, err := FmtFieldWidth("test", 8, "", "left", false) + result, err := FmtFieldWidth("", "test", 8, "", "left", false) expected := " test" if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -468,7 +468,7 @@ func TestLPadding(t *testing.T) { } func TestZeroLPadding(t *testing.T) { - result, err := FmtFieldWidth("test", 8, "", "zeroleft", false) + result, err := FmtFieldWidth("", "test", 8, "", "zeroleft", false) expected := "0000test" if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -476,7 +476,7 @@ func TestZeroLPadding(t *testing.T) { } func TestRStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 2, "right", "", false) + result, err := FmtFieldWidth("", "test", 2, "right", "", false) expected := "te" if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -484,7 +484,7 @@ func TestRStrip(t *testing.T) { } func TestXRStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 3, "xright", "", false) + result, err := FmtFieldWidth("", "test", 3, "xright", "", false) expected := "tex" if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -492,7 +492,7 @@ func TestXRStrip(t *testing.T) { } func TestLStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 2, "left", "", false) + result, err := FmtFieldWidth("", "test", 2, "left", "", false) expected := "st" if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -500,7 +500,7 @@ func TestLStrip(t *testing.T) { } func TestXLStrip(t *testing.T) { - result, err := FmtFieldWidth("test", 3, "xleft", "", false) + result, err := FmtFieldWidth("", "test", 3, "xleft", "", false) expected := "xst" if err != nil || result != expected { t.Errorf("Expected \"%s \" was \"%s\"", expected, result) @@ -508,14 +508,14 @@ func TestXLStrip(t *testing.T) { } func TestStripNotAllowed(t *testing.T) { - _, err := FmtFieldWidth("test", 3, "", "", false) + _, err := FmtFieldWidth("", "test", 3, "", "", false) if err == nil { t.Error("Expected error") } } func TestPaddingNotAllowed(t *testing.T) { - _, err := FmtFieldWidth("test", 5, "", "", false) + _, err := FmtFieldWidth("", "test", 5, "", "", false) if err == nil { t.Error("Expected error") }