Skip to content

Commit 3b6ab90

Browse files
committed
tradeoff matching record
1 parent 8b3af27 commit 3b6ab90

File tree

2 files changed

+50
-29
lines changed

2 files changed

+50
-29
lines changed

internal/acctest/compare.go

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,46 @@ import (
99

1010
// compareJSONFields is the entry point for comparing two interface values
1111
// handle string with special cases, map[string]interface{} and []interface{} or any other primitive type
12-
func compareJSONFields(requestValue, cassetteValue interface{}) bool {
12+
func compareJSONFields(requestValue, cassetteValue any, strict bool) bool {
1313
switch requestValue := requestValue.(type) {
1414
case string:
1515
return compareFieldsStrings(requestValue, cassetteValue.(string))
16-
case map[string]interface{}:
17-
return compareJSONBodies(requestValue, cassetteValue.(map[string]interface{}))
18-
case []interface{}:
19-
return compareSlices(requestValue, cassetteValue.([]interface{}))
16+
case map[string]any:
17+
return compareJSONBodies(requestValue, cassetteValue.(map[string]any), strict)
18+
case []any:
19+
return compareSlices(requestValue, cassetteValue.([]any))
2020
default:
2121
return reflect.DeepEqual(requestValue, cassetteValue)
2222
}
2323
}
2424

2525
// compareJSONBodies compare two given maps that represent json bodies
2626
// returns true if both json are equivalent
27-
func compareJSONBodies(request, cassette map[string]interface{}) bool {
27+
func compareJSONBodies(request, cassette map[string]any, strict bool) bool {
2828
for key, requestValue := range request {
2929
cassetteValue, ok := cassette[key]
3030
if !ok {
31-
// Actual request may contain a field that does not exist in cassette
32-
// New fields can appear in requests with new api features
33-
// We do not want to generate new cassettes for each new features
31+
if strict {
32+
return false
33+
}
34+
3435
continue
3536
}
3637

3738
if reflect.TypeOf(cassetteValue) != reflect.TypeOf(requestValue) {
3839
return false
3940
}
4041

41-
if !compareJSONFields(requestValue, cassetteValue) {
42+
if !compareJSONFields(requestValue, cassetteValue, strict) {
43+
return false
44+
}
45+
}
46+
47+
for key, cassetteValue := range cassette {
48+
if _, ok := request[key]; !ok && cassetteValue != nil {
49+
// Fails match if cassettes contains a field not in actual requests
50+
// Fields should not disappear from requests unless a sdk breaking change
51+
// We ignore if field is nil in cassette as it could be an old deprecated and unused field
4252
return false
4353
}
4454
}
@@ -141,7 +151,7 @@ func compareStringSlices(request, cassette []string) bool {
141151

142152
// compareSlices compares two slices of interface{}
143153
// in case of slice of map[string]interface{}, it will attempt to find a match in the other slice without taking into account the order
144-
func compareSlices(request, cassette []interface{}) bool {
154+
func compareSlices(request, cassette []any) bool {
145155
if len(request) != len(cassette) {
146156
return false
147157
}
@@ -178,10 +188,31 @@ func compareSlices(request, cassette []interface{}) bool {
178188
}
179189

180190
return true
181-
case map[string]interface{}:
191+
case map[string]any:
182192
// compare list of maps[string]interface{} without order and without ignored keys
183193
matched := 0
184194

195+
for i := range request {
196+
// cleanup ignored keys
197+
for _, key := range BodyMatcherIgnore {
198+
removeKeyRecursive(request[i].(map[string]any), key)
199+
}
200+
201+
for _, key := range BodyMatcherIgnore {
202+
removeKeyRecursive(cassette[i].(map[string]any), key)
203+
}
204+
205+
if compareJSONFields(request[i], cassette[i], false) {
206+
matched++
207+
}
208+
}
209+
210+
if matched == len(request) {
211+
return true
212+
}
213+
214+
// if no match try to compare out of order
215+
matched = 0
185216
reqVisited := make([]bool, len(request))
186217
casVisited := make([]bool, len(cassette))
187218

@@ -190,22 +221,12 @@ func compareSlices(request, cassette []interface{}) bool {
190221
continue
191222
}
192223

193-
// cleanup ignored keys
194-
for _, key := range BodyMatcherIgnore {
195-
removeKeyRecursive(request[i].(map[string]interface{}), key)
196-
}
197-
198224
for j := range cassette {
199225
if casVisited[j] {
200226
continue
201227
}
202228

203-
// cleanup ignored keys
204-
for _, key := range BodyMatcherIgnore {
205-
removeKeyRecursive(cassette[j].(map[string]interface{}), key)
206-
}
207-
208-
if compareJSONFields(request[i], cassette[j]) {
229+
if compareJSONFields(request[i], cassette[j], true) {
209230
matched++
210231
reqVisited[i] = true
211232
casVisited[j] = true

internal/acctest/vcr.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ var BodyMatcherIgnore = []string{
5151
}
5252

5353
// removeKeyRecursive removes a key from a map and all its nested maps
54-
func removeKeyRecursive(m map[string]interface{}, key string) {
54+
func removeKeyRecursive(m map[string]any, key string) {
5555
delete(m, key)
5656

5757
for _, v := range m {
58-
if v, ok := v.(map[string]interface{}); ok {
58+
if v, ok := v.(map[string]any); ok {
5959
removeKeyRecursive(v, key)
6060
}
6161
}
@@ -110,11 +110,11 @@ func cassetteBodyMatcher(request *http.Request, cassette cassette.Request) bool
110110
return true
111111
}
112112

113-
requestJSON := make(map[string]interface{})
114-
cassetteJSON := make(map[string]interface{})
113+
requestJSON := make(map[string]any)
114+
cassetteJSON := make(map[string]any)
115115

116116
// match if content is xml
117-
err = xml.Unmarshal(requestBody, new(interface{}))
117+
err = xml.Unmarshal(requestBody, new(any))
118118
if err == nil {
119119
return true
120120
}
@@ -149,7 +149,7 @@ func cassetteBodyMatcher(request *http.Request, cassette cassette.Request) bool
149149
removeKeyRecursive(cassetteJSON, key)
150150
}
151151

152-
return compareJSONBodies(requestJSON, cassetteJSON)
152+
return compareJSONBodies(requestJSON, cassetteJSON, false)
153153
}
154154

155155
// CassetteMatcher is a custom matcher that check equivalence of a played request against a recorded one

0 commit comments

Comments
 (0)