github.com/jghiloni/cli@v6.28.1-0.20170628223758-0ce05fe032a2+incompatible/api/plugin/wrapper/request_logger_test.go (about) 1 package wrapper_test 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "time" 11 12 "code.cloudfoundry.org/cli/api/plugin" 13 "code.cloudfoundry.org/cli/api/plugin/pluginfakes" 14 . "code.cloudfoundry.org/cli/api/plugin/wrapper" 15 "code.cloudfoundry.org/cli/api/plugin/wrapper/wrapperfakes" 16 17 . "github.com/onsi/ginkgo" 18 . "github.com/onsi/gomega" 19 ) 20 21 var _ = Describe("Request Logger", func() { 22 var ( 23 fakeConnection *pluginfakes.FakeConnection 24 fakeOutput *wrapperfakes.FakeRequestLoggerOutput 25 fakeProxyReader *pluginfakes.FakeProxyReader 26 27 wrapper plugin.Connection 28 29 request *http.Request 30 response *plugin.Response 31 err error 32 ) 33 34 BeforeEach(func() { 35 fakeConnection = new(pluginfakes.FakeConnection) 36 fakeOutput = new(wrapperfakes.FakeRequestLoggerOutput) 37 fakeProxyReader = new(pluginfakes.FakeProxyReader) 38 39 wrapper = NewRequestLogger(fakeOutput).Wrap(fakeConnection) 40 41 var err error 42 request, err = http.NewRequest(http.MethodGet, "https://foo.bar.com/banana", nil) 43 Expect(err).NotTo(HaveOccurred()) 44 45 request.URL.RawQuery = url.Values{ 46 "query1": {"a"}, 47 "query2": {"b"}, 48 }.Encode() 49 50 headers := http.Header{} 51 headers.Add("Aghi", "bar") 52 headers.Add("Abc", "json") 53 headers.Add("Adef", "application/json") 54 request.Header = headers 55 56 response = &plugin.Response{ 57 RawResponse: []byte("some-response-body"), 58 HTTPResponse: &http.Response{}, 59 } 60 }) 61 62 JustBeforeEach(func() { 63 err = wrapper.Make(request, response, fakeProxyReader) 64 }) 65 66 Describe("Make", func() { 67 It("outputs the request", func() { 68 Expect(err).NotTo(HaveOccurred()) 69 70 Expect(fakeOutput.DisplayTypeCallCount()).To(BeNumerically(">=", 1)) 71 name, date := fakeOutput.DisplayTypeArgsForCall(0) 72 Expect(name).To(Equal("REQUEST")) 73 Expect(date).To(BeTemporally("~", time.Now(), time.Second)) 74 75 Expect(fakeOutput.DisplayRequestHeaderCallCount()).To(Equal(1)) 76 method, uri, protocol := fakeOutput.DisplayRequestHeaderArgsForCall(0) 77 Expect(method).To(Equal(http.MethodGet)) 78 Expect(uri).To(MatchRegexp("/banana\\?(?:query1=a&query2=b|query2=b&query1=a)")) 79 Expect(protocol).To(Equal("HTTP/1.1")) 80 81 Expect(fakeOutput.DisplayHostCallCount()).To(Equal(1)) 82 host := fakeOutput.DisplayHostArgsForCall(0) 83 Expect(host).To(Equal("foo.bar.com")) 84 85 Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 3)) 86 name, value := fakeOutput.DisplayHeaderArgsForCall(0) 87 Expect(name).To(Equal("Abc")) 88 Expect(value).To(Equal("json")) 89 name, value = fakeOutput.DisplayHeaderArgsForCall(1) 90 Expect(name).To(Equal("Adef")) 91 Expect(value).To(Equal("application/json")) 92 name, value = fakeOutput.DisplayHeaderArgsForCall(2) 93 Expect(name).To(Equal("Aghi")) 94 Expect(value).To(Equal("bar")) 95 96 Expect(err).ToNot(HaveOccurred()) 97 Expect(fakeConnection.MakeCallCount()).To(Equal(1)) 98 _, _, proxyReader := fakeConnection.MakeArgsForCall(0) 99 Expect(proxyReader).To(Equal(fakeProxyReader)) 100 }) 101 102 Context("when an authorization header is in the request", func() { 103 BeforeEach(func() { 104 request.Header = http.Header{"Authorization": []string{"should not be shown"}} 105 }) 106 107 It("redacts the contents of the authorization header", func() { 108 Expect(err).NotTo(HaveOccurred()) 109 Expect(fakeOutput.DisplayHeaderCallCount()).To(Equal(1)) 110 key, value := fakeOutput.DisplayHeaderArgsForCall(0) 111 Expect(key).To(Equal("Authorization")) 112 Expect(value).To(Equal("[PRIVATE DATA HIDDEN]")) 113 }) 114 }) 115 116 Context("when passed a body", func() { 117 Context("when the request's Content-Type is application/json", func() { 118 var originalBody io.ReadCloser 119 BeforeEach(func() { 120 request.Header.Set("Content-Type", "application/json") 121 originalBody = ioutil.NopCloser(bytes.NewReader([]byte("foo"))) 122 request.Body = originalBody 123 }) 124 125 It("outputs the body", func() { 126 Expect(err).NotTo(HaveOccurred()) 127 128 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1)) 129 Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal([]byte("foo"))) 130 131 bytes, err := ioutil.ReadAll(request.Body) 132 Expect(err).NotTo(HaveOccurred()) 133 Expect(bytes).To(Equal([]byte("foo"))) 134 }) 135 }) 136 137 Context("when request's Content-Type is anything else", func() { 138 BeforeEach(func() { 139 request.Header.Set("Content-Type", "banana") 140 }) 141 142 It("does not display the request body", func() { 143 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(Equal(0)) 144 }) 145 }) 146 }) 147 148 Context("when an error occures while trying to log the request", func() { 149 var expectedErr error 150 151 BeforeEach(func() { 152 expectedErr = errors.New("this should never block the request") 153 154 calledOnce := false 155 fakeOutput.StartStub = func() error { 156 if !calledOnce { 157 calledOnce = true 158 return expectedErr 159 } 160 return nil 161 } 162 }) 163 164 It("should display the error and continue on", func() { 165 Expect(err).NotTo(HaveOccurred()) 166 167 Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(1)) 168 Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr)) 169 }) 170 }) 171 172 Context("when the request is successful", func() { 173 Context("when the response is JSON", func() { 174 BeforeEach(func() { 175 response = &plugin.Response{ 176 RawResponse: []byte(`{"some-key":"some-value"}`), 177 HTTPResponse: &http.Response{ 178 Proto: "HTTP/1.1", 179 Status: "200 OK", 180 Header: http.Header{ 181 "Content-Type": {"application/json"}, 182 "BBBBB": {"second"}, 183 "AAAAA": {"first"}, 184 "CCCCC": {"third"}, 185 }, 186 Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"some-key":"some-value"}`))), 187 }, 188 } 189 }) 190 191 It("outputs the response", func() { 192 Expect(err).NotTo(HaveOccurred()) 193 194 Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2)) 195 name, date := fakeOutput.DisplayTypeArgsForCall(1) 196 Expect(name).To(Equal("RESPONSE")) 197 Expect(date).To(BeTemporally("~", time.Now(), time.Second)) 198 199 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 200 protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0) 201 Expect(protocol).To(Equal("HTTP/1.1")) 202 Expect(status).To(Equal("200 OK")) 203 204 Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 7)) 205 name, value := fakeOutput.DisplayHeaderArgsForCall(3) 206 Expect(name).To(Equal("AAAAA")) 207 Expect(value).To(Equal("first")) 208 name, value = fakeOutput.DisplayHeaderArgsForCall(4) 209 Expect(name).To(Equal("BBBBB")) 210 Expect(value).To(Equal("second")) 211 name, value = fakeOutput.DisplayHeaderArgsForCall(5) 212 Expect(name).To(Equal("CCCCC")) 213 Expect(value).To(Equal("third")) 214 name, value = fakeOutput.DisplayHeaderArgsForCall(6) 215 Expect(name).To(Equal("Content-Type")) 216 Expect(value).To(Equal("application/json")) 217 218 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1)) 219 Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal(response.RawResponse)) 220 221 Expect(fakeOutput.DisplayDumpCallCount()).To(Equal(0)) 222 }) 223 }) 224 225 Context("when the response is not JSON", func() { 226 BeforeEach(func() { 227 response = &plugin.Response{ 228 RawResponse: []byte(`not JSON`), 229 HTTPResponse: &http.Response{ 230 Proto: "HTTP/1.1", 231 Status: "200 OK", 232 Header: http.Header{ 233 "BBBBB": {"second"}, 234 "AAAAA": {"first"}, 235 "CCCCC": {"third"}, 236 }, 237 Body: ioutil.NopCloser(bytes.NewReader([]byte(`not JSON`))), 238 }, 239 } 240 }) 241 242 It("outputs the response", func() { 243 Expect(err).NotTo(HaveOccurred()) 244 245 Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2)) 246 name, date := fakeOutput.DisplayTypeArgsForCall(1) 247 Expect(name).To(Equal("RESPONSE")) 248 Expect(date).To(BeTemporally("~", time.Now(), time.Second)) 249 250 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 251 protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0) 252 Expect(protocol).To(Equal("HTTP/1.1")) 253 Expect(status).To(Equal("200 OK")) 254 255 Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 6)) 256 name, value := fakeOutput.DisplayHeaderArgsForCall(3) 257 Expect(name).To(Equal("AAAAA")) 258 Expect(value).To(Equal("first")) 259 name, value = fakeOutput.DisplayHeaderArgsForCall(4) 260 Expect(name).To(Equal("BBBBB")) 261 Expect(value).To(Equal("second")) 262 name, value = fakeOutput.DisplayHeaderArgsForCall(5) 263 Expect(name).To(Equal("CCCCC")) 264 Expect(value).To(Equal("third")) 265 266 Expect(fakeOutput.DisplayDumpCallCount()).To(Equal(1)) 267 text := fakeOutput.DisplayDumpArgsForCall(0) 268 Expect(text).To(Equal("[NON-JSON BODY CONTENT HIDDEN]")) 269 270 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(Equal(0)) 271 }) 272 }) 273 }) 274 275 Context("when the request is unsuccessful", func() { 276 var expectedErr error 277 278 BeforeEach(func() { 279 expectedErr = errors.New("banana") 280 fakeConnection.MakeReturns(expectedErr) 281 }) 282 283 Context("when the http response is not set", func() { 284 BeforeEach(func() { 285 response = &plugin.Response{} 286 }) 287 288 It("outputs nothing", func() { 289 Expect(err).To(MatchError(expectedErr)) 290 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(0)) 291 }) 292 }) 293 294 Context("when the http response body is nil", func() { 295 BeforeEach(func() { 296 response = &plugin.Response{ 297 HTTPResponse: &http.Response{Body: nil}, 298 } 299 }) 300 301 It("does not output the response body", func() { 302 Expect(err).To(MatchError(expectedErr)) 303 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 304 305 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(Equal(0)) 306 Expect(fakeOutput.DisplayDumpCallCount()).To(Equal(0)) 307 }) 308 }) 309 310 Context("when the http response is set", func() { 311 BeforeEach(func() { 312 response = &plugin.Response{ 313 RawResponse: []byte("some-error-body"), 314 HTTPResponse: &http.Response{ 315 Proto: "HTTP/1.1", 316 Status: "200 OK", 317 Header: http.Header{ 318 "Content-Type": {"application/json"}, 319 "BBBBB": {"second"}, 320 "AAAAA": {"first"}, 321 "CCCCC": {"third"}, 322 }, 323 Body: ioutil.NopCloser(bytes.NewReader([]byte(`some-error-body`))), 324 }, 325 } 326 }) 327 328 It("outputs the response", func() { 329 Expect(err).To(MatchError(expectedErr)) 330 331 Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2)) 332 name, date := fakeOutput.DisplayTypeArgsForCall(1) 333 Expect(name).To(Equal("RESPONSE")) 334 Expect(date).To(BeTemporally("~", time.Now(), time.Second)) 335 336 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 337 protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0) 338 Expect(protocol).To(Equal("HTTP/1.1")) 339 Expect(status).To(Equal("200 OK")) 340 341 Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 7)) 342 name, value := fakeOutput.DisplayHeaderArgsForCall(3) 343 Expect(name).To(Equal("AAAAA")) 344 Expect(value).To(Equal("first")) 345 name, value = fakeOutput.DisplayHeaderArgsForCall(4) 346 Expect(name).To(Equal("BBBBB")) 347 Expect(value).To(Equal("second")) 348 name, value = fakeOutput.DisplayHeaderArgsForCall(5) 349 Expect(name).To(Equal("CCCCC")) 350 Expect(value).To(Equal("third")) 351 name, value = fakeOutput.DisplayHeaderArgsForCall(6) 352 Expect(name).To(Equal("Content-Type")) 353 Expect(value).To(Equal("application/json")) 354 355 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1)) 356 Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal([]byte("some-error-body"))) 357 }) 358 }) 359 }) 360 361 Context("when an error occures while trying to log the response", func() { 362 var ( 363 originalErr error 364 expectedErr error 365 ) 366 367 BeforeEach(func() { 368 originalErr = errors.New("this error should not be overwritten") 369 fakeConnection.MakeReturns(originalErr) 370 371 expectedErr = errors.New("this should never block the request") 372 373 calledOnce := false 374 fakeOutput.StartStub = func() error { 375 if !calledOnce { 376 calledOnce = true 377 return nil 378 } 379 return expectedErr 380 } 381 }) 382 383 It("should display the error and continue on", func() { 384 Expect(err).To(MatchError(originalErr)) 385 386 Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(1)) 387 Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr)) 388 }) 389 }) 390 391 It("starts and stops the output", func() { 392 Expect(fakeOutput.StartCallCount()).To(Equal(2)) 393 Expect(fakeOutput.StopCallCount()).To(Equal(2)) 394 }) 395 396 Context("when displaying the logs have an error", func() { 397 var expectedErr error 398 BeforeEach(func() { 399 expectedErr = errors.New("Display error on request") 400 fakeOutput.StartReturns(expectedErr) 401 }) 402 403 It("calls handle internal error", func() { 404 Expect(err).ToNot(HaveOccurred()) 405 406 Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(2)) 407 Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr)) 408 Expect(fakeOutput.HandleInternalErrorArgsForCall(1)).To(MatchError(expectedErr)) 409 }) 410 }) 411 }) 412 })