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  })