github.com/dcarley/cf-cli@v6.24.1-0.20170220111324-4225ff346898+incompatible/api/cloudcontroller/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/cloudcontroller"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/cloudcontrollerfakes"
    14  	. "code.cloudfoundry.org/cli/api/cloudcontroller/wrapper"
    15  	"code.cloudfoundry.org/cli/api/cloudcontroller/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 *cloudcontrollerfakes.FakeConnection
    24  		fakeOutput     *wrapperfakes.FakeRequestLoggerOutput
    25  
    26  		wrapper cloudcontroller.Connection
    27  
    28  		request  *http.Request
    29  		response *cloudcontroller.Response
    30  		err      error
    31  	)
    32  
    33  	BeforeEach(func() {
    34  		fakeConnection = new(cloudcontrollerfakes.FakeConnection)
    35  		fakeOutput = new(wrapperfakes.FakeRequestLoggerOutput)
    36  
    37  		wrapper = NewRequestLogger(fakeOutput).Wrap(fakeConnection)
    38  
    39  		var err error
    40  		request, err = http.NewRequest(http.MethodGet, "https://foo.bar.com/banana", nil)
    41  		Expect(err).NotTo(HaveOccurred())
    42  
    43  		request.URL.RawQuery = url.Values{
    44  			"query1": {"a"},
    45  			"query2": {"b"},
    46  		}.Encode()
    47  
    48  		headers := http.Header{}
    49  		headers.Add("Aghi", "bar")
    50  		headers.Add("Abc", "json")
    51  		headers.Add("Adef", "application/json")
    52  		request.Header = headers
    53  
    54  		response = &cloudcontroller.Response{
    55  			RawResponse:  []byte("some-response-body"),
    56  			HTTPResponse: &http.Response{},
    57  		}
    58  	})
    59  
    60  	JustBeforeEach(func() {
    61  		err = wrapper.Make(request, response)
    62  	})
    63  
    64  	Describe("Make", func() {
    65  		It("outputs the request", func() {
    66  			Expect(err).NotTo(HaveOccurred())
    67  
    68  			Expect(fakeOutput.DisplayTypeCallCount()).To(BeNumerically(">=", 1))
    69  			name, date := fakeOutput.DisplayTypeArgsForCall(0)
    70  			Expect(name).To(Equal("REQUEST"))
    71  			Expect(date).To(BeTemporally("~", time.Now(), time.Second))
    72  
    73  			Expect(fakeOutput.DisplayRequestHeaderCallCount()).To(Equal(1))
    74  			method, uri, protocol := fakeOutput.DisplayRequestHeaderArgsForCall(0)
    75  			Expect(method).To(Equal(http.MethodGet))
    76  			Expect(uri).To(MatchRegexp("/banana\\?(?:query1=a&query2=b|query2=b&query1=a)"))
    77  			Expect(protocol).To(Equal("HTTP/1.1"))
    78  
    79  			Expect(fakeOutput.DisplayHostCallCount()).To(Equal(1))
    80  			host := fakeOutput.DisplayHostArgsForCall(0)
    81  			Expect(host).To(Equal("foo.bar.com"))
    82  
    83  			Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 3))
    84  			name, value := fakeOutput.DisplayHeaderArgsForCall(0)
    85  			Expect(name).To(Equal("Abc"))
    86  			Expect(value).To(Equal("json"))
    87  			name, value = fakeOutput.DisplayHeaderArgsForCall(1)
    88  			Expect(name).To(Equal("Adef"))
    89  			Expect(value).To(Equal("application/json"))
    90  			name, value = fakeOutput.DisplayHeaderArgsForCall(2)
    91  			Expect(name).To(Equal("Aghi"))
    92  			Expect(value).To(Equal("bar"))
    93  		})
    94  
    95  		Context("when an authorization header is in the request", func() {
    96  			BeforeEach(func() {
    97  				request.Header = http.Header{"Authorization": []string{"should not be shown"}}
    98  			})
    99  
   100  			It("redacts the contents of the authorization header", func() {
   101  				Expect(err).NotTo(HaveOccurred())
   102  				Expect(fakeOutput.DisplayHeaderCallCount()).To(Equal(1))
   103  				key, value := fakeOutput.DisplayHeaderArgsForCall(0)
   104  				Expect(key).To(Equal("Authorization"))
   105  				Expect(value).To(Equal("[PRIVATE DATA HIDDEN]"))
   106  			})
   107  		})
   108  
   109  		Context("when passed a body", func() {
   110  			var originalBody io.ReadCloser
   111  			BeforeEach(func() {
   112  				originalBody = ioutil.NopCloser(bytes.NewReader([]byte("foo")))
   113  				request.Body = originalBody
   114  			})
   115  
   116  			It("outputs the body", func() {
   117  				Expect(err).NotTo(HaveOccurred())
   118  
   119  				Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1))
   120  				Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal([]byte("foo")))
   121  
   122  				bytes, err := ioutil.ReadAll(request.Body)
   123  				Expect(err).NotTo(HaveOccurred())
   124  				Expect(bytes).To(Equal([]byte("foo")))
   125  			})
   126  		})
   127  
   128  		Context("when an error occures while trying to log the request", func() {
   129  			var expectedErr error
   130  
   131  			BeforeEach(func() {
   132  				expectedErr = errors.New("this should never block the request")
   133  
   134  				calledOnce := false
   135  				fakeOutput.StartStub = func() error {
   136  					if !calledOnce {
   137  						calledOnce = true
   138  						return expectedErr
   139  					}
   140  					return nil
   141  				}
   142  			})
   143  
   144  			It("should display the error and continue on", func() {
   145  				Expect(err).NotTo(HaveOccurred())
   146  
   147  				Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(1))
   148  				Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr))
   149  			})
   150  		})
   151  
   152  		Context("when the request is successful", func() {
   153  			BeforeEach(func() {
   154  				response = &cloudcontroller.Response{
   155  					RawResponse: []byte("some-response-body"),
   156  					HTTPResponse: &http.Response{
   157  						Proto:  "HTTP/1.1",
   158  						Status: "200 OK",
   159  						Header: http.Header{
   160  							"BBBBB": {"second"},
   161  							"AAAAA": {"first"},
   162  							"CCCCC": {"third"},
   163  						},
   164  					},
   165  				}
   166  			})
   167  
   168  			It("outputs the response", func() {
   169  				Expect(err).NotTo(HaveOccurred())
   170  
   171  				Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2))
   172  				name, date := fakeOutput.DisplayTypeArgsForCall(1)
   173  				Expect(name).To(Equal("RESPONSE"))
   174  				Expect(date).To(BeTemporally("~", time.Now(), time.Second))
   175  
   176  				Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1))
   177  				protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0)
   178  				Expect(protocol).To(Equal("HTTP/1.1"))
   179  				Expect(status).To(Equal("200 OK"))
   180  
   181  				Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 6))
   182  				name, value := fakeOutput.DisplayHeaderArgsForCall(3)
   183  				Expect(name).To(Equal("AAAAA"))
   184  				Expect(value).To(Equal("first"))
   185  				name, value = fakeOutput.DisplayHeaderArgsForCall(4)
   186  				Expect(name).To(Equal("BBBBB"))
   187  				Expect(value).To(Equal("second"))
   188  				name, value = fakeOutput.DisplayHeaderArgsForCall(5)
   189  				Expect(name).To(Equal("CCCCC"))
   190  				Expect(value).To(Equal("third"))
   191  
   192  				Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1))
   193  				Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal([]byte("some-response-body")))
   194  			})
   195  		})
   196  
   197  		Context("when the request is unsuccessful", func() {
   198  			var expectedErr error
   199  
   200  			BeforeEach(func() {
   201  				expectedErr = errors.New("banana")
   202  				fakeConnection.MakeReturns(expectedErr)
   203  			})
   204  
   205  			Context("when the http response is not set", func() {
   206  				BeforeEach(func() {
   207  					response = &cloudcontroller.Response{}
   208  				})
   209  
   210  				It("outputs nothing", func() {
   211  					Expect(err).To(MatchError(expectedErr))
   212  					Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(0))
   213  				})
   214  			})
   215  
   216  			Context("when the http response is set", func() {
   217  				BeforeEach(func() {
   218  					response = &cloudcontroller.Response{
   219  						RawResponse: []byte("some-error-body"),
   220  						HTTPResponse: &http.Response{
   221  							Proto:  "HTTP/1.1",
   222  							Status: "200 OK",
   223  							Header: http.Header{
   224  								"BBBBB": {"second"},
   225  								"AAAAA": {"first"},
   226  								"CCCCC": {"third"},
   227  							},
   228  						},
   229  					}
   230  				})
   231  
   232  				It("outputs the response", func() {
   233  					Expect(err).To(MatchError(expectedErr))
   234  
   235  					Expect(fakeOutput.DisplayTypeCallCount()).To(Equal(2))
   236  					name, date := fakeOutput.DisplayTypeArgsForCall(1)
   237  					Expect(name).To(Equal("RESPONSE"))
   238  					Expect(date).To(BeTemporally("~", time.Now(), time.Second))
   239  
   240  					Expect(fakeOutput.DisplayResponseHeaderCallCount()).To(Equal(1))
   241  					protocol, status := fakeOutput.DisplayResponseHeaderArgsForCall(0)
   242  					Expect(protocol).To(Equal("HTTP/1.1"))
   243  					Expect(status).To(Equal("200 OK"))
   244  
   245  					Expect(fakeOutput.DisplayHeaderCallCount()).To(BeNumerically(">=", 6))
   246  					name, value := fakeOutput.DisplayHeaderArgsForCall(3)
   247  					Expect(name).To(Equal("AAAAA"))
   248  					Expect(value).To(Equal("first"))
   249  					name, value = fakeOutput.DisplayHeaderArgsForCall(4)
   250  					Expect(name).To(Equal("BBBBB"))
   251  					Expect(value).To(Equal("second"))
   252  					name, value = fakeOutput.DisplayHeaderArgsForCall(5)
   253  					Expect(name).To(Equal("CCCCC"))
   254  					Expect(value).To(Equal("third"))
   255  
   256  					Expect(fakeOutput.DisplayJSONBodyCallCount()).To(BeNumerically(">=", 1))
   257  					Expect(fakeOutput.DisplayJSONBodyArgsForCall(0)).To(Equal([]byte("some-error-body")))
   258  				})
   259  			})
   260  		})
   261  
   262  		Context("when an error occures while trying to log the response", func() {
   263  			var (
   264  				originalErr error
   265  				expectedErr error
   266  			)
   267  
   268  			BeforeEach(func() {
   269  				originalErr = errors.New("this error should not be overwritten")
   270  				fakeConnection.MakeReturns(originalErr)
   271  
   272  				expectedErr = errors.New("this should never block the request")
   273  
   274  				calledOnce := false
   275  				fakeOutput.StartStub = func() error {
   276  					if !calledOnce {
   277  						calledOnce = true
   278  						return nil
   279  					}
   280  					return expectedErr
   281  				}
   282  			})
   283  
   284  			It("should display the error and continue on", func() {
   285  				Expect(err).To(MatchError(originalErr))
   286  
   287  				Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(1))
   288  				Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr))
   289  			})
   290  		})
   291  
   292  		It("starts and stops the output", func() {
   293  			Expect(fakeOutput.StartCallCount()).To(Equal(2))
   294  			Expect(fakeOutput.StopCallCount()).To(Equal(2))
   295  		})
   296  
   297  		Context("when displaying the logs have an error", func() {
   298  			var expectedErr error
   299  			BeforeEach(func() {
   300  				expectedErr = errors.New("Display error on request")
   301  				fakeOutput.StartReturns(expectedErr)
   302  			})
   303  
   304  			It("calls handle internal error", func() {
   305  				Expect(err).ToNot(HaveOccurred())
   306  
   307  				Expect(fakeOutput.HandleInternalErrorCallCount()).To(Equal(2))
   308  				Expect(fakeOutput.HandleInternalErrorArgsForCall(0)).To(MatchError(expectedErr))
   309  				Expect(fakeOutput.HandleInternalErrorArgsForCall(1)).To(MatchError(expectedErr))
   310  			})
   311  		})
   312  	})
   313  })