Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New input matching rule: equals_unordered #168

Merged
merged 3 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
<br>
Nested fields are allowed for input matching too for all JSON data types. (`string`, `bool`, `array`, etc.)
<br>
Expand Down Expand Up @@ -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 (which 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:
```
{
Expand Down
46 changes: 40 additions & 6 deletions stub/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"reflect"
"regexp"
"sort"
"sync"

"github.com/lithammer/fuzzysearch/fuzzy"
Expand Down Expand Up @@ -81,6 +82,13 @@ func findStub(stub *findStubPayload) (*Output, error) {
}
}

if expect := stubrange.Input.EqualsUnordered; expect != nil {
closestMatch = append(closestMatch, closeMatch{"equals_unordered", expect})
if equalsUnordered(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) {
Expand Down Expand Up @@ -186,18 +194,40 @@ 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 equalsUnordered(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)
}

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.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) bool {
func find(expect, actual interface{}, acc, exactMatch bool, f matchFunc, ignoreOrder bool) bool {

// circuit brake
if acc == false {
Expand Down Expand Up @@ -225,9 +255,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
Expand Down Expand Up @@ -256,7 +290,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
Expand Down
9 changes: 6 additions & 3 deletions stub/stub.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ type Stub struct {
}

type Input struct {
Equals map[string]interface{} `json:"equals"`
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 {
Expand Down Expand Up @@ -118,6 +119,8 @@ func validateStub(stub *Stub) error {
break
case stub.Input.Equals != nil:
break
case stub.Input.EqualsUnordered != nil:
break
case stub.Input.Matches != nil:
break
default:
Expand Down
54 changes: 53 additions & 1 deletion stub/stub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down