github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/teams_test.go (about)

     1  package api_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"time"
    11  
    12  	"github.com/pf-qiu/concourse/v6/atc"
    13  	"github.com/pf-qiu/concourse/v6/atc/db"
    14  	"github.com/pf-qiu/concourse/v6/atc/db/dbfakes"
    15  	. "github.com/pf-qiu/concourse/v6/atc/testhelpers"
    16  	. "github.com/onsi/ginkgo"
    17  	. "github.com/onsi/gomega"
    18  )
    19  
    20  func jsonEncode(object interface{}) *bytes.Buffer {
    21  	reqPayload, err := json.Marshal(object)
    22  	Expect(err).NotTo(HaveOccurred())
    23  
    24  	return bytes.NewBuffer(reqPayload)
    25  }
    26  
    27  var _ = Describe("Teams API", func() {
    28  	var (
    29  		fakeTeam *dbfakes.FakeTeam
    30  	)
    31  
    32  	BeforeEach(func() {
    33  		fakeTeam = new(dbfakes.FakeTeam)
    34  	})
    35  
    36  	Describe("GET /api/v1/teams", func() {
    37  		var (
    38  			response      *http.Response
    39  			fakeTeamOne   *dbfakes.FakeTeam
    40  			fakeTeamTwo   *dbfakes.FakeTeam
    41  			fakeTeamThree *dbfakes.FakeTeam
    42  			teamNames     []string
    43  		)
    44  
    45  		JustBeforeEach(func() {
    46  			path := fmt.Sprintf("%s/api/v1/teams", server.URL)
    47  
    48  			request, err := http.NewRequest("GET", path, nil)
    49  			Expect(err).NotTo(HaveOccurred())
    50  
    51  			response, err = client.Do(request)
    52  			Expect(err).NotTo(HaveOccurred())
    53  		})
    54  
    55  		BeforeEach(func() {
    56  			fakeTeamOne = new(dbfakes.FakeTeam)
    57  			fakeTeamTwo = new(dbfakes.FakeTeam)
    58  			fakeTeamThree = new(dbfakes.FakeTeam)
    59  
    60  			teamNames = []string{"avengers", "aliens", "predators"}
    61  
    62  			fakeTeamOne.IDReturns(5)
    63  			fakeTeamOne.NameReturns(teamNames[0])
    64  			fakeTeamOne.AuthReturns(atc.TeamAuth{
    65  				"owner": map[string][]string{
    66  					"groups": []string{}, "users": []string{"local:username"},
    67  				},
    68  			})
    69  
    70  			fakeTeamTwo.IDReturns(9)
    71  			fakeTeamTwo.NameReturns(teamNames[1])
    72  			fakeTeamTwo.AuthReturns(atc.TeamAuth{
    73  				"owner": map[string][]string{
    74  					"groups": []string{}, "users": []string{"local:username"},
    75  				},
    76  			})
    77  
    78  			fakeTeamThree.IDReturns(22)
    79  			fakeTeamThree.NameReturns(teamNames[2])
    80  			fakeTeamThree.AuthReturns(atc.TeamAuth{
    81  				"owner": map[string][]string{
    82  					"groups": []string{}, "users": []string{"local:username"},
    83  				},
    84  			})
    85  
    86  			fakeAccess.IsAuthorizedReturnsOnCall(0, true)
    87  			fakeAccess.IsAuthorizedReturnsOnCall(1, false)
    88  			fakeAccess.IsAuthorizedReturnsOnCall(2, true)
    89  		})
    90  
    91  		Context("when the database call succeeds", func() {
    92  			BeforeEach(func() {
    93  				dbTeamFactory.GetTeamsReturns([]db.Team{fakeTeamOne, fakeTeamTwo, fakeTeamThree}, nil)
    94  			})
    95  
    96  			It("should return the teams the user is authorized for", func() {
    97  				body, err := ioutil.ReadAll(response.Body)
    98  				Expect(err).NotTo(HaveOccurred())
    99  
   100  				Expect(body).To(MatchJSON(`[
   101   					{
   102   						"id": 5,
   103   						"name": "avengers",
   104  						"auth": { "owner":{"users":["local:username"],"groups":[]}}
   105   					},
   106   					{
   107   						"id": 22,
   108   						"name": "predators",
   109  						"auth": { "owner":{"users":["local:username"],"groups":[]}}
   110   					}
   111   				]`))
   112  			})
   113  		})
   114  
   115  		Context("when the database call returns an error", func() {
   116  			var disaster error
   117  
   118  			BeforeEach(func() {
   119  				disaster = errors.New("some error")
   120  				dbTeamFactory.GetTeamsReturns(nil, disaster)
   121  			})
   122  
   123  			It("returns 500 Internal Server Error", func() {
   124  				Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   125  			})
   126  		})
   127  	})
   128  
   129  	Describe("GET /api/v1/teams/:team_name", func() {
   130  		var response *http.Response
   131  		var fakeTeam *dbfakes.FakeTeam
   132  
   133  		BeforeEach(func() {
   134  			fakeTeam = new(dbfakes.FakeTeam)
   135  			fakeTeam.IDReturns(1)
   136  			fakeTeam.NameReturns("a-team")
   137  			fakeTeam.AuthReturns(atc.TeamAuth{
   138  				"owner": map[string][]string{
   139  					"groups": {}, "users": {"local:username"},
   140  				},
   141  			})
   142  		})
   143  
   144  		JustBeforeEach(func() {
   145  			req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/teams/a-team", server.URL), nil)
   146  			Expect(err).NotTo(HaveOccurred())
   147  
   148  			req.Header.Set("Content-Type", "application/json")
   149  
   150  			response, err = client.Do(req)
   151  			Expect(err).NotTo(HaveOccurred())
   152  		})
   153  
   154  		Context("when not authenticated and not admin", func() {
   155  			BeforeEach(func() {
   156  				fakeAccess.IsAuthorizedReturns(false)
   157  				fakeAccess.IsAdminReturns(false)
   158  			})
   159  
   160  			It("returns 401", func() {
   161  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   162  			})
   163  		})
   164  
   165  		Context("when not authenticated to specified team, but have admin authority", func() {
   166  			BeforeEach(func() {
   167  				dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   168  				fakeAccess.IsAuthenticatedReturns(true)
   169  				fakeAccess.IsAdminReturns(true)
   170  				fakeAccess.IsAuthorizedReturns(false)
   171  			})
   172  
   173  			It("returns 200 ok", func() {
   174  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   175  			})
   176  
   177  			It("returns application/json", func() {
   178  				expectedHeaderEntries := map[string]string{
   179  					"Content-Type": "application/json",
   180  				}
   181  				Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   182  			})
   183  
   184  			It("returns a team JSON", func() {
   185  				body, err := ioutil.ReadAll(response.Body)
   186  				Expect(err).NotTo(HaveOccurred())
   187  
   188  				Expect(body).To(MatchJSON(`
   189  				{
   190  					"id": 1,
   191  					"name": "a-team",
   192  					"auth": {
   193  						"owner": {
   194  							"groups": [],
   195  							"users": [
   196  								"local:username"
   197  							]
   198  						}
   199  					}
   200  				}`))
   201  			})
   202  		})
   203  
   204  		Context("when authenticated to specified team", func() {
   205  			BeforeEach(func() {
   206  				dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   207  				fakeAccess.IsAuthenticatedReturns(true)
   208  				fakeAccess.IsAdminReturns(false)
   209  				fakeAccess.IsAuthorizedReturns(true)
   210  			})
   211  
   212  			It("returns 200 ok", func() {
   213  				Expect(response.StatusCode).To(Equal(http.StatusOK))
   214  			})
   215  
   216  			It("returns application/json", func() {
   217  				expectedHeaderEntries := map[string]string{
   218  					"Content-Type": "application/json",
   219  				}
   220  				Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   221  			})
   222  
   223  			It("returns a team JSON", func() {
   224  				body, err := ioutil.ReadAll(response.Body)
   225  				Expect(err).NotTo(HaveOccurred())
   226  
   227  				Expect(body).To(MatchJSON(`
   228  				{
   229  					"id": 1,
   230  					"name": "a-team",
   231  					"auth": {
   232  						"owner": {
   233  							"groups": [],
   234  							"users": [
   235  								"local:username"
   236  							]
   237  						}
   238  					}
   239  				}`))
   240  			})
   241  		})
   242  
   243  		Context("when authenticated as another team", func() {
   244  			BeforeEach(func() {
   245  				dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   246  				fakeAccess.IsAuthenticatedReturns(true)
   247  			})
   248  
   249  			It("return 403", func() {
   250  				Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   251  			})
   252  		})
   253  	})
   254  	Describe("PUT /api/v1/teams/:team_name", func() {
   255  		var (
   256  			response *http.Response
   257  			teamAuth atc.TeamAuth
   258  			atcTeam  atc.Team
   259  			path     string
   260  		)
   261  
   262  		BeforeEach(func() {
   263  			fakeTeam.IDReturns(5)
   264  			fakeTeam.NameReturns("some-team")
   265  
   266  			teamAuth = atc.TeamAuth{
   267  				"owner": map[string][]string{
   268  					"groups": {}, "users": {"local:username"},
   269  				},
   270  			}
   271  			atcTeam = atc.Team{Auth: teamAuth}
   272  			path = fmt.Sprintf("%s/api/v1/teams/some-team", server.URL)
   273  		})
   274  
   275  		JustBeforeEach(func() {
   276  			var err error
   277  			request, err := http.NewRequest("PUT", path, jsonEncode(atcTeam))
   278  			Expect(err).NotTo(HaveOccurred())
   279  
   280  			response, err = client.Do(request)
   281  			Expect(err).NotTo(HaveOccurred())
   282  		})
   283  
   284  		authorizedTeamTests := func() {
   285  			Context("when the team exists", func() {
   286  				BeforeEach(func() {
   287  					atcTeam = atc.Team{
   288  						Auth: atc.TeamAuth{
   289  							"owner": map[string][]string{
   290  								"users": []string{"local:username"},
   291  							},
   292  						},
   293  					}
   294  					dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   295  				})
   296  
   297  				It("updates provider auth", func() {
   298  					Expect(response.StatusCode).To(Equal(http.StatusOK))
   299  					Expect(fakeTeam.UpdateProviderAuthCallCount()).To(Equal(1))
   300  
   301  					updatedProviderAuth := fakeTeam.UpdateProviderAuthArgsForCall(0)
   302  					Expect(updatedProviderAuth).To(Equal(atcTeam.Auth))
   303  				})
   304  
   305  				Context("when updating provider auth fails", func() {
   306  					BeforeEach(func() {
   307  						fakeTeam.UpdateProviderAuthReturns(errors.New("stop trying to make fetch happen"))
   308  					})
   309  
   310  					It("returns 500 Internal Server error", func() {
   311  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   312  					})
   313  				})
   314  				Context("when provider auth is empty", func() {
   315  					BeforeEach(func() {
   316  						atcTeam = atc.Team{}
   317  						dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   318  					})
   319  
   320  					It("does not update provider auth", func() {
   321  						Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   322  						Expect(fakeTeam.UpdateProviderAuthCallCount()).To(Equal(0))
   323  					})
   324  				})
   325  
   326  				Context("when provider auth is invalid", func() {
   327  					BeforeEach(func() {
   328  						atcTeam = atc.Team{
   329  							Auth: atc.TeamAuth{
   330  								"owner": {
   331  									//"users": []string{},
   332  									//"groups": []string{},
   333  								},
   334  							},
   335  						}
   336  						dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   337  					})
   338  
   339  					It("does not update provider auth", func() {
   340  						Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   341  						Expect(fakeTeam.UpdateProviderAuthCallCount()).To(Equal(0))
   342  					})
   343  				})
   344  			})
   345  		}
   346  
   347  		Context("when the requester team is authorized as an admin team", func() {
   348  			BeforeEach(func() {
   349  				fakeAccess.IsAuthenticatedReturns(true)
   350  				fakeAccess.IsAuthenticatedReturns(true)
   351  				fakeAccess.IsAdminReturns(true)
   352  			})
   353  
   354  			authorizedTeamTests()
   355  
   356  			Context("when the team is not found", func() {
   357  				BeforeEach(func() {
   358  					dbTeamFactory.FindTeamReturns(nil, false, nil)
   359  					dbTeamFactory.CreateTeamReturns(fakeTeam, nil)
   360  				})
   361  
   362  				It("creates the team", func() {
   363  					Expect(response.StatusCode).To(Equal(http.StatusCreated))
   364  					Expect(dbTeamFactory.CreateTeamCallCount()).To(Equal(1))
   365  
   366  					createdTeam := dbTeamFactory.CreateTeamArgsForCall(0)
   367  					Expect(createdTeam).To(Equal(atc.Team{
   368  						Name: "some-team",
   369  						Auth: teamAuth,
   370  					}))
   371  				})
   372  
   373  				It("delete the teams in cache", func() {
   374  					Expect(dbTeamFactory.NotifyCacherCallCount()).To(Equal(1))
   375  				})
   376  
   377  				Context("when it fails to create team", func() {
   378  					BeforeEach(func() {
   379  						dbTeamFactory.CreateTeamReturns(nil, errors.New("it is never going to happen"))
   380  					})
   381  
   382  					It("returns a 500 Internal Server error", func() {
   383  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   384  					})
   385  
   386  					It("does not delete the teams in cache", func() {
   387  						Expect(dbTeamFactory.NotifyCacherCallCount()).To(Equal(0))
   388  					})
   389  				})
   390  
   391  				Context("when the team's name is an invalid identifier", func() {
   392  					BeforeEach(func() {
   393  						path = fmt.Sprintf("%s/api/v1/teams/_some-team", server.URL)
   394  						fakeTeam.NameReturns("_some-team")
   395  					})
   396  
   397  					It("returns a warning in the response body", func() {
   398  						Expect(ioutil.ReadAll(response.Body)).To(MatchJSON(`
   399  								{
   400  									"warnings": [
   401  										{
   402  											"type": "invalid_identifier",
   403  											"message": "team: '_some-team' is not a valid identifier: must start with a lowercase letter"
   404  										}
   405  									],
   406  									"team": {
   407  										"id": 5,
   408  										"name": "_some-team"
   409  									}
   410  								}`))
   411  					})
   412  				})
   413  			})
   414  		})
   415  
   416  		Context("when the requester team is authorized as the team being set", func() {
   417  			BeforeEach(func() {
   418  				fakeAccess.IsAuthenticatedReturns(true)
   419  				fakeAccess.IsAuthorizedReturns(true)
   420  			})
   421  
   422  			authorizedTeamTests()
   423  
   424  			Context("when the team is not found", func() {
   425  				BeforeEach(func() {
   426  					dbTeamFactory.FindTeamReturns(nil, false, nil)
   427  					dbTeamFactory.CreateTeamReturns(fakeTeam, nil)
   428  				})
   429  
   430  				It("does not create the team", func() {
   431  					Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   432  					Expect(dbTeamFactory.CreateTeamCallCount()).To(Equal(0))
   433  				})
   434  			})
   435  		})
   436  	})
   437  
   438  	Describe("DELETE /api/v1/teams/:team_name", func() {
   439  		var request *http.Request
   440  		var response *http.Response
   441  
   442  		var teamName string
   443  
   444  		BeforeEach(func() {
   445  			teamName = "team venture"
   446  
   447  			fakeTeam.IDReturns(2)
   448  			fakeTeam.NameReturns(teamName)
   449  		})
   450  
   451  		Context("when the requester is authenticated for some admin team", func() {
   452  			JustBeforeEach(func() {
   453  				path := fmt.Sprintf("%s/api/v1/teams/%s", server.URL, teamName)
   454  
   455  				var err error
   456  				request, err = http.NewRequest("DELETE", path, nil)
   457  				Expect(err).NotTo(HaveOccurred())
   458  
   459  				response, err = client.Do(request)
   460  				Expect(err).NotTo(HaveOccurred())
   461  			})
   462  
   463  			BeforeEach(func() {
   464  				fakeAccess.IsAuthenticatedReturns(true)
   465  				fakeAccess.IsAdminReturns(true)
   466  			})
   467  
   468  			Context("when there's a problem finding teams", func() {
   469  				BeforeEach(func() {
   470  					dbTeamFactory.FindTeamReturns(nil, false, errors.New("a dingo ate my baby!"))
   471  				})
   472  
   473  				It("returns 500 Internal Server Error", func() {
   474  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   475  				})
   476  			})
   477  
   478  			Context("when team exists", func() {
   479  				BeforeEach(func() {
   480  					dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   481  				})
   482  
   483  				It("returns 204 No Content", func() {
   484  					Expect(response.StatusCode).To(Equal(http.StatusNoContent))
   485  				})
   486  
   487  				It("receives the correct team name", func() {
   488  					Expect(dbTeamFactory.FindTeamCallCount()).To(Equal(1))
   489  					Expect(dbTeamFactory.FindTeamArgsForCall(0)).To(Equal(teamName))
   490  				})
   491  				It("deletes the team from the DB", func() {
   492  					Expect(fakeTeam.DeleteCallCount()).To(Equal(1))
   493  					//TODO delete the build events via a table drop rather
   494  				})
   495  
   496  				Context("when trying to delete the admin team", func() {
   497  					BeforeEach(func() {
   498  						teamName = atc.DefaultTeamName
   499  						fakeTeam.AdminReturns(true)
   500  						dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   501  						dbTeamFactory.GetTeamsReturns([]db.Team{fakeTeam}, nil)
   502  					})
   503  
   504  					It("returns 403 Forbidden and backs off", func() {
   505  						Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   506  						Expect(fakeTeam.DeleteCallCount()).To(Equal(0))
   507  					})
   508  				})
   509  
   510  				Context("when there's a problem deleting the team", func() {
   511  					BeforeEach(func() {
   512  						fakeTeam.DeleteReturns(errors.New("disaster"))
   513  					})
   514  
   515  					It("returns 500 Internal Server Error", func() {
   516  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   517  					})
   518  				})
   519  			})
   520  
   521  			Context("when team does not exist", func() {
   522  				BeforeEach(func() {
   523  					dbTeamFactory.FindTeamReturns(nil, false, nil)
   524  				})
   525  
   526  				It("returns 404 Not Found", func() {
   527  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   528  				})
   529  			})
   530  		})
   531  
   532  		Context("when the requester belongs to a non-admin team", func() {
   533  			JustBeforeEach(func() {
   534  				path := fmt.Sprintf("%s/api/v1/teams/%s", server.URL, "non-admin-team")
   535  
   536  				var err error
   537  				request, err = http.NewRequest("DELETE", path, nil)
   538  				Expect(err).NotTo(HaveOccurred())
   539  
   540  				response, err = client.Do(request)
   541  				Expect(err).NotTo(HaveOccurred())
   542  
   543  			})
   544  
   545  			BeforeEach(func() {
   546  				fakeAccess.IsAuthenticatedReturns(true)
   547  				fakeAccess.IsAdminReturns(false)
   548  			})
   549  
   550  			It("returns 403 forbidden", func() {
   551  				Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   552  			})
   553  		})
   554  	})
   555  
   556  	Describe("PUT /api/v1/teams/:team_name/rename", func() {
   557  		var response *http.Response
   558  		var requestBody string
   559  		var teamName string
   560  
   561  		JustBeforeEach(func() {
   562  			request, err := http.NewRequest(
   563  				"PUT",
   564  				server.URL+"/api/v1/teams/"+teamName+"/rename",
   565  				bytes.NewBufferString(requestBody),
   566  			)
   567  			Expect(err).NotTo(HaveOccurred())
   568  
   569  			response, err = client.Do(request)
   570  			Expect(err).NotTo(HaveOccurred())
   571  		})
   572  
   573  		BeforeEach(func() {
   574  			requestBody = `{"name":"some-new-name"}`
   575  			fakeTeam.IDReturns(2)
   576  		})
   577  
   578  		Context("when authenticated", func() {
   579  			BeforeEach(func() {
   580  				fakeAccess.IsAuthenticatedReturns(true)
   581  			})
   582  			Context("when requester belongs to an admin team", func() {
   583  				BeforeEach(func() {
   584  					teamName = "a-team"
   585  					fakeTeam.NameReturns(teamName)
   586  					fakeAccess.IsAdminReturns(true)
   587  					dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   588  				})
   589  
   590  				It("constructs teamDB with provided team name", func() {
   591  					Expect(dbTeamFactory.FindTeamCallCount()).To(Equal(1))
   592  					Expect(dbTeamFactory.FindTeamArgsForCall(0)).To(Equal("a-team"))
   593  				})
   594  
   595  				It("renames the team to the name provided", func() {
   596  					Expect(fakeTeam.RenameCallCount()).To(Equal(1))
   597  					Expect(fakeTeam.RenameArgsForCall(0)).To(Equal("some-new-name"))
   598  				})
   599  
   600  				It("returns 200", func() {
   601  					Expect(response.StatusCode).To(Equal(http.StatusOK))
   602  				})
   603  
   604  				Context("when a warning occurs", func() {
   605  
   606  					BeforeEach(func() {
   607  						requestBody = `{"name":"_some-new-name"}`
   608  					})
   609  
   610  					It("returns a warning in the response body", func() {
   611  						Expect(ioutil.ReadAll(response.Body)).To(MatchJSON(`
   612  							{
   613  								"warnings": [
   614  								{
   615  									"type": "invalid_identifier",
   616  									"message": "team: '_some-new-name' is not a valid identifier: must start with a lowercase letter"
   617  								}
   618  								]
   619  							}`))
   620  					})
   621  				})
   622  			})
   623  
   624  			Context("when requester belongs to the team", func() {
   625  				BeforeEach(func() {
   626  					teamName = "a-team"
   627  					fakeTeam.NameReturns(teamName)
   628  					fakeAccess.IsAuthorizedReturns(true)
   629  					dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   630  				})
   631  
   632  				It("constructs teamDB with provided team name", func() {
   633  					Expect(dbTeamFactory.FindTeamCallCount()).To(Equal(1))
   634  					Expect(dbTeamFactory.FindTeamArgsForCall(0)).To(Equal("a-team"))
   635  				})
   636  
   637  				It("renames the team to the name provided", func() {
   638  					Expect(fakeTeam.RenameCallCount()).To(Equal(1))
   639  					Expect(fakeTeam.RenameArgsForCall(0)).To(Equal("some-new-name"))
   640  				})
   641  
   642  				It("returns 200", func() {
   643  					Expect(response.StatusCode).To(Equal(http.StatusOK))
   644  				})
   645  			})
   646  
   647  			Context("when requester does not belong to the team", func() {
   648  				BeforeEach(func() {
   649  					teamName = "a-team"
   650  					fakeTeam.NameReturns(teamName)
   651  					fakeAccess.IsAuthorizedReturns(false)
   652  					dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   653  				})
   654  
   655  				It("returns 403 Forbidden", func() {
   656  					Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   657  					Expect(fakeTeam.RenameCallCount()).To(Equal(0))
   658  				})
   659  			})
   660  		})
   661  
   662  		Context("when not authenticated", func() {
   663  			BeforeEach(func() {
   664  				fakeAccess.IsAuthenticatedReturns(false)
   665  			})
   666  
   667  			It("returns 401 Unauthorized", func() {
   668  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   669  				Expect(fakeTeam.RenameCallCount()).To(Equal(0))
   670  			})
   671  		})
   672  	})
   673  
   674  	Describe("GET /api/v1/teams/:team_name/builds", func() {
   675  		var (
   676  			response    *http.Response
   677  			queryParams string
   678  			teamName    string
   679  		)
   680  
   681  		BeforeEach(func() {
   682  			teamName = "some-team"
   683  		})
   684  
   685  		JustBeforeEach(func() {
   686  			var err error
   687  
   688  			response, err = client.Get(server.URL + "/api/v1/teams/" + teamName + "/builds" + queryParams)
   689  			Expect(err).NotTo(HaveOccurred())
   690  		})
   691  
   692  		Context("when not authenticated", func() {
   693  			BeforeEach(func() {
   694  				fakeAccess.IsAuthenticatedReturns(false)
   695  				dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   696  			})
   697  
   698  			It("returns 401", func() {
   699  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   700  				Expect(fakeTeam.BuildsCallCount()).To(Equal(0))
   701  			})
   702  		})
   703  
   704  		Context("when authenticated", func() {
   705  			BeforeEach(func() {
   706  				fakeAccess.IsAuthenticatedReturns(true)
   707  				dbTeamFactory.FindTeamReturns(fakeTeam, true, nil)
   708  			})
   709  
   710  			Context("when no params are passed", func() {
   711  				It("does not set defaults for since and until", func() {
   712  					Expect(fakeTeam.BuildsCallCount()).To(Equal(1))
   713  
   714  					page := fakeTeam.BuildsArgsForCall(0)
   715  					Expect(page).To(Equal(db.Page{
   716  						Limit: 100,
   717  					}))
   718  				})
   719  			})
   720  
   721  			Context("when all the params are passed", func() {
   722  				BeforeEach(func() {
   723  					queryParams = "?from=2&to=3&limit=8"
   724  				})
   725  
   726  				It("passes them through", func() {
   727  					Expect(fakeTeam.BuildsCallCount()).To(Equal(1))
   728  
   729  					page := fakeTeam.BuildsArgsForCall(0)
   730  					Expect(page).To(Equal(db.Page{
   731  						From:  db.NewIntPtr(2),
   732  						To:    db.NewIntPtr(3),
   733  						Limit: 8,
   734  					}))
   735  				})
   736  			})
   737  
   738  			Context("when getting the builds succeeds", func() {
   739  				var returnedBuilds []db.Build
   740  
   741  				BeforeEach(func() {
   742  					queryParams = "?since=5&limit=2"
   743  
   744  					build1 := new(dbfakes.FakeBuild)
   745  					build1.IDReturns(4)
   746  					build1.NameReturns("2")
   747  					build1.JobNameReturns("some-job")
   748  					build1.PipelineNameReturns("some-pipeline")
   749  					build1.TeamNameReturns("some-team")
   750  					build1.StatusReturns(db.BuildStatusStarted)
   751  					build1.StartTimeReturns(time.Unix(1, 0))
   752  					build1.EndTimeReturns(time.Unix(100, 0))
   753  
   754  					build2 := new(dbfakes.FakeBuild)
   755  					build2.IDReturns(2)
   756  					build2.NameReturns("1")
   757  					build2.JobNameReturns("some-job")
   758  					build2.PipelineNameReturns("some-pipeline")
   759  					build2.TeamNameReturns("some-team")
   760  					build2.StatusReturns(db.BuildStatusSucceeded)
   761  					build2.StartTimeReturns(time.Unix(101, 0))
   762  					build2.EndTimeReturns(time.Unix(200, 0))
   763  
   764  					returnedBuilds = []db.Build{build1, build2}
   765  					fakeTeam.BuildsReturns(returnedBuilds, db.Pagination{}, nil)
   766  				})
   767  
   768  				It("returns 200 OK", func() {
   769  					Expect(response.StatusCode).To(Equal(http.StatusOK))
   770  				})
   771  
   772  				It("returns Content-Type 'application/json'", func() {
   773  					expectedHeaderEntries := map[string]string{
   774  						"Content-Type": "application/json",
   775  					}
   776  					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   777  				})
   778  
   779  				It("returns the builds", func() {
   780  					body, err := ioutil.ReadAll(response.Body)
   781  					Expect(err).NotTo(HaveOccurred())
   782  
   783  					Expect(body).To(MatchJSON(`[
   784  					{
   785  						"id": 4,
   786  						"name": "2",
   787  						"job_name": "some-job",
   788  						"status": "started",
   789  						"api_url": "/api/v1/builds/4",
   790  						"pipeline_name":"some-pipeline",
   791  						"team_name": "some-team",
   792  						"start_time": 1,
   793  						"end_time": 100
   794  					},
   795  					{
   796  						"id": 2,
   797  						"name": "1",
   798  						"job_name": "some-job",
   799  						"status": "succeeded",
   800  						"api_url": "/api/v1/builds/2",
   801  						"pipeline_name": "some-pipeline",
   802  						"team_name": "some-team",
   803  						"start_time": 101,
   804  						"end_time": 200
   805  					}
   806  				]`))
   807  				})
   808  
   809  				Context("when next/previous pages are available", func() {
   810  					BeforeEach(func() {
   811  						fakeTeam.BuildsReturns(returnedBuilds, db.Pagination{
   812  							Newer: &db.Page{From: db.NewIntPtr(4), Limit: 2},
   813  							Older: &db.Page{To: db.NewIntPtr(2), Limit: 2},
   814  						}, nil)
   815  					})
   816  
   817  					It("returns Link headers per rfc5988", func() {
   818  						Expect(response.Header["Link"]).To(ConsistOf([]string{
   819  							fmt.Sprintf(`<%s/api/v1/teams/some-team/builds?from=4&limit=2>; rel="previous"`, externalURL),
   820  							fmt.Sprintf(`<%s/api/v1/teams/some-team/builds?to=2&limit=2>; rel="next"`, externalURL),
   821  						}))
   822  					})
   823  				})
   824  			})
   825  
   826  			Context("when getting the build fails", func() {
   827  				BeforeEach(func() {
   828  					fakeTeam.BuildsReturns(nil, db.Pagination{}, errors.New("oh no!"))
   829  				})
   830  
   831  				It("returns 404 Not Found", func() {
   832  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   833  				})
   834  			})
   835  		})
   836  	})
   837  })