package api_test

import (
	"bytes"
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/concourse/concourse/atc"
	"github.com/concourse/concourse/atc/db"
	"github.com/concourse/concourse/atc/db/dbfakes"
	. "github.com/concourse/concourse/atc/testhelpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Workers API", func() {

	Describe("GET /api/v1/workers", func() {
		var response *http.Response

		JustBeforeEach(func() {
			req, err := http.NewRequest("GET", server.URL+"/api/v1/workers", nil)
			Expect(err).NotTo(HaveOccurred())

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())
		})

		Context("when authenticated", func() {
			var (
				teamWorker1 *dbfakes.FakeWorker
				teamWorker2 *dbfakes.FakeWorker
			)

			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(true)
				fakeAccess.IsAuthorizedReturns(true)
				fakeAccess.TeamNamesReturns([]string{"some-team"})
				dbWorkerFactory.VisibleWorkersReturns(nil, nil)

				teamWorker1 = new(dbfakes.FakeWorker)
				gardenAddr1 := "1.2.3.4:7777"
				teamWorker1.GardenAddrReturns(&gardenAddr1)
				bcURL1 := "1.2.3.4:8888"
				teamWorker1.BaggageclaimURLReturns(&bcURL1)

				teamWorker2 = new(dbfakes.FakeWorker)
				gardenAddr2 := "5.6.7.8:7777"
				teamWorker2.GardenAddrReturns(&gardenAddr2)
				bcURL2 := "5.6.7.8:8888"
				teamWorker2.BaggageclaimURLReturns(&bcURL2)
			})

			It("fetches workers by team name from worker user context", func() {
				Expect(dbWorkerFactory.VisibleWorkersCallCount()).To(Equal(1))

				teamNames := dbWorkerFactory.VisibleWorkersArgsForCall(0)
				Expect(teamNames).To(ConsistOf("some-team"))
			})

			Context("when user is an admin", func() {
				BeforeEach(func() {
					fakeAccess.IsAdminReturns(true)
					dbWorkerFactory.WorkersReturns([]db.Worker{
						teamWorker1,
						teamWorker2,
					}, nil)
				})

				It("returns all the workers", func() {
					Expect(response.StatusCode).To(Equal(http.StatusOK))
					expectedHeaderEntries := map[string]string{
						"Content-Type": "application/json",
					}
					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))

					Expect(dbWorkerFactory.WorkersCallCount()).To(Equal(1))

					var returnedWorkers []atc.Worker
					err := json.NewDecoder(response.Body).Decode(&returnedWorkers)
					Expect(err).NotTo(HaveOccurred())

					Expect(returnedWorkers).To(Equal([]atc.Worker{
						{
							GardenAddr:      "1.2.3.4:7777",
							BaggageclaimURL: "1.2.3.4:8888",
						},
						{
							GardenAddr:      "5.6.7.8:7777",
							BaggageclaimURL: "5.6.7.8:8888",
						},
					}))
				})
			})

			Context("when the workers can be listed", func() {
				BeforeEach(func() {
					dbWorkerFactory.VisibleWorkersReturns([]db.Worker{
						teamWorker1,
						teamWorker2,
					}, nil)
				})

				It("returns 200", func() {
					Expect(response.StatusCode).To(Equal(http.StatusOK))
				})

				It("returns Content-Type 'application/json'", func() {
					expectedHeaderEntries := map[string]string{
						"Content-Type": "application/json",
					}
					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
				})

				It("returns the workers", func() {
					var returnedWorkers []atc.Worker
					err := json.NewDecoder(response.Body).Decode(&returnedWorkers)
					Expect(err).NotTo(HaveOccurred())

					Expect(dbWorkerFactory.VisibleWorkersCallCount()).To(Equal(1))

					Expect(returnedWorkers).To(Equal([]atc.Worker{
						{
							GardenAddr:      "1.2.3.4:7777",
							BaggageclaimURL: "1.2.3.4:8888",
						},
						{
							GardenAddr:      "5.6.7.8:7777",
							BaggageclaimURL: "5.6.7.8:8888",
						},
					}))

				})
			})

			Context("when getting the workers fails", func() {
				BeforeEach(func() {
					dbWorkerFactory.VisibleWorkersReturns(nil, errors.New("error!"))
				})

				It("returns 500", func() {
					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
				})
			})
		})

		Context("when not authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(false)
			})

			It("returns 401", func() {
				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
			})
		})
	})

	Describe("POST /api/v1/workers", func() {
		var (
			worker    atc.Worker
			ttl       string
			certsPath string

			response *http.Response
		)

		BeforeEach(func() {
			certsPath = "/some/certs/path"
			worker = atc.Worker{
				Name:             "worker-name",
				GardenAddr:       "1.2.3.4:7777",
				BaggageclaimURL:  "5.6.7.8:7788",
				CertsPath:        &certsPath,
				HTTPProxyURL:     "http://example.com",
				HTTPSProxyURL:    "https://example.com",
				NoProxy:          "example.com,127.0.0.1,localhost",
				ActiveContainers: 2,
				ActiveVolumes:    10,
				ActiveTasks:      42,
				ResourceTypes: []atc.WorkerResourceType{
					{Type: "some-resource", Image: "some-resource-image"},
				},
				Platform: "haiku",
				Tags:     []string{"not", "a", "limerick"},
				Version:  "1.2.3",
			}

			ttl = "30s"
			fakeAccess.IsAuthorizedReturns(true)
			fakeAccess.IsSystemReturns(true)
		})

		JustBeforeEach(func() {
			payload, err := json.Marshal(worker)
			Expect(err).NotTo(HaveOccurred())

			req, err := http.NewRequest("POST", server.URL+"/api/v1/workers?ttl="+ttl, ioutil.NopCloser(bytes.NewBuffer(payload)))
			Expect(err).NotTo(HaveOccurred())

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())
		})

		Context("when authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(true)
			})

			It("tries to save the worker", func() {
				Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))
				savedWorker, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
				Expect(savedWorker).To(Equal(atc.Worker{
					GardenAddr:       "1.2.3.4:7777",
					Name:             "worker-name",
					BaggageclaimURL:  "5.6.7.8:7788",
					CertsPath:        &certsPath,
					HTTPProxyURL:     "http://example.com",
					HTTPSProxyURL:    "https://example.com",
					NoProxy:          "example.com,127.0.0.1,localhost",
					ActiveContainers: 2,
					ActiveVolumes:    10,
					ActiveTasks:      42,
					ResourceTypes: []atc.WorkerResourceType{
						{Type: "some-resource", Image: "some-resource-image"},
					},
					Platform: "haiku",
					Tags:     []string{"not", "a", "limerick"},
					Version:  "1.2.3",
				}))

				Expect(savedTTL.String()).To(Equal(ttl))
			})

			Context("when request is not from tsa", func() {
				Context("when system claim is false", func() {
					BeforeEach(func() {
						fakeAccess.IsSystemReturns(false)
					})

					It("return 403", func() {
						Expect(response.StatusCode).To(Equal(http.StatusForbidden))
					})
				})
			})

			Context("when payload contains team name", func() {
				BeforeEach(func() {
					worker.Team = "some-team"
				})

				Context("when specified team exists", func() {
					var foundTeam *dbfakes.FakeTeam

					BeforeEach(func() {
						foundTeam = new(dbfakes.FakeTeam)
						dbTeamFactory.FindTeamReturns(foundTeam, true, nil)
					})

					It("saves team name in db", func() {
						Expect(foundTeam.SaveWorkerCallCount()).To(Equal(1))
					})

					Context("when saving the worker succeeds", func() {
						BeforeEach(func() {
							foundTeam.SaveWorkerReturns(new(dbfakes.FakeWorker), nil)
						})

						It("returns 200", func() {
							Expect(response.StatusCode).To(Equal(http.StatusOK))
						})
					})

					Context("when saving the worker fails", func() {
						BeforeEach(func() {
							foundTeam.SaveWorkerReturns(nil, errors.New("oh no!"))
						})

						It("returns 500", func() {
							Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
						})
					})
				})

				Context("when specified team does not exist", func() {
					BeforeEach(func() {
						dbTeamFactory.FindTeamReturns(nil, false, nil)
					})

					It("returns 400", func() {
						Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
					})
				})
			})

			Context("when the worker has no name", func() {
				BeforeEach(func() {
					worker.Name = ""
				})

				It("tries to save the worker with the garden address as the name", func() {
					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))

					savedInfo, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
					Expect(savedInfo).To(Equal(atc.Worker{
						GardenAddr:       "1.2.3.4:7777",
						Name:             "1.2.3.4:7777",
						BaggageclaimURL:  "5.6.7.8:7788",
						CertsPath:        &certsPath,
						HTTPProxyURL:     "http://example.com",
						HTTPSProxyURL:    "https://example.com",
						NoProxy:          "example.com,127.0.0.1,localhost",
						ActiveContainers: 2,
						ActiveVolumes:    10,
						ActiveTasks:      42,
						ResourceTypes: []atc.WorkerResourceType{
							{Type: "some-resource", Image: "some-resource-image"},
						},
						Platform: "haiku",
						Tags:     []string{"not", "a", "limerick"},
						Version:  "1.2.3",
					}))

					Expect(savedTTL.String()).To(Equal(ttl))
				})
			})

			Context("when the certs path is null", func() {
				BeforeEach(func() {
					worker.CertsPath = nil
				})

				It("saves the worker with a null certs path", func() {
					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))

					savedInfo, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
					Expect(savedInfo).To(Equal(atc.Worker{
						GardenAddr:       "1.2.3.4:7777",
						Name:             "worker-name",
						BaggageclaimURL:  "5.6.7.8:7788",
						CertsPath:        nil,
						HTTPProxyURL:     "http://example.com",
						HTTPSProxyURL:    "https://example.com",
						NoProxy:          "example.com,127.0.0.1,localhost",
						ActiveContainers: 2,
						ActiveVolumes:    10,
						ActiveTasks:      42,
						ResourceTypes: []atc.WorkerResourceType{
							{Type: "some-resource", Image: "some-resource-image"},
						},
						Platform: "haiku",
						Tags:     []string{"not", "a", "limerick"},
						Version:  "1.2.3",
					}))

					Expect(savedTTL.String()).To(Equal(ttl))
				})
			})

			Context("when the certs path is an empty string", func() {
				BeforeEach(func() {
					emptyString := ""
					worker.CertsPath = &emptyString
				})

				It("saves the worker with a null certs path", func() {
					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(Equal(1))

					savedInfo, savedTTL := dbWorkerFactory.SaveWorkerArgsForCall(0)
					Expect(savedInfo).To(Equal(atc.Worker{
						GardenAddr:       "1.2.3.4:7777",
						Name:             "worker-name",
						BaggageclaimURL:  "5.6.7.8:7788",
						CertsPath:        nil,
						HTTPProxyURL:     "http://example.com",
						HTTPSProxyURL:    "https://example.com",
						NoProxy:          "example.com,127.0.0.1,localhost",
						ActiveContainers: 2,
						ActiveVolumes:    10,
						ActiveTasks:      42,
						ResourceTypes: []atc.WorkerResourceType{
							{Type: "some-resource", Image: "some-resource-image"},
						},
						Platform: "haiku",
						Tags:     []string{"not", "a", "limerick"},
						Version:  "1.2.3",
					}))

					Expect(savedTTL.String()).To(Equal(ttl))
				})
			})

			Context("when saving the worker succeeds", func() {
				var fakeWorker *dbfakes.FakeWorker
				BeforeEach(func() {
					fakeWorker = new(dbfakes.FakeWorker)
					dbWorkerFactory.SaveWorkerReturns(fakeWorker, nil)
				})

				It("returns 200", func() {
					Expect(response.StatusCode).To(Equal(http.StatusOK))
				})

			})

			Context("when saving the worker fails", func() {
				BeforeEach(func() {
					dbWorkerFactory.SaveWorkerReturns(nil, errors.New("oh no!"))
				})

				It("returns 500", func() {
					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
				})
			})

			Context("when the TTL is invalid", func() {
				BeforeEach(func() {
					ttl = "invalid-duration"
				})

				It("returns 400", func() {
					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
				})

				It("returns the validation error in the response body", func() {
					Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("malformed ttl")))
				})

				It("does not save it", func() {
					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
				})
			})

			Context("when the worker has no address", func() {
				BeforeEach(func() {
					worker.GardenAddr = ""
				})

				It("returns 400", func() {
					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
				})

				It("returns the validation error in the response body", func() {
					Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("missing garden address")))
				})

				It("does not save it", func() {
					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
				})
			})

			Context("when worker version is invalid", func() {
				BeforeEach(func() {
					worker.Version = "invalid"
				})

				It("returns 400", func() {
					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
				})

				It("returns the validation error in the response body", func() {
					Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("invalid worker version, only numeric characters are allowed")))
				})

				It("does not save it", func() {
					Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
				})
			})
		})

		Context("when not authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(false)
			})

			It("returns 401", func() {
				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
			})

			It("does not save the config", func() {
				Expect(dbWorkerFactory.SaveWorkerCallCount()).To(BeZero())
			})
		})
	})

	Describe("PUT /api/v1/workers/:worker_name/land", func() {
		var (
			response   *http.Response
			workerName string
			fakeWorker *dbfakes.FakeWorker
		)

		JustBeforeEach(func() {
			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/land", nil)
			Expect(err).NotTo(HaveOccurred())

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())
		})

		BeforeEach(func() {
			fakeWorker = new(dbfakes.FakeWorker)
			workerName = "some-worker"
			fakeWorker.NameReturns(workerName)
			fakeWorker.TeamNameReturns("some-team")
			fakeWorker.LandReturns(nil)

			fakeAccess.IsAuthenticatedReturns(true)
			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
		})

		Context("when the request is authenticated as system", func() {
			BeforeEach(func() {
				fakeAccess.IsSystemReturns(true)
			})

			It("returns 200", func() {
				Expect(response.StatusCode).To(Equal(http.StatusOK))
			})

			It("sees if the worker exists and attempts to land it", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
				Expect(fakeWorker.LandCallCount()).To(Equal(1))
			})

			Context("when landing the worker fails", func() {
				var returnedErr error

				BeforeEach(func() {
					returnedErr = errors.New("some-error")
					fakeWorker.LandReturns(returnedErr)
				})

				It("returns 500", func() {
					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
				})
			})

			Context("when the worker does not exist", func() {
				BeforeEach(func() {
					dbWorkerFactory.GetWorkerReturns(nil, false, nil)
				})

				It("returns 404", func() {
					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
				})
			})
		})

		Context("when the request is authorized as the worker's owner", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthorizedReturns(true)
			})

			It("returns 200", func() {
				Expect(response.StatusCode).To(Equal(http.StatusOK))
			})
		})

		Context("when the request is authorized as the wrong team", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthorizedReturns(false)
			})

			It("returns 403", func() {
				Expect(response.StatusCode).To(Equal(http.StatusForbidden))
			})
		})

		Context("when not authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(false)
			})

			It("returns 401", func() {
				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
			})

			It("does not attempt to find the worker", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
			})
		})
	})

	Describe("PUT /api/v1/workers/:worker_name/retire", func() {
		var (
			response   *http.Response
			workerName string
			fakeWorker *dbfakes.FakeWorker
		)

		JustBeforeEach(func() {
			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/retire", nil)
			Expect(err).NotTo(HaveOccurred())

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())
		})

		BeforeEach(func() {
			fakeWorker = new(dbfakes.FakeWorker)
			workerName = "some-worker"
			fakeWorker.NameReturns(workerName)
			fakeWorker.TeamNameReturns("some-team")
			fakeAccess.IsAuthenticatedReturns(true)

			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
			fakeWorker.RetireReturns(nil)
		})

		Context("when autheticated as system", func() {
			BeforeEach(func() {
				fakeAccess.IsSystemReturns(true)
			})

			It("returns 200", func() {
				Expect(response.StatusCode).To(Equal(http.StatusOK))
			})

			It("sees if the worker exists and attempts to retire it", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))

				Expect(fakeWorker.RetireCallCount()).To(Equal(1))
			})

			Context("when retiring the worker fails", func() {
				var returnedErr error

				BeforeEach(func() {
					returnedErr = errors.New("some-error")
					fakeWorker.RetireReturns(returnedErr)
				})

				It("returns 500", func() {
					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
				})
			})

			Context("when the worker does not exist", func() {
				BeforeEach(func() {
					dbWorkerFactory.GetWorkerReturns(nil, false, nil)
				})

				It("returns 404", func() {
					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
				})
			})
		})

		Context("when authorized as as the worker's owner", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthorizedReturns(true)
			})

			It("returns 200", func() {
				Expect(response.StatusCode).To(Equal(http.StatusOK))
			})
		})

		Context("when authorized as some other team", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthorizedReturns(false)
			})

			It("returns 403", func() {
				Expect(response.StatusCode).To(Equal(http.StatusForbidden))
			})
		})

		Context("when not authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(false)
			})

			It("returns 401", func() {
				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
			})

			It("does not attempt to find the worker", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
			})
		})
	})

	Describe("PUT /api/v1/workers/:worker_name/prune", func() {
		var (
			response   *http.Response
			workerName string
			fakeWorker *dbfakes.FakeWorker
		)

		JustBeforeEach(func() {
			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/prune", nil)
			Expect(err).NotTo(HaveOccurred())

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())
		})

		BeforeEach(func() {
			fakeWorker = new(dbfakes.FakeWorker)
			workerName = "some-worker"
			fakeWorker.NameReturns(workerName)
			fakeWorker.TeamNameReturns("some-team")

			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
			fakeAccess.IsAuthenticatedReturns(true)
			fakeAccess.IsAuthorizedReturns(true)
			fakeWorker.PruneReturns(nil)
		})

		It("returns 200", func() {
			Expect(response.StatusCode).To(Equal(http.StatusOK))
		})

		It("sees if the worker exists and attempts to prune it", func() {
			Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))
			Expect(fakeWorker.PruneCallCount()).To(Equal(1))
		})

		Context("when pruning the worker fails", func() {
			var returnedErr error

			BeforeEach(func() {
				returnedErr = errors.New("some-error")
				fakeWorker.PruneReturns(returnedErr)
			})

			It("returns 500", func() {
				Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
			})
		})

		Context("when the worker does not exist", func() {
			BeforeEach(func() {
				dbWorkerFactory.GetWorkerReturns(nil, false, nil)
			})

			It("returns 404", func() {
				Expect(response.StatusCode).To(Equal(http.StatusNotFound))
			})
		})

		Context("when the worker is running", func() {
			BeforeEach(func() {
				fakeWorker.PruneReturns(db.ErrCannotPruneRunningWorker)
			})

			It("returns 400", func() {
				Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
				Expect(ioutil.ReadAll(response.Body)).To(MatchJSON(`{"stderr":"cannot prune running worker"}`))
			})
		})

		Context("when not authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(false)
			})

			It("returns 401", func() {
				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
			})

			It("does not attempt to find the worker", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
			})
		})
	})

	Describe("PUT /api/v1/workers/:worker_name/heartbeat", func() {
		var (
			response   *http.Response
			workerName string
			ttlStr     string
			ttl        time.Duration
			err        error

			worker     atc.Worker
			fakeWorker *dbfakes.FakeWorker
		)

		BeforeEach(func() {
			fakeWorker = new(dbfakes.FakeWorker)
			workerName = "some-name"
			fakeWorker.NameReturns(workerName)
			fakeWorker.ActiveContainersReturns(2)
			fakeWorker.ActiveVolumesReturns(10)
			fakeWorker.ActiveTasksReturns(42, nil)
			fakeWorker.PlatformReturns("penguin")
			fakeWorker.TagsReturns([]string{"some-tag"})
			fakeWorker.StateReturns(db.WorkerStateRunning)
			fakeWorker.TeamNameReturns("some-team")
			fakeWorker.EphemeralReturns(true)

			ttlStr = "30s"
			ttl, err = time.ParseDuration(ttlStr)
			Expect(err).NotTo(HaveOccurred())

			worker = atc.Worker{
				Name:             workerName,
				ActiveContainers: 2,
			}
			fakeAccess.IsAuthenticatedReturns(true)
			dbWorkerFactory.HeartbeatWorkerReturns(fakeWorker, nil)
		})

		JustBeforeEach(func() {
			payload, err := json.Marshal(worker)
			Expect(err).NotTo(HaveOccurred())

			req, err := http.NewRequest("PUT", server.URL+"/api/v1/workers/"+workerName+"/heartbeat?ttl="+ttlStr, ioutil.NopCloser(bytes.NewBuffer(payload)))
			Expect(err).NotTo(HaveOccurred())

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())
		})

		It("returns 200", func() {
			Expect(response.StatusCode).To(Equal(http.StatusOK))
		})

		It("returns Content-Type 'application/json'", func() {
			expectedHeaderEntries := map[string]string{
				"Content-Type": "application/json",
			}
			Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
		})

		It("returns saved worker", func() {
			contents, err := ioutil.ReadAll(response.Body)
			Expect(err).NotTo(HaveOccurred())

			Expect(contents).To(MatchJSON(`{
				"name": "some-name",
				"state": "running",
				"addr": "",
				"baggageclaim_url": "",
				"active_containers": 2,
				"active_volumes": 10,
				"active_tasks": 42,
				"resource_types": null,
				"platform": "penguin",
				"ephemeral": true,
				"tags": ["some-tag"],
				"team": "some-team",
				"start_time": 0,
				"version": ""
			}`))
		})

		It("sees if the worker exists and attempts to heartbeat with provided ttl", func() {
			Expect(dbWorkerFactory.HeartbeatWorkerCallCount()).To(Equal(1))

			w, t := dbWorkerFactory.HeartbeatWorkerArgsForCall(0)
			Expect(w).To(Equal(worker))
			Expect(t).To(Equal(ttl))
		})

		Context("when the TTL is invalid", func() {
			BeforeEach(func() {
				ttlStr = "invalid-duration"
			})

			It("returns 400", func() {
				Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
			})

			It("returns the validation error in the response body", func() {
				Expect(ioutil.ReadAll(response.Body)).To(Equal([]byte("malformed ttl")))
			})

			It("does not heartbeat worker", func() {
				Expect(dbWorkerFactory.HeartbeatWorkerCallCount()).To(BeZero())
			})
		})

		Context("when heartbeating the worker fails", func() {
			var returnedErr error

			BeforeEach(func() {
				returnedErr = errors.New("some-error")
				dbWorkerFactory.HeartbeatWorkerReturns(nil, returnedErr)
			})

			It("returns 500", func() {
				Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
			})
		})

		Context("when the worker does not exist", func() {
			BeforeEach(func() {
				dbWorkerFactory.HeartbeatWorkerReturns(nil, db.ErrWorkerNotPresent)
			})

			It("returns 404", func() {
				Expect(response.StatusCode).To(Equal(http.StatusNotFound))
			})
		})

		Context("when not authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(false)
			})

			It("returns 401", func() {
				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
			})

			It("does not heartbeat the worker", func() {
				Expect(dbWorkerFactory.HeartbeatWorkerCallCount()).To(BeZero())
			})
		})
	})

	Describe("DELETE /api/v1/workers/:worker_name", func() {
		var (
			response   *http.Response
			workerName string
			fakeWorker *dbfakes.FakeWorker
		)

		JustBeforeEach(func() {
			req, err := http.NewRequest("DELETE", server.URL+"/api/v1/workers/"+workerName, nil)
			Expect(err).NotTo(HaveOccurred())

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())
		})

		BeforeEach(func() {
			fakeWorker = new(dbfakes.FakeWorker)
			workerName = "some-worker"
			fakeWorker.NameReturns(workerName)

			fakeAccess.IsAuthenticatedReturns(true)
			fakeWorker.DeleteReturns(nil)
			dbWorkerFactory.GetWorkerReturns(fakeWorker, true, nil)
		})

		Context("when user is system user", func() {
			BeforeEach(func() {
				fakeAccess.IsSystemReturns(true)
			})
			It("deletes the worker from the DB", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))

				Expect(fakeWorker.DeleteCallCount()).To(Equal(1))
			})
			It("returns 200", func() {
				Expect(response.StatusCode).To(Equal(http.StatusOK))
			})

			Context("when the given worker has already been deleted", func() {
				BeforeEach(func() {
					dbWorkerFactory.GetWorkerReturns(nil, false, nil)
				})
				It("returns 500", func() {
					Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
				})
			})

			Context("when deleting the worker fails", func() {
				var returnedErr error

				BeforeEach(func() {
					returnedErr = errors.New("some-error")
					fakeWorker.DeleteReturns(returnedErr)
				})

				It("returns 500", func() {
					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
				})
			})
		})

		Context("when user is admin user", func() {
			BeforeEach(func() {
				fakeAccess.IsAdminReturns(true)
			})
			It("deletes the worker from the DB", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))

				Expect(fakeWorker.DeleteCallCount()).To(Equal(1))
			})
			It("returns 200", func() {
				Expect(response.StatusCode).To(Equal(http.StatusOK))
			})
		})

		Context("when user is authorized for team", func() {
			BeforeEach(func() {
				fakeWorker.TeamNameReturns("some-team")
				fakeAccess.IsAuthorizedReturns(true)
			})
			It("deletes the worker from the DB", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(Equal(1))
				Expect(dbWorkerFactory.GetWorkerArgsForCall(0)).To(Equal(workerName))

				Expect(fakeWorker.DeleteCallCount()).To(Equal(1))
			})
			It("returns 200", func() {
				Expect(response.StatusCode).To(Equal(http.StatusOK))
			})
		})

		Context("when not authenticated", func() {
			BeforeEach(func() {
				fakeAccess.IsAuthenticatedReturns(false)
			})

			It("returns 401", func() {
				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
			})

			It("does not attempt to find the worker", func() {
				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
			})
		})
	})
})
