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: ignore db ordering in dbResponse feature #154

Merged
merged 3 commits into from
May 26, 2022
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
21 changes: 21 additions & 0 deletions README-ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -1290,3 +1290,24 @@ Example:
- '{"code":"GIFT100000-000003","partner_id":1}'
```

#### Игнорирование порядка записей в ответе на запрос в базу данных

Можно использовать флаг `ignoreDbOrdering` в секции `comparisonParams` для включения/выключения функционала проверки полученных строк в ответе от базы данных не по порядку.
Это может пригодиться для обхода использования оператора `ORDER BY` в запросах.

- `ignoreDbOrdering` - значение true/false.

Пример:
```yaml
comparisonParams:
ignoreDbOrdering: true
...
dbQuery: >
SELECT id, name, surname FROM users LIMIT 2

dbResponse:
- '{ "id": 2, "name": "John", "surname": "Doe" }'
- '{ "id": 1, "name": "Jane", "surname": "Doe" }'
```


19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1291,3 +1291,22 @@ Example:
- '{"code":"GIFT100000-000003","partner_id":1}'
```

#### Ignoring ordering in DB response

You can use `ignoreDbOrdering` flag in `comparisonParams` section to toggle DB response ordering ignore feature.
This can be used to bypass using `ORDER BY` operators in query.

- `ignoreDbOrdering` - true/false value.

Example:
```yaml
comparisonParams:
ignoreDbOrdering: true
...
dbQuery: >
SELECT id, name, surname FROM users LIMIT 2

dbResponse:
- '{ "id": 2, "name": "John", "surname": "Doe" }'
- '{ "id": 1, "name": "Jane", "surname": "Doe" }'
```
83 changes: 75 additions & 8 deletions checker/response_db/response_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ func (c *ResponseDbChecker) Check(t models.TestInterface, result *models.Result)
return errors, nil
}
// compare responses as json lists
checkErrors, err := compareDbResp(t, result)
var checkErrors []error
if t.IgnoreDbOrdering() {
checkErrors, err = compareDbRespWithoutOrdering(t.DbResponseJson(), result.DbResponse, t.GetName())
} else {
checkErrors, err = compareDbResp(t.DbResponseJson(), result.DbResponse, t.GetName(), result.DbQuery)
}
if err != nil {
return nil, err
}
Expand All @@ -72,35 +77,97 @@ func (c *ResponseDbChecker) Check(t models.TestInterface, result *models.Result)
return errors, nil
}

func compareDbResp(t models.TestInterface, result *models.Result) ([]error, error) {
func compareDbRespWithoutOrdering(expected, actual []string, testName string) ([]error, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мне кажется, что можно сделать проще и читабельнее в разы, просто отредактировав исходную функцию проверки

import (
     ...
     "sort"
     ...
)
...

func compareDbResp(expected, actual []string, testName string, query interface{}, reorder bool) ([]error, error) {
	var errors []error
	var actualJson interface{}
	var expectedJson interface{}
	
	if reorder {
	    sort.Strings(expected)
	    sort.Strings(actual)
	}
	...

var errors []error
var actualJsons []interface{}
var expectedJsons []interface{}

// gather expected and actual rows
for i, row := range expected {
// decode expected row
var expectedJson interface{}
if err := json.Unmarshal([]byte(row), &expectedJson); err != nil {
return nil, fmt.Errorf(
"invalid JSON in the expected DB response for test %s:\n row #%d:\n %s\n error:\n%s",
testName,
i,
row,
err.Error(),
)
}
expectedJsons = append(expectedJsons, expectedJson)
// decode actual row
var actualJson interface{}
if err := json.Unmarshal([]byte(actual[i]), &actualJson); err != nil {
return nil, fmt.Errorf(
"invalid JSON in the actual DB response for test %s:\n row #%d:\n %s\n error:\n%s",
testName,
i,
actual[i],
err.Error(),
)
}
actualJsons = append(actualJsons, actualJson)
}

remove := func(array []interface{}, i int) []interface{} {
array[i] = array[len(array)-1]
return array[:len(array)-1]
}

// compare actual and expected rows
for _, actualRow := range actualJsons {
for i, expectedRow := range expectedJsons {
if diff := pretty.Compare(expectedRow, actualRow); diff == "" {
expectedJsons = remove(expectedJsons, i)
break
}
}
}

if len(expectedJsons) > 0 {
errorString := "missing expected items in database:"

for _, expectedRow := range expectedJsons {
expectedRowJson, _ := json.Marshal(expectedRow)
errorString += fmt.Sprintf("\n - %s", color.CyanString("%s", expectedRowJson))
}

errors = append(errors, fmt.Errorf(errorString))
}

return errors, nil
}

func compareDbResp(expected, actual []string, testName string, query interface{}) ([]error, error) {
var errors []error
var actualJson interface{}
var expectedJson interface{}

for i, row := range t.DbResponseJson() {
for i, row := range expected {
// decode expected row
if err := json.Unmarshal([]byte(row), &expectedJson); err != nil {
return nil, fmt.Errorf(
"invalid JSON in the expected DB response for test %s:\n row #%d:\n %s\n error:\n%s",
t.GetName(),
testName,
i,
row,
err.Error(),
)
}
// decode actual row
if err := json.Unmarshal([]byte(result.DbResponse[i]), &actualJson); err != nil {
if err := json.Unmarshal([]byte(actual[i]), &actualJson); err != nil {
return nil, fmt.Errorf(
"invalid JSON in the actual DB response for test %s:\n row #%d:\n %s\n error:\n%s",
t.GetName(),
testName,
i,
result.DbResponse[i],
actual[i],
err.Error(),
)
}

// compare responses row as jsons
if err := compareDbResponseRow(expectedJson, actualJson, result.DbQuery); err != nil {
if err := compareDbResponseRow(expectedJson, actualJson, query); err != nil {
errors = append(errors, err)
}
}
Expand Down
103 changes: 103 additions & 0 deletions checker/response_db/response_db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package response_db

import "testing"

func TestCompareDbRespWithoutOrdering(t *testing.T) {
tests := []struct {
name string
expected []string
actual []string
fail bool
}{
{
name: "one line",
expected: []string{"{ \"name\": \"John\" }"},
actual: []string{"{ \"name\": \"John\" }"},
fail: false,
},
{
name: "two lines",
expected: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
actual: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
fail: false,
},
{
name: "two lines; different order",
expected: []string{"{ \"surname\": \"Doe\" }", "{ \"name\": \"John\" }"},
actual: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
fail: false,
},
{
name: "error",
expected: []string{"{ \"name\": \"Jane\" }", "{ \"surname\": \"Doe\" }"},
actual: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
fail: true,
},
}

for _, tt := range tests {
errors, err := compareDbRespWithoutOrdering(tt.expected, tt.actual, tt.name)
if tt.fail {
if err == nil && len(errors) == 0 {
t.Errorf("expected errors")
}
} else {
if err != nil {
t.Error(err)
}
if len(errors) > 0 {
t.Errorf("got errors")
}
}
}
}

func TestCompareDbResp(t *testing.T) {
tests := []struct {
name string
expected []string
actual []string
fail bool
}{
{
name: "one line",
expected: []string{"{ \"name\": \"John\" }"},
actual: []string{"{ \"name\": \"John\" }"},
fail: false,
},
{
name: "two lines",
expected: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
actual: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
fail: false,
},
{
name: "two lines; different order",
expected: []string{"{ \"surname\": \"Doe\" }", "{ \"name\": \"John\" }"},
actual: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
fail: true,
},
{
name: "error",
expected: []string{"{ \"name\": \"Jane\" }", "{ \"surname\": \"Doe\" }"},
actual: []string{"{ \"name\": \"John\" }", "{ \"surname\": \"Doe\" }"},
fail: true,
},
}

for _, tt := range tests {
errors, err := compareDbResp(tt.expected, tt.actual, tt.name, "")
if tt.fail {
if err == nil && len(errors) == 0 {
t.Errorf("expected errors")
}
} else {
if err != nil {
t.Error(err)
}
if len(errors) > 0 {
t.Errorf("got errors")
}
}
}
}
1 change: 1 addition & 0 deletions compare/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type CompareParams struct {
IgnoreValues bool `json:"ignoreValues" yaml:"ignoreValues"`
IgnoreArraysOrdering bool `json:"ignoreArraysOrdering" yaml:"ignoreArraysOrdering"`
DisallowExtraFields bool `json:"disallowExtraFields" yaml:"disallowExtraFields"`
IgnoreDbOrdering bool `json:"IgnoreDbOrdering" yaml:"ignoreDbOrdering"`
failFast bool // End compare operation after first error
}

Expand Down
1 change: 1 addition & 0 deletions models/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type TestInterface interface {
NeedsCheckingValues() bool
IgnoreArraysOrdering() bool
DisallowExtraFields() bool
IgnoreDbOrdering() bool

// Clone returns copy of current object
Clone() TestInterface
Expand Down
4 changes: 4 additions & 0 deletions testloader/yaml_file/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func (t *Test) DisallowExtraFields() bool {
return t.ComparisonParams.DisallowExtraFields
}

func (t *Test) IgnoreDbOrdering() bool {
return t.ComparisonParams.IgnoreDbOrdering
}

func (t *Test) Fixtures() []string {
return t.FixtureFiles
}
Expand Down