Skip to content

Commit

Permalink
[ISSUE-47] implement JSON diff
Browse files Browse the repository at this point in the history
  • Loading branch information
siller174 committed Nov 16, 2023
1 parent 31fc18a commit 3a43356
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 7 deletions.
36 changes: 33 additions & 3 deletions asserts/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,42 @@ package json
import (
"fmt"

jd "github.com/josephburnett/jd/lib"
"github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj"
"github.com/ozontech/cute"
cuteErrors "github.com/ozontech/cute/errors"
)

// Diff is a function to compare two jsons
func Diff(original string) cute.AssertBody {
return func(body []byte) error {
originalJSON, err := jd.ReadJsonString(original)
if err != nil {
return fmt.Errorf("could not parse original json in Diff error: '%s'", err)
}

bodyJSON, err := jd.ReadJsonString(string(body))
if err != nil {
return fmt.Errorf("could not parse body json in Diff error: '%s'", err)
}

diff := originalJSON.Diff(bodyJSON).Render()
if diff != "" {
cErr := cuteErrors.NewEmptyAssertError("JSON Diff", "JSON is not the same")
cErr.PutAttachment(&cuteErrors.Attachment{
Name: "JSON diff",
MimeType: "text/plain",
Content: []byte(diff),
})

return cErr
}

return nil
}
}

// Contains is a function to assert that a jsonpath expression extracts a value in an array
// About expression - https://goessner.net/articles/JsonPath/
func Contains(expression string, expect interface{}) cute.AssertBody {
Expand Down Expand Up @@ -99,16 +130,15 @@ func NotPresent(expression string) cute.AssertBody {
}

// GetValueFromJSON is function for get value from json
// TODO create tests
func GetValueFromJSON(js []byte, expression string) (interface{}, error) {
obj, err := oj.Parse(js)
if err != nil {
return nil, fmt.Errorf("could not parse json in ,GetValueFromJSON error: '%s'", err)
return nil, fmt.Errorf("could not parse json in GetValueFromJSON error: '%s'", err)
}

jsonPath, err := jp.ParseString(expression)
if err != nil {
return nil, fmt.Errorf("could not parse path in ,GetValueFromJSON error: '%s'", err)
return nil, fmt.Errorf("could not parse path in GetValueFromJSON error: '%s'", err)
}

res := jsonPath.Get(obj)
Expand Down
120 changes: 120 additions & 0 deletions asserts/json/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package json
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -14,6 +15,61 @@ type jsonTest struct {
IsNilErr bool
}

func TestDiff(t *testing.T) {
testCases := []struct {
name string
originalJSON string
bodyJSON string
expectedError string
}{
{
name: "SameJSON",
originalJSON: `{"key1": "value1", "key2": "value2"}`,
bodyJSON: `{"key1": "value1", "key2": "value2"}`,
expectedError: "", // No error expected, JSONs are the same
},
{
name: "DifferentValueJSON",
originalJSON: `{"key1": "value1", "key2": "value2"}`,
bodyJSON: `{"key1": "value1", "key2": "value3"}`,
expectedError: "JSON is not same",
},
{
name: "MissingKeyJSON",
originalJSON: `{"key1": "value1", "key2": "value2"}`,
bodyJSON: `{"key1": "value1"}`,
expectedError: "JSON is not same",
},
{
name: "ExtraKeyJSON",
originalJSON: `{"key1": "value1"}`,
bodyJSON: `{"key1": "value1", "key2": "value2"}`,
expectedError: "JSON is not same",
},
{
name: "EmptyJSON",
originalJSON: `{}`,
bodyJSON: `{}`,
expectedError: "", // No error expected, empty JSONs are the same
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// Call the Diff function with the test input
err := Diff(testCase.originalJSON)([]byte(testCase.bodyJSON))

// Check if the error message matches the expected result
if testCase.expectedError == "" {
assert.NoError(t, err) // No error expected
} else {
assert.Error(t, err) // Error expected
assert.Contains(t, err.Error(), testCase.expectedError)
}
})
}
}

func TestNotPresent(t *testing.T) {
tests := []jsonTest{
{
Expand Down Expand Up @@ -641,3 +697,67 @@ func TestNotEqual(t *testing.T) {
}
}
}

func TestGetValueFromJSON(t *testing.T) {
testCases := []struct {
name string
inputJSON string
expression string
expectedValue interface{}
expectedError string
}{
{
name: "ValidExpressionObject",
inputJSON: `{"key1": "value1", "key2": {"key3": "value3"}}`,
expression: "key2.key3",
expectedValue: "value3",
expectedError: "", // No error expected
},
{
name: "ValidExpressionArray",
inputJSON: `{"key1": "value1", "key2": [1, 2, 3]}`,
expression: "key2[1]",
expectedValue: int64(2),
expectedError: "", // No error expected
},
{
name: "ValidExpressionMap",
inputJSON: `{"key1": "value1", "key2": {"subkey1": "subvalue1"}}`,
expression: "key2",
expectedValue: map[string]interface{}{"subkey1": "subvalue1"},
expectedError: "", // No error expected
},
{
name: "InvalidJSON",
inputJSON: `invalid json`,
expression: "key1",
expectedValue: nil,
expectedError: "could not parse json",
},
{
name: "InvalidExpression",
inputJSON: `{"key1": "value1"}`,
expression: "key2",
expectedValue: nil,
expectedError: "could not find element by path key2 in JSON",
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
// Call the GetValueFromJSON function with the test input
value, err := GetValueFromJSON([]byte(testCase.inputJSON), testCase.expression)

// Check if the error message matches the expected result
if testCase.expectedError == "" {
assert.NoError(t, err) // No error expected
} else {
assert.Error(t, err) // Error expected
assert.Contains(t, err.Error(), testCase.expectedError)
}

// Check if the returned value matches the expected result
assert.Equal(t, testCase.expectedValue, value)
})
}
}
44 changes: 41 additions & 3 deletions errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ const (
expectedField = "Expected"
)

type CuteError interface {
error
WithNameError
WithFields
WithAttachments
}

// WithNameError is interface for creates allure step.
// If function returns error, which implement this interface, allure step will create automatically
type WithNameError interface {
Expand All @@ -19,13 +26,27 @@ type WithFields interface {
PutFields(map[string]interface{})
}

// Attachment represents an attachment to Allure with properties like name, MIME type, and content.
type Attachment struct {
Name string // Name of the attachment.
MimeType string // MIME type of the attachment.
Content []byte // Content of the attachment.
}

// WithAttachments is an interface that defines methods for managing attachments.
type WithAttachments interface {
GetAttachments() []*Attachment
PutAttachment(a *Attachment)
}

type assertError struct {
optional bool
require bool

name string
message string
fields map[string]interface{}
name string
message string
fields map[string]interface{}
attachments []*Attachment
}

// NewAssertError ...
Expand All @@ -40,6 +61,15 @@ func NewAssertError(name string, message string, actual interface{}, expected in
}
}

// NewEmptyAssertError ...
func NewEmptyAssertError(name string, message string) CuteError {
return &assertError{
name: name,
message: message,
fields: map[string]interface{}{},
}
}

func (a *assertError) Error() string {
return a.message
}
Expand All @@ -62,6 +92,14 @@ func (a *assertError) PutFields(fields map[string]interface{}) {
}
}

func (a *assertError) GetAttachments() []*Attachment {
return a.attachments
}

func (a *assertError) PutAttachment(attachment *Attachment) {
a.attachments = append(a.attachments, attachment)
}

func (a *assertError) IsOptional() bool {
return a.optional
}
Expand Down
1 change: 1 addition & 0 deletions examples/single_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func Test_Single_1(t *testing.T) {
).
ExpectExecuteTimeout(10*time.Second).
ExpectStatus(http.StatusOK).
AssertBody(json.Diff("{\"aaa\":\"bb\"}")).
AssertBody(
json.Present("$[1].name"),
json.Present("$[0].passport"), // Example fail
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/ozontech/cute
go 1.18

require (
github.com/josephburnett/jd v1.5.1
github.com/ohler55/ojg v1.12.9
github.com/ozontech/allure-go/pkg/allure v0.6.11
github.com/ozontech/allure-go/pkg/framework v0.6.28
Expand All @@ -13,10 +14,14 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.19.5 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
)
19 changes: 18 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/josephburnett/jd v1.5.1 h1:QmLNUewdF2CAezYKe1f/UIP9M5D9GtC+N7/qIyj3Pi8=
github.com/josephburnett/jd v1.5.1/go.mod h1:2pSZGHitQCumXDDTxmJehndlsltrTeVAhrzP8WfFeuc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/ohler55/ojg v1.12.9 h1:HIHORjvA/i2IyDGgf9zzkFZc0yhEZIi3Tte+m+XBzTs=
github.com/ohler55/ojg v1.12.9/go.mod h1:LBbIVRAgoFbYBXQhRhuEpaJIqq+goSO63/FQ+nyJU88=
github.com/ozontech/allure-go/pkg/allure v0.6.11 h1:1g8jCTLSI7hcAQBXwMx8HoKaVwErRPcWqSrjVGgv+Nk=
Expand Down Expand Up @@ -49,9 +63,12 @@ golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
10 changes: 10 additions & 0 deletions step.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ func processStepErrors(stepCtx provider.StepCtx, errs []error) {
}
}

if tErr, ok := err.(errors.WithAttachments); ok {
for _, v := range tErr.GetAttachments() {
if v == nil {
continue
}

currentStep.WithAttachments(allure.NewAttachment(v.Name, allure.MimeType(v.MimeType), v.Content))
}
}

statuses = append(statuses, currentStatus)

currentStep.WithAttachments(allure.NewAttachment("Error", allure.Text, []byte(err.Error())))
Expand Down

0 comments on commit 3a43356

Please sign in to comment.