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

     1  package db_test
     2  
     3  import (
     4  	"context"
     5  	"crypto/md5"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"fmt"
     9  	"strconv"
    10  	"time"
    11  
    12  	"code.cloudfoundry.org/clock"
    13  	"code.cloudfoundry.org/lager"
    14  	sq "github.com/Masterminds/squirrel"
    15  	"github.com/pf-qiu/concourse/v6/atc"
    16  	"github.com/pf-qiu/concourse/v6/atc/creds"
    17  	"github.com/pf-qiu/concourse/v6/atc/creds/dummy"
    18  	"github.com/pf-qiu/concourse/v6/atc/db"
    19  	"github.com/pf-qiu/concourse/v6/atc/db/dbtest"
    20  	"github.com/pf-qiu/concourse/v6/atc/event"
    21  	"github.com/pf-qiu/concourse/v6/tracing"
    22  	"github.com/pf-qiu/concourse/v6/vars"
    23  	. "github.com/onsi/ginkgo"
    24  	. "github.com/onsi/gomega"
    25  	gocache "github.com/patrickmn/go-cache"
    26  )
    27  
    28  var _ = Describe("Build", func() {
    29  	var (
    30  		versionsDB db.VersionsDB
    31  
    32  		// XXX(dbtests): remove these
    33  		team  db.Team
    34  		build db.Build
    35  		job   db.Job
    36  
    37  		ctx context.Context
    38  	)
    39  
    40  	BeforeEach(func() {
    41  		ctx = context.Background()
    42  
    43  		versionsDB = db.NewVersionsDB(dbConn, 100, gocache.New(10*time.Second, 10*time.Second))
    44  
    45  		var err error
    46  		var found bool
    47  		team, err = teamFactory.CreateTeam(atc.Team{Name: "some-team"})
    48  		Expect(err).ToNot(HaveOccurred())
    49  
    50  		pipelineConfig := atc.Config{
    51  			Jobs: atc.JobConfigs{
    52  				{Name: "some-job"},
    53  			},
    54  		}
    55  
    56  		pipeline, _, err := team.SavePipeline(atc.PipelineRef{Name: "some-build-pipeline"}, pipelineConfig, db.ConfigVersion(1), false)
    57  		Expect(err).ToNot(HaveOccurred())
    58  
    59  		job, found, err = pipeline.Job("some-job")
    60  		Expect(err).ToNot(HaveOccurred())
    61  		Expect(found).To(BeTrue())
    62  
    63  		build, err = job.CreateBuild()
    64  		Expect(err).NotTo(HaveOccurred())
    65  	})
    66  
    67  	It("has no plan on creation", func() {
    68  		build, err := team.CreateOneOffBuild()
    69  		Expect(err).ToNot(HaveOccurred())
    70  		Expect(build.HasPlan()).To(BeFalse())
    71  	})
    72  
    73  	Describe("LagerData", func() {
    74  		var build db.Build
    75  
    76  		var data lager.Data
    77  
    78  		JustBeforeEach(func() {
    79  			data = build.LagerData()
    80  		})
    81  
    82  		Context("for a one-off build", func() {
    83  			BeforeEach(func() {
    84  				var err error
    85  				build, err = team.CreateOneOffBuild()
    86  				Expect(err).ToNot(HaveOccurred())
    87  			})
    88  
    89  			It("includes build and team info", func() {
    90  				Expect(data).To(Equal(lager.Data{
    91  					"build_id": build.ID(),
    92  					"build":    build.Name(),
    93  					"team":     team.Name(),
    94  				}))
    95  			})
    96  		})
    97  
    98  		Context("for a job build", func() {
    99  			BeforeEach(func() {
   100  				var err error
   101  				build, err = defaultJob.CreateBuild()
   102  				Expect(err).ToNot(HaveOccurred())
   103  			})
   104  
   105  			It("includes build, team, pipeline, and job info", func() {
   106  				Expect(data).To(Equal(lager.Data{
   107  					"build_id": build.ID(),
   108  					"build":    build.Name(),
   109  					"team":     build.TeamName(),
   110  					"pipeline": build.PipelineName(),
   111  					"job":      defaultJob.Name(),
   112  				}))
   113  			})
   114  		})
   115  
   116  		Context("for a resource build", func() {
   117  			BeforeEach(func() {
   118  				var err error
   119  				var created bool
   120  				build, created, err = defaultResource.CreateBuild(context.TODO(), false, atc.Plan{})
   121  				Expect(err).ToNot(HaveOccurred())
   122  				Expect(created).To(BeTrue())
   123  			})
   124  
   125  			It("includes build, team, and pipeline", func() {
   126  				Expect(data).To(Equal(lager.Data{
   127  					"build_id": build.ID(),
   128  					"build":    build.Name(),
   129  					"team":     build.TeamName(),
   130  					"pipeline": build.PipelineName(),
   131  					"resource": defaultResource.Name(),
   132  				}))
   133  			})
   134  		})
   135  
   136  		Context("for a resource type build", func() {
   137  			BeforeEach(func() {
   138  				var err error
   139  				var created bool
   140  				build, created, err = defaultResourceType.CreateBuild(context.TODO(), false, atc.Plan{})
   141  				Expect(err).ToNot(HaveOccurred())
   142  				Expect(created).To(BeTrue())
   143  			})
   144  
   145  			It("includes build, team, and pipeline", func() {
   146  				Expect(data).To(Equal(lager.Data{
   147  					"build_id":      build.ID(),
   148  					"build":         build.Name(),
   149  					"team":          build.TeamName(),
   150  					"pipeline":      build.PipelineName(),
   151  					"resource_type": defaultResourceType.Name(),
   152  				}))
   153  			})
   154  		})
   155  	})
   156  
   157  	Describe("SyslogTag", func() {
   158  		var build db.Build
   159  
   160  		var originID event.OriginID = "some-origin"
   161  		var tag string
   162  
   163  		JustBeforeEach(func() {
   164  			tag = build.SyslogTag(originID)
   165  		})
   166  
   167  		Context("for a one-off build", func() {
   168  			BeforeEach(func() {
   169  				var err error
   170  				build, err = team.CreateOneOffBuild()
   171  				Expect(err).ToNot(HaveOccurred())
   172  			})
   173  
   174  			It("includes build and team info", func() {
   175  				Expect(tag).To(Equal(fmt.Sprintf("%s/%d/%s", team.Name(), build.ID(), originID)))
   176  			})
   177  		})
   178  
   179  		Context("for a job build", func() {
   180  			BeforeEach(func() {
   181  				var err error
   182  				build, err = defaultJob.CreateBuild()
   183  				Expect(err).ToNot(HaveOccurred())
   184  			})
   185  
   186  			It("includes build, team, pipeline, and job info", func() {
   187  				Expect(tag).To(Equal(fmt.Sprintf("%s/%s/%s/%s/%s", defaultJob.TeamName(), defaultJob.PipelineName(), defaultJob.Name(), build.Name(), originID)))
   188  			})
   189  		})
   190  
   191  		Context("for a resource build", func() {
   192  			BeforeEach(func() {
   193  				var err error
   194  				var created bool
   195  				build, created, err = defaultResource.CreateBuild(context.TODO(), false, atc.Plan{})
   196  				Expect(err).ToNot(HaveOccurred())
   197  				Expect(created).To(BeTrue())
   198  			})
   199  
   200  			It("includes build, team, and pipeline", func() {
   201  				Expect(tag).To(Equal(fmt.Sprintf("%s/%s/%s/%d/%s", defaultResource.TeamName(), defaultResource.PipelineName(), defaultResource.Name(), build.ID(), originID)))
   202  			})
   203  		})
   204  
   205  		Context("for a resource type build", func() {
   206  			BeforeEach(func() {
   207  				var err error
   208  				var created bool
   209  				build, created, err = defaultResourceType.CreateBuild(context.TODO(), false, atc.Plan{})
   210  				Expect(err).ToNot(HaveOccurred())
   211  				Expect(created).To(BeTrue())
   212  			})
   213  
   214  			It("includes build, team, and pipeline", func() {
   215  				Expect(tag).To(Equal(fmt.Sprintf("%s/%s/%s/%d/%s", defaultResourceType.TeamName(), defaultResourceType.PipelineName(), defaultResourceType.Name(), build.ID(), originID)))
   216  			})
   217  		})
   218  	})
   219  
   220  	Describe("TracingAttrs", func() {
   221  		var build db.Build
   222  
   223  		var attrs tracing.Attrs
   224  
   225  		JustBeforeEach(func() {
   226  			attrs = build.TracingAttrs()
   227  		})
   228  
   229  		Context("for a one-off build", func() {
   230  			BeforeEach(func() {
   231  				var err error
   232  				build, err = team.CreateOneOffBuild()
   233  				Expect(err).ToNot(HaveOccurred())
   234  			})
   235  
   236  			It("includes build and team info", func() {
   237  				Expect(attrs).To(Equal(tracing.Attrs{
   238  					"build_id": strconv.Itoa(build.ID()),
   239  					"build":    build.Name(),
   240  					"team":     team.Name(),
   241  				}))
   242  			})
   243  		})
   244  
   245  		Context("for a job build", func() {
   246  			BeforeEach(func() {
   247  				var err error
   248  				build, err = defaultJob.CreateBuild()
   249  				Expect(err).ToNot(HaveOccurred())
   250  			})
   251  
   252  			It("includes build, team, pipeline, and job info", func() {
   253  				Expect(attrs).To(Equal(tracing.Attrs{
   254  					"build_id": strconv.Itoa(build.ID()),
   255  					"build":    build.Name(),
   256  					"team":     build.TeamName(),
   257  					"pipeline": build.PipelineName(),
   258  					"job":      defaultJob.Name(),
   259  				}))
   260  			})
   261  		})
   262  
   263  		Context("for a resource build", func() {
   264  			BeforeEach(func() {
   265  				var err error
   266  				var created bool
   267  				build, created, err = defaultResource.CreateBuild(context.TODO(), false, atc.Plan{})
   268  				Expect(err).ToNot(HaveOccurred())
   269  				Expect(created).To(BeTrue())
   270  			})
   271  
   272  			It("includes build, team, and pipeline", func() {
   273  				Expect(attrs).To(Equal(tracing.Attrs{
   274  					"build_id": strconv.Itoa(build.ID()),
   275  					"build":    build.Name(),
   276  					"team":     build.TeamName(),
   277  					"pipeline": build.PipelineName(),
   278  					"resource": defaultResource.Name(),
   279  				}))
   280  			})
   281  		})
   282  
   283  		Context("for a resource type build", func() {
   284  			BeforeEach(func() {
   285  				var err error
   286  				var created bool
   287  				build, created, err = defaultResourceType.CreateBuild(context.TODO(), false, atc.Plan{})
   288  				Expect(err).ToNot(HaveOccurred())
   289  				Expect(created).To(BeTrue())
   290  			})
   291  
   292  			It("includes build, team, and pipeline", func() {
   293  				Expect(attrs).To(Equal(tracing.Attrs{
   294  					"build_id":      strconv.Itoa(build.ID()),
   295  					"build":         build.Name(),
   296  					"team":          build.TeamName(),
   297  					"pipeline":      build.PipelineName(),
   298  					"resource_type": defaultResourceType.Name(),
   299  				}))
   300  			})
   301  		})
   302  	})
   303  
   304  	Describe("Reload", func() {
   305  		It("updates the model", func() {
   306  			started, err := build.Start(atc.Plan{})
   307  			Expect(err).NotTo(HaveOccurred())
   308  			Expect(started).To(BeTrue())
   309  
   310  			Expect(build.Status()).To(Equal(db.BuildStatusPending))
   311  
   312  			found, err := build.Reload()
   313  			Expect(err).NotTo(HaveOccurred())
   314  			Expect(found).To(BeTrue())
   315  			Expect(build.Status()).To(Equal(db.BuildStatusStarted))
   316  		})
   317  	})
   318  
   319  	Describe("Drain", func() {
   320  		It("defaults drain to false in the beginning", func() {
   321  			Expect(build.IsDrained()).To(BeFalse())
   322  		})
   323  
   324  		It("has drain set to true after a drain and a reload", func() {
   325  			err := build.SetDrained(true)
   326  			Expect(err).NotTo(HaveOccurred())
   327  
   328  			drained := build.IsDrained()
   329  			Expect(drained).To(BeTrue())
   330  
   331  			_, err = build.Reload()
   332  			Expect(err).NotTo(HaveOccurred())
   333  			drained = build.IsDrained()
   334  			Expect(drained).To(BeTrue())
   335  		})
   336  	})
   337  
   338  	Describe("Start", func() {
   339  		var err error
   340  		var started bool
   341  		var plan atc.Plan
   342  
   343  		BeforeEach(func() {
   344  			plan = atc.Plan{
   345  				ID: atc.PlanID("56"),
   346  				Get: &atc.GetPlan{
   347  					Type:     "some-type",
   348  					Name:     "some-name",
   349  					Resource: "some-resource",
   350  					Source:   atc.Source{"some": "source"},
   351  					Params:   atc.Params{"some": "params"},
   352  					Version:  &atc.Version{"some": "version"},
   353  					Tags:     atc.Tags{"some-tags"},
   354  					VersionedResourceTypes: atc.VersionedResourceTypes{
   355  						{
   356  							ResourceType: atc.ResourceType{
   357  								Name:       "some-name",
   358  								Source:     atc.Source{"some": "source"},
   359  								Type:       "some-type",
   360  								Privileged: true,
   361  								Tags:       atc.Tags{"some-tags"},
   362  							},
   363  							Version: atc.Version{"some-resource-type": "version"},
   364  						},
   365  					},
   366  				},
   367  			}
   368  		})
   369  
   370  		JustBeforeEach(func() {
   371  			started, err = build.Start(plan)
   372  			Expect(err).NotTo(HaveOccurred())
   373  		})
   374  
   375  		Context("build has been aborted", func() {
   376  			BeforeEach(func() {
   377  				err = build.MarkAsAborted()
   378  				Expect(err).NotTo(HaveOccurred())
   379  			})
   380  
   381  			It("does not start the build", func() {
   382  				Expect(started).To(BeFalse())
   383  			})
   384  
   385  			It("leaves the build in pending state", func() {
   386  				found, err := build.Reload()
   387  				Expect(err).NotTo(HaveOccurred())
   388  				Expect(found).To(BeTrue())
   389  				Expect(build.Status()).To(Equal(db.BuildStatusPending))
   390  			})
   391  		})
   392  
   393  		Context("build has not been aborted", func() {
   394  			It("starts the build", func() {
   395  				Expect(started).To(BeTrue())
   396  			})
   397  
   398  			It("creates Start event", func() {
   399  				found, err := build.Reload()
   400  				Expect(err).NotTo(HaveOccurred())
   401  				Expect(found).To(BeTrue())
   402  				Expect(build.Status()).To(Equal(db.BuildStatusStarted))
   403  
   404  				events, err := build.Events(0)
   405  				Expect(err).NotTo(HaveOccurred())
   406  
   407  				defer db.Close(events)
   408  
   409  				Expect(events.Next()).To(Equal(envelope(event.Status{
   410  					Status: atc.StatusStarted,
   411  					Time:   build.StartTime().Unix(),
   412  				})))
   413  			})
   414  
   415  			It("updates build status", func() {
   416  				found, err := build.Reload()
   417  				Expect(err).NotTo(HaveOccurred())
   418  				Expect(found).To(BeTrue())
   419  				Expect(build.Status()).To(Equal(db.BuildStatusStarted))
   420  			})
   421  
   422  			It("saves the public plan", func() {
   423  				found, err := build.Reload()
   424  				Expect(err).NotTo(HaveOccurred())
   425  				Expect(found).To(BeTrue())
   426  				Expect(build.HasPlan()).To(BeTrue())
   427  				Expect(build.PublicPlan()).To(Equal(plan.Public()))
   428  			})
   429  		})
   430  	})
   431  
   432  	Describe("Finish", func() {
   433  		var scenario *dbtest.Scenario
   434  		var build db.Build
   435  		var expectedOutputs []db.AlgorithmVersion
   436  
   437  		BeforeEach(func() {
   438  			pipelineConfig := atc.Config{
   439  				Jobs: atc.JobConfigs{
   440  					{
   441  						Name: "some-job",
   442  						PlanSequence: []atc.Step{
   443  							{
   444  								Config: &atc.GetStep{
   445  									Name:     "input-1",
   446  									Resource: "some-resource",
   447  								},
   448  							},
   449  							{
   450  								Config: &atc.GetStep{
   451  									Name:     "input-2",
   452  									Resource: "some-other-resource",
   453  								},
   454  							},
   455  							{
   456  								Config: &atc.GetStep{
   457  									Name:     "input-3",
   458  									Resource: "some-resource",
   459  								},
   460  							},
   461  							{
   462  								Config: &atc.GetStep{
   463  									Name:     "input-4",
   464  									Resource: "some-resource",
   465  								},
   466  							},
   467  							{
   468  								Config: &atc.PutStep{
   469  									Name:     "output-1",
   470  									Resource: "some-resource",
   471  								},
   472  							},
   473  							{
   474  								Config: &atc.PutStep{
   475  									Name:     "output-2",
   476  									Resource: "some-resource",
   477  								},
   478  							},
   479  						},
   480  					},
   481  					{
   482  						Name: "downstream-job",
   483  						PlanSequence: []atc.Step{
   484  							{
   485  								Config: &atc.GetStep{
   486  									Name:   "some-resource",
   487  									Passed: []string{"some-job"},
   488  								},
   489  							},
   490  						},
   491  					},
   492  					{
   493  						Name: "no-request-job",
   494  						PlanSequence: []atc.Step{
   495  							{
   496  								Config: &atc.GetStep{
   497  									Name:   "some-resource",
   498  									Passed: []string{"downstream-job"},
   499  								},
   500  							},
   501  						},
   502  					},
   503  				},
   504  				Resources: atc.ResourceConfigs{
   505  					{
   506  						Name:   "some-resource",
   507  						Type:   dbtest.BaseResourceType,
   508  						Source: atc.Source{"some": "source"},
   509  					},
   510  					{
   511  						Name:   "some-other-resource",
   512  						Type:   dbtest.BaseResourceType,
   513  						Source: atc.Source{"some": "other-source"},
   514  					},
   515  				},
   516  			}
   517  
   518  			scenario = dbtest.Setup(
   519  				builder.WithPipeline(pipelineConfig),
   520  				builder.WithResourceVersions(
   521  					"some-resource",
   522  					atc.Version{"ver": "1"},
   523  					atc.Version{"ver": "2"},
   524  				),
   525  				builder.WithResourceVersions(
   526  					"some-other-resource",
   527  					atc.Version{"ver": "1"},
   528  					atc.Version{"ver": "2"},
   529  					atc.Version{"ver": "3"},
   530  				),
   531  				builder.WithJobBuild(&build, "some-job", dbtest.JobInputs{
   532  					{
   533  						Name:    "input-1",
   534  						Version: atc.Version{"ver": "1"},
   535  					},
   536  					{
   537  						Name:    "input-2",
   538  						Version: atc.Version{"ver": "3"},
   539  					},
   540  					{
   541  						Name:    "input-3",
   542  						Version: atc.Version{"ver": "2"},
   543  					},
   544  					{
   545  						Name:    "input-4",
   546  						Version: atc.Version{"ver": "2"},
   547  					},
   548  				}, dbtest.JobOutputs{
   549  					"output-1": atc.Version{"ver": "2"},
   550  					"output-2": atc.Version{"ver": "3"},
   551  				}),
   552  			)
   553  
   554  			Expect(build.Finish(db.BuildStatusSucceeded)).To(Succeed())
   555  
   556  			expectedOutputs = []db.AlgorithmVersion{
   557  				{
   558  					Version:    db.ResourceVersion(convertToMD5(atc.Version{"ver": "1"})),
   559  					ResourceID: scenario.Resource("some-resource").ID(),
   560  				},
   561  				{
   562  					Version:    db.ResourceVersion(convertToMD5(atc.Version{"ver": "3"})),
   563  					ResourceID: scenario.Resource("some-other-resource").ID(),
   564  				},
   565  				{
   566  					Version:    db.ResourceVersion(convertToMD5(atc.Version{"ver": "2"})),
   567  					ResourceID: scenario.Resource("some-resource").ID(),
   568  				},
   569  				{
   570  					Version:    db.ResourceVersion(convertToMD5(atc.Version{"ver": "3"})),
   571  					ResourceID: scenario.Resource("some-resource").ID(),
   572  				},
   573  			}
   574  		})
   575  
   576  		It("creates Finish event", func() {
   577  			found, err := build.Reload()
   578  			Expect(err).NotTo(HaveOccurred())
   579  			Expect(found).To(BeTrue())
   580  			Expect(build.Status()).To(Equal(db.BuildStatusSucceeded))
   581  
   582  			events, err := build.Events(0)
   583  			Expect(err).NotTo(HaveOccurred())
   584  
   585  			defer db.Close(events)
   586  
   587  			Expect(events.Next()).To(Equal(envelope(event.Status{
   588  				Status: atc.StatusSucceeded,
   589  				Time:   build.EndTime().Unix(),
   590  			})))
   591  		})
   592  
   593  		It("updates build status", func() {
   594  			found, err := build.Reload()
   595  			Expect(err).NotTo(HaveOccurred())
   596  			Expect(found).To(BeTrue())
   597  			Expect(build.Status()).To(Equal(db.BuildStatusSucceeded))
   598  		})
   599  
   600  		It("clears out the private plan", func() {
   601  			found, err := build.Reload()
   602  			Expect(err).NotTo(HaveOccurred())
   603  			Expect(found).To(BeTrue())
   604  			Expect(build.PrivatePlan()).To(Equal(atc.Plan{}))
   605  		})
   606  
   607  		It("sets completed to true", func() {
   608  			Expect(build.IsCompleted()).To(BeFalse())
   609  			Expect(build.IsRunning()).To(BeTrue())
   610  
   611  			found, err := build.Reload()
   612  			Expect(err).NotTo(HaveOccurred())
   613  			Expect(found).To(BeTrue())
   614  			Expect(build.IsCompleted()).To(BeTrue())
   615  			Expect(build.IsRunning()).To(BeFalse())
   616  		})
   617  
   618  		It("inserts inputs and outputs into successful build versions", func() {
   619  			outputs, err := versionsDB.SuccessfulBuildOutputs(ctx, build.ID())
   620  			Expect(err).NotTo(HaveOccurred())
   621  			Expect(outputs).To(ConsistOf(expectedOutputs))
   622  		})
   623  
   624  		Context("rerunning a build", func() {
   625  			var (
   626  				job db.Job
   627  
   628  				pdBuild, pdBuild2, rrBuild db.Build
   629  				err                        error
   630  				latestCompletedBuildCol    = "latest_completed_build_id"
   631  				nextBuildCol               = "next_build_id"
   632  				transitionBuildCol         = "transition_build_id"
   633  			)
   634  
   635  			BeforeEach(func() {
   636  				job = scenario.Job("some-job")
   637  			})
   638  
   639  			Context("when there is a pending build that is not a rerun", func() {
   640  				BeforeEach(func() {
   641  					pdBuild, err = job.CreateBuild()
   642  					Expect(err).NotTo(HaveOccurred())
   643  				})
   644  
   645  				Context("when rerunning the latest completed build", func() {
   646  					BeforeEach(func() {
   647  						rrBuild, err = job.RerunBuild(build)
   648  						Expect(err).NotTo(HaveOccurred())
   649  					})
   650  
   651  					Context("when the rerun finishes and status changed", func() {
   652  						BeforeEach(func() {
   653  							err = rrBuild.Finish(db.BuildStatusFailed)
   654  							Expect(err).NotTo(HaveOccurred())
   655  						})
   656  
   657  						It("updates job latest finished build id", func() {
   658  							Expect(getJobBuildID(latestCompletedBuildCol, job.ID())).To(Equal(rrBuild.ID()))
   659  						})
   660  
   661  						It("updates job next build id to the pending build", func() {
   662  							Expect(getJobBuildID(nextBuildCol, job.ID())).To(Equal(pdBuild.ID()))
   663  						})
   664  
   665  						It("updates transition build id to the rerun build", func() {
   666  							Expect(getJobBuildID(transitionBuildCol, job.ID())).To(Equal(rrBuild.ID()))
   667  						})
   668  					})
   669  
   670  					Context("when there is another pending build that is not a rerun and the first pending build finishes", func() {
   671  						BeforeEach(func() {
   672  							pdBuild2, err = job.CreateBuild()
   673  							Expect(err).NotTo(HaveOccurred())
   674  
   675  							err = pdBuild.Finish(db.BuildStatusSucceeded)
   676  							Expect(err).NotTo(HaveOccurred())
   677  						})
   678  
   679  						It("updates job next build id to be the next non rerun pending build", func() {
   680  							Expect(getJobBuildID(nextBuildCol, job.ID())).To(Equal(pdBuild2.ID()))
   681  						})
   682  
   683  						It("updates job latest finished build id", func() {
   684  							Expect(getJobBuildID(latestCompletedBuildCol, job.ID())).To(Equal(pdBuild.ID()))
   685  						})
   686  					})
   687  				})
   688  
   689  				Context("when rerunning the pending build and the pending build finished", func() {
   690  					BeforeEach(func() {
   691  						rrBuild, err = job.RerunBuild(pdBuild)
   692  						Expect(err).NotTo(HaveOccurred())
   693  
   694  						err = pdBuild.Finish(db.BuildStatusSucceeded)
   695  						Expect(err).NotTo(HaveOccurred())
   696  					})
   697  
   698  					It("updates job next build id to the rerun build", func() {
   699  						Expect(getJobBuildID(nextBuildCol, job.ID())).To(Equal(rrBuild.ID()))
   700  					})
   701  
   702  					It("updates job latest finished build id", func() {
   703  						Expect(getJobBuildID(latestCompletedBuildCol, job.ID())).To(Equal(pdBuild.ID()))
   704  					})
   705  
   706  					Context("when rerunning the rerun build", func() {
   707  						var rrBuild2 db.Build
   708  
   709  						BeforeEach(func() {
   710  							err = rrBuild.Finish(db.BuildStatusSucceeded)
   711  							Expect(err).NotTo(HaveOccurred())
   712  
   713  							rrBuild2, err = job.RerunBuild(rrBuild)
   714  							Expect(err).NotTo(HaveOccurred())
   715  						})
   716  
   717  						It("updates job next build id to the rerun build", func() {
   718  							Expect(getJobBuildID(nextBuildCol, job.ID())).To(Equal(rrBuild2.ID()))
   719  						})
   720  
   721  						It("updates job latest finished build id", func() {
   722  							Expect(getJobBuildID(latestCompletedBuildCol, job.ID())).To(Equal(rrBuild.ID()))
   723  						})
   724  					})
   725  				})
   726  
   727  				Context("when pending build finished and rerunning a non latest build and it finishes", func() {
   728  					BeforeEach(func() {
   729  						err = pdBuild.Finish(db.BuildStatusErrored)
   730  						Expect(err).NotTo(HaveOccurred())
   731  
   732  						rrBuild, err = job.RerunBuild(build)
   733  						Expect(err).NotTo(HaveOccurred())
   734  
   735  						err = rrBuild.Finish(db.BuildStatusSucceeded)
   736  						Expect(err).NotTo(HaveOccurred())
   737  					})
   738  
   739  					It("updates job next build id to nul", func() {
   740  						_, nextBuild, err := job.FinishedAndNextBuild()
   741  						Expect(err).NotTo(HaveOccurred())
   742  						Expect(nextBuild).To(BeNil())
   743  					})
   744  
   745  					It("does not updates job latest finished build id", func() {
   746  						Expect(getJobBuildID(latestCompletedBuildCol, job.ID())).To(Equal(pdBuild.ID()))
   747  					})
   748  
   749  					It("does not updates transition build id", func() {
   750  						Expect(getJobBuildID(transitionBuildCol, job.ID())).To(Equal(pdBuild.ID()))
   751  					})
   752  				})
   753  			})
   754  		})
   755  
   756  		Context("when requesting schedule", func() {
   757  			It("request schedule on the downstream job", func() {
   758  				job := scenario.Job("some-job")
   759  				downstreamJob := scenario.Job("downstream-job")
   760  
   761  				newBuild, err := job.CreateBuild()
   762  				Expect(err).NotTo(HaveOccurred())
   763  
   764  				requestedSchedule := downstreamJob.ScheduleRequestedTime()
   765  
   766  				err = newBuild.Finish(db.BuildStatusSucceeded)
   767  				Expect(err).NotTo(HaveOccurred())
   768  
   769  				found, err := downstreamJob.Reload()
   770  				Expect(err).ToNot(HaveOccurred())
   771  				Expect(found).To(BeTrue())
   772  
   773  				Expect(downstreamJob.ScheduleRequestedTime()).Should(BeTemporally(">", requestedSchedule))
   774  			})
   775  
   776  			It("do not request schedule on jobs that are not directly downstream", func() {
   777  				job := scenario.Job("some-job")
   778  				noRequestJob := scenario.Job("no-request-job")
   779  
   780  				newBuild, err := job.CreateBuild()
   781  				Expect(err).NotTo(HaveOccurred())
   782  
   783  				requestedSchedule := noRequestJob.ScheduleRequestedTime()
   784  
   785  				err = newBuild.Finish(db.BuildStatusSucceeded)
   786  				Expect(err).NotTo(HaveOccurred())
   787  
   788  				found, err := noRequestJob.Reload()
   789  				Expect(err).ToNot(HaveOccurred())
   790  				Expect(found).To(BeTrue())
   791  
   792  				Expect(noRequestJob.ScheduleRequestedTime()).Should(BeTemporally("==", requestedSchedule))
   793  			})
   794  		})
   795  
   796  		Context("archiving pipelines", func() {
   797  			var childPipeline db.Pipeline
   798  
   799  			BeforeEach(func() {
   800  				By("creating a child pipeline")
   801  				build, _ := defaultJob.CreateBuild()
   802  				childPipeline, _, _ = build.SavePipeline(atc.PipelineRef{Name: "child1-pipeline"}, defaultTeam.ID(), defaultPipelineConfig, db.ConfigVersion(0), false)
   803  				build.Finish(db.BuildStatusSucceeded)
   804  
   805  				childPipeline.Reload()
   806  				Expect(childPipeline.Archived()).To(BeFalse())
   807  			})
   808  
   809  			Context("build is successful", func() {
   810  				It("archives pipelines no longer set by the job", func() {
   811  					By("no longer setting the child pipeline")
   812  					build2, _ := defaultJob.CreateBuild()
   813  					build2.Finish(db.BuildStatusSucceeded)
   814  
   815  					childPipeline.Reload()
   816  					Expect(childPipeline.Archived()).To(BeTrue())
   817  				})
   818  
   819  				Context("chain of pipelines setting each other... like a russian doll set...", func() {
   820  					It("archives all descendent pipelines", func() {
   821  						childPipelines := []db.Pipeline{childPipeline}
   822  
   823  						By("creating a chain of pipelines, previous pipeline setting the next pipeline")
   824  						for i := 0; i < 5; i++ {
   825  							job, _, _ := childPipeline.Job("some-job")
   826  							build, _ := job.CreateBuild()
   827  							childPipeline, _, _ = build.SavePipeline(atc.PipelineRef{Name: "child-pipeline-" + strconv.Itoa(i)}, defaultTeam.ID(), defaultPipelineConfig, db.ConfigVersion(0), false)
   828  							build.Finish(db.BuildStatusSucceeded)
   829  							childPipelines = append(childPipelines, childPipeline)
   830  						}
   831  
   832  						By("parent pipeline no longer sets child pipeline in most recent build")
   833  						build, _ := defaultJob.CreateBuild()
   834  						build.Finish(db.BuildStatusSucceeded)
   835  
   836  						for _, pipeline := range childPipelines {
   837  							pipeline.Reload()
   838  							Expect(pipeline.Archived()).To(BeTrue())
   839  						}
   840  
   841  					})
   842  				})
   843  
   844  				Context("when the pipeline is not set by build", func() {
   845  					It("never gets archived", func() {
   846  						build, _ := defaultJob.CreateBuild()
   847  						teamPipeline, _, _ := defaultTeam.SavePipeline(atc.PipelineRef{Name: "team-pipeline"}, defaultPipelineConfig, db.ConfigVersion(0), false)
   848  						build.Finish(db.BuildStatusSucceeded)
   849  
   850  						teamPipeline.Reload()
   851  						Expect(teamPipeline.Archived()).To(BeFalse())
   852  					})
   853  				})
   854  			})
   855  			Context("build is not successful", func() {
   856  				It("does not archive pipelines", func() {
   857  					By("no longer setting the child pipeline")
   858  					build2, _ := defaultJob.CreateBuild()
   859  					build2.Finish(db.BuildStatusFailed)
   860  
   861  					childPipeline.Reload()
   862  					Expect(childPipeline.Archived()).To(BeFalse())
   863  				})
   864  			})
   865  		})
   866  	})
   867  
   868  	Describe("Variables", func() {
   869  		var (
   870  			globalSecrets creds.Secrets
   871  			varSourcePool creds.VarSourcePool
   872  		)
   873  
   874  		BeforeEach(func() {
   875  			globalSecrets = &dummy.Secrets{StaticVariables: vars.StaticVariables{"foo": "bar"}}
   876  
   877  			credentialManagement := creds.CredentialManagementConfig{
   878  				RetryConfig: creds.SecretRetryConfig{
   879  					Attempts: 5,
   880  					Interval: time.Second,
   881  				},
   882  				CacheConfig: creds.SecretCacheConfig{
   883  					Enabled:          true,
   884  					Duration:         time.Minute,
   885  					DurationNotFound: time.Minute,
   886  					PurgeInterval:    time.Minute * 10,
   887  				},
   888  			}
   889  			varSourcePool = creds.NewVarSourcePool(logger, credentialManagement, 1*time.Minute, 1*time.Second, clock.NewClock())
   890  		})
   891  
   892  		Context("when the build is a one-off build", func() {
   893  			var build db.Build
   894  
   895  			BeforeEach(func() {
   896  				var err error
   897  				build, err = defaultTeam.CreateOneOffBuild()
   898  				Expect(err).ToNot(HaveOccurred())
   899  			})
   900  
   901  			It("fetches from the global secrets", func() {
   902  				v, err := build.Variables(logger, globalSecrets, varSourcePool)
   903  				Expect(err).ToNot(HaveOccurred())
   904  
   905  				val, found, err := v.Get(vars.Reference{Path: "foo"})
   906  				Expect(err).ToNot(HaveOccurred())
   907  				Expect(found).To(BeTrue())
   908  				Expect(val).To(Equal("bar"))
   909  			})
   910  		})
   911  
   912  		Context("when the build is a job build", func() {
   913  			var build db.Build
   914  
   915  			BeforeEach(func() {
   916  				config := defaultPipelineConfig
   917  				config.VarSources = append(config.VarSources, atc.VarSourceConfig{
   918  					Name: "some-source",
   919  					Type: "dummy",
   920  					Config: map[string]interface{}{
   921  						"vars": map[string]interface{}{"baz": "caz"},
   922  					},
   923  				})
   924  
   925  				pipeline, _, err := defaultTeam.SavePipeline(defaultPipelineRef, config, defaultPipeline.ConfigVersion(), false)
   926  				Expect(err).ToNot(HaveOccurred())
   927  
   928  				job, found, err := pipeline.Job(defaultJob.Name())
   929  				Expect(err).ToNot(HaveOccurred())
   930  				Expect(found).To(BeTrue())
   931  
   932  				build, err = job.CreateBuild()
   933  				Expect(err).ToNot(HaveOccurred())
   934  			})
   935  
   936  			It("fetches from the global secrets", func() {
   937  				v, err := build.Variables(logger, globalSecrets, varSourcePool)
   938  				Expect(err).ToNot(HaveOccurred())
   939  
   940  				val, found, err := v.Get(vars.Reference{Path: "foo"})
   941  				Expect(err).ToNot(HaveOccurred())
   942  				Expect(found).To(BeTrue())
   943  				Expect(val).To(Equal("bar"))
   944  			})
   945  
   946  			It("fetches from the var sources", func() {
   947  				v, err := build.Variables(logger, globalSecrets, varSourcePool)
   948  				Expect(err).ToNot(HaveOccurred())
   949  
   950  				val, found, err := v.Get(vars.Reference{Source: "some-source", Path: "baz"})
   951  				Expect(err).ToNot(HaveOccurred())
   952  				Expect(found).To(BeTrue())
   953  				Expect(val).To(Equal("caz"))
   954  			})
   955  		})
   956  	})
   957  
   958  	Describe("Abort", func() {
   959  		JustBeforeEach(func() {
   960  			err := build.MarkAsAborted()
   961  			Expect(err).NotTo(HaveOccurred())
   962  
   963  			found, err := build.Reload()
   964  			Expect(err).NotTo(HaveOccurred())
   965  			Expect(found).To(BeTrue())
   966  		})
   967  
   968  		It("updates aborted to true", func() {
   969  			Expect(build.IsAborted()).To(BeTrue())
   970  		})
   971  
   972  		Context("request job rescheudle", func() {
   973  			JustBeforeEach(func() {
   974  				found, err := job.Reload()
   975  				Expect(err).NotTo(HaveOccurred())
   976  				Expect(found).To(BeTrue())
   977  			})
   978  
   979  			Context("when build is in pending state", func() {
   980  				BeforeEach(func() {
   981  					time.Sleep(1 * time.Second)
   982  				})
   983  
   984  				It("requests the job to reschedule immediately", func() {
   985  					Expect(job.ScheduleRequestedTime()).Should(BeTemporally("~", time.Now(), time.Second))
   986  				})
   987  			})
   988  
   989  			Context("when build is not in pending state", func() {
   990  				var firstRequestTime time.Time
   991  
   992  				BeforeEach(func() {
   993  					firstRequestTime = time.Now()
   994  
   995  					time.Sleep(1 * time.Second)
   996  
   997  					err := build.Finish(db.BuildStatusFailed)
   998  					Expect(err).NotTo(HaveOccurred())
   999  
  1000  					found, err := build.Reload()
  1001  					Expect(err).NotTo(HaveOccurred())
  1002  					Expect(found).To(BeTrue())
  1003  				})
  1004  
  1005  				It("does not request reschedule", func() {
  1006  					Expect(job.ScheduleRequestedTime()).Should(BeTemporally("~", firstRequestTime, time.Second))
  1007  				})
  1008  			})
  1009  		})
  1010  	})
  1011  
  1012  	Describe("Events", func() {
  1013  		It("saves and emits status events", func() {
  1014  			By("allowing you to subscribe when no events have yet occurred")
  1015  			events, err := build.Events(0)
  1016  			Expect(err).NotTo(HaveOccurred())
  1017  
  1018  			defer db.Close(events)
  1019  
  1020  			By("emitting a status event when started")
  1021  			started, err := build.Start(atc.Plan{})
  1022  			Expect(err).NotTo(HaveOccurred())
  1023  			Expect(started).To(BeTrue())
  1024  
  1025  			found, err := build.Reload()
  1026  			Expect(err).NotTo(HaveOccurred())
  1027  			Expect(found).To(BeTrue())
  1028  
  1029  			Expect(events.Next()).To(Equal(envelope(event.Status{
  1030  				Status: atc.StatusStarted,
  1031  				Time:   build.StartTime().Unix(),
  1032  			})))
  1033  
  1034  			By("emitting a status event when finished")
  1035  			err = build.Finish(db.BuildStatusSucceeded)
  1036  			Expect(err).NotTo(HaveOccurred())
  1037  
  1038  			found, err = build.Reload()
  1039  			Expect(err).NotTo(HaveOccurred())
  1040  			Expect(found).To(BeTrue())
  1041  
  1042  			Expect(events.Next()).To(Equal(envelope(event.Status{
  1043  				Status: atc.StatusSucceeded,
  1044  				Time:   build.EndTime().Unix(),
  1045  			})))
  1046  
  1047  			By("ending the stream when finished")
  1048  			_, err = events.Next()
  1049  			Expect(err).To(Equal(db.ErrEndOfBuildEventStream))
  1050  		})
  1051  
  1052  		It("emits pre-bigint migration events", func() {
  1053  			started, err := build.Start(atc.Plan{})
  1054  			Expect(err).NotTo(HaveOccurred())
  1055  			Expect(started).To(BeTrue())
  1056  
  1057  			found, err := build.Reload()
  1058  			Expect(err).NotTo(HaveOccurred())
  1059  			Expect(found).To(BeTrue())
  1060  
  1061  			_, err = dbConn.Exec(`UPDATE build_events SET build_id_old = build_id, build_id = NULL WHERE build_id = $1`, build.ID())
  1062  			Expect(err).NotTo(HaveOccurred())
  1063  
  1064  			events, err := build.Events(0)
  1065  			Expect(err).NotTo(HaveOccurred())
  1066  
  1067  			defer db.Close(events)
  1068  			Expect(events.Next()).To(Equal(envelope(event.Status{
  1069  				Status: atc.StatusStarted,
  1070  				Time:   build.StartTime().Unix(),
  1071  			})))
  1072  		})
  1073  	})
  1074  
  1075  	Describe("SaveEvent", func() {
  1076  		It("saves and propagates events correctly", func() {
  1077  			By("allowing you to subscribe when no events have yet occurred")
  1078  			events, err := build.Events(0)
  1079  			Expect(err).NotTo(HaveOccurred())
  1080  
  1081  			defer db.Close(events)
  1082  
  1083  			By("saving them in order")
  1084  			err = build.SaveEvent(event.Log{
  1085  				Payload: "some ",
  1086  			})
  1087  			Expect(err).NotTo(HaveOccurred())
  1088  
  1089  			Expect(events.Next()).To(Equal(envelope(event.Log{
  1090  				Payload: "some ",
  1091  			})))
  1092  
  1093  			err = build.SaveEvent(event.Log{
  1094  				Payload: "log",
  1095  			})
  1096  			Expect(err).NotTo(HaveOccurred())
  1097  
  1098  			Expect(events.Next()).To(Equal(envelope(event.Log{
  1099  				Payload: "log",
  1100  			})))
  1101  
  1102  			By("allowing you to subscribe from an offset")
  1103  			eventsFrom1, err := build.Events(1)
  1104  			Expect(err).NotTo(HaveOccurred())
  1105  
  1106  			defer db.Close(eventsFrom1)
  1107  
  1108  			Expect(eventsFrom1.Next()).To(Equal(envelope(event.Log{
  1109  				Payload: "log",
  1110  			})))
  1111  
  1112  			By("notifying those waiting on events as soon as they're saved")
  1113  			nextEvent := make(chan event.Envelope)
  1114  			nextErr := make(chan error)
  1115  
  1116  			go func() {
  1117  				event, err := events.Next()
  1118  				if err != nil {
  1119  					nextErr <- err
  1120  				} else {
  1121  					nextEvent <- event
  1122  				}
  1123  			}()
  1124  
  1125  			Consistently(nextEvent).ShouldNot(Receive())
  1126  			Consistently(nextErr).ShouldNot(Receive())
  1127  
  1128  			err = build.SaveEvent(event.Log{
  1129  				Payload: "log 2",
  1130  			})
  1131  			Expect(err).NotTo(HaveOccurred())
  1132  
  1133  			Eventually(nextEvent).Should(Receive(Equal(envelope(event.Log{
  1134  				Payload: "log 2",
  1135  			}))))
  1136  
  1137  			By("returning ErrBuildEventStreamClosed for Next calls after Close")
  1138  			events3, err := build.Events(0)
  1139  			Expect(err).NotTo(HaveOccurred())
  1140  
  1141  			err = events3.Close()
  1142  			Expect(err).NotTo(HaveOccurred())
  1143  
  1144  			Eventually(func() error {
  1145  				_, err := events3.Next()
  1146  				return err
  1147  			}).Should(Equal(db.ErrBuildEventStreamClosed))
  1148  		})
  1149  	})
  1150  
  1151  	Describe("SaveOutput", func() {
  1152  		var pipelineConfig atc.Config
  1153  
  1154  		var scenario *dbtest.Scenario
  1155  		var build db.Build
  1156  
  1157  		var outputVersion atc.Version
  1158  
  1159  		BeforeEach(func() {
  1160  			atc.EnableGlobalResources = true
  1161  
  1162  			pipelineConfig = atc.Config{
  1163  				Jobs: atc.JobConfigs{
  1164  					{
  1165  						Name: "some-job",
  1166  						PlanSequence: []atc.Step{
  1167  							{
  1168  								Config: &atc.GetStep{
  1169  									Name: "some-resource",
  1170  								},
  1171  							},
  1172  						},
  1173  					},
  1174  					{
  1175  						Name: "some-other-job",
  1176  						PlanSequence: []atc.Step{
  1177  							{
  1178  								Config: &atc.GetStep{
  1179  									Name: "some-other-resource",
  1180  								},
  1181  							},
  1182  						},
  1183  					},
  1184  				},
  1185  				Resources: atc.ResourceConfigs{
  1186  					{
  1187  						Name:   "some-resource",
  1188  						Type:   dbtest.BaseResourceType,
  1189  						Source: atc.Source{"some": "source"},
  1190  					},
  1191  					{
  1192  						Name:   "some-other-resource",
  1193  						Type:   dbtest.BaseResourceType,
  1194  						Source: atc.Source{"some": "other-source"},
  1195  					},
  1196  				},
  1197  			}
  1198  
  1199  			scenario = dbtest.Setup(
  1200  				builder.WithPipeline(pipelineConfig),
  1201  				builder.WithResourceVersions("some-resource", atc.Version{"some": "version"}),
  1202  				builder.WithResourceVersions("some-other-resource", atc.Version{"some": "other-version"}),
  1203  				builder.WithJobBuild(&build, "some-job", dbtest.JobInputs{
  1204  					{
  1205  						Name:    "some-resource",
  1206  						Version: atc.Version{"some": "version"},
  1207  					},
  1208  				}, dbtest.JobOutputs{}),
  1209  			)
  1210  
  1211  			outputVersion = atc.Version{"some": "new-version"}
  1212  		})
  1213  
  1214  		JustBeforeEach(func() {
  1215  			err := build.SaveOutput(
  1216  				dbtest.BaseResourceType,
  1217  				atc.Source{"some": "source"},
  1218  				atc.VersionedResourceTypes{},
  1219  				outputVersion,
  1220  				[]db.ResourceConfigMetadataField{
  1221  					{
  1222  						Name:  "meta",
  1223  						Value: "data",
  1224  					},
  1225  				},
  1226  				"output-name",
  1227  				"some-resource",
  1228  			)
  1229  			Expect(err).ToNot(HaveOccurred())
  1230  		})
  1231  
  1232  		AfterEach(func() {
  1233  			atc.EnableGlobalResources = false
  1234  		})
  1235  
  1236  		It("should set the resource's config scope", func() {
  1237  			resource, found, err := scenario.Pipeline.Resource("some-resource")
  1238  			Expect(err).ToNot(HaveOccurred())
  1239  			Expect(found).To(BeTrue())
  1240  			Expect(resource.ResourceConfigScopeID()).ToNot(BeZero())
  1241  		})
  1242  
  1243  		Context("when the version does not exist", func() {
  1244  			It("can save a build's output", func() {
  1245  				rcv := scenario.ResourceVersion("some-resource", outputVersion)
  1246  				Expect(rcv.Version()).To(Equal(db.Version(outputVersion)))
  1247  
  1248  				_, buildOutputs, err := build.Resources()
  1249  				Expect(err).ToNot(HaveOccurred())
  1250  				Expect(len(buildOutputs)).To(Equal(1))
  1251  				Expect(buildOutputs[0].Name).To(Equal("output-name"))
  1252  				Expect(buildOutputs[0].Version).To(Equal(outputVersion))
  1253  			})
  1254  
  1255  			Context("with a job in a separate team downstream of the same resource config", func() {
  1256  				var otherScenario *dbtest.Scenario
  1257  
  1258  				var beforeTime, otherJobBeforeTime time.Time
  1259  				var otherTeamBeforeTime, otherTeamOtherJobBeforeTime time.Time
  1260  
  1261  				BeforeEach(func() {
  1262  					otherScenario = dbtest.Setup(
  1263  						builder.WithTeam("other-team"),
  1264  						builder.WithPipeline(pipelineConfig),
  1265  						builder.WithResourceVersions("some-resource", atc.Version{"some": "version"}),
  1266  						builder.WithResourceVersions("some-other-resource", atc.Version{"some": "other-version"}),
  1267  					)
  1268  
  1269  					beforeTime = scenario.Job("some-job").ScheduleRequestedTime()
  1270  					otherTeamBeforeTime = otherScenario.Job("some-job").ScheduleRequestedTime()
  1271  
  1272  					otherJobBeforeTime = scenario.Job("some-other-job").ScheduleRequestedTime()
  1273  					otherTeamOtherJobBeforeTime = otherScenario.Job("some-other-job").ScheduleRequestedTime()
  1274  				})
  1275  
  1276  				It("requests schedule on jobs which use the same config", func() {
  1277  					Expect(scenario.Job("some-job").ScheduleRequestedTime()).To(BeTemporally(">", beforeTime))
  1278  					Expect(otherScenario.Job("some-job").ScheduleRequestedTime()).To(BeTemporally(">", otherTeamBeforeTime))
  1279  
  1280  					Expect(scenario.Job("some-other-job").ScheduleRequestedTime()).To(BeTemporally("==", otherJobBeforeTime))
  1281  					Expect(otherScenario.Job("some-other-job").ScheduleRequestedTime()).To(BeTemporally("==", otherTeamOtherJobBeforeTime))
  1282  				})
  1283  			})
  1284  		})
  1285  
  1286  		Context("when the version already exists", func() {
  1287  			var rcv db.ResourceConfigVersion
  1288  
  1289  			BeforeEach(func() {
  1290  				scenario.Run(
  1291  					builder.WithResourceVersions("some-resource", outputVersion),
  1292  				)
  1293  
  1294  				rcv = scenario.ResourceVersion("some-resource", outputVersion)
  1295  			})
  1296  
  1297  			It("does not increment the check order", func() {
  1298  				newRCV := scenario.ResourceVersion("some-resource", outputVersion)
  1299  				Expect(newRCV.CheckOrder()).To(Equal(rcv.CheckOrder()))
  1300  			})
  1301  
  1302  			Context("with a job in a separate team downstream of the same resource config", func() {
  1303  				var otherScenario *dbtest.Scenario
  1304  
  1305  				var beforeTime, otherJobBeforeTime time.Time
  1306  				var otherTeamBeforeTime, otherTeamOtherJobBeforeTime time.Time
  1307  
  1308  				BeforeEach(func() {
  1309  					otherScenario = dbtest.Setup(
  1310  						builder.WithTeam("other-team"),
  1311  						builder.WithPipeline(pipelineConfig),
  1312  						builder.WithResourceVersions("some-resource", atc.Version{"some": "version"}),
  1313  						builder.WithResourceVersions("some-other-resource", atc.Version{"some": "other-version"}),
  1314  					)
  1315  
  1316  					beforeTime = scenario.Job("some-job").ScheduleRequestedTime()
  1317  					otherTeamBeforeTime = otherScenario.Job("some-job").ScheduleRequestedTime()
  1318  
  1319  					otherJobBeforeTime = scenario.Job("some-other-job").ScheduleRequestedTime()
  1320  					otherTeamOtherJobBeforeTime = otherScenario.Job("some-other-job").ScheduleRequestedTime()
  1321  				})
  1322  
  1323  				It("does not request schedule on jobs which use the same config", func() {
  1324  					Expect(scenario.Job("some-job").ScheduleRequestedTime()).To(BeTemporally("==", beforeTime))
  1325  					Expect(otherScenario.Job("some-job").ScheduleRequestedTime()).To(BeTemporally("==", otherTeamBeforeTime))
  1326  
  1327  					Expect(scenario.Job("some-other-job").ScheduleRequestedTime()).To(BeTemporally("==", otherJobBeforeTime))
  1328  					Expect(otherScenario.Job("some-other-job").ScheduleRequestedTime()).To(BeTemporally("==", otherTeamOtherJobBeforeTime))
  1329  				})
  1330  			})
  1331  		})
  1332  	})
  1333  
  1334  	Describe("Resources", func() {
  1335  		var (
  1336  			scenario      *dbtest.Scenario
  1337  			build         db.Build
  1338  			inputResource db.Resource
  1339  		)
  1340  
  1341  		BeforeEach(func() {
  1342  			pipelineConfig := atc.Config{
  1343  				Jobs: atc.JobConfigs{
  1344  					{
  1345  						Name: "some-job",
  1346  						PlanSequence: []atc.Step{
  1347  							{
  1348  								Config: &atc.GetStep{
  1349  									Name:     "some-input",
  1350  									Resource: "some-resource",
  1351  								},
  1352  							},
  1353  							{
  1354  								Config: &atc.PutStep{
  1355  									Name: "some-resource",
  1356  								},
  1357  							},
  1358  							{
  1359  								Config: &atc.PutStep{
  1360  									Name: "some-other-resource",
  1361  								},
  1362  							},
  1363  						},
  1364  					},
  1365  				},
  1366  				Resources: atc.ResourceConfigs{
  1367  					{
  1368  						Name:   "some-resource",
  1369  						Type:   dbtest.BaseResourceType,
  1370  						Source: atc.Source{"some": "source-1"},
  1371  					},
  1372  					{
  1373  						Name:   "some-other-resource",
  1374  						Type:   dbtest.BaseResourceType,
  1375  						Source: atc.Source{"some": "source-2"},
  1376  					},
  1377  					{
  1378  						Name:   "some-unused-resource",
  1379  						Type:   dbtest.BaseResourceType,
  1380  						Source: atc.Source{"some": "source-3"},
  1381  					},
  1382  				},
  1383  			}
  1384  
  1385  			scenario = dbtest.Setup(
  1386  				builder.WithPipeline(pipelineConfig),
  1387  				builder.WithResourceVersions(
  1388  					"some-resource",
  1389  					atc.Version{"ver": "1"},
  1390  					atc.Version{"ver": "2"},
  1391  				),
  1392  				builder.WithJobBuild(&build, "some-job", dbtest.JobInputs{
  1393  					{
  1394  						Name:            "some-input",
  1395  						Version:         atc.Version{"ver": "1"},
  1396  						FirstOccurrence: true,
  1397  					},
  1398  				}, dbtest.JobOutputs{
  1399  					"some-resource":       atc.Version{"ver": "2"},
  1400  					"some-other-resource": atc.Version{"ver": "not-checked"},
  1401  				}),
  1402  			)
  1403  
  1404  			inputResource = scenario.Resource("some-resource")
  1405  		})
  1406  
  1407  		It("returns build inputs and outputs", func() {
  1408  			inputs, outputs, err := build.Resources()
  1409  			Expect(err).NotTo(HaveOccurred())
  1410  
  1411  			Expect(inputs).To(ConsistOf([]db.BuildInput{
  1412  				{
  1413  					Name:            "some-input",
  1414  					Version:         atc.Version{"ver": "1"},
  1415  					ResourceID:      inputResource.ID(),
  1416  					FirstOccurrence: true,
  1417  				},
  1418  			}))
  1419  
  1420  			Expect(outputs).To(ConsistOf([]db.BuildOutput{
  1421  				{
  1422  					Name:    "some-resource",
  1423  					Version: atc.Version{"ver": "2"},
  1424  				},
  1425  				{
  1426  					Name:    "some-other-resource",
  1427  					Version: atc.Version{"ver": "not-checked"},
  1428  				},
  1429  			}))
  1430  		})
  1431  
  1432  		Context("when the first occurrence is empty", func() {
  1433  			BeforeEach(func() {
  1434  				res, err := psql.Update("build_resource_config_version_inputs").
  1435  					Set("first_occurrence", nil).
  1436  					Where(sq.Eq{
  1437  						"build_id":    build.ID(),
  1438  						"resource_id": inputResource.ID(),
  1439  						"version_md5": convertToMD5(atc.Version{"ver": "1"}),
  1440  					}).
  1441  					RunWith(dbConn).
  1442  					Exec()
  1443  				Expect(err).NotTo(HaveOccurred())
  1444  				rows, err := res.RowsAffected()
  1445  				Expect(err).NotTo(HaveOccurred())
  1446  				Expect(rows).To(Equal(int64(1)))
  1447  			})
  1448  
  1449  			It("determines the first occurrence to be true", func() {
  1450  				inputs, _, err := build.Resources()
  1451  				Expect(err).NotTo(HaveOccurred())
  1452  				Expect(inputs).To(ConsistOf([]db.BuildInput{
  1453  					{
  1454  						Name:            "some-input",
  1455  						Version:         atc.Version{"ver": "1"},
  1456  						ResourceID:      inputResource.ID(),
  1457  						FirstOccurrence: true,
  1458  					},
  1459  				}))
  1460  			})
  1461  
  1462  			Context("when the a build with those inputs already exist", func() {
  1463  				var newBuild db.Build
  1464  
  1465  				BeforeEach(func() {
  1466  					scenario.Run(
  1467  						builder.WithJobBuild(&newBuild, "some-job", dbtest.JobInputs{
  1468  							{
  1469  								Name:            "some-input",
  1470  								Version:         atc.Version{"ver": "1"},
  1471  								FirstOccurrence: true,
  1472  							},
  1473  						}, dbtest.JobOutputs{
  1474  							"some-resource":       atc.Version{"ver": "2"},
  1475  							"some-other-resource": atc.Version{"ver": "not-checked"},
  1476  						}),
  1477  					)
  1478  
  1479  					res, err := psql.Update("build_resource_config_version_inputs").
  1480  						Set("first_occurrence", nil).
  1481  						Where(sq.Eq{
  1482  							"build_id":    newBuild.ID(),
  1483  							"resource_id": inputResource.ID(),
  1484  							"version_md5": convertToMD5(atc.Version{"ver": "1"}),
  1485  						}).
  1486  						RunWith(dbConn).
  1487  						Exec()
  1488  					Expect(err).NotTo(HaveOccurred())
  1489  
  1490  					rows, err := res.RowsAffected()
  1491  					Expect(err).NotTo(HaveOccurred())
  1492  					Expect(rows).To(Equal(int64(1)))
  1493  				})
  1494  
  1495  				It("determines the first occurrence to be false", func() {
  1496  					inputs, _, err := newBuild.Resources()
  1497  					Expect(err).NotTo(HaveOccurred())
  1498  					Expect(inputs).To(ConsistOf([]db.BuildInput{
  1499  						{
  1500  							Name:            "some-input",
  1501  							Version:         atc.Version{"ver": "1"},
  1502  							ResourceID:      inputResource.ID(),
  1503  							FirstOccurrence: false,
  1504  						},
  1505  					}))
  1506  				})
  1507  			})
  1508  		})
  1509  	})
  1510  
  1511  	Describe("Pipeline", func() {
  1512  		var (
  1513  			build           db.Build
  1514  			foundPipeline   db.Pipeline
  1515  			createdPipeline db.Pipeline
  1516  			found           bool
  1517  		)
  1518  
  1519  		JustBeforeEach(func() {
  1520  			var err error
  1521  			foundPipeline, found, err = build.Pipeline()
  1522  			Expect(err).ToNot(HaveOccurred())
  1523  		})
  1524  
  1525  		Context("when a job build", func() {
  1526  			BeforeEach(func() {
  1527  				var err error
  1528  				createdPipeline, _, err = team.SavePipeline(atc.PipelineRef{Name: "some-pipeline"}, atc.Config{
  1529  					Jobs: atc.JobConfigs{
  1530  						{
  1531  							Name: "some-job",
  1532  						},
  1533  					},
  1534  				}, db.ConfigVersion(1), false)
  1535  				Expect(err).ToNot(HaveOccurred())
  1536  
  1537  				job, found, err := createdPipeline.Job("some-job")
  1538  				Expect(err).ToNot(HaveOccurred())
  1539  				Expect(found).To(BeTrue())
  1540  
  1541  				build, err = job.CreateBuild()
  1542  				Expect(err).ToNot(HaveOccurred())
  1543  			})
  1544  
  1545  			It("returns the correct pipeline", func() {
  1546  				Expect(found).To(BeTrue())
  1547  				Expect(foundPipeline.Name()).To(Equal(createdPipeline.Name()))
  1548  			})
  1549  		})
  1550  	})
  1551  
  1552  	Describe("Preparation", func() {
  1553  		var (
  1554  			scenario *dbtest.Scenario
  1555  			job      db.Job
  1556  
  1557  			expectedBuildPrep db.BuildPreparation
  1558  		)
  1559  
  1560  		BeforeEach(func() {
  1561  			scenario = dbtest.Setup(
  1562  				builder.WithPipeline(atc.Config{
  1563  					Resources: atc.ResourceConfigs{
  1564  						{
  1565  							Name: "some-resource",
  1566  							Type: dbtest.BaseResourceType,
  1567  							Source: atc.Source{
  1568  								"source-config": "some-value",
  1569  							},
  1570  						},
  1571  					},
  1572  					Jobs: atc.JobConfigs{
  1573  						{
  1574  							Name:           "some-job",
  1575  							RawMaxInFlight: 1,
  1576  							PlanSequence: []atc.Step{
  1577  								{
  1578  									Config: &atc.GetStep{
  1579  										Name:     "some-input",
  1580  										Resource: "some-resource",
  1581  									},
  1582  								},
  1583  							},
  1584  						},
  1585  					},
  1586  				}),
  1587  				builder.WithResourceVersions("some-resource", atc.Version{"version": "some-version"}),
  1588  				builder.WithPendingJobBuild(&build, "some-job"),
  1589  			)
  1590  
  1591  			job = scenario.Job("some-job")
  1592  
  1593  			expectedBuildPrep = db.BuildPreparation{
  1594  				BuildID:             build.ID(),
  1595  				PausedPipeline:      db.BuildPreparationStatusNotBlocking,
  1596  				PausedJob:           db.BuildPreparationStatusNotBlocking,
  1597  				MaxRunningBuilds:    db.BuildPreparationStatusNotBlocking,
  1598  				Inputs:              map[string]db.BuildPreparationStatus{},
  1599  				InputsSatisfied:     db.BuildPreparationStatusNotBlocking,
  1600  				MissingInputReasons: db.MissingInputReasons{},
  1601  			}
  1602  		})
  1603  
  1604  		Context("when inputs are satisfied", func() {
  1605  			BeforeEach(func() {
  1606  				scenario.Run(
  1607  					builder.WithNextInputMapping("some-job", dbtest.JobInputs{
  1608  						{
  1609  							Name:    "some-input",
  1610  							Version: atc.Version{"version": "some-version"},
  1611  						},
  1612  					}),
  1613  				)
  1614  			})
  1615  
  1616  			Context("when resource check finished after build created", func() {
  1617  				BeforeEach(func() {
  1618  					scenario.Run(
  1619  						// don't save any versions, just bump the last check timestamp
  1620  						builder.WithResourceVersions("some-resource"),
  1621  					)
  1622  
  1623  					expectedBuildPrep.Inputs = map[string]db.BuildPreparationStatus{
  1624  						"some-input": db.BuildPreparationStatusNotBlocking,
  1625  					}
  1626  				})
  1627  
  1628  				Context("when the build is started", func() {
  1629  					BeforeEach(func() {
  1630  						started, err := build.Start(atc.Plan{})
  1631  						Expect(started).To(BeTrue())
  1632  						Expect(err).NotTo(HaveOccurred())
  1633  
  1634  						stillExists, err := build.Reload()
  1635  						Expect(stillExists).To(BeTrue())
  1636  						Expect(err).NotTo(HaveOccurred())
  1637  
  1638  						expectedBuildPrep.Inputs = map[string]db.BuildPreparationStatus{}
  1639  					})
  1640  
  1641  					It("returns build preparation", func() {
  1642  						buildPrep, found, err := build.Preparation()
  1643  						Expect(err).NotTo(HaveOccurred())
  1644  						Expect(found).To(BeTrue())
  1645  						Expect(buildPrep).To(Equal(expectedBuildPrep))
  1646  					})
  1647  				})
  1648  
  1649  				Context("when pipeline is paused", func() {
  1650  					BeforeEach(func() {
  1651  						err := scenario.Pipeline.Pause()
  1652  						Expect(err).NotTo(HaveOccurred())
  1653  
  1654  						expectedBuildPrep.PausedPipeline = db.BuildPreparationStatusBlocking
  1655  					})
  1656  
  1657  					It("returns build preparation with paused pipeline", func() {
  1658  						buildPrep, found, err := build.Preparation()
  1659  						Expect(err).NotTo(HaveOccurred())
  1660  						Expect(found).To(BeTrue())
  1661  						Expect(buildPrep).To(Equal(expectedBuildPrep))
  1662  					})
  1663  				})
  1664  
  1665  				Context("when job is paused", func() {
  1666  					BeforeEach(func() {
  1667  						err := scenario.Job("some-job").Pause()
  1668  						Expect(err).NotTo(HaveOccurred())
  1669  
  1670  						expectedBuildPrep.PausedJob = db.BuildPreparationStatusBlocking
  1671  					})
  1672  
  1673  					It("returns build preparation with paused pipeline", func() {
  1674  						buildPrep, found, err := build.Preparation()
  1675  						Expect(err).NotTo(HaveOccurred())
  1676  						Expect(found).To(BeTrue())
  1677  						Expect(buildPrep).To(Equal(expectedBuildPrep))
  1678  					})
  1679  				})
  1680  
  1681  				Context("when max running builds is reached", func() {
  1682  					var secondBuild db.Build
  1683  
  1684  					BeforeEach(func() {
  1685  						scenario.Run(
  1686  							builder.WithPendingJobBuild(&secondBuild, "some-job"),
  1687  							// don't save any versions, just bump the last check timestamp
  1688  							builder.WithResourceVersions("some-resource"),
  1689  						)
  1690  
  1691  						scheduled, err := job.ScheduleBuild(build)
  1692  						Expect(err).ToNot(HaveOccurred())
  1693  						Expect(scheduled).To(BeTrue())
  1694  
  1695  						scheduled, err = job.ScheduleBuild(secondBuild)
  1696  						Expect(err).ToNot(HaveOccurred())
  1697  						Expect(scheduled).To(BeFalse())
  1698  
  1699  						expectedBuildPrep.BuildID = secondBuild.ID()
  1700  						expectedBuildPrep.MaxRunningBuilds = db.BuildPreparationStatusBlocking
  1701  					})
  1702  
  1703  					It("returns build preparation with max in flight reached", func() {
  1704  						buildPrep, found, err := secondBuild.Preparation()
  1705  						Expect(err).NotTo(HaveOccurred())
  1706  						Expect(found).To(BeTrue())
  1707  						Expect(buildPrep).To(Equal(expectedBuildPrep))
  1708  					})
  1709  
  1710  					Context("when max running builds is de-reached", func() {
  1711  						BeforeEach(func() {
  1712  							err := build.Finish(db.BuildStatusSucceeded)
  1713  							Expect(err).NotTo(HaveOccurred())
  1714  
  1715  							scheduled, err := job.ScheduleBuild(secondBuild)
  1716  							Expect(err).ToNot(HaveOccurred())
  1717  							Expect(scheduled).To(BeTrue())
  1718  
  1719  							expectedBuildPrep.MaxRunningBuilds = db.BuildPreparationStatusNotBlocking
  1720  						})
  1721  
  1722  						It("returns build preparation with max in flight not reached", func() {
  1723  							buildPrep, found, err := secondBuild.Preparation()
  1724  							Expect(err).NotTo(HaveOccurred())
  1725  							Expect(found).To(BeTrue())
  1726  							Expect(buildPrep).To(Equal(expectedBuildPrep))
  1727  						})
  1728  					})
  1729  				})
  1730  			})
  1731  
  1732  			Context("when no resource check finished after build created", func() {
  1733  				BeforeEach(func() {
  1734  					expectedBuildPrep.Inputs = map[string]db.BuildPreparationStatus{
  1735  						"some-input": db.BuildPreparationStatusBlocking,
  1736  					}
  1737  					expectedBuildPrep.InputsSatisfied = db.BuildPreparationStatusBlocking
  1738  					expectedBuildPrep.MissingInputReasons = db.MissingInputReasons{
  1739  						"some-input": db.NoResourceCheckFinished,
  1740  					}
  1741  				})
  1742  
  1743  				It("returns build preparation with missing input reason", func() {
  1744  					buildPrep, found, err := build.Preparation()
  1745  					Expect(err).NotTo(HaveOccurred())
  1746  					Expect(found).To(BeTrue())
  1747  					Expect(buildPrep).To(Equal(expectedBuildPrep))
  1748  				})
  1749  			})
  1750  		})
  1751  
  1752  		Context("when inputs are not satisfied", func() {
  1753  			BeforeEach(func() {
  1754  				expectedBuildPrep.InputsSatisfied = db.BuildPreparationStatusBlocking
  1755  				expectedBuildPrep.MissingInputReasons = map[string]string{"some-input": db.MissingBuildInput}
  1756  				expectedBuildPrep.Inputs = map[string]db.BuildPreparationStatus{"some-input": db.BuildPreparationStatusBlocking}
  1757  			})
  1758  
  1759  			It("returns blocking inputs satisfied", func() {
  1760  				buildPrep, found, err := build.Preparation()
  1761  				Expect(err).NotTo(HaveOccurred())
  1762  				Expect(found).To(BeTrue())
  1763  				Expect(buildPrep).To(Equal(expectedBuildPrep))
  1764  			})
  1765  		})
  1766  
  1767  		Context("when some inputs are errored", func() {
  1768  			BeforeEach(func() {
  1769  				scenario.Run(
  1770  					builder.WithNextInputMapping("some-job", dbtest.JobInputs{
  1771  						{
  1772  							Name:         "some-input",
  1773  							ResolveError: "resolve error",
  1774  						},
  1775  					}),
  1776  				)
  1777  
  1778  				expectedBuildPrep.Inputs = map[string]db.BuildPreparationStatus{
  1779  					"some-input": db.BuildPreparationStatusBlocking,
  1780  				}
  1781  				expectedBuildPrep.InputsSatisfied = db.BuildPreparationStatusBlocking
  1782  				expectedBuildPrep.MissingInputReasons = db.MissingInputReasons{
  1783  					"some-input": "resolve error",
  1784  				}
  1785  			})
  1786  
  1787  			It("returns blocking inputs satisfied", func() {
  1788  				buildPrep, found, err := build.Preparation()
  1789  				Expect(err).NotTo(HaveOccurred())
  1790  				Expect(found).To(BeTrue())
  1791  				Expect(buildPrep).To(Equal(expectedBuildPrep))
  1792  			})
  1793  		})
  1794  
  1795  		Context("when some inputs are missing", func() {
  1796  			BeforeEach(func() {
  1797  				scenario.Run(
  1798  					builder.WithNextInputMapping("some-job", dbtest.JobInputs{
  1799  						{
  1800  							Name:    "some-input",
  1801  							Version: atc.Version{"some": "version"},
  1802  						},
  1803  					}),
  1804  
  1805  					// checked after build creation
  1806  					builder.WithResourceVersions("some-resource"),
  1807  
  1808  					// add another input
  1809  					builder.WithPipeline(atc.Config{
  1810  						Resources: atc.ResourceConfigs{
  1811  							{
  1812  								Name: "some-resource",
  1813  								Type: dbtest.BaseResourceType,
  1814  								Source: atc.Source{
  1815  									"source-config": "some-value",
  1816  								},
  1817  							},
  1818  						},
  1819  						Jobs: atc.JobConfigs{
  1820  							{
  1821  								Name:           "some-job",
  1822  								RawMaxInFlight: 1,
  1823  								PlanSequence: []atc.Step{
  1824  									{
  1825  										Config: &atc.GetStep{
  1826  											Name:     "some-input",
  1827  											Resource: "some-resource",
  1828  										},
  1829  									},
  1830  									{
  1831  										Config: &atc.GetStep{
  1832  											Name:     "some-other-input",
  1833  											Resource: "some-resource",
  1834  										},
  1835  									},
  1836  								},
  1837  							},
  1838  						},
  1839  					}),
  1840  				)
  1841  
  1842  				expectedBuildPrep.Inputs = map[string]db.BuildPreparationStatus{
  1843  					"some-input":       db.BuildPreparationStatusNotBlocking,
  1844  					"some-other-input": db.BuildPreparationStatusBlocking,
  1845  				}
  1846  				expectedBuildPrep.InputsSatisfied = db.BuildPreparationStatusBlocking
  1847  				expectedBuildPrep.MissingInputReasons = db.MissingInputReasons{
  1848  					"some-other-input": "input is not included in resolved candidates",
  1849  				}
  1850  			})
  1851  
  1852  			It("returns blocking inputs satisfied", func() {
  1853  				buildPrep, found, err := build.Preparation()
  1854  				Expect(err).NotTo(HaveOccurred())
  1855  				Expect(found).To(BeTrue())
  1856  				Expect(buildPrep).To(Equal(expectedBuildPrep))
  1857  			})
  1858  		})
  1859  	})
  1860  
  1861  	Describe("AdoptInputsAndPipes", func() {
  1862  		var scenario *dbtest.Scenario
  1863  
  1864  		BeforeEach(func() {
  1865  			scenario = dbtest.Setup(
  1866  				builder.WithPipeline(atc.Config{
  1867  					Jobs: atc.JobConfigs{
  1868  						{
  1869  							Name: "upstream-job",
  1870  							PlanSequence: []atc.Step{
  1871  								{
  1872  									Config: &atc.GetStep{
  1873  										Name:     "some-input",
  1874  										Resource: "some-resource",
  1875  									},
  1876  								},
  1877  							},
  1878  						},
  1879  						{
  1880  							Name: "downstream-job",
  1881  							PlanSequence: []atc.Step{
  1882  								{
  1883  									Config: &atc.GetStep{
  1884  										Name:     "some-input",
  1885  										Resource: "some-resource",
  1886  										Passed:   []string{"upstream-job"},
  1887  									},
  1888  								},
  1889  								{
  1890  									Config: &atc.GetStep{
  1891  										Name:     "some-other-input",
  1892  										Resource: "some-other-resource",
  1893  									},
  1894  								},
  1895  							},
  1896  						},
  1897  					},
  1898  					Resources: atc.ResourceConfigs{
  1899  						{
  1900  							Name:   "some-resource",
  1901  							Type:   dbtest.BaseResourceType,
  1902  							Source: atc.Source{"some": "source"},
  1903  						},
  1904  						{
  1905  							Name:   "some-other-resource",
  1906  							Type:   dbtest.BaseResourceType,
  1907  							Source: atc.Source{"some": "other-source"},
  1908  						},
  1909  					},
  1910  				}),
  1911  				builder.WithResourceVersions("some-resource",
  1912  					atc.Version{"version": "v1"},
  1913  					atc.Version{"version": "v2"},
  1914  					atc.Version{"version": "v3"},
  1915  				),
  1916  				builder.WithResourceVersions("some-other-resource",
  1917  					atc.Version{"version": "v1"},
  1918  				),
  1919  			)
  1920  		})
  1921  
  1922  		Describe("adopting inputs for an upstream job", func() {
  1923  			var upstreamBuild db.Build
  1924  
  1925  			BeforeEach(func() {
  1926  				scenario.Run(
  1927  					builder.WithPendingJobBuild(&upstreamBuild, "upstream-job"),
  1928  				)
  1929  			})
  1930  
  1931  			Context("for valid versions", func() {
  1932  				BeforeEach(func() {
  1933  					scenario.Run(
  1934  						builder.WithNextInputMapping("upstream-job", dbtest.JobInputs{
  1935  							{
  1936  								Name:    "some-input",
  1937  								Version: atc.Version{"version": "v3"},
  1938  							},
  1939  						}),
  1940  					)
  1941  				})
  1942  
  1943  				It("adopts the next inputs and pipes", func() {
  1944  					inputs, adopted, err := upstreamBuild.AdoptInputsAndPipes()
  1945  					Expect(err).ToNot(HaveOccurred())
  1946  					Expect(adopted).To(BeTrue())
  1947  					Expect(inputs).To(ConsistOf([]db.BuildInput{
  1948  						{
  1949  							Name:            "some-input",
  1950  							ResourceID:      scenario.Resource("some-resource").ID(),
  1951  							Version:         atc.Version{"version": "v3"},
  1952  							FirstOccurrence: false,
  1953  						},
  1954  					}))
  1955  
  1956  					Expect(upstreamBuild.InputsReady()).To(BeFalse())
  1957  					found, err := upstreamBuild.Reload()
  1958  					Expect(err).ToNot(HaveOccurred())
  1959  					Expect(found).To(BeTrue())
  1960  					Expect(upstreamBuild.InputsReady()).To(BeTrue())
  1961  				})
  1962  
  1963  				Context("followed by a downstream job", func() {
  1964  					var downstreamBuild db.Build
  1965  
  1966  					BeforeEach(func() {
  1967  						_, adopted, err := upstreamBuild.AdoptInputsAndPipes()
  1968  						Expect(err).ToNot(HaveOccurred())
  1969  						Expect(adopted).To(BeTrue())
  1970  
  1971  						Expect(upstreamBuild.Finish(db.BuildStatusSucceeded)).To(Succeed())
  1972  
  1973  						scenario.Run(
  1974  							builder.WithPendingJobBuild(&downstreamBuild, "downstream-job"),
  1975  							builder.WithNextInputMapping("downstream-job", dbtest.JobInputs{
  1976  								{
  1977  									Name:         "some-input",
  1978  									Version:      atc.Version{"version": "v3"},
  1979  									PassedBuilds: []db.Build{upstreamBuild},
  1980  								},
  1981  								{
  1982  									Name:            "some-other-input",
  1983  									Version:         atc.Version{"version": "v1"},
  1984  									FirstOccurrence: true,
  1985  								},
  1986  							}),
  1987  						)
  1988  					})
  1989  
  1990  					It("adopts the next inputs and pipes referencing the upstream build", func() {
  1991  						inputs, adopted, err := downstreamBuild.AdoptInputsAndPipes()
  1992  						Expect(err).ToNot(HaveOccurred())
  1993  						Expect(adopted).To(BeTrue())
  1994  						Expect(inputs).To(ConsistOf([]db.BuildInput{
  1995  							{
  1996  								Name:            "some-input",
  1997  								ResourceID:      scenario.Resource("some-resource").ID(),
  1998  								Version:         atc.Version{"version": "v3"},
  1999  								FirstOccurrence: false,
  2000  							},
  2001  							{
  2002  								Name:            "some-other-input",
  2003  								ResourceID:      scenario.Resource("some-other-resource").ID(),
  2004  								Version:         atc.Version{"version": "v1"},
  2005  								FirstOccurrence: true,
  2006  							},
  2007  						}))
  2008  
  2009  						Expect(downstreamBuild.InputsReady()).To(BeFalse())
  2010  						found, err := downstreamBuild.Reload()
  2011  						Expect(err).ToNot(HaveOccurred())
  2012  						Expect(found).To(BeTrue())
  2013  						Expect(downstreamBuild.InputsReady()).To(BeTrue())
  2014  
  2015  						buildPipes, err := versionsDB.LatestBuildPipes(ctx, downstreamBuild.ID())
  2016  						Expect(err).ToNot(HaveOccurred())
  2017  						Expect(buildPipes[scenario.Job("upstream-job").ID()]).To(Equal(db.BuildCursor{
  2018  							ID: upstreamBuild.ID(),
  2019  						}))
  2020  					})
  2021  				})
  2022  			})
  2023  
  2024  			Context("for bogus versions", func() {
  2025  				BeforeEach(func() {
  2026  					scenario.Run(
  2027  						builder.WithNextInputMapping("upstream-job", dbtest.JobInputs{
  2028  							{
  2029  								Name:    "some-input",
  2030  								Version: atc.Version{"version": "bogus"},
  2031  							},
  2032  						}),
  2033  					)
  2034  				})
  2035  
  2036  				It("set resolve error of that input", func() {
  2037  					buildInputs, adopted, err := upstreamBuild.AdoptInputsAndPipes()
  2038  					Expect(err).ToNot(HaveOccurred())
  2039  					Expect(adopted).To(BeFalse())
  2040  					Expect(buildInputs).To(BeNil())
  2041  
  2042  					nextBuildInputs, err := scenario.Job("upstream-job").GetNextBuildInputs()
  2043  					Expect(err).ToNot(HaveOccurred())
  2044  					Expect(len(nextBuildInputs)).To(Equal(1))
  2045  					Expect(nextBuildInputs[0].ResolveError).To(Equal("chosen version of input some-input not available"))
  2046  				})
  2047  			})
  2048  
  2049  			Context("when inputs are not determined", func() {
  2050  				BeforeEach(func() {
  2051  					scenario.Run(
  2052  						builder.WithNextInputMapping("upstream-job", dbtest.JobInputs{
  2053  							{
  2054  								Name:         "some-input",
  2055  								Version:      atc.Version{"version": "bogus"},
  2056  								ResolveError: "errored",
  2057  							},
  2058  						}),
  2059  					)
  2060  				})
  2061  
  2062  				It("does not adopt next inputs and pipes", func() {
  2063  					buildInputs, adopted, err := upstreamBuild.AdoptInputsAndPipes()
  2064  					Expect(err).ToNot(HaveOccurred())
  2065  					Expect(adopted).To(BeFalse())
  2066  					Expect(buildInputs).To(BeNil())
  2067  
  2068  					buildPipes, err := versionsDB.LatestBuildPipes(ctx, upstreamBuild.ID())
  2069  					Expect(err).ToNot(HaveOccurred())
  2070  					Expect(buildPipes).To(HaveLen(0))
  2071  
  2072  					Expect(upstreamBuild.InputsReady()).To(BeFalse())
  2073  					found, err := upstreamBuild.Reload()
  2074  					Expect(err).ToNot(HaveOccurred())
  2075  					Expect(found).To(BeTrue())
  2076  					Expect(upstreamBuild.InputsReady()).To(BeFalse())
  2077  				})
  2078  			})
  2079  		})
  2080  	})
  2081  
  2082  	Describe("AdoptRerunInputsAndPipes", func() {
  2083  		var scenario *dbtest.Scenario
  2084  		var pipelineConfig atc.Config
  2085  
  2086  		var upstreamBuild, downstreamBuild, retriggerBuild db.Build
  2087  		var buildInputs, expectedBuildInputs []db.BuildInput
  2088  		var adoptFound bool
  2089  
  2090  		BeforeEach(func() {
  2091  			pipelineConfig = atc.Config{
  2092  				Jobs: atc.JobConfigs{
  2093  					{
  2094  						Name: "upstream-job",
  2095  						PlanSequence: []atc.Step{
  2096  							{
  2097  								Config: &atc.GetStep{
  2098  									Name:     "some-input",
  2099  									Resource: "some-resource",
  2100  								},
  2101  							},
  2102  						},
  2103  					},
  2104  					{
  2105  						Name: "downstream-job",
  2106  						PlanSequence: []atc.Step{
  2107  							{
  2108  								Config: &atc.GetStep{
  2109  									Name:     "some-input",
  2110  									Resource: "some-resource",
  2111  									Passed:   []string{"upstream-job"},
  2112  								},
  2113  							},
  2114  							{
  2115  								Config: &atc.GetStep{
  2116  									Name:     "some-other-input",
  2117  									Resource: "some-other-resource",
  2118  								},
  2119  							},
  2120  						},
  2121  					},
  2122  				},
  2123  				Resources: atc.ResourceConfigs{
  2124  					{
  2125  						Name:   "some-resource",
  2126  						Type:   dbtest.BaseResourceType,
  2127  						Source: atc.Source{"some": "source"},
  2128  					},
  2129  					{
  2130  						Name:   "some-other-resource",
  2131  						Type:   dbtest.BaseResourceType,
  2132  						Source: atc.Source{"some": "other-source"},
  2133  					},
  2134  				},
  2135  			}
  2136  
  2137  			scenario = dbtest.Setup(
  2138  				builder.WithPipeline(pipelineConfig),
  2139  				builder.WithResourceVersions("some-resource",
  2140  					atc.Version{"version": "v1"},
  2141  					atc.Version{"version": "v2"},
  2142  					atc.Version{"version": "v3"},
  2143  				),
  2144  				builder.WithResourceVersions("some-other-resource",
  2145  					atc.Version{"version": "v1"},
  2146  				),
  2147  				builder.WithJobBuild(&upstreamBuild, "upstream-job", dbtest.JobInputs{
  2148  					{
  2149  						Name:    "some-input",
  2150  						Version: atc.Version{"version": "v3"},
  2151  					},
  2152  				}, dbtest.JobOutputs{}),
  2153  				builder.WithPendingJobBuild(&downstreamBuild, "downstream-job"),
  2154  			)
  2155  
  2156  			var err error
  2157  			retriggerBuild, err = job.RerunBuild(downstreamBuild)
  2158  			Expect(err).ToNot(HaveOccurred())
  2159  		})
  2160  
  2161  		JustBeforeEach(func() {
  2162  			var err error
  2163  			buildInputs, adoptFound, err = retriggerBuild.AdoptRerunInputsAndPipes()
  2164  			Expect(err).ToNot(HaveOccurred())
  2165  		})
  2166  
  2167  		Context("when the build to retrigger has inputs and pipes", func() {
  2168  			BeforeEach(func() {
  2169  				scenario.Run(
  2170  					builder.WithNextInputMapping("downstream-job", dbtest.JobInputs{
  2171  						{
  2172  							Name:            "some-input",
  2173  							Version:         atc.Version{"version": "v3"},
  2174  							PassedBuilds:    []db.Build{upstreamBuild},
  2175  							FirstOccurrence: true,
  2176  						},
  2177  						{
  2178  							Name:            "some-other-input",
  2179  							Version:         atc.Version{"version": "v1"},
  2180  							FirstOccurrence: true,
  2181  						},
  2182  					}),
  2183  				)
  2184  
  2185  				_, found, err := downstreamBuild.AdoptInputsAndPipes()
  2186  				Expect(err).ToNot(HaveOccurred())
  2187  				Expect(found).To(BeTrue())
  2188  
  2189  				expectedBuildInputs = []db.BuildInput{
  2190  					{
  2191  						Name:            "some-input",
  2192  						ResourceID:      scenario.Resource("some-resource").ID(),
  2193  						Version:         atc.Version{"version": "v3"},
  2194  						FirstOccurrence: false,
  2195  					},
  2196  					{
  2197  						Name:            "some-other-input",
  2198  						ResourceID:      scenario.Resource("some-other-resource").ID(),
  2199  						Version:         atc.Version{"version": "v1"},
  2200  						FirstOccurrence: false,
  2201  					},
  2202  				}
  2203  			})
  2204  
  2205  			It("build inputs and pipes of the build to retrigger off of as it's own build inputs but sets first occurrence to false", func() {
  2206  				Expect(adoptFound).To(BeTrue())
  2207  
  2208  				Expect(buildInputs).To(ConsistOf(expectedBuildInputs))
  2209  
  2210  				buildPipes, err := versionsDB.LatestBuildPipes(ctx, retriggerBuild.ID())
  2211  				Expect(err).ToNot(HaveOccurred())
  2212  				Expect(buildPipes).To(HaveLen(1))
  2213  				Expect(buildPipes[scenario.Job("upstream-job").ID()]).To(Equal(db.BuildCursor{
  2214  					ID: upstreamBuild.ID(),
  2215  				}))
  2216  
  2217  				reloaded, err := retriggerBuild.Reload()
  2218  				Expect(err).ToNot(HaveOccurred())
  2219  				Expect(reloaded).To(BeTrue())
  2220  				Expect(retriggerBuild.InputsReady()).To(BeTrue())
  2221  			})
  2222  
  2223  			Context("when the version becomes unavailable", func() {
  2224  				BeforeEach(func() {
  2225  					pipelineConfig.Resources[0].Source = atc.Source{"some": "new-source"}
  2226  
  2227  					scenario.Run(
  2228  						builder.WithPipeline(pipelineConfig),
  2229  						builder.WithResourceVersions("some-resource", atc.Version{"some": "new-version"}),
  2230  					)
  2231  				})
  2232  
  2233  				It("fails to adopt", func() {
  2234  					Expect(adoptFound).To(BeFalse())
  2235  				})
  2236  
  2237  				It("aborts the build", func() {
  2238  					reloaded, err := retriggerBuild.Reload()
  2239  					Expect(err).ToNot(HaveOccurred())
  2240  					Expect(reloaded).To(BeTrue())
  2241  					Expect(retriggerBuild.IsAborted()).To(BeTrue())
  2242  				})
  2243  			})
  2244  		})
  2245  
  2246  		Context("when the build to retrigger off of does not have inputs or pipes", func() {
  2247  			It("does not move build inputs and pipes", func() {
  2248  				Expect(adoptFound).To(BeFalse())
  2249  
  2250  				Expect(buildInputs).To(BeNil())
  2251  
  2252  				buildPipes, err := versionsDB.LatestBuildPipes(ctx, build.ID())
  2253  				Expect(err).ToNot(HaveOccurred())
  2254  				Expect(buildPipes).To(HaveLen(0))
  2255  
  2256  				reloaded, err := retriggerBuild.Reload()
  2257  				Expect(err).ToNot(HaveOccurred())
  2258  				Expect(reloaded).To(BeTrue())
  2259  				Expect(retriggerBuild.InputsReady()).To(BeFalse())
  2260  			})
  2261  		})
  2262  	})
  2263  
  2264  	Describe("ResourcesChecked", func() {
  2265  		var scenario *dbtest.Scenario
  2266  
  2267  		var build db.Build
  2268  		var checked bool
  2269  
  2270  		BeforeEach(func() {
  2271  			pipelineConfig := atc.Config{
  2272  				Jobs: atc.JobConfigs{
  2273  					{
  2274  						Name: "some-job",
  2275  						PlanSequence: []atc.Step{
  2276  							{
  2277  								Config: &atc.GetStep{
  2278  									Name: "some-resource",
  2279  								},
  2280  							},
  2281  							{
  2282  								Config: &atc.GetStep{
  2283  									Name: "some-other-resource",
  2284  								},
  2285  							},
  2286  						},
  2287  					},
  2288  				},
  2289  				Resources: atc.ResourceConfigs{
  2290  					{
  2291  						Name:   "some-resource",
  2292  						Type:   dbtest.BaseResourceType,
  2293  						Source: atc.Source{"some": "source"},
  2294  					},
  2295  					{
  2296  						Name:   "some-other-resource",
  2297  						Type:   dbtest.BaseResourceType,
  2298  						Source: atc.Source{"some": "other-source"},
  2299  					},
  2300  				},
  2301  			}
  2302  
  2303  			scenario = dbtest.Setup(
  2304  				builder.WithPipeline(pipelineConfig),
  2305  				builder.WithResourceVersions("some-resource", atc.Version{"some": "version"}),
  2306  				builder.WithResourceVersions("some-other-resource", atc.Version{"some": "other-version"}),
  2307  				builder.WithPendingJobBuild(&build, "some-job"),
  2308  			)
  2309  		})
  2310  
  2311  		JustBeforeEach(func() {
  2312  			var err error
  2313  			checked, err = build.ResourcesChecked()
  2314  			Expect(err).ToNot(HaveOccurred())
  2315  		})
  2316  
  2317  		Context("when all the resources in the build has been checked", func() {
  2318  			BeforeEach(func() {
  2319  				scenario.Run(
  2320  					builder.WithResourceVersions("some-resource"),
  2321  					builder.WithResourceVersions("some-other-resource"),
  2322  				)
  2323  			})
  2324  
  2325  			It("returns true", func() {
  2326  				Expect(checked).To(BeTrue())
  2327  			})
  2328  		})
  2329  
  2330  		Context("when a the resource in the build has not been checked", func() {
  2331  			It("returns false", func() {
  2332  				Expect(checked).To(BeFalse())
  2333  			})
  2334  		})
  2335  
  2336  		Context("when a pinned resource in the build has not been checked", func() {
  2337  			BeforeEach(func() {
  2338  				scenario.Run(
  2339  					builder.WithResourceVersions("some-resource"),
  2340  				)
  2341  
  2342  				rcv := scenario.ResourceVersion("some-other-resource", atc.Version{"some": "other-version"})
  2343  
  2344  				found, err := scenario.Resource("some-other-resource").PinVersion(rcv.ID())
  2345  				Expect(err).ToNot(HaveOccurred())
  2346  				Expect(found).To(BeTrue())
  2347  			})
  2348  
  2349  			It("returns true", func() {
  2350  				Expect(checked).To(BeTrue())
  2351  			})
  2352  		})
  2353  	})
  2354  
  2355  	Describe("SavePipeline", func() {
  2356  		It("saves the parent job and build ids", func() {
  2357  			By("creating a build")
  2358  			build, err := defaultJob.CreateBuild()
  2359  			Expect(err).ToNot(HaveOccurred())
  2360  
  2361  			By("saving a pipeline with the build")
  2362  			pipeline, _, err := build.SavePipeline(atc.PipelineRef{Name: "other-pipeline"}, build.TeamID(), atc.Config{
  2363  				Jobs: atc.JobConfigs{
  2364  					{
  2365  						Name: "some-job",
  2366  					},
  2367  				},
  2368  				Resources: atc.ResourceConfigs{
  2369  					{
  2370  						Name: "some-resource",
  2371  						Type: "some-base-resource-type",
  2372  						Source: atc.Source{
  2373  							"some": "source",
  2374  						},
  2375  					},
  2376  				},
  2377  				ResourceTypes: atc.ResourceTypes{
  2378  					{
  2379  						Name: "some-type",
  2380  						Type: "some-base-resource-type",
  2381  						Source: atc.Source{
  2382  							"some-type": "source",
  2383  						},
  2384  					},
  2385  				},
  2386  			}, db.ConfigVersion(0), false)
  2387  			Expect(err).ToNot(HaveOccurred())
  2388  			Expect(pipeline.ParentJobID()).To(Equal(build.JobID()))
  2389  			Expect(pipeline.ParentBuildID()).To(Equal(build.ID()))
  2390  		})
  2391  
  2392  		It("only saves the pipeline if it is the latest build", func() {
  2393  			By("creating two builds")
  2394  			buildOne, err := defaultJob.CreateBuild()
  2395  			Expect(err).ToNot(HaveOccurred())
  2396  			buildTwo, err := defaultJob.CreateBuild()
  2397  			Expect(err).ToNot(HaveOccurred())
  2398  
  2399  			By("saving a pipeline with the second build")
  2400  			pipeline, _, err := buildTwo.SavePipeline(atc.PipelineRef{Name: "other-pipeline"}, buildTwo.TeamID(), atc.Config{
  2401  				Jobs: atc.JobConfigs{
  2402  					{
  2403  						Name: "some-job",
  2404  					},
  2405  				},
  2406  				Resources: atc.ResourceConfigs{
  2407  					{
  2408  						Name: "some-resource",
  2409  						Type: "some-base-resource-type",
  2410  						Source: atc.Source{
  2411  							"some": "source",
  2412  						},
  2413  					},
  2414  				},
  2415  				ResourceTypes: atc.ResourceTypes{
  2416  					{
  2417  						Name: "some-type",
  2418  						Type: "some-base-resource-type",
  2419  						Source: atc.Source{
  2420  							"some-type": "source",
  2421  						},
  2422  					},
  2423  				},
  2424  			}, db.ConfigVersion(0), false)
  2425  			Expect(err).ToNot(HaveOccurred())
  2426  			Expect(pipeline.ParentJobID()).To(Equal(buildTwo.JobID()))
  2427  			Expect(pipeline.ParentBuildID()).To(Equal(buildTwo.ID()))
  2428  
  2429  			By("saving a pipeline with the first build")
  2430  			_, _, err = buildOne.SavePipeline(atc.PipelineRef{Name: "other-pipeline"}, buildOne.TeamID(), atc.Config{
  2431  				Jobs: atc.JobConfigs{
  2432  					{
  2433  						Name: "some-job",
  2434  					},
  2435  				},
  2436  				Resources: atc.ResourceConfigs{
  2437  					{
  2438  						Name: "some-resource",
  2439  						Type: "some-base-resource-type",
  2440  						Source: atc.Source{
  2441  							"some": "source",
  2442  						},
  2443  					},
  2444  				},
  2445  				ResourceTypes: atc.ResourceTypes{
  2446  					{
  2447  						Name: "some-type",
  2448  						Type: "some-base-resource-type",
  2449  						Source: atc.Source{
  2450  							"some-type": "source",
  2451  						},
  2452  					},
  2453  				},
  2454  			}, pipeline.ConfigVersion(), false)
  2455  			Expect(err).To(Equal(db.ErrSetByNewerBuild))
  2456  		})
  2457  
  2458  		Context("a pipeline is previously saved by team.SavePipeline", func() {
  2459  			It("the parent job and build ID are updated", func() {
  2460  				By("creating a build")
  2461  				build, err := defaultJob.CreateBuild()
  2462  				Expect(err).ToNot(HaveOccurred())
  2463  
  2464  				By("re-saving the default pipeline with the build")
  2465  				pipeline, _, err := build.SavePipeline(defaultPipelineRef, build.TeamID(), defaultPipelineConfig, db.ConfigVersion(1), false)
  2466  				Expect(err).ToNot(HaveOccurred())
  2467  				Expect(pipeline.ParentJobID()).To(Equal(build.JobID()))
  2468  				Expect(pipeline.ParentBuildID()).To(Equal(build.ID()))
  2469  			})
  2470  		})
  2471  	})
  2472  })
  2473  
  2474  func envelope(ev atc.Event) event.Envelope {
  2475  	payload, err := json.Marshal(ev)
  2476  	Expect(err).ToNot(HaveOccurred())
  2477  
  2478  	data := json.RawMessage(payload)
  2479  
  2480  	return event.Envelope{
  2481  		Event:   ev.EventType(),
  2482  		Version: ev.Version(),
  2483  		Data:    &data,
  2484  	}
  2485  }
  2486  
  2487  func convertToMD5(version atc.Version) string {
  2488  	versionJSON, err := json.Marshal(version)
  2489  	Expect(err).ToNot(HaveOccurred())
  2490  
  2491  	hasher := md5.New()
  2492  	hasher.Write([]byte(versionJSON))
  2493  	return hex.EncodeToString(hasher.Sum(nil))
  2494  }
  2495  
  2496  func getJobBuildID(col string, jobID int) int {
  2497  	var result int
  2498  
  2499  	err := psql.Select(col).
  2500  		From("jobs").
  2501  		Where(sq.Eq{"id": jobID}).
  2502  		RunWith(dbConn).
  2503  		QueryRow().
  2504  		Scan(&result)
  2505  	Expect(err).ToNot(HaveOccurred())
  2506  
  2507  	return result
  2508  }