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