Add test for both HTTPPost and HTTPJSONMap Exporter

This commit is contained in:
TeoV
2020-06-05 15:40:14 +03:00
committed by Dan Christian Bogos
parent 3fc4ca724c
commit 66bbc377fe
6 changed files with 423 additions and 10 deletions

View File

@@ -319,7 +319,7 @@ var possibleReaderTypes = utils.NewStringSet([]string{utils.MetaFileCSV,
utils.MetaPartialCSV, utils.MetaFlatstore, utils.MetaJSON, utils.META_NONE})
var possibleExporterTypes = utils.NewStringSet([]string{utils.MetaFileCSV, utils.META_NONE, utils.MetaFileFWV,
utils.MetaHTTPPost, utils.MetaHTTPjson, utils.MetaAMQPjsonMap, utils.MetaAMQPV1jsonMap, utils.MetaSQSjsonMap,
utils.MetaHTTPPost, utils.MetaHTTPjsonMap, utils.MetaAMQPjsonMap, utils.MetaAMQPV1jsonMap, utils.MetaSQSjsonMap,
utils.MetaKafkajsonMap, utils.MetaS3jsonMap})
func (cfg *CGRConfig) LazySanityCheck() {

View File

@@ -147,7 +147,6 @@
"flags": ["*attributes"],
"attribute_context": "customContext",
"attempts": 1,
"field_separator": ",",
"filters": ["*string:~*req.ExporterUsed:FWVExporter"],
"fields":[
{"tag": "TypeOfRecord", "path": "*hdr.TypeOfRecord", "type": "*constant",
@@ -218,6 +217,58 @@
{"tag": "Filler2", "path": "*trl.Filler2", "type": "*filler",
"width": 93}
],
},
{
"id": "HTTPPostExporter",
"type": "*http_post",
"export_path": "http://127.0.0.1:12080/event_http",
"tenant": "cgrates.org",
"flags": ["*attributes"],
"attribute_context": "customContext",
"attempts": 1,
"filters": ["*string:~*req.ExporterUsed:HTTPPostExporter"],
"fields":[
{"tag": "CGRID", "path": "*exp.CGRID", "type": "*variable", "value": "~*req.CGRID"},
{"tag": "RunID", "path": "*exp.RunID", "type": "*variable", "value": "~*req.RunID"},
{"tag": "ToR", "path": "*exp.ToR", "type": "*variable", "value": "~*req.ToR"},
{"tag": "OriginID", "path": "*exp.OriginID", "type": "*variable", "value": "~*req.OriginID"},
{"tag": "RequestType", "path": "*exp.RequestType", "type": "*variable", "value": "~*req.RequestType"},
{"tag": "Tenant", "path": "*exp.Tenant", "type": "*variable", "value": "~*req.Tenant"},
{"tag": "Category", "path": "*exp.Category", "type": "*variable", "value": "~*req.Category"},
{"tag": "Account", "path": "*exp.Account", "type": "*variable", "value": "~*req.Account"},
{"tag": "Subject", "path": "*exp.Subject", "type": "*variable", "value": "~*req.Subject"},
{"tag": "Destination", "path": "*exp.Destination", "type": "*variable", "value": "~*req.Destination"},
{"tag": "SetupTime", "path": "*exp.SetupTime", "type": "*variable", "value": "~*req.SetupTime"},
{"tag": "AnswerTime", "path": "*exp.AnswerTime", "type": "*variable", "value": "~*req.AnswerTime"},
{"tag": "Usage", "path": "*exp.Usage", "type": "*variable", "value": "~*req.Usage"},
{"tag": "Cost", "path": "*exp.Cost", "type": "*variable", "value": "~*req.Cost{*round:4}"},
],
},
{
"id": "HTTPJsonMapExporter",
"type": "*http_json_map",
"export_path": "http://127.0.0.1:12081/event_json_map_http",
"tenant": "cgrates.org",
"flags": ["*attributes"],
"attribute_context": "customContext",
"attempts": 1,
"filters": ["*string:~*req.ExporterUsed:HTTPJsonMapExporter"],
"fields":[
{"tag": "CGRID", "path": "*exp.CGRID", "type": "*variable", "value": "~*req.CGRID"},
{"tag": "RunID", "path": "*exp.RunID", "type": "*variable", "value": "~*req.RunID"},
{"tag": "ToR", "path": "*exp.ToR", "type": "*variable", "value": "~*req.ToR"},
{"tag": "OriginID", "path": "*exp.OriginID", "type": "*variable", "value": "~*req.OriginID"},
{"tag": "RequestType", "path": "*exp.RequestType", "type": "*variable", "value": "~*req.RequestType"},
{"tag": "Tenant", "path": "*exp.Tenant", "type": "*variable", "value": "~*req.Tenant"},
{"tag": "Category", "path": "*exp.Category", "type": "*variable", "value": "~*req.Category"},
{"tag": "Account", "path": "*exp.Account", "type": "*variable", "value": "~*req.Account"},
{"tag": "Subject", "path": "*exp.Subject", "type": "*variable", "value": "~*req.Subject"},
{"tag": "Destination", "path": "*exp.Destination", "type": "*variable", "value": "~*req.Destination"},
{"tag": "SetupTime", "path": "*exp.SetupTime", "type": "*variable", "value": "~*req.SetupTime"},
{"tag": "AnswerTime", "path": "*exp.AnswerTime", "type": "*variable", "value": "~*req.AnswerTime"},
{"tag": "Usage", "path": "*exp.Usage", "type": "*variable", "value": "~*req.Usage"},
{"tag": "Cost", "path": "*exp.Cost", "type": "*variable", "value": "~*req.Cost{*round:4}"},
],
}
]
},

View File

@@ -20,6 +20,8 @@ package ees
import (
"encoding/json"
"fmt"
"strings"
"sync"
"time"
@@ -84,11 +86,18 @@ func (httpJson *HTTPJsonMapEe) ExportEvent(cgrEv *utils.CGREvent) (err error) {
return
}
for el := eeReq.cnt.GetFirstElement(); el != nil; el = el.Next() {
var strVal string
if strVal, err = eeReq.cnt.FieldAsString(el.Value.Slice()); err != nil {
var nmIt utils.NMInterface
if nmIt, err = eeReq.cnt.Field(el.Value); err != nil {
return
}
valMp[el.Value.Slice()[1]] = strVal
itm, isNMItem := nmIt.(*config.NMItem)
if !isNMItem {
return fmt.Errorf("cannot encode reply value: %s, err: not NMItems", utils.ToJSON(el.Value))
}
if itm == nil {
continue // all attributes, not writable to diameter packet
}
valMp[strings.Join(itm.Path, utils.NestingSep)] = utils.IfaceAsString(itm.Data)
}
if aTime, err := cgrEv.FieldAsTime(utils.AnswerTime, httpJson.cgrCfg.GeneralCfg().DefaultTimezone); err == nil {
if httpJson.dc[utils.FirstEventATime].(time.Time).IsZero() || httpJson.dc[utils.FirstEventATime].(time.Time).Before(aTime) {

View File

@@ -21,9 +21,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package ees
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/rpc"
"path"
"testing"
"time"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
@@ -34,15 +40,19 @@ var (
httpJSONMapCfgPath string
httpJSONMapCfg *config.CGRConfig
httpJSONMapRpc *rpc.Client
httpJsonMap map[string]string
sTestsHTTPJsonMap = []func(t *testing.T){
testCreateDirectory,
testHTTPJsonMapLoadConfig,
testHTTPJsonMapResetDataDB,
testHTTPJsonMapResetStorDb,
testHTTPJsonMapStartEngine,
testHTTPJsonMapRPCConn,
testHTTPJsonMapStartHTTPServer,
testHTTPJsonMapExportEvent,
testStopCgrEngine,
testCleanDirectory,
}
)
@@ -86,3 +96,165 @@ func testHTTPJsonMapRPCConn(t *testing.T) {
t.Fatal(err)
}
}
func testHTTPJsonMapStartHTTPServer(t *testing.T) {
http.HandleFunc("/event_json_map_http", func(writer http.ResponseWriter, request *http.Request) {
b, err := ioutil.ReadAll(request.Body)
request.Body.Close()
if err != nil {
t.Error(err)
}
if err = json.Unmarshal(b, &httpJsonMap); err != nil {
t.Error(err)
}
})
go http.ListenAndServe(":12081", nil)
}
func testHTTPJsonMapExportEvent(t *testing.T) {
eventVoice := &utils.CGREventWithOpts{
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "voiceEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()),
utils.ToR: utils.VOICE,
utils.OriginID: "dsafdsaf",
utils.OriginHost: "192.168.1.1",
utils.RequestType: utils.META_RATED,
utils.Tenant: "cgrates.org",
utils.Category: "call",
utils.Account: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
utils.Usage: time.Duration(10) * time.Second,
utils.RunID: utils.MetaDefault,
utils.Cost: 1.01,
"ExporterUsed": "HTTPJsonMapExporter",
"ExtraFields": map[string]string{"extra1": "val_extra1",
"extra2": "val_extra2", "extra3": "val_extra3"},
},
},
}
eventData := &utils.CGREventWithOpts{
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "dataEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.CGRID: utils.Sha1("abcdef", time.Unix(1383813745, 0).UTC().String()),
utils.ToR: utils.DATA,
utils.OriginID: "abcdef",
utils.OriginHost: "192.168.1.1",
utils.RequestType: utils.META_RATED,
utils.Tenant: "AnotherTenant",
utils.Category: "call", //for data CDR use different Tenant
utils.Account: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
utils.Usage: time.Duration(10) * time.Nanosecond,
utils.RunID: utils.MetaDefault,
utils.Cost: 0.012,
"ExporterUsed": "HTTPJsonMapExporter",
"ExtraFields": map[string]string{"extra1": "val_extra1",
"extra2": "val_extra2", "extra3": "val_extra3"},
},
},
}
eventSMS := &utils.CGREventWithOpts{
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "SMSEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.CGRID: utils.Sha1("sdfwer", time.Unix(1383813745, 0).UTC().String()),
utils.ToR: utils.SMS,
utils.OriginID: "sdfwer",
utils.OriginHost: "192.168.1.1",
utils.RequestType: utils.META_RATED,
utils.Tenant: "cgrates.org",
utils.Category: "call",
utils.Account: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
utils.Usage: time.Duration(1),
utils.RunID: utils.MetaDefault,
utils.Cost: 0.15,
"ExporterUsed": "HTTPJsonMapExporter",
"ExtraFields": map[string]string{"extra1": "val_extra1",
"extra2": "val_extra2", "extra3": "val_extra3"},
},
},
}
var reply string
if err := httpJSONMapRpc.Call(utils.EventExporterSv1ProcessEvent, eventVoice, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
}
time.Sleep(10 * time.Millisecond)
// verify HTTPJsonMap for eventVoice
for key, strVal := range map[string]string{
utils.CGRID: utils.IfaceAsString(eventVoice.Event[utils.CGRID]),
utils.ToR: utils.IfaceAsString(eventVoice.Event[utils.ToR]),
utils.Category: utils.IfaceAsString(eventVoice.Event[utils.Category]),
utils.Account: utils.IfaceAsString(eventVoice.Event[utils.Account]),
utils.Subject: utils.IfaceAsString(eventVoice.Event[utils.Subject]),
utils.Destination: utils.IfaceAsString(eventVoice.Event[utils.Destination]),
utils.Cost: utils.IfaceAsString(eventVoice.Event[utils.Cost]),
} {
if rcv := httpJsonMap[key]; rcv != strVal {
t.Errorf("Expected %+v, received: %+v", strVal, rcv)
}
}
if err := httpJSONMapRpc.Call(utils.EventExporterSv1ProcessEvent, eventData, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
}
time.Sleep(10 * time.Millisecond)
// verify HTTPJsonMap for eventData
for key, strVal := range map[string]string{
utils.CGRID: utils.IfaceAsString(eventData.Event[utils.CGRID]),
utils.ToR: utils.IfaceAsString(eventData.Event[utils.ToR]),
utils.Category: utils.IfaceAsString(eventData.Event[utils.Category]),
utils.Account: utils.IfaceAsString(eventData.Event[utils.Account]),
utils.Subject: utils.IfaceAsString(eventData.Event[utils.Subject]),
utils.Destination: utils.IfaceAsString(eventData.Event[utils.Destination]),
utils.Cost: utils.IfaceAsString(eventData.Event[utils.Cost]),
} {
if rcv := httpJsonMap[key]; rcv != strVal {
t.Errorf("Expected %+v, received: %+v", strVal, rcv)
}
}
if err := httpJSONMapRpc.Call(utils.EventExporterSv1ProcessEvent, eventSMS, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
}
time.Sleep(10 * time.Millisecond)
// verify HTTPJsonMap for eventSMS
for key, strVal := range map[string]string{
utils.CGRID: utils.IfaceAsString(eventSMS.Event[utils.CGRID]),
utils.ToR: utils.IfaceAsString(eventSMS.Event[utils.ToR]),
utils.Category: utils.IfaceAsString(eventSMS.Event[utils.Category]),
utils.Account: utils.IfaceAsString(eventSMS.Event[utils.Account]),
utils.Subject: utils.IfaceAsString(eventSMS.Event[utils.Subject]),
utils.Destination: utils.IfaceAsString(eventSMS.Event[utils.Destination]),
utils.Cost: utils.IfaceAsString(eventSMS.Event[utils.Cost]),
} {
if rcv := httpJsonMap[key]; rcv != strVal {
t.Errorf("Expected %+v, received: %+v", strVal, rcv)
}
}
}

View File

@@ -19,7 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package ees
import (
"fmt"
"net/url"
"strings"
"sync"
"time"
@@ -81,11 +83,18 @@ func (httpPost *HTTPPost) ExportEvent(cgrEv *utils.CGREvent) (err error) {
return
}
for el := eeReq.cnt.GetFirstElement(); el != nil; el = el.Next() {
var strVal string
if strVal, err = eeReq.cnt.FieldAsString(el.Value.Slice()); err != nil {
var nmIt utils.NMInterface
if nmIt, err = eeReq.cnt.Field(el.Value); err != nil {
return
}
urlVals.Set(el.Value.Slice()[1], strVal)
itm, isNMItem := nmIt.(*config.NMItem)
if !isNMItem {
return fmt.Errorf("cannot encode reply value: %s, err: not NMItems", utils.ToJSON(el.Value))
}
if itm == nil {
continue // all attributes, not writable to diameter packet
}
urlVals.Set(strings.Join(itm.Path, utils.NestingSep), utils.IfaceAsString(itm.Data))
}
if aTime, err := cgrEv.FieldAsTime(utils.AnswerTime, httpPost.cgrCfg.GeneralCfg().DefaultTimezone); err == nil {
if httpPost.dc[utils.FirstEventATime].(time.Time).IsZero() || httpPost.dc[utils.FirstEventATime].(time.Time).Before(aTime) {

View File

@@ -21,9 +21,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
package ees
import (
"io/ioutil"
"net/http"
"net/rpc"
"net/url"
"path"
"testing"
"time"
"github.com/cgrates/cgrates/utils"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/engine"
@@ -34,15 +40,19 @@ var (
httpPostCfgPath string
httpPostCfg *config.CGRConfig
httpPostRpc *rpc.Client
httpValues url.Values
sTestsHTTPPost = []func(t *testing.T){
testCreateDirectory,
testHTTPPostLoadConfig,
testHTTPPostResetDataDB,
testHTTPPostResetStorDb,
testHTTPPostStartEngine,
testHTTPPostRPCConn,
testHTTPStartHTTPServer,
testHTTPExportEvent,
testStopCgrEngine,
testCleanDirectory,
}
)
@@ -86,3 +96,165 @@ func testHTTPPostRPCConn(t *testing.T) {
t.Fatal(err)
}
}
func testHTTPStartHTTPServer(t *testing.T) {
http.HandleFunc("/event_http", func(writer http.ResponseWriter, request *http.Request) {
b, err := ioutil.ReadAll(request.Body)
request.Body.Close()
if err != nil {
t.Error(err)
}
httpValues, err = url.ParseQuery(string(b))
if err != nil {
t.Errorf("Cannot parse body: %s", string(b))
}
})
go http.ListenAndServe(":12080", nil)
}
func testHTTPExportEvent(t *testing.T) {
eventVoice := &utils.CGREventWithOpts{
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "voiceEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.CGRID: utils.Sha1("dsafdsaf", time.Unix(1383813745, 0).UTC().String()),
utils.ToR: utils.VOICE,
utils.OriginID: "dsafdsaf",
utils.OriginHost: "192.168.1.1",
utils.RequestType: utils.META_RATED,
utils.Tenant: "cgrates.org",
utils.Category: "call",
utils.Account: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
utils.Usage: time.Duration(10) * time.Second,
utils.RunID: utils.MetaDefault,
utils.Cost: 1.01,
"ExporterUsed": "HTTPPostExporter",
"ExtraFields": map[string]string{"extra1": "val_extra1",
"extra2": "val_extra2", "extra3": "val_extra3"},
},
},
}
eventData := &utils.CGREventWithOpts{
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "dataEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.CGRID: utils.Sha1("abcdef", time.Unix(1383813745, 0).UTC().String()),
utils.ToR: utils.DATA,
utils.OriginID: "abcdef",
utils.OriginHost: "192.168.1.1",
utils.RequestType: utils.META_RATED,
utils.Tenant: "AnotherTenant",
utils.Category: "call", //for data CDR use different Tenant
utils.Account: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
utils.Usage: time.Duration(10) * time.Nanosecond,
utils.RunID: utils.MetaDefault,
utils.Cost: 0.012,
"ExporterUsed": "HTTPPostExporter",
"ExtraFields": map[string]string{"extra1": "val_extra1",
"extra2": "val_extra2", "extra3": "val_extra3"},
},
},
}
eventSMS := &utils.CGREventWithOpts{
CGREvent: &utils.CGREvent{
Tenant: "cgrates.org",
ID: "SMSEvent",
Time: utils.TimePointer(time.Now()),
Event: map[string]interface{}{
utils.CGRID: utils.Sha1("sdfwer", time.Unix(1383813745, 0).UTC().String()),
utils.ToR: utils.SMS,
utils.OriginID: "sdfwer",
utils.OriginHost: "192.168.1.1",
utils.RequestType: utils.META_RATED,
utils.Tenant: "cgrates.org",
utils.Category: "call",
utils.Account: "1001",
utils.Subject: "1001",
utils.Destination: "1002",
utils.SetupTime: time.Unix(1383813745, 0).UTC(),
utils.AnswerTime: time.Unix(1383813746, 0).UTC(),
utils.Usage: time.Duration(1),
utils.RunID: utils.MetaDefault,
utils.Cost: 0.15,
"ExporterUsed": "HTTPPostExporter",
"ExtraFields": map[string]string{"extra1": "val_extra1",
"extra2": "val_extra2", "extra3": "val_extra3"},
},
},
}
var reply string
if err := httpPostRpc.Call(utils.EventExporterSv1ProcessEvent, eventVoice, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
}
time.Sleep(10 * time.Millisecond)
// verify HTTPValues for eventVoice
for key, strVal := range map[string]string{
utils.CGRID: utils.IfaceAsString(eventVoice.Event[utils.CGRID]),
utils.ToR: utils.IfaceAsString(eventVoice.Event[utils.ToR]),
utils.Category: utils.IfaceAsString(eventVoice.Event[utils.Category]),
utils.Account: utils.IfaceAsString(eventVoice.Event[utils.Account]),
utils.Subject: utils.IfaceAsString(eventVoice.Event[utils.Subject]),
utils.Destination: utils.IfaceAsString(eventVoice.Event[utils.Destination]),
utils.Cost: utils.IfaceAsString(eventVoice.Event[utils.Cost]),
} {
if rcv := httpValues.Get(key); rcv != strVal {
t.Errorf("Expected %+v, received: %+v", strVal, rcv)
}
}
if err := httpPostRpc.Call(utils.EventExporterSv1ProcessEvent, eventData, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
}
time.Sleep(10 * time.Millisecond)
// verify HTTPValues for eventData
for key, strVal := range map[string]string{
utils.CGRID: utils.IfaceAsString(eventData.Event[utils.CGRID]),
utils.ToR: utils.IfaceAsString(eventData.Event[utils.ToR]),
utils.Category: utils.IfaceAsString(eventData.Event[utils.Category]),
utils.Account: utils.IfaceAsString(eventData.Event[utils.Account]),
utils.Subject: utils.IfaceAsString(eventData.Event[utils.Subject]),
utils.Destination: utils.IfaceAsString(eventData.Event[utils.Destination]),
utils.Cost: utils.IfaceAsString(eventData.Event[utils.Cost]),
} {
if rcv := httpValues.Get(key); rcv != strVal {
t.Errorf("Expected %+v, received: %+v", strVal, rcv)
}
}
if err := httpPostRpc.Call(utils.EventExporterSv1ProcessEvent, eventSMS, &reply); err != nil {
t.Error(err)
} else if reply != utils.OK {
t.Errorf("Expected %+v, received: %+v", utils.OK, reply)
}
time.Sleep(10 * time.Millisecond)
// verify HTTPValues for eventSMS
for key, strVal := range map[string]string{
utils.CGRID: utils.IfaceAsString(eventSMS.Event[utils.CGRID]),
utils.ToR: utils.IfaceAsString(eventSMS.Event[utils.ToR]),
utils.Category: utils.IfaceAsString(eventSMS.Event[utils.Category]),
utils.Account: utils.IfaceAsString(eventSMS.Event[utils.Account]),
utils.Subject: utils.IfaceAsString(eventSMS.Event[utils.Subject]),
utils.Destination: utils.IfaceAsString(eventSMS.Event[utils.Destination]),
utils.Cost: utils.IfaceAsString(eventSMS.Event[utils.Cost]),
} {
if rcv := httpValues.Get(key); rcv != strVal {
t.Errorf("Expected %+v, received: %+v", strVal, rcv)
}
}
}