github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/fly/integration/watch_test.go (about)

     1  package integration_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"os/exec"
     8  
     9  	. "github.com/onsi/ginkgo"
    10  	. "github.com/onsi/gomega"
    11  	"github.com/onsi/gomega/gbytes"
    12  	"github.com/onsi/gomega/gexec"
    13  	"github.com/onsi/gomega/ghttp"
    14  	"github.com/vito/go-sse/sse"
    15  
    16  	"github.com/pf-qiu/concourse/v6/atc"
    17  	"github.com/pf-qiu/concourse/v6/atc/event"
    18  )
    19  
    20  var _ = Describe("Watching", func() {
    21  	var streaming chan struct{}
    22  	var events chan atc.Event
    23  
    24  	BeforeEach(func() {
    25  		streaming = make(chan struct{})
    26  		events = make(chan atc.Event)
    27  	})
    28  
    29  	eventsHandler := func() http.HandlerFunc {
    30  		return ghttp.CombineHandlers(
    31  			ghttp.VerifyRequest("GET", "/api/v1/builds/3/events"),
    32  			func(w http.ResponseWriter, r *http.Request) {
    33  				flusher := w.(http.Flusher)
    34  
    35  				w.Header().Add("Content-Type", "text/event-stream; charset=utf-8")
    36  				w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
    37  				w.Header().Add("Connection", "keep-alive")
    38  
    39  				w.WriteHeader(http.StatusOK)
    40  
    41  				flusher.Flush()
    42  
    43  				close(streaming)
    44  
    45  				id := 0
    46  
    47  				for e := range events {
    48  					payload, err := json.Marshal(event.Message{Event: e})
    49  					Expect(err).NotTo(HaveOccurred())
    50  
    51  					event := sse.Event{
    52  						ID:   fmt.Sprintf("%d", id),
    53  						Name: "event",
    54  						Data: payload,
    55  					}
    56  
    57  					err = event.Write(w)
    58  					Expect(err).NotTo(HaveOccurred())
    59  
    60  					flusher.Flush()
    61  
    62  					id++
    63  				}
    64  
    65  				err := sse.Event{
    66  					Name: "end",
    67  				}.Write(w)
    68  				Expect(err).NotTo(HaveOccurred())
    69  			},
    70  		)
    71  	}
    72  
    73  	watch := func(args ...string) {
    74  		watchWithArgs := append([]string{"watch"}, args...)
    75  
    76  		flyCmd := exec.Command(flyPath, append([]string{"-t", targetName}, watchWithArgs...)...)
    77  
    78  		sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
    79  		Expect(err).NotTo(HaveOccurred())
    80  
    81  		Eventually(streaming).Should(BeClosed())
    82  
    83  		events <- event.Log{Payload: "sup"}
    84  
    85  		Eventually(sess.Out).Should(gbytes.Say("sup"))
    86  
    87  		close(events)
    88  
    89  		<-sess.Exited
    90  		Expect(sess.ExitCode()).To(Equal(0))
    91  	}
    92  
    93  	Context("with no arguments", func() {
    94  		BeforeEach(func() {
    95  			atcServer.AppendHandlers(
    96  				ghttp.CombineHandlers(
    97  					ghttp.VerifyRequest("GET", "/api/v1/builds"),
    98  					ghttp.RespondWithJSONEncoded(200, []atc.Build{
    99  						{ID: 4, Name: "1", Status: "started", JobName: "some-job"},
   100  						{ID: 3, Name: "3", Status: "started"},
   101  						{ID: 2, Name: "2", Status: "started"},
   102  						{ID: 1, Name: "1", Status: "finished"},
   103  					}),
   104  				),
   105  				eventsHandler(),
   106  			)
   107  		})
   108  
   109  		It("watches the most recent one-off build", func() {
   110  			watch()
   111  		})
   112  	})
   113  
   114  	Context("with a build ID and no job", func() {
   115  		BeforeEach(func() {
   116  			atcServer.AppendHandlers(
   117  				eventsHandler(),
   118  			)
   119  		})
   120  
   121  		It("Watches the given build id", func() {
   122  			watch("--build", "3")
   123  		})
   124  
   125  		It("Watches the given direct build URL", func() {
   126  			watch("--url", atcServer.URL()+"/builds/3")
   127  		})
   128  	})
   129  
   130  	Context("with a specific job and pipeline", func() {
   131  
   132  		var (
   133  			expectedURL         string
   134  			expectedQueryParams string
   135  			expectedStatusCode  int
   136  			expectedResponse    interface{}
   137  		)
   138  
   139  		BeforeEach(func() {
   140  			expectedURL = "/api/v1/teams/main/pipelines/some-pipeline/jobs/some-job"
   141  			expectedQueryParams = "instance_vars=%7B%22branch%22%3A%22master%22%7D"
   142  			expectedStatusCode = http.StatusOK
   143  			expectedResponse = atc.Job{}
   144  		})
   145  
   146  		JustBeforeEach(func() {
   147  			atcServer.AppendHandlers(
   148  				ghttp.CombineHandlers(
   149  					ghttp.VerifyRequest("GET", expectedURL, expectedQueryParams),
   150  					ghttp.RespondWithJSONEncoded(expectedStatusCode, expectedResponse),
   151  				),
   152  				eventsHandler(),
   153  			)
   154  		})
   155  
   156  		Context("when the job has no builds", func() {
   157  			It("returns an error and exits", func() {
   158  				flyCmd := exec.Command(flyPath, "-t", targetName, "watch", "--job", "some-pipeline/branch:master/some-job")
   159  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   160  				Expect(err).NotTo(HaveOccurred())
   161  
   162  				Eventually(sess.Err).Should(gbytes.Say("job has no builds"))
   163  				<-sess.Exited
   164  				Expect(sess.ExitCode()).To(Equal(1))
   165  			})
   166  		})
   167  
   168  		Context("when the job has a next build", func() {
   169  			BeforeEach(func() {
   170  				didStream := make(chan struct{})
   171  				streaming = didStream
   172  
   173  				expectedResponse = atc.Job{
   174  					NextBuild: &atc.Build{
   175  						ID:      3,
   176  						Name:    "3",
   177  						Status:  "started",
   178  						JobName: "some-job",
   179  					},
   180  					FinishedBuild: &atc.Build{
   181  						ID:      2,
   182  						Name:    "2",
   183  						Status:  "failed",
   184  						JobName: "some-job",
   185  					},
   186  				}
   187  			})
   188  
   189  			It("watches the job's next build", func() {
   190  				watch("--job", "some-pipeline/branch:master/some-job")
   191  			})
   192  
   193  			It("watches the job's next build URL", func() {
   194  				watch("--url", atcServer.URL()+"/teams/main/pipelines/some-pipeline/jobs/some-job?"+expectedQueryParams)
   195  			})
   196  		})
   197  
   198  		Context("when the job only has a finished build", func() {
   199  			BeforeEach(func() {
   200  				expectedResponse = atc.Job{
   201  					NextBuild: nil,
   202  					FinishedBuild: &atc.Build{
   203  						ID:      3,
   204  						Name:    "3",
   205  						Status:  "failed",
   206  						JobName: "some-job",
   207  					},
   208  				}
   209  			})
   210  
   211  			It("watches the job's finished build", func() {
   212  				watch("--job", "some-pipeline/branch:master/some-job")
   213  			})
   214  
   215  			It("watches the job's finished build URL", func() {
   216  				watch("--url", atcServer.URL()+"/teams/main/pipelines/some-pipeline/jobs/some-job?"+expectedQueryParams)
   217  			})
   218  		})
   219  
   220  		Context("with a specific build of the job", func() {
   221  			BeforeEach(func() {
   222  				expectedURL = "/api/v1/teams/main/pipelines/some-pipeline/jobs/some-job/builds/3"
   223  				expectedResponse = atc.Build{
   224  					ID:      3,
   225  					Name:    "3",
   226  					Status:  "failed",
   227  					JobName: "some-job",
   228  				}
   229  			})
   230  
   231  			It("watches the given build", func() {
   232  				watch("--job", "some-pipeline/branch:master/some-job", "--build", "3")
   233  			})
   234  
   235  			It("watches the given build URL", func() {
   236  				watch("--url", atcServer.URL()+"/teams/main/pipelines/some-pipeline/jobs/some-job/builds/3?"+expectedQueryParams)
   237  			})
   238  		})
   239  	})
   240  })