@@ -419,6 +419,62 @@ func TestImageRepositoryReconciler_scan(t *testing.T) {
419419 }
420420}
421421
422+ // deadlineCapturingRoundTripper records whether the request context carried a
423+ // deadline, then delegates to the wrapped RoundTripper.
424+ type deadlineCapturingRoundTripper struct {
425+ rt http.RoundTripper
426+ sawDeadl * bool
427+ }
428+
429+ func (d * deadlineCapturingRoundTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
430+ if _ , ok := req .Context ().Deadline (); ok {
431+ * d .sawDeadl = true
432+ }
433+ return d .rt .RoundTrip (req )
434+ }
435+
436+ // TestImageRepositoryReconciler_scan_noOwnTimeout ensures scan does not apply a
437+ // timeout of its own. The timeout must be owned by the caller (reconcile) and
438+ // span the whole operation, so that the lazily-fetched registry credentials are
439+ // not cut short by a second, separate timeout. Given a context without a
440+ // deadline, scan must not introduce one.
441+ func TestImageRepositoryReconciler_scan_noOwnTimeout (t * testing.T ) {
442+ g := NewWithT (t )
443+
444+ registryServer := test .NewRegistryServer ()
445+ defer registryServer .Close ()
446+
447+ imgRepo , _ , err := test .LoadImages (registryServer , "test-scan-timeout-" + randStringRunes (5 ), []string {"a" })
448+ g .Expect (err ).ToNot (HaveOccurred ())
449+
450+ r := ImageRepositoryReconciler {
451+ EventRecorder : record .NewFakeRecorder (32 ),
452+ Database : & mockDatabase {},
453+ patchOptions : getPatchOptions (imageRepositoryOwnedConditions , "irc" ),
454+ }
455+
456+ repo := & imagev1.ImageRepository {}
457+ repo .Spec = imagev1.ImageRepositorySpec {
458+ Image : imgRepo ,
459+ Timeout : & metav1.Duration {Duration : time .Hour },
460+ }
461+
462+ ref , err := registry .ParseImageReference (imgRepo , false )
463+ g .Expect (err ).ToNot (HaveOccurred ())
464+
465+ var sawDeadline bool
466+ opts := []remote.Option {
467+ remote .WithTransport (& deadlineCapturingRoundTripper {rt : http .DefaultTransport , sawDeadl : & sawDeadline }),
468+ }
469+
470+ // Pass a context without a deadline. If scan adds its own timeout, the
471+ // registry request context will carry a deadline.
472+ err = r .scan (context .Background (), repo , ref , opts )
473+ g .Expect (err ).ToNot (HaveOccurred ())
474+ g .Expect (sawDeadline ).To (BeFalse (),
475+ "scan must not apply its own timeout; the timeout is owned by reconcile and spans the whole scan" )
476+ }
477+
422478func TestSortTagsAndGetLatestTags (t * testing.T ) {
423479 tests := []struct {
424480 name string
0 commit comments