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

     1  package exec_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"io"
     8  	"time"
     9  
    10  	"github.com/pf-qiu/concourse/v6/atc"
    11  	"github.com/pf-qiu/concourse/v6/atc/db"
    12  	"github.com/pf-qiu/concourse/v6/atc/db/dbfakes"
    13  	"github.com/pf-qiu/concourse/v6/atc/db/lock/lockfakes"
    14  	"github.com/pf-qiu/concourse/v6/atc/exec"
    15  	"github.com/pf-qiu/concourse/v6/atc/exec/execfakes"
    16  	"github.com/pf-qiu/concourse/v6/atc/resource"
    17  	"github.com/pf-qiu/concourse/v6/atc/resource/resourcefakes"
    18  	"github.com/pf-qiu/concourse/v6/atc/runtime"
    19  	"github.com/pf-qiu/concourse/v6/atc/runtime/runtimefakes"
    20  	"github.com/pf-qiu/concourse/v6/atc/worker"
    21  	"github.com/pf-qiu/concourse/v6/atc/worker/workerfakes"
    22  	"github.com/pf-qiu/concourse/v6/tracing"
    23  	"github.com/pf-qiu/concourse/v6/vars"
    24  	"go.opentelemetry.io/otel/api/trace"
    25  	"go.opentelemetry.io/otel/api/trace/tracetest"
    26  
    27  	. "github.com/onsi/ginkgo"
    28  	. "github.com/onsi/gomega"
    29  )
    30  
    31  var _ = Describe("CheckStep", func() {
    32  	var (
    33  		ctx    context.Context
    34  		cancel context.CancelFunc
    35  
    36  		planID                    atc.PlanID
    37  		fakeRunState              *execfakes.FakeRunState
    38  		fakeResourceFactory       *resourcefakes.FakeResourceFactory
    39  		fakeResource              *resourcefakes.FakeResource
    40  		fakeResourceConfigFactory *dbfakes.FakeResourceConfigFactory
    41  		fakeResourceConfig        *dbfakes.FakeResourceConfig
    42  		fakeResourceConfigScope   *dbfakes.FakeResourceConfigScope
    43  		fakePool                  *workerfakes.FakePool
    44  		fakeStrategy              *workerfakes.FakeContainerPlacementStrategy
    45  		fakeDelegate              *execfakes.FakeCheckDelegate
    46  		fakeDelegateFactory       *execfakes.FakeCheckDelegateFactory
    47  		spanCtx                   context.Context
    48  		fakeClient                *workerfakes.FakeClient
    49  		defaultTimeout            = time.Hour
    50  
    51  		fakeStdout, fakeStderr io.Writer
    52  
    53  		stepMetadata      exec.StepMetadata
    54  		checkStep         exec.Step
    55  		checkPlan         atc.CheckPlan
    56  		containerMetadata db.ContainerMetadata
    57  
    58  		stepOk  bool
    59  		stepErr error
    60  	)
    61  
    62  	BeforeEach(func() {
    63  		ctx, cancel = context.WithCancel(context.Background())
    64  
    65  		planID = "some-plan-id"
    66  
    67  		fakeRunState = new(execfakes.FakeRunState)
    68  		fakeResourceFactory = new(resourcefakes.FakeResourceFactory)
    69  		fakeResource = new(resourcefakes.FakeResource)
    70  		fakePool = new(workerfakes.FakePool)
    71  		fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
    72  		fakeDelegateFactory = new(execfakes.FakeCheckDelegateFactory)
    73  		fakeDelegate = new(execfakes.FakeCheckDelegate)
    74  		fakeClient = new(workerfakes.FakeClient)
    75  
    76  		spanCtx = context.Background()
    77  		fakeDelegate.StartSpanReturns(spanCtx, trace.NoopSpan{})
    78  
    79  		fakeStdout = bytes.NewBufferString("out")
    80  		fakeDelegate.StdoutReturns(fakeStdout)
    81  
    82  		fakeStderr = bytes.NewBufferString("err")
    83  		fakeDelegate.StderrReturns(fakeStderr)
    84  
    85  		stepMetadata = exec.StepMetadata{}
    86  		containerMetadata = db.ContainerMetadata{}
    87  
    88  		fakeResourceFactory.NewResourceReturns(fakeResource)
    89  
    90  		fakeResourceConfigFactory = new(dbfakes.FakeResourceConfigFactory)
    91  		fakeResourceConfig = new(dbfakes.FakeResourceConfig)
    92  		fakeResourceConfig.IDReturns(501)
    93  		fakeResourceConfig.OriginBaseResourceTypeReturns(&db.UsedBaseResourceType{
    94  			ID:   502,
    95  			Name: "some-base-type",
    96  		})
    97  		fakeResourceConfigFactory.FindOrCreateResourceConfigReturns(fakeResourceConfig, nil)
    98  
    99  		fakeResourceConfigScope = new(dbfakes.FakeResourceConfigScope)
   100  		fakeDelegate.FindOrCreateScopeReturns(fakeResourceConfigScope, nil)
   101  
   102  		fakeDelegateFactory.CheckDelegateReturns(fakeDelegate)
   103  
   104  		checkPlan = atc.CheckPlan{
   105  			Name:    "some-name",
   106  			Type:    "some-base-type",
   107  			Source:  atc.Source{"some": "((source-var))"},
   108  			Timeout: "10s",
   109  			VersionedResourceTypes: atc.VersionedResourceTypes{
   110  				{
   111  					ResourceType: atc.ResourceType{
   112  						Name:   "some-custom-type",
   113  						Type:   "another-custom-type",
   114  						Source: atc.Source{"some-custom": "((source-var))"},
   115  						Params: atc.Params{"some-custom": "((params-var))"},
   116  					},
   117  					Version: atc.Version{"some-custom": "version"},
   118  				},
   119  				{
   120  					ResourceType: atc.ResourceType{
   121  						Name:       "another-custom-type",
   122  						Type:       "registry-image",
   123  						Source:     atc.Source{"another-custom": "((source-var))"},
   124  						Privileged: true,
   125  					},
   126  					Version: atc.Version{"another-custom": "version"},
   127  				},
   128  			},
   129  		}
   130  
   131  		containerMetadata = db.ContainerMetadata{
   132  			User: "test-user",
   133  		}
   134  
   135  		stepMetadata = exec.StepMetadata{
   136  			TeamID:  345,
   137  			BuildID: 678,
   138  		}
   139  
   140  		fakeRunState.GetStub = vars.StaticVariables{"source-var": "super-secret-source"}.Get
   141  	})
   142  
   143  	AfterEach(func() {
   144  		cancel()
   145  	})
   146  
   147  	JustBeforeEach(func() {
   148  		checkStep = exec.NewCheckStep(
   149  			planID,
   150  			checkPlan,
   151  			stepMetadata,
   152  			fakeResourceFactory,
   153  			fakeResourceConfigFactory,
   154  			containerMetadata,
   155  			fakeStrategy,
   156  			fakePool,
   157  			fakeDelegateFactory,
   158  			fakeClient,
   159  			defaultTimeout,
   160  		)
   161  
   162  		stepOk, stepErr = checkStep.Run(ctx, fakeRunState)
   163  	})
   164  
   165  	Context("with a reasonable configuration", func() {
   166  		BeforeEach(func() {
   167  		})
   168  
   169  		It("emits an Initializing event", func() {
   170  			Expect(fakeDelegate.InitializingCallCount()).To(Equal(1))
   171  		})
   172  
   173  		Context("when not running", func() {
   174  			BeforeEach(func() {
   175  				fakeDelegate.WaitToRunReturns(nil, false, nil)
   176  			})
   177  
   178  			It("does not run the check step", func() {
   179  				Expect(fakeClient.RunCheckStepCallCount()).To(Equal(0))
   180  			})
   181  
   182  			It("succeeds", func() {
   183  				Expect(stepOk).To(BeTrue())
   184  			})
   185  
   186  			Context("when there is a latest version", func() {
   187  				BeforeEach(func() {
   188  					fakeVersion := new(dbfakes.FakeResourceConfigVersion)
   189  					fakeVersion.VersionReturns(db.Version{"some": "latest-version"})
   190  					fakeResourceConfigScope.LatestVersionReturns(fakeVersion, true, nil)
   191  				})
   192  
   193  				It("stores the latest version as the step result", func() {
   194  					Expect(fakeRunState.StoreResultCallCount()).To(Equal(1))
   195  					id, val := fakeRunState.StoreResultArgsForCall(0)
   196  					Expect(id).To(Equal(atc.PlanID("some-plan-id")))
   197  					Expect(val).To(Equal(atc.Version{"some": "latest-version"}))
   198  				})
   199  			})
   200  
   201  			Context("when there is no version", func() {
   202  				BeforeEach(func() {
   203  					fakeResourceConfigScope.LatestVersionReturns(nil, false, nil)
   204  				})
   205  
   206  				It("does not store a version", func() {
   207  					Expect(fakeRunState.StoreResultCallCount()).To(Equal(0))
   208  				})
   209  			})
   210  		})
   211  
   212  		Context("running", func() {
   213  			var fakeLock *lockfakes.FakeLock
   214  
   215  			BeforeEach(func() {
   216  				fakeLock = new(lockfakes.FakeLock)
   217  				fakeDelegate.WaitToRunReturns(fakeLock, true, nil)
   218  			})
   219  
   220  			Context("when given a from version", func() {
   221  				BeforeEach(func() {
   222  					checkPlan.FromVersion = atc.Version{"from": "version"}
   223  				})
   224  
   225  				It("constructs the resource with the version", func() {
   226  					Expect(fakeResourceFactory.NewResourceCallCount()).To(Equal(1))
   227  					_, _, fromVersion := fakeResourceFactory.NewResourceArgsForCall(0)
   228  					Expect(fromVersion).To(Equal(checkPlan.FromVersion))
   229  				})
   230  			})
   231  
   232  			Context("when not given a from version", func() {
   233  				var fakeVersion *dbfakes.FakeResourceConfigVersion
   234  
   235  				BeforeEach(func() {
   236  					checkPlan.FromVersion = nil
   237  
   238  					fakeVersion = new(dbfakes.FakeResourceConfigVersion)
   239  					fakeVersion.VersionReturns(db.Version{"latest": "version"})
   240  					fakeResourceConfigScope.LatestVersionStub = func() (db.ResourceConfigVersion, bool, error) {
   241  						Expect(fakeDelegate.WaitToRunCallCount()).To(
   242  							Equal(1),
   243  							"should have gotten latest version after waiting, not before",
   244  						)
   245  
   246  						return fakeVersion, true, nil
   247  					}
   248  				})
   249  
   250  				It("finds the latest version itself - it's a strong, independent check step who dont need no plan", func() {
   251  					Expect(fakeResourceFactory.NewResourceCallCount()).To(Equal(1))
   252  					_, _, fromVersion := fakeResourceFactory.NewResourceArgsForCall(0)
   253  					Expect(fromVersion).To(Equal(atc.Version{"latest": "version"}))
   254  				})
   255  			})
   256  
   257  			Describe("running the check step", func() {
   258  				var runCtx context.Context
   259  				var owner db.ContainerOwner
   260  				var containerSpec worker.ContainerSpec
   261  				var workerSpec worker.WorkerSpec
   262  				var strategy worker.ContainerPlacementStrategy
   263  				var metadata db.ContainerMetadata
   264  				var processSpec runtime.ProcessSpec
   265  				var startEventDelegate runtime.StartingEventDelegate
   266  				var resource resource.Resource
   267  				var timeout time.Duration
   268  
   269  				JustBeforeEach(func() {
   270  					Expect(fakeClient.RunCheckStepCallCount()).To(Equal(1), "check step should have run")
   271  					runCtx, _, owner, containerSpec, workerSpec, strategy, metadata, processSpec, startEventDelegate, resource, timeout = fakeClient.RunCheckStepArgsForCall(0)
   272  				})
   273  
   274  				It("uses ResourceConfigCheckSessionOwner", func() {
   275  					expected := db.NewBuildStepContainerOwner(
   276  						678,
   277  						planID,
   278  						345,
   279  					)
   280  
   281  					Expect(owner).To(Equal(expected))
   282  				})
   283  
   284  				Context("when the plan is for a resource", func() {
   285  					BeforeEach(func() {
   286  						checkPlan.Resource = "some-resource"
   287  					})
   288  
   289  					It("uses ResourceConfigCheckSessionOwner", func() {
   290  						expected := db.NewResourceConfigCheckSessionContainerOwner(
   291  							501,
   292  							502,
   293  							db.ContainerOwnerExpiries{Min: 5 * time.Minute, Max: 1 * time.Hour},
   294  						)
   295  
   296  						Expect(owner).To(Equal(expected))
   297  					})
   298  				})
   299  
   300  				It("passes the process spec", func() {
   301  					Expect(processSpec).To(Equal(runtime.ProcessSpec{
   302  						Path:         "/opt/resource/check",
   303  						StdoutWriter: fakeStdout,
   304  						StderrWriter: fakeStderr,
   305  					}))
   306  				})
   307  
   308  				It("passes the delegate as the start event delegate", func() {
   309  					Expect(startEventDelegate).To(Equal(fakeDelegate))
   310  				})
   311  
   312  				Context("uses containerspec", func() {
   313  					It("with certs volume mount", func() {
   314  						Expect(containerSpec.BindMounts).To(HaveLen(1))
   315  						mount := containerSpec.BindMounts[0]
   316  
   317  						_, ok := mount.(*worker.CertsVolumeMount)
   318  						Expect(ok).To(BeTrue())
   319  					})
   320  
   321  					It("uses base type for image", func() {
   322  						Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
   323  							ResourceType: "some-base-type",
   324  						}))
   325  					})
   326  
   327  					It("with teamid set", func() {
   328  						Expect(containerSpec.TeamID).To(Equal(345))
   329  					})
   330  
   331  					It("with env vars", func() {
   332  						Expect(containerSpec.Env).To(ContainElement("BUILD_TEAM_ID=345"))
   333  					})
   334  
   335  					Context("when tracing is enabled", func() {
   336  						var buildSpan trace.Span
   337  
   338  						BeforeEach(func() {
   339  							tracing.ConfigureTraceProvider(tracetest.NewProvider())
   340  
   341  							spanCtx, buildSpan = tracing.StartSpan(ctx, "build", nil)
   342  							fakeDelegate.StartSpanReturns(spanCtx, buildSpan)
   343  						})
   344  
   345  						AfterEach(func() {
   346  							tracing.Configured = false
   347  						})
   348  
   349  						It("propagates span context to the worker client", func() {
   350  							Expect(runCtx).To(Equal(spanCtx))
   351  						})
   352  
   353  						It("populates the TRACEPARENT env var", func() {
   354  							Expect(containerSpec.Env).To(ContainElement(MatchRegexp(`TRACEPARENT=.+`)))
   355  						})
   356  					})
   357  				})
   358  
   359  				Context("uses workerspec", func() {
   360  					It("with resource type", func() {
   361  						Expect(workerSpec.ResourceType).To(Equal("some-base-type"))
   362  					})
   363  
   364  					It("with teamid", func() {
   365  						Expect(workerSpec.TeamID).To(Equal(345))
   366  					})
   367  
   368  					Context("when the plan specifies tags", func() {
   369  						BeforeEach(func() {
   370  							checkPlan.Tags = atc.Tags{"some", "tags"}
   371  						})
   372  
   373  						It("sets them in the WorkerSpec", func() {
   374  							Expect(workerSpec.Tags).To(Equal([]string{"some", "tags"}))
   375  						})
   376  					})
   377  				})
   378  
   379  				It("uses container placement strategy", func() {
   380  					Expect(strategy).To(Equal(fakeStrategy))
   381  				})
   382  
   383  				It("uses container metadata", func() {
   384  					Expect(metadata).To(Equal(containerMetadata))
   385  				})
   386  
   387  				It("uses the timeout parsed", func() {
   388  					Expect(timeout).To(Equal(10 * time.Second))
   389  				})
   390  
   391  				It("uses the resource created", func() {
   392  					Expect(resource).To(Equal(fakeResource))
   393  				})
   394  
   395  				Context("when no timeout is given on the plan", func() {
   396  					BeforeEach(func() {
   397  						checkPlan.Timeout = ""
   398  					})
   399  
   400  					It("uses the default timeout", func() {
   401  						Expect(timeout).To(Equal(time.Hour))
   402  					})
   403  				})
   404  
   405  				Context("when using a custom resource type", func() {
   406  					var fakeImageSpec worker.ImageSpec
   407  
   408  					BeforeEach(func() {
   409  						checkPlan.Type = "some-custom-type"
   410  
   411  						fakeImageSpec = worker.ImageSpec{
   412  							ImageArtifact: new(runtimefakes.FakeArtifact),
   413  						}
   414  
   415  						fakeDelegate.FetchImageReturns(fakeImageSpec, nil)
   416  					})
   417  
   418  					It("fetches the resource type image and uses it for the container", func() {
   419  						Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   420  
   421  						_, imageResource, types, privileged := fakeDelegate.FetchImageArgsForCall(0)
   422  
   423  						By("fetching the type image")
   424  						Expect(imageResource).To(Equal(atc.ImageResource{
   425  							Name:    "some-custom-type",
   426  							Type:    "another-custom-type",
   427  							Source:  atc.Source{"some-custom": "((source-var))"},
   428  							Params:  atc.Params{"some-custom": "((params-var))"},
   429  							Version: atc.Version{"some-custom": "version"},
   430  						}))
   431  
   432  						By("excluding the type from the FetchImage call")
   433  						Expect(types).To(Equal(atc.VersionedResourceTypes{
   434  							{
   435  								ResourceType: atc.ResourceType{
   436  									Name:       "another-custom-type",
   437  									Type:       "registry-image",
   438  									Source:     atc.Source{"another-custom": "((source-var))"},
   439  									Privileged: true,
   440  								},
   441  								Version: atc.Version{"another-custom": "version"},
   442  							},
   443  						}))
   444  
   445  						By("not being privileged")
   446  						Expect(privileged).To(BeFalse())
   447  					})
   448  
   449  					It("sets the bottom-most type in the worker spec", func() {
   450  						Expect(workerSpec).To(Equal(worker.WorkerSpec{
   451  							TeamID:       stepMetadata.TeamID,
   452  							ResourceType: "registry-image",
   453  						}))
   454  					})
   455  
   456  					It("sets the image spec in the container spec", func() {
   457  						Expect(containerSpec.ImageSpec).To(Equal(fakeImageSpec))
   458  					})
   459  
   460  					Context("when the resource type is privileged", func() {
   461  						BeforeEach(func() {
   462  							checkPlan.Type = "another-custom-type"
   463  						})
   464  
   465  						It("fetches the image with privileged", func() {
   466  							Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   467  							_, _, _, privileged := fakeDelegate.FetchImageArgsForCall(0)
   468  							Expect(privileged).To(BeTrue())
   469  						})
   470  					})
   471  
   472  					Context("when the plan configures tags", func() {
   473  						BeforeEach(func() {
   474  							checkPlan.Tags = atc.Tags{"plan", "tags"}
   475  						})
   476  
   477  						It("fetches using the tags", func() {
   478  							Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   479  							_, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0)
   480  							Expect(imageResource.Tags).To(Equal(atc.Tags{"plan", "tags"}))
   481  						})
   482  					})
   483  
   484  					Context("when the resource type configures tags", func() {
   485  						BeforeEach(func() {
   486  							taggedType, found := checkPlan.VersionedResourceTypes.Lookup("some-custom-type")
   487  							Expect(found).To(BeTrue())
   488  
   489  							taggedType.Tags = atc.Tags{"type", "tags"}
   490  
   491  							newTypes := checkPlan.VersionedResourceTypes.Without("some-custom-type")
   492  							newTypes = append(newTypes, taggedType)
   493  
   494  							checkPlan.VersionedResourceTypes = newTypes
   495  						})
   496  
   497  						It("fetches using the type tags", func() {
   498  							Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   499  							_, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0)
   500  							Expect(imageResource.Tags).To(Equal(atc.Tags{"type", "tags"}))
   501  						})
   502  
   503  						Context("when the plan ALSO configures tags", func() {
   504  							BeforeEach(func() {
   505  								checkPlan.Tags = atc.Tags{"plan", "tags"}
   506  							})
   507  
   508  							It("fetches using only the type tags", func() {
   509  								Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   510  								_, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0)
   511  								Expect(imageResource.Tags).To(Equal(atc.Tags{"type", "tags"}))
   512  							})
   513  						})
   514  					})
   515  				})
   516  			})
   517  
   518  			Context("with tracing configured", func() {
   519  				var buildSpan trace.Span
   520  
   521  				BeforeEach(func() {
   522  					tracing.ConfigureTraceProvider(tracetest.NewProvider())
   523  
   524  					spanCtx, buildSpan = tracing.StartSpan(context.Background(), "fake-operation", nil)
   525  					fakeDelegate.StartSpanReturns(spanCtx, buildSpan)
   526  				})
   527  
   528  				AfterEach(func() {
   529  					tracing.Configured = false
   530  				})
   531  
   532  				It("propagates span context to scope", func() {
   533  					Expect(fakeResourceConfigScope.SaveVersionsCallCount()).To(Equal(1))
   534  					spanContext, _ := fakeResourceConfigScope.SaveVersionsArgsForCall(0)
   535  					traceID := buildSpan.SpanContext().TraceID.String()
   536  					traceParent := spanContext.Get("traceparent")
   537  					Expect(traceParent).To(ContainSubstring(traceID))
   538  				})
   539  			})
   540  
   541  			Context("having RunCheckStep succeed", func() {
   542  				BeforeEach(func() {
   543  					fakeClient.RunCheckStepReturns(worker.CheckResult{
   544  						Versions: []atc.Version{
   545  							{"version": "1"},
   546  							{"version": "2"},
   547  						},
   548  					}, nil)
   549  				})
   550  
   551  				It("succeeds", func() {
   552  					Expect(stepOk).To(BeTrue())
   553  				})
   554  
   555  				It("saves the versions to the config scope", func() {
   556  					Expect(fakeResourceConfigFactory.FindOrCreateResourceConfigCallCount()).To(Equal(1))
   557  					type_, source, types := fakeResourceConfigFactory.FindOrCreateResourceConfigArgsForCall(0)
   558  					Expect(type_).To(Equal("some-base-type"))
   559  					Expect(source).To(Equal(atc.Source{"some": "super-secret-source"}))
   560  					Expect(types).To(Equal(atc.VersionedResourceTypes{
   561  						{
   562  							ResourceType: atc.ResourceType{
   563  								Name:   "some-custom-type",
   564  								Type:   "another-custom-type",
   565  								Source: atc.Source{"some-custom": "super-secret-source"},
   566  
   567  								// params don't need to be interpolated because it's used for
   568  								// fetching, not constructing the resource config
   569  								Params: atc.Params{"some-custom": "((params-var))"},
   570  							},
   571  							Version: atc.Version{"some-custom": "version"},
   572  						},
   573  						{
   574  							ResourceType: atc.ResourceType{
   575  								Name:       "another-custom-type",
   576  								Type:       "registry-image",
   577  								Source:     atc.Source{"another-custom": "super-secret-source"},
   578  								Privileged: true,
   579  							},
   580  							Version: atc.Version{"another-custom": "version"},
   581  						},
   582  					}))
   583  
   584  					Expect(fakeDelegate.FindOrCreateScopeCallCount()).To(Equal(1))
   585  					config := fakeDelegate.FindOrCreateScopeArgsForCall(0)
   586  					Expect(config).To(Equal(fakeResourceConfig))
   587  
   588  					spanContext, versions := fakeResourceConfigScope.SaveVersionsArgsForCall(0)
   589  					Expect(spanContext).To(Equal(db.SpanContext{}))
   590  					Expect(versions).To(Equal([]atc.Version{
   591  						{"version": "1"},
   592  						{"version": "2"},
   593  					}))
   594  				})
   595  
   596  				It("stores the latest version as the step result", func() {
   597  					Expect(fakeRunState.StoreResultCallCount()).To(Equal(1))
   598  					id, val := fakeRunState.StoreResultArgsForCall(0)
   599  					Expect(id).To(Equal(atc.PlanID("some-plan-id")))
   600  					Expect(val).To(Equal(atc.Version{"version": "2"}))
   601  				})
   602  
   603  				It("emits a successful Finished event", func() {
   604  					Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
   605  					_, succeeded := fakeDelegate.FinishedArgsForCall(0)
   606  					Expect(succeeded).To(BeTrue())
   607  				})
   608  
   609  				Context("when no versions are returned", func() {
   610  					BeforeEach(func() {
   611  						fakeClient.RunCheckStepReturns(worker.CheckResult{Versions: []atc.Version{}}, nil)
   612  					})
   613  
   614  					It("succeeds", func() {
   615  						Expect(stepErr).ToNot(HaveOccurred())
   616  						Expect(stepOk).To(BeTrue())
   617  					})
   618  
   619  					It("does not store a version", func() {
   620  						Expect(fakeRunState.StoreResultCallCount()).To(Equal(0))
   621  					})
   622  				})
   623  
   624  				Context("before running the check", func() {
   625  					BeforeEach(func() {
   626  						fakeResourceConfigScope.UpdateLastCheckStartTimeStub = func() (bool, error) {
   627  							Expect(fakeClient.RunCheckStepCallCount()).To(Equal(0))
   628  							return true, nil
   629  						}
   630  					})
   631  
   632  					It("updates the scope's last check start time", func() {
   633  						Expect(fakeResourceConfigScope.UpdateLastCheckStartTimeCallCount()).To(Equal(1))
   634  						Expect(fakeClient.RunCheckStepCallCount()).To(Equal(1))
   635  					})
   636  				})
   637  
   638  				Context("after saving", func() {
   639  					BeforeEach(func() {
   640  						fakeResourceConfigScope.SaveVersionsStub = func(db.SpanContext, []atc.Version) error {
   641  							Expect(fakeDelegate.PointToCheckedConfigCallCount()).To(BeZero())
   642  							Expect(fakeResourceConfigScope.UpdateLastCheckEndTimeCallCount()).To(Equal(0))
   643  							return nil
   644  						}
   645  					})
   646  
   647  					It("updates the scope's last check end time", func() {
   648  						Expect(fakeResourceConfigScope.UpdateLastCheckEndTimeCallCount()).To(Equal(1))
   649  					})
   650  
   651  					It("points the resource or resource type to the scope", func() {
   652  						Expect(fakeResourceConfigScope.SaveVersionsCallCount()).To(Equal(1))
   653  						Expect(fakeDelegate.PointToCheckedConfigCallCount()).To(Equal(1))
   654  						scope := fakeDelegate.PointToCheckedConfigArgsForCall(0)
   655  						Expect(scope).To(Equal(fakeResourceConfigScope))
   656  					})
   657  				})
   658  
   659  				Context("after pointing the resource type to the scope", func() {
   660  					BeforeEach(func() {
   661  						fakeDelegate.PointToCheckedConfigStub = func(db.ResourceConfigScope) error {
   662  							Expect(fakeLock.ReleaseCallCount()).To(Equal(0))
   663  							return nil
   664  						}
   665  					})
   666  
   667  					It("releases the lock", func() {
   668  						Expect(fakeDelegate.PointToCheckedConfigCallCount()).To(Equal(1))
   669  						Expect(fakeLock.ReleaseCallCount()).To(Equal(1))
   670  					})
   671  				})
   672  			})
   673  
   674  			Context("having RunCheckStep erroring", func() {
   675  				var expectedErr error
   676  
   677  				BeforeEach(func() {
   678  					expectedErr = errors.New("run-check-step-err")
   679  					fakeClient.RunCheckStepReturns(worker.CheckResult{}, expectedErr)
   680  				})
   681  
   682  				It("errors", func() {
   683  					Expect(stepErr).To(HaveOccurred())
   684  					Expect(errors.Is(stepErr, expectedErr)).To(BeTrue())
   685  				})
   686  
   687  				It("points the resource or resource type to the scope", func() {
   688  					// even though we failed to check, we should still point to the new
   689  					// scope; it'd be kind of weird leave the resource pointing to the old
   690  					// scope for a substantial config change that also happens to be
   691  					// broken.
   692  					Expect(fakeDelegate.PointToCheckedConfigCallCount()).To(Equal(1))
   693  					scope := fakeDelegate.PointToCheckedConfigArgsForCall(0)
   694  					Expect(scope).To(Equal(fakeResourceConfigScope))
   695  				})
   696  
   697  				It("updates the scope's last check end time", func() {
   698  					Expect(fakeResourceConfigScope.UpdateLastCheckEndTimeCallCount()).To(Equal(1))
   699  				})
   700  
   701  				// Finished is for script success/failure, whereas this is an error
   702  				It("does not emit a Finished event", func() {
   703  					Expect(fakeDelegate.FinishedCallCount()).To(Equal(0))
   704  				})
   705  
   706  				Context("with a script failure", func() {
   707  					BeforeEach(func() {
   708  						fakeClient.RunCheckStepReturns(worker.CheckResult{}, runtime.ErrResourceScriptFailed{
   709  							ExitStatus: 42,
   710  						})
   711  					})
   712  
   713  					It("does not error", func() {
   714  						// don't return an error - the script output has already been
   715  						// printed, and emitting an errored event would double it up
   716  						Expect(stepErr).ToNot(HaveOccurred())
   717  					})
   718  
   719  					It("updates the scope's last check end time", func() {
   720  						Expect(fakeResourceConfigScope.UpdateLastCheckEndTimeCallCount()).To(Equal(1))
   721  					})
   722  
   723  					It("emits a failed Finished event", func() {
   724  						Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
   725  						_, succeeded := fakeDelegate.FinishedArgsForCall(0)
   726  						Expect(succeeded).To(BeFalse())
   727  					})
   728  				})
   729  			})
   730  
   731  			Context("having SaveVersions failing", func() {
   732  				var expectedErr error
   733  
   734  				BeforeEach(func() {
   735  					expectedErr = errors.New("save-versions-err")
   736  
   737  					fakeResourceConfigScope.SaveVersionsReturns(expectedErr)
   738  				})
   739  
   740  				It("errors", func() {
   741  					Expect(stepErr).To(HaveOccurred())
   742  					Expect(errors.Is(stepErr, expectedErr)).To(BeTrue())
   743  				})
   744  			})
   745  		})
   746  	})
   747  
   748  	Context("having credentials in the config", func() {
   749  		BeforeEach(func() {
   750  			checkPlan.Source = atc.Source{"some": "((super-secret-source))"}
   751  		})
   752  
   753  		Context("having cred evaluation failing", func() {
   754  			var expectedErr error
   755  
   756  			BeforeEach(func() {
   757  				expectedErr = errors.New("creds-err")
   758  
   759  				fakeRunState.GetReturns(nil, false, expectedErr)
   760  			})
   761  
   762  			It("errors", func() {
   763  				Expect(stepErr).To(HaveOccurred())
   764  				Expect(errors.Is(stepErr, expectedErr)).To(BeTrue())
   765  			})
   766  		})
   767  	})
   768  
   769  	Context("having credentials in a resource type", func() {
   770  		BeforeEach(func() {
   771  			resTypes := atc.VersionedResourceTypes{
   772  				{
   773  					ResourceType: atc.ResourceType{
   774  						Source: atc.Source{
   775  							"some-custom": "((super-secret-source))",
   776  						},
   777  					},
   778  				},
   779  			}
   780  
   781  			checkPlan.VersionedResourceTypes = resTypes
   782  		})
   783  
   784  		Context("having cred evaluation failing", func() {
   785  			var expectedErr error
   786  
   787  			BeforeEach(func() {
   788  				expectedErr = errors.New("creds-err")
   789  
   790  				fakeRunState.GetReturns(nil, false, expectedErr)
   791  			})
   792  
   793  			It("errors", func() {
   794  				Expect(stepErr).To(HaveOccurred())
   795  				Expect(errors.Is(stepErr, expectedErr)).To(BeTrue())
   796  			})
   797  		})
   798  	})
   799  
   800  	Context("having a timeout that fails parsing", func() {
   801  		BeforeEach(func() {
   802  			checkPlan.Timeout = "bogus"
   803  		})
   804  
   805  		It("errors", func() {
   806  			Expect(stepErr).To(HaveOccurred())
   807  			Expect(stepErr.Error()).To(ContainSubstring("invalid duration"))
   808  		})
   809  	})
   810  })