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