github.com/chenbh/concourse/v6@v6.4.2/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/chenbh/concourse/v6/atc" 17 "github.com/chenbh/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 Context("when the job has no builds", func() { 132 BeforeEach(func() { 133 atcServer.AppendHandlers( 134 ghttp.CombineHandlers( 135 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines/some-pipeline/jobs/some-job"), 136 ghttp.RespondWithJSONEncoded(200, atc.Job{}), 137 ), 138 eventsHandler(), 139 ) 140 }) 141 142 It("returns an error and exits", func() { 143 flyCmd := exec.Command(flyPath, "-t", targetName, "watch", "--job", "some-pipeline/some-job") 144 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 145 Expect(err).NotTo(HaveOccurred()) 146 147 Eventually(sess.Err).Should(gbytes.Say("job has no builds")) 148 <-sess.Exited 149 Expect(sess.ExitCode()).To(Equal(1)) 150 }) 151 }) 152 153 Context("when the job has a next build", func() { 154 BeforeEach(func() { 155 didStream := make(chan struct{}) 156 streaming = didStream 157 158 atcServer.AppendHandlers( 159 ghttp.CombineHandlers( 160 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines/some-pipeline/jobs/some-job"), 161 ghttp.RespondWithJSONEncoded(200, atc.Job{ 162 NextBuild: &atc.Build{ 163 ID: 3, 164 Name: "3", 165 Status: "started", 166 JobName: "some-job", 167 }, 168 FinishedBuild: &atc.Build{ 169 ID: 2, 170 Name: "2", 171 Status: "failed", 172 JobName: "some-job", 173 }, 174 }), 175 ), 176 eventsHandler(), 177 ) 178 }) 179 180 It("watches the job's next build", func() { 181 watch("--job", "some-pipeline/some-job") 182 }) 183 184 It("watches the job's next build URL", func() { 185 watch("--url", atcServer.URL()+"/teams/main/pipelines/some-pipeline/jobs/some-job") 186 }) 187 }) 188 189 Context("when the job only has a finished build", func() { 190 BeforeEach(func() { 191 atcServer.AppendHandlers( 192 ghttp.CombineHandlers( 193 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines/main/jobs/some-job"), 194 ghttp.RespondWithJSONEncoded(200, atc.Job{ 195 NextBuild: nil, 196 FinishedBuild: &atc.Build{ 197 ID: 3, 198 Name: "3", 199 Status: "failed", 200 JobName: "some-job", 201 }, 202 }), 203 ), 204 eventsHandler(), 205 ) 206 }) 207 208 It("watches the job's finished build", func() { 209 watch("--job", "main/some-job") 210 }) 211 212 It("watches the job's finished build URL", func() { 213 watch("--url", atcServer.URL()+"/teams/main/pipelines/main/jobs/some-job") 214 }) 215 }) 216 217 Context("with a specific build of the job", func() { 218 BeforeEach(func() { 219 atcServer.AppendHandlers( 220 ghttp.CombineHandlers( 221 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines/main/jobs/some-job/builds/3"), 222 ghttp.RespondWithJSONEncoded(200, atc.Build{ 223 ID: 3, 224 Name: "3", 225 Status: "failed", 226 JobName: "some-job", 227 }), 228 ), 229 eventsHandler(), 230 ) 231 }) 232 233 It("watches the given build", func() { 234 watch("--job", "main/some-job", "--build", "3") 235 }) 236 237 It("watches the given build URL", func() { 238 watch("--url", atcServer.URL()+"/teams/main/pipelines/main/jobs/some-job/builds/3") 239 }) 240 }) 241 }) 242 })