github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/transport/hijack_streamer_test.go (about) 1 package transport_test 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net" 13 "net/http" 14 "net/url" 15 "strings" 16 "time" 17 18 "code.cloudfoundry.org/garden" 19 "github.com/pf-qiu/concourse/v6/atc/db/dbfakes" 20 gconn "github.com/pf-qiu/concourse/v6/atc/worker/gclient/connection" 21 "github.com/pf-qiu/concourse/v6/atc/worker/transport" 22 "github.com/pf-qiu/concourse/v6/atc/worker/transport/transportfakes" 23 . "github.com/onsi/ginkgo" 24 . "github.com/onsi/gomega" 25 "github.com/tedsuo/rata" 26 27 "github.com/pf-qiu/concourse/v6/atc/db" 28 "github.com/concourse/retryhttp/retryhttpfakes" 29 ) 30 31 var _ = Describe("hijackStreamer", func() { 32 var ( 33 savedWorker *dbfakes.FakeWorker 34 savedWorkerAddress string 35 fakeDB *transportfakes.FakeTransportDB 36 fakeRoundTripper *transportfakes.FakeRoundTripper 37 fakeHijackableClient *retryhttpfakes.FakeHijackableClient 38 hijackStreamer gconn.HijackStreamer 39 fakeRequestGenerator *transportfakes.FakeRequestGenerator 40 handler string 41 params rata.Params 42 query url.Values 43 contentType string 44 ) 45 BeforeEach(func() { 46 savedWorkerAddress = "some-garden-addr" 47 48 savedWorker = new(dbfakes.FakeWorker) 49 50 savedWorker.GardenAddrReturns(&savedWorkerAddress) 51 savedWorker.ExpiresAtReturns(time.Now().Add(123 * time.Minute)) 52 savedWorker.StateReturns(db.WorkerStateRunning) 53 54 fakeDB = new(transportfakes.FakeTransportDB) 55 fakeDB.GetWorkerReturns(savedWorker, true, nil) 56 57 fakeRequestGenerator = new(transportfakes.FakeRequestGenerator) 58 59 fakeRoundTripper = new(transportfakes.FakeRoundTripper) 60 fakeHijackableClient = new(retryhttpfakes.FakeHijackableClient) 61 62 hijackStreamer = &transport.WorkerHijackStreamer{ 63 HttpClient: &http.Client{Transport: fakeRoundTripper}, 64 HijackableClient: fakeHijackableClient, 65 Req: fakeRequestGenerator, 66 } 67 68 handler = "Ping" 69 params = map[string]string{"param1": "value1"} 70 contentType = "application/json" 71 query = map[string][]string{"key": []string{"some", "values"}} 72 73 request, err := http.NewRequest("POST", "http://example.url", strings.NewReader("some-request-body")) 74 Expect(err).NotTo(HaveOccurred()) 75 fakeRequestGenerator.CreateRequestReturns(request, nil) 76 }) 77 78 Describe("hijackStreamer #Stream", func() { 79 var ( 80 body io.Reader 81 actualReadCloser io.ReadCloser 82 streamErr error 83 httpResp http.Response 84 expectedString string 85 ) 86 87 BeforeEach(func() { 88 expectedString = "some-example-string" 89 body = strings.NewReader(expectedString) 90 91 fakeRoundTripper.RoundTripReturns(&httpResp, nil) 92 }) 93 94 JustBeforeEach(func() { 95 actualReadCloser, streamErr = hijackStreamer.Stream(handler, body, params, query, contentType) 96 }) 97 98 Context("when httpResponse is success", func() { 99 BeforeEach(func() { 100 httpResp = http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(body)} 101 }) 102 103 It("returns response body", func() { 104 actualBodyBytes, err := ioutil.ReadAll(actualReadCloser) 105 Expect(err).ToNot(HaveOccurred()) 106 Expect(expectedString).To(Equal(string(actualBodyBytes))) 107 }) 108 }) 109 110 Context("when httpResponse is not success", func() { 111 var fakeBody *transportfakes.FakeReadCloser 112 113 BeforeEach(func() { 114 fakeBody = new(transportfakes.FakeReadCloser) 115 httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody} 116 117 bodyBuf, _ := json.Marshal(garden.Error{Err: errors.New("some-error")}) 118 realBody := strings.NewReader(string(bodyBuf)) 119 fakeBody.ReadStub = func(buf []byte) (int, error) { 120 Expect(fakeBody.CloseCallCount()).To(BeZero()) 121 return realBody.Read(buf) 122 } 123 }) 124 125 It("closes httpResp.Body and returns error", func() { 126 Expect(actualReadCloser).To(BeNil()) 127 Expect(streamErr).To(HaveOccurred()) 128 Expect(fakeBody.CloseCallCount()).To(Equal(1)) 129 Expect(streamErr).To(MatchError("some-error")) 130 }) 131 }) 132 133 Context("when httpResponse is not success with bad response", func() { 134 var fakeBody *transportfakes.FakeReadCloser 135 136 BeforeEach(func() { 137 fakeBody = new(transportfakes.FakeReadCloser) 138 httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody} 139 140 realBody := strings.NewReader("some-error") 141 fakeBody.ReadStub = func(buf []byte) (int, error) { 142 Expect(fakeBody.CloseCallCount()).To(BeZero()) 143 return realBody.Read(buf) 144 } 145 }) 146 147 It("closes httpResp.Body and returns bad response", func() { 148 Expect(actualReadCloser).To(BeNil()) 149 Expect(streamErr).To(HaveOccurred()) 150 Expect(fakeBody.CloseCallCount()).To(Equal(1)) 151 Expect(streamErr).To(MatchError(fmt.Errorf("bad response: %s", errors.New("invalid character 's' looking for beginning of value")))) 152 }) 153 }) 154 155 Context("when httpResponse fails", func() { 156 BeforeEach(func() { 157 httpResp = http.Response{StatusCode: http.StatusTeapot, Body: ioutil.NopCloser(body)} 158 }) 159 160 It("returns error", func() { 161 Expect(actualReadCloser).To(BeNil()) 162 Expect(streamErr).To(HaveOccurred()) 163 }) 164 165 It("creates request with the right arguments", func() { 166 Expect(fakeRequestGenerator.CreateRequestCallCount()).To(Equal(1)) 167 actualHandler, actualParams, actualBody := fakeRequestGenerator.CreateRequestArgsForCall(0) 168 Expect(actualHandler).To(Equal(handler)) 169 Expect(actualParams).To(Equal(params)) 170 Expect(actualBody).To(Equal(body)) 171 }) 172 173 It("httpClient makes the right request", func() { 174 expectedRequest, err := http.NewRequest("POST", "http://example.url", strings.NewReader("some-request-body")) 175 expectedRequest.Header.Add("Content-Type", "application/json") 176 expectedRequest.URL.RawQuery = "key=some&key=values" 177 Expect(err).NotTo(HaveOccurred()) 178 Expect(fakeRoundTripper.RoundTripCallCount()).To(Equal(1)) 179 actualRequest := fakeRoundTripper.RoundTripArgsForCall(0) 180 Expect(actualRequest.Method).To(Equal(expectedRequest.Method)) 181 Expect(actualRequest.URL).To(Equal(expectedRequest.URL)) 182 Expect(actualRequest.Header).To(Equal(expectedRequest.Header)) 183 184 s, err := ioutil.ReadAll(actualRequest.Body) 185 Expect(err).NotTo(HaveOccurred()) 186 Expect(string(s)).To(Equal("some-request-body")) 187 }) 188 189 }) 190 }) 191 192 Describe("hijackStreamer #Hijack", func() { 193 var ( 194 body io.Reader 195 hijackError error 196 httpResp http.Response 197 expectedString string 198 actualHijackedConn net.Conn 199 actualResponseReader *bufio.Reader 200 fakeHijackCloser *retryhttpfakes.FakeHijackCloser 201 expectedResponseReader *bufio.Reader 202 fakeHijackedConn *retryhttpfakes.FakeConn 203 ) 204 205 BeforeEach(func() { 206 expectedResponseReader = new(bufio.Reader) 207 fakeHijackedConn = new(retryhttpfakes.FakeConn) 208 209 expectedString = "some-example-string" 210 body = strings.NewReader(expectedString) 211 fakeHijackCloser = new(retryhttpfakes.FakeHijackCloser) 212 }) 213 214 JustBeforeEach(func() { 215 actualHijackedConn, actualResponseReader, hijackError = hijackStreamer.Hijack(context.TODO(), handler, body, params, query, contentType) 216 }) 217 218 Context("when request is successful", func() { 219 BeforeEach(func() { 220 fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil) 221 httpResp = http.Response{StatusCode: http.StatusOK} 222 fakeHijackCloser.HijackReturns(fakeHijackedConn, expectedResponseReader) 223 }) 224 225 It("returns success response and hijackCloser", func() { 226 Expect(hijackError).ToNot(HaveOccurred()) 227 Expect(fakeHijackCloser.HijackCallCount()).To(Equal(1)) 228 Expect(actualHijackedConn).To(Equal(fakeHijackedConn)) 229 Expect(actualResponseReader).To(Equal(expectedResponseReader)) 230 }) 231 }) 232 233 Context("when httpResponse is not success", func() { 234 var fakeBody *transportfakes.FakeReadCloser 235 236 BeforeEach(func() { 237 fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil) 238 fakeBody = new(transportfakes.FakeReadCloser) 239 httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody} 240 241 realBody := strings.NewReader("some-error") 242 fakeBody.ReadStub = func(buf []byte) (int, error) { 243 Expect(fakeBody.CloseCallCount()).To(BeZero()) 244 return realBody.Read(buf) 245 } 246 }) 247 248 It("closes httpResp.Body, hijackCloser and returns error", func() { 249 Expect(fakeHijackCloser).NotTo(BeNil()) 250 Expect(hijackError).To(HaveOccurred()) 251 Expect(fakeBody.CloseCallCount()).To(Equal(1)) 252 Expect(hijackError).To(MatchError(fmt.Errorf("Backend error: Exit status: %d, message: %s", httpResp.StatusCode, "some-error"))) 253 Expect(fakeHijackCloser.CloseCallCount()).To(Equal(1)) 254 }) 255 }) 256 257 Context("when httpResponse fails with ExecutableNotFoundError", func() { 258 var fakeBody *transportfakes.FakeReadCloser 259 gerr := garden.ExecutableNotFoundError{Message: "sorry, couldn't find it"} 260 261 BeforeEach(func() { 262 fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil) 263 fakeBody = new(transportfakes.FakeReadCloser) 264 httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody} 265 266 jsonBody, _ := garden.Error{Err: gerr}.MarshalJSON() 267 realBody := bytes.NewReader(jsonBody) 268 fakeBody.ReadStub = func(buf []byte) (int, error) { 269 Expect(fakeBody.CloseCallCount()).To(BeZero()) 270 return realBody.Read(buf) 271 } 272 }) 273 274 It("closes httpResp.Body, hijackCloser and returns ExecutableNotFoundError", func() { 275 Expect(fakeHijackCloser).NotTo(BeNil()) 276 Expect(hijackError).To(HaveOccurred()) 277 Expect(fakeBody.CloseCallCount()).To(Equal(1)) 278 Expect(hijackError).To(MatchError(gerr)) 279 Expect(fakeHijackCloser.CloseCallCount()).To(Equal(1)) 280 }) 281 }) 282 283 Context("when httpResponse is not success with bad response", func() { 284 var fakeBody *transportfakes.FakeReadCloser 285 286 BeforeEach(func() { 287 fakeHijackableClient.DoReturns(&httpResp, fakeHijackCloser, nil) 288 fakeBody = new(transportfakes.FakeReadCloser) 289 httpResp = http.Response{StatusCode: http.StatusTeapot, Body: fakeBody} 290 291 fakeBody.ReadStub = func(buf []byte) (int, error) { 292 Expect(fakeBody.CloseCallCount()).To(BeZero()) 293 return 0, errors.New("error reading") 294 } 295 }) 296 297 It("closes httpResp.Body and returns bad response", func() { 298 Expect(fakeHijackCloser).NotTo(BeNil()) 299 Expect(hijackError).To(HaveOccurred()) 300 Expect(fakeBody.CloseCallCount()).To(Equal(1)) 301 Expect(hijackError).To(MatchError(fmt.Errorf("Backend error: Exit status: %d, error reading response body: %s", httpResp.StatusCode, "error reading"))) 302 Expect(fakeHijackCloser.CloseCallCount()).To(Equal(1)) 303 }) 304 }) 305 306 Context("when httpResponse fails", func() { 307 BeforeEach(func() { 308 fakeHijackableClient.DoReturns(nil, nil, errors.New("Request failed")) 309 httpResp = http.Response{StatusCode: http.StatusTeapot, Body: ioutil.NopCloser(body)} 310 }) 311 312 It("returns error", func() { 313 Expect(hijackError).To(HaveOccurred()) 314 Expect(actualHijackedConn).To(BeNil()) 315 Expect(actualResponseReader).To(BeNil()) 316 }) 317 318 It("makes the right request", func() { 319 expectedRequest, err := http.NewRequest("POST", "http://example.url", strings.NewReader("some-request-body")) 320 expectedRequest.Header.Add("Content-Type", "application/json") 321 expectedRequest.URL.RawQuery = "key=some&key=values" 322 Expect(err).NotTo(HaveOccurred()) 323 Expect(fakeHijackableClient.DoCallCount()).To(Equal(1)) 324 actualRequest := fakeHijackableClient.DoArgsForCall(0) 325 326 Expect(actualRequest.Method).To(Equal(expectedRequest.Method)) 327 Expect(actualRequest.URL).To(Equal(expectedRequest.URL)) 328 Expect(actualRequest.Header).To(Equal(expectedRequest.Header)) 329 330 s, err := ioutil.ReadAll(actualRequest.Body) 331 Expect(err).NotTo(HaveOccurred()) 332 Expect(string(s)).To(Equal("some-request-body")) 333 }) 334 }) 335 }) 336 })