github.com/blend/go-sdk@v1.20220411.3/webutil/request_option_test.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package webutil 9 10 import ( 11 "bytes" 12 "context" 13 "encoding/json" 14 "fmt" 15 "io" 16 "net/http" 17 "net/http/httptest" 18 "net/url" 19 "strings" 20 "testing" 21 22 "github.com/blend/go-sdk/assert" 23 ) 24 25 type xmlBody struct { 26 X []string `xml:"x"` 27 Y []string `xml:"y"` 28 } 29 30 func TestRequestOptions(t *testing.T) { 31 assert := assert.New(t) 32 33 req := &http.Request{} 34 35 assert.Empty(req.Method) 36 assert.Nil(OptMethod("POST")(req)) 37 assert.Equal("POST", req.Method) 38 39 req.Method = "" 40 assert.Nil(OptGet()(req)) 41 assert.Equal("GET", req.Method) 42 43 req.Method = "" 44 assert.Nil(OptPost()(req)) 45 assert.Equal("POST", req.Method) 46 47 req.Method = "" 48 assert.Nil(OptPut()(req)) 49 assert.Equal("PUT", req.Method) 50 51 req.Method = "" 52 assert.Nil(OptPatch()(req)) 53 assert.Equal("PATCH", req.Method) 54 55 req.Method = "" 56 assert.Nil(OptDelete()(req)) 57 assert.Equal("DELETE", req.Method) 58 59 type contextKey struct{} 60 assert.Nil(req.Context().Value(contextKey{})) 61 assert.Nil(OptContext(context.WithValue(context.Background(), contextKey{}, "foo"))(req)) 62 assert.Equal("foo", req.Context().Value(contextKey{})) 63 64 assert.Nil(req.URL) 65 assert.Nil(OptQuery(url.Values{"foo": []string{"bar", "baz"}})(req)) 66 assert.NotNil(req.URL) 67 assert.Equal("foo=bar&foo=baz", req.URL.RawQuery) 68 69 req.URL = &url.URL{} 70 assert.Nil(OptQueryValue("foo", "bar")(req)) 71 assert.NotNil(req.URL) 72 assert.Equal("foo=bar", req.URL.RawQuery) 73 74 assert.Nil(req.Header) 75 assert.Nil(OptHeader(http.Header{"X-Foo": []string{"bar", "baz"}})(req)) 76 assert.Equal("bar", req.Header.Get("X-Foo")) 77 78 req.Header = nil 79 assert.Nil(OptHeaderValue("X-Foo", "bar")(req)) 80 assert.Equal("bar", req.Header.Get("X-Foo")) 81 82 assert.Nil(req.PostForm) 83 assert.Nil(OptPostForm(url.Values{"foo": []string{"bar", "baz"}})(req)) 84 assert.Equal("bar", req.PostForm.Get("foo")) 85 86 req.PostForm = nil 87 assert.Nil(OptPostFormValue("buzz", "fuzz")(req)) 88 assert.Equal("fuzz", req.PostForm.Get("buzz")) 89 90 req.Header = nil 91 assert.Nil(OptCookie(&http.Cookie{Name: "sid", Value: "my value"})(req)) 92 c, err := req.Cookie("sid") 93 assert.Nil(err) 94 assert.Equal("my value", c.Value) 95 96 req.Header = nil 97 assert.Nil(OptCookieValue("jsid", "another value")(req)) 98 c, err = req.Cookie("jsid") 99 assert.Nil(err) 100 assert.Equal("another value", c.Value) 101 102 assert.Nil(req.Body) 103 assert.Nil(OptBody(io.NopCloser(bytes.NewReader([]byte("foo bar"))))(req)) 104 assert.NotNil(req.Body) 105 read, err := io.ReadAll(req.Body) 106 assert.Nil(err) 107 assert.Equal([]byte("foo bar"), read) 108 109 req.Body = nil 110 assert.Nil(OptBodyBytes([]byte("bar foo"))(req)) 111 assert.NotNil(req.Body) 112 read, err = io.ReadAll(req.Body) 113 assert.Nil(err) 114 assert.Equal([]byte("bar foo"), read) 115 116 postedFiles := []PostedFile{ 117 {Key: "file0", FileName: "file.txt", Contents: []byte("foo bar baz")}, 118 {Key: "file1", FileName: "file_1.txt", Contents: []byte("fuzzy wuzzy was a bear")}, 119 } 120 req.Header = nil 121 req.Body = nil 122 assert.Nil(OptPostedFiles(postedFiles...)(req)) 123 assert.NotEmpty(req.Header) 124 assert.NotNil(req.Body) 125 126 req.Header = nil 127 req.Body = nil 128 assert.Nil(OptJSONBody([]string{"foo", "bar"})(req)) 129 assert.Equal(ContentTypeApplicationJSON, req.Header.Get(HeaderContentType)) 130 assert.NotNil(req.Body) 131 132 req.Header = nil 133 req.Body = nil 134 assert.Nil(OptXMLBody([]string{"foo", "bar"})(req)) 135 assert.Equal(ContentTypeApplicationXML, req.Header.Get(HeaderContentType)) 136 assert.NotNil(req.Body) 137 } 138 139 func TestOptBodyBytes(t *testing.T) { 140 assert := assert.New(t) 141 body := []byte("hello\n") 142 opt := OptBodyBytes(body) 143 144 r := &http.Request{} 145 err := opt(r) 146 assert.Nil(err) 147 148 bodyBytes, err := io.ReadAll(r.Body) 149 assert.Nil(err) 150 assert.Equal(body, bodyBytes) 151 assert.Equal(r.ContentLength, 6) 152 validateRequestGetBody(assert, r, body) 153 } 154 155 func TestOptPostedFiles(t *testing.T) { 156 assert := assert.New(t) 157 file1 := PostedFile{Key: "a", FileName: "b.txt", Contents: []byte("hey")} 158 file2 := PostedFile{Key: "c", FileName: "d.txt", Contents: []byte("bye")} 159 file3 := PostedFile{Key: "d", FileName: "e.txt", Contents: []byte("bytes"), ContentType: "application/pdf"} 160 opt := OptPostedFiles(file1, file2, file3) 161 162 r := &http.Request{} 163 err := opt(r) 164 assert.Nil(err) 165 166 boundary := getBoundary(assert, r.Header) 167 ct := fmt.Sprintf("multipart/form-data; boundary=%s", boundary) 168 assert.Equal(r.Header, http.Header{HeaderContentType: []string{ct}}) 169 bodyBytes, err := io.ReadAll(r.Body) 170 defer r.Body.Close() 171 assert.Nil(err) 172 expected := fmt.Sprintf( 173 "--%[1]s\r\nContent-Disposition: form-data; name=%[2]q; filename=%[3]q\r\n"+ 174 "Content-Type: application/octet-stream\r\n\r\n%[4]s\r\n"+ 175 "--%[1]s\r\nContent-Disposition: form-data; name=%[5]q; filename=%[6]q\r\n"+ 176 "Content-Type: application/octet-stream\r\n\r\n%[7]s\r\n"+ 177 "--%[1]s\r\nContent-Disposition: form-data; name=%[8]q; filename=%[9]q\r\n"+ 178 "Content-Type: %[10]s\r\n\r\n%[11]s\r\n--%[1]s--\r\n", 179 boundary, 180 file1.Key, 181 file1.FileName, 182 file1.Contents, 183 file2.Key, 184 file2.FileName, 185 file2.Contents, 186 file3.Key, 187 file3.FileName, 188 file3.ContentType, 189 file3.Contents, 190 ) 191 assert.Equal(expected, string(bodyBytes)) 192 assert.Equal(r.ContentLength, len(expected)) 193 validateRequestGetBody(assert, r, []byte(expected)) 194 } 195 196 func TestOptPostedFiles_postForm(t *testing.T) { 197 assert := assert.New(t) 198 199 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 200 if err := req.ParseMultipartForm(1 << 20); err != nil { 201 http.Error(w, err.Error(), http.StatusBadRequest) 202 return 203 } 204 if req.FormValue("foo") != "foo-value" { 205 http.Error(w, "post form value `foo` missing or incorrect", http.StatusBadRequest) 206 return 207 } 208 if req.FormValue("bar") != "bar-value" { 209 http.Error(w, "post form value `bar` missing or incorrect", http.StatusBadRequest) 210 return 211 } 212 213 files, err := PostedFiles(req) 214 if err != nil { 215 http.Error(w, err.Error(), http.StatusBadRequest) 216 return 217 } 218 if len(files) < 2 { 219 http.Error(w, "posted files missing", http.StatusBadRequest) 220 return 221 } 222 223 w.WriteHeader(http.StatusOK) 224 fmt.Fprintf(w, "OK!") 225 })) 226 defer testServer.Close() 227 228 file1 := PostedFile{Key: "a", FileName: "b.txt", Contents: []byte("hey")} 229 file2 := PostedFile{Key: "c", FileName: "d.txt", Contents: []byte("bye")} 230 231 postFormOpt0 := OptPostFormValue("foo", "foo-value") 232 postFormOpt1 := OptPostFormValue("bar", "bar-value") 233 opt := OptPostedFiles(file1, file2) 234 235 r, err := http.NewRequest(http.MethodPost, testServer.URL, nil) 236 assert.Nil(err) 237 err = postFormOpt0(r) 238 assert.Nil(err) 239 err = postFormOpt1(r) 240 assert.Nil(err) 241 err = opt(r) 242 assert.Nil(err) 243 244 res, err := http.DefaultClient.Do(r) 245 assert.Nil(err) 246 defer res.Body.Close() 247 message, _ := io.ReadAll(res.Body) 248 assert.Equal(http.StatusOK, res.StatusCode, string(message)) 249 } 250 251 func TestOptJSONBody(t *testing.T) { 252 assert := assert.New(t) 253 payload := map[string]float64{"x": 1.25, "y": -5.75} 254 opt := OptJSONBody(payload) 255 256 r := &http.Request{} 257 err := opt(r) 258 assert.Nil(err) 259 260 assert.NotNil(r.Body) 261 assert.NotNil(r.GetBody) 262 263 assert.Equal(r.Header, http.Header{HeaderContentType: []string{ContentTypeApplicationJSON}}) 264 bodyBytes, err := io.ReadAll(r.Body) 265 defer r.Body.Close() 266 assert.Nil(err) 267 expected := []byte(`{"x":1.25,"y":-5.75}`) 268 assert.Equal(expected, bodyBytes) 269 assert.Equal(r.ContentLength, 20) 270 271 validateRequestGetBody(assert, r, expected) 272 273 verifyContents, _ := json.Marshal(payload) 274 verify, err := http.NewRequest("POST", "https://example.com/test", bytes.NewReader(verifyContents)) 275 assert.Nil(err) 276 assert.Equal(verify.ContentLength, r.ContentLength) 277 } 278 279 func TestOptXMLBody(t *testing.T) { 280 assert := assert.New(t) 281 payload := xmlBody{X: []string{"hello"}, Y: []string{"goodbye"}} 282 opt := OptXMLBody(payload) 283 284 r := &http.Request{} 285 err := opt(r) 286 assert.Nil(err) 287 288 assert.Equal(r.Header, http.Header{HeaderContentType: []string{ContentTypeApplicationXML}}) 289 bodyBytes, err := io.ReadAll(r.Body) 290 assert.Nil(err) 291 expected := []byte("<xmlBody><x>hello</x><y>goodbye</y></xmlBody>") 292 assert.Equal(expected, bodyBytes) 293 assert.Equal(r.ContentLength, 45) 294 validateRequestGetBody(assert, r, expected) 295 } 296 297 func getBoundary(assert *assert.Assertions, h http.Header) string { 298 boundaryPrefix := "multipart/form-data; boundary=" 299 ct := h.Get(HeaderContentType) 300 assert.True(strings.HasPrefix(ct, boundaryPrefix)) 301 return strings.TrimPrefix(ct, boundaryPrefix) 302 } 303 304 func validateRequestGetBody(assert *assert.Assertions, r *http.Request, expected []byte) { 305 assert.NotNil(r.GetBody) 306 bodyRC, err := r.GetBody() 307 assert.Nil(err) 308 defer bodyRC.Close() 309 bodyBytes, err := io.ReadAll(bodyRC) 310 assert.Nil(err) 311 assert.Equal(expected, bodyBytes) 312 }