github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/jobs_test.go (about) 1 package api_test 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "strings" 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 var _ = Describe("Jobs API", func() { 21 var fakeJob *dbfakes.FakeJob 22 var versionedResourceTypes atc.VersionedResourceTypes 23 var fakePipeline *dbfakes.FakePipeline 24 25 BeforeEach(func() { 26 fakeJob = new(dbfakes.FakeJob) 27 fakePipeline = new(dbfakes.FakePipeline) 28 dbTeamFactory.FindTeamReturns(dbTeam, true, nil) 29 dbTeam.PipelineReturns(fakePipeline, true, nil) 30 31 versionedResourceTypes = atc.VersionedResourceTypes{ 32 atc.VersionedResourceType{ 33 ResourceType: atc.ResourceType{ 34 Name: "some-resource-1", 35 Type: "some-base-type-1", 36 Source: atc.Source{"some": "source-1"}, 37 }, 38 Version: atc.Version{"some": "version-1"}, 39 }, 40 atc.VersionedResourceType{ 41 ResourceType: atc.ResourceType{ 42 Name: "some-resource-2", 43 Type: "some-base-type-2", 44 Source: atc.Source{"some": "source-2"}, 45 }, 46 Version: atc.Version{"some": "version-2"}, 47 }, 48 atc.VersionedResourceType{ 49 ResourceType: atc.ResourceType{ 50 Name: "some-resource-3", 51 Type: "some-base-type-3", 52 Source: atc.Source{"some": "source-3"}, 53 }, 54 Version: atc.Version{"some": "version-3"}, 55 }, 56 } 57 58 fakePipeline.ResourceTypesReturns([]db.ResourceType{ 59 fakeDBResourceType(versionedResourceTypes[0]), 60 fakeDBResourceType(versionedResourceTypes[1]), 61 fakeDBResourceType(versionedResourceTypes[2]), 62 }, nil) 63 }) 64 65 Describe("GET /api/v1/jobs", func() { 66 var response *http.Response 67 68 JustBeforeEach(func() { 69 req, err := http.NewRequest("GET", server.URL+"/api/v1/jobs", nil) 70 Expect(err).NotTo(HaveOccurred()) 71 72 req.Header.Set("Content-Type", "application/json") 73 74 response, err = client.Do(req) 75 Expect(err).NotTo(HaveOccurred()) 76 }) 77 78 BeforeEach(func() { 79 dbJobFactory.VisibleJobsReturns([]atc.JobSummary{ 80 { 81 ID: 1, 82 Name: "some-job", 83 Paused: true, 84 PipelineID: 1, 85 PipelineName: "some-pipeline", 86 TeamName: "some-team", 87 88 Inputs: []atc.JobInputSummary{ 89 { 90 Name: "some-input", 91 Resource: "some-input", 92 Trigger: false, 93 }, 94 { 95 Name: "some-name", 96 Resource: "some-other-input", 97 Passed: []string{"a", "b"}, 98 Trigger: true, 99 }, 100 }, 101 102 NextBuild: &atc.BuildSummary{ 103 ID: 3, 104 Name: "2", 105 JobName: "some-job", 106 PipelineID: 1, 107 PipelineName: "some-pipeline", 108 TeamName: "some-team", 109 Status: "started", 110 }, 111 FinishedBuild: &atc.BuildSummary{ 112 ID: 1, 113 Name: "1", 114 JobName: "some-job", 115 PipelineID: 1, 116 PipelineName: "some-pipeline", 117 TeamName: "some-team", 118 Status: "succeeded", 119 StartTime: 1, 120 EndTime: 100, 121 }, 122 123 Groups: []string{"group-1", "group-2"}, 124 }, 125 }, nil) 126 }) 127 128 It("returns 200 OK", func() { 129 Expect(response.StatusCode).To(Equal(http.StatusOK)) 130 }) 131 132 It("returns application/json", func() { 133 expectedHeaderEntries := map[string]string{ 134 "Content-Type": "application/json", 135 } 136 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 137 }) 138 139 It("returns all jobs from public pipelines and pipelines in authenticated teams", func() { 140 body, err := ioutil.ReadAll(response.Body) 141 Expect(err).NotTo(HaveOccurred()) 142 143 Expect(body).To(MatchJSON(`[ 144 { 145 "id": 1, 146 "name": "some-job", 147 "pipeline_id": 1, 148 "pipeline_name": "some-pipeline", 149 "team_name": "some-team", 150 "paused": true, 151 "next_build": { 152 "id": 3, 153 "team_name": "some-team", 154 "name": "2", 155 "status": "started", 156 "job_name": "some-job", 157 "pipeline_id": 1, 158 "pipeline_name": "some-pipeline" 159 }, 160 "finished_build": { 161 "id": 1, 162 "team_name": "some-team", 163 "name": "1", 164 "status": "succeeded", 165 "job_name": "some-job", 166 "pipeline_id": 1, 167 "pipeline_name": "some-pipeline", 168 "start_time": 1, 169 "end_time": 100 170 }, 171 "inputs": [ 172 { 173 "name": "some-input", 174 "resource": "some-input" 175 }, 176 { 177 "name": "some-name", 178 "resource": "some-other-input", 179 "passed": [ 180 "a", 181 "b" 182 ], 183 "trigger": true 184 } 185 ], 186 "groups": ["group-1", "group-2"] 187 } 188 ]`)) 189 }) 190 191 Context("when getting the jobs fails", func() { 192 BeforeEach(func() { 193 dbJobFactory.VisibleJobsReturns(nil, errors.New("nope")) 194 }) 195 196 It("returns 500", func() { 197 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 198 }) 199 }) 200 201 Context("when there are no visible jobs", func() { 202 BeforeEach(func() { 203 dbJobFactory.VisibleJobsReturns(nil, nil) 204 }) 205 206 It("returns empty array", func() { 207 body, err := ioutil.ReadAll(response.Body) 208 Expect(err).NotTo(HaveOccurred()) 209 210 Expect(body).To(MatchJSON(`[]`)) 211 }) 212 }) 213 214 Context("when not authenticated", func() { 215 It("populates job factory with no team names", func() { 216 Expect(dbJobFactory.VisibleJobsCallCount()).To(Equal(1)) 217 Expect(dbJobFactory.VisibleJobsArgsForCall(0)).To(BeEmpty()) 218 }) 219 }) 220 221 Context("when authenticated", func() { 222 BeforeEach(func() { 223 fakeAccess.TeamNamesReturns([]string{"some-team"}) 224 }) 225 226 It("constructs job factory with provided team names", func() { 227 Expect(dbJobFactory.VisibleJobsCallCount()).To(Equal(1)) 228 Expect(dbJobFactory.VisibleJobsArgsForCall(0)).To(ContainElement("some-team")) 229 }) 230 231 Context("user has the admin privilege", func() { 232 BeforeEach(func() { 233 fakeAccess.IsAdminReturns(true) 234 }) 235 236 It("returns all jobs from public and private pipelines from unauthenticated teams", func() { 237 Expect(dbJobFactory.AllActiveJobsCallCount()).To(Equal(1)) 238 }) 239 }) 240 }) 241 }) 242 243 Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name", func() { 244 var response *http.Response 245 246 JustBeforeEach(func() { 247 var err error 248 249 response, err = client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job") 250 Expect(err).NotTo(HaveOccurred()) 251 }) 252 253 Context("when not authenticated", func() { 254 BeforeEach(func() { 255 fakeAccess.IsAuthenticatedReturns(false) 256 }) 257 258 Context("and the pipeline is private", func() { 259 BeforeEach(func() { 260 fakePipeline.PublicReturns(false) 261 }) 262 263 It("returns 401", func() { 264 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 265 }) 266 }) 267 268 Context("and the pipeline is public", func() { 269 BeforeEach(func() { 270 fakePipeline.JobReturns(fakeJob, true, nil) 271 fakePipeline.PublicReturns(true) 272 fakeJob.FinishedAndNextBuildReturns(nil, nil, nil) 273 }) 274 275 It("returns 200 OK", func() { 276 Expect(response.StatusCode).To(Equal(http.StatusOK)) 277 }) 278 }) 279 }) 280 281 Context("when authenticated and not authorized", func() { 282 BeforeEach(func() { 283 fakeAccess.IsAuthenticatedReturns(true) 284 fakeAccess.IsAuthorizedReturns(false) 285 }) 286 287 Context("and the pipeline is private", func() { 288 BeforeEach(func() { 289 fakePipeline.PublicReturns(false) 290 }) 291 292 It("returns 403", func() { 293 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 294 }) 295 }) 296 297 Context("and the pipeline is public", func() { 298 BeforeEach(func() { 299 fakePipeline.JobReturns(fakeJob, true, nil) 300 fakePipeline.PublicReturns(true) 301 fakeJob.FinishedAndNextBuildReturns(nil, nil, nil) 302 }) 303 304 It("returns 200 OK", func() { 305 Expect(response.StatusCode).To(Equal(http.StatusOK)) 306 }) 307 }) 308 }) 309 310 Context("when authenticated and authorized", func() { 311 var build1 *dbfakes.FakeBuild 312 var build2 *dbfakes.FakeBuild 313 314 BeforeEach(func() { 315 fakeAccess.IsAuthenticatedReturns(true) 316 fakeAccess.IsAuthorizedReturns(true) 317 }) 318 319 Context("when getting the build succeeds", func() { 320 BeforeEach(func() { 321 build1 = new(dbfakes.FakeBuild) 322 build1.IDReturns(1) 323 build1.NameReturns("1") 324 build1.JobNameReturns("some-job") 325 build1.PipelineIDReturns(1) 326 build1.PipelineNameReturns("some-pipeline") 327 build1.TeamNameReturns("some-team") 328 build1.StatusReturns(db.BuildStatusSucceeded) 329 build1.StartTimeReturns(time.Unix(1, 0)) 330 build1.EndTimeReturns(time.Unix(100, 0)) 331 332 build2 = new(dbfakes.FakeBuild) 333 build2.IDReturns(3) 334 build2.NameReturns("2") 335 build2.JobNameReturns("some-job") 336 build2.PipelineIDReturns(1) 337 build2.PipelineNameReturns("some-pipeline") 338 build2.TeamNameReturns("some-team") 339 build2.StatusReturns(db.BuildStatusStarted) 340 }) 341 342 Context("when getting the job fails", func() { 343 BeforeEach(func() { 344 fakePipeline.JobReturns(nil, false, errors.New("nope")) 345 }) 346 347 It("returns 500", func() { 348 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 349 }) 350 }) 351 352 Context("when getting the job succeeds", func() { 353 BeforeEach(func() { 354 fakeJob.IDReturns(1) 355 fakeJob.PausedReturns(true) 356 fakeJob.FirstLoggedBuildIDReturns(99) 357 fakeJob.PipelineIDReturns(1) 358 fakeJob.PipelineNameReturns("some-pipeline") 359 fakeJob.NameReturns("some-job") 360 fakeJob.TagsReturns([]string{"group-1", "group-2"}) 361 fakeJob.FinishedAndNextBuildReturns(build1, build2, nil) 362 363 fakePipeline.JobReturns(fakeJob, true, nil) 364 }) 365 366 It("fetches the inputs", func() { 367 Expect(fakeJob.InputsCallCount()).To(Equal(1)) 368 }) 369 370 Context("when getting the inputs fails", func() { 371 BeforeEach(func() { 372 fakeJob.InputsReturns(nil, errors.New("nope")) 373 }) 374 375 It("returns 500", func() { 376 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 377 }) 378 }) 379 380 Context("when getting the inputs succeeds", func() { 381 BeforeEach(func() { 382 fakeJob.InputsReturns([]atc.JobInput{ 383 { 384 Name: "some-input", 385 Resource: "some-input", 386 }, 387 { 388 Name: "some-name", 389 Resource: "some-other-input", 390 Passed: []string{"a", "b"}, 391 Trigger: true, 392 }, 393 }, nil) 394 }) 395 396 It("fetches the outputs", func() { 397 Expect(fakeJob.OutputsCallCount()).To(Equal(1)) 398 }) 399 400 Context("when getting the outputs fails", func() { 401 BeforeEach(func() { 402 fakeJob.OutputsReturns(nil, errors.New("nope")) 403 }) 404 405 It("returns 500", func() { 406 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 407 }) 408 }) 409 410 Context("when getting the outputs succeeds", func() { 411 BeforeEach(func() { 412 fakeJob.OutputsReturns([]atc.JobOutput{ 413 { 414 Name: "some-output", 415 Resource: "some-output", 416 }, 417 { 418 Name: "some-other-output", 419 Resource: "some-other-output", 420 }, 421 }, nil) 422 }) 423 424 It("fetches by job", func() { 425 Expect(fakeJob.FinishedAndNextBuildCallCount()).To(Equal(1)) 426 }) 427 428 It("returns 200 OK", func() { 429 Expect(response.StatusCode).To(Equal(http.StatusOK)) 430 }) 431 432 It("returns Content-Type 'application/json'", func() { 433 expectedHeaderEntries := map[string]string{ 434 "Content-Type": "application/json", 435 } 436 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 437 }) 438 439 It("returns the job's name, if it's paused, and any running and finished builds", func() { 440 body, err := ioutil.ReadAll(response.Body) 441 Expect(err).NotTo(HaveOccurred()) 442 443 Expect(body).To(MatchJSON(`{ 444 "id": 1, 445 "name": "some-job", 446 "pipeline_id": 1, 447 "pipeline_name": "some-pipeline", 448 "team_name": "some-team", 449 "paused": true, 450 "first_logged_build_id": 99, 451 "next_build": { 452 "id": 3, 453 "name": "2", 454 "job_name": "some-job", 455 "status": "started", 456 "api_url": "/api/v1/builds/3", 457 "pipeline_id": 1, 458 "pipeline_name": "some-pipeline", 459 "team_name": "some-team" 460 }, 461 "finished_build": { 462 "id": 1, 463 "name": "1", 464 "job_name": "some-job", 465 "status": "succeeded", 466 "api_url": "/api/v1/builds/1", 467 "pipeline_id": 1, 468 "pipeline_name": "some-pipeline", 469 "team_name": "some-team", 470 "start_time": 1, 471 "end_time": 100 472 }, 473 "inputs": [ 474 { 475 "name": "some-input", 476 "resource": "some-input", 477 "trigger": false 478 }, 479 { 480 "name": "some-name", 481 "resource": "some-other-input", 482 "passed": ["a", "b"], 483 "trigger": true 484 } 485 ], 486 "outputs": [ 487 { 488 "name": "some-output", 489 "resource": "some-output" 490 }, 491 { 492 "name": "some-other-output", 493 "resource": "some-other-output" 494 } 495 ], 496 "groups": ["group-1", "group-2"] 497 }`)) 498 499 }) 500 501 Context("when there are no running or finished builds", func() { 502 BeforeEach(func() { 503 fakeJob.FinishedAndNextBuildReturns(nil, nil, nil) 504 }) 505 506 It("returns null as their entries", func() { 507 var job atc.Job 508 err := json.NewDecoder(response.Body).Decode(&job) 509 Expect(err).NotTo(HaveOccurred()) 510 511 Expect(job.NextBuild).To(BeNil()) 512 Expect(job.FinishedBuild).To(BeNil()) 513 }) 514 }) 515 516 Context("when getting the job's builds fails", func() { 517 BeforeEach(func() { 518 fakeJob.FinishedAndNextBuildReturns(nil, nil, errors.New("oh no!")) 519 }) 520 521 It("returns 500", func() { 522 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 523 }) 524 }) 525 }) 526 }) 527 }) 528 }) 529 530 Context("when the job is not found", func() { 531 BeforeEach(func() { 532 fakePipeline.JobReturns(nil, false, nil) 533 }) 534 535 It("returns 404", func() { 536 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 537 }) 538 }) 539 }) 540 }) 541 542 Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/badge", func() { 543 var response *http.Response 544 545 JustBeforeEach(func() { 546 var err error 547 548 response, err = client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/badge") 549 Expect(err).NotTo(HaveOccurred()) 550 }) 551 552 Context("when authenticated and not authorized", func() { 553 BeforeEach(func() { 554 fakeAccess.IsAuthenticatedReturns(true) 555 fakeAccess.IsAuthorizedReturns(false) 556 }) 557 558 Context("and the pipeline is private", func() { 559 BeforeEach(func() { 560 fakePipeline.PublicReturns(false) 561 }) 562 563 It("returns 403", func() { 564 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 565 }) 566 }) 567 568 Context("and the pipeline is public", func() { 569 BeforeEach(func() { 570 fakePipeline.PublicReturns(true) 571 fakePipeline.JobReturns(fakeJob, true, nil) 572 }) 573 574 It("returns 200 OK", func() { 575 Expect(response.StatusCode).To(Equal(http.StatusOK)) 576 }) 577 }) 578 }) 579 580 Context("when authorized", func() { 581 BeforeEach(func() { 582 fakeAccess.IsAuthenticatedReturns(true) 583 fakeAccess.IsAuthorizedReturns(true) 584 585 fakePipeline.JobReturns(fakeJob, true, nil) 586 fakeJob.NameReturns("some-job") 587 }) 588 589 It("fetches by job", func() { 590 Expect(fakeJob.FinishedAndNextBuildCallCount()).To(Equal(1)) 591 }) 592 593 It("returns 200 OK", func() { 594 Expect(response.StatusCode).To(Equal(http.StatusOK)) 595 }) 596 597 It("returns Content-Type as image/svg+xml and disables caching", func() { 598 expectedHeaderEntries := map[string]string{ 599 "Content-Type": "image/svg+xml", 600 "Cache-Control": "no-cache, no-store, must-revalidate", 601 "Expires": "0", 602 } 603 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 604 }) 605 606 Context("when generates bagde title", func() { 607 It("uses url `title` parameter", func() { 608 response, err := client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/badge?title=cov") 609 Expect(err).NotTo(HaveOccurred()) 610 611 body, err := ioutil.ReadAll(response.Body) 612 Expect(err).NotTo(HaveOccurred()) 613 614 Expect(strings.Contains(string(body), ` 615 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">cov</text> 616 <text x="18.5" y="14">cov</text> 617 `)).Should(BeTrue()) 618 }) 619 It("uses default `build` title if not specified", func() { 620 response, err := client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/badge") 621 Expect(err).NotTo(HaveOccurred()) 622 623 body, err := ioutil.ReadAll(response.Body) 624 Expect(err).NotTo(HaveOccurred()) 625 626 Expect(strings.Contains(string(body), ` 627 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text> 628 <text x="18.5" y="14">build</text> 629 `)).Should(BeTrue()) 630 }) 631 It("uses default `build` title if url `title` parameter is falsy", func() { 632 response, err := client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/badge?title=") 633 Expect(err).NotTo(HaveOccurred()) 634 635 body, err := ioutil.ReadAll(response.Body) 636 Expect(err).NotTo(HaveOccurred()) 637 638 Expect(strings.Contains(string(body), ` 639 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text> 640 <text x="18.5" y="14">build</text> 641 `)).Should(BeTrue()) 642 }) 643 It("html escapes title", func() { 644 response, err := client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/badge?title=%24cov") 645 Expect(err).NotTo(HaveOccurred()) 646 647 body, err := ioutil.ReadAll(response.Body) 648 Expect(err).NotTo(HaveOccurred()) 649 650 Expect(strings.Contains(string(body), ` 651 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">$cov</text> 652 <text x="18.5" y="14">$cov</text> 653 `)).Should(BeTrue()) 654 }) 655 }) 656 657 Context("when the finished build is successful", func() { 658 BeforeEach(func() { 659 build1 := new(dbfakes.FakeBuild) 660 build1.IDReturns(1) 661 build1.NameReturns("1") 662 build1.JobNameReturns("some-job") 663 build1.PipelineNameReturns("some-pipeline") 664 build1.StartTimeReturns(time.Unix(1, 0)) 665 build1.EndTimeReturns(time.Unix(100, 0)) 666 build1.StatusReturns(db.BuildStatusSucceeded) 667 668 build2 := new(dbfakes.FakeBuild) 669 build2.IDReturns(3) 670 build2.NameReturns("2") 671 build2.JobNameReturns("some-job") 672 build2.PipelineNameReturns("some-pipeline") 673 build2.StatusReturns(db.BuildStatusStarted) 674 675 fakeJob.FinishedAndNextBuildReturns(build1, build2, nil) 676 }) 677 678 It("returns 200 OK", func() { 679 Expect(response.StatusCode).To(Equal(http.StatusOK)) 680 }) 681 682 It("returns some SVG showing that the job is successful", func() { 683 body, err := ioutil.ReadAll(response.Body) 684 Expect(err).NotTo(HaveOccurred()) 685 686 Expect(string(body)).To(Equal(`<?xml version="1.0" encoding="UTF-8"?> 687 <svg xmlns="http://www.w3.org/2000/svg" width="88" height="20"> 688 <linearGradient id="b" x2="0" y2="100%"> 689 <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> 690 <stop offset="1" stop-opacity=".1" /> 691 </linearGradient> 692 <mask id="a"> 693 <rect width="88" height="20" rx="3" fill="#fff" /> 694 </mask> 695 <g mask="url(#a)"> 696 <path fill="#555" d="M0 0h37v20H0z" /> 697 <path fill="#44cc11" d="M37 0h51v20H37z" /> 698 <path fill="url(#b)" d="M0 0h88v20H0z" /> 699 </g> 700 <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> 701 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text> 702 <text x="18.5" y="14">build</text> 703 <text x="61.5" y="15" fill="#010101" fill-opacity=".3">passing</text> 704 <text x="61.5" y="14">passing</text> 705 </g> 706 </svg>`)) 707 }) 708 }) 709 710 Context("when the finished build is failed", func() { 711 BeforeEach(func() { 712 build1 := new(dbfakes.FakeBuild) 713 build1.IDReturns(1) 714 build1.NameReturns("1") 715 build1.JobNameReturns("some-job") 716 build1.PipelineNameReturns("some-pipeline") 717 build1.StartTimeReturns(time.Unix(1, 0)) 718 build1.EndTimeReturns(time.Unix(100, 0)) 719 build1.StatusReturns(db.BuildStatusFailed) 720 721 build2 := new(dbfakes.FakeBuild) 722 build2.IDReturns(3) 723 build2.NameReturns("2") 724 build2.JobNameReturns("some-job") 725 build2.PipelineNameReturns("some-pipeline") 726 build2.StatusReturns(db.BuildStatusStarted) 727 728 fakeJob.FinishedAndNextBuildReturns(build1, build2, nil) 729 }) 730 731 It("returns 200 OK", func() { 732 Expect(response.StatusCode).To(Equal(http.StatusOK)) 733 }) 734 735 It("returns some SVG showing that the job has failed", func() { 736 body, err := ioutil.ReadAll(response.Body) 737 Expect(err).NotTo(HaveOccurred()) 738 739 Expect(string(body)).To(Equal(`<?xml version="1.0" encoding="UTF-8"?> 740 <svg xmlns="http://www.w3.org/2000/svg" width="80" height="20"> 741 <linearGradient id="b" x2="0" y2="100%"> 742 <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> 743 <stop offset="1" stop-opacity=".1" /> 744 </linearGradient> 745 <mask id="a"> 746 <rect width="80" height="20" rx="3" fill="#fff" /> 747 </mask> 748 <g mask="url(#a)"> 749 <path fill="#555" d="M0 0h37v20H0z" /> 750 <path fill="#e05d44" d="M37 0h43v20H37z" /> 751 <path fill="url(#b)" d="M0 0h80v20H0z" /> 752 </g> 753 <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> 754 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text> 755 <text x="18.5" y="14">build</text> 756 <text x="57.5" y="15" fill="#010101" fill-opacity=".3">failing</text> 757 <text x="57.5" y="14">failing</text> 758 </g> 759 </svg>`)) 760 }) 761 }) 762 763 Context("when the finished build was aborted", func() { 764 BeforeEach(func() { 765 build1 := new(dbfakes.FakeBuild) 766 build1.IDReturns(1) 767 build1.NameReturns("1") 768 build1.JobNameReturns("some-job") 769 build1.PipelineNameReturns("some-pipeline") 770 build1.StatusReturns(db.BuildStatusAborted) 771 772 build2 := new(dbfakes.FakeBuild) 773 build2.IDReturns(1) 774 build2.NameReturns("1") 775 build2.JobNameReturns("some-job") 776 build2.PipelineNameReturns("some-pipeline") 777 build2.StartTimeReturns(time.Unix(1, 0)) 778 build2.EndTimeReturns(time.Unix(100, 0)) 779 build2.StatusReturns(db.BuildStatusAborted) 780 781 fakeJob.FinishedAndNextBuildReturns(build1, build2, nil) 782 }) 783 784 It("returns 200 OK", func() { 785 Expect(response.StatusCode).To(Equal(http.StatusOK)) 786 }) 787 788 It("returns some SVG showing that the job was aborted", func() { 789 body, err := ioutil.ReadAll(response.Body) 790 Expect(err).NotTo(HaveOccurred()) 791 792 Expect(string(body)).To(Equal(`<?xml version="1.0" encoding="UTF-8"?> 793 <svg xmlns="http://www.w3.org/2000/svg" width="90" height="20"> 794 <linearGradient id="b" x2="0" y2="100%"> 795 <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> 796 <stop offset="1" stop-opacity=".1" /> 797 </linearGradient> 798 <mask id="a"> 799 <rect width="90" height="20" rx="3" fill="#fff" /> 800 </mask> 801 <g mask="url(#a)"> 802 <path fill="#555" d="M0 0h37v20H0z" /> 803 <path fill="#8f4b2d" d="M37 0h53v20H37z" /> 804 <path fill="url(#b)" d="M0 0h90v20H0z" /> 805 </g> 806 <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> 807 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text> 808 <text x="18.5" y="14">build</text> 809 <text x="62.5" y="15" fill="#010101" fill-opacity=".3">aborted</text> 810 <text x="62.5" y="14">aborted</text> 811 </g> 812 </svg>`)) 813 }) 814 }) 815 816 Context("when the finished build errored", func() { 817 BeforeEach(func() { 818 build1 := new(dbfakes.FakeBuild) 819 build1.IDReturns(1) 820 build1.NameReturns("1") 821 build1.JobNameReturns("some-job") 822 build1.PipelineNameReturns("some-pipeline") 823 build1.StartTimeReturns(time.Unix(1, 0)) 824 build1.EndTimeReturns(time.Unix(100, 0)) 825 build1.StatusReturns(db.BuildStatusErrored) 826 827 build2 := new(dbfakes.FakeBuild) 828 build2.IDReturns(3) 829 build2.NameReturns("2") 830 build2.JobNameReturns("some-job") 831 build2.PipelineNameReturns("some-pipeline") 832 build2.StatusReturns(db.BuildStatusStarted) 833 834 fakeJob.FinishedAndNextBuildReturns(build1, build2, nil) 835 }) 836 837 It("returns 200 OK", func() { 838 Expect(response.StatusCode).To(Equal(http.StatusOK)) 839 }) 840 841 It("returns some SVG showing that the job has errored", func() { 842 body, err := ioutil.ReadAll(response.Body) 843 Expect(err).NotTo(HaveOccurred()) 844 845 Expect(string(body)).To(Equal(`<?xml version="1.0" encoding="UTF-8"?> 846 <svg xmlns="http://www.w3.org/2000/svg" width="88" height="20"> 847 <linearGradient id="b" x2="0" y2="100%"> 848 <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> 849 <stop offset="1" stop-opacity=".1" /> 850 </linearGradient> 851 <mask id="a"> 852 <rect width="88" height="20" rx="3" fill="#fff" /> 853 </mask> 854 <g mask="url(#a)"> 855 <path fill="#555" d="M0 0h37v20H0z" /> 856 <path fill="#fe7d37" d="M37 0h51v20H37z" /> 857 <path fill="url(#b)" d="M0 0h88v20H0z" /> 858 </g> 859 <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> 860 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text> 861 <text x="18.5" y="14">build</text> 862 <text x="61.5" y="15" fill="#010101" fill-opacity=".3">errored</text> 863 <text x="61.5" y="14">errored</text> 864 </g> 865 </svg>`)) 866 }) 867 }) 868 869 Context("when there are no running or finished builds", func() { 870 BeforeEach(func() { 871 fakeJob.FinishedAndNextBuildReturns(nil, nil, nil) 872 }) 873 874 It("returns an unknown badge", func() { 875 body, err := ioutil.ReadAll(response.Body) 876 Expect(err).NotTo(HaveOccurred()) 877 Expect(string(body)).To(Equal(`<?xml version="1.0" encoding="UTF-8"?> 878 <svg xmlns="http://www.w3.org/2000/svg" width="98" height="20"> 879 <linearGradient id="b" x2="0" y2="100%"> 880 <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> 881 <stop offset="1" stop-opacity=".1" /> 882 </linearGradient> 883 <mask id="a"> 884 <rect width="98" height="20" rx="3" fill="#fff" /> 885 </mask> 886 <g mask="url(#a)"> 887 <path fill="#555" d="M0 0h37v20H0z" /> 888 <path fill="#9f9f9f" d="M37 0h61v20H37z" /> 889 <path fill="url(#b)" d="M0 0h98v20H0z" /> 890 </g> 891 <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> 892 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">build</text> 893 <text x="18.5" y="14">build</text> 894 <text x="66.5" y="15" fill="#010101" fill-opacity=".3">unknown</text> 895 <text x="66.5" y="14">unknown</text> 896 </g> 897 </svg>`)) 898 }) 899 }) 900 901 Context("when getting the job's builds fails", func() { 902 BeforeEach(func() { 903 fakeJob.FinishedAndNextBuildReturns(nil, nil, errors.New("oh no!")) 904 }) 905 906 It("returns 500", func() { 907 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 908 }) 909 }) 910 911 Context("when the job is not present", func() { 912 BeforeEach(func() { 913 fakePipeline.JobReturns(nil, false, nil) 914 }) 915 916 It("returns 404", func() { 917 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 918 }) 919 }) 920 }) 921 }) 922 923 Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs", func() { 924 var response *http.Response 925 var dashboardResponse []atc.JobSummary 926 927 JustBeforeEach(func() { 928 var err error 929 930 response, err = client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs") 931 Expect(err).NotTo(HaveOccurred()) 932 }) 933 934 Context("when getting the dashboard succeeds", func() { 935 936 BeforeEach(func() { 937 938 dashboardResponse = []atc.JobSummary{ 939 { 940 ID: 1, 941 Name: "job-1", 942 PipelineID: 2, 943 PipelineName: "another-pipeline", 944 PipelineInstanceVars: atc.InstanceVars{"branch": "master"}, 945 TeamName: "some-team", 946 Paused: true, 947 NextBuild: &atc.BuildSummary{ 948 ID: 3, 949 Name: "2", 950 JobName: "job-1", 951 PipelineID: 2, 952 PipelineName: "another-pipeline", 953 PipelineInstanceVars: atc.InstanceVars{"branch": "master"}, 954 TeamName: "some-team", 955 Status: "started", 956 }, 957 FinishedBuild: &atc.BuildSummary{ 958 ID: 1, 959 Name: "1", 960 JobName: "job-1", 961 PipelineID: 2, 962 PipelineName: "another-pipeline", 963 PipelineInstanceVars: atc.InstanceVars{"branch": "master"}, 964 TeamName: "some-team", 965 Status: "succeeded", 966 StartTime: 1, 967 EndTime: 100, 968 }, 969 TransitionBuild: &atc.BuildSummary{ 970 ID: 5, 971 Name: "five", 972 JobName: "job-1", 973 PipelineID: 2, 974 PipelineName: "another-pipeline", 975 PipelineInstanceVars: atc.InstanceVars{"branch": "master"}, 976 TeamName: "some-team", 977 Status: "failed", 978 StartTime: 101, 979 EndTime: 200, 980 }, 981 Inputs: []atc.JobInputSummary{ 982 { 983 Name: "input-1", 984 Resource: "input-1", 985 }, 986 }, 987 Groups: []string{ 988 "group-1", "group-2", 989 }, 990 }, 991 { 992 ID: 2, 993 Name: "job-2", 994 PipelineID: 2, 995 PipelineName: "another-pipeline", 996 PipelineInstanceVars: atc.InstanceVars{"branch": "master"}, 997 TeamName: "some-team", 998 Paused: true, 999 NextBuild: nil, 1000 FinishedBuild: &atc.BuildSummary{ 1001 ID: 4, 1002 Name: "1", 1003 JobName: "job-2", 1004 PipelineID: 2, 1005 PipelineName: "another-pipeline", 1006 PipelineInstanceVars: atc.InstanceVars{"branch": "master"}, 1007 TeamName: "some-team", 1008 Status: "succeeded", 1009 StartTime: 101, 1010 EndTime: 200, 1011 }, 1012 TransitionBuild: nil, 1013 Inputs: []atc.JobInputSummary{ 1014 { 1015 Name: "input-2", 1016 Resource: "input-2", 1017 }, 1018 }, 1019 Groups: []string{ 1020 "group-2", 1021 }, 1022 }, 1023 { 1024 ID: 3, 1025 Name: "job-3", 1026 PipelineID: 2, 1027 PipelineName: "another-pipeline", 1028 PipelineInstanceVars: atc.InstanceVars{"branch": "master"}, 1029 TeamName: "some-team", 1030 Paused: true, 1031 NextBuild: nil, 1032 FinishedBuild: nil, 1033 TransitionBuild: nil, 1034 Inputs: []atc.JobInputSummary{ 1035 { 1036 Name: "input-3", 1037 Resource: "input-3", 1038 }, 1039 }, 1040 Groups: []string{}, 1041 }, 1042 } 1043 fakePipeline.DashboardReturns(dashboardResponse, nil) 1044 }) 1045 1046 Context("when not authorized", func() { 1047 BeforeEach(func() { 1048 fakeAccess.IsAuthorizedReturns(false) 1049 }) 1050 1051 Context("when not authenticated", func() { 1052 BeforeEach(func() { 1053 fakeAccess.IsAuthenticatedReturns(false) 1054 }) 1055 1056 Context("and the pipeline is private", func() { 1057 BeforeEach(func() { 1058 fakePipeline.PublicReturns(false) 1059 }) 1060 1061 It("returns 401", func() { 1062 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1063 }) 1064 }) 1065 1066 Context("and the pipeline is public", func() { 1067 BeforeEach(func() { 1068 fakePipeline.PublicReturns(true) 1069 }) 1070 1071 It("returns 200 OK", func() { 1072 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1073 }) 1074 }) 1075 }) 1076 }) 1077 1078 Context("when authorized", func() { 1079 BeforeEach(func() { 1080 fakeAccess.IsAuthorizedReturns(true) 1081 fakeAccess.IsAuthenticatedReturns(true) 1082 }) 1083 1084 It("returns 200 OK", func() { 1085 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1086 }) 1087 1088 It("returns Content-Type 'application/json'", func() { 1089 expectedHeaderEntries := map[string]string{ 1090 "Content-Type": "application/json", 1091 } 1092 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 1093 }) 1094 1095 It("returns each job's name and any running and finished builds", func() { 1096 body, err := ioutil.ReadAll(response.Body) 1097 Expect(err).NotTo(HaveOccurred()) 1098 1099 Expect(body).To(MatchJSON(`[ 1100 { 1101 "id": 1, 1102 "name": "job-1", 1103 "pipeline_id": 2, 1104 "pipeline_name": "another-pipeline", 1105 "pipeline_instance_vars": { 1106 "branch": "master" 1107 }, 1108 "team_name": "some-team", 1109 "paused": true, 1110 "next_build": { 1111 "id": 3, 1112 "name": "2", 1113 "job_name": "job-1", 1114 "status": "started", 1115 "pipeline_id": 2, 1116 "pipeline_name": "another-pipeline", 1117 "pipeline_instance_vars": { 1118 "branch": "master" 1119 }, 1120 "team_name": "some-team" 1121 }, 1122 "finished_build": { 1123 "id": 1, 1124 "name": "1", 1125 "job_name": "job-1", 1126 "status": "succeeded", 1127 "pipeline_id": 2, 1128 "pipeline_name": "another-pipeline", 1129 "pipeline_instance_vars": { 1130 "branch": "master" 1131 }, 1132 "team_name": "some-team", 1133 "start_time": 1, 1134 "end_time": 100 1135 }, 1136 "transition_build": { 1137 "id": 5, 1138 "name": "five", 1139 "job_name": "job-1", 1140 "status": "failed", 1141 "pipeline_id": 2, 1142 "pipeline_name": "another-pipeline", 1143 "pipeline_instance_vars": { 1144 "branch": "master" 1145 }, 1146 "team_name": "some-team", 1147 "start_time": 101, 1148 "end_time": 200 1149 }, 1150 "inputs": [{"name": "input-1", "resource": "input-1"}], 1151 "groups": ["group-1", "group-2"] 1152 }, 1153 { 1154 "id": 2, 1155 "name": "job-2", 1156 "pipeline_id": 2, 1157 "pipeline_name": "another-pipeline", 1158 "pipeline_instance_vars": { 1159 "branch": "master" 1160 }, 1161 "team_name": "some-team", 1162 "paused": true, 1163 "finished_build": { 1164 "id": 4, 1165 "name": "1", 1166 "job_name": "job-2", 1167 "status": "succeeded", 1168 "pipeline_id": 2, 1169 "pipeline_name": "another-pipeline", 1170 "pipeline_instance_vars": { 1171 "branch": "master" 1172 }, 1173 "team_name": "some-team", 1174 "start_time": 101, 1175 "end_time": 200 1176 }, 1177 "inputs": [{"name": "input-2", "resource": "input-2"}], 1178 "groups": ["group-2"] 1179 }, 1180 { 1181 "id": 3, 1182 "name": "job-3", 1183 "pipeline_id": 2, 1184 "pipeline_name": "another-pipeline", 1185 "pipeline_instance_vars": { 1186 "branch": "master" 1187 }, 1188 "team_name": "some-team", 1189 "paused": true, 1190 "inputs": [{"name": "input-3", "resource": "input-3"}] 1191 } 1192 ]`)) 1193 }) 1194 1195 Context("when there are no jobs in dashboard", func() { 1196 BeforeEach(func() { 1197 dashboardResponse = []atc.JobSummary{} 1198 fakePipeline.DashboardReturns(dashboardResponse, nil) 1199 }) 1200 It("should return an empty array", func() { 1201 body, err := ioutil.ReadAll(response.Body) 1202 Expect(err).NotTo(HaveOccurred()) 1203 1204 Expect(body).To(MatchJSON(`[]`)) 1205 }) 1206 }) 1207 1208 Context("when getting the dashboard fails", func() { 1209 Context("with an unknown error", func() { 1210 BeforeEach(func() { 1211 fakePipeline.DashboardReturns(nil, errors.New("oh no!")) 1212 }) 1213 1214 It("returns 500", func() { 1215 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1216 }) 1217 }) 1218 }) 1219 }) 1220 }) 1221 }) 1222 1223 Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds", func() { 1224 var response *http.Response 1225 var queryParams string 1226 1227 JustBeforeEach(func() { 1228 var err error 1229 1230 fakePipeline.NameReturns("some-pipeline") 1231 response, err = client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/builds" + queryParams) 1232 Expect(err).NotTo(HaveOccurred()) 1233 }) 1234 1235 Context("when authenticated and not authorized", func() { 1236 BeforeEach(func() { 1237 fakeAccess.IsAuthorizedReturns(false) 1238 fakeAccess.IsAuthenticatedReturns(true) 1239 }) 1240 1241 Context("and the pipeline is private", func() { 1242 BeforeEach(func() { 1243 fakePipeline.PublicReturns(false) 1244 }) 1245 1246 It("returns 403", func() { 1247 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 1248 }) 1249 }) 1250 1251 Context("and the pipeline is public", func() { 1252 BeforeEach(func() { 1253 fakeBuild := new(dbfakes.FakeBuild) 1254 1255 fakePipeline.PublicReturns(true) 1256 fakePipeline.JobReturns(fakeJob, true, nil) 1257 fakeJob.BuildReturns(fakeBuild, true, nil) 1258 }) 1259 1260 It("returns 200 OK", func() { 1261 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1262 }) 1263 }) 1264 }) 1265 1266 Context("when authorized", func() { 1267 BeforeEach(func() { 1268 fakeAccess.IsAuthorizedReturns(true) 1269 }) 1270 1271 Context("when getting the job succeeds", func() { 1272 BeforeEach(func() { 1273 fakeJob.NameReturns("some-job") 1274 fakePipeline.JobReturns(fakeJob, true, nil) 1275 }) 1276 1277 Context("when no params are passed", func() { 1278 It("does not set defaults for since and until", func() { 1279 Expect(fakeJob.BuildsCallCount()).To(Equal(1)) 1280 1281 page := fakeJob.BuildsArgsForCall(0) 1282 Expect(page).To(Equal(db.Page{ 1283 Limit: 100, 1284 })) 1285 }) 1286 }) 1287 1288 Context("when all the params are passed", func() { 1289 BeforeEach(func() { 1290 queryParams = "?from=2&to=3&limit=8" 1291 }) 1292 1293 It("passes them through", func() { 1294 Expect(fakeJob.BuildsCallCount()).To(Equal(1)) 1295 1296 page := fakeJob.BuildsArgsForCall(0) 1297 Expect(page).To(Equal(db.Page{ 1298 From: db.NewIntPtr(2), 1299 To: db.NewIntPtr(3), 1300 Limit: 8, 1301 })) 1302 }) 1303 }) 1304 1305 Context("when getting the builds succeeds", func() { 1306 var returnedBuilds []db.Build 1307 1308 BeforeEach(func() { 1309 queryParams = "?since=5&limit=2" 1310 1311 build1 := new(dbfakes.FakeBuild) 1312 build1.IDReturns(4) 1313 build1.NameReturns("2") 1314 build1.JobNameReturns("some-job") 1315 build1.PipelineNameReturns("some-pipeline") 1316 build1.TeamNameReturns("some-team") 1317 build1.StatusReturns(db.BuildStatusStarted) 1318 build1.StartTimeReturns(time.Unix(1, 0)) 1319 build1.EndTimeReturns(time.Unix(100, 0)) 1320 1321 build2 := new(dbfakes.FakeBuild) 1322 build2.IDReturns(2) 1323 build2.NameReturns("1") 1324 build2.JobNameReturns("some-job") 1325 build2.PipelineNameReturns("some-pipeline") 1326 build2.TeamNameReturns("some-team") 1327 build2.StatusReturns(db.BuildStatusSucceeded) 1328 build2.StartTimeReturns(time.Unix(101, 0)) 1329 build2.EndTimeReturns(time.Unix(200, 0)) 1330 1331 build3 := new(dbfakes.FakeBuild) 1332 build3.IDReturns(3) 1333 build3.NameReturns("1.1") 1334 build3.JobNameReturns("some-job") 1335 build3.PipelineNameReturns("some-pipeline") 1336 build3.TeamNameReturns("some-team") 1337 build3.StatusReturns(db.BuildStatusSucceeded) 1338 build3.StartTimeReturns(time.Unix(102, 0)) 1339 build3.EndTimeReturns(time.Unix(300, 0)) 1340 build3.RerunOfReturns(2) 1341 build3.RerunOfNameReturns("1") 1342 build3.RerunNumberReturns(3) 1343 1344 returnedBuilds = []db.Build{build1, build2, build3} 1345 fakeJob.BuildsReturns(returnedBuilds, db.Pagination{}, nil) 1346 }) 1347 1348 It("returns 200 OK", func() { 1349 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1350 }) 1351 1352 It("returns Content-Type 'application/json'", func() { 1353 expectedHeaderEntries := map[string]string{ 1354 "Content-Type": "application/json", 1355 } 1356 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 1357 }) 1358 1359 It("returns the builds", func() { 1360 body, err := ioutil.ReadAll(response.Body) 1361 Expect(err).NotTo(HaveOccurred()) 1362 1363 Expect(body).To(MatchJSON(`[ 1364 { 1365 "id": 4, 1366 "name": "2", 1367 "job_name": "some-job", 1368 "status": "started", 1369 "api_url": "/api/v1/builds/4", 1370 "pipeline_name":"some-pipeline", 1371 "team_name": "some-team", 1372 "start_time": 1, 1373 "end_time": 100 1374 }, 1375 { 1376 "id": 2, 1377 "name": "1", 1378 "job_name": "some-job", 1379 "status": "succeeded", 1380 "api_url": "/api/v1/builds/2", 1381 "pipeline_name": "some-pipeline", 1382 "team_name": "some-team", 1383 "start_time": 101, 1384 "end_time": 200 1385 }, 1386 { 1387 "id": 3, 1388 "name": "1.1", 1389 "job_name": "some-job", 1390 "status": "succeeded", 1391 "api_url": "/api/v1/builds/3", 1392 "pipeline_name": "some-pipeline", 1393 "team_name": "some-team", 1394 "start_time": 102, 1395 "end_time": 300, 1396 "rerun_of": { 1397 "id": 2, 1398 "name": "1" 1399 }, 1400 "rerun_number": 3 1401 } 1402 ]`)) 1403 }) 1404 1405 Context("when next/previous pages are available", func() { 1406 BeforeEach(func() { 1407 fakeJob.BuildsReturns(returnedBuilds, db.Pagination{ 1408 Newer: &db.Page{From: db.NewIntPtr(4), Limit: 2}, 1409 Older: &db.Page{To: db.NewIntPtr(2), Limit: 2}, 1410 }, nil) 1411 }) 1412 1413 It("returns Link headers per rfc5988", func() { 1414 Expect(response.Header["Link"]).To(ConsistOf([]string{ 1415 fmt.Sprintf(`<%s/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/builds?from=4&limit=2>; rel="previous"`, externalURL), 1416 fmt.Sprintf(`<%s/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/builds?to=2&limit=2>; rel="next"`, externalURL), 1417 })) 1418 }) 1419 1420 Context("and builds are on instanced pipeline", func() { 1421 BeforeEach(func() { 1422 fakePipeline.InstanceVarsReturns(atc.InstanceVars{"branch": "master"}) 1423 }) 1424 1425 It("returns Link headers per rfc5988", func() { 1426 link := fmt.Sprintf(`<%s/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/builds?`, externalURL) 1427 Expect(response.Header["Link"]).To(ConsistOf([]string{ 1428 link + `from=4&limit=2&instance_vars=%7B%22branch%22%3A%22master%22%7D>; rel="previous"`, 1429 link + `to=2&limit=2&instance_vars=%7B%22branch%22%3A%22master%22%7D>; rel="next"`, 1430 })) 1431 }) 1432 }) 1433 }) 1434 }) 1435 1436 Context("when getting the build fails", func() { 1437 BeforeEach(func() { 1438 fakeJob.BuildsReturns(nil, db.Pagination{}, errors.New("oh no!")) 1439 }) 1440 1441 It("returns 404 Not Found", func() { 1442 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1443 }) 1444 }) 1445 }) 1446 1447 Context("when getting the job fails", func() { 1448 BeforeEach(func() { 1449 fakePipeline.JobReturns(nil, false, errors.New("oh no!")) 1450 }) 1451 1452 It("returns 500", func() { 1453 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1454 }) 1455 }) 1456 1457 Context("when the job is not found", func() { 1458 BeforeEach(func() { 1459 fakePipeline.JobReturns(nil, false, nil) 1460 }) 1461 1462 It("returns 404 Not Found", func() { 1463 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1464 }) 1465 }) 1466 }) 1467 }) 1468 1469 Describe("POST /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds", func() { 1470 var request *http.Request 1471 var response *http.Response 1472 1473 BeforeEach(func() { 1474 var err error 1475 1476 request, err = http.NewRequest("POST", server.URL+"/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/builds", nil) 1477 Expect(err).NotTo(HaveOccurred()) 1478 }) 1479 1480 JustBeforeEach(func() { 1481 var err error 1482 1483 response, err = client.Do(request) 1484 Expect(err).NotTo(HaveOccurred()) 1485 }) 1486 1487 Context("when authorized and authenticated", func() { 1488 BeforeEach(func() { 1489 fakeAccess.IsAuthorizedReturns(true) 1490 fakeAccess.IsAuthenticatedReturns(true) 1491 }) 1492 1493 Context("when getting the job fails", func() { 1494 BeforeEach(func() { 1495 fakePipeline.JobReturns(nil, false, errors.New("errorrr")) 1496 }) 1497 1498 It("returns a 500", func() { 1499 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1500 }) 1501 }) 1502 1503 Context("when the job is not found", func() { 1504 BeforeEach(func() { 1505 fakePipeline.JobReturns(nil, false, nil) 1506 }) 1507 1508 It("returns a 404", func() { 1509 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1510 }) 1511 }) 1512 1513 Context("when getting the job succeeds", func() { 1514 BeforeEach(func() { 1515 fakeJob.NameReturns("some-job") 1516 fakePipeline.JobReturns(fakeJob, true, nil) 1517 }) 1518 1519 Context("when manual triggering is disabled", func() { 1520 BeforeEach(func() { 1521 fakeJob.DisableManualTriggerReturns(true) 1522 }) 1523 1524 It("should return 409", func() { 1525 Expect(response.StatusCode).To(Equal(http.StatusConflict)) 1526 }) 1527 1528 It("does not trigger the build", func() { 1529 Expect(fakeJob.CreateBuildCallCount()).To(Equal(0)) 1530 }) 1531 }) 1532 1533 Context("when manual triggering is enabled", func() { 1534 BeforeEach(func() { 1535 fakeJob.DisableManualTriggerReturns(false) 1536 }) 1537 1538 Context("when triggering the build fails", func() { 1539 BeforeEach(func() { 1540 fakeJob.CreateBuildReturns(nil, errors.New("nopers")) 1541 }) 1542 It("returns a 500", func() { 1543 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1544 }) 1545 }) 1546 1547 Context("when triggering the build succeeds", func() { 1548 BeforeEach(func() { 1549 build := new(dbfakes.FakeBuild) 1550 build.IDReturns(42) 1551 build.NameReturns("1") 1552 build.JobNameReturns("some-job") 1553 build.PipelineNameReturns("a-pipeline") 1554 build.TeamNameReturns("some-team") 1555 build.StatusReturns(db.BuildStatusStarted) 1556 build.StartTimeReturns(time.Unix(1, 0)) 1557 build.EndTimeReturns(time.Unix(100, 0)) 1558 1559 fakeJob.CreateBuildReturns(build, nil) 1560 }) 1561 1562 It("triggers the build", func() { 1563 Expect(fakeJob.CreateBuildCallCount()).To(Equal(1)) 1564 }) 1565 1566 Context("when finding the pipeline resources fails", func() { 1567 BeforeEach(func() { 1568 fakePipeline.ResourcesReturns(nil, errors.New("nope")) 1569 }) 1570 1571 It("returns a 500", func() { 1572 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1573 }) 1574 }) 1575 1576 Context("when finding the pipeline resources succeeds", func() { 1577 var fakeResource *dbfakes.FakeResource 1578 1579 BeforeEach(func() { 1580 fakeResource = new(dbfakes.FakeResource) 1581 fakeResource.NameReturns("some-input") 1582 fakeResource.CurrentPinnedVersionReturns(atc.Version{"some": "version"}) 1583 1584 fakePipeline.ResourcesReturns([]db.Resource{fakeResource}, nil) 1585 }) 1586 1587 Context("when finding the pipeline resource types fails", func() { 1588 BeforeEach(func() { 1589 fakePipeline.ResourceTypesReturns(nil, errors.New("nope")) 1590 }) 1591 1592 It("returns a 500", func() { 1593 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1594 }) 1595 }) 1596 1597 Context("when finding the pipeline resources types succeeds", func() { 1598 var fakeResourceType *dbfakes.FakeResourceType 1599 1600 BeforeEach(func() { 1601 fakeResourceType = new(dbfakes.FakeResourceType) 1602 fakeResourceType.NameReturns("some-input") 1603 1604 fakePipeline.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil) 1605 }) 1606 1607 It("fetches the job inputs", func() { 1608 Expect(fakeJob.InputsCallCount()).To(Equal(1)) 1609 }) 1610 1611 Context("when it fails to fetch the job inputs", func() { 1612 BeforeEach(func() { 1613 fakeJob.InputsReturns(nil, errors.New("nope")) 1614 }) 1615 1616 It("returns a 500", func() { 1617 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1618 }) 1619 }) 1620 1621 Context("when the job inputs are successfully fetched", func() { 1622 BeforeEach(func() { 1623 fakeJob.InputsReturns([]atc.JobInput{ 1624 { 1625 Name: "some-input", 1626 Resource: "some-input", 1627 }, 1628 }, nil) 1629 }) 1630 1631 It("returns 200 OK", func() { 1632 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1633 }) 1634 1635 It("returns Content-Type 'application/json'", func() { 1636 expectedHeaderEntries := map[string]string{ 1637 "Content-Type": "application/json", 1638 } 1639 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 1640 }) 1641 1642 It("creates a check for the resource", func() { 1643 Expect(dbCheckFactory.TryCreateCheckCallCount()).To(Equal(1)) 1644 }) 1645 1646 It("runs the check from the current pinned version", func() { 1647 _, _, _, fromVersion, _ := dbCheckFactory.TryCreateCheckArgsForCall(0) 1648 Expect(fromVersion).To(Equal(atc.Version{"some": "version"})) 1649 }) 1650 1651 It("returns the build", func() { 1652 body, err := ioutil.ReadAll(response.Body) 1653 Expect(err).NotTo(HaveOccurred()) 1654 1655 Expect(body).To(MatchJSON(`{ 1656 "id": 42, 1657 "name": "1", 1658 "job_name": "some-job", 1659 "status": "started", 1660 "api_url": "/api/v1/builds/42", 1661 "pipeline_name": "a-pipeline", 1662 "team_name": "some-team", 1663 "start_time": 1, 1664 "end_time": 100 1665 }`)) 1666 }) 1667 }) 1668 }) 1669 }) 1670 }) 1671 }) 1672 }) 1673 }) 1674 }) 1675 1676 Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/inputs", func() { 1677 var response *http.Response 1678 1679 JustBeforeEach(func() { 1680 var err error 1681 1682 response, err = client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/inputs") 1683 Expect(err).NotTo(HaveOccurred()) 1684 }) 1685 1686 Context("when not authenticated", func() { 1687 BeforeEach(func() { 1688 fakeAccess.IsAuthenticatedReturns(false) 1689 }) 1690 1691 It("returns 401", func() { 1692 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1693 }) 1694 }) 1695 1696 Context("when authenticated", func() { 1697 BeforeEach(func() { 1698 fakeAccess.IsAuthenticatedReturns(true) 1699 }) 1700 1701 Context("when not authorized", func() { 1702 BeforeEach(func() { 1703 fakeAccess.IsAuthorizedReturns(false) 1704 }) 1705 1706 It("returns 403", func() { 1707 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 1708 }) 1709 }) 1710 1711 Context("when authorized", func() { 1712 BeforeEach(func() { 1713 fakeAccess.IsAuthorizedReturns(true) 1714 }) 1715 1716 Context("when getting the job fails", func() { 1717 BeforeEach(func() { 1718 fakePipeline.JobReturns(nil, false, errors.New("some-error")) 1719 }) 1720 1721 It("returns 500", func() { 1722 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1723 }) 1724 }) 1725 1726 Context("when the job is not found", func() { 1727 BeforeEach(func() { 1728 fakePipeline.JobReturns(nil, false, nil) 1729 }) 1730 1731 It("returns 404", func() { 1732 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1733 }) 1734 }) 1735 1736 Context("when getting the job succeeds", func() { 1737 BeforeEach(func() { 1738 fakePipeline.JobReturns(fakeJob, true, nil) 1739 }) 1740 1741 Context("when getting the resources fails", func() { 1742 BeforeEach(func() { 1743 fakePipeline.ResourcesReturns(nil, errors.New("some-error")) 1744 }) 1745 1746 It("returns 500", func() { 1747 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1748 }) 1749 }) 1750 1751 Context("when getting the resources succeeds", func() { 1752 BeforeEach(func() { 1753 resource1 := new(dbfakes.FakeResource) 1754 resource1.IDReturns(1) 1755 resource1.NameReturns("some-resource") 1756 resource1.TypeReturns("some-type") 1757 resource1.SourceReturns(atc.Source{"some": "source"}) 1758 1759 resource2 := new(dbfakes.FakeResource) 1760 resource1.IDReturns(2) 1761 resource2.NameReturns("some-other-resource") 1762 resource2.TypeReturns("some-other-type") 1763 resource2.SourceReturns(atc.Source{"some": "other-source"}) 1764 1765 fakePipeline.ResourcesReturns([]db.Resource{resource1, resource2}, nil) 1766 }) 1767 1768 Context("when getting the input versions for the job fails", func() { 1769 BeforeEach(func() { 1770 fakeJob.GetFullNextBuildInputsReturns(nil, false, errors.New("oh no!")) 1771 }) 1772 1773 It("returns 500", func() { 1774 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1775 }) 1776 }) 1777 1778 Context("when the job has no input versions available", func() { 1779 BeforeEach(func() { 1780 fakeJob.GetFullNextBuildInputsReturns(nil, false, nil) 1781 }) 1782 1783 It("returns 404", func() { 1784 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1785 }) 1786 }) 1787 1788 Context("when the job has input versions", func() { 1789 BeforeEach(func() { 1790 inputs := []db.BuildInput{ 1791 { 1792 Name: "some-input", 1793 Version: atc.Version{"some": "version"}, 1794 ResourceID: 1, 1795 }, 1796 { 1797 Name: "some-other-input", 1798 Version: atc.Version{"some": "other-version"}, 1799 ResourceID: 2, 1800 }, 1801 } 1802 1803 fakeJob.GetFullNextBuildInputsReturns(inputs, true, nil) 1804 }) 1805 1806 It("fetches the job config", func() { 1807 Expect(fakeJob.ConfigCallCount()).To(Equal(1)) 1808 }) 1809 1810 Context("when it fails to fetch the job config", func() { 1811 BeforeEach(func() { 1812 fakeJob.ConfigReturns(atc.JobConfig{}, errors.New("nope")) 1813 }) 1814 1815 It("returns a 500", func() { 1816 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1817 }) 1818 }) 1819 1820 Context("when the job inputs are successfully fetched", func() { 1821 BeforeEach(func() { 1822 fakeJob.ConfigReturns(atc.JobConfig{ 1823 Name: "some-job", 1824 PlanSequence: []atc.Step{ 1825 { 1826 Config: &atc.GetStep{ 1827 Name: "some-input", 1828 Resource: "some-resource", 1829 Passed: []string{"job-a", "job-b"}, 1830 Params: atc.Params{"some": "params"}, 1831 }, 1832 }, 1833 { 1834 Config: &atc.GetStep{ 1835 Name: "some-other-input", 1836 Resource: "some-other-resource", 1837 Passed: []string{"job-c", "job-d"}, 1838 Params: atc.Params{"some": "other-params"}, 1839 Tags: []string{"some-tag"}, 1840 }, 1841 }, 1842 }, 1843 }, nil) 1844 }) 1845 1846 It("returns 200 OK", func() { 1847 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1848 }) 1849 1850 It("returns Content-Type 'application/json'", func() { 1851 expectedHeaderEntries := map[string]string{ 1852 "Content-Type": "application/json", 1853 } 1854 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 1855 }) 1856 1857 It("returns the inputs", func() { 1858 body, err := ioutil.ReadAll(response.Body) 1859 Expect(err).NotTo(HaveOccurred()) 1860 1861 Expect(body).To(MatchJSON(`[ 1862 { 1863 "name": "some-input", 1864 "resource": "some-resource", 1865 "type": "some-type", 1866 "source": {"some": "source"}, 1867 "version": {"some": "version"}, 1868 "params": {"some": "params"} 1869 }, 1870 { 1871 "name": "some-other-input", 1872 "resource": "some-other-resource", 1873 "type": "some-other-type", 1874 "source": {"some": "other-source"}, 1875 "version": {"some": "other-version"}, 1876 "params": {"some": "other-params"}, 1877 "tags": ["some-tag"] 1878 } 1879 ]`)) 1880 }) 1881 }) 1882 }) 1883 }) 1884 }) 1885 }) 1886 }) 1887 }) 1888 1889 Describe("GET /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds/:build_name", func() { 1890 var response *http.Response 1891 1892 JustBeforeEach(func() { 1893 var err error 1894 1895 response, err = client.Get(server.URL + "/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/builds/some-build") 1896 Expect(err).NotTo(HaveOccurred()) 1897 }) 1898 1899 Context("when authorized", func() { 1900 BeforeEach(func() { 1901 fakeAccess.IsAuthorizedReturns(true) 1902 fakeAccess.IsAuthenticatedReturns(true) 1903 }) 1904 1905 Context("when getting the job succeeds", func() { 1906 var fakeJob *dbfakes.FakeJob 1907 1908 BeforeEach(func() { 1909 fakeJob = new(dbfakes.FakeJob) 1910 fakeJob.NameReturns("some-job") 1911 fakePipeline.JobReturns(fakeJob, true, nil) 1912 }) 1913 1914 Context("when getting the build succeeds", func() { 1915 BeforeEach(func() { 1916 dbBuild := new(dbfakes.FakeBuild) 1917 dbBuild.IDReturns(1) 1918 dbBuild.NameReturns("1") 1919 dbBuild.JobNameReturns("some-job") 1920 dbBuild.PipelineNameReturns("a-pipeline") 1921 dbBuild.TeamNameReturns("some-team") 1922 dbBuild.StatusReturns(db.BuildStatusSucceeded) 1923 dbBuild.StartTimeReturns(time.Unix(1, 0)) 1924 dbBuild.EndTimeReturns(time.Unix(100, 0)) 1925 fakeJob.BuildReturns(dbBuild, true, nil) 1926 }) 1927 1928 It("returns 200 OK", func() { 1929 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1930 }) 1931 1932 It("returns Content-Type 'application/json'", func() { 1933 expectedHeaderEntries := map[string]string{ 1934 "Content-Type": "application/json", 1935 } 1936 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 1937 }) 1938 1939 It("fetches by job and build name", func() { 1940 Expect(fakeJob.BuildCallCount()).To(Equal(1)) 1941 1942 buildName := fakeJob.BuildArgsForCall(0) 1943 Expect(buildName).To(Equal("some-build")) 1944 }) 1945 1946 It("returns the build", func() { 1947 body, err := ioutil.ReadAll(response.Body) 1948 Expect(err).NotTo(HaveOccurred()) 1949 1950 Expect(body).To(MatchJSON(`{ 1951 "id": 1, 1952 "name": "1", 1953 "job_name": "some-job", 1954 "status": "succeeded", 1955 "api_url": "/api/v1/builds/1", 1956 "pipeline_name": "a-pipeline", 1957 "team_name": "some-team", 1958 "start_time": 1, 1959 "end_time": 100 1960 }`)) 1961 1962 }) 1963 }) 1964 1965 Context("when the build is not found", func() { 1966 BeforeEach(func() { 1967 fakeJob.BuildReturns(nil, false, nil) 1968 }) 1969 1970 It("returns Not Found", func() { 1971 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1972 }) 1973 }) 1974 1975 Context("when getting the build fails", func() { 1976 BeforeEach(func() { 1977 fakeJob.BuildReturns(nil, false, errors.New("oh no!")) 1978 }) 1979 1980 It("returns Internal Server Error", func() { 1981 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1982 }) 1983 }) 1984 }) 1985 1986 Context("when the job is not found", func() { 1987 BeforeEach(func() { 1988 fakePipeline.JobReturns(nil, false, nil) 1989 }) 1990 1991 It("returns Not Found", func() { 1992 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1993 }) 1994 }) 1995 1996 Context("when getting the build fails", func() { 1997 BeforeEach(func() { 1998 fakePipeline.JobReturns(nil, false, errors.New("oh no!")) 1999 }) 2000 2001 It("returns Internal Server Error", func() { 2002 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2003 }) 2004 }) 2005 }) 2006 2007 Context("when not authorized", func() { 2008 BeforeEach(func() { 2009 fakeAccess.IsAuthorizedReturns(false) 2010 }) 2011 2012 Context("and the pipeline is private", func() { 2013 BeforeEach(func() { 2014 fakePipeline.PublicReturns(false) 2015 }) 2016 2017 Context("when not authenticated", func() { 2018 BeforeEach(func() { 2019 fakeAccess.IsAuthenticatedReturns(false) 2020 }) 2021 It("returns 401", func() { 2022 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 2023 }) 2024 }) 2025 2026 Context("when authenticated", func() { 2027 BeforeEach(func() { 2028 fakeAccess.IsAuthenticatedReturns(true) 2029 }) 2030 2031 It("returns 403", func() { 2032 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 2033 }) 2034 }) 2035 }) 2036 2037 Context("and the pipeline is public", func() { 2038 BeforeEach(func() { 2039 fakeBuild := new(dbfakes.FakeBuild) 2040 fakePipeline.JobReturns(fakeJob, true, nil) 2041 fakeJob.BuildReturns(fakeBuild, true, nil) 2042 2043 fakePipeline.PublicReturns(true) 2044 }) 2045 2046 It("returns 200 OK", func() { 2047 Expect(response.StatusCode).To(Equal(http.StatusOK)) 2048 }) 2049 }) 2050 }) 2051 }) 2052 2053 Describe("POST /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds/:build_name", func() { 2054 var request *http.Request 2055 var response *http.Response 2056 2057 BeforeEach(func() { 2058 var err error 2059 2060 request, err = http.NewRequest("POST", server.URL+"/api/v1/teams/some-team/pipelines/some-pipeline/jobs/some-job/builds/some-build", nil) 2061 Expect(err).NotTo(HaveOccurred()) 2062 }) 2063 2064 JustBeforeEach(func() { 2065 var err error 2066 2067 response, err = client.Do(request) 2068 Expect(err).NotTo(HaveOccurred()) 2069 }) 2070 2071 Context("when authorized and authenticated", func() { 2072 BeforeEach(func() { 2073 fakeAccess.IsAuthorizedReturns(true) 2074 fakeAccess.IsAuthenticatedReturns(true) 2075 }) 2076 2077 Context("when getting the job fails", func() { 2078 BeforeEach(func() { 2079 fakePipeline.JobReturns(nil, false, errors.New("errorrr")) 2080 }) 2081 2082 It("returns a 500", func() { 2083 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2084 }) 2085 }) 2086 2087 Context("when the job is not found", func() { 2088 BeforeEach(func() { 2089 fakePipeline.JobReturns(nil, false, nil) 2090 }) 2091 2092 It("returns a 404", func() { 2093 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 2094 }) 2095 }) 2096 2097 Context("when getting the job succeeds", func() { 2098 BeforeEach(func() { 2099 fakeJob.NameReturns("some-job") 2100 fakePipeline.JobReturns(fakeJob, true, nil) 2101 }) 2102 2103 It("tries to get the build to rerun", func() { 2104 Expect(fakeJob.BuildCallCount()).To(Equal(1)) 2105 }) 2106 2107 Context("when getting the build to rerun fails", func() { 2108 BeforeEach(func() { 2109 fakeJob.BuildReturns(nil, false, errors.New("oops")) 2110 }) 2111 2112 It("returns a 500", func() { 2113 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2114 }) 2115 }) 2116 2117 Context("when the build to rerun is not found", func() { 2118 BeforeEach(func() { 2119 fakeJob.BuildReturns(nil, false, nil) 2120 }) 2121 2122 It("returns a 404", func() { 2123 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 2124 }) 2125 }) 2126 2127 Context("when getting the build to rerun succeeds", func() { 2128 var fakeBuild *dbfakes.FakeBuild 2129 BeforeEach(func() { 2130 fakeBuild = new(dbfakes.FakeBuild) 2131 fakeBuild.IDReturns(1) 2132 fakeBuild.NameReturns("1") 2133 fakeBuild.JobNameReturns("some-job") 2134 fakeBuild.PipelineNameReturns("a-pipeline") 2135 fakeBuild.TeamNameReturns("some-team") 2136 fakeBuild.StatusReturns(db.BuildStatusStarted) 2137 fakeBuild.StartTimeReturns(time.Unix(1, 0)) 2138 fakeBuild.EndTimeReturns(time.Unix(100, 0)) 2139 2140 fakeJob.BuildReturns(fakeBuild, true, nil) 2141 }) 2142 2143 Context("when the build has no inputs", func() { 2144 BeforeEach(func() { 2145 fakeBuild.InputsReadyReturns(false) 2146 }) 2147 2148 It("returns a 500", func() { 2149 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2150 }) 2151 }) 2152 2153 Context("when the build is input ready", func() { 2154 BeforeEach(func() { 2155 fakeBuild.InputsReadyReturns(true) 2156 }) 2157 Context("when creating the rerun build fails", func() { 2158 BeforeEach(func() { 2159 fakeJob.RerunBuildReturns(nil, errors.New("nopers")) 2160 }) 2161 2162 It("returns a 500", func() { 2163 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2164 }) 2165 }) 2166 2167 Context("when creating the rerun build succeeds", func() { 2168 BeforeEach(func() { 2169 build := new(dbfakes.FakeBuild) 2170 build.IDReturns(2) 2171 build.NameReturns("1.1") 2172 build.JobNameReturns("some-job") 2173 build.PipelineNameReturns("a-pipeline") 2174 build.TeamNameReturns("some-team") 2175 build.StatusReturns(db.BuildStatusStarted) 2176 build.StartTimeReturns(time.Unix(1, 0)) 2177 build.EndTimeReturns(time.Unix(100, 0)) 2178 2179 fakeJob.RerunBuildReturns(build, nil) 2180 }) 2181 2182 It("returns 200 OK", func() { 2183 Expect(response.StatusCode).To(Equal(http.StatusOK)) 2184 }) 2185 2186 It("returns Content-Type 'application/json'", func() { 2187 expectedHeaderEntries := map[string]string{ 2188 "Content-Type": "application/json", 2189 } 2190 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 2191 }) 2192 2193 It("returns the build", func() { 2194 body, err := ioutil.ReadAll(response.Body) 2195 Expect(err).NotTo(HaveOccurred()) 2196 2197 Expect(body).To(MatchJSON(`{ 2198 "id": 2, 2199 "name": "1.1", 2200 "job_name": "some-job", 2201 "status": "started", 2202 "api_url": "/api/v1/builds/2", 2203 "pipeline_name": "a-pipeline", 2204 "team_name": "some-team", 2205 "start_time": 1, 2206 "end_time": 100 2207 }`)) 2208 }) 2209 }) 2210 }) 2211 }) 2212 }) 2213 }) 2214 }) 2215 2216 Describe("PUT /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/pause", func() { 2217 var response *http.Response 2218 2219 JustBeforeEach(func() { 2220 var err error 2221 2222 request, err := http.NewRequest("PUT", server.URL+"/api/v1/teams/some-team/pipelines/some-pipeline/jobs/job-name/pause", nil) 2223 Expect(err).NotTo(HaveOccurred()) 2224 2225 response, err = client.Do(request) 2226 Expect(err).NotTo(HaveOccurred()) 2227 }) 2228 2229 Context("when authenticated", func() { 2230 BeforeEach(func() { 2231 fakeAccess.IsAuthenticatedReturns(true) 2232 }) 2233 Context("when authorized", func() { 2234 BeforeEach(func() { 2235 fakeAccess.IsAuthorizedReturns(true) 2236 2237 fakePipeline.JobReturns(fakeJob, true, nil) 2238 fakeJob.PauseReturns(nil) 2239 }) 2240 2241 It("finds the job on the pipeline and pauses it", func() { 2242 jobName := fakePipeline.JobArgsForCall(0) 2243 Expect(jobName).To(Equal("job-name")) 2244 2245 Expect(fakeJob.PauseCallCount()).To(Equal(1)) 2246 2247 Expect(response.StatusCode).To(Equal(http.StatusOK)) 2248 }) 2249 2250 Context("when the job is not found", func() { 2251 BeforeEach(func() { 2252 fakePipeline.JobReturns(nil, false, nil) 2253 }) 2254 2255 It("returns a 404", func() { 2256 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 2257 }) 2258 }) 2259 2260 Context("when finding the job fails", func() { 2261 BeforeEach(func() { 2262 fakePipeline.JobReturns(nil, false, errors.New("some-error")) 2263 }) 2264 2265 It("returns a 500", func() { 2266 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2267 }) 2268 }) 2269 2270 Context("when the job fails to be paused", func() { 2271 BeforeEach(func() { 2272 fakeJob.PauseReturns(errors.New("some-error")) 2273 }) 2274 2275 It("returns a 500", func() { 2276 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2277 }) 2278 }) 2279 }) 2280 }) 2281 2282 Context("when not authenticated", func() { 2283 BeforeEach(func() { 2284 fakeAccess.IsAuthenticatedReturns(false) 2285 }) 2286 2287 It("returns Status Unauthorized", func() { 2288 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 2289 }) 2290 }) 2291 }) 2292 2293 Describe("PUT /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/unpause", func() { 2294 var response *http.Response 2295 2296 JustBeforeEach(func() { 2297 var err error 2298 2299 request, err := http.NewRequest("PUT", server.URL+"/api/v1/teams/some-team/pipelines/some-pipeline/jobs/job-name/unpause", nil) 2300 Expect(err).NotTo(HaveOccurred()) 2301 2302 response, err = client.Do(request) 2303 Expect(err).NotTo(HaveOccurred()) 2304 }) 2305 2306 Context("when authorized", func() { 2307 BeforeEach(func() { 2308 fakeAccess.IsAuthorizedReturns(true) 2309 }) 2310 2311 Context("when authenticated", func() { 2312 BeforeEach(func() { 2313 fakeAccess.IsAuthenticatedReturns(true) 2314 2315 fakePipeline.JobReturns(fakeJob, true, nil) 2316 fakeJob.UnpauseReturns(nil) 2317 }) 2318 2319 It("finds the job on the pipeline and unpauses it", func() { 2320 jobName := fakePipeline.JobArgsForCall(0) 2321 Expect(jobName).To(Equal("job-name")) 2322 2323 Expect(fakeJob.UnpauseCallCount()).To(Equal(1)) 2324 2325 Expect(response.StatusCode).To(Equal(http.StatusOK)) 2326 }) 2327 2328 Context("when the job is not found", func() { 2329 BeforeEach(func() { 2330 fakePipeline.JobReturns(nil, false, nil) 2331 }) 2332 2333 It("returns a 404", func() { 2334 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 2335 }) 2336 }) 2337 2338 Context("when finding the job fails", func() { 2339 BeforeEach(func() { 2340 fakePipeline.JobReturns(nil, false, errors.New("some-error")) 2341 }) 2342 2343 It("returns a 500", func() { 2344 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2345 }) 2346 }) 2347 2348 Context("when the job fails to be unpaused", func() { 2349 BeforeEach(func() { 2350 fakeJob.UnpauseReturns(errors.New("some-error")) 2351 }) 2352 2353 It("returns a 500", func() { 2354 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2355 }) 2356 }) 2357 }) 2358 }) 2359 2360 Context("when not authenticated", func() { 2361 BeforeEach(func() { 2362 fakeAccess.IsAuthenticatedReturns(false) 2363 }) 2364 2365 It("returns Status Unauthorized", func() { 2366 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 2367 }) 2368 }) 2369 }) 2370 2371 Describe("DELETE /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/tasks/:step_name/cache", func() { 2372 var ( 2373 request *http.Request 2374 response *http.Response 2375 ) 2376 2377 BeforeEach(func() { 2378 var err error 2379 2380 request, err = http.NewRequest("DELETE", server.URL+"/api/v1/teams/some-team/pipelines/some-pipeline/jobs/job-name/tasks/:step_name/cache", nil) 2381 Expect(err).NotTo(HaveOccurred()) 2382 }) 2383 2384 JustBeforeEach(func() { 2385 var err error 2386 2387 response, err = client.Do(request) 2388 Expect(err).NotTo(HaveOccurred()) 2389 }) 2390 2391 Context("when authorized", func() { 2392 BeforeEach(func() { 2393 fakeAccess.IsAuthorizedReturns(true) 2394 }) 2395 2396 Context("when authenticated", func() { 2397 BeforeEach(func() { 2398 fakeAccess.IsAuthenticatedReturns(true) 2399 2400 fakePipeline.JobReturns(fakeJob, true, nil) 2401 fakeJob.ClearTaskCacheReturns(1, nil) 2402 2403 }) 2404 2405 Context("when no cachePath is passed", func() { 2406 It("it finds the right job", func() { 2407 jobName := fakePipeline.JobArgsForCall(0) 2408 Expect(jobName).To(Equal("job-name")) 2409 }) 2410 2411 It("it clears the db cache entries successfully", func() { 2412 Expect(fakeJob.ClearTaskCacheCallCount()).To(Equal(1)) 2413 _, cachePath := fakeJob.ClearTaskCacheArgsForCall(0) 2414 Expect(cachePath).To(Equal("")) 2415 }) 2416 2417 It("returns 200 OK", func() { 2418 Expect(response.StatusCode).To(Equal(http.StatusOK)) 2419 }) 2420 2421 It("returns Content-Type 'application/json'", func() { 2422 expectedHeaderEntries := map[string]string{ 2423 "Content-Type": "application/json", 2424 } 2425 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 2426 }) 2427 2428 It("it returns the number of rows deleted", func() { 2429 body, err := ioutil.ReadAll(response.Body) 2430 Expect(err).NotTo(HaveOccurred()) 2431 2432 Expect(body).To(MatchJSON(`{"caches_removed": 1}`)) 2433 }) 2434 2435 Context("but no rows were deleted", func() { 2436 BeforeEach(func() { 2437 fakeJob.ClearTaskCacheReturns(0, nil) 2438 }) 2439 2440 It("it returns that 0 rows were deleted", func() { 2441 body, err := ioutil.ReadAll(response.Body) 2442 Expect(err).NotTo(HaveOccurred()) 2443 2444 Expect(body).To(MatchJSON(`{"caches_removed": 0}`)) 2445 }) 2446 2447 }) 2448 }) 2449 2450 Context("when a cachePath is passed", func() { 2451 BeforeEach(func() { 2452 query := request.URL.Query() 2453 query.Add(atc.ClearTaskCacheQueryPath, "cache-path") 2454 request.URL.RawQuery = query.Encode() 2455 }) 2456 2457 It("it finds the right job", func() { 2458 jobName := fakePipeline.JobArgsForCall(0) 2459 Expect(jobName).To(Equal("job-name")) 2460 }) 2461 2462 It("it clears the db cache entries successfully", func() { 2463 Expect(fakeJob.ClearTaskCacheCallCount()).To(Equal(1)) 2464 _, cachePath := fakeJob.ClearTaskCacheArgsForCall(0) 2465 Expect(cachePath).To(Equal("cache-path")) 2466 }) 2467 2468 It("returns 200 OK", func() { 2469 Expect(response.StatusCode).To(Equal(http.StatusOK)) 2470 }) 2471 2472 It("returns Content-Type 'application/json'", func() { 2473 expectedHeaderEntries := map[string]string{ 2474 "Content-Type": "application/json", 2475 } 2476 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 2477 }) 2478 2479 It("it returns the number of rows deleted", func() { 2480 body, err := ioutil.ReadAll(response.Body) 2481 Expect(err).NotTo(HaveOccurred()) 2482 2483 Expect(body).To(MatchJSON(`{"caches_removed": 1}`)) 2484 }) 2485 2486 Context("but no rows corresponding to the cachePath are deleted", func() { 2487 BeforeEach(func() { 2488 fakeJob.ClearTaskCacheReturns(0, nil) 2489 }) 2490 2491 It("it returns that 0 rows were deleted", func() { 2492 body, err := ioutil.ReadAll(response.Body) 2493 Expect(err).NotTo(HaveOccurred()) 2494 2495 Expect(body).To(MatchJSON(`{"caches_removed": 0}`)) 2496 }) 2497 }) 2498 }) 2499 2500 Context("when the job is not found", func() { 2501 BeforeEach(func() { 2502 fakePipeline.JobReturns(nil, false, nil) 2503 }) 2504 2505 It("returns a 404", func() { 2506 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 2507 }) 2508 }) 2509 2510 Context("when finding the job fails", func() { 2511 BeforeEach(func() { 2512 fakePipeline.JobReturns(nil, false, errors.New("some-error")) 2513 }) 2514 2515 It("returns a 500", func() { 2516 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2517 }) 2518 }) 2519 2520 Context("when there are problems removing the db cache entries", func() { 2521 BeforeEach(func() { 2522 fakeJob.ClearTaskCacheReturns(-1, errors.New("some-error")) 2523 }) 2524 2525 It("returns a 500", func() { 2526 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2527 }) 2528 }) 2529 }) 2530 }) 2531 2532 Context("when not authenticated", func() { 2533 BeforeEach(func() { 2534 fakeAccess.IsAuthenticatedReturns(false) 2535 }) 2536 2537 It("returns Status Unauthorized", func() { 2538 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 2539 }) 2540 }) 2541 }) 2542 2543 Describe("PUT /api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/schedule", func() { 2544 var response *http.Response 2545 2546 JustBeforeEach(func() { 2547 var err error 2548 2549 request, err := http.NewRequest("PUT", server.URL+"/api/v1/teams/some-team/pipelines/some-pipeline/jobs/job-name/schedule", nil) 2550 Expect(err).NotTo(HaveOccurred()) 2551 2552 response, err = client.Do(request) 2553 Expect(err).NotTo(HaveOccurred()) 2554 }) 2555 2556 Context("when authenticated", func() { 2557 BeforeEach(func() { 2558 fakeAccess.IsAuthenticatedReturns(true) 2559 }) 2560 Context("when authorized", func() { 2561 BeforeEach(func() { 2562 fakeAccess.IsAuthorizedReturns(true) 2563 2564 fakePipeline.JobReturns(fakeJob, true, nil) 2565 fakeJob.RequestScheduleReturns(nil) 2566 }) 2567 2568 It("finds the job on the pipeline and schedules it", func() { 2569 jobName := fakePipeline.JobArgsForCall(0) 2570 Expect(jobName).To(Equal("job-name")) 2571 2572 Expect(fakeJob.RequestScheduleCallCount()).To(Equal(1)) 2573 2574 Expect(response.StatusCode).To(Equal(http.StatusOK)) 2575 }) 2576 2577 Context("when the job is not found", func() { 2578 BeforeEach(func() { 2579 fakePipeline.JobReturns(nil, false, nil) 2580 }) 2581 2582 It("returns a 404", func() { 2583 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 2584 }) 2585 }) 2586 2587 Context("when finding the job fails", func() { 2588 BeforeEach(func() { 2589 fakePipeline.JobReturns(nil, false, errors.New("some-error")) 2590 }) 2591 2592 It("returns a 500", func() { 2593 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2594 }) 2595 }) 2596 2597 Context("when the job fails to be scheduled", func() { 2598 BeforeEach(func() { 2599 fakeJob.RequestScheduleReturns(errors.New("some-error")) 2600 }) 2601 2602 It("returns a 500", func() { 2603 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 2604 }) 2605 }) 2606 }) 2607 }) 2608 2609 Context("when not authenticated", func() { 2610 BeforeEach(func() { 2611 fakeAccess.IsAuthenticatedReturns(false) 2612 }) 2613 2614 It("returns Status Unauthorized", func() { 2615 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 2616 }) 2617 }) 2618 }) 2619 }) 2620 2621 func fakeDBResourceType(t atc.VersionedResourceType) *dbfakes.FakeResourceType { 2622 fake := new(dbfakes.FakeResourceType) 2623 fake.NameReturns(t.Name) 2624 fake.TypeReturns(t.Type) 2625 fake.SourceReturns(t.Source) 2626 fake.VersionReturns(t.Version) 2627 return fake 2628 }