From a82cff88b9adcde4a03a9a903d1c7cbe0bd62e56 Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Fri, 15 Sep 2017 18:48:45 -0700 Subject: [PATCH] Unit test for conversion utilities + Using new validation library + Ran go-fmt on files --- src/glide.lock | 6 +- src/glide.yaml | 2 + src/tr1d1um/WDMP_Type.go | 39 ++++- src/tr1d1um/WDMP_test.go | 64 +++++++ src/tr1d1um/conversion_utils.go | 150 ++++++---------- src/tr1d1um/conversion_utils_test.go | 252 +++++++++++++++++++++++---- src/tr1d1um/http.go | 57 +++--- src/tr1d1um/http_test.go | 6 +- src/tr1d1um/tr1d1um.go | 55 +++--- src/tr1d1um/tr1d1um_type.go | 9 +- 10 files changed, 435 insertions(+), 205 deletions(-) create mode 100644 src/tr1d1um/WDMP_test.go diff --git a/src/glide.lock b/src/glide.lock index d978dfc7..20638c9d 100644 --- a/src/glide.lock +++ b/src/glide.lock @@ -1,5 +1,5 @@ -hash: 07931599e3a6477a3bc52ecf5deedf3c17d71f6665f332d607f4a365a1386fb0 -updated: 2017-09-12T14:41:24.503934812-07:00 +hash: 3eec5107271bc3ff2719d45f3601279145b952cfb6c924b83da4e432d2faf8c6 +updated: 2017-09-15T11:46:13.542649385-07:00 imports: - name: github.com/c9s/goprocinfo version: 19cb9f127a9c8d2034cf59ccb683cdb94b9deb6c @@ -31,6 +31,8 @@ imports: - log/level - name: github.com/go-logfmt/logfmt version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 +- name: github.com/go-ozzo/ozzo-validation + version: 85dcd8368eba387e65a03488b003e233994e87e9 - name: github.com/go-stack/stack version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf - name: github.com/gorilla/context diff --git a/src/glide.yaml b/src/glide.yaml index ec86231b..4d7e41cc 100644 --- a/src/glide.yaml +++ b/src/glide.yaml @@ -3,3 +3,5 @@ homepage: https://github.com/Comcast/tr1d1um import: - package: github.com/Comcast/webpa-common version: cb246362b61aaf042a936fa615a48554d111f9b4 +- package: github.com/go-ozzo/ozzo-validation + version: v3.3 diff --git a/src/tr1d1um/WDMP_Type.go b/src/tr1d1um/WDMP_Type.go index a77f9b4a..4288e2ef 100644 --- a/src/tr1d1um/WDMP_Type.go +++ b/src/tr1d1um/WDMP_Type.go @@ -1,6 +1,11 @@ package main -const( +import ( + "errors" + "github.com/go-ozzo/ozzo-validation" +) + +const ( COMMAND_GET = "GET" COMMAND_GET_ATTRS = "GET_ATTRIBUTES" COMMAND_SET = "SET" @@ -16,14 +21,15 @@ const( ERR_UNSUCCESSFUL_DATA_PARSE = "Unsuccessful Data Parse" ) + /* GET-Flavored structs */ type GetWDMP struct { - Command string `json:"command"` - Names []string `json:"names,omitempty"` - Attribute string `json:"attributes,omitempty"` + Command string `json:"command"` + Names []string `json:"names,omitempty"` + Attribute string `json:"attributes,omitempty"` } /* @@ -33,8 +39,8 @@ type GetWDMP struct { type Attr map[string]interface{} type SetParam struct { - Name* string `json:"name"` - DataType* int32 `json:"dataType,omitempty"` + Name *string `json:"name"` + DataType *int8 `json:"dataType,omitempty"` Value interface{} `json:"value,omitempty"` Attributes Attr `json:"attributes,omitempty"` } @@ -68,3 +74,24 @@ type DeleteRowWDMP struct { Row string `json:"row"` } +/* Validation functions */ + +//Applicable for the SET and TEST_SET +func (sp SetParam) Validate() error { + return validation.ValidateStruct(&sp, + validation.Field(&sp.Name, validation.NotNil), + validation.Field(&sp.DataType, validation.NotNil), + validation.Field(&sp.Value, validation.Required)) +} + +func ValidateSETAttrParams(params []SetParam) (err error) { + if params == nil || len(params) == 0 { + return errors.New("invalid list of params") + } + for _, param := range params { + if err = validation.Validate(param.Attributes, validation.Required.Error("invalid attr")); err != nil { + return + } + } + return +} diff --git a/src/tr1d1um/WDMP_test.go b/src/tr1d1um/WDMP_test.go new file mode 100644 index 00000000..f01dfef4 --- /dev/null +++ b/src/tr1d1um/WDMP_test.go @@ -0,0 +1,64 @@ +package main + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +/* tests correct usage of library */ + +func TestValidateSET(t *testing.T) { + assert := assert.New(t) + sp := SetParam{Attributes: Attr{"0": 0}, Name: &name, Value: value, DataType: &dataType} + + t.Run("Perfect", func(t *testing.T) { + assert.Nil(sp.Validate()) + }) + + t.Run("DataTypeIssue", func(t *testing.T) { + sp.DataType = nil + dataTypeErr := sp.Validate() + assert.Contains(dataTypeErr, "dataType") + sp.DataType = &dataType // put back value + }) + + t.Run("ValueIssue", func(t *testing.T) { + sp.Value = nil + valueErr := sp.Validate() + assert.Contains(valueErr, "value") + sp.Value = value + }) + + t.Run("Name", func(t *testing.T) { + sp.Name = nil + nameErr := sp.Validate() + assert.Contains(nameErr, "name") + }) +} + +func TestValidateSETAttrParams(t *testing.T) { + assert := assert.New(t) + + t.Run("ZeroCases", func(t *testing.T) { + errNil := ValidateSETAttrParams(nil) + errEmpty := ValidateSETAttrParams([]SetParam{}) + + assert.NotNil(errNil) + assert.NotNil(errEmpty) + assert.Contains(errNil.Error(), "invalid list") + assert.Contains(errEmpty.Error(), "invalid list") + }) + + t.Run("InvalidAttr", func(t *testing.T) { + param := SetParam{Attributes: Attr{}} + err := ValidateSETAttrParams([]SetParam{param}) + assert.NotNil(err) + assert.Contains(err.Error(), "invalid attr") + }) + + t.Run("Ideal", func(t *testing.T) { + param := SetParam{Attributes: Attr{"notify": 0}} + err := ValidateSETAttrParams([]SetParam{param}) + assert.Nil(err) + }) +} diff --git a/src/tr1d1um/conversion_utils.go b/src/tr1d1um/conversion_utils.go index 4e40801d..da952f4c 100644 --- a/src/tr1d1um/conversion_utils.go +++ b/src/tr1d1um/conversion_utils.go @@ -2,32 +2,33 @@ package main import ( "encoding/json" - "strings" - "net/http" - "io" "errors" + "github.com/go-ozzo/ozzo-validation" + "io" + "net/http" + "strings" ) var ( - ErrJsonEmpty = errors.New("JSON payload is empty") + ErrJsonEmpty = errors.New("JSON payload is empty") ) -type BodyReader func(io.Reader)([]byte,error) +type BodyReader func(io.Reader) ([]byte, error) type Vars map[string]string /* The following functions break down the different cases for requests (https://swagger.webpa.comcast.net/) - containing WDMP content. Their main functionality is to attempt at reading such content, validating it - and subsequently returning a json type encoding of it. Most often this resulting []byte is used as payload for - wrp messages +containing WDMP content. Their main functionality is to attempt at reading such content, validating it +and subsequently returning a json type encoding of it. Most often this resulting []byte is used as payload for +wrp messages */ -func GetFlavorFormat(req *http.Request, attr, namesKey, sep string) (payload[]byte, err error){ +func GetFlavorFormat(req *http.Request, attr, namesKey, sep string) (payload []byte, err error) { wdmp := new(GetWDMP) if nameGroup := req.FormValue(namesKey); nameGroup != "" { wdmp.Command = COMMAND_GET wdmp.Names = strings.Split(nameGroup, sep) - } else{ + } else { err = errors.New("names is a required property for GET") return } @@ -41,13 +42,14 @@ func GetFlavorFormat(req *http.Request, attr, namesKey, sep string) (payload[]by return } -func SetFlavorFormat(req *http.Request, ReadEntireBody BodyReader) (payload[]byte, err error){ +func SetFlavorFormat(req *http.Request, ReadEntireBody BodyReader) (payload []byte, err error) { wdmp := new(SetWDMP) - DecodeJsonPayload(req.Body, wdmp, ReadEntireBody) - wdmp.Command, err = ValidateAndRetrieveCommand(req.Header, wdmp) + if err = DecodeJsonPayload(req.Body, wdmp, ReadEntireBody); err != nil { + return + } - if err != nil { + if err = ValidateAndDeduceSET(req.Header, wdmp); err != nil { return } @@ -55,13 +57,13 @@ func SetFlavorFormat(req *http.Request, ReadEntireBody BodyReader) (payload[]byt return } -func DeleteFlavorFormat(urlVars Vars, rowKey string) (payload[]byte, err error){ - wdmp := &DeleteRowWDMP{Command:COMMAND_DELETE_ROW} +func DeleteFlavorFormat(urlVars Vars, rowKey string) (payload []byte, err error) { + wdmp := &DeleteRowWDMP{Command: COMMAND_DELETE_ROW} - if row, exists := GetFromUrlPath(rowKey, urlVars); exists { + if row, exists := GetFromUrlPath(rowKey, urlVars); exists && row != "" { wdmp.Row = row } else { - err = errors.New("row name is required") + err = errors.New("non-empty row name is required") return } @@ -69,8 +71,8 @@ func DeleteFlavorFormat(urlVars Vars, rowKey string) (payload[]byte, err error){ return } -func AddFlavorFormat(body io.Reader, urlVars Vars, tableName string, ReadEntireBody BodyReader) (payload[]byte, err error){ - wdmp := &AddRowWDMP{Command:COMMAND_ADD_ROW} +func AddFlavorFormat(body io.Reader, urlVars Vars, tableName string, ReadEntireBody BodyReader) (payload []byte, err error) { + wdmp := &AddRowWDMP{Command: COMMAND_ADD_ROW} if table, exists := GetFromUrlPath(tableName, urlVars); exists { wdmp.Table = table @@ -78,22 +80,22 @@ func AddFlavorFormat(body io.Reader, urlVars Vars, tableName string, ReadEntireB err = errors.New("tableName is required for this method") return } - err = DecodeJsonPayload(body, wdmp.Row, ReadEntireBody) - if err != nil { + if err = DecodeJsonPayload(body, &wdmp.Row, ReadEntireBody); err != nil { return } if len(wdmp.Row) == 0 { err = errors.New("input data is empty") + return } payload, err = json.Marshal(wdmp) return } -func ReplaceFlavorFormat(body io.Reader, urlVars Vars, tableName string, ReadEntireBody BodyReader) (payload[]byte, err error){ - wdmp := ReplaceRowsWDMP{Command:COMMAND_REPLACE_ROWS} +func ReplaceFlavorFormat(body io.Reader, urlVars Vars, tableName string, ReadEntireBody BodyReader) (payload []byte, err error) { + wdmp := &ReplaceRowsWDMP{Command: COMMAND_REPLACE_ROWS} if table, exists := GetFromUrlPath(tableName, urlVars); exists { wdmp.Table = table @@ -102,14 +104,11 @@ func ReplaceFlavorFormat(body io.Reader, urlVars Vars, tableName string, ReadEnt return } - err = DecodeJsonPayload(body, &wdmp.Rows, ReadEntireBody) - - if err != nil { + if err = DecodeJsonPayload(body, &wdmp.Rows, ReadEntireBody); err != nil { return } - if !ValidREPLACEParams(wdmp.Rows){ - err = errors.New("invalid Replacement data") + if err = validation.Validate(wdmp.Rows, validation.Required); err != nil { return } @@ -117,78 +116,28 @@ func ReplaceFlavorFormat(body io.Reader, urlVars Vars, tableName string, ReadEnt return } - -/* Validation functions */ -func ValidateAndRetrieveCommand(header http.Header, wdmp *SetWDMP) (command string, err error){ - if newCid := header.Get(HEADER_WPA_SYNC_NEW_CID); newCid != "" { - wdmp.OldCid = header.Get(HEADER_WPA_SYNC_OLD_CID) - wdmp.NewCid = newCid - wdmp.SyncCmc = header.Get(HEADER_WPA_SYNC_CMC) - command, err = ValidateSETParams(false, wdmp, COMMAND_TEST_SET) - } else { - command, err = ValidateSETParams(true, wdmp, "") - } - return -} - -// -Inputs-: -// **checkingForSetAttr**: true if we're checking for the required parameter properties for the SET_ATTRIBUTES command -// These properties are: attributes and name -// -// **wdmp**: the WDMP object from which we retrieve the parameters -// -// **override**: overrides the final suggested command if non-empty. Useful if one just wants to check for SET command -// parameter properties (value, dataType, name) -// -// -Outputs-: -// *command**: the final command based on the analysis of the parameters -// **err**: it is non-nil if any required property is violated -func ValidateSETParams(checkingForSetAttr bool, wdmp *SetWDMP, override string) (command string, err error){ - for _, sp := range wdmp.Parameters { - if sp.Name == nil { - err = errors.New("name is required for parameters") - return +//This method attempts at defaulting to the SET command given that all the command property requirements are satisfied. +// (name, value, dataType). Then, if the new_cid is provided, it is deduced that the command should be TEST_SET +//else, +func ValidateAndDeduceSET(header http.Header, wdmp *SetWDMP) (err error) { + if err = validation.Validate(wdmp.Parameters, validation.Required); err == nil { + wdmp.Command = COMMAND_SET + if newCid := header.Get(HEADER_WPA_SYNC_NEW_CID); newCid != "" { + wdmp.OldCid, wdmp.NewCid = header.Get(HEADER_WPA_SYNC_OLD_CID), newCid + wdmp.SyncCmc = SetOrLeave(wdmp.SyncCmc, header.Get(HEADER_WPA_SYNC_CMC)) //field is optional + wdmp.Command = COMMAND_TEST_SET } - - if checkingForSetAttr { - if sp.Value != nil || sp.Attributes == nil { - checkingForSetAttr = false - } - } else { //in this case, we are just checking valid parameters for SET - if sp.DataType == nil || sp.Value == nil { - err = errors.New("dataType and value are required for SET command") + } else { + errMsg := err.Error() + if !(errMsg == "cannot be blank" || strings.Contains(errMsg, "name")) { + if err = ValidateSETAttrParams(wdmp.Parameters); err == nil { + wdmp.Command = COMMAND_SET_ATTRS } } } - - if override != "" { - command = override - return - } - - if checkingForSetAttr { // checked for SET_ATTRS properties until the end and found no violation - command = COMMAND_SET_ATTRS - return - } - - command = COMMAND_SET - return -} - -//Validate non-Empty mapping A (nonEmpty keys -> non-Empty(mapping B (string -> string)) -func ValidREPLACEParams(rows map[string]map[string]string) (valid bool){ - for k, v := range rows { - if k == "" || v == nil || len(v) == 0 { - return - } - } - if len(rows) > 0 { - valid = true - } return } - /* Other helper functions */ func DecodeJsonPayload(body io.Reader, v interface{}, ReadEntireBody BodyReader) (err error) { payload, err := ReadEntireBody(body) @@ -206,9 +155,18 @@ func DecodeJsonPayload(body io.Reader, v interface{}, ReadEntireBody BodyReader) return } -func GetFromUrlPath(key string, urlVars map[string]string)(val string, exists bool){ - if urlVars!= nil { +func GetFromUrlPath(key string, urlVars map[string]string) (val string, exists bool) { + if urlVars != nil { val, exists = urlVars[key] } return } + +//if newVal is empty, the currentVal is return +//else newVal is return +func SetOrLeave(currentVal, newVal string) string { + if validation.Validate(newVal, validation.Required) != nil { + return currentVal + } + return newVal +} diff --git a/src/tr1d1um/conversion_utils_test.go b/src/tr1d1um/conversion_utils_test.go index f6c77f53..d8b4dd76 100644 --- a/src/tr1d1um/conversion_utils_test.go +++ b/src/tr1d1um/conversion_utils_test.go @@ -1,67 +1,123 @@ package main import ( - "testing" + "bytes" "encoding/json" + "errors" "github.com/stretchr/testify/assert" - "net/http" - "strings" "io" - "errors" + "net/http" "net/http/httptest" - "bytes" + "strings" + "testing" ) -var sampleNames = []string{"p1","p2"} +var ( + sampleNames = []string{"p1", "p2"} + dataType int8 = 3 + value string = "someVal" + name string = "someName" +) func TestGetFlavorFormat(t *testing.T) { assert := assert.New(t) - t.Run("IdealGet", func (t *testing.T) { + t.Run("IdealGet", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=p1,p2", nil) - wdmp := &GetWDMP{Command:COMMAND_GET, Names:sampleNames} + wdmp := &GetWDMP{Command: COMMAND_GET, Names: sampleNames} expected, expectedErr := json.Marshal(wdmp) - actual, actualErr := GetFlavorFormat(req,"attributes", "names", ",") + actual, actualErr := GetFlavorFormat(req, "attributes", "names", ",") assert.EqualValues(expected, actual) assert.EqualValues(expectedErr, actualErr) }) - t.Run("IdealGetAttr", func (t *testing.T){ + t.Run("IdealGetAttr", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=p1,p2&attributes=attr1", nil) - wdmp := &GetWDMP{Command:COMMAND_GET_ATTRS, Names:sampleNames, Attribute:"attr1"} + wdmp := &GetWDMP{Command: COMMAND_GET_ATTRS, Names: sampleNames, Attribute: "attr1"} expected, expectedErr := json.Marshal(wdmp) - actual, actualErr := GetFlavorFormat(req,"attributes", "names", ",") + actual, actualErr := GetFlavorFormat(req, "attributes", "names", ",") assert.EqualValues(expected, actual) assert.EqualValues(expectedErr, actualErr) }) - t.Run("NoNames", func (t *testing.T){ + t.Run("NoNames", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=", nil) - _ , err := GetFlavorFormat(req,"attributes", "names", ",") + _, err := GetFlavorFormat(req, "attributes", "names", ",") assert.NotNil(err) assert.True(strings.HasPrefix(err.Error(), "names is a required")) }) } +func TestSetFlavorFormat(t *testing.T) { + assert := assert.New(t) + cReader := CustomResultReader{} + + valid := SetParam{Name: &name, DataType: &dataType, Value: value, Attributes: Attr{"notify": 0}} -func TestAddFlavorFormat(t *testing.T) { + req := httptest.NewRequest(http.MethodPatch, "http://device/config?k=v", nil) + t.Run("DecodeJsonErr", func(t *testing.T) { + cReader.err = errors.New("JSON") + _, err := SetFlavorFormat(req, cReader.CustomReader) + assert.NotNil(err) + assert.EqualValues("JSON", err.Error()) + }) + + t.Run("InvalidData", func(t *testing.T) { + cReader.data, cReader.err = []byte("{}"), nil + _, err := SetFlavorFormat(req, cReader.CustomReader) + assert.NotNil(err) + assert.EqualValues("cannot be blank", err.Error()) + }) + + t.Run("IdealPath", func(t *testing.T) { + wdmpObj := &SetWDMP{Command: COMMAND_SET, Parameters: []SetParam{valid}} + + cReader.data = []byte(`{"parameters":[{"name": "someName","value":"someVal","attributes": {"notify": 0}, + "dataType": 3}]}`) + + actual, actualErr := SetFlavorFormat(req, cReader.CustomReader) + expected, expectedErr := json.Marshal(wdmpObj) + + assert.EqualValues(expected, actual) + assert.EqualValues(expectedErr, actualErr) + }) } func TestDeleteFlavorFormat(t *testing.T) { + assert := assert.New(t) + commonVars := Vars{"param": "rowName", "emptyParam": ""} -} + t.Run("NoRowName", func(t *testing.T) { + _, err := DeleteFlavorFormat(Vars{}, "param") + assert.NotNil(err) + assert.True(strings.HasSuffix(err.Error(), "name is required")) + }) + t.Run("EmptyRowName", func(t *testing.T) { + _, err := DeleteFlavorFormat(commonVars, "emptyParam") + assert.NotNil(err) + assert.True(strings.HasSuffix(err.Error(), "name is required")) + }) + + t.Run("IdealPath", func(t *testing.T) { + wdmpObj := &DeleteRowWDMP{Command: COMMAND_DELETE_ROW, Row: "rowName"} + expected, expectedErr := json.Marshal(wdmpObj) + actual, actualErr := DeleteFlavorFormat(commonVars, "param") + assert.EqualValues(expected, actual) + assert.EqualValues(expectedErr, actualErr) + }) +} func TestReplaceFlavorFormat(t *testing.T) { assert := assert.New(t) @@ -69,29 +125,29 @@ func TestReplaceFlavorFormat(t *testing.T) { commonVars := Vars{"uThere?": "yes!"} emptyVars := Vars{} - t.Run("TableNotProvided", func (t *testing.T){ + t.Run("TableNotProvided", func(t *testing.T) { _, err := ReplaceFlavorFormat(nil, emptyVars, "uThere?", cReader.CustomReader) assert.NotNil(err) assert.True(strings.HasPrefix(err.Error(), "tableName")) }) - - t.Run("DecodeJsonErr", func (t *testing.T) { + + t.Run("DecodeJsonErr", func(t *testing.T) { cReader.data = []byte("") _, err := ReplaceFlavorFormat(nil, commonVars, "uThere?", cReader.CustomReader) assert.NotNil(err) assert.True(strings.HasPrefix(err.Error(), "JSON")) }) - - t.Run("InvalidParams", func (t *testing.T) { + + t.Run("InvalidParams", func(t *testing.T) { cReader.data = []byte("{}") _, err := ReplaceFlavorFormat(nil, commonVars, "uThere?", cReader.CustomReader) assert.NotNil(err) - assert.True(strings.HasPrefix(err.Error(), "invalid")) + assert.True(strings.HasSuffix(err.Error(), "blank")) }) - t.Run("IdealPath", func (t *testing.T) { - wdmpObj := &ReplaceRowsWDMP{Command:COMMAND_REPLACE_ROWS, Table: commonVars["uThere?"]} - wdmpObj.Rows = map[string]map[string]string{"0":{"uno":"one", "dos":"two"}} + t.Run("IdealPath", func(t *testing.T) { + wdmpObj := &ReplaceRowsWDMP{Command: COMMAND_REPLACE_ROWS, Table: commonVars["uThere?"]} + wdmpObj.Rows = map[string]map[string]string{"0": {"uno": "one", "dos": "two"}} cReader.data = []byte(`{"0":{"uno":"one","dos":"two"}}`) actual, actualErr := ReplaceFlavorFormat(nil, commonVars, "uThere?", cReader.CustomReader) @@ -102,12 +158,52 @@ func TestReplaceFlavorFormat(t *testing.T) { }) } +func TestAddFlavorFormat(t *testing.T) { + assert := assert.New(t) + cReader := CustomResultReader{} + commonVars := Vars{"uThere?": "yes!"} + emptyVars := Vars{} + + t.Run("TableNotProvided", func(t *testing.T) { + _, err := AddFlavorFormat(nil, emptyVars, "uThere?", cReader.CustomReader) + assert.NotNil(err) + assert.True(strings.HasPrefix(err.Error(), "tableName")) + }) + + t.Run("DecodeJsonErr", func(t *testing.T) { + cReader.data = []byte("") + _, err := AddFlavorFormat(nil, commonVars, "uThere?", cReader.CustomReader) + assert.NotNil(err) + assert.True(strings.HasPrefix(err.Error(), "JSON")) + }) + + t.Run("EmptyData", func(t *testing.T) { + cReader.data = []byte("{}") + _, err := AddFlavorFormat(nil, commonVars, "uThere?", cReader.CustomReader) + assert.NotNil(err) + assert.True(strings.HasSuffix(err.Error(), "data is empty")) + }) + + t.Run("IdealPath", func(t *testing.T) { + wdmpObj := &AddRowWDMP{Command: COMMAND_ADD_ROW, Table: commonVars["uThere?"]} + wdmpObj.Row = map[string]string{"uno": "one", "dos": "two"} + + cReader.data = []byte(`{"uno":"one","dos":"two"}`) + + actual, actualErr := AddFlavorFormat(nil, commonVars, "uThere?", cReader.CustomReader) + expected, expectedErr := json.Marshal(wdmpObj) + + assert.EqualValues(expected, actual) + assert.EqualValues(expectedErr, actualErr) + }) +} + func TestGetFromUrlPath(t *testing.T) { assert := assert.New(t) - fakeUrlVar := map[string]string{"k1":"k1v1,k1v2", "k2": "k2v1"} + fakeUrlVar := map[string]string{"k1": "k1v1,k1v2", "k2": "k2v1"} - t.Run("NormalCases", func (t *testing.T){ + t.Run("NormalCases", func(t *testing.T) { k1ValGroup, exists := GetFromUrlPath("k1", fakeUrlVar) assert.True(exists) @@ -118,12 +214,12 @@ func TestGetFromUrlPath(t *testing.T) { assert.EqualValues("k2v1", k2ValGroup) }) - t.Run("NonNilNonExistent", func (t *testing.T){ + t.Run("NonNilNonExistent", func(t *testing.T) { _, exists := GetFromUrlPath("k3", fakeUrlVar) assert.False(exists) }) - t.Run("NilCase", func (t *testing.T){ + t.Run("NilCase", func(t *testing.T) { _, exists := GetFromUrlPath("k", nil) assert.False(exists) }) @@ -134,15 +230,15 @@ func TestDecodeJsonPayload(t *testing.T) { cReader := CustomResultReader{} - t.Run("ReadEntireBodyFails", func (t *testing.T) { + t.Run("ReadEntireBodyFails", func(t *testing.T) { cReader.err = errors.New("fail") err := DecodeJsonPayload(nil, nil, cReader.CustomReader) assert.NotNil(err) - assert.EqualValues(err.Error(),"fail") + assert.EqualValues(err.Error(), "fail") }) - t.Run("EmptyInput", func (t *testing.T) { + t.Run("EmptyInput", func(t *testing.T) { emptyBody := bytes.NewBufferString("") cReader.data, cReader.err = []byte(""), nil @@ -152,10 +248,10 @@ func TestDecodeJsonPayload(t *testing.T) { assert.EqualValues(err, ErrJsonEmpty) }) - t.Run("IdealPath", func (t *testing.T) { + t.Run("IdealPath", func(t *testing.T) { cReader.data = []byte(`{"0":"zero","1":"one"}`) - expected := map[string]string{"0":"zero", "1":"one"} + expected := map[string]string{"0": "zero", "1": "one"} actual := make(map[string]string) err := DecodeJsonPayload(nil, &actual, cReader.CustomReader) @@ -165,12 +261,94 @@ func TestDecodeJsonPayload(t *testing.T) { }) } +func TestValidateAndDeduceSETCommand(t *testing.T) { + assert := assert.New(t) + + empty := []SetParam{} + attrs := Attr{"attr1": 1, "attr2": "two"} + + noDataType := SetParam{Value: value, Name: &name} + valid := SetParam{Name: &name, DataType: &dataType, Value: value} + attrParam := SetParam{Name: &name, DataType: &dataType, Attributes: attrs} + + testAndSetHeader := http.Header{HEADER_WPA_SYNC_NEW_CID: []string{"newCid"}} + emptyHeader := http.Header{} + + wdmp := new(SetWDMP) + + /* Tests with different possible failures */ + t.Run("NilParams", func(t *testing.T) { + err := ValidateAndDeduceSET(http.Header{}, wdmp) + assert.NotNil(err) + assert.True(strings.Contains(err.Error(), "cannot be blank")) + assert.EqualValues("", wdmp.Command) + }) + + t.Run("EmptyParams", func(t *testing.T) { + wdmp.Parameters = empty + err := ValidateAndDeduceSET(http.Header{}, wdmp) + assert.NotNil(err) + assert.True(strings.Contains(err.Error(), "cannot be blank")) + assert.EqualValues("", wdmp.Command) + }) + + //Will attempt at validating SET_ATTR properties instead + t.Run("MissingSETProperty", func(t *testing.T) { + wdmp.Parameters = append(empty, noDataType) + err := ValidateAndDeduceSET(emptyHeader, wdmp) + assert.EqualValues("invalid attr", err.Error()) + assert.EqualValues("", wdmp.Command) + }) + + /* Ideal command cases */ + t.Run("MultipleValidSET", func(t *testing.T) { + wdmp.Parameters = append(empty, valid, valid) + assert.Nil(ValidateAndDeduceSET(emptyHeader, wdmp)) + assert.EqualValues(COMMAND_SET, wdmp.Command) + }) + + t.Run("MultipleValidTEST_SET", func(t *testing.T) { + wdmp.Parameters = append(empty, valid, valid) + assert.Nil(ValidateAndDeduceSET(testAndSetHeader, wdmp)) + assert.EqualValues(COMMAND_TEST_SET, wdmp.Command) + }) + + t.Run("MultipleValidSET_ATTRS", func(t *testing.T) { + wdmp.Parameters = append(empty, attrParam, attrParam) + assert.Nil(ValidateAndDeduceSET(emptyHeader, wdmp)) + assert.EqualValues(COMMAND_SET_ATTRS, wdmp.Command) + }) + + /* + + t.Run("SET", func (t *testing.T) { + + }) + + t.Run("SET_ATTRS", func (t *testing.T) { + + }) + + t.Run("TEST_SET", func (t *testing.T) { + + }) + */ + +} + +func TestSetOrLeave(t *testing.T) { + assert := assert.New(t) + assert.EqualValues("fallback", SetOrLeave("fallback", "")) + assert.EqualValues("", SetOrLeave("", "")) + assert.EqualValues("theNewVal", SetOrLeave("", "theNewVal")) +} + +/*Set the data and err fields and the next call to CustomReader will return them*/ type CustomResultReader struct { data []byte - err error + err error } -func(c CustomResultReader) CustomReader (_ io.Reader)([]byte,error){ +func (c CustomResultReader) CustomReader(_ io.Reader) ([]byte, error) { return c.data, c.err } - diff --git a/src/tr1d1um/http.go b/src/tr1d1um/http.go index 76f58871..b1df8ac8 100644 --- a/src/tr1d1um/http.go +++ b/src/tr1d1um/http.go @@ -1,31 +1,33 @@ package main + import ( - "net/http" - "time" "bytes" - "github.com/go-kit/kit/log" - "github.com/Comcast/webpa-common/logging" - "io/ioutil" - "github.com/Comcast/webpa-common/wrp" "encoding/json" "errors" - "io" + "github.com/Comcast/webpa-common/logging" + "github.com/Comcast/webpa-common/wrp" + "github.com/go-kit/kit/log" "github.com/gorilla/mux" + "io" + "io/ioutil" + "net/http" + "time" ) + type ConversionHandler struct { - infoLogger log.Logger + infoLogger log.Logger errorLogger log.Logger - timeOut time.Duration - targetUrl string + timeOut time.Duration + targetUrl string /*These functions should be set during handler set up */ - GetFlavorFormat func(*http.Request, string, string, string) ([]byte, error) - SetFlavorFormat func(*http.Request, BodyReader) ([]byte, error) - DeleteFlavorFormat func(Vars, string) ([]byte, error) - AddFlavorFormat func(io.Reader, Vars, string, BodyReader) ([]byte, error) + GetFlavorFormat func(*http.Request, string, string, string) ([]byte, error) + SetFlavorFormat func(*http.Request, BodyReader) ([]byte, error) + DeleteFlavorFormat func(Vars, string) ([]byte, error) + AddFlavorFormat func(io.Reader, Vars, string, BodyReader) ([]byte, error) ReplaceFlavorFormat func(io.Reader, Vars, string, BodyReader) ([]byte, error) } -func (sh ConversionHandler) ConversionGETHandler(resp http.ResponseWriter, req *http.Request){ +func (sh ConversionHandler) ConversionGETHandler(resp http.ResponseWriter, req *http.Request) { wdmpPayload, err := sh.GetFlavorFormat(req, "attributes", "names", ",") if err != nil { @@ -33,12 +35,12 @@ func (sh ConversionHandler) ConversionGETHandler(resp http.ResponseWriter, req * return } - wrpMessage := &wrp.SimpleRequestResponse{Type:wrp.SimpleRequestResponseMessageType, Payload:wdmpPayload} + wrpMessage := &wrp.SimpleRequestResponse{Type: wrp.SimpleRequestResponseMessageType, Payload: wdmpPayload} sh.SendData(resp, wrpMessage) } -func (sh ConversionHandler) ConversionSETHandler(resp http.ResponseWriter, req *http.Request){ +func (sh ConversionHandler) ConversionSETHandler(resp http.ResponseWriter, req *http.Request) { wdmpPayload, err := sh.SetFlavorFormat(req, ioutil.ReadAll) if err != nil { @@ -46,12 +48,12 @@ func (sh ConversionHandler) ConversionSETHandler(resp http.ResponseWriter, req * return } - wrpMessage := &wrp.SimpleRequestResponse{Type:wrp.SimpleRequestResponseMessageType, Payload:wdmpPayload} + wrpMessage := &wrp.SimpleRequestResponse{Type: wrp.SimpleRequestResponseMessageType, Payload: wdmpPayload} sh.SendData(resp, wrpMessage) } -func (sh ConversionHandler) ConversionDELETEHandler(resp http.ResponseWriter, req *http.Request){ +func (sh ConversionHandler) ConversionDELETEHandler(resp http.ResponseWriter, req *http.Request) { wdmpPayload, err := sh.DeleteFlavorFormat(mux.Vars(req), "parameter") if err != nil { @@ -59,12 +61,12 @@ func (sh ConversionHandler) ConversionDELETEHandler(resp http.ResponseWriter, re return } - wrpMessage := &wrp.SimpleRequestResponse{Type:wrp.SimpleRequestResponseMessageType, Payload:wdmpPayload} + wrpMessage := &wrp.SimpleRequestResponse{Type: wrp.SimpleRequestResponseMessageType, Payload: wdmpPayload} sh.SendData(resp, wrpMessage) } -func (sh ConversionHandler) ConversionREPLACEHandler(resp http.ResponseWriter, req *http.Request){ +func (sh ConversionHandler) ConversionREPLACEHandler(resp http.ResponseWriter, req *http.Request) { wdmpPayload, err := sh.ReplaceFlavorFormat(req.Body, mux.Vars(req), "parameter", ioutil.ReadAll) if err != nil { @@ -72,12 +74,12 @@ func (sh ConversionHandler) ConversionREPLACEHandler(resp http.ResponseWriter, r return } - wrpMessage := &wrp.SimpleRequestResponse{Type:wrp.SimpleRequestResponseMessageType, Payload:wdmpPayload} + wrpMessage := &wrp.SimpleRequestResponse{Type: wrp.SimpleRequestResponseMessageType, Payload: wdmpPayload} sh.SendData(resp, wrpMessage) } -func (sh ConversionHandler) ConversionADDHandler(resp http.ResponseWriter, req *http.Request){ +func (sh ConversionHandler) ConversionADDHandler(resp http.ResponseWriter, req *http.Request) { wdmpPayload, err := sh.AddFlavorFormat(req.Body, mux.Vars(req), "parameter", ioutil.ReadAll) if err != nil { @@ -85,12 +87,12 @@ func (sh ConversionHandler) ConversionADDHandler(resp http.ResponseWriter, req * return } - wrpMessage := &wrp.SimpleRequestResponse{Type:wrp.SimpleRequestResponseMessageType, Payload:wdmpPayload} + wrpMessage := &wrp.SimpleRequestResponse{Type: wrp.SimpleRequestResponseMessageType, Payload: wdmpPayload} sh.SendData(resp, wrpMessage) } -func (sh ConversionHandler) SendData(resp http.ResponseWriter, wrpMessage *wrp.SimpleRequestResponse){ +func (sh ConversionHandler) SendData(resp http.ResponseWriter, wrpMessage *wrp.SimpleRequestResponse) { wrpPayload, err := json.Marshal(wrpMessage) if err != nil { @@ -98,7 +100,7 @@ func (sh ConversionHandler) SendData(resp http.ResponseWriter, wrpMessage *wrp.S return } - clientWithDeadline := http.Client{Timeout:sh.timeOut} + clientWithDeadline := http.Client{Timeout: sh.timeOut} //todo: any headers to be added here requestToServer, err := http.NewRequest("GET", sh.targetUrl, bytes.NewBuffer(wrpPayload)) @@ -111,7 +113,7 @@ func (sh ConversionHandler) SendData(resp http.ResponseWriter, wrpMessage *wrp.S respFromServer, err := clientWithDeadline.Do(requestToServer) - if err != nil{ + if err != nil { resp.WriteHeader(http.StatusInternalServerError) resp.Write([]byte("Error while posting request")) sh.errorLogger.Log(logging.MessageKey(), "Could not complete request", logging.ErrorKey(), err.Error()) @@ -122,4 +124,3 @@ func (sh ConversionHandler) SendData(resp http.ResponseWriter, wrpMessage *wrp.S resp.WriteHeader(respFromServer.StatusCode) resp.Write([]byte(respFromServer.Status)) } - diff --git a/src/tr1d1um/http_test.go b/src/tr1d1um/http_test.go index a1ad8aec..9bd62fd1 100644 --- a/src/tr1d1um/http_test.go +++ b/src/tr1d1um/http_test.go @@ -6,8 +6,8 @@ type logTracker struct { } func (fake *logTracker) Log(keyVals ...interface{}) (err error) { - for i, keyVal := range keyVals{ - if i % 2 == 0{ + for i, keyVal := range keyVals { + if i%2 == 0 { fake.keys = append(fake.keys, keyVal) } else { fake.vals = append(fake.vals, keyVal) @@ -15,6 +15,7 @@ func (fake *logTracker) Log(keyVals ...interface{}) (err error) { } return } + /* func TestConversionGETHandlerWrapFailure(t *testing.T) { assert := assert.New(t) @@ -30,4 +31,3 @@ func TestConversionGETHandlerWrapFailure(t *testing.T) { } */ //todo: more cases - diff --git a/src/tr1d1um/tr1d1um.go b/src/tr1d1um/tr1d1um.go index 0c7d95a5..9029569e 100644 --- a/src/tr1d1um/tr1d1um.go +++ b/src/tr1d1um/tr1d1um.go @@ -1,33 +1,33 @@ package main import ( - "os" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "github.com/Comcast/webpa-common/server" "github.com/Comcast/webpa-common/logging" - "github.com/gorilla/mux" - "github.com/Comcast/webpa-common/secure/handler" "github.com/Comcast/webpa-common/secure" - "github.com/SermoDigital/jose/jwt" - "github.com/justinas/alice" + "github.com/Comcast/webpa-common/secure/handler" "github.com/Comcast/webpa-common/secure/key" - "time" + "github.com/Comcast/webpa-common/server" + "github.com/SermoDigital/jose/jwt" "github.com/go-kit/kit/log" + "github.com/gorilla/mux" + "github.com/justinas/alice" + "github.com/spf13/pflag" + "github.com/spf13/viper" "net/http" + "os" + "time" ) const ( applicationName = "tr1d1um" - DefaultKeyId = "current" + DefaultKeyId = "current" ) + func tr1d1um(arguments []string) int { var ( - f= pflag.NewFlagSet(applicationName, pflag.ContinueOnError) - v= viper.New() - logger, _, err= server.Initialize(applicationName, arguments, f, v) + f = pflag.NewFlagSet(applicationName, pflag.ContinueOnError) + v = viper.New() + logger, _, err = server.Initialize(applicationName, arguments, f, v) ) - if err != nil { logging.Error(logger).Log(logging.MessageKey(), "Unable to initialize Viper environment: %s\n", logging.ErrorKey(), err) @@ -35,9 +35,9 @@ func tr1d1um(arguments []string) int { } var ( - messageKey = logging.MessageKey() - errorKey = logging.ErrorKey() - infoLogger = logging.Info(logger) + messageKey = logging.MessageKey() + errorKey = logging.ErrorKey() + infoLogger = logging.Info(logger) errorLogger = logging.Error(logger) ) @@ -46,14 +46,14 @@ func tr1d1um(arguments []string) int { tConfig := new(Tr1d1umConfig) err = v.Unmarshal(tConfig) //todo: decide best way to get current unexported fields from viper if err != nil { - errorLogger.Log(messageKey,"Unable to unmarshal configuration data into struct", errorKey, err) + errorLogger.Log(messageKey, "Unable to unmarshal configuration data into struct", errorKey, err) return 1 } preHandler, err := SetUpPreHandler(v, logger) if err != nil { - infoLogger.Log(messageKey,"Error setting up pre handler", errorKey, err) + infoLogger.Log(messageKey, "Error setting up pre handler", errorKey, err) return 1 } @@ -68,26 +68,26 @@ func tr1d1um(arguments []string) int { return 0 } -func AddRoutes(r *mux.Router, preHandler *alice.Chain, conversionHandler *ConversionHandler) (* mux.Router) { +func AddRoutes(r *mux.Router, preHandler *alice.Chain, conversionHandler *ConversionHandler) *mux.Router { var BodyNonNil = func(request *http.Request, match *mux.RouteMatch) bool { return request.Body != nil } //todo: inquire about API version r.Handle("/device/{deviceid}/{service}", preHandler.ThenFunc(conversionHandler.ConversionGETHandler)). - Methods(http.MethodGet) + Methods(http.MethodGet) r.Handle("/device/{deviceid}/{service}", preHandler.ThenFunc(conversionHandler.ConversionSETHandler)). - Methods(http.MethodPatch).MatcherFunc(BodyNonNil) + Methods(http.MethodPatch).MatcherFunc(BodyNonNil) r.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.ThenFunc(conversionHandler. - ConversionDELETEHandler)).Methods(http.MethodDelete) + ConversionDELETEHandler)).Methods(http.MethodDelete) r.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.ThenFunc(conversionHandler. - ConversionADDHandler)).Methods(http.MethodPost).MatcherFunc(BodyNonNil) + ConversionADDHandler)).Methods(http.MethodPost).MatcherFunc(BodyNonNil) r.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.ThenFunc(conversionHandler. - ConversionREPLACEHandler)).Methods(http.MethodPut).MatcherFunc(BodyNonNil) + ConversionREPLACEHandler)).Methods(http.MethodPut).MatcherFunc(BodyNonNil) return r } @@ -141,7 +141,7 @@ func GetValidator(v *viper.Viper) (validator secure.Validator, err error) { return } -func SetUpHandler(tConfig *Tr1d1umConfig, errorLogger log.Logger, infoLogger log.Logger)(cHandler *ConversionHandler){ +func SetUpHandler(tConfig *Tr1d1umConfig, errorLogger log.Logger, infoLogger log.Logger) (cHandler *ConversionHandler) { cHandler = &ConversionHandler{timeOut: time.Duration(tConfig.timeOut), targetUrl: tConfig.targetUrl} //pass loggers cHandler.errorLogger = errorLogger @@ -156,7 +156,7 @@ func SetUpHandler(tConfig *Tr1d1umConfig, errorLogger log.Logger, infoLogger log return } -func SetUpPreHandler(v *viper.Viper, logger log.Logger) (preHandler *alice.Chain, err error){ +func SetUpPreHandler(v *viper.Viper, logger log.Logger) (preHandler *alice.Chain, err error) { validator, err := GetValidator(v) if err != nil { return @@ -177,4 +177,3 @@ func SetUpPreHandler(v *viper.Viper, logger log.Logger) (preHandler *alice.Chain func main() { os.Exit(tr1d1um(os.Args)) } - diff --git a/src/tr1d1um/tr1d1um_type.go b/src/tr1d1um/tr1d1um_type.go index 2944d694..6c3a8498 100644 --- a/src/tr1d1um/tr1d1um_type.go +++ b/src/tr1d1um/tr1d1um_type.go @@ -1,8 +1,8 @@ package main import ( - "github.com/Comcast/webpa-common/secure/key" "github.com/Comcast/webpa-common/secure" + "github.com/Comcast/webpa-common/secure/key" ) type Tr1d1umConfig struct { @@ -18,9 +18,9 @@ type Tr1d1umConfig struct { MaxApiTcpConns int64 `json:"maxApiTcpConnections"` MaxHealthTcpConns int64 `json:"maxHealthTcpConnections"` ServiceList []string `json:"serviceList"` - WrpSource string `json:"wrpSource"` - timeOut int64 `json:"timeOut"` - targetUrl string `json:"targetUrl"` + WrpSource string `json:"wrpSource"` + timeOut int64 `json:"timeOut"` + targetUrl string `json:"targetUrl"` JWTValidators []struct { // JWTKeys is used to create the key.Resolver for JWT verification keys @@ -40,4 +40,3 @@ type JWTValidator struct { // custom rules for validation over and above the standard RFC rules. Custom secure.JWTValidatorFactory } -