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