1package api_test 2 3import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "time" 11 12 "github.com/concourse/concourse/atc" 13 "github.com/concourse/concourse/atc/db" 14 "github.com/concourse/concourse/atc/db/dbfakes" 15 . "github.com/concourse/concourse/atc/testhelpers" 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18) 19 20var _ = Describe("Builds API", func() { 21 22 Describe("POST /api/v1/builds", func() { 23 var plan atc.Plan 24 var response *http.Response 25 26 BeforeEach(func() { 27 plan = atc.Plan{ 28 Task: &atc.TaskPlan{ 29 Config: &atc.TaskConfig{ 30 Run: atc.TaskRunConfig{ 31 Path: "ls", 32 }, 33 }, 34 }, 35 } 36 }) 37 38 JustBeforeEach(func() { 39 reqPayload, err := json.Marshal(plan) 40 Expect(err).NotTo(HaveOccurred()) 41 42 req, err := http.NewRequest("POST", server.URL+"/api/v1/teams/some-team/builds", bytes.NewBuffer(reqPayload)) 43 Expect(err).NotTo(HaveOccurred()) 44 45 req.Header.Set("Content-Type", "application/json") 46 47 response, err = client.Do(req) 48 Expect(err).NotTo(HaveOccurred()) 49 }) 50 51 Context("when not authenticated", func() { 52 BeforeEach(func() { 53 fakeAccess.IsAuthenticatedReturns(false) 54 }) 55 56 It("returns 401", func() { 57 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 58 }) 59 60 It("does not trigger a build", func() { 61 Expect(dbTeam.CreateStartedBuildCallCount()).To(BeZero()) 62 }) 63 }) 64 65 Context("when authenticated", func() { 66 BeforeEach(func() { 67 fakeAccess.IsAuthenticatedReturns(true) 68 }) 69 70 Context("when not authorized", func() { 71 BeforeEach(func() { 72 fakeAccess.IsAuthorizedReturns(false) 73 }) 74 75 It("returns 403", func() { 76 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 77 }) 78 }) 79 80 Context("when authorized", func() { 81 BeforeEach(func() { 82 fakeAccess.IsAuthorizedReturns(true) 83 }) 84 85 Context("when creating a started build fails", func() { 86 BeforeEach(func() { 87 dbTeam.CreateStartedBuildReturns(nil, errors.New("oh no!")) 88 }) 89 90 It("returns 500 Internal Server Error", func() { 91 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 92 }) 93 }) 94 95 Context("when creating a started build succeeds", func() { 96 var fakeBuild *dbfakes.FakeBuild 97 98 BeforeEach(func() { 99 fakeBuild = new(dbfakes.FakeBuild) 100 fakeBuild.IDReturns(42) 101 fakeBuild.NameReturns("1") 102 fakeBuild.TeamNameReturns("some-team") 103 fakeBuild.StatusReturns("started") 104 fakeBuild.StartTimeReturns(time.Unix(1, 0)) 105 fakeBuild.EndTimeReturns(time.Unix(100, 0)) 106 fakeBuild.ReapTimeReturns(time.Unix(200, 0)) 107 108 dbTeam.CreateStartedBuildReturns(fakeBuild, nil) 109 }) 110 111 It("returns 201 Created", func() { 112 Expect(response.StatusCode).To(Equal(http.StatusCreated)) 113 }) 114 115 It("returns Content-Type 'application/json'", func() { 116 expectedHeaderEntries := map[string]string{ 117 "Content-Type": "application/json", 118 } 119 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 120 }) 121 122 It("creates a started build", func() { 123 Expect(dbTeam.CreateStartedBuildCallCount()).To(Equal(1)) 124 Expect(dbTeam.CreateStartedBuildArgsForCall(0)).To(Equal(plan)) 125 }) 126 127 It("returns the created build", func() { 128 body, err := ioutil.ReadAll(response.Body) 129 Expect(err).NotTo(HaveOccurred()) 130 131 Expect(body).To(MatchJSON(`{ 132 "id": 42, 133 "name": "1", 134 "team_name": "some-team", 135 "status": "started", 136 "api_url": "/api/v1/builds/42", 137 "start_time": 1, 138 "end_time": 100, 139 "reap_time": 200 140 }`)) 141 }) 142 143 }) 144 }) 145 }) 146 }) 147 148 Describe("GET /api/v1/builds", func() { 149 var response *http.Response 150 var queryParams string 151 var returnedBuilds []db.Build 152 153 BeforeEach(func() { 154 queryParams = "" 155 build1 := new(dbfakes.FakeBuild) 156 build1.IDReturns(4) 157 build1.NameReturns("2") 158 build1.JobNameReturns("job2") 159 build1.PipelineNameReturns("pipeline2") 160 build1.TeamNameReturns("some-team") 161 build1.StatusReturns(db.BuildStatusStarted) 162 build1.StartTimeReturns(time.Unix(1, 0)) 163 build1.EndTimeReturns(time.Unix(100, 0)) 164 build1.ReapTimeReturns(time.Unix(300, 0)) 165 166 build2 := new(dbfakes.FakeBuild) 167 build2.IDReturns(3) 168 build2.NameReturns("1") 169 build2.JobNameReturns("job1") 170 build2.PipelineNameReturns("pipeline1") 171 build2.TeamNameReturns("some-team") 172 build2.StatusReturns(db.BuildStatusSucceeded) 173 build2.StartTimeReturns(time.Unix(101, 0)) 174 build2.EndTimeReturns(time.Unix(200, 0)) 175 build2.ReapTimeReturns(time.Unix(400, 0)) 176 177 returnedBuilds = []db.Build{build1, build2} 178 fakeAccess.TeamNamesReturns([]string{"some-team"}) 179 }) 180 181 JustBeforeEach(func() { 182 var err error 183 184 response, err = client.Get(server.URL + "/api/v1/builds" + queryParams) 185 Expect(err).NotTo(HaveOccurred()) 186 }) 187 188 Context("when not authenticated", func() { 189 BeforeEach(func() { 190 fakeAccess.IsAuthenticatedReturns(false) 191 }) 192 193 Context("when no params are passed", func() { 194 BeforeEach(func() { 195 queryParams = "" 196 }) 197 198 It("does not set defaults for since and until", func() { 199 Expect(dbBuildFactory.VisibleBuildsCallCount()).To(Equal(1)) 200 201 teamName, page := dbBuildFactory.VisibleBuildsArgsForCall(0) 202 Expect(page).To(Equal(db.Page{ 203 Limit: 100, 204 })) 205 Expect(teamName).To(ConsistOf("some-team")) 206 }) 207 }) 208 209 Context("when all the params are passed", func() { 210 BeforeEach(func() { 211 queryParams = "?from=2&to=3&limit=8" 212 }) 213 214 It("passes them through", func() { 215 Expect(dbBuildFactory.VisibleBuildsCallCount()).To(Equal(1)) 216 217 _, page := dbBuildFactory.VisibleBuildsArgsForCall(0) 218 Expect(page).To(Equal(db.Page{ 219 From: db.NewIntPtr(2), 220 To: db.NewIntPtr(3), 221 Limit: 8, 222 })) 223 }) 224 225 Context("timestamp is provided", func() { 226 BeforeEach(func() { 227 queryParams = "?timestamps=true" 228 }) 229 230 It("calls AllBuilds", func() { 231 _, page := dbBuildFactory.VisibleBuildsArgsForCall(0) 232 Expect(page.UseDate).To(Equal(true)) 233 }) 234 }) 235 }) 236 237 Context("when getting the builds succeeds", func() { 238 BeforeEach(func() { 239 dbBuildFactory.VisibleBuildsReturns(returnedBuilds, db.Pagination{}, nil) 240 }) 241 242 It("returns 200 OK", func() { 243 Expect(response.StatusCode).To(Equal(http.StatusOK)) 244 }) 245 246 It("returns Content-Type 'application/json'", func() { 247 expectedHeaderEntries := map[string]string{ 248 "Content-Type": "application/json", 249 } 250 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 251 }) 252 253 It("returns all builds", func() { 254 body, err := ioutil.ReadAll(response.Body) 255 Expect(err).NotTo(HaveOccurred()) 256 257 Expect(body).To(MatchJSON(`[ 258 { 259 "id": 4, 260 "name": "2", 261 "job_name": "job2", 262 "pipeline_name": "pipeline2", 263 "team_name": "some-team", 264 "status": "started", 265 "api_url": "/api/v1/builds/4", 266 "start_time": 1, 267 "end_time": 100, 268 "reap_time": 300 269 }, 270 { 271 "id": 3, 272 "name": "1", 273 "job_name": "job1", 274 "pipeline_name": "pipeline1", 275 "team_name": "some-team", 276 "status": "succeeded", 277 "api_url": "/api/v1/builds/3", 278 "start_time": 101, 279 "end_time": 200, 280 "reap_time": 400 281 } 282 ]`)) 283 }) 284 }) 285 286 Context("when next/previous pages are available", func() { 287 BeforeEach(func() { 288 dbBuildFactory.VisibleBuildsReturns(returnedBuilds, db.Pagination{ 289 Newer: &db.Page{From: db.NewIntPtr(4), Limit: 2}, 290 Older: &db.Page{To: db.NewIntPtr(3), Limit: 2}, 291 }, nil) 292 }) 293 294 It("returns Link headers per rfc5988", func() { 295 Expect(response.Header["Link"]).To(ConsistOf([]string{ 296 fmt.Sprintf(`<%s/api/v1/builds?from=4&limit=2>; rel="previous"`, externalURL), 297 fmt.Sprintf(`<%s/api/v1/builds?to=3&limit=2>; rel="next"`, externalURL), 298 })) 299 }) 300 }) 301 302 Context("when getting all builds fails", func() { 303 BeforeEach(func() { 304 dbBuildFactory.VisibleBuildsReturns(nil, db.Pagination{}, errors.New("oh no!")) 305 }) 306 307 It("returns 500 Internal Server Error", func() { 308 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 309 }) 310 }) 311 }) 312 313 Context("when authenticated", func() { 314 BeforeEach(func() { 315 fakeAccess.IsAuthenticatedReturns(true) 316 }) 317 318 Context("when user has the admin privilege", func() { 319 BeforeEach(func() { 320 fakeAccess.IsAdminReturns(true) 321 }) 322 323 It("calls AllBuilds", func() { 324 Expect(dbBuildFactory.AllBuildsCallCount()).To(Equal(1)) 325 Expect(dbBuildFactory.VisibleBuildsCallCount()).To(Equal(0)) 326 }) 327 328 }) 329 330 Context("when no params are passed", func() { 331 BeforeEach(func() { 332 queryParams = "" 333 }) 334 335 It("does not set defaults for since and until", func() { 336 Expect(dbBuildFactory.VisibleBuildsCallCount()).To(Equal(1)) 337 338 _, page := dbBuildFactory.VisibleBuildsArgsForCall(0) 339 Expect(page).To(Equal(db.Page{ 340 Limit: 100, 341 })) 342 }) 343 }) 344 345 Context("when all the params are passed", func() { 346 BeforeEach(func() { 347 queryParams = "?from=2&to=3&limit=8" 348 }) 349 350 It("passes them through", func() { 351 Expect(dbBuildFactory.VisibleBuildsCallCount()).To(Equal(1)) 352 353 _, page := dbBuildFactory.VisibleBuildsArgsForCall(0) 354 Expect(page).To(Equal(db.Page{ 355 From: db.NewIntPtr(2), 356 To: db.NewIntPtr(3), 357 Limit: 8, 358 })) 359 }) 360 }) 361 362 Context("when getting the builds succeeds", func() { 363 BeforeEach(func() { 364 dbBuildFactory.VisibleBuildsReturns(returnedBuilds, db.Pagination{}, nil) 365 }) 366 367 It("returns 200 OK", func() { 368 Expect(response.StatusCode).To(Equal(http.StatusOK)) 369 }) 370 371 It("returns Content-Type 'application/json'", func() { 372 expectedHeaderEntries := map[string]string{ 373 "Content-Type": "application/json", 374 } 375 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 376 }) 377 378 It("returns all builds", func() { 379 body, err := ioutil.ReadAll(response.Body) 380 Expect(err).NotTo(HaveOccurred()) 381 382 Expect(body).To(MatchJSON(`[ 383 { 384 "id": 4, 385 "name": "2", 386 "job_name": "job2", 387 "pipeline_name": "pipeline2", 388 "team_name": "some-team", 389 "status": "started", 390 "api_url": "/api/v1/builds/4", 391 "start_time": 1, 392 "end_time": 100, 393 "reap_time": 300 394 }, 395 { 396 "id": 3, 397 "name": "1", 398 "job_name": "job1", 399 "pipeline_name": "pipeline1", 400 "team_name": "some-team", 401 "status": "succeeded", 402 "api_url": "/api/v1/builds/3", 403 "start_time": 101, 404 "end_time": 200, 405 "reap_time": 400 406 } 407 ]`)) 408 }) 409 410 It("returns builds for teams from the token", func() { 411 Expect(dbBuildFactory.VisibleBuildsCallCount()).To(Equal(1)) 412 teamName, _ := dbBuildFactory.VisibleBuildsArgsForCall(0) 413 Expect(teamName).To(ConsistOf("some-team")) 414 }) 415 }) 416 417 Context("when next/previous pages are available", func() { 418 BeforeEach(func() { 419 dbBuildFactory.VisibleBuildsReturns(returnedBuilds, db.Pagination{ 420 Newer: &db.Page{From: db.NewIntPtr(4), Limit: 2}, 421 Older: &db.Page{To: db.NewIntPtr(3), Limit: 2}, 422 }, nil) 423 }) 424 425 It("returns Link headers per rfc5988", func() { 426 Expect(response.Header["Link"]).To(ConsistOf([]string{ 427 fmt.Sprintf(`<%s/api/v1/builds?from=4&limit=2>; rel="previous"`, externalURL), 428 fmt.Sprintf(`<%s/api/v1/builds?to=3&limit=2>; rel="next"`, externalURL), 429 })) 430 }) 431 }) 432 433 Context("when getting all builds fails", func() { 434 BeforeEach(func() { 435 dbBuildFactory.VisibleBuildsReturns(nil, db.Pagination{}, errors.New("oh no!")) 436 }) 437 438 It("returns 500 Internal Server Error", func() { 439 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 440 }) 441 }) 442 }) 443 }) 444 445 Describe("GET /api/v1/builds/:build_id", func() { 446 var response *http.Response 447 448 Context("when parsing the build_id fails", func() { 449 BeforeEach(func() { 450 var err error 451 452 response, err = client.Get(server.URL + "/api/v1/builds/nope") 453 Expect(err).NotTo(HaveOccurred()) 454 }) 455 456 It("returns Bad Request", func() { 457 Expect(response.StatusCode).To(Equal(http.StatusBadRequest)) 458 }) 459 }) 460 461 Context("when parsing the build_id succeeds", func() { 462 JustBeforeEach(func() { 463 var err error 464 465 response, err = client.Get(server.URL + "/api/v1/builds/1") 466 Expect(err).NotTo(HaveOccurred()) 467 }) 468 469 Context("when calling the database fails", func() { 470 BeforeEach(func() { 471 dbBuildFactory.BuildReturns(nil, false, errors.New("disaster")) 472 }) 473 474 It("returns 500 Internal Server Error", func() { 475 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 476 }) 477 }) 478 479 Context("when the build cannot be found", func() { 480 BeforeEach(func() { 481 dbBuildFactory.BuildReturns(nil, false, nil) 482 }) 483 484 It("returns Not Found", func() { 485 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 486 }) 487 }) 488 489 Context("when the build can be found", func() { 490 BeforeEach(func() { 491 build.IDReturns(1) 492 build.NameReturns("1") 493 build.JobNameReturns("job1") 494 build.PipelineNameReturns("pipeline1") 495 build.TeamNameReturns("some-team") 496 build.StatusReturns(db.BuildStatusSucceeded) 497 build.StartTimeReturns(time.Unix(1, 0)) 498 build.EndTimeReturns(time.Unix(100, 0)) 499 build.ReapTimeReturns(time.Unix(200, 0)) 500 dbBuildFactory.BuildReturns(build, true, nil) 501 build.PipelineReturns(fakePipeline, true, nil) 502 fakePipeline.PublicReturns(true) 503 }) 504 505 Context("when not authenticated", func() { 506 BeforeEach(func() { 507 fakeAccess.IsAuthenticatedReturns(false) 508 fakeAccess.IsAuthorizedReturns(false) 509 }) 510 511 Context("and build is one off", func() { 512 BeforeEach(func() { 513 build.PipelineReturns(nil, false, nil) 514 }) 515 516 It("returns 401", func() { 517 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 518 }) 519 }) 520 521 Context("and the pipeline is private", func() { 522 BeforeEach(func() { 523 fakePipeline.PublicReturns(false) 524 build.PipelineReturns(fakePipeline, true, nil) 525 }) 526 527 It("returns 401", func() { 528 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 529 }) 530 }) 531 532 Context("and the pipeline is public", func() { 533 BeforeEach(func() { 534 fakePipeline.PublicReturns(true) 535 build.PipelineReturns(fakePipeline, true, nil) 536 }) 537 538 It("returns 200", func() { 539 Expect(response.StatusCode).To(Equal(http.StatusOK)) 540 }) 541 }) 542 }) 543 544 Context("when authenticated", func() { 545 BeforeEach(func() { 546 fakeAccess.IsAuthenticatedReturns(true) 547 }) 548 549 Context("when user is not authorized", func() { 550 BeforeEach(func() { 551 fakeAccess.IsAuthorizedReturns(false) 552 553 }) 554 It("returns 200 OK", func() { 555 Expect(response.StatusCode).To(Equal(http.StatusOK)) 556 }) 557 }) 558 559 Context("when user is authorized", func() { 560 BeforeEach(func() { 561 fakeAccess.IsAuthorizedReturns(true) 562 }) 563 564 It("returns 200 OK", func() { 565 Expect(response.StatusCode).To(Equal(http.StatusOK)) 566 }) 567 568 It("returns Content-Type 'application/json'", func() { 569 expectedHeaderEntries := map[string]string{ 570 "Content-Type": "application/json", 571 } 572 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 573 }) 574 575 It("returns the build with the given build_id", func() { 576 Expect(dbBuildFactory.BuildCallCount()).To(Equal(1)) 577 buildID := dbBuildFactory.BuildArgsForCall(0) 578 Expect(buildID).To(Equal(1)) 579 580 body, err := ioutil.ReadAll(response.Body) 581 Expect(err).NotTo(HaveOccurred()) 582 583 Expect(body).To(MatchJSON(`{ 584 "id": 1, 585 "name": "1", 586 "status": "succeeded", 587 "job_name": "job1", 588 "pipeline_name": "pipeline1", 589 "team_name": "some-team", 590 "api_url": "/api/v1/builds/1", 591 "start_time": 1, 592 "end_time": 100, 593 "reap_time": 200 594 }`)) 595 }) 596 }) 597 }) 598 }) 599 }) 600 }) 601 602 Describe("GET /api/v1/builds/:build_id/resources", func() { 603 var response *http.Response 604 605 Context("when the build is found", func() { 606 BeforeEach(func() { 607 build.JobNameReturns("job1") 608 build.TeamNameReturns("some-team") 609 build.PipelineReturns(fakePipeline, true, nil) 610 build.PipelineIDReturns(42) 611 dbBuildFactory.BuildReturns(build, true, nil) 612 }) 613 614 JustBeforeEach(func() { 615 var err error 616 617 response, err = client.Get(server.URL + "/api/v1/builds/3/resources") 618 Expect(err).NotTo(HaveOccurred()) 619 }) 620 621 Context("when not authenticated", func() { 622 BeforeEach(func() { 623 fakeAccess.IsAuthenticatedReturns(false) 624 }) 625 626 Context("and build is one off", func() { 627 BeforeEach(func() { 628 build.PipelineReturns(nil, false, nil) 629 }) 630 631 It("returns 401", func() { 632 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 633 }) 634 }) 635 636 Context("and the pipeline is private", func() { 637 BeforeEach(func() { 638 fakePipeline.PublicReturns(false) 639 build.PipelineReturns(fakePipeline, true, nil) 640 }) 641 642 It("returns 401", func() { 643 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 644 }) 645 }) 646 647 Context("and the pipeline is public", func() { 648 BeforeEach(func() { 649 fakePipeline.PublicReturns(true) 650 build.PipelineReturns(fakePipeline, true, nil) 651 }) 652 653 It("returns 200", func() { 654 Expect(response.StatusCode).To(Equal(http.StatusOK)) 655 }) 656 }) 657 }) 658 659 Context("when authenticated, but not authorized", func() { 660 BeforeEach(func() { 661 fakeAccess.IsAuthenticatedReturns(true) 662 fakeAccess.IsAuthorizedReturns(false) 663 }) 664 665 It("returns 403", func() { 666 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 667 }) 668 }) 669 670 Context("when authenticated and authorized", func() { 671 BeforeEach(func() { 672 fakeAccess.IsAuthenticatedReturns(true) 673 fakeAccess.IsAuthorizedReturns(true) 674 }) 675 676 It("returns 200 OK", func() { 677 Expect(response.StatusCode).To(Equal(http.StatusOK)) 678 }) 679 680 Context("when the build inputs/outputs are not empty", func() { 681 BeforeEach(func() { 682 build.ResourcesReturns([]db.BuildInput{ 683 { 684 Name: "input1", 685 Version: atc.Version{"version": "value1"}, 686 ResourceID: 1, 687 FirstOccurrence: true, 688 }, 689 { 690 Name: "input2", 691 Version: atc.Version{"version": "value2"}, 692 ResourceID: 2, 693 FirstOccurrence: false, 694 }, 695 }, 696 []db.BuildOutput{ 697 { 698 Name: "myresource3", 699 Version: atc.Version{"version": "value3"}, 700 }, 701 { 702 Name: "myresource4", 703 Version: atc.Version{"version": "value4"}, 704 }, 705 }, nil) 706 }) 707 708 It("returns Content-Type 'application/json'", func() { 709 expectedHeaderEntries := map[string]string{ 710 "Content-Type": "application/json", 711 } 712 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 713 }) 714 715 It("returns the build with it's input and output versioned resources", func() { 716 body, err := ioutil.ReadAll(response.Body) 717 Expect(err).NotTo(HaveOccurred()) 718 719 Expect(body).To(MatchJSON(`{ 720 "inputs": [ 721 { 722 "name": "input1", 723 "version": {"version": "value1"}, 724 "pipeline_id": 42, 725 "first_occurrence": true 726 }, 727 { 728 "name": "input2", 729 "version": {"version": "value2"}, 730 "pipeline_id": 42, 731 "first_occurrence": false 732 } 733 ], 734 "outputs": [ 735 { 736 "name": "myresource3", 737 "version": {"version": "value3"} 738 }, 739 { 740 "name": "myresource4", 741 "version": {"version": "value4"} 742 } 743 ] 744 }`)) 745 }) 746 }) 747 748 Context("when the build resources error", func() { 749 BeforeEach(func() { 750 build.ResourcesReturns([]db.BuildInput{}, []db.BuildOutput{}, errors.New("where are my feedback?")) 751 }) 752 753 It("returns internal server error", func() { 754 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 755 }) 756 }) 757 758 Context("with an invalid build", func() { 759 Context("when the lookup errors", func() { 760 BeforeEach(func() { 761 dbBuildFactory.BuildReturns(build, false, errors.New("Freakin' out man, I'm freakin' out!")) 762 }) 763 764 It("returns internal server error", func() { 765 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 766 }) 767 }) 768 769 Context("when the build does not exist", func() { 770 BeforeEach(func() { 771 dbBuildFactory.BuildReturns(nil, false, nil) 772 }) 773 774 It("returns internal server error", func() { 775 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 776 }) 777 }) 778 }) 779 }) 780 }) 781 782 Context("with an invalid build_id", func() { 783 JustBeforeEach(func() { 784 var err error 785 786 response, err = client.Get(server.URL + "/api/v1/builds/nope/resources") 787 Expect(err).NotTo(HaveOccurred()) 788 }) 789 790 It("returns internal server error", func() { 791 Expect(response.StatusCode).To(Equal(http.StatusBadRequest)) 792 }) 793 }) 794 }) 795 796 Describe("GET /api/v1/builds/:build_id/events", func() { 797 var ( 798 request *http.Request 799 response *http.Response 800 ) 801 802 BeforeEach(func() { 803 var err error 804 805 request, err = http.NewRequest("GET", server.URL+"/api/v1/builds/128/events", nil) 806 Expect(err).NotTo(HaveOccurred()) 807 }) 808 809 JustBeforeEach(func() { 810 var err error 811 812 response, err = client.Do(request) 813 Expect(err).NotTo(HaveOccurred()) 814 }) 815 816 Context("when the build can be found", func() { 817 BeforeEach(func() { 818 build.JobNameReturns("some-job") 819 build.TeamNameReturns("some-team") 820 build.PipelineReturns(fakePipeline, true, nil) 821 dbBuildFactory.BuildReturns(build, true, nil) 822 }) 823 824 Context("when authenticated, but not authorized", func() { 825 BeforeEach(func() { 826 fakeAccess.IsAuthenticatedReturns(true) 827 fakeAccess.IsAuthorizedReturns(false) 828 }) 829 830 It("returns 403", func() { 831 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 832 }) 833 }) 834 835 Context("when authorized", func() { 836 BeforeEach(func() { 837 fakeAccess.IsAuthenticatedReturns(true) 838 fakeAccess.IsAuthorizedReturns(true) 839 }) 840 841 It("returns 200", func() { 842 Expect(response.StatusCode).To(Equal(200)) 843 }) 844 845 It("serves the request via the event handler", func() { 846 body, err := ioutil.ReadAll(response.Body) 847 Expect(err).NotTo(HaveOccurred()) 848 849 Expect(string(body)).To(Equal("fake event handler factory was here")) 850 851 Expect(constructedEventHandler.build).To(Equal(build)) 852 Expect(dbBuildFactory.BuildCallCount()).To(Equal(1)) 853 buildID := dbBuildFactory.BuildArgsForCall(0) 854 Expect(buildID).To(Equal(128)) 855 }) 856 }) 857 858 Context("when not authenticated", func() { 859 BeforeEach(func() { 860 fakeAccess.IsAuthenticatedReturns(false) 861 }) 862 863 Context("and the pipeline is private", func() { 864 BeforeEach(func() { 865 build.PipelineReturns(fakePipeline, true, nil) 866 fakePipeline.PublicReturns(false) 867 }) 868 869 It("returns 401", func() { 870 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 871 }) 872 }) 873 874 Context("and the pipeline is public", func() { 875 BeforeEach(func() { 876 build.PipelineReturns(fakePipeline, true, nil) 877 fakePipeline.PublicReturns(true) 878 }) 879 880 Context("when the job is found", func() { 881 var fakeJob *dbfakes.FakeJob 882 883 BeforeEach(func() { 884 fakeJob = new(dbfakes.FakeJob) 885 fakePipeline.JobReturns(fakeJob, true, nil) 886 }) 887 888 Context("and the job is private", func() { 889 BeforeEach(func() { 890 fakeJob.PublicReturns(false) 891 }) 892 893 It("returns 401", func() { 894 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 895 }) 896 }) 897 898 Context("and the job is public", func() { 899 BeforeEach(func() { 900 fakeJob.PublicReturns(true) 901 }) 902 903 It("returns 200", func() { 904 Expect(response.StatusCode).To(Equal(200)) 905 }) 906 907 It("serves the request via the event handler", func() { 908 body, err := ioutil.ReadAll(response.Body) 909 Expect(err).NotTo(HaveOccurred()) 910 911 Expect(string(body)).To(Equal("fake event handler factory was here")) 912 913 Expect(constructedEventHandler.build).To(Equal(build)) 914 Expect(dbBuildFactory.BuildCallCount()).To(Equal(1)) 915 buildID := dbBuildFactory.BuildArgsForCall(0) 916 Expect(buildID).To(Equal(128)) 917 }) 918 }) 919 }) 920 921 Context("when finding the job fails", func() { 922 BeforeEach(func() { 923 fakePipeline.JobReturns(nil, false, errors.New("nope")) 924 }) 925 926 It("returns Internal Server Error", func() { 927 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 928 }) 929 }) 930 931 Context("when the job cannot be found", func() { 932 BeforeEach(func() { 933 fakePipeline.JobReturns(nil, false, nil) 934 }) 935 936 It("returns Not Found", func() { 937 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 938 }) 939 }) 940 }) 941 942 Context("when the build can not be found", func() { 943 BeforeEach(func() { 944 dbBuildFactory.BuildReturns(nil, false, nil) 945 }) 946 947 It("returns Not Found", func() { 948 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 949 }) 950 }) 951 952 Context("when calling the database fails", func() { 953 BeforeEach(func() { 954 dbBuildFactory.BuildReturns(nil, false, errors.New("nope")) 955 }) 956 957 It("returns Internal Server Error", func() { 958 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 959 }) 960 }) 961 }) 962 }) 963 964 Context("when calling the database fails", func() { 965 BeforeEach(func() { 966 dbBuildFactory.BuildReturns(nil, false, errors.New("nope")) 967 }) 968 969 It("returns Internal Server Error", func() { 970 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 971 }) 972 }) 973 }) 974 975 Describe("PUT /api/v1/builds/:build_id/abort", func() { 976 var ( 977 response *http.Response 978 ) 979 980 JustBeforeEach(func() { 981 var err error 982 983 req, err := http.NewRequest("PUT", server.URL+"/api/v1/builds/128/abort", nil) 984 Expect(err).NotTo(HaveOccurred()) 985 986 response, err = client.Do(req) 987 Expect(err).NotTo(HaveOccurred()) 988 }) 989 990 Context("when not authenticated", func() { 991 BeforeEach(func() { 992 fakeAccess.IsAuthenticatedReturns(false) 993 }) 994 995 It("returns 401", func() { 996 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 997 }) 998 }) 999 1000 Context("when authenticated", func() { 1001 BeforeEach(func() { 1002 fakeAccess.IsAuthenticatedReturns(true) 1003 }) 1004 1005 Context("when looking up the build fails", func() { 1006 BeforeEach(func() { 1007 dbBuildFactory.BuildReturns(nil, false, errors.New("nope")) 1008 }) 1009 1010 It("returns 500", func() { 1011 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1012 }) 1013 }) 1014 1015 Context("when the build can not be found", func() { 1016 BeforeEach(func() { 1017 dbBuildFactory.BuildReturns(nil, false, nil) 1018 }) 1019 1020 It("returns 404", func() { 1021 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1022 }) 1023 }) 1024 1025 Context("when the build is found", func() { 1026 BeforeEach(func() { 1027 build.TeamNameReturns("some-team") 1028 dbBuildFactory.BuildReturns(build, true, nil) 1029 }) 1030 1031 Context("when not authorized", func() { 1032 BeforeEach(func() { 1033 fakeAccess.IsAuthorizedReturns(false) 1034 }) 1035 1036 It("returns 403", func() { 1037 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 1038 }) 1039 }) 1040 1041 Context("when authorized", func() { 1042 BeforeEach(func() { 1043 fakeAccess.IsAuthorizedReturns(true) 1044 }) 1045 1046 Context("when aborting the build fails", func() { 1047 BeforeEach(func() { 1048 build.MarkAsAbortedReturns(errors.New("nope")) 1049 }) 1050 1051 It("returns 500", func() { 1052 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1053 }) 1054 }) 1055 1056 Context("when aborting succeeds", func() { 1057 BeforeEach(func() { 1058 build.MarkAsAbortedReturns(nil) 1059 }) 1060 1061 It("returns 204", func() { 1062 Expect(response.StatusCode).To(Equal(http.StatusNoContent)) 1063 }) 1064 }) 1065 }) 1066 }) 1067 }) 1068 }) 1069 1070 Describe("GET /api/v1/builds/:build_id/preparation", func() { 1071 var response *http.Response 1072 1073 JustBeforeEach(func() { 1074 var err error 1075 response, err = http.Get(server.URL + "/api/v1/builds/42/preparation") 1076 Expect(err).NotTo(HaveOccurred()) 1077 }) 1078 1079 Context("when the build is found", func() { 1080 var buildPrep db.BuildPreparation 1081 1082 BeforeEach(func() { 1083 buildPrep = db.BuildPreparation{ 1084 BuildID: 42, 1085 PausedPipeline: db.BuildPreparationStatusNotBlocking, 1086 PausedJob: db.BuildPreparationStatusNotBlocking, 1087 MaxRunningBuilds: db.BuildPreparationStatusBlocking, 1088 Inputs: map[string]db.BuildPreparationStatus{ 1089 "foo": db.BuildPreparationStatusNotBlocking, 1090 "bar": db.BuildPreparationStatusBlocking, 1091 }, 1092 InputsSatisfied: db.BuildPreparationStatusBlocking, 1093 MissingInputReasons: db.MissingInputReasons{"some-input": "some-reason"}, 1094 } 1095 dbBuildFactory.BuildReturns(build, true, nil) 1096 build.JobNameReturns("job1") 1097 build.TeamNameReturns("some-team") 1098 build.PreparationReturns(buildPrep, true, nil) 1099 }) 1100 1101 Context("when authenticated, but not authorized", func() { 1102 BeforeEach(func() { 1103 fakeAccess.IsAuthenticatedReturns(true) 1104 fakeAccess.IsAuthorizedReturns(false) 1105 build.PipelineReturns(fakePipeline, true, nil) 1106 }) 1107 1108 It("returns 403", func() { 1109 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 1110 }) 1111 }) 1112 1113 Context("when not authenticated", func() { 1114 BeforeEach(func() { 1115 fakeAccess.IsAuthenticatedReturns(false) 1116 }) 1117 1118 Context("and build is one off", func() { 1119 BeforeEach(func() { 1120 build.PipelineReturns(nil, false, nil) 1121 }) 1122 1123 It("returns 401", func() { 1124 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1125 }) 1126 }) 1127 1128 Context("and the pipeline is private", func() { 1129 BeforeEach(func() { 1130 build.PipelineReturns(fakePipeline, true, nil) 1131 fakePipeline.PublicReturns(false) 1132 }) 1133 1134 It("returns 401", func() { 1135 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1136 }) 1137 }) 1138 1139 Context("and the pipeline is public", func() { 1140 BeforeEach(func() { 1141 build.PipelineReturns(fakePipeline, true, nil) 1142 fakePipeline.PublicReturns(true) 1143 }) 1144 1145 Context("when the job is found", func() { 1146 var fakeJob *dbfakes.FakeJob 1147 BeforeEach(func() { 1148 fakeJob = new(dbfakes.FakeJob) 1149 fakePipeline.JobReturns(fakeJob, true, nil) 1150 }) 1151 1152 Context("when job is private", func() { 1153 BeforeEach(func() { 1154 fakeJob.PublicReturns(false) 1155 }) 1156 1157 It("returns 401", func() { 1158 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1159 }) 1160 }) 1161 1162 Context("when job is public", func() { 1163 BeforeEach(func() { 1164 fakeJob.PublicReturns(true) 1165 }) 1166 1167 It("returns 200", func() { 1168 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1169 }) 1170 }) 1171 }) 1172 1173 Context("when finding the job fails", func() { 1174 BeforeEach(func() { 1175 fakePipeline.JobReturns(nil, false, errors.New("nope")) 1176 }) 1177 1178 It("returns Internal Server Error", func() { 1179 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1180 }) 1181 }) 1182 1183 Context("when the job cannot be found", func() { 1184 BeforeEach(func() { 1185 fakePipeline.JobReturns(nil, false, nil) 1186 }) 1187 1188 It("returns Not Found", func() { 1189 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1190 }) 1191 }) 1192 }) 1193 }) 1194 1195 Context("when authenticated", func() { 1196 BeforeEach(func() { 1197 fakeAccess.IsAuthenticatedReturns(true) 1198 fakeAccess.IsAuthorizedReturns(true) 1199 }) 1200 1201 It("fetches data from the db", func() { 1202 Expect(build.PreparationCallCount()).To(Equal(1)) 1203 }) 1204 1205 It("returns OK", func() { 1206 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1207 }) 1208 1209 It("returns Content-Type 'application/json'", func() { 1210 expectedHeaderEntries := map[string]string{ 1211 "Content-Type": "application/json", 1212 } 1213 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 1214 }) 1215 1216 It("returns the build preparation", func() { 1217 body, err := ioutil.ReadAll(response.Body) 1218 Expect(err).NotTo(HaveOccurred()) 1219 1220 Expect(body).To(MatchJSON(`{ 1221 "build_id": 42, 1222 "paused_pipeline": "not_blocking", 1223 "paused_job": "not_blocking", 1224 "max_running_builds": "blocking", 1225 "inputs": { 1226 "foo": "not_blocking", 1227 "bar": "blocking" 1228 }, 1229 "inputs_satisfied": "blocking", 1230 "missing_input_reasons": { 1231 "some-input": "some-reason" 1232 } 1233 }`)) 1234 }) 1235 1236 Context("when the build preparation is not found", func() { 1237 BeforeEach(func() { 1238 dbBuildFactory.BuildReturns(build, true, nil) 1239 build.PreparationReturns(db.BuildPreparation{}, false, nil) 1240 }) 1241 1242 It("returns Not Found", func() { 1243 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1244 }) 1245 }) 1246 1247 Context("when looking up the build preparation fails", func() { 1248 BeforeEach(func() { 1249 dbBuildFactory.BuildReturns(build, true, nil) 1250 build.PreparationReturns(db.BuildPreparation{}, false, errors.New("ho ho ho merry festivus")) 1251 }) 1252 1253 It("returns 500 Internal Server Error", func() { 1254 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1255 }) 1256 }) 1257 }) 1258 }) 1259 1260 Context("when looking up the build fails", func() { 1261 BeforeEach(func() { 1262 dbBuildFactory.BuildReturns(nil, false, errors.New("ho ho ho merry festivus")) 1263 }) 1264 1265 It("returns 500 Internal Server Error", func() { 1266 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1267 }) 1268 }) 1269 1270 Context("when build is not found", func() { 1271 BeforeEach(func() { 1272 dbBuildFactory.BuildReturns(nil, false, nil) 1273 }) 1274 1275 It("returns 404", func() { 1276 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1277 }) 1278 }) 1279 }) 1280 1281 Describe("GET /api/v1/builds/:build_id/plan", func() { 1282 var plan *json.RawMessage 1283 1284 var response *http.Response 1285 1286 BeforeEach(func() { 1287 data := []byte(`{"some":"plan"}`) 1288 plan = (*json.RawMessage)(&data) 1289 }) 1290 1291 JustBeforeEach(func() { 1292 var err error 1293 response, err = http.Get(server.URL + "/api/v1/builds/42/plan") 1294 Expect(err).NotTo(HaveOccurred()) 1295 }) 1296 1297 Context("when the build is found", func() { 1298 BeforeEach(func() { 1299 build.JobNameReturns("job1") 1300 build.TeamNameReturns("some-team") 1301 dbBuildFactory.BuildReturns(build, true, nil) 1302 }) 1303 1304 Context("when authenticated, but not authorized", func() { 1305 BeforeEach(func() { 1306 fakeAccess.IsAuthenticatedReturns(true) 1307 fakeAccess.IsAuthorizedReturns(false) 1308 1309 build.PipelineReturns(fakePipeline, true, nil) 1310 }) 1311 1312 It("returns 403", func() { 1313 Expect(response.StatusCode).To(Equal(http.StatusForbidden)) 1314 }) 1315 }) 1316 1317 Context("when not authenticated", func() { 1318 BeforeEach(func() { 1319 fakeAccess.IsAuthenticatedReturns(false) 1320 }) 1321 1322 Context("and build is one off", func() { 1323 BeforeEach(func() { 1324 build.PipelineReturns(nil, false, nil) 1325 }) 1326 1327 It("returns 401", func() { 1328 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1329 }) 1330 }) 1331 1332 Context("and the pipeline is private", func() { 1333 BeforeEach(func() { 1334 build.PipelineReturns(fakePipeline, true, nil) 1335 fakePipeline.PublicReturns(false) 1336 }) 1337 1338 It("returns 401", func() { 1339 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1340 }) 1341 }) 1342 1343 Context("and the pipeline is public", func() { 1344 BeforeEach(func() { 1345 build.PipelineReturns(fakePipeline, true, nil) 1346 fakePipeline.PublicReturns(true) 1347 }) 1348 1349 Context("when finding the job fails", func() { 1350 BeforeEach(func() { 1351 fakePipeline.JobReturns(nil, false, errors.New("nope")) 1352 }) 1353 It("returns 500", func() { 1354 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1355 }) 1356 }) 1357 1358 Context("when the job does not exist", func() { 1359 BeforeEach(func() { 1360 fakePipeline.JobReturns(nil, false, nil) 1361 }) 1362 It("returns 404", func() { 1363 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1364 }) 1365 }) 1366 1367 Context("when the job exists", func() { 1368 var fakeJob *dbfakes.FakeJob 1369 1370 BeforeEach(func() { 1371 fakeJob = new(dbfakes.FakeJob) 1372 fakePipeline.JobReturns(fakeJob, true, nil) 1373 }) 1374 1375 Context("and the job is public", func() { 1376 BeforeEach(func() { 1377 fakeJob.PublicReturns(true) 1378 }) 1379 Context("and the build has a plan", func() { 1380 BeforeEach(func() { 1381 build.HasPlanReturns(true) 1382 }) 1383 It("returns 200", func() { 1384 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1385 }) 1386 }) 1387 Context("and the build has no plan", func() { 1388 BeforeEach(func() { 1389 build.HasPlanReturns(false) 1390 }) 1391 It("returns 404", func() { 1392 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1393 }) 1394 }) 1395 }) 1396 1397 Context("and the job is private", func() { 1398 BeforeEach(func() { 1399 fakeJob.PublicReturns(false) 1400 }) 1401 It("returns 401", func() { 1402 Expect(response.StatusCode).To(Equal(http.StatusUnauthorized)) 1403 }) 1404 }) 1405 }) 1406 }) 1407 }) 1408 1409 Context("when authenticated", func() { 1410 BeforeEach(func() { 1411 fakeAccess.IsAuthenticatedReturns(true) 1412 fakeAccess.IsAuthorizedReturns(true) 1413 }) 1414 1415 Context("when the build returns a plan", func() { 1416 BeforeEach(func() { 1417 build.HasPlanReturns(true) 1418 build.PublicPlanReturns(plan) 1419 build.SchemaReturns("some-schema") 1420 }) 1421 1422 It("returns OK", func() { 1423 Expect(response.StatusCode).To(Equal(http.StatusOK)) 1424 }) 1425 1426 It("returns Content-Type 'application/json'", func() { 1427 expectedHeaderEntries := map[string]string{ 1428 "Content-Type": "application/json", 1429 } 1430 Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries)) 1431 }) 1432 1433 It("returns the plan", func() { 1434 body, err := ioutil.ReadAll(response.Body) 1435 Expect(err).NotTo(HaveOccurred()) 1436 1437 Expect(body).To(MatchJSON(`{ 1438 "schema": "some-schema", 1439 "plan": {"some":"plan"} 1440 }`)) 1441 }) 1442 }) 1443 1444 Context("when the build has no plan", func() { 1445 BeforeEach(func() { 1446 build.HasPlanReturns(false) 1447 }) 1448 1449 It("returns no Content-Type header", func() { 1450 expectedHeaderEntries := map[string]string{ 1451 "Content-Type": "", 1452 } 1453 Expect(response).ShouldNot(IncludeHeaderEntries(expectedHeaderEntries)) 1454 }) 1455 1456 It("returns not found", func() { 1457 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1458 }) 1459 }) 1460 }) 1461 }) 1462 1463 Context("when the build is not found", func() { 1464 BeforeEach(func() { 1465 dbBuildFactory.BuildReturns(nil, false, nil) 1466 }) 1467 1468 It("returns Not Found", func() { 1469 Expect(response.StatusCode).To(Equal(http.StatusNotFound)) 1470 }) 1471 }) 1472 1473 Context("when looking up the build fails", func() { 1474 BeforeEach(func() { 1475 dbBuildFactory.BuildReturns(nil, false, errors.New("oh no!")) 1476 }) 1477 1478 It("returns 500 Internal Server Error", func() { 1479 Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) 1480 }) 1481 }) 1482 }) 1483}) 1484