1package api_test
2
3import (
4	"errors"
5	"fmt"
6	"io/ioutil"
7	"net/http"
8	"time"
9
10	"github.com/concourse/concourse/atc"
11	"github.com/concourse/concourse/atc/db"
12	"github.com/concourse/concourse/atc/db/dbfakes"
13	. "github.com/concourse/concourse/atc/testhelpers"
14	. "github.com/onsi/ginkgo"
15	. "github.com/onsi/gomega"
16)
17
18var _ = Describe("Versions API", func() {
19	var fakePipeline *dbfakes.FakePipeline
20
21	BeforeEach(func() {
22		fakePipeline = new(dbfakes.FakePipeline)
23		dbTeamFactory.FindTeamReturns(dbTeam, true, nil)
24		dbTeam.PipelineReturns(fakePipeline, true, nil)
25	})
26
27	Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions", func() {
28		var response *http.Response
29		var queryParams string
30		var fakeResource *dbfakes.FakeResource
31
32		BeforeEach(func() {
33			queryParams = ""
34			fakeResource = new(dbfakes.FakeResource)
35		})
36
37		JustBeforeEach(func() {
38			var err error
39
40			request, err := http.NewRequest("GET", server.URL+"/api/v1/teams/a-team/pipelines/a-pipeline/resources/some-resource/versions"+queryParams, nil)
41			Expect(err).NotTo(HaveOccurred())
42
43			response, err = client.Do(request)
44			Expect(err).NotTo(HaveOccurred())
45		})
46
47		Context("when not authorized", func() {
48			BeforeEach(func() {
49				fakeAccess.IsAuthorizedReturns(false)
50			})
51
52			Context("and the pipeline is private", func() {
53				BeforeEach(func() {
54					fakePipeline.PublicReturns(false)
55				})
56
57				Context("user is not authenticated", func() {
58					BeforeEach(func() {
59						fakeAccess.IsAuthenticatedReturns(false)
60					})
61
62					It("returns 401", func() {
63						Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
64					})
65				})
66
67				Context("user is authenticated", func() {
68					BeforeEach(func() {
69						fakeAccess.IsAuthenticatedReturns(true)
70					})
71
72					It("returns 403", func() {
73						Expect(response.StatusCode).To(Equal(http.StatusForbidden))
74					})
75				})
76			})
77
78			Context("and the pipeline is public", func() {
79				BeforeEach(func() {
80					fakePipeline.PublicReturns(true)
81					fakePipeline.ResourceReturns(fakeResource, true, nil)
82
83					returnedVersions := []atc.ResourceVersion{
84						{
85							ID:      4,
86							Enabled: true,
87							Version: atc.Version{
88								"some": "version",
89							},
90							Metadata: []atc.MetadataField{
91								{
92									Name:  "some",
93									Value: "metadata",
94								},
95							},
96						},
97						{
98							ID:      2,
99							Enabled: false,
100							Version: atc.Version{
101								"some": "version",
102							},
103							Metadata: []atc.MetadataField{
104								{
105									Name:  "some",
106									Value: "metadata",
107								},
108							},
109						},
110					}
111
112					fakeResource.VersionsReturns(returnedVersions, db.Pagination{}, true, nil)
113				})
114
115				It("returns 200 OK", func() {
116					Expect(response.StatusCode).To(Equal(http.StatusOK))
117				})
118
119				It("returns content type application/json", func() {
120					expectedHeaderEntries := map[string]string{
121						"Content-Type": "application/json",
122					}
123					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
124				})
125
126				Context("when resource is public", func() {
127					BeforeEach(func() {
128						fakeResource.PublicReturns(true)
129					})
130
131					It("returns the json", func() {
132						body, err := ioutil.ReadAll(response.Body)
133						Expect(err).NotTo(HaveOccurred())
134
135						Expect(body).To(MatchJSON(`[
136					{
137						"id": 4,
138						"enabled": true,
139						"version": {"some":"version"},
140						"metadata": [
141							{
142								"name":"some",
143								"value":"metadata"
144							}
145						]
146					},
147					{
148						"id":2,
149						"enabled": false,
150						"version": {"some":"version"},
151						"metadata": [
152							{
153								"name":"some",
154								"value":"metadata"
155							}
156						]
157					}
158				]`))
159					})
160				})
161
162				Context("when resource is not public", func() {
163					Context("when the user is not authenticated", func() {
164						It("returns the json without version metadata", func() {
165							body, err := ioutil.ReadAll(response.Body)
166							Expect(err).NotTo(HaveOccurred())
167
168							Expect(body).To(MatchJSON(`[
169								{
170									"id": 4,
171									"enabled": true,
172									"version": {"some":"version"}
173								},
174								{
175									"id":2,
176									"enabled": false,
177									"version": {"some":"version"}
178								}
179							]`))
180						})
181					})
182
183					Context("when the user is authenticated", func() {
184						BeforeEach(func() {
185							fakeAccess.IsAuthenticatedReturns(true)
186						})
187
188						It("returns the json without version metadata", func() {
189							body, err := ioutil.ReadAll(response.Body)
190							Expect(err).NotTo(HaveOccurred())
191
192							Expect(body).To(MatchJSON(`[
193								{
194									"id": 4,
195									"enabled": true,
196									"version": {"some":"version"}
197								},
198								{
199									"id":2,
200									"enabled": false,
201									"version": {"some":"version"}
202								}
203							]`))
204						})
205					})
206				})
207			})
208		})
209
210		Context("when authorized", func() {
211			BeforeEach(func() {
212				fakeAccess.IsAuthenticatedReturns(true)
213				fakeAccess.IsAuthorizedReturns(true)
214			})
215
216			It("finds the resource", func() {
217				Expect(fakePipeline.ResourceCallCount()).To(Equal(1))
218				Expect(fakePipeline.ResourceArgsForCall(0)).To(Equal("some-resource"))
219			})
220
221			Context("when finding the resource succeeds", func() {
222				BeforeEach(func() {
223					fakePipeline.ResourceReturns(fakeResource, true, nil)
224				})
225
226				Context("when no params are passed", func() {
227					It("does not set defaults for since and until", func() {
228						Expect(fakeResource.VersionsCallCount()).To(Equal(1))
229
230						page, versionFilter := fakeResource.VersionsArgsForCall(0)
231						Expect(page).To(Equal(db.Page{
232							Limit: 100,
233						}))
234						Expect(versionFilter).To(Equal(atc.Version{}))
235					})
236				})
237
238				Context("when all the params are passed", func() {
239					BeforeEach(func() {
240						queryParams = "?from=5&to=7&limit=8&filter=ref:foo&filter=some-ref:blah"
241					})
242
243					It("passes them through", func() {
244						Expect(fakeResource.VersionsCallCount()).To(Equal(1))
245
246						page, versionFilter := fakeResource.VersionsArgsForCall(0)
247						Expect(page).To(Equal(db.Page{
248							From:  db.NewIntPtr(5),
249							To:    db.NewIntPtr(7),
250							Limit: 8,
251						}))
252						Expect(versionFilter).To(Equal(atc.Version{
253							"ref":      "foo",
254							"some-ref": "blah",
255						}))
256					})
257				})
258
259				Context("when params includes version filter has special char", func() {
260					Context("space char", func() {
261						BeforeEach(func() {
262							queryParams = "?filter=some%20ref:some%20value"
263						})
264
265						It("passes them through", func() {
266							Expect(fakeResource.VersionsCallCount()).To(Equal(1))
267
268							_, versionFilter := fakeResource.VersionsArgsForCall(0)
269							Expect(versionFilter).To(Equal(atc.Version{
270								"some ref": "some value",
271							}))
272						})
273					})
274
275					Context("% char", func() {
276						BeforeEach(func() {
277							queryParams = "?filter=ref:some%25value"
278						})
279
280						It("passes them through", func() {
281							Expect(fakeResource.VersionsCallCount()).To(Equal(1))
282
283							_, versionFilter := fakeResource.VersionsArgsForCall(0)
284							Expect(versionFilter).To(Equal(atc.Version{
285								"ref": "some%value",
286							}))
287						})
288					})
289
290					Context(": char", func() {
291						BeforeEach(func() {
292							queryParams = "?filter=key%3Awith%3Acolon:abcdef"
293						})
294
295						It("passes them through by splitting on first colon", func() {
296							Expect(fakeResource.VersionsCallCount()).To(Equal(1))
297
298							_, versionFilter := fakeResource.VersionsArgsForCall(0)
299							Expect(versionFilter).To(Equal(atc.Version{
300								"key": "with:colon:abcdef",
301							}))
302						})
303					})
304
305					Context("if there is no : ", func() {
306						BeforeEach(func() {
307							queryParams = "?filter=abcdef"
308						})
309
310						It("set no filter when fetching versions", func() {
311							Expect(fakeResource.VersionsCallCount()).To(Equal(1))
312
313							_, versionFilter := fakeResource.VersionsArgsForCall(0)
314							Expect(versionFilter).To(BeEmpty())
315						})
316					})
317				})
318
319				Context("when getting the versions succeeds", func() {
320					var returnedVersions []atc.ResourceVersion
321
322					BeforeEach(func() {
323						queryParams = "?since=5&limit=2"
324						returnedVersions = []atc.ResourceVersion{
325							{
326								ID:      4,
327								Enabled: true,
328								Version: atc.Version{
329									"some": "version",
330									"ref":  "foo",
331								},
332								Metadata: []atc.MetadataField{
333									{
334										Name:  "some",
335										Value: "metadata",
336									},
337								},
338							},
339							{
340								ID:      2,
341								Enabled: false,
342								Version: atc.Version{
343									"some": "version",
344									"ref":  "blah",
345								},
346								Metadata: []atc.MetadataField{
347									{
348										Name:  "some",
349										Value: "metadata",
350									},
351								},
352							},
353						}
354
355						fakeResource.VersionsReturns(returnedVersions, db.Pagination{}, true, nil)
356					})
357
358					It("returns 200 OK", func() {
359						Expect(response.StatusCode).To(Equal(http.StatusOK))
360					})
361
362					It("returns content type application/json", func() {
363						expectedHeaderEntries := map[string]string{
364							"Content-Type": "application/json",
365						}
366						Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
367					})
368
369					It("returns the json", func() {
370						body, err := ioutil.ReadAll(response.Body)
371						Expect(err).NotTo(HaveOccurred())
372
373						Expect(body).To(MatchJSON(`[
374					{
375						"id": 4,
376						"enabled": true,
377						"version": {"some":"version", "ref":"foo"},
378						"metadata": [
379							{
380								"name":"some",
381								"value":"metadata"
382							}
383						]
384					},
385					{
386						"id":2,
387						"enabled": false,
388						"version": {"some":"version", "ref":"blah"},
389						"metadata": [
390							{
391								"name":"some",
392								"value":"metadata"
393							}
394						]
395					}
396				]`))
397					})
398
399					Context("when next/previous pages are available", func() {
400						BeforeEach(func() {
401							fakePipeline.NameReturns("some-pipeline")
402							fakeResource.VersionsReturns(returnedVersions, db.Pagination{
403								Newer: &db.Page{From: db.NewIntPtr(4), Limit: 2},
404								Older: &db.Page{To: db.NewIntPtr(2), Limit: 2},
405							}, true, nil)
406						})
407
408						It("returns Link headers per rfc5988", func() {
409							Expect(response.Header["Link"]).To(ConsistOf([]string{
410								fmt.Sprintf(`<%s/api/v1/teams/a-team/pipelines/some-pipeline/resources/some-resource/versions?from=4&limit=2>; rel="previous"`, externalURL),
411								fmt.Sprintf(`<%s/api/v1/teams/a-team/pipelines/some-pipeline/resources/some-resource/versions?to=2&limit=2>; rel="next"`, externalURL),
412							}))
413						})
414					})
415				})
416
417				Context("when the versions can't be found", func() {
418					BeforeEach(func() {
419						fakeResource.VersionsReturns(nil, db.Pagination{}, false, nil)
420					})
421
422					It("returns 404 not found", func() {
423						Expect(response.StatusCode).To(Equal(http.StatusNotFound))
424					})
425				})
426
427				Context("when getting the versions fails", func() {
428					BeforeEach(func() {
429						fakeResource.VersionsReturns(nil, db.Pagination{}, false, errors.New("oh no!"))
430					})
431
432					It("returns 500 Internal Server Error", func() {
433						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
434					})
435				})
436			})
437
438			Context("when finding the resource fails", func() {
439				BeforeEach(func() {
440					fakePipeline.ResourceReturns(nil, false, errors.New("oh no!"))
441				})
442
443				It("returns 500 Internal Server Error", func() {
444					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
445				})
446			})
447
448			Context("when the resource is not found", func() {
449				BeforeEach(func() {
450					fakePipeline.ResourceReturns(nil, false, nil)
451				})
452
453				It("returns 404 not found", func() {
454					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
455				})
456			})
457		})
458	})
459
460	Describe("PUT /api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/enable", func() {
461		var response *http.Response
462		var fakeResource *dbfakes.FakeResource
463
464		JustBeforeEach(func() {
465			var err error
466
467			request, err := http.NewRequest("PUT", server.URL+"/api/v1/teams/a-team/pipelines/a-pipeline/resources/resource-name/versions/42/enable", nil)
468			Expect(err).NotTo(HaveOccurred())
469
470			response, err = client.Do(request)
471			Expect(err).NotTo(HaveOccurred())
472		})
473
474		Context("when authenticated", func() {
475			BeforeEach(func() {
476				fakeAccess.IsAuthenticatedReturns(true)
477			})
478
479			Context("when authorized", func() {
480				BeforeEach(func() {
481					fakeAccess.IsAuthorizedReturns(true)
482				})
483
484				It("tries to find the resource", func() {
485					resourceName := fakePipeline.ResourceArgsForCall(0)
486					Expect(resourceName).To(Equal("resource-name"))
487				})
488
489				Context("when finding the resource succeeds", func() {
490					BeforeEach(func() {
491						fakeResource = new(dbfakes.FakeResource)
492						fakeResource.IDReturns(1)
493						fakePipeline.ResourceReturns(fakeResource, true, nil)
494					})
495
496					It("tries to enable the right resource config version", func() {
497						resourceConfigVersionID := fakeResource.EnableVersionArgsForCall(0)
498						Expect(resourceConfigVersionID).To(Equal(42))
499					})
500
501					Context("when enabling the resource succeeds", func() {
502						BeforeEach(func() {
503							fakeResource.EnableVersionReturns(nil)
504						})
505
506						It("returns 200", func() {
507							Expect(response.StatusCode).To(Equal(http.StatusOK))
508						})
509					})
510
511					Context("when enabling the resource fails", func() {
512						BeforeEach(func() {
513							fakeResource.EnableVersionReturns(errors.New("welp"))
514						})
515
516						It("returns 500", func() {
517							Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
518						})
519					})
520				})
521
522				Context("when it fails to find the resource", func() {
523					BeforeEach(func() {
524						fakePipeline.ResourceReturns(nil, false, errors.New("welp"))
525					})
526
527					It("returns Internal Server Error", func() {
528						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
529					})
530				})
531
532				Context("when the resource is not found", func() {
533					BeforeEach(func() {
534						fakePipeline.ResourceReturns(nil, false, nil)
535					})
536
537					It("returns not found", func() {
538						Expect(response.StatusCode).To(Equal(http.StatusNotFound))
539					})
540				})
541			})
542
543			Context("when not authorized", func() {
544				BeforeEach(func() {
545					fakeAccess.IsAuthorizedReturns(false)
546				})
547				It("returns Forbidden", func() {
548					Expect(response.StatusCode).To(Equal(http.StatusForbidden))
549				})
550			})
551		})
552
553		Context("when not authenticated", func() {
554			BeforeEach(func() {
555				fakeAccess.IsAuthenticatedReturns(false)
556			})
557
558			It("returns Unauthorized", func() {
559				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
560			})
561		})
562	})
563
564	Describe("PUT /api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/disable", func() {
565		var response *http.Response
566		var fakeResource *dbfakes.FakeResource
567
568		JustBeforeEach(func() {
569			var err error
570
571			request, err := http.NewRequest("PUT", server.URL+"/api/v1/teams/a-team/pipelines/a-pipeline/resources/resource-name/versions/42/disable", nil)
572			Expect(err).NotTo(HaveOccurred())
573
574			response, err = client.Do(request)
575			Expect(err).NotTo(HaveOccurred())
576		})
577
578		Context("when authenticated ", func() {
579			BeforeEach(func() {
580				fakeAccess.IsAuthenticatedReturns(true)
581			})
582
583			Context("when authorized", func() {
584				BeforeEach(func() {
585					fakeAccess.IsAuthorizedReturns(true)
586				})
587
588				It("tries to find the resource", func() {
589					resourceName := fakePipeline.ResourceArgsForCall(0)
590					Expect(resourceName).To(Equal("resource-name"))
591				})
592
593				Context("when finding the resource succeeds", func() {
594					BeforeEach(func() {
595						fakeResource = new(dbfakes.FakeResource)
596						fakeResource.IDReturns(1)
597						fakePipeline.ResourceReturns(fakeResource, true, nil)
598					})
599
600					It("tries to disable the right resource config version", func() {
601						resourceConfigVersionID := fakeResource.DisableVersionArgsForCall(0)
602						Expect(resourceConfigVersionID).To(Equal(42))
603					})
604
605					Context("when disabling the resource version succeeds", func() {
606						BeforeEach(func() {
607							fakeResource.DisableVersionReturns(nil)
608						})
609
610						It("returns 200", func() {
611							Expect(response.StatusCode).To(Equal(http.StatusOK))
612						})
613					})
614
615					Context("when disabling the resource fails", func() {
616						BeforeEach(func() {
617							fakeResource.DisableVersionReturns(errors.New("welp"))
618						})
619
620						It("returns 500", func() {
621							Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
622						})
623					})
624				})
625
626				Context("when it fails to find the resource", func() {
627					BeforeEach(func() {
628						fakePipeline.ResourceReturns(nil, false, errors.New("welp"))
629					})
630
631					It("returns Internal Server Error", func() {
632						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
633					})
634				})
635
636				Context("when the resource is not found", func() {
637					BeforeEach(func() {
638						fakePipeline.ResourceReturns(nil, false, nil)
639					})
640
641					It("returns not found", func() {
642						Expect(response.StatusCode).To(Equal(http.StatusNotFound))
643					})
644				})
645			})
646			Context("when not authorized", func() {
647				BeforeEach(func() {
648					fakeAccess.IsAuthorizedReturns(false)
649				})
650
651				It("returns Forbidden", func() {
652					Expect(response.StatusCode).To(Equal(http.StatusForbidden))
653				})
654			})
655		})
656		Context("when not authenticated", func() {
657			BeforeEach(func() {
658				fakeAccess.IsAuthenticatedReturns(false)
659			})
660
661			It("returns Unauthorized", func() {
662				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
663			})
664		})
665	})
666
667	Describe("PUT /api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/pin", func() {
668		var response *http.Response
669		var fakeResource *dbfakes.FakeResource
670
671		JustBeforeEach(func() {
672			var err error
673
674			request, err := http.NewRequest("PUT", server.URL+"/api/v1/teams/a-team/pipelines/a-pipeline/resources/resource-name/versions/42/pin", nil)
675			Expect(err).NotTo(HaveOccurred())
676
677			response, err = client.Do(request)
678			Expect(err).NotTo(HaveOccurred())
679		})
680
681		Context("when authenticated", func() {
682			BeforeEach(func() {
683				fakeAccess.IsAuthenticatedReturns(true)
684			})
685
686			Context("when authorized", func() {
687				BeforeEach(func() {
688					fakeAccess.IsAuthorizedReturns(true)
689				})
690
691				It("tries to find the resource", func() {
692					resourceName := fakePipeline.ResourceArgsForCall(0)
693					Expect(resourceName).To(Equal("resource-name"))
694				})
695
696				Context("when finding the resource succeeds", func() {
697					BeforeEach(func() {
698						fakeResource = new(dbfakes.FakeResource)
699						fakeResource.IDReturns(1)
700						fakePipeline.ResourceReturns(fakeResource, true, nil)
701					})
702
703					It("tries to pin the right resource config version", func() {
704						resourceConfigVersionID := fakeResource.PinVersionArgsForCall(0)
705						Expect(resourceConfigVersionID).To(Equal(42))
706					})
707
708					Context("when pinning the resource succeeds", func() {
709						BeforeEach(func() {
710							fakeResource.PinVersionReturns(true, nil)
711						})
712
713						It("returns 200", func() {
714							Expect(response.StatusCode).To(Equal(http.StatusOK))
715						})
716					})
717
718					Context("when pinning the resource fails by resource not exist", func() {
719						BeforeEach(func() {
720							fakeResource.PinVersionReturns(false, nil)
721						})
722
723						It("returns 404", func() {
724							Expect(response.StatusCode).To(Equal(http.StatusNotFound))
725						})
726					})
727
728					Context("when pinning the resource fails by error", func() {
729						BeforeEach(func() {
730							fakeResource.PinVersionReturns(false, errors.New("welp"))
731						})
732
733						It("returns 500", func() {
734							Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
735						})
736					})
737				})
738
739				Context("when it fails to find the resource", func() {
740					BeforeEach(func() {
741						fakePipeline.ResourceReturns(nil, false, errors.New("welp"))
742					})
743
744					It("returns Internal Server Error", func() {
745						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
746					})
747				})
748
749				Context("when the resource is not found", func() {
750					BeforeEach(func() {
751						fakePipeline.ResourceReturns(nil, false, nil)
752					})
753
754					It("returns not found", func() {
755						Expect(response.StatusCode).To(Equal(http.StatusNotFound))
756					})
757				})
758			})
759
760			Context("when not authorized", func() {
761				BeforeEach(func() {
762					fakeAccess.IsAuthorizedReturns(false)
763				})
764				It("returns Forbidden", func() {
765					Expect(response.StatusCode).To(Equal(http.StatusForbidden))
766				})
767			})
768		})
769
770		Context("when not authenticated", func() {
771			BeforeEach(func() {
772				fakeAccess.IsAuthenticatedReturns(false)
773			})
774
775			It("returns Unauthorized", func() {
776				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
777			})
778		})
779	})
780
781	Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/input_to", func() {
782		var response *http.Response
783		var stringVersionID string
784		var fakeResource *dbfakes.FakeResource
785
786		JustBeforeEach(func() {
787			var err error
788
789			request, err := http.NewRequest("GET", server.URL+"/api/v1/teams/a-team/pipelines/a-pipeline/resources/some-resource/versions/"+stringVersionID+"/input_to", nil)
790			Expect(err).NotTo(HaveOccurred())
791
792			response, err = client.Do(request)
793			Expect(err).NotTo(HaveOccurred())
794		})
795
796		BeforeEach(func() {
797			fakeResource = new(dbfakes.FakeResource)
798			fakeResource.IDReturns(1)
799			stringVersionID = "123"
800		})
801
802		Context("when not authorized", func() {
803			BeforeEach(func() {
804				fakeAccess.IsAuthorizedReturns(false)
805			})
806
807			Context("and the pipeline is private", func() {
808				BeforeEach(func() {
809					fakePipeline.PublicReturns(false)
810				})
811
812				Context("when authenticated", func() {
813					BeforeEach(func() {
814						fakeAccess.IsAuthenticatedReturns(true)
815					})
816
817					It("returns 403", func() {
818						Expect(response.StatusCode).To(Equal(http.StatusForbidden))
819					})
820				})
821
822				Context("when not authenticated", func() {
823					BeforeEach(func() {
824						fakeAccess.IsAuthenticatedReturns(false)
825					})
826
827					It("returns 401", func() {
828						Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
829					})
830				})
831			})
832
833			Context("and the pipeline is public", func() {
834				BeforeEach(func() {
835					fakePipeline.PublicReturns(true)
836					fakePipeline.ResourceReturns(fakeResource, true, nil)
837				})
838
839				It("returns 200 OK", func() {
840					Expect(response.StatusCode).To(Equal(http.StatusOK))
841				})
842			})
843		})
844
845		Context("when authorized", func() {
846			BeforeEach(func() {
847				fakeAccess.IsAuthenticatedReturns(true)
848				fakeAccess.IsAuthorizedReturns(true)
849			})
850
851			Context("when not finding the resource", func() {
852				BeforeEach(func() {
853					fakePipeline.ResourceReturns(nil, false, nil)
854				})
855
856				It("returns 404", func() {
857					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
858				})
859			})
860
861			Context("when failing to retrieve the resource", func() {
862				BeforeEach(func() {
863					fakePipeline.ResourceReturns(nil, false, errors.New("banana"))
864				})
865
866				It("returns 500", func() {
867					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
868				})
869			})
870
871			It("looks for the resource", func() {
872				Expect(fakePipeline.ResourceCallCount()).To(Equal(1))
873				Expect(fakePipeline.ResourceArgsForCall(0)).To(Equal("some-resource"))
874			})
875
876			Context("when resource retrieval succeeds", func() {
877				BeforeEach(func() {
878					fakePipeline.ResourceReturns(fakeResource, true, nil)
879				})
880
881				It("looks up the given version ID", func() {
882					Expect(fakePipeline.GetBuildsWithVersionAsInputCallCount()).To(Equal(1))
883					resourceID, versionID := fakePipeline.GetBuildsWithVersionAsInputArgsForCall(0)
884					Expect(resourceID).To(Equal(1))
885					Expect(versionID).To(Equal(123))
886				})
887
888				Context("when getting the builds succeeds", func() {
889					BeforeEach(func() {
890						build1 := new(dbfakes.FakeBuild)
891						build1.IDReturns(1024)
892						build1.NameReturns("5")
893						build1.JobNameReturns("some-job")
894						build1.PipelineNameReturns("a-pipeline")
895						build1.TeamNameReturns("a-team")
896						build1.StatusReturns(db.BuildStatusSucceeded)
897						build1.StartTimeReturns(time.Unix(1, 0))
898						build1.EndTimeReturns(time.Unix(100, 0))
899
900						build2 := new(dbfakes.FakeBuild)
901						build2.IDReturns(1025)
902						build2.NameReturns("6")
903						build2.JobNameReturns("some-job")
904						build2.PipelineNameReturns("a-pipeline")
905						build2.TeamNameReturns("a-team")
906						build2.StatusReturns(db.BuildStatusSucceeded)
907						build2.StartTimeReturns(time.Unix(200, 0))
908						build2.EndTimeReturns(time.Unix(300, 0))
909
910						fakePipeline.GetBuildsWithVersionAsInputReturns([]db.Build{build1, build2}, nil)
911					})
912
913					It("returns 200 OK", func() {
914						Expect(response.StatusCode).To(Equal(http.StatusOK))
915					})
916
917					It("returns content type application/json", func() {
918						expectedHeaderEntries := map[string]string{
919							"Content-Type": "application/json",
920						}
921						Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
922					})
923
924					It("returns the json", func() {
925						body, err := ioutil.ReadAll(response.Body)
926						Expect(err).NotTo(HaveOccurred())
927
928						Expect(body).To(MatchJSON(`[
929					{
930						"id": 1024,
931						"team_name": "a-team",
932						"name": "5",
933						"status": "succeeded",
934						"job_name": "some-job",
935						"api_url": "/api/v1/builds/1024",
936						"pipeline_name": "a-pipeline",
937						"start_time": 1,
938						"end_time": 100
939					},
940					{
941						"id": 1025,
942						"name": "6",
943						"team_name": "a-team",
944						"status": "succeeded",
945						"job_name": "some-job",
946						"api_url": "/api/v1/builds/1025",
947						"pipeline_name": "a-pipeline",
948						"start_time": 200,
949						"end_time": 300
950					}
951				]`))
952					})
953				})
954
955				Context("when the version ID is invalid", func() {
956					BeforeEach(func() {
957						stringVersionID = "hello"
958					})
959
960					It("returns an empty list", func() {
961						body, err := ioutil.ReadAll(response.Body)
962						Expect(err).NotTo(HaveOccurred())
963
964						Expect(body).To(MatchJSON(`[]`))
965					})
966				})
967
968				Context("when the call to get builds returns an error", func() {
969					BeforeEach(func() {
970						fakePipeline.GetBuildsWithVersionAsInputReturns(nil, errors.New("NOPE"))
971					})
972
973					It("returns a 500 internal server error", func() {
974						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
975					})
976				})
977			})
978		})
979	})
980
981	Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/output_of", func() {
982		var response *http.Response
983		var stringVersionID string
984		var fakeResource *dbfakes.FakeResource
985
986		JustBeforeEach(func() {
987			var err error
988
989			request, err := http.NewRequest("GET", server.URL+"/api/v1/teams/a-team/pipelines/a-pipeline/resources/some-resource/versions/"+stringVersionID+"/output_of", nil)
990			Expect(err).NotTo(HaveOccurred())
991
992			response, err = client.Do(request)
993			Expect(err).NotTo(HaveOccurred())
994		})
995
996		BeforeEach(func() {
997			stringVersionID = "123"
998			fakeResource = new(dbfakes.FakeResource)
999			fakeResource.IDReturns(1)
1000		})
1001
1002		Context("when not authorized", func() {
1003			BeforeEach(func() {
1004				fakeAccess.IsAuthorizedReturns(false)
1005			})
1006
1007			Context("and the pipeline is private", func() {
1008				BeforeEach(func() {
1009					fakePipeline.PublicReturns(false)
1010				})
1011
1012				Context("when authenticated", func() {
1013					BeforeEach(func() {
1014						fakeAccess.IsAuthenticatedReturns(true)
1015					})
1016
1017					It("returns 403", func() {
1018						Expect(response.StatusCode).To(Equal(http.StatusForbidden))
1019					})
1020				})
1021
1022				Context("when not authenticated", func() {
1023					BeforeEach(func() {
1024						fakeAccess.IsAuthenticatedReturns(false)
1025					})
1026
1027					It("returns 401", func() {
1028						Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
1029					})
1030				})
1031			})
1032
1033			Context("and the pipeline is public", func() {
1034				BeforeEach(func() {
1035					fakePipeline.PublicReturns(true)
1036					fakePipeline.ResourceReturns(fakeResource, true, nil)
1037				})
1038
1039				It("returns 200 OK", func() {
1040					Expect(response.StatusCode).To(Equal(http.StatusOK))
1041				})
1042			})
1043		})
1044
1045		Context("when authorized", func() {
1046			BeforeEach(func() {
1047				fakeAccess.IsAuthenticatedReturns(true)
1048				fakeAccess.IsAuthorizedReturns(true)
1049			})
1050
1051			Context("when not finding the resource", func() {
1052				BeforeEach(func() {
1053					fakePipeline.ResourceReturns(nil, false, nil)
1054				})
1055
1056				It("returns 404", func() {
1057					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
1058				})
1059			})
1060
1061			Context("when failing to retrieve the resource", func() {
1062				BeforeEach(func() {
1063					fakePipeline.ResourceReturns(nil, false, errors.New("banana"))
1064				})
1065
1066				It("returns 500", func() {
1067					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
1068				})
1069			})
1070
1071			It("looks for the resource", func() {
1072				Expect(fakePipeline.ResourceCallCount()).To(Equal(1))
1073				Expect(fakePipeline.ResourceArgsForCall(0)).To(Equal("some-resource"))
1074			})
1075
1076			Context("when resource retrieval succeeds", func() {
1077				BeforeEach(func() {
1078					fakePipeline.ResourceReturns(fakeResource, true, nil)
1079				})
1080
1081				It("looks up the given version ID", func() {
1082					Expect(fakePipeline.GetBuildsWithVersionAsOutputCallCount()).To(Equal(1))
1083					resourceID, versionID := fakePipeline.GetBuildsWithVersionAsOutputArgsForCall(0)
1084					Expect(resourceID).To(Equal(1))
1085					Expect(versionID).To(Equal(123))
1086				})
1087
1088				Context("when getting the builds succeeds", func() {
1089					BeforeEach(func() {
1090						build1 := new(dbfakes.FakeBuild)
1091						build1.IDReturns(1024)
1092						build1.NameReturns("5")
1093						build1.JobNameReturns("some-job")
1094						build1.PipelineNameReturns("a-pipeline")
1095						build1.TeamNameReturns("a-team")
1096						build1.StatusReturns(db.BuildStatusSucceeded)
1097						build1.StartTimeReturns(time.Unix(1, 0))
1098						build1.EndTimeReturns(time.Unix(100, 0))
1099
1100						build2 := new(dbfakes.FakeBuild)
1101						build2.IDReturns(1025)
1102						build2.NameReturns("6")
1103						build2.JobNameReturns("some-job")
1104						build2.PipelineNameReturns("a-pipeline")
1105						build2.TeamNameReturns("a-team")
1106						build2.StatusReturns(db.BuildStatusSucceeded)
1107						build2.StartTimeReturns(time.Unix(200, 0))
1108						build2.EndTimeReturns(time.Unix(300, 0))
1109
1110						fakePipeline.GetBuildsWithVersionAsOutputReturns([]db.Build{build1, build2}, nil)
1111					})
1112
1113					It("returns 200 OK", func() {
1114						Expect(response.StatusCode).To(Equal(http.StatusOK))
1115					})
1116
1117					It("returns content type application/json", func() {
1118						expectedHeaderEntries := map[string]string{
1119							"Content-Type": "application/json",
1120						}
1121						Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
1122					})
1123
1124					It("returns the json", func() {
1125						body, err := ioutil.ReadAll(response.Body)
1126						Expect(err).NotTo(HaveOccurred())
1127
1128						Expect(body).To(MatchJSON(`[
1129					{
1130						"id": 1024,
1131						"name": "5",
1132						"status": "succeeded",
1133						"job_name": "some-job",
1134						"api_url": "/api/v1/builds/1024",
1135						"pipeline_name": "a-pipeline",
1136						"team_name": "a-team",
1137						"start_time": 1,
1138						"end_time": 100
1139					},
1140					{
1141						"id": 1025,
1142						"name": "6",
1143						"status": "succeeded",
1144						"job_name": "some-job",
1145						"api_url": "/api/v1/builds/1025",
1146						"pipeline_name": "a-pipeline",
1147						"team_name": "a-team",
1148						"start_time": 200,
1149						"end_time": 300
1150					}
1151				]`))
1152					})
1153				})
1154
1155				Context("when the version ID is invalid", func() {
1156					BeforeEach(func() {
1157						stringVersionID = "hello"
1158					})
1159
1160					It("returns an empty list", func() {
1161						body, err := ioutil.ReadAll(response.Body)
1162						Expect(err).NotTo(HaveOccurred())
1163
1164						Expect(body).To(MatchJSON(`[]`))
1165					})
1166				})
1167
1168				Context("when the call to get builds returns an error", func() {
1169					BeforeEach(func() {
1170						fakePipeline.GetBuildsWithVersionAsOutputReturns(nil, errors.New("NOPE"))
1171					})
1172
1173					It("returns a 500 internal server error", func() {
1174						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
1175					})
1176				})
1177			})
1178		})
1179	})
1180})
1181