github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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 makeErr 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 makeErr = wrapper.Make(request, response, fakeProxyReader) 64 }) 65 66 Describe("Make", func() { 67 It("outputs the request", func() { 68 Expect(makeErr).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(fakeConnection.MakeCallCount()).To(Equal(1)) 97 _, _, proxyReader := fakeConnection.MakeArgsForCall(0) 98 Expect(proxyReader).To(Equal(fakeProxyReader)) 99 }) 100 101 Context("when an authorization header is in the request", func() { 102 BeforeEach(func() { 103 request.Header = http.Header{"Authorization": []string{"should not be shown"}} 104 }) 105 106 It("redacts the contents of the authorization header", func() { 107 Expect(makeErr).NotTo(HaveOccurred()) 108 Expect(fakeOutput.DisplayHeaderCallCount()).To(Equal(1)) 109 key, value := fakeOutput.DisplayHeaderArgsForCall(0) 110 Expect(key).To(Equal("Authorization")) 111 Expect(value).To(Equal("[PRIVATE DATA HIDDEN]")) 112 }) 113 }) 114 115 Context("when passed a body", func() { 116 Context("when the request's Content-Type is application/json", func() { 117 var originalBody io.ReadCloser 118 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(makeErr).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(makeErr).NotTo(HaveOccurred()) 144 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(Equal(0)) 145 }) 146 }) 147 }) 148 149 Context("when an error occures while trying to log the request", func() { 150 var expectedErr error 151 152 BeforeEach(func() { 153 expectedErr = errors.New("this should never block the request") 154 155 calledOnce := false 156 fakeOutput.StartStub = func() error { 157 if !calledOnce { 158 calledOnce = true 159 return expectedErr 160 } 161 return nil 162 } 163 }) 164 165 It("should display the error and continue on", func() { 166 Expect(makeErr).NotTo(HaveOccurred()) 167 168 Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(1)) 169 Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr)) 170 }) 171 }) 172 173 Context("when the request is successful", func() { 174 Context("when the response is JSON", func() { 175 BeforeEach(func() { 176 response = &plugin.Response{ 177 RawResponse: []byte(`{"some-key":"some-value"}`), 178 HTTPResponse: &http.Response{ 179 Proto: "HTTP/1.1", 180 Status: "200 OK", 181 Header: http.Header{ 182 "Content-Type": {"application/json"}, 183 "BBBBB": {"second"}, 184 "AAAAA": {"first"}, 185 "CCCCC": {"third"}, 186 }, 187 Body: ioutil.NopCloser(bytes.NewReader([]byte(`{"some-key":"some-value"}`))), 188 }, 189 } 190 }) 191 192 It("outputs the response", func() { 193 Expect(makeErr).NotTo(HaveOccurred()) 194 195 Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2)) 196 name, date := fakeOutput.DisplayTypeArgsForCall(1) 197 Expect(name).To(Equal("RESPONSE")) 198 Expect(date).To(BeTemporally("~", time.Now(), time.Second)) 199 200 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 201 protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0) 202 Expect(protocol).To(Equal("HTTP/1.1")) 203 Expect(status).To(Equal("200 OK")) 204 205 Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 7)) 206 name, value := fakeOutput.DisplayHeaderArgsForCall(3) 207 Expect(name).To(Equal("AAAAA")) 208 Expect(value).To(Equal("first")) 209 name, value = fakeOutput.DisplayHeaderArgsForCall(4) 210 Expect(name).To(Equal("BBBBB")) 211 Expect(value).To(Equal("second")) 212 name, value = fakeOutput.DisplayHeaderArgsForCall(5) 213 Expect(name).To(Equal("CCCCC")) 214 Expect(value).To(Equal("third")) 215 name, value = fakeOutput.DisplayHeaderArgsForCall(6) 216 Expect(name).To(Equal("Content-Type")) 217 Expect(value).To(Equal("application/json")) 218 219 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1)) 220 Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal(response.RawResponse)) 221 222 Expect(fakeOutput.DisplayDumpCallCount()).To(Equal(0)) 223 }) 224 }) 225 226 Context("when the response is not JSON", func() { 227 BeforeEach(func() { 228 response = &plugin.Response{ 229 RawResponse: []byte(`not JSON`), 230 HTTPResponse: &http.Response{ 231 Proto: "HTTP/1.1", 232 Status: "200 OK", 233 Header: http.Header{ 234 "BBBBB": {"second"}, 235 "AAAAA": {"first"}, 236 "CCCCC": {"third"}, 237 }, 238 Body: ioutil.NopCloser(bytes.NewReader([]byte(`not JSON`))), 239 }, 240 } 241 }) 242 243 It("outputs the response", func() { 244 Expect(makeErr).NotTo(HaveOccurred()) 245 246 Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2)) 247 name, date := fakeOutput.DisplayTypeArgsForCall(1) 248 Expect(name).To(Equal("RESPONSE")) 249 Expect(date).To(BeTemporally("~", time.Now(), time.Second)) 250 251 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 252 protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0) 253 Expect(protocol).To(Equal("HTTP/1.1")) 254 Expect(status).To(Equal("200 OK")) 255 256 Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 6)) 257 name, value := fakeOutput.DisplayHeaderArgsForCall(3) 258 Expect(name).To(Equal("AAAAA")) 259 Expect(value).To(Equal("first")) 260 name, value = fakeOutput.DisplayHeaderArgsForCall(4) 261 Expect(name).To(Equal("BBBBB")) 262 Expect(value).To(Equal("second")) 263 name, value = fakeOutput.DisplayHeaderArgsForCall(5) 264 Expect(name).To(Equal("CCCCC")) 265 Expect(value).To(Equal("third")) 266 267 Expect(fakeOutput.DisplayDumpCallCount()).To(Equal(1)) 268 text := fakeOutput.DisplayDumpArgsForCall(0) 269 Expect(text).To(Equal("[NON-JSON BODY CONTENT HIDDEN]")) 270 271 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(Equal(0)) 272 }) 273 }) 274 }) 275 276 Context("when the request is unsuccessful", func() { 277 var expectedErr error 278 279 BeforeEach(func() { 280 expectedErr = errors.New("banana") 281 fakeConnection.MakeReturns(expectedErr) 282 }) 283 284 Context("when the http response is not set", func() { 285 BeforeEach(func() { 286 response = &plugin.Response{} 287 }) 288 289 It("outputs nothing", func() { 290 Expect(makeErr).To(MatchError(expectedErr)) 291 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(0)) 292 }) 293 }) 294 295 Context("when the http response body is nil", func() { 296 BeforeEach(func() { 297 response = &plugin.Response{ 298 HTTPResponse: &http.Response{Body: nil}, 299 } 300 }) 301 302 It("does not output the response body", func() { 303 Expect(makeErr).To(MatchError(expectedErr)) 304 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 305 306 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(Equal(0)) 307 Expect(fakeOutput.DisplayDumpCallCount()).To(Equal(0)) 308 }) 309 }) 310 311 Context("when the http response is set", func() { 312 BeforeEach(func() { 313 response = &plugin.Response{ 314 RawResponse: []byte("some-error-body"), 315 HTTPResponse: &http.Response{ 316 Proto: "HTTP/1.1", 317 Status: "200 OK", 318 Header: http.Header{ 319 "Content-Type": {"application/json"}, 320 "BBBBB": {"second"}, 321 "AAAAA": {"first"}, 322 "CCCCC": {"third"}, 323 }, 324 Body: ioutil.NopCloser(bytes.NewReader([]byte(`some-error-body`))), 325 }, 326 } 327 }) 328 329 It("outputs the response", func() { 330 Expect(makeErr).To(MatchError(expectedErr)) 331 332 Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2)) 333 name, date := fakeOutput.DisplayTypeArgsForCall(1) 334 Expect(name).To(Equal("RESPONSE")) 335 Expect(date).To(BeTemporally("~", time.Now(), time.Second)) 336 337 Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1)) 338 protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0) 339 Expect(protocol).To(Equal("HTTP/1.1")) 340 Expect(status).To(Equal("200 OK")) 341 342 Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 7)) 343 name, value := fakeOutput.DisplayHeaderArgsForCall(3) 344 Expect(name).To(Equal("AAAAA")) 345 Expect(value).To(Equal("first")) 346 name, value = fakeOutput.DisplayHeaderArgsForCall(4) 347 Expect(name).To(Equal("BBBBB")) 348 Expect(value).To(Equal("second")) 349 name, value = fakeOutput.DisplayHeaderArgsForCall(5) 350 Expect(name).To(Equal("CCCCC")) 351 Expect(value).To(Equal("third")) 352 name, value = fakeOutput.DisplayHeaderArgsForCall(6) 353 Expect(name).To(Equal("Content-Type")) 354 Expect(value).To(Equal("application/json")) 355 356 Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1)) 357 Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal([]byte("some-error-body"))) 358 }) 359 }) 360 }) 361 362 Context("when an error occures while trying to log the response", func() { 363 var ( 364 originalErr error 365 expectedErr error 366 ) 367 368 BeforeEach(func() { 369 originalErr = errors.New("this error should not be overwritten") 370 fakeConnection.MakeReturns(originalErr) 371 372 expectedErr = errors.New("this should never block the request") 373 374 calledOnce := false 375 fakeOutput.StartStub = func() error { 376 if !calledOnce { 377 calledOnce = true 378 return nil 379 } 380 return expectedErr 381 } 382 }) 383 384 It("should display the error and continue on", func() { 385 Expect(makeErr).To(MatchError(originalErr)) 386 387 Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(1)) 388 Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr)) 389 }) 390 }) 391 392 It("starts and stops the output", func() { 393 Expect(makeErr).ToNot(HaveOccurred()) 394 Expect(fakeOutput.StartCallCount()).To(Equal(2)) 395 Expect(fakeOutput.StopCallCount()).To(Equal(2)) 396 }) 397 398 Context("when displaying the logs have an error", func() { 399 var expectedErr error 400 401 BeforeEach(func() { 402 expectedErr = errors.New("Display error on request") 403 fakeOutput.StartReturns(expectedErr) 404 }) 405 406 It("calls handle internal error", func() { 407 Expect(makeErr).ToNot(HaveOccurred()) 408 409 Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(2)) 410 Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr)) 411 Expect(fakeOutput.HandleInternalErrorArgsForCall(1)).To(MatchError(expectedErr)) 412 }) 413 }) 414 }) 415 })