diff --git a/replay/json_match.go b/replay/json_match.go index 15804f0..e86d450 100644 --- a/replay/json_match.go +++ b/replay/json_match.go @@ -17,13 +17,27 @@ package replay import ( "encoding/json" "fmt" + "q" "sort" + "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +type jsonMatchOptions struct { + UnorderedArrayPaths map[string]bool +} + +type JSONMatchOption func(*jsonMatchOptions) + +func WithUnorderedArrayPaths(unorderedArrayPaths map[string]bool) JSONMatchOption { + return func(opts *jsonMatchOptions) { + opts.UnorderedArrayPaths = unorderedArrayPaths + } +} + type testingT interface { assert.TestingT require.TestingT @@ -41,18 +55,24 @@ func AssertJSONMatchesPattern( t *testing.T, expectedPattern json.RawMessage, actual json.RawMessage, + opts ...JSONMatchOption, ) { - assertJSONMatchesPattern(t, expectedPattern, actual) + assertJSONMatchesPattern(t, expectedPattern, actual, opts...) } func assertJSONMatchesPattern( t testingT, expectedPattern json.RawMessage, actual json.RawMessage, + opts ...JSONMatchOption, ) { - var p, a interface{} + options := jsonMatchOptions{} + for _, opt := range opts { + opt(&options) + } + if err := json.Unmarshal(expectedPattern, &p); err != nil { require.NoError(t, err) } @@ -91,6 +111,15 @@ func assertJSONMatchesPattern( path, len(pp), prettyJSON(t, a)) return } + q.Q(path, options.UnorderedArrayPaths) + if options.UnorderedArrayPaths[path] { + sort.SliceStable(aa, func(i, j int) bool { + return strings.Compare( + fmt.Sprintf("%v", aa[i]), + fmt.Sprintf("%v", aa[j]), + ) < 0 + }) + } for i, pv := range pp { av := aa[i] match(fmt.Sprintf("%s[%d]", path, i), pv, av) diff --git a/replay/json_match_test.go b/replay/json_match_test.go index e0ec18c..2ac39da 100644 --- a/replay/json_match_test.go +++ b/replay/json_match_test.go @@ -29,6 +29,23 @@ func TestJsonMatch(t *testing.T) { AssertJSONMatchesPattern(t, []byte(`{"\\": "*"}`), []byte(`"*"`)) AssertJSONMatchesPattern(t, []byte(`[1, "*", 3]`), []byte(`[1, 2, 3]`)) AssertJSONMatchesPattern(t, []byte(`{"foo": "*", "bar": 3}`), []byte(`{"foo": 1, "bar": 3}`)) + AssertJSONMatchesPattern(t, []byte(`[1, 2, 3]`), []byte(`[1, 3, 2]`), + WithUnorderedArrayPaths(map[string]bool{"#": true})) + AssertJSONMatchesPattern(t, + []byte(`[{"key1":"val"}, {"key2":"val"}]`), + []byte(`[{"key2":"val"}, {"key1":"val"}]`), + WithUnorderedArrayPaths(map[string]bool{"#": true}), + ) + AssertJSONMatchesPattern(t, + []byte(`[{"key":"val1"}, {"key":"val2"}]`), + []byte(`[{"key":"val2"}, {"key":"val1"}]`), + WithUnorderedArrayPaths(map[string]bool{"#": true}), + ) + AssertJSONMatchesPattern(t, + []byte(`{"arr":[{"key":"val1"}, {"key":"val2"}]}`), + []byte(`{"arr":[{"key":"val2"}, {"key":"val1"}]}`), + WithUnorderedArrayPaths(map[string]bool{`#["arr"]`: true}), + ) } func TestJsonListLengthMistmatch(t *testing.T) { diff --git a/replay/replay.go b/replay/replay.go index 0943097..fe022a0 100644 --- a/replay/replay.go +++ b/replay/replay.go @@ -181,6 +181,11 @@ func replay[Req protoreflect.ProtoMessage, Resp protoreflect.ProtoMessage]( assert.NoError(t, err) resp, err := serve(ctx, req) + if err != nil && entry.Errors != nil { + assert.Equal(t, *entry.Errors, err.Error()) + return + } + require.NoError(t, err) bytes, err := jsonpb.Marshal(resp) @@ -188,7 +193,7 @@ func replay[Req protoreflect.ProtoMessage, Resp protoreflect.ProtoMessage]( var expected, actual json.RawMessage = entry.Response, bytes - AssertJSONMatchesPattern(t, expected, actual) + AssertJSONMatchesPattern(t, expected, actual, WithUnorderedArrayPaths(map[string]bool{`#["failures"]`: true})) } // ReplayFile executes ReplaySequence on all pulumirpc.ResourceProvider events found in the file produced with @@ -236,4 +241,5 @@ type jsonLogEntry struct { Method string `json:"method"` Request json.RawMessage `json:"request,omitempty"` Response json.RawMessage `json:"response,omitempty"` + Errors *string `json:"errors,omitempty"` }