From ba67e819e2c8d3a528013f41dc8ac482292efef6 Mon Sep 17 00:00:00 2001 From: Lev Marder Date: Fri, 6 May 2022 17:51:25 +0300 Subject: [PATCH 1/2] ignore db ordering in dbResponse feature --- README-ru.md | 21 ++++++++ README.md | 19 +++++++ checker/response_db/response_db.go | 69 ++++++++++++++++++++++++- models/test.go | 1 + testloader/yaml_file/test.go | 4 ++ testloader/yaml_file/test_definition.go | 1 + 6 files changed, 114 insertions(+), 1 deletion(-) diff --git a/README-ru.md b/README-ru.md index 70f88ed..031d4e1 100644 --- a/README-ru.md +++ b/README-ru.md @@ -1271,3 +1271,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 c670d0f..3bda468 100644 --- a/README.md +++ b/README.md @@ -1272,3 +1272,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..6eb3e02 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, result) + } else { + checkErrors, err = compareDbResp(t, result) + } if err != nil { return nil, err } @@ -72,6 +77,68 @@ func (c *ResponseDbChecker) Check(t models.TestInterface, result *models.Result) return errors, nil } +func compareDbRespWithoutOrdering(t models.TestInterface, result *models.Result) ([]error, error) { + var errors []error + var actualJsons []interface{} + var expectedJsons []interface{} + + // gather expected and actual rows + for i, row := range t.DbResponseJson() { + // 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", + t.GetName(), + i, + row, + err.Error(), + ) + } + expectedJsons = append(expectedJsons, expectedJson) + // decode actual row + var actualJson interface{} + if err := json.Unmarshal([]byte(result.DbResponse[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(), + i, + result.DbResponse[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(t models.TestInterface, result *models.Result) ([]error, error) { var errors []error var actualJson interface{} 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 } diff --git a/testloader/yaml_file/test_definition.go b/testloader/yaml_file/test_definition.go index 48effe8..01648f0 100644 --- a/testloader/yaml_file/test_definition.go +++ b/testloader/yaml_file/test_definition.go @@ -41,6 +41,7 @@ type comparisonParams 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"` } type scriptParams struct { From 7433636f3b83dd9095c3525f2f2d860c83bd88eb Mon Sep 17 00:00:00 2001 From: Lev Marder Date: Wed, 11 May 2022 18:57:58 +0300 Subject: [PATCH 2/2] tests for response_db added --- checker/response_db/response_db.go | 30 +++---- checker/response_db/response_db_test.go | 103 ++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 checker/response_db/response_db_test.go diff --git a/checker/response_db/response_db.go b/checker/response_db/response_db.go index 6eb3e02..bb7b14e 100644 --- a/checker/response_db/response_db.go +++ b/checker/response_db/response_db.go @@ -65,9 +65,9 @@ func (c *ResponseDbChecker) Check(t models.TestInterface, result *models.Result) // compare responses as json lists var checkErrors []error if t.IgnoreDbOrdering() { - checkErrors, err = compareDbRespWithoutOrdering(t, result) + checkErrors, err = compareDbRespWithoutOrdering(t.DbResponseJson(), result.DbResponse, t.GetName()) } else { - checkErrors, err = compareDbResp(t, result) + checkErrors, err = compareDbResp(t.DbResponseJson(), result.DbResponse, t.GetName(), result.DbQuery) } if err != nil { return nil, err @@ -77,19 +77,19 @@ func (c *ResponseDbChecker) Check(t models.TestInterface, result *models.Result) return errors, nil } -func compareDbRespWithoutOrdering(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 t.DbResponseJson() { + 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", - t.GetName(), + testName, i, row, err.Error(), @@ -98,12 +98,12 @@ func compareDbRespWithoutOrdering(t models.TestInterface, result *models.Result) expectedJsons = append(expectedJsons, expectedJson) // decode actual row var actualJson interface{} - 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(), ) } @@ -139,35 +139,35 @@ func compareDbRespWithoutOrdering(t models.TestInterface, result *models.Result) return errors, nil } -func compareDbResp(t models.TestInterface, result *models.Result) ([]error, error) { +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") + } + } + } +}