Skip to content

Commit

Permalink
Merge pull request #154 from ikramanop/feature/ignore-db-ordering
Browse files Browse the repository at this point in the history
New: ignore db ordering in dbResponse feature
  • Loading branch information
Nikita Tomchik authored May 26, 2022
2 parents cba2087 + 1aba9d5 commit c909012
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 8 deletions.
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) {
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

0 comments on commit c909012

Please sign in to comment.