Skip to content

Commit 5876f5f

Browse files
committed
fix(acctest): perform deepequality on nested request body
1 parent 555a269 commit 5876f5f

File tree

2 files changed

+123
-15
lines changed

2 files changed

+123
-15
lines changed

internal/acctest/acctest_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package acctest_test
2+
3+
import (
4+
"context"
5+
"io"
6+
"net/http"
7+
"strings"
8+
"testing"
9+
10+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
11+
"gopkg.in/dnaeon/go-vcr.v3/cassette"
12+
)
13+
14+
var barMemberCreationBody = `{
15+
"organization_id": "6867048b-fe12-4e96-835e-41c79a39604b",
16+
"tags": [
17+
"bar"
18+
],
19+
"member": {
20+
"email": "[email protected]",
21+
"send_password_email": false,
22+
"send_welcome_email": false,
23+
"username": "bar",
24+
"password": "",
25+
"first_name": "",
26+
"last_name": "",
27+
"phone_number": "",
28+
"locale": ""
29+
}
30+
}
31+
`
32+
33+
var fooMemberCreationBody = `{
34+
"organization_id": "6867048b-fe12-4e96-835e-41c79a39604b",
35+
"tags": [
36+
"foo"
37+
],
38+
"member": {
39+
"email": "[email protected]",
40+
"send_password_email": false,
41+
"send_welcome_email": false,
42+
"username": "foo",
43+
"password": "",
44+
"first_name": "",
45+
"last_name": "",
46+
"phone_number": "",
47+
"locale": ""
48+
}
49+
}
50+
`
51+
52+
// we don't use httptest.NewRequest because it does not set the GetBody func
53+
func newRequest(method, url string, body io.Reader) *http.Request {
54+
req, err := http.NewRequestWithContext(context.Background(), method, url, body)
55+
if err != nil {
56+
panic(err) // lintignore: R009
57+
}
58+
59+
return req
60+
}
61+
62+
var testBodyMatcherCases = []struct {
63+
requestBody *http.Request
64+
cassetteBody *cassette.Request
65+
shouldMatch bool
66+
}{
67+
{
68+
requestBody: newRequest(http.MethodPost, "https://api.scaleway.com/iam/v1alpha1/users", strings.NewReader(barMemberCreationBody)),
69+
cassetteBody: &cassette.Request{
70+
URL: "https://api.scaleway.com/iam/v1alpha1/users",
71+
Method: http.MethodPost,
72+
Body: fooMemberCreationBody,
73+
ContentLength: int64(len(fooMemberCreationBody)),
74+
},
75+
shouldMatch: false,
76+
},
77+
{
78+
requestBody: newRequest(http.MethodPost, "https://api.scaleway.com/iam/v1alpha1/users", strings.NewReader(barMemberCreationBody)),
79+
cassetteBody: &cassette.Request{
80+
URL: "https://api.scaleway.com/iam/v1alpha1/users",
81+
Method: http.MethodPost,
82+
Body: barMemberCreationBody,
83+
ContentLength: int64(len(barMemberCreationBody)),
84+
},
85+
shouldMatch: true,
86+
},
87+
{
88+
requestBody: newRequest(http.MethodGet, "https://api.scaleway.com/iam/v1alpha1/users/6867048b-fe12-4e96-835e-41c79a39604b", nil),
89+
cassetteBody: &cassette.Request{
90+
URL: "https://api.scaleway.com/iam/v1alpha1/users/6867048b-fe12-4e96-835e-41c79a39604b",
91+
Method: http.MethodGet,
92+
Body: "",
93+
ContentLength: 0,
94+
},
95+
shouldMatch: true,
96+
},
97+
}
98+
99+
func TestCassetteMatcher(t *testing.T) {
100+
for _, test := range testBodyMatcherCases {
101+
shouldMatch := acctest.CassetteMatcher(test.requestBody, *test.cassetteBody)
102+
if shouldMatch != test.shouldMatch {
103+
t.Errorf("expected %v, got %v", test.shouldMatch, shouldMatch)
104+
}
105+
}
106+
}

internal/acctest/vcr.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/url"
1111
"os"
1212
"path/filepath"
13+
"reflect"
1314
"regexp"
1415
"strings"
1516
"testing"
@@ -74,8 +75,7 @@ func compareJSONFields(expected, actualI interface{}) bool {
7475

7576
return compareJSONFieldsStrings(expected.(string), actual)
7677
default:
77-
// Consider equality when not handled
78-
return true
78+
return reflect.DeepEqual(expected, actualI)
7979
}
8080
}
8181

@@ -116,39 +116,41 @@ func cassetteBodyMatcher(actualRequest *http.Request, cassetteRequest cassette.R
116116
if actualRequest.Body == nil || actualRequest.ContentLength == 0 {
117117
if cassetteRequest.Body == "" {
118118
return true // Body match if both are empty
119-
} else if _, isFile := actualRequest.Body.(*os.File); isFile {
119+
}
120+
121+
if _, isFile := actualRequest.Body.(*os.File); isFile {
120122
return true // Body match if request is sending a file, maybe do more check here
121123
}
122124

123125
return false
124126
}
125127

126-
actualBody, err := actualRequest.GetBody()
128+
actualBodyReader, err := actualRequest.GetBody()
127129
if err != nil {
128130
panic(fmt.Errorf("cassette body matcher: failed to copy actualRequest body: %w", err)) // lintignore: R009
129131
}
130132

131-
actualRawBody, err := io.ReadAll(actualBody)
133+
actualBody, err := io.ReadAll(actualBodyReader)
132134
if err != nil {
133135
panic(fmt.Errorf("cassette body matcher: failed to read actualRequest body: %w", err)) // lintignore: R009
134136
}
135137

136138
// Try to match raw bodies if they are not JSON (ex: cloud-init config)
137-
if string(actualRawBody) == cassetteRequest.Body {
139+
if string(actualBody) == cassetteRequest.Body {
138140
return true
139141
}
140142

141143
actualJSON := make(map[string]interface{})
142144
cassetteJSON := make(map[string]interface{})
143145

144-
err = xml.Unmarshal(actualRawBody, new(interface{}))
146+
// match if content is xml
147+
err = xml.Unmarshal(actualBody, new(interface{}))
145148
if err == nil {
146-
// match if content is xml
147149
return true
148150
}
149151

150-
if !json.Valid(actualRawBody) {
151-
values, err := url.ParseQuery(string(actualRawBody))
152+
if !json.Valid(actualBody) {
153+
values, err := url.ParseQuery(string(actualBody))
152154
if err != nil {
153155
panic(fmt.Errorf("cassette body matcher: failed to parse body as url values: %w", err)) // lintignore: R009
154156
}
@@ -162,9 +164,9 @@ func cassetteBodyMatcher(actualRequest *http.Request, cassetteRequest cassette.R
162164
return compareFormBodies(values, cassetteRequest.Form)
163165
}
164166

165-
err = json.Unmarshal(actualRawBody, &actualJSON)
167+
err = json.Unmarshal(actualBody, &actualJSON)
166168
if err != nil {
167-
panic(fmt.Errorf("cassette body matcher: failed to parse json body: %w", err)) // lintignore: R009
169+
panic(fmt.Errorf("cassette body matcher: failed to parse request body as json: %w", err)) // lintignore: R009
168170
}
169171

170172
err = json.Unmarshal([]byte(cassetteRequest.Body), &cassetteJSON)
@@ -182,9 +184,9 @@ func cassetteBodyMatcher(actualRequest *http.Request, cassetteRequest cassette.R
182184
return compareJSONBodies(cassetteJSON, actualJSON)
183185
}
184186

185-
// cassetteMatcher is a custom matcher that check equivalence of a played request against a recorded one
187+
// CassetteMatcher is a custom matcher that check equivalence of a played request against a recorded one
186188
// It compares method, path and query but will remove unwanted values from query
187-
func cassetteMatcher(actual *http.Request, expected cassette.Request) bool {
189+
func CassetteMatcher(actual *http.Request, expected cassette.Request) bool {
188190
expectedURL, _ := url.Parse(expected.URL)
189191
actualURL := actual.URL
190192
actualURLValues := actualURL.Query()
@@ -297,7 +299,7 @@ func getHTTPRecoder(t *testing.T, pkgFolder string, update bool) (client *http.C
297299
}(r)
298300

299301
// Add custom matcher for requests and cassettes
300-
r.SetMatcher(cassetteMatcher)
302+
r.SetMatcher(CassetteMatcher)
301303

302304
// Add a filter which removes Authorization headers from all requests:
303305
r.AddHook(func(i *cassette.Interaction) error {

0 commit comments

Comments
 (0)