github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/buildserver/eventhandler_test.go (about) 1 package buildserver_test 2 3 import ( 4 "encoding/json" 5 "errors" 6 . "github.com/pf-qiu/concourse/v6/atc/testhelpers" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "time" 11 12 "code.cloudfoundry.org/lager/lagertest" 13 . "github.com/pf-qiu/concourse/v6/atc/api/buildserver" 14 "github.com/pf-qiu/concourse/v6/atc/db" 15 "github.com/pf-qiu/concourse/v6/atc/db/dbfakes" 16 "github.com/pf-qiu/concourse/v6/atc/event" 17 "github.com/vito/go-sse/sse" 18 19 . "github.com/onsi/ginkgo" 20 . "github.com/onsi/gomega" 21 ) 22 23 func fakeEvent(payload string) event.Envelope { 24 msg := json.RawMessage(payload) 25 return event.Envelope{ 26 Data: &msg, 27 Event: "fake", 28 Version: "42.0", 29 } 30 } 31 32 var _ = Describe("Handler", func() { 33 var ( 34 build *dbfakes.FakeBuild 35 36 server *httptest.Server 37 ) 38 39 BeforeEach(func() { 40 build = new(dbfakes.FakeBuild) 41 42 server = httptest.NewServer(NewEventHandler(lagertest.NewTestLogger("test"), build)) 43 }) 44 45 Describe("GET", func() { 46 var ( 47 request *http.Request 48 response *http.Response 49 ) 50 51 BeforeEach(func() { 52 var err error 53 54 request, err = http.NewRequest("GET", server.URL, nil) 55 Expect(err).NotTo(HaveOccurred()) 56 }) 57 58 Context("when subscribing to the build succeeds", func() { 59 var fakeEventSource *dbfakes.FakeEventSource 60 var returnedEvents []event.Envelope 61 62 BeforeEach(func() { 63 returnedEvents = []event.Envelope{ 64 fakeEvent(`{"event":1}`), 65 fakeEvent(`{"event":2}`), 66 fakeEvent(`{"event":3}`), 67 } 68 69 fakeEventSource = new(dbfakes.FakeEventSource) 70 71 build.EventsStub = func(from uint) (db.EventSource, error) { 72 fakeEventSource.NextStub = func() (event.Envelope, error) { 73 defer GinkgoRecover() 74 75 Expect(fakeEventSource.CloseCallCount()).To(Equal(0)) 76 77 if from >= uint(len(returnedEvents)) { 78 return event.Envelope{}, db.ErrEndOfBuildEventStream 79 } 80 81 from++ 82 83 return returnedEvents[from-1], nil 84 } 85 86 return fakeEventSource, nil 87 } 88 }) 89 90 AfterEach(func() { 91 Eventually(fakeEventSource.CloseCallCount, 30*time.Second).Should(Equal(1)) 92 }) 93 94 JustBeforeEach(func() { 95 var err error 96 97 client := &http.Client{ 98 Transport: &http.Transport{}, 99 } 100 response, err = client.Do(request) 101 Expect(err).NotTo(HaveOccurred()) 102 }) 103 104 It("gets the events from the right build, starting at 0", func() { 105 _ = response.Body.Close() 106 Eventually(build.EventsCallCount).Should(Equal(1)) 107 actualFrom := build.EventsArgsForCall(0) 108 Expect(actualFrom).To(BeZero()) 109 }) 110 111 It("returns 200", func() { 112 _ = response.Body.Close() 113 Expect(response.StatusCode).To(Equal(http.StatusOK)) 114 }) 115 116 It("returns Content-Type as text/event-stream", func() { 117 _ = response.Body.Close() 118 expectedHeaderEntries := map[string]string{ 119 "Content-Type": "text/event-stream; charset=utf-8", 120 "Cache-Control": "no-cache, no-store, must-revalidate", 121 "X-Accel-Buffering": "no", 122 } 123 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 124 125 expectedHeaderEntries = map[string]string{ 126 "Connection": "keep-alive", 127 } 128 Expect(response).ShouldNot(IncludeHeaderEntries(expectedHeaderEntries)) 129 130 }) 131 132 It("returns the protocol version as X-ATC-Stream-Version", func() { 133 _ = response.Body.Close() 134 expectedHeaderEntries := map[string]string{ 135 "X-Atc-Stream-Version": "2.0", 136 } 137 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 138 }) 139 140 It("emits them, followed by an end event", func() { 141 defer db.Close(response.Body) 142 reader := sse.NewReadCloser(response.Body) 143 144 Expect(reader.Next()).To(Equal(sse.Event{ 145 ID: "0", 146 Name: "event", 147 Data: []byte(`{"data":{"event":1},"event":"fake","version":"42.0"}`), 148 })) 149 150 Expect(reader.Next()).To(Equal(sse.Event{ 151 ID: "1", 152 Name: "event", 153 Data: []byte(`{"data":{"event":2},"event":"fake","version":"42.0"}`), 154 })) 155 156 Expect(reader.Next()).To(Equal(sse.Event{ 157 ID: "2", 158 Name: "event", 159 Data: []byte(`{"data":{"event":3},"event":"fake","version":"42.0"}`), 160 })) 161 162 Expect(reader.Next()).To(Equal(sse.Event{ 163 ID: "3", 164 Name: "end", 165 Data: []byte{}, 166 })) 167 }) 168 169 Context("when the Last-Event-ID header is given", func() { 170 BeforeEach(func() { 171 request.Header.Set("Last-Event-ID", "1") 172 }) 173 174 It("starts subscribing from after the id", func() { 175 _ = response.Body.Close() 176 Eventually(build.EventsCallCount).Should(Equal(1)) 177 actualFrom := build.EventsArgsForCall(0) 178 Expect(actualFrom).To(Equal(uint(2))) 179 }) 180 }) 181 }) 182 183 Context("when the eventsource returns an error", func() { 184 var fakeEventSource *dbfakes.FakeEventSource 185 var disaster error 186 187 BeforeEach(func() { 188 disaster = errors.New("a coffee machine") 189 190 fakeEventSource = new(dbfakes.FakeEventSource) 191 192 from := 0 193 fakeEventSource.NextStub = func() (event.Envelope, error) { 194 defer GinkgoRecover() 195 196 Expect(fakeEventSource.CloseCallCount()).To(Equal(0)) 197 198 from++ 199 200 if from == 1 { 201 return fakeEvent(`{"event":1}`), nil 202 } else { 203 return event.Envelope{}, disaster 204 } 205 } 206 207 build.EventsReturns(fakeEventSource, nil) 208 }) 209 210 AfterEach(func() { 211 Eventually(fakeEventSource.CloseCallCount, 30*time.Second).Should(Equal(1)) 212 }) 213 214 JustBeforeEach(func() { 215 var err error 216 217 client := &http.Client{ 218 Transport: &http.Transport{}, 219 } 220 response, err = client.Do(request) 221 Expect(err).NotTo(HaveOccurred()) 222 }) 223 224 It("just stops sending events", func() { 225 reader := sse.NewReadCloser(response.Body) 226 227 Expect(reader.Next()).To(Equal(sse.Event{ 228 ID: "0", 229 Name: "event", 230 Data: []byte(`{"data":{"event":1},"event":"fake","version":"42.0"}`), 231 })) 232 233 _, err := reader.Next() 234 Expect(err).To(HaveOccurred()) 235 Expect(err).To(Equal(io.EOF)) 236 }) 237 }) 238 239 Context("when the event stream never ends", func() { 240 var fakeEventSource *dbfakes.FakeEventSource 241 BeforeEach(func() { 242 fakeEventSource = new(dbfakes.FakeEventSource) 243 fakeEventSource.NextReturns(fakeEvent(`{"event":1}`), nil) 244 build.EventsReturns(fakeEventSource, nil) 245 }) 246 247 JustBeforeEach(func() { 248 var err error 249 250 client := &http.Client{ 251 Transport: &http.Transport{}, 252 } 253 response, err = client.Do(request) 254 Expect(err).NotTo(HaveOccurred()) 255 }) 256 257 Context("when request accepts gzip", func() { 258 BeforeEach(func() { 259 request.Header.Set("Accept-Encoding", "gzip") 260 }) 261 262 It("closes the event stream when connection is closed", func() { 263 err := response.Body.Close() 264 Expect(err).NotTo(HaveOccurred()) 265 Eventually(fakeEventSource.CloseCallCount, 30*time.Second).Should(Equal(1)) 266 }) 267 }) 268 }) 269 270 Context("when subscribing to it fails", func() { 271 BeforeEach(func() { 272 build.EventsReturns(nil, errors.New("nope")) 273 }) 274 275 JustBeforeEach(func() { 276 var err error 277 278 client := &http.Client{ 279 Transport: &http.Transport{}, 280 } 281 response, err = client.Do(request) 282 Expect(err).NotTo(HaveOccurred()) 283 }) 284 285 It("returns 500", func() { 286 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 287 }) 288 }) 289 }) 290 })