diff --git a/README-ru.md b/README-ru.md index fb77e92..fa0a6f3 100644 --- a/README-ru.md +++ b/README-ru.md @@ -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" }' +``` + + diff --git a/README.md b/README.md index 637568b..a0277e3 100644 --- a/README.md +++ b/README.md @@ -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" }' +``` diff --git a/checker/response_db/response_db.go b/checker/response_db/response_db.go index db6f4c0..bb7b14e 100644 --- a/checker/response_db/response_db.go +++ b/checker/response_db/response_db.go @@ -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 } @@ -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) } } diff --git a/checker/response_db/response_db_test.go b/checker/response_db/response_db_test.go new file mode 100644 index 0000000..675f1e7 --- /dev/null +++ b/checker/response_db/response_db_test.go @@ -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") + } + } + } +} diff --git a/compare/compare.go b/compare/compare.go index b61c0e4..613a019 100644 --- a/compare/compare.go +++ b/compare/compare.go @@ -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 } diff --git a/models/test.go b/models/test.go index 984b0ad..434b767 100644 --- a/models/test.go +++ b/models/test.go @@ -46,6 +46,7 @@ type TestInterface interface { NeedsCheckingValues() bool IgnoreArraysOrdering() bool DisallowExtraFields() bool + IgnoreDbOrdering() bool // Clone returns copy of current object Clone() TestInterface diff --git a/testloader/yaml_file/test.go b/testloader/yaml_file/test.go index 9d9ca36..23ab56d 100644 --- a/testloader/yaml_file/test.go +++ b/testloader/yaml_file/test.go @@ -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 }