From 10238b5659b4234b2a6369006a64da4675efe4e5 Mon Sep 17 00:00:00 2001 From: anna Date: Thu, 11 Jul 2024 17:56:47 +0400 Subject: [PATCH 1/3] added new type: equals but without order --- stub/storage.go | 56 +++++++++++++++++++++++++++++++++++++++++++------ stub/stub.go | 3 +++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/stub/storage.go b/stub/storage.go index 4faf810d..aeb928d9 100644 --- a/stub/storage.go +++ b/stub/storage.go @@ -8,6 +8,7 @@ import ( "reflect" "regexp" "sync" + "sort" "github.com/lithammer/fuzzysearch/fuzzy" ) @@ -81,6 +82,13 @@ func findStub(stub *findStubPayload) (*Output, error) { } } + if expect := stubrange.Input.EqualsSets; expect != nil { + closestMatch = append(closestMatch, closeMatch{"equals_sets", expect}) + if equals_sets(stub.Data, expect) { + return &stubrange.Output, nil + } + } + if expect := stubrange.Input.Contains; expect != nil { closestMatch = append(closestMatch, closeMatch{"contains", expect}) if contains(stubrange.Input.Contains, stub.Data) { @@ -186,18 +194,50 @@ func regexMatch(expect, actual interface{}) bool { } func equals(expect, actual map[string]interface{}) bool { - return find(expect, actual, true, true, deepEqual) + return find(expect, actual, true, true, deepEqual, false) +} + +func equals_sets(expect, actual map[string]interface{}) bool { + return find(expect, actual, true, true, deepEqual, true) } func contains(expect, actual map[string]interface{}) bool { - return find(expect, actual, true, false, deepEqual) + return find(expect, actual, true, false, deepEqual, false) } func matches(expect, actual map[string]interface{}) bool { - return find(expect, actual, true, false, regexMatch) + return find(expect, actual, true, false, regexMatch, false) +} + +type sortableSlice []interface{} + +func (s sortableSlice) Len() int { + return len(s) +} + +func (s sortableSlice) Less(i, j int) bool { + return fmt.Sprint(s[i]) < fmt.Sprint(s[j]) +} + +func (s sortableSlice) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func equalsIgnoreOrder(expect, actual interface{}) bool { + expectSlice, expectOk := expect.([]interface{}) + actualSlice, actualOk := actual.([]interface{}) + if !expectOk || !actualOk { + return false + } + if len(expectSlice) != len(actualSlice) { + return false + } + sort.Sort(sortableSlice(expectSlice)) + sort.Sort(sortableSlice(actualSlice)) + return reflect.DeepEqual(expectSlice, actualSlice) } -func find(expect, actual interface{}, acc, exactMatch bool, f matchFunc) bool { +func find(expect, actual interface{}, acc, exactMatch bool, f matchFunc, ignoreOrder bool) bool { // circuit brake if acc == false { @@ -225,9 +265,13 @@ func find(expect, actual interface{}, acc, exactMatch bool, f matchFunc) bool { } } + if expectArrayOk && actualArrayOk && ignoreOrder { + return equalsIgnoreOrder(expectArrayValue, actualArrayValue) + } + for expectItemIndex, expectItemValue := range expectArrayValue { actualItemValue := actualArrayValue[expectItemIndex] - acc = find(expectItemValue, actualItemValue, acc, exactMatch, f) + acc = find(expectItemValue, actualItemValue, acc, exactMatch, f, ignoreOrder) } return acc @@ -256,7 +300,7 @@ func find(expect, actual interface{}, acc, exactMatch bool, f matchFunc) bool { for expectItemKey, expectItemValue := range expectMapValue { actualItemValue := actualMapValue[expectItemKey] - acc = find(expectItemValue, actualItemValue, acc, exactMatch, f) + acc = find(expectItemValue, actualItemValue, acc, exactMatch, f, ignoreOrder) } return acc diff --git a/stub/stub.go b/stub/stub.go index 5509e7ea..39697a87 100644 --- a/stub/stub.go +++ b/stub/stub.go @@ -56,6 +56,7 @@ type Stub struct { type Input struct { Equals map[string]interface{} `json:"equals"` + EqualsSets map[string]interface{} `json:"equals_sets"` Contains map[string]interface{} `json:"contains"` Matches map[string]interface{} `json:"matches"` } @@ -118,6 +119,8 @@ func validateStub(stub *Stub) error { break case stub.Input.Equals != nil: break + case stub.Input.EqualsSets != nil: + break case stub.Input.Matches != nil: break default: From 6a5956d1b3a2f390ed58466007bf944307ebd4bc Mon Sep 17 00:00:00 2001 From: anna Date: Fri, 9 Aug 2024 16:32:42 +0500 Subject: [PATCH 2/3] edits after CR & updated readme & added tests --- Readme.md | 25 ++++++++++++++++++++++ stub/storage.go | 54 +++++++++++++++++++---------------------------- stub/stub.go | 10 ++++----- stub/stub_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 105 insertions(+), 38 deletions(-) diff --git a/Readme.md b/Readme.md index bbabbab4..648b5358 100644 --- a/Readme.md +++ b/Readme.md @@ -151,6 +151,31 @@ Nested fields are allowed for input matching too for all JSON data types. (`stri } ``` +**equals_unordered** will match the exact field name and value of input into expected stub, except lists (wich are compared as sets). example stub JSON: + + +``` +{ + . + . + "input":{ + "equals_unordered":{ + "name":"gripmock", + "greetings": { + "english": "Hello World!", + "indonesian": "Halo Dunia!", + "turkish": "Merhaba Dünya!" + }, + "ok": true, + "numbers": [4, 8, 15, 16, 23, 42] + "null": null + } + } + . + . +} +``` + **contains** will match input that has the value declared expected fields. example stub JSON: ``` { diff --git a/stub/storage.go b/stub/storage.go index aeb928d9..2d21637c 100644 --- a/stub/storage.go +++ b/stub/storage.go @@ -7,8 +7,8 @@ import ( "log" "reflect" "regexp" + "sort" "sync" - "sort" "github.com/lithammer/fuzzysearch/fuzzy" ) @@ -82,9 +82,9 @@ func findStub(stub *findStubPayload) (*Output, error) { } } - if expect := stubrange.Input.EqualsSets; expect != nil { - closestMatch = append(closestMatch, closeMatch{"equals_sets", expect}) - if equals_sets(stub.Data, expect) { + if expect := stubrange.Input.EqualsUnordered; expect != nil { + closestMatch = append(closestMatch, closeMatch{"equals_unordered", expect}) + if equalsUnordered(stub.Data, expect) { return &stubrange.Output, nil } } @@ -197,7 +197,7 @@ func equals(expect, actual map[string]interface{}) bool { return find(expect, actual, true, true, deepEqual, false) } -func equals_sets(expect, actual map[string]interface{}) bool { +func equalsUnordered(expect, actual map[string]interface{}) bool { return find(expect, actual, true, true, deepEqual, true) } @@ -209,32 +209,22 @@ func matches(expect, actual map[string]interface{}) bool { return find(expect, actual, true, false, regexMatch, false) } -type sortableSlice []interface{} - -func (s sortableSlice) Len() int { - return len(s) -} - -func (s sortableSlice) Less(i, j int) bool { - return fmt.Sprint(s[i]) < fmt.Sprint(s[j]) -} - -func (s sortableSlice) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - func equalsIgnoreOrder(expect, actual interface{}) bool { - expectSlice, expectOk := expect.([]interface{}) - actualSlice, actualOk := actual.([]interface{}) - if !expectOk || !actualOk { - return false - } - if len(expectSlice) != len(actualSlice) { - return false - } - sort.Sort(sortableSlice(expectSlice)) - sort.Sort(sortableSlice(actualSlice)) - return reflect.DeepEqual(expectSlice, actualSlice) + expectSlice, expectOk := expect.([]interface{}) + actualSlice, actualOk := actual.([]interface{}) + if !expectOk || !actualOk { + return false + } + if len(expectSlice) != len(actualSlice) { + return false + } + sort.Slice(expectSlice, func(i, j int) bool { + return fmt.Sprint(expectSlice[i]) < fmt.Sprint(expectSlice[j]) + }) + sort.Slice(actualSlice, func(i, j int) bool { + return fmt.Sprint(actualSlice[i]) < fmt.Sprint(actualSlice[j]) + }) + return reflect.DeepEqual(expectSlice, actualSlice) } func find(expect, actual interface{}, acc, exactMatch bool, f matchFunc, ignoreOrder bool) bool { @@ -266,8 +256,8 @@ func find(expect, actual interface{}, acc, exactMatch bool, f matchFunc, ignoreO } if expectArrayOk && actualArrayOk && ignoreOrder { - return equalsIgnoreOrder(expectArrayValue, actualArrayValue) - } + return equalsIgnoreOrder(expectArrayValue, actualArrayValue) + } for expectItemIndex, expectItemValue := range expectArrayValue { actualItemValue := actualArrayValue[expectItemIndex] diff --git a/stub/stub.go b/stub/stub.go index 39697a87..541e35ea 100644 --- a/stub/stub.go +++ b/stub/stub.go @@ -55,10 +55,10 @@ type Stub struct { } type Input struct { - Equals map[string]interface{} `json:"equals"` - EqualsSets map[string]interface{} `json:"equals_sets"` - Contains map[string]interface{} `json:"contains"` - Matches map[string]interface{} `json:"matches"` + Equals map[string]interface{} `json:"equals"` + EqualsUnordered map[string]interface{} `json:"equals_unordered"` + Contains map[string]interface{} `json:"contains"` + Matches map[string]interface{} `json:"matches"` } type Output struct { @@ -119,7 +119,7 @@ func validateStub(stub *Stub) error { break case stub.Input.Equals != nil: break - case stub.Input.EqualsSets != nil: + case stub.Input.EqualsUnordered != nil: break case stub.Input.Matches != nil: break diff --git a/stub/stub_test.go b/stub/stub_test.go index 4225abb3..7782d7ea 100644 --- a/stub/stub_test.go +++ b/stub/stub_test.go @@ -48,7 +48,7 @@ func TestStub(t *testing.T) { return httptest.NewRequest("GET", "/", nil) }, handler: listStub, - expect: "{\"Testing\":{\"TestMethod\":[{\"Input\":{\"equals\":{\"Hola\":\"Mundo\"},\"contains\":null,\"matches\":null},\"Output\":{\"data\":{\"Hello\":\"World\"},\"error\":\"\"}}]}}\n", + expect: "{\"Testing\":{\"TestMethod\":[{\"Input\":{\"equals\":{\"Hola\":\"Mundo\"},\"equals_unordered\":null,\"contains\":null,\"matches\":null},\"Output\":{\"data\":{\"Hello\":\"World\"},\"error\":\"\"}}]}}\n", }, { name: "find stub equals", @@ -99,6 +99,58 @@ func TestStub(t *testing.T) { handler: handleFindStub, expect: "{\"data\":{\"Hello\":\"World\"},\"error\":\"\"}\n", }, + { + name: "add stub equals_unordered", + mock: func() *http.Request { + payload := `{ + "service": "TestingUnordered", + "method":"TestMethod", + "input": { + "equals_unordered": { + "ids": [1,2] + } + }, + "output":{ + "data":{ + "hello":"world" + } + } + }` + return httptest.NewRequest("POST", "/add", bytes.NewReader([]byte(payload))) + }, + handler: addStub, + expect: `Success add stub`, + }, + { + name: "find stub equals_unordered", + mock: func() *http.Request { + payload := `{ + "service":"TestingUnordered", + "method":"TestMethod", + "data":{ + "ids":[1,2] + } + }` + return httptest.NewRequest("GET", "/find", bytes.NewReader([]byte(payload))) + }, + handler: handleFindStub, + expect: "{\"data\":{\"hello\":\"world\"},\"error\":\"\"}\n", + }, + { + name: "find stub equals_unordered reversed", + mock: func() *http.Request { + payload := `{ + "service":"TestingUnordered", + "method":"TestMethod", + "data":{ + "ids":[2,1] + } + }` + return httptest.NewRequest("GET", "/find", bytes.NewReader([]byte(payload))) + }, + handler: handleFindStub, + expect: "{\"data\":{\"hello\":\"world\"},\"error\":\"\"}\n", + }, { name: "add stub contains", mock: func() *http.Request { From aaa2e0e48d9fc9c68af3a213e77b1c48d8f1fe55 Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 12 Aug 2024 16:13:31 +0400 Subject: [PATCH 3/3] Update Readme.md --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 648b5358..cd031197 100644 --- a/Readme.md +++ b/Readme.md @@ -122,7 +122,7 @@ Stub will respond with the expected response only if the request matches any rul So if you do a `curl -X POST -d '{"service":"Greeter","method":"SayHello","data":{"name":"gripmock"}}' localhost:4771/find` stub service will find a match from listed stubs stored there. ### Input Matching Rule -Input matching has 3 rules to match an input: **equals**,**contains** and **regex** +Input matching has 4 rules to match an input: **equals**, **equals_unordered**, **contains** and **regex**
Nested fields are allowed for input matching too for all JSON data types. (`string`, `bool`, `array`, etc.)
@@ -151,7 +151,7 @@ Nested fields are allowed for input matching too for all JSON data types. (`stri } ``` -**equals_unordered** will match the exact field name and value of input into expected stub, except lists (wich are compared as sets). example stub JSON: +**equals_unordered** will match the exact field name and value of input into expected stub, except lists (which are compared as sets). example stub JSON: ```