github.com/chenbh/concourse/v6@v6.4.2/atc/lidar/scanner_test.go (about) 1 package lidar_test 2 3 import ( 4 "context" 5 "errors" 6 "time" 7 8 "code.cloudfoundry.org/lager/lagertest" 9 "github.com/chenbh/concourse/v6/atc" 10 "github.com/chenbh/concourse/v6/atc/creds/credsfakes" 11 "github.com/chenbh/concourse/v6/atc/db" 12 "github.com/chenbh/concourse/v6/atc/db/dbfakes" 13 "github.com/chenbh/concourse/v6/atc/db/lock/lockfakes" 14 "github.com/chenbh/concourse/v6/atc/lidar" 15 . "github.com/onsi/ginkgo" 16 . "github.com/onsi/gomega" 17 ) 18 19 type Scanner interface { 20 Run(ctx context.Context) error 21 } 22 23 var _ = Describe("Scanner", func() { 24 var ( 25 err error 26 27 fakeCheckFactory *dbfakes.FakeCheckFactory 28 fakeSecrets *credsfakes.FakeSecrets 29 30 logger *lagertest.TestLogger 31 scanner Scanner 32 ) 33 34 BeforeEach(func() { 35 fakeCheckFactory = new(dbfakes.FakeCheckFactory) 36 fakeSecrets = new(credsfakes.FakeSecrets) 37 38 logger = lagertest.NewTestLogger("test") 39 scanner = lidar.NewScanner( 40 logger, 41 fakeCheckFactory, 42 fakeSecrets, 43 time.Minute*1, 44 time.Minute*1, 45 time.Minute*10, 46 ) 47 }) 48 49 JustBeforeEach(func() { 50 err = scanner.Run(context.TODO()) 51 }) 52 53 Describe("Run", func() { 54 var fakeLock *lockfakes.FakeLock 55 56 BeforeEach(func() { 57 fakeLock = new(lockfakes.FakeLock) 58 fakeCheckFactory.AcquireScanningLockReturns(fakeLock, true, nil) 59 }) 60 61 Context("when fetching resources fails", func() { 62 BeforeEach(func() { 63 fakeCheckFactory.ResourcesReturns(nil, errors.New("nope")) 64 }) 65 66 It("errors", func() { 67 Expect(err).To(HaveOccurred()) 68 }) 69 }) 70 71 Context("when fetching resources succeeds", func() { 72 var fakeResource *dbfakes.FakeResource 73 74 BeforeEach(func() { 75 fakeResource = new(dbfakes.FakeResource) 76 fakeResource.NameReturns("some-name") 77 fakeResource.TagsReturns([]string{"tag-a", "tag-b"}) 78 fakeResource.SourceReturns(atc.Source{"some": "source"}) 79 80 fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource}, nil) 81 }) 82 83 Context("when fetching resource types fails", func() { 84 BeforeEach(func() { 85 fakeCheckFactory.ResourceTypesReturns(nil, errors.New("nope")) 86 }) 87 88 It("errors", func() { 89 Expect(err).To(HaveOccurred()) 90 }) 91 }) 92 93 Context("when fetching resources types succeeds", func() { 94 var fakeResourceType *dbfakes.FakeResourceType 95 96 BeforeEach(func() { 97 fakeResourceType = new(dbfakes.FakeResourceType) 98 fakeResourceType.NameReturns("some-type") 99 fakeResourceType.TypeReturns("some-base-type") 100 fakeResourceType.TagsReturns([]string{"some-tag"}) 101 fakeResourceType.SourceReturns(atc.Source{"some": "type-source"}) 102 103 fakeCheckFactory.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil) 104 }) 105 106 Context("when the resource parent type is a base type", func() { 107 BeforeEach(func() { 108 fakeResource.TypeReturns("base-type") 109 }) 110 111 Context("when the check interval is parseable", func() { 112 BeforeEach(func() { 113 fakeResource.CheckEveryReturns("10s") 114 }) 115 116 Context("when the last check end time is within our interval", func() { 117 BeforeEach(func() { 118 fakeResource.LastCheckEndTimeReturns(time.Now()) 119 }) 120 121 It("does not check", func() { 122 Expect(fakeCheckFactory.CreateCheckCallCount()).To(Equal(0)) 123 }) 124 125 It("clears the check error", func() { 126 Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1)) 127 Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil()) 128 }) 129 }) 130 131 Context("when the last check end time is past our interval", func() { 132 BeforeEach(func() { 133 fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Hour)) 134 }) 135 136 It("creates a check", func() { 137 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1)) 138 }) 139 140 It("clears the check error", func() { 141 Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1)) 142 Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil()) 143 }) 144 145 It("sends a notification for the checker to run", func() { 146 Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1)) 147 }) 148 149 Context("when try creating a check panic", func() { 150 BeforeEach(func() { 151 fakeCheckFactory.TryCreateCheckStub = func(context.Context, db.Checkable, db.ResourceTypes, atc.Version, bool) (db.Check, bool, error) { 152 panic("something went wrong") 153 } 154 }) 155 156 It("recover from the panic", func() { 157 Expect(err).ToNot(HaveOccurred()) 158 Eventually(fakeResource.SetCheckSetupErrorCallCount).Should(Equal(1)) 159 Eventually(fakeResource.SetCheckSetupErrorArgsForCall(0).Error).Should(ContainSubstring("something went wrong")) 160 }) 161 }) 162 }) 163 164 Context("when the checkable has a pinned version", func() { 165 BeforeEach(func() { 166 fakeResource.CurrentPinnedVersionReturns(atc.Version{"some": "version"}) 167 }) 168 169 It("creates a check", func() { 170 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1)) 171 _, _, _, fromVersion, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0) 172 Expect(fromVersion).To(Equal(atc.Version{"some": "version"})) 173 }) 174 175 It("clears the check error", func() { 176 Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1)) 177 Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil()) 178 }) 179 180 It("sends a notification for the checker to run", func() { 181 Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1)) 182 }) 183 }) 184 185 Context("when the checkable does not have a pinned version", func() { 186 BeforeEach(func() { 187 fakeResource.CurrentPinnedVersionReturns(nil) 188 }) 189 190 It("creates a check", func() { 191 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1)) 192 _, _, _, fromVersion, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0) 193 Expect(fromVersion).To(BeNil()) 194 }) 195 196 It("clears the check error", func() { 197 Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1)) 198 Expect(fakeResource.SetCheckSetupErrorArgsForCall(0)).To(BeNil()) 199 }) 200 201 It("sends a notification for the checker to run", func() { 202 Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1)) 203 }) 204 }) 205 }) 206 }) 207 208 Context("when the resource has a parent type", func() { 209 BeforeEach(func() { 210 fakeResource.TypeReturns("custom-type") 211 fakeResource.PipelineIDReturns(1) 212 fakeResourceType.NameReturns("custom-type") 213 fakeResourceType.PipelineIDReturns(1) 214 }) 215 216 Context("when it fails to create a check for parent resource", func() { 217 BeforeEach(func() { 218 fakeResourceType.CheckEveryReturns("not-a-duration") 219 }) 220 221 It("sets the check error", func() { 222 Expect(fakeResourceType.SetCheckSetupErrorCallCount()).To(Equal(1)) 223 Expect(fakeResource.SetCheckSetupErrorCallCount()).To(Equal(1)) 224 err := fakeResource.SetCheckSetupErrorArgsForCall(0) 225 Expect(err.Error()).To(ContainSubstring("parent type 'custom-type' error:")) 226 }) 227 228 It("does not create a check", func() { 229 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(0)) 230 }) 231 }) 232 233 Context("when the parent type requires a check", func() { 234 BeforeEach(func() { 235 fakeResourceType.LastCheckEndTimeReturns(time.Now().Add(-time.Hour)) 236 fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Hour)) 237 }) 238 239 Context("when the parent type has a version", func() { 240 BeforeEach(func() { 241 fakeResourceType.VersionReturns(atc.Version{"some": "version"}) 242 }) 243 244 It("creates a check for both the parent and the resource", func() { 245 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(2)) 246 247 _, checkable, _, _, manuallyTriggered := fakeCheckFactory.TryCreateCheckArgsForCall(0) 248 Expect(checkable).To(Equal(fakeResourceType)) 249 Expect(manuallyTriggered).To(BeFalse()) 250 251 _, checkable, _, _, manuallyTriggered = fakeCheckFactory.TryCreateCheckArgsForCall(1) 252 Expect(checkable).To(Equal(fakeResource)) 253 Expect(manuallyTriggered).To(BeFalse()) 254 }) 255 256 It("sends a notification for the checker to run", func() { 257 Expect(fakeCheckFactory.NotifyCheckerCallCount()).To(Equal(1)) 258 }) 259 }) 260 }) 261 }) 262 }) 263 }) 264 265 Context("when there are multiple resources that use the same resource type", func() { 266 var fakeResource1, fakeResource2 *dbfakes.FakeResource 267 var fakeResourceType *dbfakes.FakeResourceType 268 269 BeforeEach(func() { 270 fakeResource1 = new(dbfakes.FakeResource) 271 fakeResource1.NameReturns("some-name") 272 fakeResource1.SourceReturns(atc.Source{"some": "source"}) 273 fakeResource1.TypeReturns("custom-type") 274 fakeResource1.PipelineIDReturns(1) 275 fakeResource1.LastCheckEndTimeReturns(time.Now().Add(-time.Hour)) 276 277 fakeResource2 = new(dbfakes.FakeResource) 278 fakeResource2.NameReturns("some-name") 279 fakeResource2.SourceReturns(atc.Source{"some": "source"}) 280 fakeResource2.TypeReturns("custom-type") 281 fakeResource2.PipelineIDReturns(1) 282 fakeResource2.LastCheckEndTimeReturns(time.Now().Add(-time.Hour)) 283 284 fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource1, fakeResource2}, nil) 285 286 fakeResourceType = new(dbfakes.FakeResourceType) 287 fakeResourceType.NameReturns("custom-type") 288 fakeResourceType.PipelineIDReturns(1) 289 fakeResourceType.TypeReturns("some-base-type") 290 fakeResourceType.SourceReturns(atc.Source{"some": "type-source"}) 291 fakeResourceType.LastCheckEndTimeReturns(time.Now().Add(-time.Hour)) 292 293 fakeCheckFactory.ResourceTypesReturns([]db.ResourceType{fakeResourceType}, nil) 294 }) 295 296 It("only tries to create a check for the resource type once", func() { 297 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(3)) 298 299 var checked []string 300 _, checkable, _, _, _ := fakeCheckFactory.TryCreateCheckArgsForCall(0) 301 checked = append(checked, checkable.Name()) 302 303 _, checkable, _, _, _ = fakeCheckFactory.TryCreateCheckArgsForCall(1) 304 checked = append(checked, checkable.Name()) 305 306 _, checkable, _, _, _ = fakeCheckFactory.TryCreateCheckArgsForCall(2) 307 checked = append(checked, checkable.Name()) 308 309 Expect(checked).To(ConsistOf([]string{fakeResourceType.Name(), fakeResource1.Name(), fakeResource2.Name()})) 310 }) 311 }) 312 313 Context("Default with webhook check interval", func() { 314 var fakeResource *dbfakes.FakeResource 315 BeforeEach(func() { 316 fakeResource = new(dbfakes.FakeResource) 317 fakeResource.NameReturns("some-name") 318 fakeResource.TagsReturns([]string{"tag-a", "tag-b"}) 319 fakeResource.SourceReturns(atc.Source{"some": "source"}) 320 fakeResource.TypeReturns("base-type") 321 fakeResource.CheckEveryReturns("") 322 fakeCheckFactory.ResourcesReturns([]db.Resource{fakeResource}, nil) 323 324 }) 325 326 Context("resource has webhook", func() { 327 BeforeEach(func() { 328 fakeResource.HasWebhookReturns(true) 329 }) 330 331 Context("last check is 9 minutes ago", func() { 332 BeforeEach(func() { 333 fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Minute * 9)) 334 }) 335 336 It("does not create a check", func() { 337 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(0)) 338 }) 339 }) 340 341 Context("last check is 11 minutes ago", func() { 342 BeforeEach(func() { 343 fakeResource.LastCheckEndTimeReturns(time.Now().Add(-time.Minute * 11)) 344 }) 345 346 It("does not create a check", func() { 347 Expect(fakeCheckFactory.TryCreateCheckCallCount()).To(Equal(1)) 348 }) 349 }) 350 }) 351 }) 352 }) 353 })