diff --git a/api_testcase.go b/api_testcase.go index 5b47e87..87363b1 100644 --- a/api_testcase.go +++ b/api_testcase.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "encoding/json" "fmt" "net/http" "path/filepath" @@ -10,13 +9,13 @@ import ( "time" "github.com/programmfabrik/apitest/pkg/lib/datastore" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/golib" "github.com/programmfabrik/apitest/pkg/lib/api" "github.com/programmfabrik/apitest/pkg/lib/compare" "github.com/programmfabrik/apitest/pkg/lib/report" "github.com/programmfabrik/apitest/pkg/lib/template" - "github.com/programmfabrik/apitest/pkg/lib/util" "github.com/sirupsen/logrus" ) @@ -191,8 +190,8 @@ func (testCase *Case) checkCollectResponse(response api.Response) (responses int var ( loadedResponses any - jsonRespArray util.JsonArray - leftResponses util.JsonArray + jsonRespArray jsutil.Array + leftResponses jsutil.Array spec api.ResponseSerialization responsesMatch compare.CompareResult ) @@ -203,15 +202,15 @@ func (testCase *Case) checkCollectResponse(response api.Response) (responses int } switch t := loadedResponses.(type) { - case util.JsonArray: + case jsutil.Array: jsonRespArray = t - case util.JsonObject: - jsonRespArray = util.JsonArray{t} + case jsutil.Object: + jsonRespArray = jsutil.Array{t} default: return -1, fmt.Errorf("loading check response: no valid type") } - leftResponses = make(util.JsonArray, 0) + leftResponses = make(jsutil.Array, 0) for _, v := range jsonRespArray { spec, err = testCase.loadResponseSerialization(v) if err != nil { @@ -262,14 +261,14 @@ func (testCase Case) executeRequest(counter int) (responsesMatch compare.Compare return responsesMatch, req, apiResp, err } - //Do Request + // Do Request req, err = testCase.loadRequest() if err != nil { err = fmt.Errorf("loading request: %w", err) return responsesMatch, req, apiResp, err } - //Log request on trace level (so only v2 will trigger this) + // Log request on trace level (so only v2 will trigger this) if testCase.LogNetwork != nil && *testCase.LogNetwork { logrus.Tracef("[REQUEST]:\n%s\n\n", limitLines(req.ToString(logCurl), Config.Apitest.Limit.Request)) } @@ -387,7 +386,7 @@ func (testCase Case) run() (successs bool, apiResponse api.Response, err error) time.Sleep(time.Duration(*testCase.WaitBefore) * time.Millisecond) } - //Poll repeats the request until the right response is found, or a timeout triggers + // Poll repeats the request until the right response is found, or a timeout triggers for { // delay between repeating a request if testCase.Delay != nil { @@ -431,8 +430,9 @@ func (testCase Case) run() (successs bool, apiResponse api.Response, err error) break } - //break if timeout or we do not have a repeater - if timedOut := time.Since(startTime) > (time.Duration(testCase.Timeout) * time.Millisecond); timedOut && testCase.Timeout != -1 { + // break if timeout or we do not have a repeater + timedOut := time.Since(startTime) > (time.Duration(testCase.Timeout) * time.Millisecond) + if timedOut && testCase.Timeout != -1 { if timedOut && testCase.Timeout > 0 { logrus.Warnf("Pull Timeout '%dms' exceeded", testCase.Timeout) r.SaveToReportLogF("Pull Timeout '%dms' exceeded", testCase.Timeout) @@ -457,10 +457,10 @@ func (testCase Case) run() (successs bool, apiResponse api.Response, err error) } } - collectArray, ok := testCase.CollectResponse.(util.JsonArray) + collectArray, ok := testCase.CollectResponse.(jsutil.Array) if ok { for _, v := range collectArray { - jsonV, err := json.Marshal(v) + jsonV, err := jsutil.Marshal(v) if err != nil { testCase.logReq(request) testCase.logResp(apiResponse) @@ -556,11 +556,14 @@ func (testCase Case) loadRequestSerialization() (req api.Request, err error) { return spec, fmt.Errorf("loading request data: %w", err) } - specBytes, err = json.Marshal(requestData) + specBytes, err = jsutil.Marshal(requestData) if err != nil { - return spec, fmt.Errorf("marshaling requet: %w", err) + return spec, fmt.Errorf("marshaling request: %w", err) + } + err = jsutil.Unmarshal(specBytes, &spec) + if err != nil { + return spec, fmt.Errorf("unmarshaling request: %w", err) } - err = util.Unmarshal(specBytes, &spec) spec.ManifestDir = testCase.manifestDir spec.DataStore = testCase.dataStore @@ -571,7 +574,8 @@ func (testCase Case) loadRequestSerialization() (req api.Request, err error) { spec.Headers = make(map[string]any) } for k, v := range testCase.standardHeader { - if _, exist := spec.Headers[k]; !exist { + _, exist := spec.Headers[k] + if !exist { spec.Headers[k] = v } } @@ -580,7 +584,8 @@ func (testCase Case) loadRequestSerialization() (req api.Request, err error) { spec.HeaderFromStore = make(map[string]string) } for k, v := range testCase.standardHeaderFromStore { - if _, exist := spec.HeaderFromStore[k]; !exist { + _, exist := spec.HeaderFromStore[k] + if !exist { spec.HeaderFromStore[k] = v } } @@ -595,12 +600,12 @@ func (testCase Case) loadResponseSerialization(genJSON any) (spec api.ResponseSe return spec, fmt.Errorf("loading response data: %w", err) } - specBytes, err := json.Marshal(responseData) + specBytes, err := jsutil.Marshal(responseData) if err != nil { return spec, fmt.Errorf("marshaling response: %w", err) } - err = util.Unmarshal(specBytes, &spec) + err = jsutil.Unmarshal(specBytes, &spec) if err != nil { return spec, fmt.Errorf("unmarshaling response: %w", err) } diff --git a/api_testcase_test.go b/api_testcase_test.go index 870604c..44f9c7c 100644 --- a/api_testcase_test.go +++ b/api_testcase_test.go @@ -1,15 +1,13 @@ package main import ( - "encoding/json" "fmt" - "math/rand" "net/http" "net/http/httptest" "testing" - "time" "github.com/programmfabrik/apitest/pkg/lib/datastore" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/tidwall/gjson" "github.com/programmfabrik/apitest/pkg/lib/filesystem" @@ -18,12 +16,500 @@ import ( "github.com/spf13/afero" ) -func init() { - rand.Seed(time.Now().UnixNano()) - -} func TestGjson(t *testing.T) { - jsolo := `{"body":[{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":46,"global_object_id":"1@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":1,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:05+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":47,"global_object_id":"2@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":2,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:05+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":48,"global_object_id":"3@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":3,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:06+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":49,"global_object_id":"4@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":4,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:06+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"event":{"_id":50,"global_object_id":"1@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":1,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:06+01:00","type":"OBJECT_INDEX"}},{"event":{"_id":51,"global_object_id":"2@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":2,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:06+01:00","type":"OBJECT_INDEX"}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":52,"global_object_id":"5@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":5,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:06+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":53,"global_object_id":"6@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":6,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:06+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":54,"global_object_id":"7@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":7,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:06+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":55,"global_object_id":"8@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":8,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":56,"global_object_id":"9@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":9,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":57,"global_object_id":"10@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":10,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":58,"global_object_id":"11@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":11,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":59,"global_object_id":"12@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":12,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"event":{"_id":60,"global_object_id":"5@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":5,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INDEX"}},{"event":{"_id":61,"global_object_id":"6@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":6,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INDEX"}},{"event":{"_id":62,"global_object_id":"3@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":3,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INDEX"}},{"event":{"_id":63,"global_object_id":"4@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":4,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INDEX"}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":64,"global_object_id":"13@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":13,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:07+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":65,"global_object_id":"14@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":14,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:08+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":66,"global_object_id":"15@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":15,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:08+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"_session":{"token":"ac554a02-3ef0-42da-8ffb-603d73de95f9"},"event":{"_id":67,"global_object_id":"16@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":16,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","session_self":true,"timestamp":"2019-03-13T10:41:08+01:00","type":"OBJECT_INSERT"},"user":{"_generated_displayname":"Root","_id":1}},{"event":{"_id":68,"global_object_id":"8@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":8,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:08+01:00","type":"OBJECT_INDEX"}},{"event":{"_id":69,"global_object_id":"9@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":9,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:08+01:00","type":"OBJECT_INDEX"}},{"event":{"_id":70,"global_object_id":"7@ebe5e467-4da9-4cff-81b6-cee9b1385b7c","object_id":7,"object_version":1,"objecttype":"main","pollable":true,"schema":"USER","timestamp":"2019-03-13T10:41:08+01:00","type":"OBJECT_INDEX"}}],"header":{"Cache-Control":["no-cache"],"Content-Type":["application/json; charset=utf-8"],"Date":["Wed, 13 Mar 2019 09:41:16 GMT"],"Last-Modified":["Wed, 13 Mar 2019, 09:41:16 GMT"],"Pragma":["no-cache"],"Server":["Apache/2.4.25 (Debian)"],"Vary":["Origin,Accept-Encoding"],"X-Easydb-Api-Version":["1"],"X-Easydb-Base-Schema-Version":["207"],"X-Easydb-Solution":["simon"],"X-Easydb-User-Schema-Version":["2"]},"statuscode":200}` + jsolo := `{ + "body": [ + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 46, + "global_object_id": "1@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 1, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:05+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 47, + "global_object_id": "2@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 2, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:05+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 48, + "global_object_id": "3@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 3, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:06+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 49, + "global_object_id": "4@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 4, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:06+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "event": { + "_id": 50, + "global_object_id": "1@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 1, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:06+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "event": { + "_id": 51, + "global_object_id": "2@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 2, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:06+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 52, + "global_object_id": "5@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 5, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:06+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 53, + "global_object_id": "6@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 6, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:06+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 54, + "global_object_id": "7@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 7, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:06+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 55, + "global_object_id": "8@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 8, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 56, + "global_object_id": "9@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 9, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 57, + "global_object_id": "10@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 10, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 58, + "global_object_id": "11@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 11, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 59, + "global_object_id": "12@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 12, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "event": { + "_id": 60, + "global_object_id": "5@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 5, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "event": { + "_id": 61, + "global_object_id": "6@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 6, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "event": { + "_id": 62, + "global_object_id": "3@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 3, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "event": { + "_id": 63, + "global_object_id": "4@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 4, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 64, + "global_object_id": "13@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 13, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:07+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 65, + "global_object_id": "14@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 14, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:08+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 66, + "global_object_id": "15@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 15, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:08+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "_session": { + "token": "ac554a02-3ef0-42da-8ffb-603d73de95f9" + }, + "event": { + "_id": 67, + "global_object_id": "16@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 16, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "session_self": true, + "timestamp": "2019-03-13T10:41:08+01:00", + "type": "OBJECT_INSERT" + }, + "user": { + "_generated_displayname": "Root", + "_id": 1 + } + }, + { + "event": { + "_id": 68, + "global_object_id": "8@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 8, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:08+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "event": { + "_id": 69, + "global_object_id": "9@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 9, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:08+01:00", + "type": "OBJECT_INDEX" + } + }, + { + "event": { + "_id": 70, + "global_object_id": "7@ebe5e467-4da9-4cff-81b6-cee9b1385b7c", + "object_id": 7, + "object_version": 1, + "objecttype": "main", + "pollable": true, + "schema": "USER", + "timestamp": "2019-03-13T10:41:08+01:00", + "type": "OBJECT_INDEX" + } + } + ], + "header": { + "Cache-Control": [ + "no-cache" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Wed, 13 Mar 2019 09:41:16 GMT" + ], + "Last-Modified": [ + "Wed, 13 Mar 2019, 09:41:16 GMT" + ], + "Pragma": [ + "no-cache" + ], + "Server": [ + "Apache/2.4.25 (Debian)" + ], + "Vary": [ + "Origin,Accept-Encoding" + ], + "X-Easydb-Api-Version": [ + "1" + ], + "X-Easydb-Base-Schema-Version": [ + "207" + ], + "X-Easydb-Solution": [ + "simon" + ], + "X-Easydb-User-Schema-Version": [ + "2" + ] + }, + "statuscode": 200 + }` fmt.Println(gjson.Get(jsolo, "body|@reverse|0.event._id")) } @@ -33,55 +519,66 @@ func TestCollectResponseShouldWork(t *testing.T) { i := -2 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `[{"ID":%d},{"ID":%d}]`, i, i+1) + fmt.Fprintf(w, `[ + { + "ID": %d + }, + { + "ID": %d + } + ]`, i, i+1) i = i + 2 })) defer ts.Close() - testManifest := []byte(` - { - "name": "CollectTest", - "request":{ - "endpoint": "suggest", - "method": "GET" - }, - "timeout_ms":3000, - "collect_response":[ + testManifest := []byte(`{ + "collect_response": [ + { + "body": [ { - "body":[{ - "ID":2 - }] - }, + "ID": 2 + } + ] + }, + { + "body": [ { - "body":[{ - "ID":22 - }] - }, + "ID": 22 + } + ] + }, + { + "body": [ { - "body":[{ - "ID":122 - }] - }, + "ID": 122 + } + ] + }, + { + "body": [ { - "body":[{ - "ID":212 - }] + "ID": 212 } ] - - } -`) + } + ], + "name": "CollectTest", + "request": { + "endpoint": "suggest", + "method": "GET" + }, + "timeout_ms": 3000 + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -100,40 +597,50 @@ func TestCollectLoadExternalFile(t *testing.T) { i := -2 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `[{"ID":%d},{"ID":%d}]`, i, i+1) + fmt.Fprintf(w, `[ + { + "ID": %d + }, + { + "ID": %d + } + ]`, i, i+1) i = i + 2 })) defer ts.Close() filesystem.Fs = afero.NewMemMapFs() externalFile := []byte(`{ - "body":[{ - "ID":2 - }] - }`) - - testManifest := []byte(` - { - "name": "CollectTest", - "request":{ - "endpoint": "suggest", - "method": "GET" - }, - "timeout_ms":300, - "collect_response":["@collect.json"] - }`) + "body": [ + { + "ID": 2 + } + ] + }`) + + testManifest := []byte(`{ + "collect_response": [ + "@collect.json" + ], + "name": "CollectTest", + "request": { + "endpoint": "suggest", + "method": "GET" + }, + "timeout_ms": 300 + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) - afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "collect.json", externalFile, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -152,48 +659,64 @@ func TestCollectLoadExternalCollect(t *testing.T) { i := -2 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, `[{"ID":%d},{"ID":%d}]`, i, i+1) + fmt.Fprintf(w, `[ + { + "ID": %d + }, + { + "ID": %d + } + ]`, i, i+1) i = i + 2 })) defer ts.Close() filesystem.Fs = afero.NewMemMapFs() - externalFile := []byte(`[{ - "body":[{ - "ID":2 - }] - },{ - "body":[{ - "ID":6 - }] - },{ - "body":[{ - "ID":7 - }] - }]`) - - testManifest := []byte(` - { - "name": "CollectTest", - "request":{ - "endpoint": "suggest", - "method": "GET" - }, - "timeout_ms":3000, - "collect_response":"@collect.json" - }`) + externalFile := []byte(`[ + { + "body": [ + { + "ID": 2 + } + ] + }, + { + "body": [ + { + "ID": 6 + } + ] + }, + { + "body": [ + { + "ID": 7 + } + ] + } + ]`) + + testManifest := []byte(`{ + "collect_response": "@collect.json", + "name": "CollectTest", + "request": { + "endpoint": "suggest", + "method": "GET" + }, + "timeout_ms": 3000 + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) - afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "collect.json", externalFile, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -212,87 +735,122 @@ func TestCollectEvents(t *testing.T) { j := 100 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `[ - { - "event": { - "type": "OBJECT_INDEX", - "_id": 961, - "object_version": 1, - "object_id": 118, - "schema": "USER", - "objecttype": "pictures", - "global_object_id": "118@8367e587-f999-4e72-b69d-b5742eb4d5f4", - "timestamp": "2018-11-28T17:37:27+01:00", - "pollable": true - } - }, - { - "event": { - "type": "OBJECT_INDEX", - "_id": 962, - "object_version": 0, - "object_id": 1000832836, - "schema": "BASE", - "basetype": "asset", - "timestamp": "2018-11-28T17:37:27+01:00", - "pollable": true - } - }, - { - "event": { - "type": "OBJECT_INDEX", - "_id": 963, - "object_version": 0, - "object_id": %d, - "schema": "BASE", - "basetype": "asset", - "timestamp": "2018-11-28T17:37:27+01:00", - "pollable": true - } - }, - { - "event": { - "type": "OBJECT_INDEX", - "_id": 963, - "object_version": 0, - "object_id": %d, - "schema": "BASE", - "basetype": "asset", - "timestamp": "2018-11-28T17:37:27+01:00", - "pollable": true - } - } -] -`, i, j) + { + "event": { + "type": "OBJECT_INDEX", + "_id": 961, + "object_version": 1, + "object_id": 118, + "schema": "USER", + "objecttype": "pictures", + "global_object_id": "118@8367e587-f999-4e72-b69d-b5742eb4d5f4", + "timestamp": "2018-11-28T17:37:27+01:00", + "pollable": true + } + }, + { + "event": { + "type": "OBJECT_INDEX", + "_id": 962, + "object_version": 0, + "object_id": 1000832836, + "schema": "BASE", + "basetype": "asset", + "timestamp": "2018-11-28T17:37:27+01:00", + "pollable": true + } + }, + { + "event": { + "type": "OBJECT_INDEX", + "_id": 963, + "object_version": 0, + "object_id": %d, + "schema": "BASE", + "basetype": "asset", + "timestamp": "2018-11-28T17:37:27+01:00", + "pollable": true + } + }, + { + "event": { + "type": "OBJECT_INDEX", + "_id": 963, + "object_version": 0, + "object_id": %d, + "schema": "BASE", + "basetype": "asset", + "timestamp": "2018-11-28T17:37:27+01:00", + "pollable": true + } + } + ]`, i, j) i++ j-- })) defer ts.Close() filesystem.Fs = afero.NewMemMapFs() - externalFile := []byte(`[{"body":[{"event":{"object_id":117}}]},{"body":[{"event":{"object_id":118}}]},{"body":[{"event":{"object_id":418}}]},{"body":[{"event":{"object_id":92}}]}]`) - - testManifest := []byte(` - { - "name": "CollectTest", - "request":{ - "endpoint": "suggest", - "method": "GET" - }, - "timeout_ms":6000, - "collect_response":"@collect.json" - }`) + externalFile := []byte(`[ + { + "body": [ + { + "event": { + "object_id": 117 + } + } + ] + }, + { + "body": [ + { + "event": { + "object_id": 118 + } + } + ] + }, + { + "body": [ + { + "event": { + "object_id": 418 + } + } + ] + }, + { + "body": [ + { + "event": { + "object_id": 92 + } + } + ] + } + ]`) + + testManifest := []byte(`{ + "collect_response": "@collect.json", + "name": "CollectTest", + "request": { + "endpoint": "suggest", + "method": "GET" + }, + "timeout_ms": 6000 + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) - afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "collect.json", externalFile, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -316,36 +874,34 @@ func TestCollectResponseShouldFail(t *testing.T) { })) defer ts.Close() - testManifest := []byte(` - { - "name": "CollectTest", - "request":{ - "endpoint": "suggest", - "method": "GET" - }, - "timeout_ms":30, - "collect_response":[ + testManifest := []byte(`{ + "collect_response": [ + { + "body": [ { - "body":[{ - "ID":1 - }] + "ID": 1 } ] - - } -`) + } + ], + "name": "CollectTest", + "request": { + "endpoint": "suggest", + "method": "GET" + }, + "timeout_ms": 30 + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() r.Root().NoLogTime = true var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -379,35 +935,32 @@ func TestHeaderFromDatastoreWithMap(t *testing.T) { })) defer ts.Close() - testManifest := []byte(` - { - "name": "CollectTest", - "request":{ - "endpoint": "suggest", - "method": "GET", - "header_from_store":{ - "authHeader":"hallo[du]" - } + testManifest := []byte(`{ + "name": "CollectTest", + "request": { + "endpoint": "suggest", + "header_from_store": { + "authHeader": "hallo[du]" }, - "response":{ - "body": { - "Auth": "du index" - } + "method": "GET" + }, + "response": { + "body": { + "Auth": "du index" } - } -`) + } + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() r.Root().NoLogTime = true var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -430,35 +983,32 @@ func TestHeaderFromDatastoreWithSlice(t *testing.T) { })) defer ts.Close() - testManifest := []byte(` - { - "name": "CollectTest", - "request":{ - "endpoint": "suggest", - "method": "GET", - "header_from_store":{ - "authHeader":"hallo[3]" - } + testManifest := []byte(`{ + "name": "CollectTest", + "request": { + "endpoint": "suggest", + "header_from_store": { + "authHeader": "hallo[3]" }, - "response":{ - "body": { - "Auth": "es index" - } + "method": "GET" + }, + "response": { + "body": { + "Auth": "es index" } - } -`) + } + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() r.Root().NoLogTime = true var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -484,39 +1034,38 @@ func TestCookieSetInDatastore(t *testing.T) { Name: "sess", Value: "you_session_data", }) - fmt.Fprint(w, `{"status": "done"}`) + fmt.Fprint(w, `{ + "status": "done" + }`) })) defer ts.Close() - testManifest := []byte(` - { - "name": "CookieToStoreTest", - "request":{ - "endpoint": "whatever", - "method": "GET" - }, - "response":{ - "body": { - "status": "done" - } - }, - "store_response_gjson": { - "sess_cookie": "cookie.sess" + testManifest := []byte(`{ + "name": "CookieToStoreTest", + "request": { + "endpoint": "whatever", + "method": "GET" + }, + "response": { + "body": { + "status": "done" } - } -`) + }, + "store_response_gjson": { + "sess_cookie": "cookie.sess" + } + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() r.Root().NoLogTime = true var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -529,15 +1078,13 @@ func TestCookieSetInDatastore(t *testing.T) { } ckData, err := test.dataStore.Get("sess_cookie") - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) var ck http.Cookie - ckBytes, err := json.Marshal(ckData) + ckBytes, err := jsutil.Marshal(ckData) if err != nil { t.Fatalf("Error marshalling Cookie raw object: %v\n%s", ckData, err.Error()) } - err = json.Unmarshal(ckBytes, &ck) + err = jsutil.Unmarshal(ckBytes, &ck) if err != nil { t.Fatalf("Error unmarshalling into Cookie object: %v\n%s", ckData, err.Error()) } @@ -550,69 +1097,86 @@ func TestCookiesReceivedFromRequest(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ck, err := r.Cookie("sess") if err != nil { - fmt.Fprintf(w, `{"status": "error", "error": %q}`, err) + fmt.Fprintf(w, `{ + "status": "error", + "error": %q + }`, err) return } if ck == nil { - fmt.Fprint(w, `{"status": "error", "error": "empty sess cookie"}`) + fmt.Fprint(w, `{ + "error": "empty sess cookie", + "status": "error" + }`) return } if ck.Value != "you_session_data" { - fmt.Fprint(w, `{"status": "error", "error": "invalid sess cookie value"}`) + fmt.Fprint(w, `{ + "error": "invalid sess cookie value", + "status": "error" + }`) return } ck2, err := r.Cookie("sess2") if err != nil { - fmt.Fprintf(w, `{"status": "error", "error": %q}`, err) + fmt.Fprintf(w, `{ + "status": "error", + "error": %q + }`, err) return } if ck2 == nil { - fmt.Fprint(w, `{"status": "error", "error": "sess2 empty cookie"}`) + fmt.Fprint(w, `{ + "error": "sess2 empty cookie", + "status": "error" + }`) return } if ck2.Value != "yet_another_sess" { - fmt.Fprint(w, `{"status": "error", "error": "invalid sess2 cookie value"}`) + fmt.Fprint(w, `{ + "error": "invalid sess2 cookie value", + "status": "error" + }`) return } - fmt.Fprint(w, `{"status": "done"}`) + fmt.Fprint(w, `{ + "status": "done" + }`) })) defer ts.Close() - testManifest := []byte(` - { - "name": "CookiesReceivedTest", - "request":{ - "endpoint": "whatever", - "method": "GET", - "cookies": { - "sess": { - "value_from_store": "sess_cookie" - }, - "sess2": { - "value_from_store": "?sess2_cookie", - "value": "yet_another_sess" - } + testManifest := []byte(`{ + "name": "CookiesReceivedTest", + "request": { + "cookies": { + "sess": { + "value_from_store": "sess_cookie" + }, + "sess2": { + "value": "yet_another_sess", + "value_from_store": "?sess2_cookie" } }, - "response":{ - "body": { - "status": "done" - } + "endpoint": "whatever", + "method": "GET" + }, + "response": { + "body": { + "status": "done" } - } -`) + } + }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + err := afero.WriteFile(filesystem.Fs, "manifest.json", testManifest, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) r := report.NewReport() r.Root().NoLogTime = true var test Case - err := json.Unmarshal(testManifest, &test) - if err != nil { - t.Fatal(err) - } + err = jsutil.Unmarshal(testManifest, &test) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test.ServerURL = ts.URL test.dataStore = datastore.NewStore(false) @@ -628,3 +1192,10 @@ func TestCookiesReceivedFromRequest(t *testing.T) { t.Fatalf("Did fail but it should not") } } + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" + } + return err.Error() +} diff --git a/api_testsuite.go b/api_testsuite.go index 05b7109..f3e989d 100644 --- a/api_testsuite.go +++ b/api_testsuite.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "fmt" "io" "net/http" @@ -21,6 +20,7 @@ import ( "github.com/programmfabrik/apitest/internal/smtp" "github.com/programmfabrik/apitest/pkg/lib/datastore" "github.com/programmfabrik/apitest/pkg/lib/filesystem" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/apitest/pkg/lib/report" "github.com/programmfabrik/apitest/pkg/lib/template" "github.com/programmfabrik/apitest/pkg/lib/util" @@ -106,7 +106,7 @@ func newTestSuite( return &suitePreload, err } - err = util.Unmarshal(manifest, &suitePreload) + err = jsutil.Unmarshal(manifest, &suitePreload) if err != nil { err = fmt.Errorf("unmarshaling manifest %q: %w", manifestPath, err) suitePreload.reporterRoot.Failure = err.Error() @@ -142,7 +142,7 @@ func newTestSuite( } // fmt.Printf(%q, string(manifest)) // We unmarshall the final manifest into the final working suite - err = util.Unmarshal(manifest, &suite) + err = jsutil.Unmarshal(manifest, &suite) if err != nil { err = fmt.Errorf("unmarshaling manifest %q: %w", manifestPath, err) suite.reporterRoot.Failure = err.Error() @@ -151,7 +151,7 @@ func newTestSuite( suite.httpServerHost = suitePreload.httpServerHost suite.loader = suitePreload.loader - //Append suite manifest path to name, so we know in an automatic setup where the test is loaded from + // Append suite manifest path to name, so we know in an automatic setup where the test is loaded from suite.Name = fmt.Sprintf("%s (%s)", suite.Name, manifestPath) // Parse serverURL @@ -247,7 +247,7 @@ func (ats *Suite) run() bool { } type testContainer struct { - CaseByte json.RawMessage + CaseByte []byte Path string } @@ -268,7 +268,10 @@ func (ats *Suite) buildLoader(rootLoader template.Loader, parallelRunIdx int) te } func (ats *Suite) parseAndRunTest( - v any, testFilePath string, r *report.ReportElement, rootLoader template.Loader, + v any, + testFilePath string, + r *report.ReportElement, + rootLoader template.Loader, allowParallelExec bool, ) bool { parallelRuns := 1 @@ -310,9 +313,13 @@ func (ats *Suite) parseAndRunTest( } func (ats *Suite) testGoroutine( - waitGroup *sync.WaitGroup, successCount *atomic.Uint32, - testFilePath string, r *report.ReportElement, rootLoader template.Loader, - runIdx int, testRaw json.RawMessage, + waitGroup *sync.WaitGroup, + successCount *atomic.Uint32, + testFilePath string, + r *report.ReportElement, + rootLoader template.Loader, + runIdx int, + testRaw jsutil.RawMessage, ) { defer waitGroup.Done() @@ -332,12 +339,12 @@ func (ats *Suite) testGoroutine( } // Build list of test cases - var testCases []json.RawMessage - err = util.Unmarshal(testRendered, &testCases) + var testCases []jsutil.RawMessage + err = jsutil.Unmarshal(testRendered, &testCases) if err != nil { // Input could not be deserialized into list, try to deserialize into single object - var singleTest json.RawMessage - err = util.Unmarshal(testRendered, &singleTest) + var singleTest jsutil.RawMessage + err = jsutil.Unmarshal(testRendered, &singleTest) if err != nil { // Malformed json r.SaveToReportLog(err.Error()) @@ -347,7 +354,7 @@ func (ats *Suite) testGoroutine( return } - testCases = []json.RawMessage{singleTest} + testCases = []jsutil.RawMessage{singleTest} } for testIdx, testCase := range testCases { @@ -356,7 +363,7 @@ func (ats *Suite) testGoroutine( // If testCase can be unmarshalled as string, we may have a // reference to another test using @ notation at hand var testCaseStr string - err = util.Unmarshal(testCase, &testCaseStr) + err = jsutil.Unmarshal(testCase, &testCaseStr) if err == nil && util.IsPathSpec(testCaseStr) { // Recurse if the testCase points to another file using @ notation success = ats.parseAndRunTest( @@ -399,7 +406,7 @@ func (ats *Suite) runLiteralTest( r.SetName(testFilePath) var test Case - err := util.Unmarshal(tc.CaseByte, &test) + err := jsutil.Unmarshal(tc.CaseByte, &test) if err != nil { r.SaveToReportLog(err.Error()) logrus.Error(fmt.Errorf("can not unmarshal single test (%s): %w", testFilePath, err)) @@ -435,9 +442,8 @@ func (ats *Suite) runLiteralTest( return true } -func (ats *Suite) loadManifest() (b []byte, err error) { +func (ats *Suite) loadManifest() (manifest []byte, err error) { var ( - res []byte loader template.Loader serverURL *url.URL manifestFile afero.File @@ -458,17 +464,20 @@ func (ats *Suite) loadManifest() (b []byte, err error) { manifestFile, err = filesystem.Fs.Open(ats.manifestPath) if err != nil { - return res, fmt.Errorf("opening manifestPath (%s): %w", ats.manifestPath, err) + return nil, fmt.Errorf("opening manifestPath (%s): %w", ats.manifestPath, err) } defer manifestFile.Close() manifestTmpl, err := io.ReadAll(manifestFile) if err != nil { - return res, fmt.Errorf("loading manifest (%s): %w", ats.manifestPath, err) + return nil, fmt.Errorf("loading manifest (%s): %w", ats.manifestPath, err) } - b, err = loader.Render(manifestTmpl, ats.manifestDir, nil) - ats.loader = loader + manifest, err = loader.Render(manifestTmpl, ats.manifestDir, nil) + if err != nil { + return nil, fmt.Errorf("loading manifest (%s): %w", ats.manifestPath, err) + } - return b, err + ats.loader = loader + return manifest, nil } diff --git a/api_testsuite_test.go b/api_testsuite_test.go index 5fcbf8e..058cec9 100644 --- a/api_testsuite_test.go +++ b/api_testsuite_test.go @@ -4,22 +4,22 @@ import ( "testing" "github.com/programmfabrik/apitest/pkg/lib/filesystem" + go_test_utils "github.com/programmfabrik/go-test-utils" "github.com/spf13/afero" ) func TestLoadManifest(t *testing.T) { filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "externalFile", []byte(`{"load":{"me":"loaded"}}`), 0644) - - afero.WriteFile(filesystem.Fs, "testManifest.json", []byte(`{"testload": {{ file "externalFile" | gjson "load.me"}}}`), 0644) + err := afero.WriteFile(filesystem.Fs, "externalFile", []byte(`{"load":{"me":"loaded"}}`), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "testManifest.json", []byte(`{"testload": {{ file "externalFile" | gjson "load.me"}}}`), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) s := Suite{manifestPath: "testManifest.json"} res, err := s.loadManifest() - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if string(res) != `{"testload": "loaded"}` { t.Errorf(`Exp '{"testload": "loaded"}' != '%s' Got`, res) @@ -29,17 +29,16 @@ func TestLoadManifest(t *testing.T) { func TestLoadManifestCustomDelimiters(t *testing.T) { filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "externalFile", []byte(`{"load":{"me":"loaded"}}`), 0644) - + err := afero.WriteFile(filesystem.Fs, "externalFile", []byte(`{"load":{"me":"loaded"}}`), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) afero.WriteFile(filesystem.Fs, "testManifest.json", []byte(`// template-delims: ## ## - // template-remove-tokens: "" "...." + // template-remove-tokens: "" "...." {"testload": ## file "externalFile" | gjson "load.me" ##}"...."`), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) s := Suite{manifestPath: "testManifest.json"} res, err := s.loadManifest() - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if string(res) != ` diff --git a/config.go b/config.go index f386185..ffb00ed 100644 --- a/config.go +++ b/config.go @@ -48,7 +48,8 @@ func loadConfig(cfgFile string) { logrus.Infof("No config file provided (will only use command line parameters)") } - if err := viper.ReadInConfig(); err != nil { + err := viper.ReadInConfig() + if err != nil { logrus.Infof("No config %q read (will only use command line parameters): %s", cfgFile, err.Error()) } @@ -99,7 +100,7 @@ func (config *testToolConfig) extractTestDirectories() (err error) { // logrus.Infof("Skipping: %s", path) return filepath.SkipDir } - //Skip directories not containing a manifest + // Skip directories not containing a manifest _, err2 = filesystem.Fs.Stat(filepath.Join(path, "manifest.json")) if err2 != nil { return nil diff --git a/config_test.go b/config_test.go index 66c4b05..69f1ac8 100644 --- a/config_test.go +++ b/config_test.go @@ -22,7 +22,7 @@ var ( ) func SetupFS() { - //Setup testserver + // Setup testserver server = go_test_utils.NewTestServer(go_test_utils.Routes{ "/api/v1/session": func(w *http.ResponseWriter, r *http.Request) { (*w).Write([]byte("{\"token\": \"mock\"}")) @@ -32,7 +32,7 @@ func SetupFS() { }, }) - //Setup test filesystem + // Setup test filesystem filesystem.Fs = afero.NewMemMapFs() filesystem.Fs.MkdirAll(filepath.Dir(manifestPath1), 0755) filesystem.Fs.MkdirAll(filepath.Dir(manifestPath2), 0755) @@ -52,13 +52,13 @@ func SetupFS() { func TestTestToolConfig_ExtractTestDirectories(t *testing.T) { SetupFS() - //Invalid rootDirectory -> Expect error + // Invalid rootDirectory -> Expect error _, err := newTestToolConfig(server.URL+"/api/v1", []string{"invalid"}, false, false, false) go_test_utils.ExpectError(t, err, "NewTestToolConfig did not fail on invalid root directory") - //Invalid rootDirectory -> Expect error + // Invalid rootDirectory -> Expect error conf, err := newTestToolConfig(server.URL+"/api/v1", []string{"path"}, false, false, false) - go_test_utils.ExpectNoError(t, err, "NewTestToolConfig did fail on valid root directory") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) expectedResults := []string{ filepath.Dir(manifestPath1), diff --git a/http_server.go b/http_server.go index aac8ffa..2a3379d 100644 --- a/http_server.go +++ b/http_server.go @@ -3,7 +3,6 @@ package main import ( "bytes" "context" - "encoding/json" "io" "net/http" "net/url" @@ -12,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/programmfabrik/apitest/internal/httpproxy" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/apitest/pkg/lib/util" "github.com/programmfabrik/golib" "github.com/sirupsen/logrus" @@ -176,7 +176,7 @@ func bounceJSON(w http.ResponseWriter, r *http.Request) { QueryParams: r.URL.Query(), } if len(bodyBytes) > 0 { - err = json.Unmarshal(bodyBytes, &bodyJSON) + err = jsutil.Unmarshal(bodyBytes, &bodyJSON) if err != nil { formatErrorResponse(w, 500, err, errorBody) return diff --git a/internal/handlerutil/util.go b/internal/handlerutil/util.go index a1bacd7..76eacee 100644 --- a/internal/handlerutil/util.go +++ b/internal/handlerutil/util.go @@ -1,9 +1,9 @@ package handlerutil import ( - "encoding/json" "net/http" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/sirupsen/logrus" ) @@ -33,7 +33,7 @@ func RespondWithJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - err := json.NewEncoder(w).Encode(v) + err := jsutil.Encode(w, v) if err != nil { logrus.Errorf("Could not encode JSON response: %s (%v)", err, v) } diff --git a/internal/httpproxy/store.go b/internal/httpproxy/store.go index a38d30c..b932fad 100644 --- a/internal/httpproxy/store.go +++ b/internal/httpproxy/store.go @@ -1,7 +1,6 @@ package httpproxy import ( - "encoding/json" "fmt" "io" "net/http" @@ -9,6 +8,7 @@ import ( "strconv" "github.com/programmfabrik/apitest/internal/handlerutil" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" ) // mode definition @@ -65,7 +65,7 @@ func (st *store) write(w http.ResponseWriter, r *http.Request) { st.Data = append(st.Data, storeEntry{offset, reqData}) - err = json.NewEncoder(w).Encode(struct { + err = jsutil.Encode(w, struct { Offset int `json:"offset"` }{offset}) if err != nil { diff --git a/internal/smtp/http.go b/internal/smtp/http.go index 9d81991..462ec85 100644 --- a/internal/smtp/http.go +++ b/internal/smtp/http.go @@ -3,7 +3,6 @@ package smtp import ( "bytes" _ "embed" - "encoding/json" "fmt" "html/template" "io" @@ -19,6 +18,7 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/programmfabrik/apitest/internal/handlerutil" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/golib" "github.com/sirupsen/logrus" ) @@ -253,9 +253,10 @@ func (h *smtpHTTPHandler) handleMessageIndex(w http.ResponseWriter, r *http.Requ messagesOut = append(messagesOut, buildMessageBasicMeta(msg)) } - out := make(map[string]any) - out["count"] = len(receivedMessages) - out["messages"] = messagesOut + out := map[string]any{ + "count": len(receivedMessages), + "messages": messagesOut, + } handlerutil.RespondWithJSON(w, http.StatusOK, out) } @@ -380,24 +381,22 @@ func buildContentMeta(c *receivedContent) (out map[string]any) { contentTypeParams = make(map[string]string) } + headers := make(map[string]any) + for k, v := range c.Headers() { + headers[k] = v + } + out = map[string]any{ "bodySize": len(c.Body()), "isMultipart": c.IsMultipart(), "contentType": c.ContentType(), "contentTypeParams": contentTypeParams, + "headers": headers, } - headers := make(map[string]any) - for k, v := range c.Headers() { - headers[k] = v - } - out["headers"] = headers - if c.IsMultipart() { multipartIndex := buildMultipartIndex(c.Multiparts()) - for k, v := range multipartIndex { - out[k] = v - } + maps.Copy(out, multipartIndex) } return out @@ -436,9 +435,7 @@ func buildMessageFullMeta(msg *receivedMessage) (out map[string]any) { out = buildMessageBasicMeta(msg) contentMeta := buildContentMeta(msg.Content()) - for k, v := range contentMeta { - out[k] = v - } + maps.Copy(out, contentMeta) return out } @@ -450,9 +447,10 @@ func buildMultipartIndex(parts []*receivedPart) (out map[string]any) { multipartsOut[i] = buildMultipartMeta(part) } - out = make(map[string]any) - out["multipartsCount"] = len(parts) - out["multiparts"] = multipartsOut + out = map[string]any{ + "multipartsCount": len(parts), + "multiparts": multipartsOut, + } return out } @@ -484,7 +482,7 @@ func extractSearchRegexes(qp url.Values, paramName string) (rgs []*regexp.Regexp sp := []string{} for _, v := range qp[paramName] { var searchParams []string - err := json.Unmarshal([]byte(v), &searchParams) + err := jsutil.UnmarshalString(v, &searchParams) if err == nil { sp = append(sp, searchParams...) } else { diff --git a/internal/smtp/smtp_test.go b/internal/smtp/smtp_test.go index b7c1654..1ec4708 100644 --- a/internal/smtp/smtp_test.go +++ b/internal/smtp/smtp_test.go @@ -319,8 +319,18 @@ func runTestSession() *Server { server := NewServer(addr, 0) server.clock = func() time.Time { return testTime } - go server.ListenAndServe() - defer server.Shutdown(context.Background()) + go func() { + err := server.ListenAndServe() + if err != nil { + panic(err) + } + }() + defer func() { + err := server.Shutdown(context.Background()) + if err != nil { + panic(err) + } + }() // give the server some time to open time.Sleep(time.Second) diff --git a/main.go b/main.go index a96ce24..3fc8bc0 100644 --- a/main.go +++ b/main.go @@ -142,12 +142,14 @@ func runApiTests(cmd *cobra.Command, args []string) { // Check if paths are valid for _, rootDirectory := range rootDirectorys { - if _, err := os.Stat(rootDirectory); rootDirectory != "." && os.IsNotExist(err) { + _, err := os.Stat(rootDirectory) + if rootDirectory != "." && os.IsNotExist(err) { logrus.Fatalf("The path '%s' for the test folders is not valid", rootDirectory) } } for _, singleTest := range singleTests { - if _, err := os.Stat(singleTest); singleTest != "" && os.IsNotExist(err) { + _, err := os.Stat(singleTest) + if singleTest != "" && os.IsNotExist(err) { logrus.Fatalf("The path '%s' for the single test is not valid", singleTest) } } diff --git a/pkg/lib/api/build_policies.go b/pkg/lib/api/build_policies.go index 40aaf5c..23fe8ed 100644 --- a/pkg/lib/api/build_policies.go +++ b/pkg/lib/api/build_policies.go @@ -2,7 +2,6 @@ package api import ( "bytes" - "encoding/json" "fmt" "io" "mime/multipart" @@ -10,6 +9,7 @@ import ( "strings" "github.com/pkg/errors" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/apitest/pkg/lib/util" ) @@ -22,7 +22,7 @@ func buildMultipart(request Request) (additionalHeaders map[string]string, body var replaceFilename *string val, ok := request.Body.(map[string]any)["file:filename"] if ok { - f, ok := val.(util.JsonString) + f, ok := val.(jsutil.String) if !ok { return nil, nil, fmt.Errorf("file:filename should be a string") } @@ -36,7 +36,7 @@ func buildMultipart(request Request) (additionalHeaders map[string]string, body return nil } - rawPathSpec, ok := val.(util.JsonString) + rawPathSpec, ok := val.(jsutil.String) if !ok { return fmt.Errorf("pathSpec should be a string") } @@ -60,7 +60,8 @@ func buildMultipart(request Request) (additionalHeaders map[string]string, body if err != nil { return err } - if _, err := io.Copy(part, file); err != nil { + _, err = io.Copy(part, file) + if err != nil { return err } @@ -106,7 +107,7 @@ func buildRegular(request Request) (additionalHeaders map[string]string, body io if request.Body == nil { body = bytes.NewBuffer([]byte{}) } else { - bodyBytes, err := json.Marshal(request.Body) + bodyBytes, err := jsutil.Marshal(request.Body) if err != nil { return nil, nil, fmt.Errorf("marshaling request body: %w", err) } diff --git a/pkg/lib/api/build_policies_test.go b/pkg/lib/api/build_policies_test.go index 3384178..b3ab425 100644 --- a/pkg/lib/api/build_policies_test.go +++ b/pkg/lib/api/build_policies_test.go @@ -16,8 +16,8 @@ func TestBuildMultipart(t *testing.T) { assertContent := "mock" assertFilename := "mockfile.json" filesystem.Fs = afero.NewMemMapFs() - filesystem.Fs.MkdirAll("test/path", 0755) - afero.WriteFile(filesystem.Fs, fmt.Sprintf("test/%s", assertFilename), []byte(assertContent), 0644) + _ = filesystem.Fs.MkdirAll("test/path", 0755) + _ = afero.WriteFile(filesystem.Fs, fmt.Sprintf("test/%s", assertFilename), []byte(assertContent), 0644) testRequest := Request{ Body: map[string]any{ @@ -28,16 +28,17 @@ func TestBuildMultipart(t *testing.T) { } httpRequest, err := testRequest.buildHttpRequest() - go_test_utils.ExpectNoError(t, err, "error building multipart request") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) testReader, err := httpRequest.MultipartReader() - go_test_utils.ExpectNoError(t, err, "error getting multipart reader from request") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) part, err := testReader.NextPart() - go_test_utils.ExpectNoError(t, err, "error reading part from multipart reader") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertStringEquals(t, part.FileName(), assertFilename) buf := new(bytes.Buffer) - buf.ReadFrom(part) + _, err = buf.ReadFrom(part) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertStringEquals(t, assertContent, buf.String()) } @@ -50,9 +51,7 @@ func TestBuildMultipart_ErrPathSpec(t *testing.T) { } _, _, err := buildMultipart(testRequest) - if err == nil { - t.Fatal("expected error") - } + go_test_utils.ExpectError(t, err, "expected error") if !strings.Contains(err.Error(), "pathSpec noPathspec is not valid") { t.Error("expected error because of invalid pathspec") } @@ -67,9 +66,7 @@ func TestBuildMultipart_ErrPathSpecNoString(t *testing.T) { } _, _, err := buildMultipart(testRequest) - if err == nil { - t.Fatal("expected error") - } + go_test_utils.ExpectError(t, err, "expected error") if !strings.Contains(err.Error(), "pathSpec should be a string") { t.Error("expected error because of invalid type for pathSpec") } @@ -84,10 +81,15 @@ func TestBuildMultipart_FileDoesNotExist(t *testing.T) { } _, _, err := buildMultipart(testRequest) - if err == nil { - t.Fatal("expected error") - } + go_test_utils.ExpectError(t, err, "expected error") if !strings.Contains(err.Error(), "does_not_exist.json: file does not exist") { t.Errorf("expected error because file does not exist") } } + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" + } + return err.Error() +} diff --git a/pkg/lib/api/pre_process.go b/pkg/lib/api/pre_process.go index 8a92544..0e5bad9 100644 --- a/pkg/lib/api/pre_process.go +++ b/pkg/lib/api/pre_process.go @@ -2,21 +2,21 @@ package api import ( "bytes" - "encoding/json" "fmt" "os/exec" "strconv" "strings" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/golib" ) type cmdOutputType string const ( - CmdOutputStdout cmdOutputType = "stdout" - CmdOutputStderr cmdOutputType = "stderr" - CmdOutputExitCode cmdOutputType = "exitcode" + cmdOutputStdout cmdOutputType = "stdout" + cmdOutputStderr cmdOutputType = "stderr" + cmdOutputExitCode cmdOutputType = "exitcode" ) type preProcess struct { @@ -48,7 +48,7 @@ func (proc *preProcess) runPreProcess(response Response) (resp Response, err err } if proc.Cmd.Output == "" { - proc.Cmd.Output = CmdOutputStdout + proc.Cmd.Output = cmdOutputStdout } bodyReader = bytes.NewReader(response.Body) @@ -71,11 +71,11 @@ func (proc *preProcess) runPreProcess(response Response) (resp Response, err err } switch proc.Cmd.Output { - case CmdOutputExitCode: + case cmdOutputExitCode: response.Body = []byte(strconv.Itoa(exitCode)) - case CmdOutputStderr: + case cmdOutputStderr: response.Body = stderrBytes - case CmdOutputStdout: + case cmdOutputStdout: response.Body, err2 = golib.JsonBytesIndent(preProcessError{ Command: strings.Join(cmd.Args, " "), Error: err.Error(), @@ -88,11 +88,11 @@ func (proc *preProcess) runPreProcess(response Response) (resp Response, err err } switch proc.Cmd.Output { - case CmdOutputExitCode: + case cmdOutputExitCode: response.Body = []byte(strconv.Itoa(cmd.ProcessState.ExitCode())) - case CmdOutputStderr: + case cmdOutputStderr: response.Body = stderr.Bytes() - case CmdOutputStdout: + case cmdOutputStdout: response.Body = stdout.Bytes() } @@ -109,7 +109,7 @@ func ensureJson(data []byte) (dataFixed []byte) { dataFixed = data - parseErr = json.Unmarshal(data, &v) + parseErr = jsutil.Unmarshal(data, &v) if parseErr != nil { dataFixed, _ = golib.JsonBytes(string(data)) } diff --git a/pkg/lib/api/request.go b/pkg/lib/api/request.go index f690544..f55562e 100755 --- a/pkg/lib/api/request.go +++ b/pkg/lib/api/request.go @@ -2,7 +2,6 @@ package api import ( "crypto/tls" - "encoding/json" "fmt" "io" "net/http" @@ -15,6 +14,7 @@ import ( "github.com/moul/http2curl" "github.com/pkg/errors" "github.com/programmfabrik/apitest/pkg/lib/datastore" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/apitest/pkg/lib/util" "github.com/programmfabrik/golib" ) @@ -63,7 +63,7 @@ type Request struct { func (request Request) buildHttpRequest() (req *http.Request, err error) { if request.buildPolicy == nil { - //Set Build policy + // Set Build policy switch request.BodyType { case "multipart": request.buildPolicy = buildMultipart @@ -75,7 +75,7 @@ func (request Request) buildHttpRequest() (req *http.Request, err error) { request.buildPolicy = buildRegular } } - //Render Request Url + // Render Request Url requestUrl := fmt.Sprintf("%s/%s", request.ServerURL, request.Endpoint) if request.Endpoint == "" { @@ -237,11 +237,11 @@ func (request Request) buildHttpRequest() (req *http.Request, err error) { if len(storeKey) > 0 && request.DataStore != nil { cookieInt, err := request.DataStore.Get(storeKey) if err == nil && cookieInt != "" { - ckBytes, err := json.Marshal(cookieInt) + ckBytes, err := jsutil.Marshal(cookieInt) if err != nil { return nil, fmt.Errorf("could not marshal cookie %q from Datastore", storeKey) } - err = json.Unmarshal(ckBytes, &ck) + err = jsutil.Unmarshal(ckBytes, &ck) if err != nil { return nil, fmt.Errorf("could not unmarshal cookie %q from Datastore (%q): %w", storeKey, string(ckBytes), err) } @@ -313,7 +313,7 @@ func (request Request) ToString(curl bool) (res string) { rep := "" for key, val := range request.Body.(map[string]any) { - pathSpec, ok := val.(util.JsonString) + pathSpec, ok := val.(jsutil.String) if !ok { panic(errors.New("pathSpec should be a string")) } diff --git a/pkg/lib/api/request_test.go b/pkg/lib/api/request_test.go index faa109d..998b80e 100755 --- a/pkg/lib/api/request_test.go +++ b/pkg/lib/api/request_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/programmfabrik/apitest/pkg/lib/datastore" - "github.com/programmfabrik/apitest/pkg/lib/util" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" go_test_utils "github.com/programmfabrik/go-test-utils" ) @@ -48,7 +48,7 @@ func TestBuildCurl(t *testing.T) { "query_param": "value", }, ServerURL: "https://serverUrl", - Body: util.JsonObject{ + Body: jsutil.Object{ "hey": 1, }, } @@ -90,7 +90,10 @@ func TestRequestBuildHttpWithCookie(t *testing.T) { request.buildPolicy = buildRegular ds := datastore.NewStore(false) for key, val := range storeCookies { - ds.Set(key, val) + err := ds.Set(key, val) + if err != nil { + t.Fatalf("Could not store cookie in datastore: %s", err.Error()) + } } request.DataStore = ds httpRequest, err := request.buildHttpRequest() diff --git a/pkg/lib/api/response.go b/pkg/lib/api/response.go index c7d2fe2..1ddf84f 100755 --- a/pkg/lib/api/response.go +++ b/pkg/lib/api/response.go @@ -5,7 +5,6 @@ import ( "bytes" "crypto/md5" "encoding/hex" - "encoding/json" "fmt" "io" "net/http" @@ -15,6 +14,7 @@ import ( "unicode/utf8" "github.com/programmfabrik/apitest/pkg/lib/csv" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/apitest/pkg/lib/util" "github.com/programmfabrik/golib" ) @@ -25,7 +25,7 @@ type Response struct { HeaderFlat map[string]any // ":control" is an object, so we must use "any" here Cookies []*http.Cookie Body []byte - BodyControl util.JsonObject + BodyControl jsutil.Object Format ResponseFormat ReqDur time.Duration @@ -58,7 +58,7 @@ type ResponseSerialization struct { Headers map[string]any `yaml:"header" json:"header,omitempty"` Cookies map[string]cookie `yaml:"cookie" json:"cookie,omitempty"` Body any `yaml:"body" json:"body,omitempty"` - BodyControl util.JsonObject `yaml:"body:control" json:"body:control,omitempty"` + BodyControl jsutil.Object `yaml:"body:control" json:"body:control,omitempty"` Format ResponseFormat `yaml:"format" json:"format,omitempty"` } @@ -75,6 +75,17 @@ type responseFormatXLSX struct { SheetIdx int `json:"sheet_idx,omitempty"` } +const ( + responseTypeXml string = "xml" + responseTypeXml2 string = "xml2" + responseTypeHtml string = "html" + responseTypeXhtml string = "xhtml" + responseTypeXlsx string = "xlsx" + responseTypeCsv string = "csv" + responseTypeBinary string = "binary" + responseTypeText string = "text" +) + type ResponseFormat struct { IgnoreBody bool `json:"-"` // if true, do not try to parse the body (since it is not expected in the response) Type string `json:"type"` // default "json", allowed: "csv", "json", "xml", "xml2", "html", "xhtml", "binary", "text", "xlsx" @@ -87,7 +98,7 @@ func NewResponse(statusCode *int, headersAny map[string]any, cookies []*http.Cookie, body io.Reader, - bodyControl util.JsonObject, + bodyControl jsutil.Object, bodyFormat ResponseFormat, ) (res Response, err error) { @@ -111,7 +122,7 @@ func NewResponse(statusCode *int, return res, fmt.Errorf("unknown type %T in header %q", v2, key) } } - headers[key] = v + headers[key] = headerS continue case []string: headers[key] = v @@ -148,7 +159,7 @@ func NewResponse(statusCode *int, func NewResponseFromSpec(spec ResponseSerialization) (res Response, err error) { var body io.Reader if spec.Body != nil { - bodyBytes, err := json.Marshal(spec.Body) + bodyBytes, err := jsutil.Marshal(spec.Body) if err != nil { return res, err } @@ -178,8 +189,8 @@ func NewResponseFromSpec(spec ResponseSerialization) (res Response, err error) { } // splitLines is a helper function needed for format "text" -func splitLines(s string) (lines util.JsonArray) { - lines = util.JsonArray{} +func splitLines(s string) (lines jsutil.Array) { + lines = jsutil.Array{} sc := bufio.NewScanner(strings.NewReader(s)) for sc.Scan() { lines = append(lines, sc.Text()) @@ -214,27 +225,27 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm } switch responseFormat.Type { - case "xml", "xml2": + case responseTypeXml, responseTypeXml2: bodyData, err = util.Xml2Json(resp.Body, responseFormat.Type) if err != nil { return res, fmt.Errorf("could not marshal xml to json: %w", err) } - case "html": + case responseTypeHtml: bodyData, err = util.Html2Json(resp.Body) if err != nil { return res, fmt.Errorf("could not marshal html to json: %w", err) } - case "xhtml": + case responseTypeXhtml: bodyData, err = util.Xhtml2Json(resp.Body) if err != nil { return res, fmt.Errorf("could not marshal xhtml to json: %w", err) } - case "xlsx": + case responseTypeXlsx: bodyData, err = util.Xlsx2Json(resp.Body, responseFormat.XLSX.SheetIdx) if err != nil { return res, fmt.Errorf("could not marshal xlsx to json: %w", err) } - case "csv": + case responseTypeCsv: runeComma := ',' if responseFormat.CSV.Comma != "" { runeComma = []rune(responseFormat.CSV.Comma)[0] @@ -245,28 +256,28 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm return res, fmt.Errorf("could not parse csv: %w", err) } - bodyData, err = json.Marshal(csvData) + bodyData, err = jsutil.Marshal(csvData) if err != nil { return res, fmt.Errorf("could not marshal csv to json: %w", err) } - case "binary": + case responseTypeBinary: // We have another file format (binary). We thereby take the md5 Hash of the body and compare that one hasher := md5.New() hasher.Write([]byte(resp.Body)) - jsonObject := util.JsonObject{ - "md5sum": util.JsonString(hex.EncodeToString(hasher.Sum(nil))), + jsonObject := jsutil.Object{ + "md5sum": jsutil.String(hex.EncodeToString(hasher.Sum(nil))), } - bodyData, err = json.Marshal(jsonObject) + bodyData, err = jsutil.Marshal(jsonObject) if err != nil { return res, fmt.Errorf("could not marshal body with md5sum to json: %w", err) } - case "text": + case responseTypeText: // render the content as text bodyText := string(resp.Body) bodyTextTrimmed := strings.TrimSpace(bodyText) - jsonObject := util.JsonObject{ - "text": util.JsonString(bodyText), - "text_trimmed": util.JsonString(bodyTextTrimmed), + jsonObject := jsutil.Object{ + "text": jsutil.String(bodyText), + "text_trimmed": jsutil.String(bodyTextTrimmed), "lines": splitLines(bodyText), "float64": nil, "int64": nil, @@ -275,11 +286,11 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm // ignore errors silently in case the text is not numerical n, err2 := strconv.ParseFloat(bodyTextTrimmed, 64) if err2 == nil { - jsonObject["float64"] = util.JsonNumber(n) - jsonObject["int64"] = util.JsonNumber(int64(n)) + jsonObject["float64"] = n + jsonObject["int64"] = int64(n) } - bodyData, err = json.Marshal(jsonObject) + bodyData, err = jsutil.Marshal(jsonObject) if err != nil { return res, fmt.Errorf("could not marshal body to text (string): %w", err) } @@ -330,7 +341,7 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm if !responseFormat.IgnoreBody { if len(bodyData) > 0 { - err = json.Unmarshal(bodyData, &bodyJSON) + err = jsutil.Unmarshal(bodyData, &bodyJSON) if err != nil { return res, err } @@ -343,12 +354,12 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm responseJSON.Body = &bodyJSON } - responseBytes, err = json.Marshal(responseJSON) + responseBytes, err = jsutil.Marshal(responseJSON) if err != nil { return res, err } - err = json.Unmarshal(responseBytes, &res) + err = jsutil.Unmarshal(responseBytes, &res) if err != nil { return res, err } @@ -366,7 +377,7 @@ func (response Response) ToGenericJSON() (res any, err error) { // We have a json, and thereby try to unmarshal it into our body if len(response.Body) > 0 { - err = json.Unmarshal(response.Body, &bodyJSON) + err = jsutil.Unmarshal(response.Body, &bodyJSON) if err != nil { return res, err } @@ -406,11 +417,11 @@ func (response Response) ToGenericJSON() (res any, err error) { responseJSON.Body = &bodyJSON } - responseBytes, err = json.Marshal(responseJSON) + responseBytes, err = jsutil.Marshal(responseJSON) if err != nil { return res, err } - err = json.Unmarshal(responseBytes, &res) + err = jsutil.Unmarshal(responseBytes, &res) if err != nil { return res, err } @@ -464,7 +475,14 @@ func (response Response) ToString() (s string) { // for logging, always show the body resp.Format.IgnoreBody = false switch resp.Format.Type { - case "xml", "xml2", "csv", "html", "xhtml", "text", "binary": + case responseTypeXml, + responseTypeXml2, + responseTypeHtml, + responseTypeXhtml, + responseTypeXlsx, + responseTypeCsv, + responseTypeBinary, + responseTypeText: bodyString, err = resp.ServerResponseToJsonString(true) if err != nil { bodyString = "[BINARY DATA NOT DISPLAYED]\n\n" diff --git a/pkg/lib/api/response_test.go b/pkg/lib/api/response_test.go index 39e41f6..97602fe 100644 --- a/pkg/lib/api/response_test.go +++ b/pkg/lib/api/response_test.go @@ -1,13 +1,13 @@ package api import ( - "encoding/json" + "fmt" "net/http" "net/http/httptest" "strings" "testing" - "github.com/programmfabrik/apitest/pkg/lib/util" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" go_test_utils "github.com/programmfabrik/go-test-utils" "github.com/programmfabrik/golib" "github.com/tidwall/gjson" @@ -21,7 +21,7 @@ func TestResponse_ToGenericJson(t *testing.T) { }, } genericJson, err := response.ToGenericJSON() - go_test_utils.ExpectNoError(t, err, "error calling response.ToGenericJson") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) jsonObjResp, ok := genericJson.(map[string]any) if !ok { @@ -31,7 +31,7 @@ func TestResponse_ToGenericJson(t *testing.T) { if !ok { t.Fatalf("responseJsonObj should have status code field") } - if statusCode != float64(200) { + if statusCode != jsutil.Number("200") { t.Errorf("responseJson had wrong statuscode, expected 200, got: %d", statusCode) } jsonHeaders, ok := jsonObjResp["header"] @@ -52,14 +52,14 @@ func TestResponse_NewResponseFromSpec(t *testing.T) { StatusCode: golib.IntRef(200), Headers: map[string]any{ "foo": []string{"bar"}, - "foo2:control": util.JsonObject{ + "foo2:control": jsutil.Object{ "must_not_exist": true, }, }, Body: nil, } response, err := NewResponseFromSpec(responseSpec) - go_test_utils.ExpectNoError(t, err, "unexpected error") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertIntEquals(t, *response.StatusCode, *responseSpec.StatusCode) go_test_utils.AssertStringEquals(t, response.Headers["foo"].([]string)[0], "bar") } @@ -69,12 +69,12 @@ func TestResponse_NewResponseFromSpec_StatusCode_not_set(t *testing.T) { Body: nil, } _, err := NewResponseFromSpec(responseSpec) - go_test_utils.ExpectNoError(t, err, "unexpected error") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) } func TestResponse_NewResponse(t *testing.T) { response, err := NewResponse(golib.IntRef(200), nil, nil, strings.NewReader("foo"), nil, ResponseFormat{}) - go_test_utils.ExpectNoError(t, err, "unexpected error") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertIntEquals(t, *response.StatusCode, 200) } @@ -87,7 +87,7 @@ func TestResponse_String(t *testing.T) { }` response, err := NewResponse(golib.IntRef(200), nil, nil, strings.NewReader(requestString), nil, ResponseFormat{}) - go_test_utils.ExpectNoError(t, err, "error constructing response") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) assertString := "200\n\n\n" + requestString assertString = strings.ReplaceAll(assertString, "\n", "") @@ -110,24 +110,16 @@ func TestResponse_Cookies(t *testing.T) { defer ts.Close() res, err := http.Get(ts.URL) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) defer res.Body.Close() header, err := httpHeaderToMap(res.Header) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) response, err := NewResponse(golib.IntRef(res.StatusCode), header, res.Cookies(), res.Body, nil, ResponseFormat{}) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) jsonStr, err := response.ServerResponseToJsonString(false) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if !gjson.Valid(jsonStr) { t.Fatalf("Invalid serialized JSON: %s", jsonStr) @@ -142,14 +134,10 @@ func TestResponse_Cookies(t *testing.T) { } var ck http.Cookie - vb, err := json.Marshal(v.Value()) - if err != nil { - t.Fatalf("Error marshalling Cookie raw object: %v\n%s", v, err.Error()) - } - err = json.Unmarshal(vb, &ck) - if err != nil { - t.Fatalf("Error unmarshalling into Cookie object: %v\n%s", v, err.Error()) - } + vb, err := jsutil.Marshal(v.Value()) + go_test_utils.ExpectNoError(t, err, fmt.Sprintf("marshalling Cookie raw object: %v", v)) + err = jsutil.Unmarshal(vb, &ck) + go_test_utils.ExpectNoError(t, err, fmt.Sprintf("unmarshalling into Cookie object: %v", v)) go_test_utils.AssertStringEquals(t, ck.Value, "you_session_data") } diff --git a/pkg/lib/compare/comparer.go b/pkg/lib/compare/comparer.go index 9d84f88..027b39c 100644 --- a/pkg/lib/compare/comparer.go +++ b/pkg/lib/compare/comparer.go @@ -1,11 +1,10 @@ package compare import ( - "encoding/json" "fmt" "strings" - "github.com/programmfabrik/apitest/pkg/lib/util" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" ) type CompareResult struct { @@ -28,7 +27,7 @@ func (f compareFailure) Error() string { // jsonNumberEq is comparing ints, floats or strings of the number. It fails to // compare different formats, 1e10 != 10000000000, although it is the same mathematical value. -func jsonNumberEq(numberExp, numberGot json.Number) (eq bool) { +func jsonNumberEq(numberExp, numberGot jsutil.Number) (eq bool) { expInt, expIntErr := numberExp.Int64() gotInt, gotIntErr := numberGot.Int64() @@ -73,7 +72,7 @@ func jsonNumberEq(numberExp, numberGot json.Number) (eq bool) { } func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, err error) { - //left may be nil, because we dont specify the content of the field + // left may be nil, because we dont specify the content of the field if left == nil && right == nil { res := CompareResult{ Equal: true, @@ -94,22 +93,22 @@ func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, e } switch typedLeft := left.(type) { - case json.Number: - typedRight, ok := right.(json.Number) + case float64: + typedRight, ok := right.(float64) if !ok { res := CompareResult{ false, []compareFailure{ { "$", - fmt.Sprintf("expected json.Number, but got %T", right), + fmt.Sprintf("expected float64, but got %T", right), }, }, } return res, nil } - if jsonNumberEq(typedLeft, typedRight) { + if typedLeft == typedRight { res = CompareResult{ Equal: true, } @@ -119,15 +118,15 @@ func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, e Failures: []compareFailure{ { "", - fmt.Sprintf("Got '%s', expected '%s'", typedRight, typedLeft), + fmt.Sprintf("Got %f, expected %f", typedRight, typedLeft), }, }, } } return res, nil - case util.JsonObject: - rightAsObject, ok := right.(util.JsonObject) + case jsutil.Object: + rightAsObject, ok := right.(jsutil.Object) if !ok { res := CompareResult{ false, @@ -142,7 +141,8 @@ func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, e } return ObjectEqualWithControl(typedLeft, rightAsObject, control) - case util.JsonArray: + + case jsutil.Array: /*if len(typedLeft) == 0 { res := CompareResult{ Equal: true, @@ -150,7 +150,7 @@ func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, e return res, nil }*/ - rightAsArray, ok := right.(util.JsonArray) + rightAsArray, ok := right.(jsutil.Array) if !ok { res := CompareResult{ false, @@ -165,8 +165,8 @@ func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, e } return ArrayEqualWithControl(typedLeft, rightAsArray, control) - case util.JsonString: - rightAsString, ok := right.(util.JsonString) + case jsutil.String: + rightAsString, ok := right.(jsutil.String) if !ok { res := CompareResult{ false, @@ -195,19 +195,25 @@ func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, e } } return res, nil - case util.JsonNumber: - rightAsNumber, ok := right.(util.JsonNumber) + + case jsutil.Number: + rightAsNumber, ok := right.(jsutil.Number) if !ok { - res := CompareResult{ - false, - []compareFailure{ - { - "$", - "the actual response is no JsonNumber", + switch v := right.(type) { + case int64, float64: + rightAsNumber = jsutil.Number(fmt.Sprint(v)) + default: + res := CompareResult{ + false, + []compareFailure{ + { + "$", + fmt.Sprintf("the actual response is no JsonNumber, is '%T'", right), + }, }, - }, + } + return res, nil } - return res, nil } if typedLeft == rightAsNumber { res = CompareResult{ @@ -226,8 +232,8 @@ func JsonEqual(left, right any, control ComparisonContext) (res CompareResult, e } return res, nil - case util.JsonBool: - rightAsBool, ok := right.(util.JsonBool) + case jsutil.Bool: + rightAsBool, ok := right.(jsutil.Bool) if !ok { res := CompareResult{ false, diff --git a/pkg/lib/compare/comparer_test.go b/pkg/lib/compare/comparer_test.go index 45194dc..c978248 100644 --- a/pkg/lib/compare/comparer_test.go +++ b/pkg/lib/compare/comparer_test.go @@ -4,7 +4,8 @@ import ( "fmt" "testing" - "github.com/programmfabrik/apitest/pkg/lib/util" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" + go_test_utils "github.com/programmfabrik/go-test-utils" "github.com/stretchr/testify/assert" ) @@ -13,7 +14,7 @@ var trivialComparerTestData = []struct { have string match bool name string - err error + eErr error }{ { `{ @@ -205,7 +206,7 @@ var trivialComparerTestData = []struct { `nil`, false, "string conversion fails", - nil, + fmt.Errorf("[$] the actual response is no JsonString"), }, { `"a"`, @@ -374,19 +375,24 @@ func TestTrivialJsonComparer(t *testing.T) { var json1, json2 any for _, td := range trivialComparerTestData { t.Run(td.name, func(t *testing.T) { - util.Unmarshal([]byte(td.want), &json1) - util.Unmarshal([]byte(td.have), &json2) - tjcMatch, err := JsonEqual(json1, json2, ComparisonContext{}) - if err != nil { - t.Fatal("Error occurred: ", err) + err := jsutil.UnmarshalString(td.want, &json1) + if td.eErr == nil { + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) } + err = jsutil.UnmarshalString(td.have, &json2) + if td.eErr == nil { + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + } + + tjcMatch, err := JsonEqual(json1, json2, ComparisonContext{}) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if td.match != tjcMatch.Equal { t.Errorf("Got %t, expected %t", tjcMatch.Equal, td.match) } - if td.err != nil { - if len(tjcMatch.Failures) != 1 || td.err.Error() != tjcMatch.Failures[0].String() { - t.Errorf("Error missmatch. Got '%s', epected '%s'", tjcMatch.Failures[0].String(), td.err) + if td.eErr != nil { + if len(tjcMatch.Failures) != 1 || td.eErr.Error() != tjcMatch.Failures[0].String() { + t.Errorf("Error missmatch. Got '%s', epected '%s'", tjcMatch.Failures[0].String(), td.eErr) } } }) @@ -417,3 +423,10 @@ func TestJsonNumberEq(t *testing.T) { return } } + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" + } + return err.Error() +} diff --git a/pkg/lib/compare/comparison_functions.go b/pkg/lib/compare/comparison_functions.go index 317fd6b..6c5a8b6 100755 --- a/pkg/lib/compare/comparison_functions.go +++ b/pkg/lib/compare/comparison_functions.go @@ -1,7 +1,6 @@ package compare import ( - "encoding/json" "errors" "fmt" "maps" @@ -9,7 +8,7 @@ import ( "strconv" "strings" - "github.com/programmfabrik/apitest/pkg/lib/util" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/golib" ) @@ -26,19 +25,21 @@ type ComparisonContext struct { mustNotExist bool isObject bool isBool bool - numberGT *util.JsonNumber - numberGE *util.JsonNumber - numberLT *util.JsonNumber - numberLE *util.JsonNumber - regexMatch *util.JsonString - regexMatchNot *util.JsonString - startsWith *util.JsonString - endsWith *util.JsonString + numberGT *jsutil.Number + numberGE *jsutil.Number + numberLT *jsutil.Number + numberLE *jsutil.Number + regexMatch *jsutil.String + regexMatchNot *jsutil.String + startsWith *jsutil.String + endsWith *jsutil.String notEqualNull bool notEqual *any } -func fillComparisonContext(in util.JsonObject) (out *ComparisonContext, err error) { +var controlKeyRegex = regexp.MustCompile(`(?P.*?):control`) + +func fillComparisonContext(in jsutil.Object) (out *ComparisonContext, err error) { out = &ComparisonContext{} for k, v := range in { @@ -169,7 +170,7 @@ func fillComparisonContext(in util.JsonObject) (out *ComparisonContext, err erro out.isBool = tV case "number_gt": // Number must be bigger - tV, ok := v.(util.JsonNumber) + tV, ok := v.(jsutil.Number) if !ok { err = errors.New("number_gt is no number") return @@ -179,7 +180,7 @@ func fillComparisonContext(in util.JsonObject) (out *ComparisonContext, err erro out.isNumber = true case "number_ge": // Number must be equal or bigger - tV, ok := v.(util.JsonNumber) + tV, ok := v.(jsutil.Number) if !ok { err = errors.New("number_gt is no number") return @@ -189,7 +190,7 @@ func fillComparisonContext(in util.JsonObject) (out *ComparisonContext, err erro out.isNumber = true case "number_lt": // Number must be smaller - tV, ok := v.(util.JsonNumber) + tV, ok := v.(jsutil.Number) if !ok { err = errors.New("number_lt is no number") return @@ -199,7 +200,7 @@ func fillComparisonContext(in util.JsonObject) (out *ComparisonContext, err erro out.isNumber = true case "number_le": // Number must be equal or smaller - tV, ok := v.(util.JsonNumber) + tV, ok := v.(jsutil.Number) if !ok { err = errors.New("number_le is no number") return @@ -232,9 +233,14 @@ func fillComparisonContext(in util.JsonObject) (out *ComparisonContext, err erro // objectComparsion checks if two objects are equal // hereby we also check our control structures and the noExtra parameter. If noExtra is true it is not allowed to have // elements than set -func objectComparison(left, right util.JsonObject, noExtra bool) (res CompareResult, err error) { +func objectComparison(left, right jsutil.Object, noExtra bool) (res CompareResult, err error) { + var ( + rv, lv any + rOK, lOK bool + k string + ) + res.Equal = true - keyRegex := regexp.MustCompile(`(?P.*?):control`) takenInRight := make(map[string]bool) takenInLeft := make(map[string]bool) @@ -245,15 +251,12 @@ func objectComparison(left, right util.JsonObject, noExtra bool) (res CompareRes if takenInLeft[ck] { continue } - var rv, lv any - var rOK, lOK bool control := &ComparisonContext{} - var k string // Check which type of key we have if strings.HasSuffix(ck, ":control") { // We have a control key - k = keyRegex.FindStringSubmatch(ck)[1] + k = controlKeyRegex.FindStringSubmatch(ck)[1] if !takenInRight[k] { rv, rOK = right[k] } @@ -262,7 +265,7 @@ func objectComparison(left, right util.JsonObject, noExtra bool) (res CompareRes takenInLeft[k] = true takenInRight[k] = true - cvObj, ok := cv.(util.JsonObject) + cvObj, ok := cv.(jsutil.Object) if ok { control, err = fillComparisonContext(cvObj) if err != nil { @@ -282,7 +285,7 @@ func objectComparison(left, right util.JsonObject, noExtra bool) (res CompareRes takenInLeft[k] = true takenInRight[k] = true - leftObj, ok := leftCopy[k+":control"].(util.JsonObject) + leftObj, ok := leftCopy[k+":control"].(jsutil.Object) if ok { iControl, err := fillComparisonContext(leftObj) if err != nil { @@ -346,13 +349,13 @@ func objectComparison(left, right util.JsonObject, noExtra bool) (res CompareRes // ArrayComparison offerst the compare feature to other packages, with the standard behavior // noExtra=false, orderMatter=false -func ArrayComparison(left, right util.JsonArray) (res CompareResult, err error) { +func ArrayComparison(left, right jsutil.Array) (res CompareResult, err error) { return arrayComparison(left, right, ComparisonContext{}, ComparisonContext{}) } // arrayComparison makes a simple array comparison by either running trough both arrays with the same key (orderMaters) // or taking a value from the left array and search it in the right one -func arrayComparison(left, right util.JsonArray, currControl ComparisonContext, nextControl ComparisonContext) (res CompareResult, err error) { +func arrayComparison(left, right jsutil.Array, currControl ComparisonContext, nextControl ComparisonContext) (res CompareResult, err error) { res.Equal = true if len(left) > len(right) { @@ -372,7 +375,7 @@ func arrayComparison(left, right util.JsonArray, currControl ComparisonContext, } takenInRight := make(map[int]bool) - var lastPositionFromLeftInRight int = -1 + lastPositionFromLeftInRight := -1 for lk, lv := range left { if currControl.orderMatters { @@ -393,7 +396,7 @@ func arrayComparison(left, right util.JsonArray, currControl ComparisonContext, if !takenInRight[lk] { key := fmt.Sprintf("[%d]", lk) elStr := fmt.Sprintf("%v", lv) - elBytes, err := json.Marshal(lv) + elBytes, err := jsutil.Marshal(lv) if err == nil { elStr = string(elBytes) } @@ -455,11 +458,11 @@ func arrayComparison(left, right util.JsonArray, currControl ComparisonContext, return res, nil } -func ObjectEqualWithControl(left, right util.JsonObject, control ComparisonContext) (res CompareResult, err error) { +func ObjectEqualWithControl(left, right jsutil.Object, control ComparisonContext) (res CompareResult, err error) { return objectComparison(left, right, control.noExtra) } -func ArrayEqualWithControl(left, right util.JsonArray, control ComparisonContext) (res CompareResult, err error) { +func ArrayEqualWithControl(left, right jsutil.Array, control ComparisonContext) (res CompareResult, err error) { nextControl := ComparisonContext{ noExtra: control.elementNoExtra, depth: -9999, @@ -537,7 +540,7 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { return fmt.Errorf("should be 'Array' but is '%s'", jsonType) } - rightArray := right.(util.JsonArray) + rightArray := right.(jsutil.Array) rightLen := int64(len(rightArray)) if rightLen != *control.elementCount { return fmt.Errorf("length of the actual response array %d != %d expected length", rightLen, *control.elementCount) @@ -546,40 +549,44 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { // Check for number range if control.numberGE != nil { - rightNumber := right.(util.JsonNumber) + rightNumber := right.(jsutil.Number) if !(rightNumber >= *control.numberGE) { - return fmt.Errorf("actual number %f is not equal or greater than %f", rightNumber, *control.numberGE) + return fmt.Errorf("actual number %s is not equal or greater than %s", rightNumber, *control.numberGE) } } if control.numberGT != nil { - rightNumber := right.(util.JsonNumber) + rightNumber := right.(jsutil.Number) if !(rightNumber > *control.numberGT) { - return fmt.Errorf("actual number %f is not greater than %f", rightNumber, *control.numberGT) + return fmt.Errorf("actual number %s is not greater than %s", rightNumber, *control.numberGT) } } if control.numberLE != nil { - rightNumber := right.(util.JsonNumber) + rightNumber := right.(jsutil.Number) if !(rightNumber <= *control.numberLE) { - return fmt.Errorf("actual number %f is not equal or less than %f", rightNumber, *control.numberLE) + return fmt.Errorf("actual number %s is not equal or less than %s", rightNumber, *control.numberLE) } } if control.numberLT != nil { - rightNumber := right.(util.JsonNumber) + rightNumber := right.(jsutil.Number) if !(rightNumber < *control.numberLT) { - return fmt.Errorf("actual number %f is not less than %f", rightNumber, *control.numberLT) + return fmt.Errorf("actual number %s is not less than %s", rightNumber, *control.numberLT) } } + var ( + matchS string + doesMatch bool + ) + // Check if string matches regex if control.regexMatch != nil { - var matchS string jsonType := getJsonType(right) if jsonType != "String" { matchS = fmt.Sprintf("%v", right) } else { - matchS = right.(util.JsonString) + matchS = right.(jsutil.String) } - doesMatch, err := regexp.Match(*control.regexMatch, []byte(matchS)) + doesMatch, err = regexp.Match(*control.regexMatch, []byte(matchS)) if err != nil { return fmt.Errorf("could not match regex %q: %w", *control.regexMatch, err) } @@ -590,15 +597,13 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { // Check if string does not match regex if control.regexMatchNot != nil { - - var matchS string jsonType := getJsonType(right) if jsonType != "String" { matchS = fmt.Sprintf("%v", right) } else { - matchS = right.(util.JsonString) + matchS = right.(jsutil.String) } - doesMatch, err := regexp.Match(*control.regexMatchNot, []byte(matchS)) + doesMatch, err = regexp.Match(*control.regexMatchNot, []byte(matchS)) if err != nil { return fmt.Errorf("could not match regex %q: %w", *control.regexMatchNot, err) } @@ -614,7 +619,7 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { return fmt.Errorf("should be 'String' for starts_with but is '%s'", jsonType) } - if !strings.HasPrefix(right.(util.JsonString), *control.startsWith) { + if !strings.HasPrefix(right.(jsutil.String), *control.startsWith) { return fmt.Errorf("does not start with '%s'", *control.startsWith) } } @@ -624,7 +629,7 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { return fmt.Errorf("should be 'String' for ends_with but is '%s'", jsonType) } - if !strings.HasSuffix(right.(util.JsonString), *control.endsWith) { + if !strings.HasSuffix(right.(jsutil.String), *control.endsWith) { return fmt.Errorf("does not end with '%s'", *control.endsWith) } } @@ -642,11 +647,11 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { if jsonType == controlJsonType { switch jsonType { case "Array": - leftMar, err := json.Marshal((*control.notEqual).(util.JsonArray)) + leftMar, err := jsutil.Marshal((*control.notEqual).(jsutil.Array)) if err != nil { return fmt.Errorf("could not marshal left part: %w", err) } - rightMar, err := json.Marshal(right.(util.JsonArray)) + rightMar, err := jsutil.Marshal(right.(jsutil.Array)) if err != nil { return fmt.Errorf("could not marshal right part: %w", err) } @@ -654,20 +659,20 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { return fmt.Errorf("is equal to %s %s, should not be equal", jsonType, string(leftMar)) } case "String": - if (*control.notEqual).(util.JsonString) == right.(util.JsonString) { - return fmt.Errorf("is equal to %s '%s', should not be equal", jsonType, (*control.notEqual).(util.JsonString)) + if (*control.notEqual).(jsutil.String) == right.(jsutil.String) { + return fmt.Errorf("is equal to %s '%s', should not be equal", jsonType, (*control.notEqual).(jsutil.String)) } case "Number": - if (*control.notEqual).(util.JsonNumber) == right.(util.JsonNumber) { - return fmt.Errorf("is equal to %s %v, should not be equal", jsonType, (*control.notEqual).(util.JsonNumber)) + if *control.notEqual == right { + return fmt.Errorf("is equal to %s %v, should not be equal", jsonType, *control.notEqual) } case "JsonNumber": - if jsonNumberEq((*control.notEqual).(json.Number), right.(json.Number)) { + if jsonNumberEq((*control.notEqual).(jsutil.Number), right.(jsutil.Number)) { return fmt.Errorf("expected %v, got %v", right, *control.notEqual) } case "Bool": - if (*control.notEqual).(util.JsonBool) == right.(util.JsonBool) { - return fmt.Errorf("is equal to %s %v, should not be equal", jsonType, (*control.notEqual).(util.JsonBool)) + if (*control.notEqual).(jsutil.Bool) == right.(jsutil.Bool) { + return fmt.Errorf("is equal to %s %v, should not be equal", jsonType, (*control.notEqual).(jsutil.Bool)) } } } @@ -678,17 +683,17 @@ func keyChecks(right any, rOK bool, control ComparisonContext) (err error) { func getJsonType(value any) string { switch value.(type) { - case util.JsonObject: + case jsutil.Object: return "Object" - case util.JsonArray: + case jsutil.Array: return "Array" - case util.JsonString: + case jsutil.String: return "String" - case util.JsonNumber, int: + case int, float64: return "Number" - case json.Number: + case jsutil.Number: return "JsonNumber" - case util.JsonBool: + case jsutil.Bool: return "Bool" default: return "No JSON Type: " + fmt.Sprintf("%v[%T]", value, value) @@ -703,7 +708,7 @@ func getAsInt64(value any) (n int64, err error) { return int64(t), nil case float32, float64: return strconv.ParseInt(fmt.Sprintf("%.0f", t), 10, 64) - case json.Number: + case jsutil.Number: return t.Int64() default: return 0, fmt.Errorf("'%v' has no valid json number type", value) diff --git a/pkg/lib/compare/comparison_functions_test.go b/pkg/lib/compare/comparison_functions_test.go index f6c2108..357a0a0 100644 --- a/pkg/lib/compare/comparison_functions_test.go +++ b/pkg/lib/compare/comparison_functions_test.go @@ -3,7 +3,7 @@ package compare import ( "testing" - "github.com/programmfabrik/apitest/pkg/lib/util" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" go_test_utils "github.com/programmfabrik/go-test-utils" "github.com/yudai/pp" ) @@ -11,827 +11,796 @@ import ( func TestComparison(t *testing.T) { testData := []struct { name string - left util.JsonObject - right util.JsonObject + left jsutil.Object + right jsutil.Object eEqual bool eFailures []compareFailure }{ - // { - // name: "Should be equal", - // left: util.JsonObject{ - // "array": util.JsonArray{ - // "val2", - // "val3", - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonArray{ - // "val2", - // "val3", - // }, - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "There should be any string", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "must_exist": true, - // "is_string": true, - // }, - // }, + { + name: "Should be equal", + left: jsutil.Object{ + "array": jsutil.Array{ + "val2", + "val3", + }, + }, + right: jsutil.Object{ + "array": jsutil.Array{ + "val2", + "val3", + }, + }, + eEqual: true, + eFailures: nil, + }, + { + name: "There should be any string", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "must_exist": true, + "is_string": true, + }, + }, - // right: util.JsonObject{ - // "stringerino": "not equal", - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "String matches regex", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "match": "\\d+\\..+", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "123.abc", - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "String must not match regex", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "not_match": "\\d+\\..+", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "123.abc", - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "stringerino", - // Message: "matches regex '\\d+\\..+' but should not match", - // }, - // }, - // }, - // { - // name: "String must not match regex", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "not_match": "\\d+\\..+", - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "123.abc", - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "stringerino", - // Message: "matches regex '\\d+\\..+' but should not match", - // }, - // }, - // }, - // { - // name: "String does not match regex", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "match": "\\d+\\.\\d+", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "xyz-456", - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "stringerino", - // Message: "\"xyz-456\" does not match regex '\\d+\\.\\d+'", - // }, - // }, - // }, - // { - // name: "String match with invalid regex (must fail)", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "match": ".+[", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "", - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "stringerino", - // Message: "could not match regex '.+[': 'error parsing regexp: missing closing ]: `[`'", - // }, - // }, - // }, - // { - // name: "String match tried on integer (must fail)", - // left: util.JsonObject{ - // "numberino:control": util.JsonObject{ - // "match": ".+", - // }, - // }, - // right: util.JsonObject{ - // "numberino": util.JsonNumber(123), - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "numberino", - // Message: "should be 'String' for regex match but is 'Number'", - // }, - // }, - // }, - // { - // name: "String starts with another string", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "starts_with": "123.", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "123.abc", - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "String does not start with another string", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "starts_with": "789.", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "123.abc", - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "stringerino", - // Message: "does not start with '789.'", - // }, - // }, - // }, - // { - // name: "String ends with another string", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "ends_with": ".abc", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "123.abc", - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "String does not end with another string", - // left: util.JsonObject{ - // "stringerino:control": util.JsonObject{ - // "ends_with": ".xyz", - // "is_string": true, - // }, - // }, - // right: util.JsonObject{ - // "stringerino": "123.abc", - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "stringerino", - // Message: "does not end with '.xyz'", - // }, - // }, - // }, - // { - // name: "There should be any number", - // left: util.JsonObject{ - // "numberino:control": util.JsonObject{ - // "must_exist": true, - // "is_number": true, - // }, - // }, - // right: util.JsonObject{ - // "numberino": util.JsonNumber(99999999), - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "There should be any bool", - // left: util.JsonObject{ - // "boolerino:control": util.JsonObject{ - // "must_exist": true, - // "is_bool": true, - // }, - // }, - // right: util.JsonObject{ - // "boolerino": util.JsonBool(false), - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "There should be any array", - // left: util.JsonObject{ - // "arrayerino:control": util.JsonObject{ - // "must_exist": true, - // "is_array": true, - // }, - // }, - // right: util.JsonObject{ - // "arrayerino": util.JsonArray(nil), - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "There should be an empty object", - // left: util.JsonObject{ - // "objecterino": util.JsonObject{}, - // "objecterino:control": util.JsonObject{ - // "no_extra": true, - // }, - // }, - // right: util.JsonObject{ - // "objecterino": util.JsonObject{"1": 1, "2": 2, "3": 3}, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // {"objecterino", `extra elements found in object`}, - // }, - // }, - // { - // name: "There should be empty array", - // left: util.JsonObject{ - // "arrayerino": util.JsonArray{}, - // "arrayerino:control": util.JsonObject{ - // "no_extra": true, - // }, - // }, - // right: util.JsonObject{ - // "arrayerino": util.JsonArray{"1", "2", "3"}, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // {"arrayerino", `extra elements found in array`}, - // }, - // }, - // { - // name: "There should be any object", - // left: util.JsonObject{ - // "objecterino:control": util.JsonObject{ - // "must_exist": true, - // "is_object": true, - // }, - // }, - // right: util.JsonObject{ - // "objecterino": util.JsonObject(nil), - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "Token match with wrong order", - // left: util.JsonObject{ - // "tokens": util.JsonArray{ - // util.JsonObject{ - // "suggest": "a", - // }, - // util.JsonObject{ - // "suggest": "ab", - // }, - // util.JsonObject{ - // "suggest": "abc", - // }, - // }, - // }, - // right: util.JsonObject{ - // "tokens": util.JsonArray{ - // util.JsonObject{ - // "suggest": "a", - // }, - // util.JsonObject{ - // "suggest": "abc", - // }, - // util.JsonObject{ - // "suggest": "ab", - // }, - // }, - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "There should be no object", - // left: util.JsonObject{ - // "objecterino:control": util.JsonObject{ - // "must_not_exist": true, - // }, - // }, - // right: util.JsonObject{}, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "There should be no object but it exists", - // left: util.JsonObject{ - // "objecterino:control": util.JsonObject{ - // "must_not_exist": true, - // }, - // }, - // right: util.JsonObject{ - // "objecterino": util.JsonObject(nil), - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "objecterino", - // Message: "was found, but should NOT exist", - // }, - // }, - // }, - // { - // name: "There should be no deeper object but it exists", - // left: util.JsonObject{ - // "it": util.JsonArray{ - // util.JsonObject{ - // "objecterino:control": util.JsonObject{ - // "must_not_exist": true, - // }, - // }, - // }, - // }, - // right: util.JsonObject{ - // "it": util.JsonArray{ - // util.JsonObject{ - // "objecterino": util.JsonString("I AM HERE"), - // }, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "it[0].objecterino", - // Message: "was found, but should NOT exist", - // }, - // }, - // }, - // { - // name: "There should be no deeper object but it exists2", - // left: util.JsonObject{ - // "it": util.JsonArray{ - // util.JsonObject{ - // "objecterino:control": util.JsonObject{ - // "must_not_exist": true, - // }, - // }, - // }, - // }, - // right: util.JsonObject{ - // "it": util.JsonArray{ - // util.JsonObject{ - // "objecterino": util.JsonString("I AM HERE"), - // }, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "it[0].objecterino", - // Message: "was found, but should NOT exist", - // }, - // }, - // }, - // { - // name: "There should be a exact object match", - // left: util.JsonObject{ - // "objecterino": util.JsonObject{ - // "1": util.JsonNumber(1), - // "2": util.JsonNumber(2), - // "3": util.JsonNumber(3), - // }, - // "objecterino:control": util.JsonObject{ - // "no_extra": true, - // }, - // }, - // right: util.JsonObject{ - // "objecterino": util.JsonObject{ - // "1": util.JsonNumber(1), - // "3": util.JsonNumber(3), - // "2": util.JsonNumber(2), - // }, - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "There should be a exact object match even if order is mixed", - // left: util.JsonObject{ - // "objecterino": util.JsonObject{ - // "1": util.JsonNumber(1), - // "2": util.JsonNumber(2), - // "3": util.JsonNumber(3), - // }, - // "objecterino:control": util.JsonObject{ - // "no_extra": true, - // }, - // }, - // right: util.JsonObject{ - // "objecterino": util.JsonObject{ - // "2": util.JsonNumber(2), - // "3": util.JsonNumber(3), - // "1": util.JsonNumber(1), - // }, - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "Exact match is not present", - // left: util.JsonObject{ - // "MYobjecterino": util.JsonObject{ - // "1": util.JsonNumber(1), - // "2": util.JsonNumber(2), - // "3": util.JsonNumber(3), - // }, - // "MYobjecterino:control": util.JsonObject{ - // "no_extra": true, - // }, - // }, - // right: util.JsonObject{ - // "MYobjecterino": util.JsonObject{ - // "2": util.JsonNumber(2), - // "4": util.JsonNumber(4), - // "1": util.JsonNumber(1), - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "MYobjecterino.3", - // Message: "was not found, but should exist", - // }, - // { - // Key: "MYobjecterino", - // Message: "extra elements found in object", - // }, - // }, - // }, - // { - // name: "Not all contained", - // left: util.JsonObject{ - // "array": util.JsonArray{ - // "val2", - // "val3", - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonArray{ - // "val1", - // "val2", - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "array[1]", - // Message: "Got 'val1', expected 'val3'", - // }, - // }, - // }, - // { - // name: "Wrong order", - // left: util.JsonObject{ - // "array": util.JsonArray{ - // "val3", - // "val2", - // }, - // "array:control": util.JsonObject{ - // "order_matters": true, - // "no_extra": true, - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonArray{ - // "val2", - // "val3", - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "array[1]", - // Message: "element \"val2\" not found in array in proper order", - // }, - // { - // Key: "array", - // Message: "extra elements found in array", - // }, - // }, - // }, - // { - // name: "Wrong order deeper with map", - // left: util.JsonObject{ - // "array": util.JsonObject{ - // "inner": util.JsonObject{ - // "deeper": util.JsonArray{ - // "val4", - // "val5", - // }, - // "deeper:control": util.JsonObject{ - // "order_matters": true, - // }, - // }, - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonObject{ - // "inner": util.JsonObject{ - // "deeper": util.JsonArray{ - // "val5", - // "val4", - // }, - // }, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "array.inner.deeper[1]", - // Message: "element \"val5\" not found in array in proper order", - // }, - // }, - // }, - // { - // name: "Right error message for array", - // left: util.JsonObject{ - // "body": util.JsonArray{ - // util.JsonObject{ - // "henk": "denk", - // }, - // }, - // }, - // right: util.JsonObject{ - // "body": util.JsonArray{ - // util.JsonObject{}, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "body[0].henk", - // Message: "was not found, but should exist", - // }, - // }, - // }, - // /* { - // name: "Wrong order deeper with arrays", - // left: util.JsonObject{ - // "array": util.JsonArray{ - // util.JsonArray{ - // util.JsonArray{ - // "val9", - // "val10", - // }, - // }, - // }, - // "array:control": util.JsonObject{ - // "order_matters": true, - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonArray{ - // util.JsonArray{ - // util.JsonArray{ - // "val10", - // "val9", - // }, - // }, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "array", - // Message: "[0][0][0]Expected 'val9' != 'val10' Got", - // }, - // { - // Key: "array", - // Message: "[0][0][1]Expected 'val10' != 'val9' Got", - // }, - // }, - // },*/ - // { - // name: "All fine deeper with arrays", - // left: util.JsonObject{ - // "array": util.JsonArray{ - // util.JsonArray{ - // util.JsonArray{ - // "val9", - // "val10", - // }, - // }, - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonArray{ - // util.JsonArray{ - // util.JsonArray{ - // "val9", - // "val10", - // }, - // }, - // }, - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "Check array length", - // left: util.JsonObject{ - // "array:control": util.JsonObject{ - // "element_count": 3, - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonArray{ - // util.JsonArray{ - // "val9", - // "val10", - // }, - // util.JsonArray{ - // "val9", - // "val10", - // }, - // util.JsonArray{ - // "val9", - // "val10", - // }, - // }, - // }, - // eEqual: true, - // eFailures: nil, - // }, - // { - // name: "Check array length and fail", - // left: util.JsonObject{ - // "array:control": util.JsonObject{ - // "element_count": 2, - // }, - // "array": util.JsonArray{ - // util.JsonArray{}, - // }, - // }, - // right: util.JsonObject{ - // "array": util.JsonArray{ - // util.JsonArray{ - // util.JsonArray{ - // "val9", - // "val10", - // }, - // }, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "array", - // Message: "length of the actual response array '1' != '2' expected length", - // }, - // }, - // }, - // { - // name: "Check controls in array (1 element)", - // left: util.JsonObject{ - // "body": util.JsonArray{ - // util.JsonObject{ - // "pool": util.JsonObject{ - // "reference:control": util.JsonObject{ - // "is_number": true, - // }, - // }, - // }, - // }, - // }, - // right: util.JsonObject{ - // "body": util.JsonArray{ - // util.JsonObject{ - // "pool": util.JsonObject{ - // "reference": "system:root", - // }, - // }, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "body[0].pool.reference", - // Message: "should be 'Number' but is 'String'", - // }, - // }, - // }, - // { - // name: "Check controls in array (more elements)", - // left: util.JsonObject{ - // "body": util.JsonArray{ - // util.JsonObject{ - // "pool": util.JsonObject{ - // "reference:control": util.JsonObject{ - // "is_number": true, - // }, - // }, - // }, - // util.JsonObject{ - // "pool": util.JsonObject{ - // "reference:control": util.JsonObject{ - // "is_number": true, - // }, - // }, - // }, - // }, - // }, - // right: util.JsonObject{ - // "body": util.JsonArray{ - // util.JsonObject{ - // "pool": util.JsonObject{ - // "reference": "system:root", - // }, - // }, - // util.JsonObject{ - // "pool": util.JsonObject{ - // "reference": 123, - // }, - // }, - // }, - // }, - // eEqual: false, - // eFailures: []CompareFailure{ - // { - // Key: "body[0].pool.reference", - // Message: "should be 'Number' but is 'String'", - // }, - // { - // Key: "body[0].pool.reference", - // Message: "should be 'Number' but is 'No JSON Type: 123'", - // }, - // { - // Key: "body[1].pool.reference", - // Message: "should be 'Number' but is 'String'", - // }, - // { - // Key: "body[1].pool.reference", - // Message: "should be 'Number' but is 'No JSON Type: 123'", - // }, - // }, - // }, + right: jsutil.Object{ + "stringerino": "not equal", + }, + eEqual: true, + eFailures: nil, + }, + { + name: "String matches regex", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "match": "\\d+\\..+", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "123.abc", + }, + eEqual: true, + eFailures: nil, + }, + { + name: "String must not match regex", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "not_match": "\\d+\\..+", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "123.abc", + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "stringerino", + Message: "matches regex \"\\\\d+\\\\..+\" but should not match", + }, + }, + }, + { + name: "String must not match regex", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "not_match": "\\d+\\..+", + }, + }, + right: jsutil.Object{ + "stringerino": "123.abc", + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "stringerino", + Message: "matches regex \"\\\\d+\\\\..+\" but should not match", + }, + }, + }, + { + name: "String does not match regex", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "match": "\\d+\\.\\d+", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "xyz-456", + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "stringerino", + Message: "string \"xyz-456\" does not match regex \"\\\\d+\\\\.\\\\d+\"", + }, + }, + }, + { + name: "String match with invalid regex (must fail)", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "match": ".+[", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "", + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "stringerino", + Message: "could not match regex \".+[\": error parsing regexp: missing closing ]: `[`", + }, + }, + }, + { + name: "String starts with another string", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "starts_with": "123.", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "123.abc", + }, + eEqual: true, + eFailures: nil, + }, + { + name: "String does not start with another string", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "starts_with": "789.", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "123.abc", + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "stringerino", + Message: "does not start with '789.'", + }, + }, + }, + { + name: "String ends with another string", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "ends_with": ".abc", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "123.abc", + }, + eEqual: true, + eFailures: nil, + }, + { + name: "String does not end with another string", + left: jsutil.Object{ + "stringerino:control": jsutil.Object{ + "ends_with": ".xyz", + "is_string": true, + }, + }, + right: jsutil.Object{ + "stringerino": "123.abc", + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "stringerino", + Message: "does not end with '.xyz'", + }, + }, + }, + { + name: "There should be any number", + left: jsutil.Object{ + "numberino:control": jsutil.Object{ + "must_exist": true, + "is_number": true, + }, + }, + right: jsutil.Object{ + "numberino": jsutil.Number("99999999"), + }, + eEqual: true, + eFailures: nil, + }, + { + name: "There should be any bool", + left: jsutil.Object{ + "boolerino:control": jsutil.Object{ + "must_exist": true, + "is_bool": true, + }, + }, + right: jsutil.Object{ + "boolerino": jsutil.Bool(false), + }, + eEqual: true, + eFailures: nil, + }, + { + name: "There should be any array", + left: jsutil.Object{ + "arrayerino:control": jsutil.Object{ + "must_exist": true, + "is_array": true, + }, + }, + right: jsutil.Object{ + "arrayerino": jsutil.Array(nil), + }, + eEqual: true, + eFailures: nil, + }, + { + name: "There should be an empty object", + left: jsutil.Object{ + "objecterino": jsutil.Object{}, + "objecterino:control": jsutil.Object{ + "no_extra": true, + }, + }, + right: jsutil.Object{ + "objecterino": jsutil.Object{"1": 1, "2": 2, "3": 3}, + }, + eEqual: false, + eFailures: []compareFailure{ + {"objecterino", `extra elements found in object`}, + }, + }, + { + name: "There should be empty array", + left: jsutil.Object{ + "arrayerino": jsutil.Array{}, + "arrayerino:control": jsutil.Object{ + "no_extra": true, + }, + }, + right: jsutil.Object{ + "arrayerino": jsutil.Array{"1", "2", "3"}, + }, + eEqual: false, + eFailures: []compareFailure{ + {"arrayerino", `extra elements found in array`}, + }, + }, + { + name: "There should be any object", + left: jsutil.Object{ + "objecterino:control": jsutil.Object{ + "must_exist": true, + "is_object": true, + }, + }, + right: jsutil.Object{ + "objecterino": jsutil.Object(nil), + }, + eEqual: true, + eFailures: nil, + }, + { + name: "Token match with wrong order", + left: jsutil.Object{ + "tokens": jsutil.Array{ + jsutil.Object{ + "suggest": "a", + }, + jsutil.Object{ + "suggest": "ab", + }, + jsutil.Object{ + "suggest": "abc", + }, + }, + }, + right: jsutil.Object{ + "tokens": jsutil.Array{ + jsutil.Object{ + "suggest": "a", + }, + jsutil.Object{ + "suggest": "abc", + }, + jsutil.Object{ + "suggest": "ab", + }, + }, + }, + eEqual: true, + eFailures: nil, + }, + { + name: "There should be no object", + left: jsutil.Object{ + "objecterino:control": jsutil.Object{ + "must_not_exist": true, + }, + }, + right: jsutil.Object{}, + eEqual: true, + eFailures: nil, + }, + { + name: "There should be no object but it exists", + left: jsutil.Object{ + "objecterino:control": jsutil.Object{ + "must_not_exist": true, + }, + }, + right: jsutil.Object{ + "objecterino": jsutil.Object(nil), + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "objecterino", + Message: "was found, but should NOT exist", + }, + }, + }, + { + name: "There should be no deeper object but it exists", + left: jsutil.Object{ + "it": jsutil.Array{ + jsutil.Object{ + "objecterino:control": jsutil.Object{ + "must_not_exist": true, + }, + }, + }, + }, + right: jsutil.Object{ + "it": jsutil.Array{ + jsutil.Object{ + "objecterino": jsutil.String("I AM HERE"), + }, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "it[0].objecterino", + Message: "was found, but should NOT exist", + }, + }, + }, + { + name: "There should be no deeper object but it exists2", + left: jsutil.Object{ + "it": jsutil.Array{ + jsutil.Object{ + "objecterino:control": jsutil.Object{ + "must_not_exist": true, + }, + }, + }, + }, + right: jsutil.Object{ + "it": jsutil.Array{ + jsutil.Object{ + "objecterino": jsutil.String("I AM HERE"), + }, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "it[0].objecterino", + Message: "was found, but should NOT exist", + }, + }, + }, + { + name: "There should be a exact object match", + left: jsutil.Object{ + "objecterino": jsutil.Object{ + "1": jsutil.Number("1"), + "2": jsutil.Number("2"), + "3": jsutil.Number("3"), + }, + "objecterino:control": jsutil.Object{ + "no_extra": true, + }, + }, + right: jsutil.Object{ + "objecterino": jsutil.Object{ + "1": jsutil.Number("1"), + "3": jsutil.Number("3"), + "2": jsutil.Number("2"), + }, + }, + eEqual: true, + eFailures: nil, + }, + { + name: "There should be a exact object match even if order is mixed", + left: jsutil.Object{ + "objecterino": jsutil.Object{ + "1": jsutil.Number("1"), + "2": jsutil.Number("2"), + "3": jsutil.Number("3"), + }, + "objecterino:control": jsutil.Object{ + "no_extra": true, + }, + }, + right: jsutil.Object{ + "objecterino": jsutil.Object{ + "2": jsutil.Number("2"), + "3": jsutil.Number("3"), + "1": jsutil.Number("1"), + }, + }, + eEqual: true, + eFailures: nil, + }, + { + name: "Exact match is not present", + left: jsutil.Object{ + "MYobjecterino": jsutil.Object{ + "1": jsutil.Number("1"), + "2": jsutil.Number("2"), + "3": jsutil.Number("3"), + }, + "MYobjecterino:control": jsutil.Object{ + "no_extra": true, + }, + }, + right: jsutil.Object{ + "MYobjecterino": jsutil.Object{ + "2": jsutil.Number("2"), + "4": jsutil.Number("4"), + "1": jsutil.Number("1"), + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "MYobjecterino.3", + Message: "was not found, but should exist", + }, + { + Key: "MYobjecterino", + Message: "extra elements found in object", + }, + }, + }, + { + name: "Not all contained", + left: jsutil.Object{ + "array": jsutil.Array{ + "val2", + "val3", + }, + }, + right: jsutil.Object{ + "array": jsutil.Array{ + "val1", + "val2", + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "array[1]", + Message: "Got 'val1', expected 'val3'", + }, + }, + }, + { + name: "Wrong order", + left: jsutil.Object{ + "array": jsutil.Array{ + "val3", + "val2", + }, + "array:control": jsutil.Object{ + "order_matters": true, + "no_extra": true, + }, + }, + right: jsutil.Object{ + "array": jsutil.Array{ + "val2", + "val3", + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "array[1]", + Message: "element \"val2\" not found in array in proper order", + }, + { + Key: "array", + Message: "extra elements found in array", + }, + }, + }, + { + name: "Wrong order deeper with map", + left: jsutil.Object{ + "array": jsutil.Object{ + "inner": jsutil.Object{ + "deeper": jsutil.Array{ + "val4", + "val5", + }, + "deeper:control": jsutil.Object{ + "order_matters": true, + }, + }, + }, + }, + right: jsutil.Object{ + "array": jsutil.Object{ + "inner": jsutil.Object{ + "deeper": jsutil.Array{ + "val5", + "val4", + }, + }, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "array.inner.deeper[1]", + Message: "element \"val5\" not found in array in proper order", + }, + }, + }, + { + name: "Right error message for array", + left: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "henk": "denk", + }, + }, + }, + right: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{}, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "body[0].henk", + Message: "was not found, but should exist", + }, + }, + }, + /* { + name: "Wrong order deeper with arrays", + left: jsutil.Object{ + "array": jsutil.Array{ + jsutil.Array{ + jsutil.Array{ + "val9", + "val10", + }, + }, + }, + "array:control": jsutil.Object{ + "order_matters": true, + }, + }, + right: jsutil.Object{ + "array": jsutil.Array{ + jsutil.Array{ + jsutil.Array{ + "val10", + "val9", + }, + }, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "array", + Message: "[0][0][0]Expected 'val9' != 'val10' Got", + }, + { + Key: "array", + Message: "[0][0][1]Expected 'val10' != 'val9' Got", + }, + }, + },*/ + { + name: "All fine deeper with arrays", + left: jsutil.Object{ + "array": jsutil.Array{ + jsutil.Array{ + jsutil.Array{ + "val9", + "val10", + }, + }, + }, + }, + right: jsutil.Object{ + "array": jsutil.Array{ + jsutil.Array{ + jsutil.Array{ + "val9", + "val10", + }, + }, + }, + }, + eEqual: true, + eFailures: nil, + }, + { + name: "Check array length", + left: jsutil.Object{ + "array:control": jsutil.Object{ + "element_count": 3, + }, + }, + right: jsutil.Object{ + "array": jsutil.Array{ + jsutil.Array{ + "val9", + "val10", + }, + jsutil.Array{ + "val9", + "val10", + }, + jsutil.Array{ + "val9", + "val10", + }, + }, + }, + eEqual: true, + eFailures: nil, + }, + { + name: "Check array length and fail", + left: jsutil.Object{ + "array:control": jsutil.Object{ + "element_count": 2, + }, + "array": jsutil.Array{ + jsutil.Array{}, + }, + }, + right: jsutil.Object{ + "array": jsutil.Array{ + jsutil.Array{ + jsutil.Array{ + "val10", + }, + }, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "array", + Message: "length of the actual response array 1 != 2 expected length", + }, + }, + }, + { + name: "Check controls in array (1 element)", + left: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ + "reference:control": jsutil.Object{ + "is_number": true, + }, + }, + }, + }, + }, + right: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ + "reference": "system:root", + }, + }, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "body[0].pool.reference", + Message: "should be 'JsonNumber' or 'Number' but is 'String'", + }, + }, + }, + { + name: "Check controls in array (more elements)", + left: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ + "reference:control": jsutil.Object{ + "is_number": true, + }, + }, + }, + jsutil.Object{ + "pool": jsutil.Object{ + "reference:control": jsutil.Object{ + "is_number": true, + }, + }, + }, + }, + }, + right: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ + "reference": "system:root", + }, + }, + jsutil.Object{ + "pool": jsutil.Object{ + "reference": 123, + }, + }, + }, + }, + eEqual: false, + eFailures: []compareFailure{ + { + Key: "body[1].pool.reference", + Message: "should be 'JsonNumber' or 'Number' but is 'String'", + }, + }, + }, { name: "Check controls in array (more elements, different order)", - left: util.JsonObject{ - "body": util.JsonArray{ - util.JsonObject{ - "pool": util.JsonObject{ - "reference:control": util.JsonObject{ + left: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ + "reference:control": jsutil.Object{ "is_number": true, }, }, }, - util.JsonObject{ - "pool": util.JsonObject{ - "reference:control": util.JsonObject{ + jsutil.Object{ + "pool": jsutil.Object{ + "reference:control": jsutil.Object{ "is_string": true, }, }, }, }, }, - right: util.JsonObject{ - "body": util.JsonArray{ - util.JsonObject{ - "pool": util.JsonObject{ + right: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ "reference": "system:root", }, }, - util.JsonObject{ - "pool": util.JsonObject{ + jsutil.Object{ + "pool": jsutil.Object{ "reference": 123, }, }, @@ -842,28 +811,28 @@ func TestComparison(t *testing.T) { }, { name: "Check body no extra", - left: util.JsonObject{ - "body": util.JsonArray{ - util.JsonObject{ - "pool": util.JsonObject{ + left: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ "reference": "system:root", }, }, }, - "body:control": util.JsonObject{ + "body:control": jsutil.Object{ "no_extra": true, }, }, - right: util.JsonObject{ - "body": util.JsonArray{ - util.JsonObject{ - "pool": util.JsonObject{ + right: jsutil.Object{ + "body": jsutil.Array{ + jsutil.Object{ + "pool": jsutil.Object{ "reference": "system:root", "reference2": "system:root", }, }, - util.JsonObject{ - "pool": util.JsonObject{ + jsutil.Object{ + "pool": jsutil.Object{ "reference": "system:root", }, }, @@ -879,12 +848,12 @@ func TestComparison(t *testing.T) { }, { name: "check control not_equal (different types number, string)", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": "right", }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": 123.456, }, eEqual: true, @@ -892,12 +861,12 @@ func TestComparison(t *testing.T) { }, { name: "check control not_equal (different types number, array)", - left: util.JsonObject{ - "v:control": util.JsonObject{ - "not_equal": util.JsonArray([]any{"right"}), + left: jsutil.Object{ + "v:control": jsutil.Object{ + "not_equal": jsutil.Array([]any{"right"}), }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": 123.456, }, eEqual: true, @@ -905,12 +874,12 @@ func TestComparison(t *testing.T) { }, { name: "check control not_equal (different types string, number)", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": 123.45, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": "left", }, eEqual: true, @@ -918,12 +887,12 @@ func TestComparison(t *testing.T) { }, { name: "check control not_equal (different types bool, number)", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": 456.789, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": true, }, eEqual: true, @@ -931,12 +900,12 @@ func TestComparison(t *testing.T) { }, { name: "check control not_equal (different types number, bool)", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": true, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": 789.0001, }, eEqual: true, @@ -944,12 +913,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal with null and null", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": nil, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": nil, }, eEqual: false, @@ -962,12 +931,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal with null and string", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": nil, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": "not null", }, eEqual: true, @@ -975,12 +944,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal with string and null", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": "not null", }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": nil, }, eEqual: true, @@ -988,25 +957,25 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal with null and array", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": nil, }, }, - right: util.JsonObject{ - "v": util.JsonArray([]any{"not null"}), + right: jsutil.Object{ + "v": jsutil.Array([]any{"not null"}), }, eEqual: true, eFailures: nil, }, { name: "Check not_equal with array and null", - left: util.JsonObject{ - "v:control": util.JsonObject{ - "not_equal": util.JsonArray([]any{"not null"}), + left: jsutil.Object{ + "v:control": jsutil.Object{ + "not_equal": jsutil.Array([]any{"not null"}), }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": nil, }, eEqual: true, @@ -1014,12 +983,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal: string with different value", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": "left", }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": "right", }, eEqual: true, @@ -1027,12 +996,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal: string with same value", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": "left", }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": "left", }, eEqual: false, @@ -1045,26 +1014,26 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal: array with different value", - left: util.JsonObject{ - "v:control": util.JsonObject{ - "not_equal": util.JsonArray([]any{"left", "right"}), + left: jsutil.Object{ + "v:control": jsutil.Object{ + "not_equal": jsutil.Array([]any{"left", "right"}), }, }, - right: util.JsonObject{ - "v": util.JsonArray([]any{"right", "left"}), + right: jsutil.Object{ + "v": jsutil.Array([]any{"right", "left"}), }, eEqual: true, eFailures: nil, }, { name: "Check not_equal: array with same value", - left: util.JsonObject{ - "v:control": util.JsonObject{ - "not_equal": util.JsonArray([]any{"left", "right"}), + left: jsutil.Object{ + "v:control": jsutil.Object{ + "not_equal": jsutil.Array([]any{"left", "right"}), }, }, - right: util.JsonObject{ - "v": util.JsonArray([]any{"left", "right"}), + right: jsutil.Object{ + "v": jsutil.Array([]any{"left", "right"}), }, eEqual: false, eFailures: []compareFailure{ @@ -1076,12 +1045,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal: number with different value", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": 123.45, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": 6.789, }, eEqual: true, @@ -1089,12 +1058,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal: number with same value", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": 0.111, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": 0.111, }, eEqual: false, @@ -1107,12 +1076,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal: boolean with different value", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": false, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": true, }, eEqual: true, @@ -1120,12 +1089,12 @@ func TestComparison(t *testing.T) { }, { name: "Check not_equal: boolean with same value", - left: util.JsonObject{ - "v:control": util.JsonObject{ + left: jsutil.Object{ + "v:control": jsutil.Object{ "not_equal": false, }, }, - right: util.JsonObject{ + right: jsutil.Object{ "v": false, }, eEqual: false, @@ -1141,12 +1110,10 @@ func TestComparison(t *testing.T) { for _, data := range testData { t.Run(data.name, func(t *testing.T) { equal, err := JsonEqual(data.left, data.right, ComparisonContext{}) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if equal.Equal != data.eEqual { - t.Errorf("Expected equal '%t' != '%t' Got equal", data.eEqual, equal.Equal) + t.Errorf("Expected '%t', got '%t'", data.eEqual, equal.Equal) return } diff --git a/pkg/lib/csv/csv.go b/pkg/lib/csv/csv.go index 737e38d..6128cd4 100644 --- a/pkg/lib/csv/csv.go +++ b/pkg/lib/csv/csv.go @@ -3,11 +3,12 @@ package csv import ( "bytes" "encoding/csv" - "encoding/json" "fmt" "io" "strconv" "strings" + + "github.com/programmfabrik/apitest/pkg/lib/jsutil" ) // Get information @@ -35,7 +36,7 @@ func CSVToMap(inputCSV []byte, comma rune) (output []map[string]any, err error) output = []map[string]any{} - //Iterate over the records with skipping the first two lines (as they contain the infos) + // Iterate over the records with skipping the first two lines (as they contain the infos) for _, v := range records[2:] { tmpRow := make(map[string]any, 0) @@ -89,7 +90,7 @@ func GenericCSVToMap(inputCSV []byte, comma rune) (output []map[string]any, err output = []map[string]any{} - //Iterate over the records with skipping the first two lines (as they contain the infos) + // Iterate over the records with skipping the first two lines (as they contain the infos) for _, v := range records[1:] { tmpRow := make(map[string]any, 0) @@ -230,14 +231,7 @@ func getTyped(value, format string) (typed any, err error) { } retArray := make([]string, 0) - for _, v := range records[0] { - // DEBUG: The previous code would trim values here. - // Uncomment to debug your CSV... - // if len(strings.TrimSpace(v)) != len(v) { - // println(fmt.Sprintf("Trimming %s %v", v, records[0])) - // } - retArray = append(retArray, v) - } + retArray = append(retArray, records[0]...) return retArray, nil case "int64,array": if value == "" { @@ -249,7 +243,7 @@ func getTyped(value, format string) (typed any, err error) { return nil, err } - //Check if we only have one row. If not return error + // Check if we only have one row. If not return error if len(records) > 1 { return nil, fmt.Errorf("Only one row is allowed for type 'int64,array'") } @@ -276,7 +270,7 @@ func getTyped(value, format string) (typed any, err error) { return nil, err } - //Check if we only have one row. If not return error + // Check if we only have one row. If not return error if len(records) > 1 { return nil, fmt.Errorf("Only one row is allowed for type 'float64,array'") @@ -303,7 +297,7 @@ func getTyped(value, format string) (typed any, err error) { return nil, err } - //Check if we only have one row. If not return error + // Check if we only have one row. If not return error if len(records) > 1 { return nil, fmt.Errorf("Only one row is allowed for type 'bool,array'") } @@ -318,7 +312,7 @@ func getTyped(value, format string) (typed any, err error) { return nil, nil } var data any - err = json.Unmarshal([]byte(value), &data) + err = jsutil.UnmarshalString(value, &data) if err != nil { return nil, fmt.Errorf("file_csv: Error in JSON: %q: %s", value, err) } diff --git a/pkg/lib/datastore/datastore.go b/pkg/lib/datastore/datastore.go index bb8986e..bb44bb4 100755 --- a/pkg/lib/datastore/datastore.go +++ b/pkg/lib/datastore/datastore.go @@ -8,10 +8,13 @@ import ( "strings" "sync" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) +var dsMapRegex = regexp.MustCompile(`^(.*?)\[(.+?)\]$`) + type Datastore struct { storage map[string]any // custom storage responseJson []string // store the responses @@ -109,21 +112,32 @@ func (ds *Datastore) SetMap(smap map[string]any) (err error) { } func (ds *Datastore) Set(index string, value any) (err error) { - var dsMapRegex = regexp.MustCompile(`^(.*?)\[(.+?)\]$`) - //typeswitch for checking if float is actually int switch t := value.(type) { case float64: + // try to convert float if float is actually int if math.Mod(t, 1.0) == 0 { - //is int + // is int value = int(t) } + case jsutil.Number: + // try to store primitive number types where possible + n, err := t.Int64() + if err == nil { + value = n + } else { + // ignore errors silently + f, err := t.Float64() + if err == nil { + value = f + } + } } ds.lock.Lock() defer ds.lock.Unlock() - //Slice in datastore + // Slice in datastore if strings.HasSuffix(index, "[]") { // do a push to an array use_index := index[:len(index)-2] @@ -145,24 +159,27 @@ func (ds *Datastore) Set(index string, value any) (err error) { ds.storage[use_index] = append(s, value) - } else if rego := dsMapRegex.FindStringSubmatch(index); len(rego) > 0 { - // do a push to an array - use_index := rego[1] - _, ok := ds.storage[use_index] - if !ok { - ds.storage[use_index] = make(map[string]any, 0) - } + } else { + rego := dsMapRegex.FindStringSubmatch(index) + if len(rego) > 0 { + // do a push to an array + use_index := rego[1] + _, ok := ds.storage[use_index] + if !ok { + ds.storage[use_index] = make(map[string]any, 0) + } - s, ok := ds.storage[use_index].(map[string]any) - if !ok { - ds.storage[use_index] = make(map[string]any, 0) - s = ds.storage[use_index].(map[string]any) - } - s[rego[2]] = value - ds.storage[use_index] = s + s, ok := ds.storage[use_index].(map[string]any) + if !ok { + ds.storage[use_index] = make(map[string]any, 0) + s = ds.storage[use_index].(map[string]any) + } + s[rego[2]] = value + ds.storage[use_index] = s - } else { - ds.storage[index] = value + } else { + ds.storage[index] = value + } } if ds.logDatastore { @@ -184,10 +201,9 @@ func (ds Datastore) Get(index string) (res any, err error) { return ds.storage, nil } - var dsMapRegex = regexp.MustCompile(`^(.*?)\[(.+?)\]$`) - - if rego := dsMapRegex.FindStringSubmatch(index); len(rego) > 0 { - //we have a map or slice + rego := dsMapRegex.FindStringSubmatch(index) + if len(rego) > 0 { + // we have a map or slice useIndex := rego[1] mapIndex := rego[2] @@ -199,10 +215,10 @@ func (ds Datastore) Get(index string) (res any, err error) { tmpResMap, ok := tmpRes.(map[string]any) if ok { - //We have a map + // We have a map mapVal, ok := tmpResMap[mapIndex] if !ok { - //Value not found in map, so return empty string + // Value not found in map, so return empty string return "", nil } else { return mapVal, nil @@ -211,7 +227,7 @@ func (ds Datastore) Get(index string) (res any, err error) { tmpResSlice, ok := tmpRes.([]any) if ok { - //We have a slice + // We have a slice sliceIdx, err := strconv.Atoi(mapIndex) if err != nil { return "", datastoreIndexError{error: fmt.Sprintf("datastore: could not convert key to int: %s", mapIndex)} diff --git a/pkg/lib/datastore/datastore_test.go b/pkg/lib/datastore/datastore_test.go index 04b2450..1c8317e 100644 --- a/pkg/lib/datastore/datastore_test.go +++ b/pkg/lib/datastore/datastore_test.go @@ -15,9 +15,12 @@ func TestDataStore_Get(t *testing.T) { func TestDataStore_GetSlice(t *testing.T) { store := NewStore(false) - store.Set("slice[]", "val1") - store.Set("slice[]", "val2") - store.Set("slice[]", "val2") + err := store.Set("slice[]", "val1") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.Set("slice[]", "val2") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.Set("slice[]", "val2") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) responseBytes, err := store.Get("slice[2]") go_test_utils.AssertErrorEquals(t, err, nil) go_test_utils.AssertStringEquals(t, responseBytes.(string), `val2`) @@ -37,42 +40,56 @@ func TestDataStore_GetSlice(t *testing.T) { func TestStoreTypeInt(t *testing.T) { store := NewStore(false) - store.Set("ownInt", 1.0) - store.SetWithGjson(`{"id",1.000000}`, map[string]string{"jsonInt": "id"}) + err := store.Set("ownInt", 1.0) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.SetWithGjson(`{"id",1.000000}`, map[string]string{"jsonInt": "id"}) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) - oVal, _ := store.Get("ownInt") - jVal, _ := store.Get("jsonInt") + oVal, err := store.Get("ownInt") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + jVal, err := store.Get("jsonInt") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if oVal != jVal { t.Errorf("%d != %d", oVal, jVal) } - store.Set("ownInt", 1.1) - store.SetWithGjson(`{"id",1.100000}`, map[string]string{"jsonInt": "id"}) + err = store.Set("ownInt", 1.1) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.SetWithGjson(`{"id",1.100000}`, map[string]string{"jsonInt": "id"}) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) - oVal, _ = store.Get("ownInt") - jVal, _ = store.Get("jsonInt") + oVal, err = store.Get("ownInt") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + jVal, err = store.Get("jsonInt") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if oVal != jVal { t.Errorf("%f != %f", oVal, jVal) } - } func TestDataStore_GetMap(t *testing.T) { store := NewStore(false) - store.Set("map[key1]", "val1") - store.Set("map[key2]", "val2") - store.Set("map[key3]", "val2") + err := store.Set("map[key1]", "val1") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.Set("map[key2]", "val2") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.Set("map[key3]", "val2") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + responseBytes, err := store.Get("map[key2]") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertErrorEquals(t, err, nil) go_test_utils.AssertStringEquals(t, responseBytes.(string), `val2`) responseBytes, err = store.Get("map[key5]") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertErrorEquals(t, err, nil) go_test_utils.AssertStringEquals(t, responseBytes.(string), ``) responseBytes, err = store.Get("map[-1]") + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertErrorEquals(t, err, nil) go_test_utils.AssertStringEquals(t, responseBytes.(string), ``) } @@ -81,25 +98,24 @@ func TestDataStore_Get_BodyArray(t *testing.T) { store := NewStore(false) store.AppendResponse(`{"body":["foo","bar"],"statuscode":200}`) responseBytes, err := store.Get("0") - if err != nil { - t.Fatal(err) - } - + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertStringEquals(t, responseBytes.(string), `{"body":["foo","bar"],"statuscode":200}`) } func TestDataStore_MAP(t *testing.T) { store := NewStore(false) - store.Set("test[head1]", 2) - store.Set("test[head3]", nil) - store.Set("test[head3]", 3) - store.Set("test[head1]", 1) + err := store.Set("test[head1]", 2) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.Set("test[head3]", nil) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.Set("test[head3]", 3) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.Set("test[head1]", 1) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) tA, err := store.Get("test") - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if tA.(map[string]any)["head1"] != 1 { t.Errorf("Have '%v' != '%d' Want", tA.(map[string]any)["head1"], 1) @@ -117,8 +133,16 @@ func TestDataStore_Get_Err_Index_Out_Of_Bounds(t *testing.T) { if err == nil { t.Errorf("expected error, got nil") } else { - if _, ok := err.(datastoreIndexOutOfBoundsError); !ok { + _, ok := err.(datastoreIndexOutOfBoundsError) + if !ok { t.Errorf("Wrong error type. Expected 'DatastoreIndexOutOfBoundsError' != '%T' Got", err) } } } + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" + } + return err.Error() +} diff --git a/pkg/lib/filesystem/util_test.go b/pkg/lib/filesystem/util_test.go index d58f744..772df99 100644 --- a/pkg/lib/filesystem/util_test.go +++ b/pkg/lib/filesystem/util_test.go @@ -5,19 +5,28 @@ import ( "strconv" "testing" + go_test_utils "github.com/programmfabrik/go-test-utils" "github.com/spf13/afero" ) func TestMemFS(t *testing.T) { Fs = afero.NewMemMapFs() + var err error + for i := range 1000 { - Fs.MkdirAll(filepath.Join("store", "test1", "data", strconv.Itoa(i)), 0755) + err = Fs.MkdirAll(filepath.Join("store", "test1", "data", strconv.Itoa(i)), 0755) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) } for i := range 1000 { _, err := Fs.Open(filepath.Join("store", "test1", "data", strconv.Itoa(i))) - if err != nil { - t.Error(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + } +} + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" } + return err.Error() } diff --git a/pkg/lib/util/json.go b/pkg/lib/jsutil/json.go similarity index 79% rename from pkg/lib/util/json.go rename to pkg/lib/jsutil/json.go index ade7816..ef0004f 100644 --- a/pkg/lib/util/json.go +++ b/pkg/lib/jsutil/json.go @@ -1,39 +1,71 @@ -package util +package jsutil import ( "bufio" "bytes" "encoding/json" "fmt" + "io" "regexp" "strconv" "strings" + "github.com/programmfabrik/golib" "github.com/tidwall/jsonc" ) -type JsonObject = map[string]any -type JsonArray = []any -type JsonString = string -type JsonNumber = float64 -type JsonBool = bool +type ( + Object = map[string]any + Array = []any + String = string + Number = json.Number + Bool = bool + RawMessage = json.RawMessage +) -var coloredError bool +var ( + coloredError bool + cjsonCommentRegex = regexp.MustCompile(`(?m)^[\t ]*#.*$`) +) func init() { coloredError = true } +// Marshal converts the given interface into json bytes +func Marshal(v any) (data []byte, err error) { + return golib.JsonBytes(v) +} + +// Encode marshals the given interface and writes the json bytes to the given writer +func Encode(w io.Writer, v any) (err error) { + data, err := Marshal(v) + if err != nil { + return err + } + _, err = w.Write(data) + if err != nil { + return err + } + return nil +} + +// UnmarshalString is a wrapper for Unmarshal for string input +func UnmarshalString(input string, output any) (err error) { + return Unmarshal([]byte(input), output) +} + +// Unmarshal decodes the input bytes into the output if it is valid cjson func Unmarshal(input []byte, output any) (err error) { // Remove # comments from template - var commentRegex = regexp.MustCompile(`(?m)^[\t ]*#.*$`) - tmplBytes := []byte(commentRegex.ReplaceAllString(string(input), ``)) + tmplBytes := cjsonCommentRegex.ReplaceAll(input, []byte{}) // Remove //, /* comments plus tailing commas tmplBytes = jsonc.ToJSON(tmplBytes) dec := json.NewDecoder(bytes.NewReader(tmplBytes)) dec.DisallowUnknownFields() + dec.UseNumber() // unmarshal into object err = dec.Decode(output) @@ -147,7 +179,7 @@ func lineAndCharacter(input string, offset int) (line int, character int, err er if offset > len(input) || offset < 0 { return 0, 0, fmt.Errorf("Couldn't find offset %d within the input.", offset) } - //humans count line from 1 + // humans count line from 1 line = 1 for _, b := range input[:offset] { if b == rune('\n') { diff --git a/pkg/lib/util/json_test.go b/pkg/lib/jsutil/json_test.go similarity index 86% rename from pkg/lib/util/json_test.go rename to pkg/lib/jsutil/json_test.go index 8f7f25b..9e21d82 100644 --- a/pkg/lib/util/json_test.go +++ b/pkg/lib/jsutil/json_test.go @@ -1,4 +1,4 @@ -package util +package jsutil import ( "fmt" @@ -95,8 +95,8 @@ welt:1 } for _, v := range testCases { - var out JsonObject - oErr := Unmarshal([]byte(v.iJson), &out) + var out Object + oErr := UnmarshalString(v.iJson, &out) go_test_utils.AssertErrorEquals(t, oErr, v.eError) } @@ -106,22 +106,22 @@ welt:1 func TestRemoveComments(t *testing.T) { testCases := []struct { iJson string - eOut JsonObject + eOut Object }{ { `{ "hallo":2 }`, - JsonObject{ - "hallo": float64(2), + Object{ + "hallo": Number("2"), }, }, { `{ "hallo":2 }`, - JsonObject{ - "hallo": float64(2), + Object{ + "hallo": Number("2"), }, }, { @@ -131,8 +131,8 @@ func TestRemoveComments(t *testing.T) { #line2 }`, - JsonObject{ - "hallo": float64(2), + Object{ + "hallo": Number("2"), }, }, { @@ -143,16 +143,21 @@ func TestRemoveComments(t *testing.T) { #line2 "hey":"ha" }`, - JsonObject{ - "hallo": float64(2), + Object{ + "hallo": Number("2"), "hey": "ha", }, }, } + var ( + out Object + err error + ) + for _, v := range testCases { - var out JsonObject - Unmarshal([]byte(v.iJson), &out) + err = UnmarshalString(v.iJson, &out) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) for k, v := range v.eOut { if out[k] != v { t.Errorf("[%s] Have %T '%v' != '%f' want", k, k, out[k], v) @@ -165,13 +170,13 @@ func TestRemoveComments(t *testing.T) { func TestCJSONUnmarshalSyntaxErr(t *testing.T) { testCases := []struct { cjsonString string - eObject JsonObject + eObject Object eError error }{ { `{"hallo":3}`, - JsonObject{ - "hallo": float64(3), + Object{ + "hallo": Number("3"), }, nil, }, @@ -243,8 +248,8 @@ func TestCJSONUnmarshalSyntaxErr(t *testing.T) { } for _, v := range testCases { - oObject := JsonObject{} - oErr := Unmarshal([]byte(v.cjsonString), &oObject) + oObject := Object{} + oErr := UnmarshalString(v.cjsonString, &oObject) go_test_utils.AssertErrorEquals(t, oErr, v.eError) if oErr == nil { @@ -269,8 +274,8 @@ func TestCJSONUnmarshalTypeErr(t *testing.T) { var oObject expectedStructure - oErr := Unmarshal( - []byte(cjsonString), + oErr := UnmarshalString( + cjsonString, &oObject, ) @@ -283,3 +288,10 @@ func TestCJSONUnmarshalTypeErr(t *testing.T) { }, ) } + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" + } + return err.Error() +} diff --git a/pkg/lib/report/parsing_functions.go b/pkg/lib/report/parsing_functions.go index d41e032..f975d9e 100755 --- a/pkg/lib/report/parsing_functions.go +++ b/pkg/lib/report/parsing_functions.go @@ -95,7 +95,8 @@ func ParseJSONResult(baseResult *ReportElement) []byte { func parseJSONStatsResult(baseResult *ReportElement) []byte { currUsername := "unknown" - if currUser, _ := user.Current(); currUser != nil { + currUser, _ := user.Current() + if currUser != nil { currUsername = currUser.Username } currPath, _ := os.Getwd() diff --git a/pkg/lib/report/report.go b/pkg/lib/report/report.go index a4b784a..949572b 100755 --- a/pkg/lib/report/report.go +++ b/pkg/lib/report/report.go @@ -145,12 +145,10 @@ func (r *ReportElement) getTestResult() *ReportElement { func (r ReportElement) getLog() []string { errors := make([]string, 0) - //root Errors - for _, singleMessage := range r.LogStorage { - errors = append(errors, singleMessage) - } + // root Errors + errors = append(errors, r.LogStorage...) - //Child errors + // Child errors for _, singleTest := range r.SubTests { errors = append(errors, singleTest.getLog()...) } diff --git a/pkg/lib/report/report_test.go b/pkg/lib/report/report_test.go index 7815713..453fc48 100644 --- a/pkg/lib/report/report_test.go +++ b/pkg/lib/report/report_test.go @@ -1,13 +1,13 @@ package report import ( - "encoding/json" "encoding/xml" "testing" "time" "github.com/programmfabrik/apitest/pkg/lib/compare" - "github.com/programmfabrik/apitest/pkg/lib/util" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" + go_test_utils "github.com/programmfabrik/go-test-utils" ) func TestReportStructure(t *testing.T) { @@ -51,42 +51,47 @@ func TestReportGetJSONResult(t *testing.T) { jsonResult := r.GetTestResult(ParseJSONResult) expResult := []byte(`{ - "failures": 2, - "sub_tests": [ - { - "failures": 1, - "name": "Level 1 - 1" - }, - { - "failures": 1, - "name": "Level 1 - 2", - "sub_tests": [ - { - "failures": 0, - "name": "Level 2 - 1" - }, - { - "failures": 1, - "name": "Level 2 - 2", - "sub_tests": [ - { - "failures": 1, - "name": "Level 3 - 1" - } - ] - } - ] - } - ] -}`) - - var expJ, realJ any - - util.Unmarshal(jsonResult, &realJ) - util.Unmarshal(expResult, &expJ) - - equal, _ := compare.JsonEqual(expJ, realJ, compare.ComparisonContext{}) - + "failures": 2, + "sub_tests": [ + { + "failures": 1, + "name": "Level 1 - 1" + }, + { + "failures": 1, + "name": "Level 1 - 2", + "sub_tests": [ + { + "failures": 0, + "name": "Level 2 - 1" + }, + { + "failures": 1, + "name": "Level 2 - 2", + "sub_tests": [ + { + "failures": 1, + "name": "Level 3 - 1" + } + ] + } + ] + } + ] + }`) + + var ( + expJ, realJ any + err error + ) + + err = jsutil.Unmarshal(jsonResult, &realJ) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = jsutil.Unmarshal(expResult, &expJ) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + + equal, err := compare.JsonEqual(expJ, realJ, compare.ComparisonContext{}) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if !equal.Equal { t.Errorf("Wanted:\n%s\n\nGot:\n%s", expResult, jsonResult) t.Fail() @@ -117,10 +122,16 @@ func TestReportGetJUnitResult(t *testing.T) { ` - var expX, realX xmlRoot + var ( + expX, realX xmlRoot + expJ, realJ any + err error + ) - xml.Unmarshal([]byte(expResult), &expX) - xml.Unmarshal(jsonResult, &realX) + err = xml.Unmarshal([]byte(expResult), &expX) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = xml.Unmarshal(jsonResult, &realX) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) realX.Id = "" realX.Name = "" @@ -133,18 +144,20 @@ func TestReportGetJUnitResult(t *testing.T) { } } - expJBytes, _ := json.Marshal(expX) - realJBytes, _ := json.Marshal(realX) - - var expJ, realJ any - - util.Unmarshal(expJBytes, &expJ) - util.Unmarshal(realJBytes, &realJ) + expJBytes, err := jsutil.Marshal(expX) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + realJBytes, err := jsutil.Marshal(realX) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) - equal, _ := compare.JsonEqual(expJ, realJ, compare.ComparisonContext{}) + err = jsutil.Unmarshal(expJBytes, &expJ) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = jsutil.Unmarshal(realJBytes, &realJ) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + equal, err := compare.JsonEqual(expJ, realJ, compare.ComparisonContext{}) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if !equal.Equal { - // t.Error(equal.Failures) + // t.Error(equal.Failures) t.Errorf("Wanted:\n%s\n\nGot:\n%s", expJBytes, realJBytes) t.Fail() } @@ -220,7 +233,8 @@ func TestReportGetStatsResult(t *testing.T) { jsonResult := r.GetTestResult(parseJSONStatsResult) var statsRep statsReport - util.Unmarshal(jsonResult, &statsRep) + err := jsutil.Unmarshal(jsonResult, &statsRep) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if statsRep.Version != r.Version { t.Fatalf("Got version %s, expected %s", statsRep.Version, r.Version) @@ -241,3 +255,10 @@ func TestReportGetStatsResult(t *testing.T) { t.Fatalf("Manifest 3 in group %d, expected to be in 1", statsRep.Manifests[2].Group) } } + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" + } + return err.Error() +} diff --git a/pkg/lib/template/template_funcs.go b/pkg/lib/template/template_funcs.go index 125bdb1..7ba8841 100644 --- a/pkg/lib/template/template_funcs.go +++ b/pkg/lib/template/template_funcs.go @@ -1,7 +1,6 @@ package template import ( - "encoding/json" "fmt" "io" "path/filepath" @@ -10,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/programmfabrik/apitest/pkg/lib/csv" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/apitest/pkg/lib/util" ) @@ -22,7 +22,7 @@ func n(count any) (elements []struct{}, err error) { return make([]struct{}, v), nil case int: return make([]struct{}, v), nil - case json.Number: + case jsutil.Number: var i int64 i, err = v.Int64() if err != nil { @@ -38,13 +38,13 @@ func n(count any) (elements []struct{}, err error) { func rowsToMap(keyCol, valCol string, rows []map[string]any) (retMap map[string]any, err error) { retMap = make(map[string]any) - //If there is no keyCol, return empty map + // If there is no keyCol, return empty map if keyCol == "" { return } for _, singlewRow := range rows { - //Get typed map index + // Get typed map index if singlewRow[keyCol] == nil { continue } @@ -54,14 +54,14 @@ func rowsToMap(keyCol, valCol string, rows []map[string]any) (retMap map[string] } if valCol != "" { - //Normal row + // Normal row val := singlewRow[valCol] if val == nil { val = "" } retMap[mapIndex] = val } else { - //Row with not valCol + // Row with not valCol retMap[mapIndex] = singlewRow } } @@ -111,7 +111,7 @@ func pivotRows(key, typ string, rows []map[string]any) (sheet []map[string]any, sheetRow[sheetKey] = v case "json": var i any - err = json.Unmarshal([]byte(v), &i) + err = jsutil.UnmarshalString(v, &i) if err == nil { sheetRow[sheetKey] = i } @@ -120,8 +120,8 @@ func pivotRows(key, typ string, rows []map[string]any) (sheet []map[string]any, case "float64": sheetRow[sheetKey], _ = strconv.ParseFloat(v, 10) case "number": - var number json.Number - err = json.Unmarshal([]byte(v), &number) + var number jsutil.Number + err = jsutil.UnmarshalString(v, &number) if err == nil { sheetRow[sheetKey] = number } @@ -240,7 +240,7 @@ func subtract(b, a any) (result any, err error) { } func intOrFloatFromJsonNumber(a any) any { - aN, ok := a.(json.Number) + aN, ok := a.(jsutil.Number) if !ok { return a } diff --git a/pkg/lib/template/template_loader.go b/pkg/lib/template/template_loader.go index 8096173..5097ecd 100644 --- a/pkg/lib/template/template_loader.go +++ b/pkg/lib/template/template_loader.go @@ -6,7 +6,6 @@ import ( "database/sql" "encoding/base64" "encoding/hex" - "encoding/json" "fmt" "net/url" "reflect" @@ -16,6 +15,7 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/programmfabrik/apitest/pkg/lib/datastore" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/golib" "github.com/sirupsen/logrus" "golang.org/x/mod/semver" @@ -29,6 +29,13 @@ import ( "github.com/tidwall/gjson" ) +var ( + delimsRegex = regexp.MustCompile(`(?m)^[\t ]*(?://|/\*)[\t ]*template-delims:[\t ]*([^\t ]+)[\t ]+([^\t\n ]+).*$`) + removeCheckRegex = regexp.MustCompile(`(?m)^[\t ]*(?://|/\*)[\t ]*template-remove-tokens:[\t ]*(.+)$`) + splitRegex = regexp.MustCompile(`[\t ]`) + removeCommentRegex = regexp.MustCompile(`(?m)^[\t ]*//.*$`) +) + // delimiters as go template parsing options type delimiters struct { Left string @@ -69,31 +76,22 @@ func (loader *Loader) Render( ctx any) (res []byte, err error) { var ( - delimsRE *regexp.Regexp - removeCheckRE *regexp.Regexp - splitRE *regexp.Regexp - removeCommentRE *regexp.Regexp - matches []string - replacements []string - newTmplStr string + matches []string + replacements []string + newTmplStr string ) - delimsRE = regexp.MustCompile(`(?m)^[\t ]*(?://|/\*)[\t ]*template-delims:[\t ]*([^\t ]+)[\t ]+([^\t\n ]+).*$`) - removeCheckRE = regexp.MustCompile(`(?m)^[\t ]*(?://|/\*)[\t ]*template-remove-tokens:[\t ]*(.+)$`) - splitRE = regexp.MustCompile(`[\t ]`) - removeCommentRE = regexp.MustCompile(`(?m)^[\t ]*//.*$`) - // First check for custom delimiters - matches = delimsRE.FindStringSubmatch(string(tmplBytes)) + matches = delimsRegex.FindStringSubmatch(string(tmplBytes)) if len(matches) == 3 { loader.Delimiters.Left, loader.Delimiters.Right = matches[1], matches[2] } // Second check for placeholders removal - matches = removeCheckRE.FindStringSubmatch(string(tmplBytes)) + matches = removeCheckRegex.FindStringSubmatch(string(tmplBytes)) replacements = []string{} if len(matches) > 1 { - placeholders := splitRE.Split(matches[1], -1) + placeholders := splitRegex.Split(matches[1], -1) for _, s := range placeholders { replacements = append(replacements, s, "") } @@ -103,7 +101,7 @@ func (loader *Loader) Render( // Remove comments from template if comments are not the delimiters if loader.Delimiters.Left != "//" { - tmplBytes = []byte(removeCommentRE.ReplaceAllString(string(tmplBytes), ``)) + tmplBytes = []byte(removeCommentRegex.ReplaceAllString(string(tmplBytes), ``)) } funcMap := template.FuncMap{ @@ -277,7 +275,7 @@ func (loader *Loader) Render( return loader.datastore.Get(key) }, "unmarshal": func(s string) (gj any, err error) { - err = util.Unmarshal([]byte(s), &gj) + err = jsutil.UnmarshalString(s, &gj) if err != nil { return nil, err } @@ -285,11 +283,7 @@ func (loader *Loader) Render( }, "N": n, "marshal": func(data any) (jsonBytes string, err error) { - var ( - bytes []byte - ) - - bytes, err = json.Marshal(data) + bytes, err := jsutil.Marshal(data) if err != nil { return "", err } diff --git a/pkg/lib/template/template_loader_test.go b/pkg/lib/template/template_loader_test.go index df118cf..d72ae56 100644 --- a/pkg/lib/template/template_loader_test.go +++ b/pkg/lib/template/template_loader_test.go @@ -8,7 +8,6 @@ import ( "github.com/programmfabrik/apitest/pkg/lib/datastore" "github.com/programmfabrik/apitest/pkg/lib/test_utils" "github.com/programmfabrik/golib" - "github.com/stretchr/testify/assert" "github.com/programmfabrik/apitest/pkg/lib/api" "github.com/programmfabrik/apitest/pkg/lib/filesystem" @@ -52,8 +51,10 @@ func TestRender_LoadFile_withParam(t *testing.T) { target := []byte(`{{ .Param1 }}`) filesystem.Fs = afero.NewMemMapFs() - filesystem.Fs.MkdirAll("some/path", 0755) - afero.WriteFile(filesystem.Fs, "some/path/somefile.json", target, 0644) + err := filesystem.Fs.MkdirAll("some/path", 0755) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "some/path/somefile.json", target, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "some/path", nil) @@ -67,9 +68,12 @@ func TestRenderWithDataStore_LoadFile_withParam_recursive(t *testing.T) { last := []byte(`{{ .Param1 }}`) filesystem.Fs = afero.NewMemMapFs() - filesystem.Fs.MkdirAll("root/a/b/", 0755) - afero.WriteFile(filesystem.Fs, "root/a/next.tmpl", next, 0644) - afero.WriteFile(filesystem.Fs, "root/a/b/last.tmpl", last, 0644) + err := filesystem.Fs.MkdirAll("root/a/b/", 0755) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "root/a/next.tmpl", next, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "root/a/b/last.tmpl", last, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "root", nil) @@ -83,15 +87,16 @@ func TestBigIntRender(t *testing.T) { inputNumber := "132132132182323" - resp, _ := api.NewResponse(golib.IntRef(200), nil, nil, strings.NewReader(fmt.Sprintf(`{"bigINT":%s}`, inputNumber)), nil, api.ResponseFormat{}) + resp, err := api.NewResponse(golib.IntRef(200), nil, nil, strings.NewReader(fmt.Sprintf(`{"bigINT":%s}`, inputNumber)), nil, api.ResponseFormat{}) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) - respJson, _ := resp.ServerResponseToJsonString(false) - store.SetWithGjson(respJson, map[string]string{"testINT": "body.bigINT"}) + respJson, err := resp.ServerResponseToJsonString(false) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = store.SetWithGjson(respJson, map[string]string{"testINT": "body.bigINT"}) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) res, err := loader.Render([]byte(`{{ datastore "testINT" }}`), "", nil) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if string(res) != inputNumber { t.Error(string(res), " != ", inputNumber) } @@ -104,6 +109,7 @@ func TestRowsToMapTemplate(t *testing.T) { loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "some/path", nil) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) t.Log(string(res)) go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) @@ -119,8 +125,10 @@ func TestRender_LoadFile_GJson_Params(t *testing.T) { target := []byte(`{ "key": ["{{ .Param1 }}", "{{ .Param2 }}"]}`) filesystem.Fs = afero.NewMemMapFs() - filesystem.Fs.MkdirAll("some/path", 0755) - afero.WriteFile(filesystem.Fs, "some/path/somefile.json", target, 0644) + err := filesystem.Fs.MkdirAll("some/path", 0755) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "some/path/somefile.json", target, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "some/path", nil) @@ -134,85 +142,129 @@ func TestRender_LoadFile_CSV(t *testing.T) { expected string expectErr bool }{ - {`id,name,friends,ages -int64,string,"string,array","int64,array" -1,simon,"simon,jonas,stefan","21,24,12"`, `[{"ages":[21,24,12],"friends":["simon","jonas","stefan"],"id":1,"name":"simon"}]`, false}, - {`id,name,friends,ages - -int64,string,"string,array","int64,array" - -,,, -1,simon,"simon,jonas,stefan","21,24,12"`, `[{"ages":[21,24,12],"friends":["simon","jonas","stefan"],"id":1,"name":"simon"}]`, false}, - {`id,name,friends,ages - -int64,string,"string,array","int64,array" - -,,, -,hans,, -1,simon,"simon,jonas,stefan","21,24,12"`, `[{"ages":[],"friends":[],"id":0,"name":"hans"},{"ages":[21,24,12],"friends":["simon","jonas","stefan"],"id":1,"name":"simon"}]`, false}, - {`id,name,friends,ages - -int64,string,"string,array","int64,array" - -,,, -,hans,, -1,simon,"simon,jo -nas,ste -fan","21,24,12"`, ``, true}, - - {`id,name,friends,ages - -int64,string,"string,array","int64,array" - -,,, -#,hans,, -1,simon,"simon,""jo -nas"",""a,b""","21,24,12"`, `[{"ages":[21,24,12],"friends":["simon","jo\nnas","a,b"],"id":1,"name":"simon"}]`, false}, - {`id,name,friends,ages - -int64,string,"string,array" - -,,, -#,hans,, -1,simon,"simon,""jo -nas"",""a,b""","21,24,12"`, `[{"friends":["simon","jo\nnas","a,b"],"id":1,"name":"simon"}]`, false}, - {`id,name, ,ages - -int64,string,"string,array" - -,,, -#,hans,, -1,simon,"simon,""jo -nas"",""a,b""","21,24,12"`, `[{"id":1,"name":"simon"}]`, false}, - {`id,name,de,friends,ages - -int64,string,,"string,array" - -,,, -#,hans,, -1,simon,LALALALA,"simon,""jo -nas"",""a,b""","21,24,12"`, `[{"friends":["simon","jo\nnas","a,b"],"id":1,"name":"simon"}]`, false}, - {`id,name,de,friends,ages - -int64,string,s,"string,array" - -,,, -#,hans,, -1,simon,LALALALA,"simon,""jo -nas"",""a,b""","21,24,12"`, ``, true}, - {`id,name,,ages -int64,string,"string,array","int64,array"`, `[]`, false}, - {`id,name,friends,ages -int64,string,"stringer,array","int64,array"`, ``, true}, + { + `id,name,friends,ages + int64,string,"string,array","int64,array" + 1,simon,"simon,jonas,stefan","21,24,12"`, + `[{"ages":[21,24,12],"friends":["simon","jonas","stefan"],"id":1,"name":"simon"}]`, + false, + }, + { + `id,name,friends,ages + + int64,string,"string,array","int64,array" + + ,,, + 1,simon,"simon,jonas,stefan","21,24,12"`, + `[{"ages":[21,24,12],"friends":["simon","jonas","stefan"],"id":1,"name":"simon"}]`, + false, + }, + { + `id,name,friends,ages + + int64,string,"string,array","int64,array" + + ,,, + ,hans,, + 1,simon,"simon,jonas,stefan","21,24,12"`, + `[{"ages":[],"friends":[],"id":0,"name":"hans"},{"ages":[21,24,12],"friends":["simon","jonas","stefan"],"id":1,"name":"simon"}]`, + false, + }, + { + `id,name,friends,ages + + int64,string,"string,array","int64,array" + + ,,, + ,hans,, + 1,simon,"simon,jo + nas,ste + fan","21,24,12"`, + ``, + true, + }, + { + `id,name,friends,ages + + int64,string,"string,array","int64,array" + + ,,, + #,hans,, + 1,simon,"simon,""jo + nas"",""a,b""","21,24,12"`, + `[{"ages":[21,24,12],"friends":["simon","jo\nnas","a,b"],"id":1,"name":"simon"}]`, + false, + }, + { + `id,name,friends,ages + + int64,string,"string,array" + + ,,, + #,hans,, + 1,simon,"simon,""jo + nas"",""a,b""","21,24,12"`, + `[{"friends":["simon","jo\nnas","a,b"],"id":1,"name":"simon"}]`, + false, + }, + { + `id,name, ,ages + + int64,string,"string,array" + + ,,, + #,hans,, + 1,simon,"simon,""jo + nas"",""a,b""","21,24,12"`, + `[{"id":1,"name":"simon"}]`, + false, + }, + { + `id,name,de,friends,ages + + int64,string,,"string,array" + + ,,, + #,hans,, + 1,simon,LALALALA,"simon,""jo + nas"",""a,b""","21,24,12"`, + `[{"friends":["simon","jo\nnas","a,b"],"id":1,"name":"simon"}]`, + false, + }, + { + `id,name,de,friends,ages + + int64,string,s,"string,array" + + ,,, + #,hans,, + 1,simon,LALALALA,"simon,""jo + nas"",""a,b""","21,24,12"`, + ``, + true, + }, + { + `id,name,,ages + int64,string,"string,array","int64,array"`, + `[]`, + false, + }, + { + `id,name,friends,ages + int64,string,"stringer,array","int64,array"`, + ``, + true, + }, } for i, testCase := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { root := []byte(`{{ file_csv "somefile.json" ',' | marshal }}`) - target := []byte(testCase.csv) + target := []byte(strings.ReplaceAll(testCase.csv, "\t", "")) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "somefile.json", target, 0644) + err := afero.WriteFile(filesystem.Fs, "somefile.json", target, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "", nil) @@ -230,7 +282,7 @@ int64,string,"stringer,array","int64,array"`, ``, true}, } } else { if !testCase.expectErr { - t.Errorf("No error expected %q", err) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) } } }) @@ -243,32 +295,34 @@ func TestRender_LoadFile_CSV_And_Row_To_Map(t *testing.T) { expected string expectedErr error }{ - {`id,name,friends,ages + { + `id,name,friends,ages -int64,string,"string,array","int64,array" + int64,string,"string,array","int64,array" -,,, -,hans,, -1,simon,"simon,jonas,stefan","21,24,12"`, `{"hans":[],"simon":[21,24,12]}`, nil}, + ,,, + ,hans,, + 1,simon,"simon,jonas,stefan","21,24,12"`, + `{"hans":[],"simon":[21,24,12]}`, + nil, + }, } for i, testCase := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { root := []byte(`{{ file_csv "somefile.json" ',' | rows_to_map "name" "ages" | marshal }}`) - target := []byte(testCase.csv) + target := []byte(strings.ReplaceAll(testCase.csv, "\t", "")) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "somefile.json", target, 0644) + err := afero.WriteFile(filesystem.Fs, "somefile.json", target, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "", nil) - eErrString := "" if testCase.expectedErr != nil { - eErrString = testCase.expectedErr.Error() - } - go_test_utils.AssertErrorContains(t, err, eErrString) - - if err == nil { + go_test_utils.AssertErrorContains(t, err, testCase.expectedErr.Error()) + } else { + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertStringEquals(t, string(res), testCase.expected) } }) @@ -282,32 +336,40 @@ func TestRender_LoadFile_CSV_Gjson(t *testing.T) { expected string expectedErr error }{ - {`id,name,friends,ages -int64,string,"string,array","int64,array" -1,simon,"simon,jonas,stefan","21,24,12"`, `0.name`, `"simon"`, nil}, - {`id,name,friends,ages -int64,string,"string,array","int64,array" -1,simon,"simon,jonas,stefan","21,24,12" -2,stefan,"simon,jonas,stefan","21,24,12"`, `1.friends.2`, `"stefan"`, fmt.Errorf("")}, + { + `id,name,friends,ages + int64,string,"string,array","int64,array" + 1,simon,"simon,jonas,stefan","21,24,12"`, + `0.name`, + `"simon"`, + nil, + }, + { + `id,name,friends,ages + int64,string,"string,array","int64,array" + 1,simon,"simon,jonas,stefan","21,24,12" + 2,stefan,"simon,jonas,stefan","21,24,12"`, + `1.friends.2`, + `"stefan"`, + fmt.Errorf(""), + }, } for i, testCase := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { root := []byte(fmt.Sprintf(`{{ file_csv "somefile.json" ',' | marshal | gjson %q }}`, testCase.gjson)) - target := []byte(testCase.csv) + target := []byte(strings.ReplaceAll(testCase.csv, "\t", "")) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "somefile.json", target, 0644) + err := afero.WriteFile(filesystem.Fs, "somefile.json", target, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "", nil) - eErrString := "" if testCase.expectedErr != nil { - eErrString = testCase.expectedErr.Error() - } - go_test_utils.AssertErrorContains(t, err, eErrString) - - if err == nil { + go_test_utils.AssertErrorContains(t, err, testCase.expectedErr.Error()) + } else { + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) go_test_utils.AssertStringEquals(t, string(res), testCase.expected) } }) @@ -321,64 +383,94 @@ func TestRender_LoadFile_GJson(t *testing.T) { expected string expectedErr error }{ - {`body.1._id`, `{ - "body": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - }`, `2`, nil}, - {`body.0`, `{ - "body": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - }`, `{ - "_id": 1 - }`, nil}, - {`body.invalid`, `{ - "body": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - }`, ``, fmt.Errorf("'body.invalid' was not found or was empty string")}, //beware wrong access returns nothing - {`body.array`, `{ - "body": { - "array": [ - 1, - 2 + { + `body.1._id`, + `{ + "body": [ + { + "_id": 1 + }, + { + "_id": 2 + } ] - } - }`, `[ - 1, - 2 - ]`, nil}, - {`body.array.1`, `{ - "body": { - "array": [ - 1, - 2 + }`, + `2`, + nil, + }, + { + `body.0`, + `{ + "body": [ + { + "_id": 1 + }, + { + "_id": 2 + } ] - } - }`, `2`, nil}, - {`body.0._id`, `{ - "body": [ - { - "_id": 2 + }`, + `{ + "_id": 1 + }`, + nil, + }, + { + `body.invalid`, + `{ + "body": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + }`, + ``, + fmt.Errorf("'body.invalid' was not found or was empty string"), // beware wrong access returns nothing + }, + { + `body.array`, + `{ + "body": { + "array": [ + 1, + 2 + ] } - ] - }`, `2`, nil}, + }`, + `[ + 1, + 2 + ]`, + nil, + }, + { + `body.array.1`, + `{ + "body": { + "array": [ + 1, + 2 + ] + } + }`, + `2`, + nil, + }, + { + `body.0._id`, + `{ + "body": [ + { + "_id": 2 + } + ] + }`, + `2`, + nil, + }, } for i, testCase := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { @@ -386,19 +478,22 @@ func TestRender_LoadFile_GJson(t *testing.T) { target := []byte(testCase.json) filesystem.Fs = afero.NewMemMapFs() - filesystem.Fs.MkdirAll("some/path", 0755) - afero.WriteFile(filesystem.Fs, "some/path/somefile.json", target, 0644) + err := filesystem.Fs.MkdirAll("some/path", 0755) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "some/path/somefile.json", target, 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) loader := NewLoader(datastore.NewStore(false)) res, err := loader.Render(root, "some/path", nil) - eErrString := "" if testCase.expectedErr != nil { - eErrString = testCase.expectedErr.Error() - } - go_test_utils.AssertErrorContains(t, err, eErrString) - - if err != nil { - go_test_utils.AssertStringEquals(t, string(res), testCase.expected) + go_test_utils.AssertErrorContains(t, err, testCase.expectedErr.Error()) + } else { + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + go_test_utils.AssertStringEquals( + t, + strings.ReplaceAll(string(res), "\t", ""), + strings.ReplaceAll(testCase.expected, "\t", ""), + ) } }) } @@ -420,9 +515,7 @@ func Test_DataStore_GJson(t *testing.T) { ) store := datastore.NewStore(false) jsonResponse, err := response.ServerResponseToJsonString(false) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) store.AppendResponse(string(jsonResponse)) loader := NewLoader(store) @@ -452,7 +545,6 @@ func Test_DataStore_GJson(t *testing.T) { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { root := []byte(fmt.Sprintf(`{{ datastore 0 | gjson %q }}`, testCase.path)) res, err := loader.Render(root, "some/path", nil) - go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) test_utils.AssertJsonStringEquals(t, string(res), testCase.expected) }) @@ -465,35 +557,46 @@ func TestReplaceHost(t *testing.T) { err error ) - h, err = replaceHost("localhost:9925", "192.168.122.56:8978") - if !assert.Error(t, err) { - return - } - - h, err = replaceHost("localhost:9925", "192.168.122.56") - if !assert.NoError(t, err) { - return - } - if !assert.Equal(t, "192.168.122.56:9925", h) { - return - } - - h, err = replaceHost("http://localhost:8978/images", "martins.mac") - if !assert.NoError(t, err) { - return - } - if !assert.Equal(t, "http://martins.mac:8978/images", h) { - return - } - - h, err = replaceHost("http://localhost:8978", "192.168.122.56") - if !assert.NoError(t, err) { - return + testCases := []struct { + srcUrl string + serverHost string + expected string + expErr bool + }{ + { + "localhost:9925", + "192.168.122.56:8978", + "xxx", + true, + }, + { + "localhost:9925", + "192.168.122.56", + "192.168.122.56:9925", + false, + }, + { + "http://localhost:8978/images", + "martins.mac", + "http://martins.mac:8978/images", + false, + }, + { + "http://localhost:8978", + "192.168.122.56", + "http://192.168.122.56:8978", + false, + }, } - if !assert.Equal(t, "http://192.168.122.56:8978", h) { - return + for _, testCase := range testCases { + h, err = replaceHost(testCase.srcUrl, testCase.serverHost) + if !testCase.expErr { + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + go_test_utils.AssertStringEquals(t, testCase.expected, h) + } else { + go_test_utils.ExpectError(t, err, "expected error for invalid host") + } } - } func errorStringIfNotNil(err error) (errS string) { diff --git a/pkg/lib/template/util.go b/pkg/lib/template/util.go index 340af58..81ac37e 100644 --- a/pkg/lib/template/util.go +++ b/pkg/lib/template/util.go @@ -1,9 +1,9 @@ package template import ( - "encoding/json" "fmt" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/apitest/pkg/lib/util" ) @@ -25,27 +25,29 @@ func LoadManifestDataAsObject(data any, manifestDir string, loader Loader) (path return nil, res, fmt.Errorf("rendering request: %w", err) } - var jsonObject util.JsonObject - var jsonArray util.JsonArray + var ( + jsonObject jsutil.Object + jsonArray jsutil.Array + ) - if err = util.Unmarshal(requestBytes, &jsonObject); err != nil { - if err = util.Unmarshal(requestBytes, &jsonArray); err == nil { + if err = jsutil.Unmarshal(requestBytes, &jsonObject); err != nil { + if err = jsutil.Unmarshal(requestBytes, &jsonArray); err == nil { return pathSpec, jsonArray, nil } return nil, res, fmt.Errorf("unmarshalling: %w", err) } return pathSpec, jsonObject, nil - case util.JsonObject: + case jsutil.Object: return nil, typedData, nil - case util.JsonArray: + case jsutil.Array: return nil, typedData, nil default: return nil, res, fmt.Errorf("specification needs to be string[@...] or jsonObject but is: %v", data) } } -func LoadManifestDataAsRawJson(data any, manifestDir string) (pathSpec *util.PathSpec, res json.RawMessage, err error) { +func LoadManifestDataAsRawJson(data any, manifestDir string) (pathSpec *util.PathSpec, res jsutil.RawMessage, err error) { switch typedData := data.(type) { case []byte: err = res.UnmarshalJSON(typedData) @@ -60,12 +62,12 @@ func LoadManifestDataAsRawJson(data any, manifestDir string) (pathSpec *util.Pat return nil, res, fmt.Errorf("loading fileFromPathSpec: %w", err) } return pathSpec, res, nil - case util.JsonObject, util.JsonArray: - jsonMar, err := json.Marshal(typedData) + case jsutil.Object, jsutil.Array: + jsonMar, err := jsutil.Marshal(typedData) if err != nil { return nil, res, fmt.Errorf("marshaling: %w", err) } - if err = util.Unmarshal(jsonMar, &res); err != nil { + if err = jsutil.Unmarshal(jsonMar, &res); err != nil { return nil, res, fmt.Errorf("unmarshalling: %w", err) } return nil, res, nil diff --git a/pkg/lib/test_utils/test_utils.go b/pkg/lib/test_utils/test_utils.go index a1da6b8..1cc0d84 100644 --- a/pkg/lib/test_utils/test_utils.go +++ b/pkg/lib/test_utils/test_utils.go @@ -1,11 +1,11 @@ package test_utils import ( - "encoding/json" "log" "net/http" "testing" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" go_test_utils "github.com/programmfabrik/go-test-utils" "github.com/programmfabrik/golib" "github.com/sergi/go-diff/diffmatchpatch" @@ -37,7 +37,7 @@ func AssertJsonStringEquals(t testing.TB, expected, got string) { expectedMinified, gotMinifed []byte ) - err := json.Unmarshal([]byte(expected), &expectedJson) + err := jsutil.UnmarshalString(expected, &expectedJson) if err != nil { t.Error(err) } @@ -46,7 +46,7 @@ func AssertJsonStringEquals(t testing.TB, expected, got string) { log.Fatal(err) } - err = json.Unmarshal([]byte(got), &gotJson) + err = jsutil.UnmarshalString(got, &gotJson) if err != nil { t.Error(err) } diff --git a/pkg/lib/util/file.go b/pkg/lib/util/file.go index 619608b..be6fde0 100644 --- a/pkg/lib/util/file.go +++ b/pkg/lib/util/file.go @@ -42,10 +42,10 @@ func openLocalFile(path, rootDir string) (f io.ReadCloser, err error) { func LocalPath(path, rootDir string) (absPath string) { if strings.HasPrefix(path, "./") { - //Path relative to binary + // Path relative to binary absPath = path } else if strings.HasPrefix(path, "/") { - //Absolute Path + // Absolute Path absPath = filepath.Join("/", path) } else { absPath = filepath.Join(rootDir, path) diff --git a/pkg/lib/util/file_test.go b/pkg/lib/util/file_test.go index bdfdae3..3d48b66 100644 --- a/pkg/lib/util/file_test.go +++ b/pkg/lib/util/file_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/programmfabrik/apitest/pkg/lib/filesystem" + go_test_utils "github.com/programmfabrik/go-test-utils" "github.com/spf13/afero" ) @@ -30,7 +31,8 @@ func TestOpenFileOrUrl(t *testing.T) { })) defer ts.Close() - afero.WriteFile(filesystem.Fs, "localExists", []byte("Hallo ich bin da!"), 0644) + err := afero.WriteFile(filesystem.Fs, "localExists", []byte("Hallo ich bin da!"), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) tests := []testOpenFileStruct{ { @@ -65,9 +67,7 @@ func TestOpenFileOrUrl(t *testing.T) { } else { defer file.Close() data, err := io.ReadAll(file) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if md5.Sum(data) != v.expHash { t.Errorf("Got '%s' != '%s' Exp", md5.Sum(data), v.expHash) @@ -81,47 +81,46 @@ func TestOpenFileOrUrl(t *testing.T) { func TestOpenLocalFile(t *testing.T) { filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, filepath.Join("/", "root", "file.json"), []byte("From ROOT /"), 0644) - afero.WriteFile(filesystem.Fs, "file.json", []byte("From binary ./"), 0644) - afero.WriteFile(filesystem.Fs, filepath.Join("/", "manifestdir", "file.json"), []byte("From manifest"), 0644) + err := afero.WriteFile(filesystem.Fs, filepath.Join("/", "root", "file.json"), []byte("From ROOT /"), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, "file.json", []byte("From binary ./"), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + err = afero.WriteFile(filesystem.Fs, filepath.Join("/", "manifestdir", "file.json"), []byte("From manifest"), 0644) + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) reader, err := openLocalFile("/root/file.json", "/manifestdir") - if err != nil { - t.Fatalf("Root File: %s", err.Error()) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) + defer reader.Close() rootFile, err := io.ReadAll(reader) - if err != nil { - t.Fatalf("Root File: %s", err.Error()) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if string(rootFile) != "From ROOT /" { t.Errorf("Wrong file content for root file: %s", string(rootFile)) } reader, err = openLocalFile("file.json", "/manifestdir") - if err != nil { - t.Fatalf("Manifest File: %s", err.Error()) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) defer reader.Close() manifestFile, err := io.ReadAll(reader) - if err != nil { - t.Fatalf("Manifest File: %s", err.Error()) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if string(manifestFile) != "From manifest" { t.Errorf("Wrong file content for manifest file: %s", string(manifestFile)) } reader, err = openLocalFile("./file.json", "/manifestdir") - if err != nil { - t.Fatalf("Binary File: %s", err.Error()) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) defer reader.Close() binaryFile, err := io.ReadAll(reader) - if err != nil { - t.Fatalf("Binary File: %s", err.Error()) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if string(binaryFile) != "From binary ./" { t.Errorf("Wrong file content for binary file: %s", string(binaryFile)) } } + +func errorStringIfNotNil(err error) (errS string) { + if err == nil { + return "" + } + return err.Error() +} diff --git a/pkg/lib/util/oauth_test.go b/pkg/lib/util/oauth_test.go index 1551434..ced2a66 100644 --- a/pkg/lib/util/oauth_test.go +++ b/pkg/lib/util/oauth_test.go @@ -6,6 +6,8 @@ import ( "net/http" "net/http/httptest" "testing" + + go_test_utils "github.com/programmfabrik/go-test-utils" ) func TestGetPasswordCredentialsToken(t *testing.T) { @@ -33,9 +35,7 @@ func TestGetPasswordCredentialsToken(t *testing.T) { Secret: "whatever", } _, err := cfg.GetPasswordCredentialsAuthToken("hey", "yo") - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -45,9 +45,7 @@ func TestGetPasswordCredentialsToken(t *testing.T) { Secret: "whatever", } _, err = cfg.GetPasswordCredentialsAuthToken("hey", "yo") - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -57,9 +55,7 @@ func TestGetPasswordCredentialsToken(t *testing.T) { Secret: theSecret, } token, err := cfg.GetPasswordCredentialsAuthToken("hey", "yo") - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if token == nil { t.Fatal("No token nor error returned") } @@ -96,9 +92,7 @@ func TestGetClientCredentialsToken(t *testing.T) { Secret: "whatever", } _, err := cfg.GetClientCredentialsAuthToken() - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -108,9 +102,7 @@ func TestGetClientCredentialsToken(t *testing.T) { Secret: "whatever", } _, err = cfg.GetClientCredentialsAuthToken() - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -120,9 +112,7 @@ func TestGetClientCredentialsToken(t *testing.T) { Secret: theSecret, } token, err := cfg.GetClientCredentialsAuthToken() - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if token == nil { t.Fatal("No token nor error returned") } @@ -186,9 +176,7 @@ func TestGetCodeToken(t *testing.T) { Secret: "whatever", } _, err := cfg.GetCodeAuthToken() - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -200,9 +188,7 @@ func TestGetCodeToken(t *testing.T) { Secret: "whatever", } _, err = cfg.GetCodeAuthToken() - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -212,9 +198,7 @@ func TestGetCodeToken(t *testing.T) { Secret: theSecret, } _, err = cfg.GetCodeAuthToken("username", theUsername, "password", thePassword) - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -226,9 +210,7 @@ func TestGetCodeToken(t *testing.T) { Secret: theSecret, } _, err = cfg.GetCodeAuthToken("username", theUsername, "password", "wrong") - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -240,9 +222,7 @@ func TestGetCodeToken(t *testing.T) { Secret: theSecret, } token, err := cfg.GetCodeAuthToken("username", theUsername, "password", thePassword) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if token == nil { t.Fatal("No token nor error returned") } @@ -263,9 +243,7 @@ func TestGetCodeToken(t *testing.T) { Secret: theSecret, } token, err = cfg.GetCodeAuthToken("guest_access", "1") - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if token == nil { t.Fatal("No token nor error returned") } @@ -315,9 +293,7 @@ func TestGetAuthToken(t *testing.T) { RedirectURL: rs.URL, } _, err := cfg.GetAuthToken() - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -325,9 +301,7 @@ func TestGetAuthToken(t *testing.T) { RedirectURL: rs.URL, } _, err = cfg.GetAuthToken("username", theUsername, "password", thePassword) - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -337,9 +311,7 @@ func TestGetAuthToken(t *testing.T) { RedirectURL: rs.URL, } _, err = cfg.GetAuthToken("username", theUsername, "password", "wrong") - if err == nil { - t.Fatal("Expected error") - } + go_test_utils.ExpectError(t, err, "Expected error") cfg = OAuthClientConfig{ Client: theClient, @@ -349,9 +321,7 @@ func TestGetAuthToken(t *testing.T) { RedirectURL: rs.URL, } token, err := cfg.GetAuthToken("username", theUsername, "password", thePassword) - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if token == nil { t.Fatal("No token nor error returned") } @@ -370,9 +340,7 @@ func TestGetAuthToken(t *testing.T) { RedirectURL: rs.URL, } token, err = cfg.GetAuthToken("guest_access", "1") - if err != nil { - t.Fatal(err) - } + go_test_utils.ExpectNoError(t, err, errorStringIfNotNil(err)) if token == nil { t.Fatal("No token nor error returned") } diff --git a/pkg/lib/util/util.go b/pkg/lib/util/util.go index 818c99c..bfd77a3 100644 --- a/pkg/lib/util/util.go +++ b/pkg/lib/util/util.go @@ -3,7 +3,6 @@ package util import ( "bytes" "encoding/csv" - "encoding/json" "fmt" "regexp" "strconv" @@ -12,11 +11,14 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/clbanning/mxj" libcsv "github.com/programmfabrik/apitest/pkg/lib/csv" + "github.com/programmfabrik/apitest/pkg/lib/jsutil" "github.com/programmfabrik/golib" "github.com/xuri/excelize/v2" "golang.org/x/net/html" ) +var xmlDeclarationRegex = regexp.MustCompile(`<\?xml.*?\?>`) + func removeFromJsonArray(input []any, removeIndex int) (output []any) { output = make([]any, len(input)) copy(output, input) @@ -39,7 +41,7 @@ func GetStringFromInterface(queryParam any) (v string, err error) { return fmt.Sprintf("%d", t), nil default: var jsonVal []byte - jsonVal, err = json.Marshal(t) + jsonVal, err = jsutil.Marshal(t) return string(jsonVal), err } } @@ -50,12 +52,10 @@ func GetStringFromInterface(queryParam any) (v string, err error) { // - "xml2": use mxj.NewMapXmlSeq (simpler format) func Xml2Json(rawXml []byte, format string) (jsonStr []byte, err error) { var ( - mv mxj.Map - xmlDeclarationRegex *regexp.Regexp - replacedXML []byte + mv mxj.Map + replacedXML []byte ) - xmlDeclarationRegex = regexp.MustCompile(`<\?xml.*?\?>`) replacedXML = xmlDeclarationRegex.ReplaceAll(rawXml, []byte{}) switch format { @@ -147,7 +147,7 @@ func Xlsx2Json(rawXlsx []byte, sheetIdx int) (jsonStr []byte, err error) { return []byte{}, fmt.Errorf("could not parse csv: %w", err) } - jsonStr, err = json.Marshal(csvData) + jsonStr, err = jsutil.Marshal(csvData) if err != nil { return []byte{}, fmt.Errorf("could not convert to json: %w", err) } diff --git a/pkg/lib/util/util_test.go b/pkg/lib/util/util_test.go index 4713300..7a6708c 100644 --- a/pkg/lib/util/util_test.go +++ b/pkg/lib/util/util_test.go @@ -1,22 +1,33 @@ package util -import "testing" +import ( + "testing" + + "github.com/programmfabrik/apitest/pkg/lib/jsutil" +) func TestRemoveFromJsonArray(t *testing.T) { - input := []any{JsonString("0"), JsonString("1"), JsonString("2"), JsonString("3"), JsonString("4"), JsonString("5")} + input := []any{ + jsutil.String("0"), + jsutil.String("1"), + jsutil.String("2"), + jsutil.String("3"), + jsutil.String("4"), + jsutil.String("5"), + } output := removeFromJsonArray(input, 2) - if len(output) != 5 || output[2] != JsonString("3") { + if len(output) != 5 || output[2] != jsutil.String("3") { t.Errorf("Wrong slice removal: %s", output) } output2 := removeFromJsonArray(input, 5) - if len(output2) != 5 || output2[4] != JsonString("4") { + if len(output2) != 5 || output2[4] != jsutil.String("4") { t.Errorf("Wrong slice removal: %s", output2) } output3 := removeFromJsonArray(input, 0) - if len(output3) != 5 || output3[4] != JsonString("5") { + if len(output3) != 5 || output3[4] != jsutil.String("5") { t.Errorf("Wrong slice removal: %s", output3) } } diff --git a/test/_res/assets/number1.txt b/test/_res/assets/number1.txt new file mode 100644 index 0000000..9b4c81a --- /dev/null +++ b/test/_res/assets/number1.txt @@ -0,0 +1 @@ + 42.35 diff --git a/test/_res/assets/number2.txt b/test/_res/assets/number2.txt new file mode 100644 index 0000000..4841020 --- /dev/null +++ b/test/_res/assets/number2.txt @@ -0,0 +1 @@ + 87 diff --git a/test/response/format/number/high_numbers.json b/test/response/format/number/high_numbers.json new file mode 100644 index 0000000..568c461 --- /dev/null +++ b/test/response/format/number/high_numbers.json @@ -0,0 +1,41 @@ +{ + "name": "bounce-json: numbers out of int64 range", + "request": { + "server_url": "http://localhost:9999", + "endpoint": "bounce-json", + "method": "POST", + "body": { + "number1": 1234, + "number2": 1234.567, + "number3": 2147483648, // max for int32: 2,147,483,647 + "number4": 9223372036854775808, // max for int64: 9,223,372,036,854,775,807 + "number5": -9223372036854775808, + "number6": 1234e4567, + "number7": -1234e4567, + "number8": 1.7e308, // max for float64: 1.7E+308 + "number9": 1.8e308, + "number10": -1.8e308, + "number11": 46546848348651535484445.5435848343165, + "number12": -87786184587458754158.7846848 + } + }, + "response": { + "statuscode": 200, + "body": { + "body": { + "number1": 1234, + "number2": 1234.567, + "number3": 2147483648, + "number4": 9223372036854775808, + "number5": -9223372036854775808, + "number6": 1234e4567, + "number7": -1234e4567, + "number8": 1.7e308, + "number9": 1.8e308, + "number10": -1.8e308, + "number11": 46546848348651535484445.5435848343165, + "number12": -87786184587458754158.7846848 + } + } + } +} \ No newline at end of file diff --git a/test/response/format/number/manifest.json b/test/response/format/number/manifest.json index ca4809b..7a66e33 100644 --- a/test/response/format/number/manifest.json +++ b/test/response/format/number/manifest.json @@ -4,9 +4,9 @@ "dir": "../../_res/assets", "testmode": false }, - "name": "check 2^53-1 int numbers", + "name": "check numbers in and out of int range", "tests": [ - // cmp equal - "@number.json" + "@number.json" + ,"@high_numbers.json" ] } \ No newline at end of file diff --git a/test/response/format/number/number.json b/test/response/format/number/number.json index 470615f..8179737 100644 --- a/test/response/format/number/number.json +++ b/test/response/format/number/number.json @@ -1,5 +1,5 @@ { - "name": "bounce-json", + "name": "bounce-json: numbers in int64 range", "request": { "server_url": "http://localhost:9999", "endpoint": "bounce-json",