From 4e0022d4c794da2c4d849a7458c308796db4b884 Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Mon, 3 Nov 2025 11:15:02 +0100 Subject: [PATCH 1/9] v2: add gomock support This should make it easier to write dynamic mocks in unit tests. --- Makefile | 5 ++++- go.mod | 8 +++++++- go.sum | 6 ++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index df0aa387e..9f97e63b5 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ tidy: $(GO) mod tidy make -C v1 tidy -sanity: tidy format vet +sanity: tidy format vet generate make -C v1 sanity git diff --exit-code .PHONY: sanity @@ -70,6 +70,9 @@ sanity: tidy format vet format: verify-gofmt make -C v1 verify-gofmt +generate: + $(GO) generate $(GO_MOD_FLAGS) $(GO_BUILD_FLAGS) ./internal/pkg/... + cover: $(GO) tool cover -html=tests/results/cover.out -o tests/results/cover.html diff --git a/go.mod b/go.mod index 5b204f935..ddda748f8 100644 --- a/go.mod +++ b/go.mod @@ -200,13 +200,16 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/mock v0.6.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.27.0 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.36.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/grpc v1.73.0 // indirect @@ -230,4 +233,7 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect ) -tool github.com/openshift/build-machinery-go +tool ( + github.com/openshift/build-machinery-go + go.uber.org/mock/mockgen +) diff --git a/go.sum b/go.sum index 50b59c0bd..8b76d916e 100644 --- a/go.sum +++ b/go.sum @@ -475,6 +475,8 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -500,8 +502,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= From b9ce192a64f3e74c5864e4f86955be1026442130 Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Mon, 3 Nov 2025 11:25:53 +0100 Subject: [PATCH 2/9] v2: pkg/additional: use generated mock --- go.mod | 2 +- internal/pkg/additional/interface.go | 2 + .../additional/local_stored_collector_test.go | 124 ++---------------- .../additional/mock/interface_generated.go | 57 ++++++++ 4 files changed, 70 insertions(+), 115 deletions(-) create mode 100644 internal/pkg/additional/mock/interface_generated.go diff --git a/go.mod b/go.mod index ddda748f8..690852933 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( require ( github.com/docker/cli v28.3.3+incompatible + go.uber.org/mock v0.6.0 k8s.io/klog v1.0.0 ) @@ -200,7 +201,6 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect - go.uber.org/mock v0.6.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.27.0 // indirect diff --git a/internal/pkg/additional/interface.go b/internal/pkg/additional/interface.go index 057d6b1d1..61826a97f 100644 --- a/internal/pkg/additional/interface.go +++ b/internal/pkg/additional/interface.go @@ -6,6 +6,8 @@ import ( "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" ) +//go:generate go tool mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock + type CollectorInterface interface { AdditionalImagesCollector(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) } diff --git a/internal/pkg/additional/local_stored_collector_test.go b/internal/pkg/additional/local_stored_collector_test.go index 5fb4a14e5..9a1750257 100644 --- a/internal/pkg/additional/local_stored_collector_test.go +++ b/internal/pkg/additional/local_stored_collector_test.go @@ -4,23 +4,13 @@ import ( "context" "testing" - "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" - "go.podman.io/image/v5/types" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" ) -// setup mocks -// we need to mock Manifest, Mirror - -type MockMirror struct{} -type MockManifest struct { - Log clog.PluggableLoggerInterface -} - func TestAdditionalImageCollector(t *testing.T) { log := clog.New("trace") @@ -60,10 +50,7 @@ func TestAdditionalImageCollector(t *testing.T) { }, } - mockmirror := MockMirror{} - manifest := MockManifest{Log: log} - - ex := New(log, cfg, opts, mockmirror, manifest) + ex := New(log, cfg, opts, nil, nil) ctx := context.Background() // this test covers mirrorToDisk @@ -95,10 +82,7 @@ func TestAdditionalImageCollector(t *testing.T) { }, } res, err := ex.AdditionalImagesCollector(ctx) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) assert.ElementsMatch(t, expected, res) }) @@ -106,7 +90,7 @@ func TestAdditionalImageCollector(t *testing.T) { // this test covers diskToMirror opts.Mode = mirror.DiskToMirror opts.Destination = "docker://mirror.acme.com" - ex = New(log, cfg, opts, mockmirror, manifest) + ex = New(log, cfg, opts, nil, nil) t.Run("Testing AdditionalImagesCollector : diskToMirror should pass", func(t *testing.T) { expected := []v2alpha1.CopyImageSchema{ @@ -136,17 +120,14 @@ func TestAdditionalImageCollector(t *testing.T) { }, } res, err := ex.AdditionalImagesCollector(ctx) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) assert.ElementsMatch(t, expected, res) }) t.Run("Testing AdditionalImagesCollector : diskToMirror with generateV1Tags should use latest for images by digest", func(t *testing.T) { // should error diskToMirror opts.Mode = mirror.DiskToMirror - ex = New(log, cfg, opts, mockmirror, manifest) + ex = New(log, cfg, opts, nil, nil) ex = WithV1Tags(ex) expected := []v2alpha1.CopyImageSchema{ { @@ -175,17 +156,14 @@ func TestAdditionalImageCollector(t *testing.T) { }, } res, err := ex.AdditionalImagesCollector(ctx) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) assert.ElementsMatch(t, expected, res) }) // should error mirrorToDisk cfg.Mirror.AdditionalImages[1].Name = "sometest.registry.com/testns/test@shaf30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea" opts.Mode = mirror.MirrorToDisk - ex = New(log, cfg, opts, mockmirror, manifest) + ex = New(log, cfg, opts, nil, nil) t.Run("Testing AdditionalImagesCollector : mirrorToDisk should not fail (skipped)", func(t *testing.T) { expected := []v2alpha1.CopyImageSchema{ @@ -209,10 +187,7 @@ func TestAdditionalImageCollector(t *testing.T) { }, } res, err := ex.AdditionalImagesCollector(ctx) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) assert.ElementsMatch(t, expected, res) }) @@ -220,7 +195,7 @@ func TestAdditionalImageCollector(t *testing.T) { // should error diskToMirror cfg.Mirror.AdditionalImages[1].Name = "sometest.registry.com/testns/test@shaf30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea" opts.Mode = mirror.DiskToMirror - ex = New(log, cfg, opts, mockmirror, manifest) + ex = New(log, cfg, opts, nil, nil) expected := []v2alpha1.CopyImageSchema{ { Destination: "docker://mirror.acme.com/ubi8/ubi:latest", @@ -242,86 +217,7 @@ func TestAdditionalImageCollector(t *testing.T) { }, } res, err := ex.AdditionalImagesCollector(ctx) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) assert.ElementsMatch(t, expected, res) }) } - -func (o MockMirror) Run(ctx context.Context, src, dest string, mode mirror.Mode, opts *mirror.CopyOptions) error { - return nil -} - -func (o MockMirror) Check(ctx context.Context, image string, opts *mirror.CopyOptions, asCopySrc bool) (bool, error) { - return true, nil -} - -func (o MockManifest) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { - opcl := v2alpha1.OperatorLabels{OperatorsOperatorframeworkIoIndexConfigsV1: "/configs"} - opc := v2alpha1.OperatorConfig{Labels: opcl} - ocs := &v2alpha1.OperatorConfigSchema{Config: opc} - return ocs, nil -} - -func (o MockManifest) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { - relatedImages := []v2alpha1.RelatedImage{ - {Name: "testA", Image: "sometestimage-a@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - {Name: "testB", Image: "sometestimage-b@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - {Name: "testC", Image: "sometestimage-c@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - {Name: "testD", Image: "sometestimage-d@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - } - return relatedImages, nil -} - -func (o MockManifest) GetOCIImageIndex(name string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - }, nil -} - -func (o MockManifest) GetOCIImageManifest(name string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - Config: v2alpha1.OCIManifest{ - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, nil -} - -func (o MockManifest) ExtractOCILayers(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o MockManifest) ExtractLayers(filePath, name, label string) error { - return nil -} - -func (o MockManifest) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o MockManifest) ImageDigest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string) (string, error) { - return "123456", nil -} - -func (o MockManifest) ImageManifest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string, instanceDigest *digest.Digest) ([]byte, string, error) { - return nil, "", nil -} diff --git a/internal/pkg/additional/mock/interface_generated.go b/internal/pkg/additional/mock/interface_generated.go new file mode 100644 index 000000000..9e300d998 --- /dev/null +++ b/internal/pkg/additional/mock/interface_generated.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go +// +// Generated by this command: +// +// mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + v2alpha1 "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" + gomock "go.uber.org/mock/gomock" +) + +// MockCollectorInterface is a mock of CollectorInterface interface. +type MockCollectorInterface struct { + ctrl *gomock.Controller + recorder *MockCollectorInterfaceMockRecorder + isgomock struct{} +} + +// MockCollectorInterfaceMockRecorder is the mock recorder for MockCollectorInterface. +type MockCollectorInterfaceMockRecorder struct { + mock *MockCollectorInterface +} + +// NewMockCollectorInterface creates a new mock instance. +func NewMockCollectorInterface(ctrl *gomock.Controller) *MockCollectorInterface { + mock := &MockCollectorInterface{ctrl: ctrl} + mock.recorder = &MockCollectorInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCollectorInterface) EXPECT() *MockCollectorInterfaceMockRecorder { + return m.recorder +} + +// AdditionalImagesCollector mocks base method. +func (m *MockCollectorInterface) AdditionalImagesCollector(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AdditionalImagesCollector", ctx) + ret0, _ := ret[0].([]v2alpha1.CopyImageSchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AdditionalImagesCollector indicates an expected call of AdditionalImagesCollector. +func (mr *MockCollectorInterfaceMockRecorder) AdditionalImagesCollector(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AdditionalImagesCollector", reflect.TypeOf((*MockCollectorInterface)(nil).AdditionalImagesCollector), ctx) +} From eddf48735edf67ba750d8058f87e0eda836a35f7 Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Mon, 3 Nov 2025 15:51:30 +0100 Subject: [PATCH 3/9] v2: pkg/manifest: use generated manifest mock --- internal/pkg/delete/delete_images_test.go | 55 +-- internal/pkg/manifest/interface.go | 2 + .../pkg/manifest/mock/interface_generated.go | 163 +++++++ internal/pkg/operator/common_test.go | 123 +++++- .../pkg/operator/filtered_collector_test.go | 184 ++------ internal/pkg/release/cincinnati_test.go | 64 +-- internal/pkg/release/graph_test.go | 37 +- .../release/local_stored_collector_test.go | 413 +++++++++--------- .../cosign_tag_based_signatures_test.go | 72 ++- 9 files changed, 583 insertions(+), 530 deletions(-) create mode 100644 internal/pkg/manifest/mock/interface_generated.go diff --git a/internal/pkg/delete/delete_images_test.go b/internal/pkg/delete/delete_images_test.go index 01fd39137..3bc845af8 100644 --- a/internal/pkg/delete/delete_images_test.go +++ b/internal/pkg/delete/delete_images_test.go @@ -6,13 +6,13 @@ import ( "os" "testing" - "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" - "go.podman.io/image/v5/types" + "go.uber.org/mock/gomock" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" mirror "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" ) @@ -71,7 +71,12 @@ func TestAllDeleteImages(t *testing.T) { }, } - di := New(log, opts, &mockBatch{}, &mockBlobs{}, isc, &mockManifest{}, "/tmp", &mockSignatureHandler{}) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + di := New(log, opts, &mockBatch{}, &mockBlobs{}, isc, manifestMock, "/tmp", &mockSignatureHandler{}) t.Run("Testing ReadDeleteData : should pass", func(t *testing.T) { opts.Global.WorkingDir = common.TestFolder @@ -99,7 +104,7 @@ func TestAllDeleteImages(t *testing.T) { defer os.RemoveAll(testFolder) opts.Global.WorkingDir = common.TestFolder opts.Global.ForceCacheDelete = true - deleteDI := New(log, opts, &mockBatch{}, &mockBlobs{}, v2alpha1.ImageSetConfiguration{}, &mockManifest{}, "/tmp", &mockSignatureHandler{}) + deleteDI := New(log, opts, &mockBatch{}, &mockBlobs{}, v2alpha1.ImageSetConfiguration{}, manifestMock, "/tmp", &mockSignatureHandler{}) imgs, err := di.ReadDeleteMetaData() if err != nil { t.Fatal("should not fail") @@ -143,8 +148,13 @@ func TestWriteMetaData(t *testing.T) { LocalStorageFQDN: "localhost:8888", } + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + cfg := v2alpha1.ImageSetConfiguration{} - di := New(log, opts, &mockBatch{}, &mockBlobs{}, cfg, &mockManifest{}, "/tmp", &mockSignatureHandler{}) + di := New(log, opts, &mockBatch{}, &mockBlobs{}, cfg, manifestMock, "/tmp", &mockSignatureHandler{}) t.Run("Testing ReadDeleteData : should pass", func(t *testing.T) { cpImages := []v2alpha1.CopyImageSchema{ @@ -162,7 +172,6 @@ func TestWriteMetaData(t *testing.T) { } func TestSigDeleteItems(t *testing.T) { - tempDir := t.TempDir() defer os.RemoveAll(tempDir) @@ -333,8 +342,6 @@ type mockBlobs struct { Fail bool } -type mockManifest struct{} - func (o mockBatch) Worker(ctx context.Context, collectorSchema v2alpha1.CollectorSchema, opts mirror.CopyOptions) (v2alpha1.CollectorSchema, error) { copiedImages := v2alpha1.CollectorSchema{ AllImages: []v2alpha1.CopyImageSchema{}, @@ -355,35 +362,3 @@ func (o *mockBlobs) GatherBlobs(ctx context.Context, image string) (map[string]s } return res, nil } - -func (o mockManifest) GetOCIImageIndex(dir string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{}, nil -} - -func (o mockManifest) GetOCIImageManifest(file string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{}, nil -} - -func (o mockManifest) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { - return &v2alpha1.OperatorConfigSchema{}, nil -} - -func (o mockManifest) ExtractOCILayers(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o mockManifest) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { - return []v2alpha1.RelatedImage{}, nil -} - -func (o mockManifest) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o mockManifest) ImageDigest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string) (string, error) { - return "", nil -} - -func (o mockManifest) ImageManifest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string, instanceDigest *digest.Digest) ([]byte, string, error) { - return nil, "", nil -} diff --git a/internal/pkg/manifest/interface.go b/internal/pkg/manifest/interface.go index 30f4340af..e7ea5055f 100644 --- a/internal/pkg/manifest/interface.go +++ b/internal/pkg/manifest/interface.go @@ -9,6 +9,8 @@ import ( "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" ) +//go:generate go tool mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock + type ManifestInterface interface { GetOCIImageIndex(dir string) (*v2alpha1.OCISchema, error) GetOCIImageManifest(file string) (*v2alpha1.OCISchema, error) diff --git a/internal/pkg/manifest/mock/interface_generated.go b/internal/pkg/manifest/mock/interface_generated.go new file mode 100644 index 000000000..979a3a5df --- /dev/null +++ b/internal/pkg/manifest/mock/interface_generated.go @@ -0,0 +1,163 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go +// +// Generated by this command: +// +// mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + digest "github.com/opencontainers/go-digest" + v2alpha1 "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" + types "go.podman.io/image/v5/types" + gomock "go.uber.org/mock/gomock" +) + +// MockManifestInterface is a mock of ManifestInterface interface. +type MockManifestInterface struct { + ctrl *gomock.Controller + recorder *MockManifestInterfaceMockRecorder + isgomock struct{} +} + +// MockManifestInterfaceMockRecorder is the mock recorder for MockManifestInterface. +type MockManifestInterfaceMockRecorder struct { + mock *MockManifestInterface +} + +// NewMockManifestInterface creates a new mock instance. +func NewMockManifestInterface(ctrl *gomock.Controller) *MockManifestInterface { + mock := &MockManifestInterface{ctrl: ctrl} + mock.recorder = &MockManifestInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockManifestInterface) EXPECT() *MockManifestInterfaceMockRecorder { + return m.recorder +} + +// ConvertOCIIndexToSingleManifest mocks base method. +func (m *MockManifestInterface) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ConvertOCIIndexToSingleManifest", dir, oci) + ret0, _ := ret[0].(error) + return ret0 +} + +// ConvertOCIIndexToSingleManifest indicates an expected call of ConvertOCIIndexToSingleManifest. +func (mr *MockManifestInterfaceMockRecorder) ConvertOCIIndexToSingleManifest(dir, oci any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConvertOCIIndexToSingleManifest", reflect.TypeOf((*MockManifestInterface)(nil).ConvertOCIIndexToSingleManifest), dir, oci) +} + +// ExtractOCILayers mocks base method. +func (m *MockManifestInterface) ExtractOCILayers(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExtractOCILayers", filePath, toPath, label, oci) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExtractOCILayers indicates an expected call of ExtractOCILayers. +func (mr *MockManifestInterfaceMockRecorder) ExtractOCILayers(filePath, toPath, label, oci any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtractOCILayers", reflect.TypeOf((*MockManifestInterface)(nil).ExtractOCILayers), filePath, toPath, label, oci) +} + +// GetOCIImageIndex mocks base method. +func (m *MockManifestInterface) GetOCIImageIndex(dir string) (*v2alpha1.OCISchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOCIImageIndex", dir) + ret0, _ := ret[0].(*v2alpha1.OCISchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOCIImageIndex indicates an expected call of GetOCIImageIndex. +func (mr *MockManifestInterfaceMockRecorder) GetOCIImageIndex(dir any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOCIImageIndex", reflect.TypeOf((*MockManifestInterface)(nil).GetOCIImageIndex), dir) +} + +// GetOCIImageManifest mocks base method. +func (m *MockManifestInterface) GetOCIImageManifest(file string) (*v2alpha1.OCISchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOCIImageManifest", file) + ret0, _ := ret[0].(*v2alpha1.OCISchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOCIImageManifest indicates an expected call of GetOCIImageManifest. +func (mr *MockManifestInterfaceMockRecorder) GetOCIImageManifest(file any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOCIImageManifest", reflect.TypeOf((*MockManifestInterface)(nil).GetOCIImageManifest), file) +} + +// GetOperatorConfig mocks base method. +func (m *MockManifestInterface) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOperatorConfig", file) + ret0, _ := ret[0].(*v2alpha1.OperatorConfigSchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOperatorConfig indicates an expected call of GetOperatorConfig. +func (mr *MockManifestInterfaceMockRecorder) GetOperatorConfig(file any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOperatorConfig", reflect.TypeOf((*MockManifestInterface)(nil).GetOperatorConfig), file) +} + +// GetReleaseSchema mocks base method. +func (m *MockManifestInterface) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetReleaseSchema", filePath) + ret0, _ := ret[0].([]v2alpha1.RelatedImage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetReleaseSchema indicates an expected call of GetReleaseSchema. +func (mr *MockManifestInterfaceMockRecorder) GetReleaseSchema(filePath any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReleaseSchema", reflect.TypeOf((*MockManifestInterface)(nil).GetReleaseSchema), filePath) +} + +// ImageDigest mocks base method. +func (m *MockManifestInterface) ImageDigest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImageDigest", ctx, sourceCtx, imgRef) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ImageDigest indicates an expected call of ImageDigest. +func (mr *MockManifestInterfaceMockRecorder) ImageDigest(ctx, sourceCtx, imgRef any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImageDigest", reflect.TypeOf((*MockManifestInterface)(nil).ImageDigest), ctx, sourceCtx, imgRef) +} + +// ImageManifest mocks base method. +func (m *MockManifestInterface) ImageManifest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string, instanceDigest *digest.Digest) ([]byte, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImageManifest", ctx, sourceCtx, imgRef, instanceDigest) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ImageManifest indicates an expected call of ImageManifest. +func (mr *MockManifestInterfaceMockRecorder) ImageManifest(ctx, sourceCtx, imgRef, instanceDigest any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImageManifest", reflect.TypeOf((*MockManifestInterface)(nil).ImageManifest), ctx, sourceCtx, imgRef, instanceDigest) +} diff --git a/internal/pkg/operator/common_test.go b/internal/pkg/operator/common_test.go index 6b27d4b37..c679be8dd 100644 --- a/internal/pkg/operator/common_test.go +++ b/internal/pkg/operator/common_test.go @@ -1,21 +1,32 @@ package operator import ( - "os" + "errors" + "fmt" + "path" "testing" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" - "github.com/stretchr/testify/assert" + "github.com/openshift/oc-mirror/v2/internal/pkg/parser" ) func TestPrepareDeleteForV1(t *testing.T) { log := clog.New("trace") tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := setupManifestMock(mockCtrl) + type testCase struct { caseName string relatedImages map[string][]v2alpha1.RelatedImage @@ -60,16 +71,15 @@ func TestPrepareDeleteForV1(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, &MockManifest{}) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) ex.Opts.Mode = mirror.MirrorToMirror ex.generateV1DestTags = true ex.Opts.Destination = "docker://localhost:5000/test" res, err := ex.prepareD2MCopyBatch(testCase.relatedImages) - if testCase.expectedError && err == nil { - t.Fatalf("should fail") - } - if !testCase.expectedError && err != nil { - t.Fatal("should not fail") + if testCase.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) } assert.ElementsMatch(t, testCase.expectedResult, res) }) @@ -80,7 +90,12 @@ func TestPrepareM2MCopyBatch(t *testing.T) { log := clog.New("trace") tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := setupManifestMock(mockCtrl) + type testCase struct { caseName string relatedImages map[string][]v2alpha1.RelatedImage @@ -416,15 +431,14 @@ func TestPrepareM2MCopyBatch(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, &MockManifest{}) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) ex.Opts.Mode = mirror.MirrorToMirror ex.Opts.Destination = "docker://localhost:5000/test" res, err := ex.dispatchImagesForM2M(testCase.relatedImages) - if testCase.expectedError && err == nil { - t.Fatalf("should fail") - } - if !testCase.expectedError && err != nil { - t.Fatal("should not fail") + if testCase.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) } assert.ElementsMatch(t, testCase.expectedResult, res) }) @@ -483,3 +497,80 @@ func TestOperatorCollector(t *testing.T) { }) }) } + +func setupManifestMock(mockCtrl *gomock.Controller) *manifestmock.MockManifestInterface { + const ( + validImageDigest = "f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea" + size = 567 + ) + var ( + digestValue = fmt.Sprintf("sha256:%s", validImageDigest) + validOCIImageIndex = &v2alpha1.OCISchema{ + SchemaVersion: 2, + Manifests: []v2alpha1.OCIManifest{ + { + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: digestValue, + Size: size, + }, + }, + } + validImageManifest = &v2alpha1.OCISchema{ + SchemaVersion: 2, + Manifests: []v2alpha1.OCIManifest{ + { + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: digestValue, + Size: size, + }, + }, + Config: v2alpha1.OCIManifest{ + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: digestValue, + Size: size, + }, + } + ) + + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + ImageDigest(gomock.Any(), gomock.Any(), gomock.Any()). + Return(validImageDigest, nil). + AnyTimes() + + manifestMock. + EXPECT(). + GetOCIImageIndex(gomock.Any()). + Return(validOCIImageIndex, nil). + AnyTimes() + + manifestMock. + EXPECT(). + GetOCIImageManifest(gomock.Any()). + Return(validImageManifest, nil). + AnyTimes() + + manifestMock. + EXPECT(). + GetOperatorConfig(gomock.Any()). + DoAndReturn(func(any) (*v2alpha1.OperatorConfigSchema, error) { + return parser.ParseJsonFile[*v2alpha1.OperatorConfigSchema](path.Join(common.TestFolder, "operator-config.json")) + }). + AnyTimes() + + manifestMock. + EXPECT(). + ExtractOCILayers(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + + manifestMock. + EXPECT(). + ConvertOCIIndexToSingleManifest(gomock.Any(), gomock.Any()). + Return(errors.New("not implemented")). + MaxTimes(0) + + return manifestMock +} diff --git a/internal/pkg/operator/filtered_collector_test.go b/internal/pkg/operator/filtered_collector_test.go index b7db39d74..40817bd07 100644 --- a/internal/pkg/operator/filtered_collector_test.go +++ b/internal/pkg/operator/filtered_collector_test.go @@ -2,38 +2,28 @@ package operator import ( "context" - "errors" "fmt" "os" - "path" "path/filepath" "testing" - "github.com/opencontainers/go-digest" "github.com/operator-framework/operator-registry/alpha/declcfg" "github.com/operator-framework/operator-registry/alpha/property" "github.com/otiai10/copy" "github.com/stretchr/testify/assert" - "go.podman.io/image/v5/types" + "go.uber.org/mock/gomock" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + "github.com/openshift/oc-mirror/v2/internal/pkg/manifest" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" - "github.com/openshift/oc-mirror/v2/internal/pkg/parser" ) type MockMirror struct { Fail bool } -type MockManifest struct { - Log clog.PluggableLoggerInterface - FailImageIndex bool - FailImageManifest bool - FailExtract bool -} - type MockHandler struct { Log clog.PluggableLoggerInterface } @@ -78,20 +68,6 @@ var ( }, }, } - // nolint: unused - nominalConfigD2MWithTargetCatalogTag = v2alpha1.ImageSetConfiguration{ - ImageSetConfigurationSpec: v2alpha1.ImageSetConfigurationSpec{ - Mirror: v2alpha1.Mirror{ - Operators: []v2alpha1.Operator{ - { - Catalog: "redhat-operator-index:v4.14", - TargetCatalog: "test-namespace/test-catalog", - TargetTag: "v2.0", - }, - }, - }, - }, - } nominalConfigM2D = v2alpha1.ImageSetConfiguration{ ImageSetConfigurationSpec: v2alpha1.ImageSetConfigurationSpec{ Mirror: v2alpha1.Mirror{ @@ -318,7 +294,11 @@ func TestFilterCollectorM2D(t *testing.T) { } ctx := context.Background() - manifest := &MockManifest{Log: log} + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := setupManifestMock(mockCtrl) testDir, err := filepath.Abs(common.TestFolder) assert.NoError(t, err, "should get tests/ absolute path") @@ -481,7 +461,7 @@ func TestFilterCollectorM2D(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifest) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) ex = ex.withConfig(testCase.config) res, err := ex.OperatorImageCollector(ctx) if testCase.expectedError { @@ -495,9 +475,9 @@ func TestFilterCollectorM2D(t *testing.T) { // this test should cover over 80% M2D t.Run("Testing OperatorImageCollector - Mirror to disk: should pass", func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifest) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) // ensure coverage in new.go - _ = NewWithFilter(log, "working-dir", ex.Config, ex.Opts, ex.Mirror, manifest) + _ = NewWithFilter(log, "working-dir", ex.Config, ex.Opts, ex.Mirror, manifestMock) }) } @@ -517,6 +497,12 @@ func TestFilterCollectorD2M(t *testing.T) { } ctx := context.Background() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := setupManifestMock(mockCtrl) + os.RemoveAll(common.TestFolder + "hold-operator/") os.RemoveAll(common.TestFolder + "operator-images") os.RemoveAll(common.TestFolder + "tmp/") @@ -608,7 +594,7 @@ func TestFilterCollectorD2M(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_DiskToMirror(tempDir, log) + ex := setupFilterCollector_DiskToMirror(tempDir, log, manifestMock) ex = ex.withConfig(testCase.config) res, err := ex.OperatorImageCollector(ctx) if testCase.expectedError { @@ -637,6 +623,12 @@ func TestFilterCollectorM2M(t *testing.T) { } ctx := context.Background() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := setupManifestMock(mockCtrl) + os.RemoveAll(common.TestFolder + "hold-operator/") os.RemoveAll(common.TestFolder + "operator-images") os.RemoveAll(common.TestFolder + "tmp/") @@ -786,7 +778,7 @@ func TestFilterCollectorM2M(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, &MockManifest{}) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) ex.Opts.Mode = mirror.MirrorToMirror ex.Opts.Destination = "docker://localhost:5000/test" ex = ex.withConfig(testCase.config) @@ -801,8 +793,7 @@ func TestFilterCollectorM2M(t *testing.T) { } } -func setupFilterCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface) *FilterCollector { - manifest := &MockManifest{Log: log} +func setupFilterCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *FilterCollector { handler := &MockHandler{Log: log} globalD2M := &mirror.GlobalOptions{ SecurePolicy: false, @@ -843,7 +834,7 @@ func setupFilterCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerI return ex } -func setupFilterCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest *MockManifest) *FilterCollector { +func setupFilterCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *FilterCollector { handler := &MockHandler{Log: log} globalM2D := &mirror.GlobalOptions{ @@ -899,129 +890,6 @@ func (o MockMirror) Check(ctx context.Context, image string, opts *mirror.CopyOp return true, nil } -func (o MockManifest) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { - return parser.ParseJsonFile[*v2alpha1.OperatorConfigSchema](path.Join(common.TestFolder, "operator-config.json")) -} - -func (o MockManifest) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { - relatedImages := []v2alpha1.RelatedImage{ - {Name: "testA", Image: "sometestimage-a@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - {Name: "testB", Image: "sometestimage-b@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - {Name: "testC", Image: "sometestimage-c@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - {Name: "testD", Image: "sometestimage-d@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"}, - } - return relatedImages, nil -} - -func (o MockManifest) GetImageIndex(name string) (*v2alpha1.OCISchema, error) { - if o.FailImageIndex { - return &v2alpha1.OCISchema{}, fmt.Errorf("forced error image index") - } - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - }, nil -} - -func (o MockManifest) GetImageManifest(name string) (*v2alpha1.OCISchema, error) { - if o.FailImageManifest { - return &v2alpha1.OCISchema{}, fmt.Errorf("forced error image index") - } - - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - Config: v2alpha1.OCIManifest{ - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, nil -} - -func (o MockManifest) ExtractLayersOCI(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { - if o.FailExtract { - return fmt.Errorf("forced extract oci fail") - } - return nil -} - -func (o MockManifest) ConvertIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o MockManifest) GetDigest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string) (string, error) { - return "f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", nil -} - -func (o MockManifest) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return errors.New("not implemented") -} - -func (o MockManifest) ExtractOCILayers(from, to, label string, oci *v2alpha1.OCISchema) error { - if o.FailExtract { - return errors.New("forced extract to fail") - } - return nil -} - -func (o MockManifest) GetOCIImageIndex(dir string) (*v2alpha1.OCISchema, error) { - if o.FailImageIndex { - return nil, errors.New("forced error image index") - } - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - }, nil -} - -func (o MockManifest) GetOCIImageManifest(dir string) (*v2alpha1.OCISchema, error) { - if o.FailImageManifest { - return nil, errors.New("forced error image manifest") - } - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - Config: v2alpha1.OCIManifest{ - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, nil -} - -func (o MockManifest) ImageDigest(ctx context.Context, srcCtx *types.SystemContext, ref string) (string, error) { - return "f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", nil -} - -func (o MockManifest) ImageManifest(ctx context.Context, srcCtx *types.SystemContext, ref string, digest *digest.Digest) ([]byte, string, error) { - return nil, "", errors.New("not implemented") -} - func (o MockHandler) getCatalog(filePath string) (OperatorCatalog, error) { return OperatorCatalog{}, nil } diff --git a/internal/pkg/release/cincinnati_test.go b/internal/pkg/release/cincinnati_test.go index a31546c2b..bae5818d7 100644 --- a/internal/pkg/release/cincinnati_test.go +++ b/internal/pkg/release/cincinnati_test.go @@ -8,11 +8,11 @@ import ( "os" "testing" - digest "github.com/opencontainers/go-digest" - "go.podman.io/image/v5/types" + "go.uber.org/mock/gomock" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" ) @@ -21,12 +21,13 @@ type mockSignature struct { } func TestGetReleaseReferenceImages(t *testing.T) { - log := clog.New("trace") + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + tmpDir := t.TempDir() - _ = os.MkdirAll(tmpDir+"/"+"hold-release/cincinnati-graph-data/", 0755) - defer os.RemoveAll(tmpDir) + _ = os.MkdirAll(tmpDir+"/"+"hold-release/cincinnati-graph-data/", 0o755) global := &mirror.GlobalOptions{SecurePolicy: false} global.WorkingDir = tmpDir @@ -106,7 +107,6 @@ func TestGetReleaseReferenceImages(t *testing.T) { } t.Run("TestGetReleaseReferenceImages should pass", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) @@ -130,7 +130,6 @@ func TestGetReleaseReferenceImages(t *testing.T) { }) t.Run("TestGetReleaseReferenceImages should pass (no channels)", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) @@ -156,7 +155,6 @@ func TestGetReleaseReferenceImages(t *testing.T) { }) t.Run("TestGetReleaseReferenceImages should fail", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) @@ -182,7 +180,6 @@ func TestGetReleaseReferenceImages(t *testing.T) { }) t.Run("TestGetReleaseReferenceImages should pass (platform.release & kubevirt)", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) @@ -199,9 +196,15 @@ func TestGetReleaseReferenceImages(t *testing.T) { } c.url = endpoint - mm := NewManifest() + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + ImageDigest(gomock.Any(), gomock.Any(), gomock.Any()). + Return("123456546546546546546546546", nil). + AnyTimes() - sch := NewCincinnati(log, mm, &cfgReleaseKubeVirt, opts, c, true, signature) + sch := NewCincinnati(log, manifestMock, &cfgReleaseKubeVirt, opts, c, true, signature) res, _ := sch.GetReleaseReferenceImages(context.Background()) log.Debug("result from cincinnati %v", res) @@ -209,48 +212,9 @@ func TestGetReleaseReferenceImages(t *testing.T) { t.Fatalf("should return a related images") } }) - -} - -type mockManifest struct{} - -func NewManifest() mockManifest { - return mockManifest{} } func (o mockSignature) GenerateReleaseSignatures(ctx context.Context, rd []v2alpha1.CopyImageSchema) ([]v2alpha1.CopyImageSchema, error) { o.Log.Info("signature verification (mock)") return []v2alpha1.CopyImageSchema{}, nil } - -func (o mockManifest) ImageDigest(ctx context.Context, srcContext *types.SystemContext, img string) (string, error) { - return "123456546546546546546546546", nil -} - -func (o mockManifest) GetOCIImageIndex(dir string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{}, nil -} - -func (o mockManifest) GetOCIImageManifest(file string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{}, nil -} - -func (o mockManifest) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { - return &v2alpha1.OperatorConfigSchema{}, nil -} - -func (o mockManifest) ExtractOCILayers(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o mockManifest) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { - return []v2alpha1.RelatedImage{}, nil -} - -func (o mockManifest) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o mockManifest) ImageManifest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string, instanceDigest *digest.Digest) ([]byte, string, error) { - return nil, "", nil -} diff --git a/internal/pkg/release/graph_test.go b/internal/pkg/release/graph_test.go index 8805946d6..feb2283b3 100644 --- a/internal/pkg/release/graph_test.go +++ b/internal/pkg/release/graph_test.go @@ -7,9 +7,13 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" ) @@ -18,7 +22,6 @@ type mockImageBuilder struct { } func TestCreateGraphImage(t *testing.T) { - log := clog.New("trace") globalM2D := &mirror.GlobalOptions{ SecurePolicy: false, @@ -127,14 +130,18 @@ func TestCreateGraphImage(t *testing.T) { ctx := context.Background() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + // this test should cover over 80% M2D t.Run("Testing CreateGraphImage - Mirror to disk: should pass", func(t *testing.T) { - manifest := &MockManifest{Log: log} ex := &LocalStorageCollector{ Log: log, Mirror: &MockMirror{Fail: false}, Config: cfgm2d, - Manifest: manifest, + Manifest: manifestMock, Opts: m2dOpts, Cincinnati: cincinnati, LocalStorageFQDN: "localhost:9999", @@ -142,22 +149,18 @@ func TestCreateGraphImage(t *testing.T) { } // just to ensure we cover new.go - _ = New(log, "nada", cfgm2d, m2dOpts, &MockMirror{}, &MockManifest{}, cincinnati, &mockImageBuilder{}) + _ = New(log, "nada", cfgm2d, m2dOpts, &MockMirror{}, manifestMock, cincinnati, &mockImageBuilder{}) _, err := ex.CreateGraphImage(ctx, graphURL) - if err != nil { - t.Fatalf("should not fail") - } - + assert.NoError(t, err) }) t.Run("Testing CreateGraphImage - Mirror to disk: should fail", func(t *testing.T) { - manifest := &MockManifest{Log: log} ex := &LocalStorageCollector{ Log: log, Mirror: &MockMirror{Fail: false}, Config: cfgm2d, - Manifest: manifest, + Manifest: manifestMock, Opts: m2dOpts, Cincinnati: cincinnati, LocalStorageFQDN: "localhost:9999", @@ -165,19 +168,15 @@ func TestCreateGraphImage(t *testing.T) { } _, err := ex.CreateGraphImage(ctx, "nada") - if err == nil { - t.Fatalf("should fail") - } - + assert.Error(t, err) }) t.Run("Testing CreateGraphImage - Mirror to disk: should fail", func(t *testing.T) { - manifest := &MockManifest{Log: log} ex := &LocalStorageCollector{ Log: log, Mirror: &MockMirror{Fail: false}, Config: cfgm2d, - Manifest: manifest, + Manifest: manifestMock, Opts: m2dOpts, Cincinnati: cincinnati, LocalStorageFQDN: "localhost:9999", @@ -185,12 +184,8 @@ func TestCreateGraphImage(t *testing.T) { } _, err := ex.CreateGraphImage(ctx, graphURL) - if err == nil { - t.Fatalf("should fail") - } - + assert.Error(t, err) }) - } func (o mockImageBuilder) BuildAndPush(ctx context.Context, targetRef string, layoutPath layout.Path, cmd []string, layers ...v1.Layer) (string, error) { diff --git a/internal/pkg/release/local_stored_collector_test.go b/internal/pkg/release/local_stored_collector_test.go index 96c3d26f7..15be70e23 100644 --- a/internal/pkg/release/local_stored_collector_test.go +++ b/internal/pkg/release/local_stored_collector_test.go @@ -2,6 +2,7 @@ package release import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -9,15 +10,15 @@ import ( "testing" "github.com/google/uuid" - digest "github.com/opencontainers/go-digest" "github.com/otiai10/copy" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "go.podman.io/image/v5/types" + "go.uber.org/mock/gomock" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + "github.com/openshift/oc-mirror/v2/internal/pkg/manifest" + manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" ) @@ -25,13 +26,6 @@ type MockMirror struct { Fail bool } -type MockManifest struct { - Log clog.PluggableLoggerInterface - FailImageIndex bool - FailImageManifest bool - FailExtract bool -} - type MockCincinnati struct { Config v2alpha1.ImageSetConfiguration Opts mirror.CopyOptions @@ -43,24 +37,83 @@ func TestReleaseLocalStoredCollector(t *testing.T) { log := clog.New("trace") tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + const validImageDigest = "3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419" + + var ( + validImageIndex = &v2alpha1.OCISchema{ + SchemaVersion: 2, + Manifests: []v2alpha1.OCIManifest{ + { + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: fmt.Sprintf("sha256:%s", validImageDigest), + Size: 567, + }, + }, + } + validImageManifest = &v2alpha1.OCISchema{ + SchemaVersion: 2, + Manifests: []v2alpha1.OCIManifest{ + { + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: fmt.Sprintf("sha256:%s", validImageDigest), + Size: 567, + }, + }, + Config: v2alpha1.OCIManifest{ + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: fmt.Sprintf("sha256:%s", validImageDigest), + Size: 567, + }, + } + validReleaseSchema = []v2alpha1.RelatedImage{ + {Name: "agent-installer-api-server", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e4182331e"}, + {Name: "agent-installer-node-agent", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:955faaa822dc107f4dffa6a7e457f8d57a65d10949f74f6780ddd63c115e31e5"}, + {Name: "agent-installer-orchestrator", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:4949b93b3fd0f6b22197402ba22c2775eba408b53d30ac2e3ab2dda409314f5e"}, + {Name: "apiserver-network-proxy", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:2a0dd75b1b327a0c5b17145fc71beb2bf805e6cc3b8fc3f672ce06772caddf21"}, + } + ) // this test should cover over 80% M2D t.Run("Testing ReleaseImageCollector - Mirror to disk: should pass", func(t *testing.T) { ctx := context.Background() - manifest := &MockManifest{Log: log} - - ex := setupCollector_MirrorToDisk(tempDir, log, manifest) + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + GetOCIImageIndex(gomock.Any()). + Return(validImageIndex, nil). + AnyTimes() + + manifestMock. + EXPECT(). + GetOCIImageManifest(gomock.Any()). + Return(validImageManifest, nil). + AnyTimes() + + manifestMock. + EXPECT(). + ExtractOCILayers(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + + manifestMock. + EXPECT(). + GetReleaseSchema(gomock.Any()). + Return(validReleaseSchema, nil). + AnyTimes() + + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.10-x86_64")) - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) res, err := ex.ReleaseImageCollector(ctx) - if err != nil { - t.Fatalf("should not fail: %v", err) - } + assert.NoError(t, err) + // must contain 4 release component images // must contain 1 graph image // must contain 1 release image @@ -114,28 +167,34 @@ func TestReleaseLocalStoredCollector(t *testing.T) { }) t.Run("Testing ReleaseImageCollector - Disk to mirror : should pass", func(t *testing.T) { - os.RemoveAll(common.TestFolder + "hold-release/") os.RemoveAll(common.TestFolder + "release-images") os.RemoveAll(common.TestFolder + "tmp/") - ex := setupCollector_DiskToMirror(tempDir, log) - //copy tests/hold-test-fake to working-dir + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + GetReleaseSchema(gomock.Any()). + Return(validReleaseSchema, nil). + AnyTimes() + + manifestMock. + EXPECT(). + ImageDigest(gomock.Any(), gomock.Any(), gomock.Any()). + Return(validImageDigest, nil). + AnyTimes() + + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) + // copy tests/hold-test-fake to working-dir err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.10-x86_64")) - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) res, err := ex.ReleaseImageCollector(context.Background()) - if err != nil { - t.Fatalf("should not fail: %v", err) - } - if len(res) == 0 { - t.Fatalf("should contain at least 1 image") - } - if !strings.Contains(res[0].Source, ex.LocalStorageFQDN) { - t.Fatalf("source images should be from local storage") - } + assert.NoError(t, err) + assert.NotEmpty(t, res, "should contain at least 1 image") + assert.Contains(t, res[0].Source, ex.LocalStorageFQDN, "source images should be from local storage") + // must contain 4 release component images // must contain 1 graph image // must contain 1 release image @@ -189,12 +248,25 @@ func TestReleaseLocalStoredCollector(t *testing.T) { }) t.Run("Testing ReleaseImageCollector with real GetReleaseReferenceImages - Disk to mirror : should pass", func(t *testing.T) { - os.RemoveAll(common.TestFolder + "hold-release/") os.RemoveAll(common.TestFolder + "release-images") os.RemoveAll(common.TestFolder + "tmp/") - ex := setupCollector_DiskToMirror(tempDir, log) + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + GetReleaseSchema(gomock.Any()). + Return(validReleaseSchema, nil). + AnyTimes() + + manifestMock. + EXPECT(). + ImageDigest(gomock.Any(), gomock.Any(), gomock.Any()). + Return(validImageDigest, nil). + AnyTimes() + + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) client := &ocpClient{} client.SetQueryParams(ex.Config.Mirror.Platform.Architectures[0], ex.Config.Mirror.Platform.Channels[0].Name, "") @@ -206,91 +278,118 @@ func TestReleaseLocalStoredCollector(t *testing.T) { ex.Opts.Global.WorkingDir = filepath.Join(common.TestFolder, "working-dir-fake") res, err := ex.ReleaseImageCollector(context.Background()) - if err != nil { - t.Fatalf("should not fail: %v", err) - } - if len(res) == 0 { - t.Fatalf("should contain at least 1 image") - } - if !strings.Contains(res[0].Source, ex.LocalStorageFQDN) { - t.Fatalf("source images should be from local storage") - } + assert.NoError(t, err) + assert.NotEmpty(t, res, "should contain at least 1 image") + assert.Contains(t, res[0].Source, ex.LocalStorageFQDN, "source images should be from local storage") log.Debug("completed test related images %v ", res) }) t.Run("Testing ReleaseImageCollector : should fail image index", func(t *testing.T) { - manifest := &MockManifest{Log: log, FailImageIndex: true} + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) - ex := setupCollector_MirrorToDisk(tempDir, log, manifest) + manifestMock. + EXPECT(). + GetOCIImageIndex(gomock.Any()). + Return(nil, errors.New("forced error image index")). + AnyTimes() + + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) res, err := ex.ReleaseImageCollector(context.Background()) - if err == nil { - t.Fatalf("should fail") - } + assert.Error(t, err) log.Debug("completed test related images %v ", res) }) t.Run("Testing ReleaseImageCollector : should fail image manifest", func(t *testing.T) { - manifest := &MockManifest{Log: log, FailImageManifest: true} - ex := setupCollector_MirrorToDisk(tempDir, log, manifest) + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + GetOCIImageIndex(gomock.Any()). + Return(validImageIndex, nil). + AnyTimes() + + manifestMock. + EXPECT(). + GetOCIImageManifest(gomock.Any()). + Return(nil, errors.New("force fail error")). + AnyTimes() + + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) res, err := ex.ReleaseImageCollector(context.Background()) - if err == nil { - t.Fatalf("should fail") - } + assert.Error(t, err) log.Debug("completed test related images %v ", res) }) t.Run("Testing ReleaseImageCollector : should fail extract", func(t *testing.T) { - manifest := &MockManifest{Log: log, FailExtract: true} - ex := setupCollector_MirrorToDisk(tempDir, log, manifest) + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + GetOCIImageIndex(gomock.Any()). + Return(validImageIndex, nil). + AnyTimes() + + manifestMock. + EXPECT(). + GetOCIImageManifest(gomock.Any()). + Return(validImageManifest, nil). + AnyTimes() + + manifestMock. + EXPECT(). + ExtractOCILayers(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("forced extract oci fail")). + AnyTimes() + + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) res, err := ex.ReleaseImageCollector(context.Background()) - if err == nil { - t.Fatalf("should fail") - } + assert.Error(t, err) log.Debug("completed test related images %v ", res) }) - } func TestGraphImage(t *testing.T) { log := clog.New("trace") tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + t.Run("Testing GraphImage : should fail", func(t *testing.T) { - ex := setupCollector_DiskToMirror(tempDir, log) + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) res, err := ex.GraphImage() - if err != nil { - t.Fatalf("should pass") - } + assert.NoError(t, err) assert.Equal(t, ex.Opts.Destination+"/"+graphImageName+":latest", res) }) - } func TestReleaseImage(t *testing.T) { log := clog.New("trace") tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + t.Run("Testing ReleaseImage : should pass", func(t *testing.T) { os.RemoveAll(common.TestFolder + "hold-release/") os.RemoveAll(common.TestFolder + "release-images") os.RemoveAll(common.TestFolder + "tmp/") - ex := setupCollector_DiskToMirror(tempDir, log) - //copy tests/hold-test-fake to working-dir + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) + // copy tests/hold-test-fake to working-dir err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.9-x86_64")) - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) res, err := ex.ReleaseImage(context.Background()) - if err != nil { - t.Fatalf("should pass: %v", err) - } + assert.NoError(t, err) assert.Contains(t, res, "localhost:5000/test/openshift/release-images") }) } @@ -409,6 +508,10 @@ func TestHandleGraphImage(t *testing.T) { expectedGraphCopy: v2alpha1.CopyImageSchema{}, }, } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { tempDir := t.TempDir() @@ -443,17 +546,33 @@ func TestHandleGraphImage(t *testing.T) { } log := clog.New("trace") - manifestMock := new(ManifestMock) - if testCase.imageInCache { - manifestMock.On("ImageDigest", mock.Anything, mock.Anything, "docker://localhost:9999/openshift/graph-image:latest").Return("123456", nil) - } else { - manifestMock.On("ImageDigest", mock.Anything, mock.Anything, "docker://localhost:9999/openshift/graph-image:latest").Return("", fmt.Errorf("simulating image doesn't exist in cache")) - } - if testCase.imageInWorkingDir { - manifestMock.On("ImageDigest", mock.Anything, mock.Anything, "oci://"+copyOpts.Global.WorkingDir+"/"+graphPreparationDir).Return("123456", nil) - } else { - manifestMock.On("ImageDigest", mock.Anything, mock.Anything, "oci://"+copyOpts.Global.WorkingDir+"/"+graphPreparationDir).Return("", fmt.Errorf("simulating image doesn't exist in cache")) - } + + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + ImageDigest(gomock.Any(), gomock.Any(), gomock.Eq("docker://localhost:9999/openshift/graph-image:latest")). + DoAndReturn(func(any, any, any) (string, error) { + if testCase.imageInCache { + return "123456", nil + } else { + return "", errors.New("simulating image doesn't exist in cache") + } + }). + AnyTimes() + + manifestMock. + EXPECT(). + ImageDigest(gomock.Any(), gomock.Any(), gomock.Eq("oci://"+filepath.Join(copyOpts.Global.WorkingDir, graphPreparationDir))). + DoAndReturn(func(any, any, any) (string, error) { + if testCase.imageInWorkingDir { + return "123456", nil + } else { + return "", errors.New("simulating image doesn't exist in cache") + } + }). + AnyTimes() + if testCase.updateURLOverride != "" { t.Setenv("UPDATE_URL_OVERRIDE", testCase.updateURLOverride) } @@ -467,11 +586,10 @@ func TestHandleGraphImage(t *testing.T) { LogsDir: "/tmp/", } graphImage, err := ex.handleGraphImage(context.Background()) - if testCase.expectedError && err == nil { - t.Error("expecting test to fail with error, but no error returned") - } - if !testCase.expectedError && err != nil { - t.Errorf("unexpected failure: %v", err) + if testCase.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) } testCase.expectedGraphCopy.Source = strings.Replace(testCase.expectedGraphCopy.Source, "TEMPDIR", copyOpts.Global.WorkingDir, 1) testCase.expectedGraphCopy.Origin = strings.Replace(testCase.expectedGraphCopy.Origin, "TEMPDIR", copyOpts.Global.WorkingDir, 1) @@ -482,9 +600,7 @@ func TestHandleGraphImage(t *testing.T) { } } -func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface) *LocalStorageCollector { - manifest := &MockManifest{Log: log} - +func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *LocalStorageCollector { globalD2M := &mirror.GlobalOptions{ SecurePolicy: false, WorkingDir: tempDir + "/working-dir", @@ -546,8 +662,7 @@ func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterfa return ex } -func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest *MockManifest) *LocalStorageCollector { - +func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *LocalStorageCollector { globalM2D := &mirror.GlobalOptions{ SecurePolicy: false, WorkingDir: tempDir, @@ -677,77 +792,6 @@ func (o MockMirror) Check(ctx context.Context, image string, opts *mirror.CopyOp return true, nil } -func (o MockManifest) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { - return nil, nil -} - -func (o MockManifest) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { - relatedImages := []v2alpha1.RelatedImage{ - {Name: "agent-installer-api-server", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e4182331e"}, - {Name: "agent-installer-node-agent", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:955faaa822dc107f4dffa6a7e457f8d57a65d10949f74f6780ddd63c115e31e5"}, - {Name: "agent-installer-orchestrator", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:4949b93b3fd0f6b22197402ba22c2775eba408b53d30ac2e3ab2dda409314f5e"}, - {Name: "apiserver-network-proxy", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:2a0dd75b1b327a0c5b17145fc71beb2bf805e6cc3b8fc3f672ce06772caddf21"}, - } - return relatedImages, nil -} - -func (o MockManifest) GetOCIImageIndex(name string) (*v2alpha1.OCISchema, error) { - if o.FailImageIndex { - return &v2alpha1.OCISchema{}, fmt.Errorf("forced error image index") - } - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - }, nil -} - -func (o MockManifest) GetOCIImageManifest(name string) (*v2alpha1.OCISchema, error) { - if o.FailImageManifest { - return &v2alpha1.OCISchema{}, fmt.Errorf("forced error image index") - } - - return &v2alpha1.OCISchema{ - SchemaVersion: 2, - Manifests: []v2alpha1.OCIManifest{ - { - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, - Config: v2alpha1.OCIManifest{ - MediaType: "application/vnd.oci.image.manifest.v1+json", - Digest: "sha256:3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", - Size: 567, - }, - }, nil -} - -func (o MockManifest) ExtractOCILayers(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { - if o.FailExtract { - return fmt.Errorf("forced extract oci fail") - } - return nil -} - -func (o MockManifest) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (o MockManifest) ImageDigest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string) (string, error) { - return "3ef0b0141abd1548f60c4f3b23ecfc415142b0e842215f38e98610a3b2e52419", nil -} - -func (o MockManifest) ImageManifest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string, instanceDigest *digest.Digest) ([]byte, string, error) { - return nil, "", nil -} - func (o MockCincinnati) GetReleaseReferenceImages(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) { var res []v2alpha1.CopyImageSchema res = append(res, v2alpha1.CopyImageSchema{Type: v2alpha1.TypeOCPRelease, Source: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64", Origin: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64"}) @@ -779,34 +823,3 @@ func (o MockCincinnati) GenerateReleaseSignatures(ctx context.Context, images [] } return imagesByTag, nil } - -type ManifestMock struct { - mock.Mock -} - -func (o *ManifestMock) GetOCIImageIndex(dir string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{}, nil -} -func (o *ManifestMock) GetOCIImageManifest(file string) (*v2alpha1.OCISchema, error) { - return &v2alpha1.OCISchema{}, nil -} -func (o *ManifestMock) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { - return &v2alpha1.OperatorConfigSchema{}, nil -} -func (o *ManifestMock) ExtractOCILayers(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { - return nil -} -func (o *ManifestMock) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { - return []v2alpha1.RelatedImage{}, nil -} -func (o *ManifestMock) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return nil -} -func (o *ManifestMock) ImageDigest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string) (string, error) { - args := o.Called(ctx, sourceCtx, imgRef) - return args.String(0), args.Error(1) -} - -func (o *ManifestMock) ImageManifest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string, instanceDigest *digest.Digest) ([]byte, string, error) { - return nil, "", nil -} diff --git a/internal/pkg/signature/cosign_tag_based_signatures_test.go b/internal/pkg/signature/cosign_tag_based_signatures_test.go index 709448d81..3ce426138 100644 --- a/internal/pkg/signature/cosign_tag_based_signatures_test.go +++ b/internal/pkg/signature/cosign_tag_based_signatures_test.go @@ -2,50 +2,20 @@ package signature import ( "context" - "fmt" + "errors" "os" "testing" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" "go.podman.io/image/v5/manifest" - "go.podman.io/image/v5/types" + "go.uber.org/mock/gomock" - "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" ) -type mockManifest struct{} - -func (m *mockManifest) GetOCIImageIndex(dir string) (*v2alpha1.OCISchema, error) { - return nil, nil -} - -func (m *mockManifest) GetOCIImageManifest(file string) (*v2alpha1.OCISchema, error) { - return nil, nil -} - -func (m *mockManifest) ExtractOCILayers(filePath, toPath, label string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (m *mockManifest) ConvertOCIIndexToSingleManifest(dir string, oci *v2alpha1.OCISchema) error { - return nil -} - -func (m *mockManifest) GetReleaseSchema(filePath string) ([]v2alpha1.RelatedImage, error) { - return nil, nil -} - -func (m *mockManifest) GetOperatorConfig(file string) (*v2alpha1.OperatorConfigSchema, error) { - return nil, nil -} - -func (m *mockManifest) ImageDigest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string) (string, error) { - return "", nil -} - var multiArchManifest = `{ "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", @@ -89,17 +59,6 @@ var multiArchManifest = `{ ] }` -func (m *mockManifest) ImageManifest(ctx context.Context, sourceCtx *types.SystemContext, imgRef string, instanceDigest *digest.Digest) ([]byte, string, error) { - switch imgRef { - case "docker://registry.example.com/test/single:latest": - return []byte("single-arch-manifest"), manifest.DockerV2Schema2MediaType, nil - case "docker://registry.example.com/test/multi:latest": - return []byte(multiArchManifest), manifest.DockerV2ListMediaType, nil - default: - return nil, "", fmt.Errorf("unknown reference") - } -} - func TestSigstoreAttachmentTag(t *testing.T) { tests := []struct { name string @@ -153,10 +112,33 @@ func TestGetSignatureTag(t *testing.T) { SrcImage: srcOpts, } + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + + manifestMock. + EXPECT(). + ImageManifest(gomock.Any(), gomock.Any(), gomock.Eq("docker://registry.example.com/test/single:latest"), gomock.Any()). + Return([]byte("single-arch-manifest"), manifest.DockerV2Schema1MediaType, nil). + AnyTimes() + + manifestMock. + EXPECT(). + ImageManifest(gomock.Any(), gomock.Any(), gomock.Eq("docker://registry.example.com/test/multi:latest"), gomock.Any()). + Return([]byte(multiArchManifest), manifest.DockerV2ListMediaType, nil). + AnyTimes() + + manifestMock. + EXPECT(). + ImageManifest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, "", errors.New("unknown reference")). + AnyTimes() + handler := &SignatureHandler{ opts: opts, log: log, - ocmirrormanifest: &mockManifest{}, + ocmirrormanifest: manifestMock, } tests := []struct { From 1852dd1f5bc4317aef9cdeb758f9c07a473a599f Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Mon, 3 Nov 2025 17:44:03 +0100 Subject: [PATCH 4/9] v2: pkg/mirror: use generated mirror mock --- internal/pkg/batch/concurrent_worker_test.go | 174 +++++++++++------- internal/pkg/cli/delete_test.go | 94 ++++------ internal/pkg/cli/dryrun_test.go | 84 ++++----- internal/pkg/cli/executor_test.go | 150 ++++++--------- internal/pkg/mirror/mirror.go | 12 +- internal/pkg/mirror/mock/mirror_generated.go | 151 +++++++++++++++ internal/pkg/operator/common_test.go | 9 +- .../pkg/operator/filtered_collector_test.go | 57 +++--- internal/pkg/release/graph_test.go | 11 +- .../release/local_stored_collector_test.go | 40 ++-- 10 files changed, 452 insertions(+), 330 deletions(-) create mode 100644 internal/pkg/mirror/mock/mirror_generated.go diff --git a/internal/pkg/batch/concurrent_worker_test.go b/internal/pkg/batch/concurrent_worker_test.go index 8c63e48ef..df065e1c8 100644 --- a/internal/pkg/batch/concurrent_worker_test.go +++ b/internal/pkg/batch/concurrent_worker_test.go @@ -10,11 +10,13 @@ import ( "time" "github.com/distribution/distribution/v3/registry/api/errcode" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" ) type BatchSchema struct { @@ -91,93 +93,126 @@ func TestChannelConcurrentWorker(t *testing.T) { timestampStr := time.Now().Format("20060102_150405") + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + noFailMirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + noFailMirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + collectedImages := v2alpha1.CollectorSchema{AllImages: relatedImages, TotalReleaseImages: 4, TotalOperatorImages: 3, TotalAdditionalImages: 2} + t.Run("Testing m2m Worker - no errors: should pass", func(t *testing.T) { - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(8), timestampStr) + w := New(ChannelConcurrentWorker, log, tempDir, noFailMirrorMock, uint(8), timestampStr) copiedImages, err := w.Worker(context.Background(), collectedImages, m2mopts) - if err != nil { - t.Fatal("should pass") - } + assert.NoError(t, err) assert.ElementsMatch(t, relatedImages, copiedImages.AllImages) }) t.Run("Testing m2d Worker - no errors: should pass", func(t *testing.T) { - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - - w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(8), timestampStr) + w := New(ChannelConcurrentWorker, log, tempDir, noFailMirrorMock, uint(8), timestampStr) copiedImages, err := w.Worker(context.Background(), collectedImages, m2dopts) - if err != nil { - t.Fatal("should pass") - } + assert.NoError(t, err) assert.ElementsMatch(t, relatedImages, copiedImages.AllImages) }) + t.Run("Testing d2m Worker - no errors: should pass", func(t *testing.T) { - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(8), timestampStr) + w := New(ChannelConcurrentWorker, log, tempDir, noFailMirrorMock, uint(8), timestampStr) copiedImages, err := w.Worker(context.Background(), collectedImages, d2mopts) - if err != nil { - t.Fatal("should pass") - } + assert.NoError(t, err) assert.ElementsMatch(t, relatedImages, copiedImages.AllImages) }) t.Run("Testing delete Worker - no errors: should pass", func(t *testing.T) { - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) - w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(8), timestampStr) + w := New(ChannelConcurrentWorker, log, tempDir, noFailMirrorMock, uint(8), timestampStr) copiedImages, err := w.Worker(context.Background(), collectedImages, deleteopts) - if err != nil { - t.Fatal("should pass") - } + assert.NoError(t, err) assert.ElementsMatch(t, relatedImages, copiedImages.AllImages) }) + t.Run("Testing m2d Worker - single error on operator: should return safe error", func(t *testing.T) { - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, "docker://registry/name/namespace/sometestimage-c@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", mock.Anything, mock.Anything, mock.Anything).Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Eq("docker://registry/name/namespace/sometestimage-c@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}). + AnyTimes() + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(8), timestampStr) copiedImages, err := w.Worker(context.Background(), collectedImages, m2dopts) - if err == nil { - t.Fatal("should return safe error") - } - + assert.Error(t, err, "should return safe error") assert.Equal(t, len(relatedImages)-1, len(copiedImages.AllImages)) }) + t.Run("Testing d2m Worker - 1 err release / 2 errors: should return unsafe error", func(t *testing.T) { - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, "docker://registry/name/namespace/sometestimage-f@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", mock.Anything, mock.Anything, mock.Anything).Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}) - mirrorMock.On("Run", mock.Anything, "docker://registry/name/namespace/sometestimage-b@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", mock.Anything, mock.Anything, mock.Anything).Return(errcode.Error{Code: errcode.ErrorCodeManifestUnknown, Message: "Manifest Unknown"}) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Eq("docker://registry/name/namespace/sometestimage-f@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}). + AnyTimes() + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Eq("docker://registry/name/namespace/sometestimage-b@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errcode.Error{Code: errcode.ErrorCodeManifestUnknown, Message: "Manifest Unknown"}). + AnyTimes() + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(8), timestampStr) copiedImages, err := w.Worker(context.Background(), collectedImages, d2mopts) - if err == nil { - t.Fatal("should return unsafe error") - } - + assert.Error(t, err, "should return unsafe error") assert.GreaterOrEqual(t, len(relatedImages), len(copiedImages.AllImages)) }) + t.Run("Testing d2m Worker - 2 errors: should return safe error", func(t *testing.T) { - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, "docker://registry/name/namespace/sometestimage-f@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", mock.Anything, mock.Anything, mock.Anything).Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}) - mirrorMock.On("Run", mock.Anything, "docker://registry/name/namespace/sometestimage-h@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", mock.Anything, mock.Anything, mock.Anything).Return(errcode.Error{Code: errcode.ErrorCodeManifestUnknown, Message: "Manifest Unknown"}) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Eq("docker://registry/name/namespace/sometestimage-f@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}). + AnyTimes() + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Eq("docker://registry/name/namespace/sometestimage-h@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errcode.Error{Code: errcode.ErrorCodeManifestUnknown, Message: "Manifest Unknown"}). + AnyTimes() + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(8), timestampStr) copiedImages, err := w.Worker(context.Background(), collectedImages, d2mopts) - if err == nil { - t.Fatal("should return safe error") - } - + assert.Error(t, err, "should return safe error") assert.GreaterOrEqual(t, len(relatedImages), len(copiedImages.AllImages)) }) @@ -196,9 +231,20 @@ func TestChannelConcurrentWorker(t *testing.T) { collectedImages := v2alpha1.CollectorSchema{AllImages: relatedImages, TotalOperatorImages: 2, CopyImageSchemaMap: *copyImageSchemaMap} - mirrorMock := new(MirrorMock) - mirrorMock.On("Run", mock.Anything, "docker://registry/name/namespace/sometestimage-f@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", mock.Anything, mock.Anything, mock.Anything).Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}) - mirrorMock.On("Run", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Eq("docker://registry/name/namespace/sometestimage-f@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea"), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errcode.Error{Code: errcode.ErrorCodeUnauthorized, Message: "unauthorized"}). + AnyTimes() + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + w := New(ChannelConcurrentWorker, log, tempDir, mirrorMock, uint(1), timestampStr) _, err := w.Worker(context.Background(), collectedImages, m2dopts) @@ -462,30 +508,16 @@ func TestShouldSkipImage(t *testing.T) { } skip, err := shouldSkipImageOld(testCase.img, testCase.mode, testCase.errArray) - if testCase.expectedError && err == nil { - t.Error("expected to fail with error, but no error was returned") - } - if !testCase.expectedError && err != nil { - t.Errorf("unexpected failure : %v", err) + if testCase.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) } assert.Equal(t, testCase.expectToSkip, skip) }) } } -type MirrorMock struct { - mock.Mock -} - -func (o *MirrorMock) Run(ctx context.Context, src, dest string, mode mirror.Mode, opts *mirror.CopyOptions) error { - args := o.Called(ctx, src, dest, mode, opts) - return args.Error(0) -} - -func (o *MirrorMock) Check(ctx context.Context, image string, opts *mirror.CopyOptions, asCopySrc bool) (bool, error) { - return true, nil -} - // later, we can consider making this func smarter: // by putting related images, release content images first // and deferring operator bundle images, second diff --git a/internal/pkg/cli/delete_test.go b/internal/pkg/cli/delete_test.go index 803fe501c..e6aa514ab 100644 --- a/internal/pkg/cli/delete_test.go +++ b/internal/pkg/cli/delete_test.go @@ -6,20 +6,22 @@ import ( "testing" "github.com/distribution/distribution/v3/registry" + "github.com/otiai10/copy" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" "github.com/openshift/oc-mirror/v2/internal/pkg/config" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" - "github.com/otiai10/copy" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" ) // TestExecutorValidateDelete func TestExecutorValidateDelete(t *testing.T) { t.Run("Testing Delete Executor : validate delete should pass", func(t *testing.T) { - log := clog.New("trace") global := &mirror.GlobalOptions{ @@ -58,28 +60,26 @@ func TestExecutorValidateDelete(t *testing.T) { opts.Global.ConfigPath = common.TestFolder + "isc.yaml" err := ex.ValidateDelete([]string{"docker://test"}) - if err == nil { - t.Fatalf("should fail") - } + assert.Error(t, err) // check for config path error opts.Global.ConfigPath = "" opts.Global.DeleteGenerate = true err = ex.ValidateDelete([]string{"docker://test"}) - assert.Equal(t, "the --config flag is mandatory when used with the --generate flag", err.Error()) + assert.ErrorContains(t, err, "the --config flag is mandatory when used with the --generate flag") // check when workspace is not set opts.Global.ConfigPath = common.TestFolder + "isc.yaml" ex.Opts.Global.WorkingDir = "" err = ex.ValidateDelete([]string{"docker://test"}) - assert.Equal(t, "use the --workspace flag, it is mandatory when using the delete command with the --generate flag", err.Error()) + assert.ErrorContains(t, err, "use the --workspace flag, it is mandatory when using the delete command with the --generate flag") // check when delete yaml file ex.Opts.Global.WorkingDir = "file://test" opts.Global.ConfigPath = common.TestFolder + "isc.yaml" opts.Global.DeleteGenerate = false err = ex.ValidateDelete([]string{"test"}) - assert.Equal(t, "the --delete-yaml-file flag is mandatory when not using the --generate flag", err.Error()) + assert.ErrorContains(t, err, "the --delete-yaml-file flag is mandatory when not using the --generate flag") // check when destination is set but no protocol ex.Opts.Global.WorkingDir = "file://test" @@ -87,7 +87,7 @@ func TestExecutorValidateDelete(t *testing.T) { opts.Global.DeleteGenerate = false opts.Global.DeleteYaml = common.TestFolder + "delete/delete-images.yaml" err = ex.ValidateDelete([]string{"test"}) - assert.Equal(t, "the destination registry argument must have a docker:// protocol prefix", err.Error()) + assert.ErrorContains(t, err, "the destination registry argument must have a docker:// protocol prefix") // check when destination is set yaml file not found ex.Opts.Global.WorkingDir = "file://test" @@ -95,8 +95,7 @@ func TestExecutorValidateDelete(t *testing.T) { opts.Global.DeleteGenerate = false opts.Global.DeleteYaml = "../../nothing" err = ex.ValidateDelete([]string{"docker://test"}) - assert.Equal(t, "file not found ../../nothing", err.Error()) - + assert.ErrorContains(t, err, "file not found ../../nothing") }) } @@ -143,22 +142,22 @@ func TestExecutorCompleteDelete(t *testing.T) { defer os.RemoveAll("../../pkg/cli/working-dir") err := ex.CompleteDelete([]string{"docker://myregistry:5000"}) - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) // using imagesetconfig not deleteimagesetconfig - should fail opts.Global.ConfigPath = common.TestFolder + "isc.yaml" err = ex.CompleteDelete([]string{"docker://myregistry:5000"}) - if err == nil { - t.Fatalf("should fail") - } - + assert.Error(t, err) }) } // TestExecutorRunDelete func TestExecutorRunDelete(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + t.Run("Testing Executor : run delete should pass", func(t *testing.T) { log := clog.New("trace") @@ -185,23 +184,16 @@ func TestExecutorRunDelete(t *testing.T) { opts.Global.From = "" testFolder := t.TempDir() - defer os.RemoveAll(testFolder) // storage cache for test regCfg, err := setupRegForTest(testFolder) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) reg, err := registry.NewRegistry(context.Background(), regCfg) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) // read the DeleteImageSetConfiguration dcfg, err := config.ReadConfig(opts.Global.ConfigPath, v2alpha1.DeleteImageSetConfigurationKind) - if err != nil { - log.Error("imagesetconfig %v ", err) - } + assert.NoError(t, err) converted := dcfg.(v2alpha1.DeleteImageSetConfiguration) // we now coerce deleteimagesetconfig to imagesetconfig @@ -216,10 +208,9 @@ func TestExecutorRunDelete(t *testing.T) { } collector := &Collector{Log: log, Config: isc, Opts: opts, Fail: false} - mockMirror := Mirror{} mockBatch := Batch{} - _ = os.MkdirAll(testFolder+"/docker/registry/v2/repositories", 0755) - _ = os.MkdirAll(common.TestFolder+"cache-fake-temp", 0755) + _ = os.MkdirAll(testFolder+"/docker/registry/v2/repositories", 0o755) + _ = os.MkdirAll(common.TestFolder+"cache-fake-temp", 0o755) defer os.RemoveAll(common.TestFolder + "cache-fake-temp") opts.LocalStorageFQDN = regCfg.HTTP.Addr @@ -231,7 +222,7 @@ func TestExecutorRunDelete(t *testing.T) { Release: collector, AdditionalImages: collector, HelmCollector: collector, - Mirror: mockMirror, + Mirror: mirrorMock, Batch: &mockBatch, LocalStorageService: *reg, LogsDir: "/tmp/", @@ -247,14 +238,10 @@ func TestExecutorRunDelete(t *testing.T) { // copy cache-fake to cache-fake-temp for testing err = copy.Copy(common.TestFolder+"cache-fake/", common.TestFolder+"cache-fake-temp/") - if err != nil { - t.Fatalf("should not fail : %v", err) - } + assert.NoError(t, err) err = ex.RunDelete(res) - if err != nil { - t.Fatalf("should not fail : %v", err) - } + assert.NoError(t, err) }) t.Run("Testing Executor : run delete --generate should pass", func(t *testing.T) { @@ -288,19 +275,13 @@ func TestExecutorRunDelete(t *testing.T) { // storage cache for test regCfg, err := setupRegForTest(testFolder) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) reg, err := registry.NewRegistry(context.Background(), regCfg) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) // read the DeleteImageSetConfiguration dcfg, err := config.ReadConfig(opts.Global.ConfigPath, v2alpha1.DeleteImageSetConfigurationKind) - if err != nil { - log.Error("imagesetconfig %v ", err) - } + assert.NoError(t, err) converted := dcfg.(v2alpha1.DeleteImageSetConfiguration) // we now coerce deleteimagesetconfig to imagesetconfig @@ -315,10 +296,9 @@ func TestExecutorRunDelete(t *testing.T) { } collector := &Collector{Log: log, Config: isc, Opts: opts, Fail: false} - mockMirror := Mirror{} mockBatch := Batch{} - _ = os.MkdirAll(testFolder+"/docker/registry/v2/repositories", 0755) - _ = os.MkdirAll(common.TestFolder+"cache-fake-temp", 0755) + _ = os.MkdirAll(testFolder+"/docker/registry/v2/repositories", 0o755) + _ = os.MkdirAll(common.TestFolder+"cache-fake-temp", 0o755) defer os.RemoveAll(common.TestFolder + "cache-fake-temp") opts.LocalStorageFQDN = regCfg.HTTP.Addr @@ -330,7 +310,7 @@ func TestExecutorRunDelete(t *testing.T) { Release: collector, AdditionalImages: collector, HelmCollector: collector, - Mirror: mockMirror, + Mirror: mirrorMock, Batch: &mockBatch, LocalStorageService: *reg, LogsDir: "/tmp/", @@ -346,14 +326,10 @@ func TestExecutorRunDelete(t *testing.T) { // copy cache-fake to cache-fake-temp for testing err = copy.Copy(common.TestFolder+"cache-fake/", common.TestFolder+"cache-fake-temp/") - if err != nil { - t.Fatalf("should not fail : %v", err) - } + assert.NoError(t, err) // test generate err = ex.RunDelete(res) - if err != nil { - t.Fatalf("should not fail : %v", err) - } + assert.NoError(t, err) }) } diff --git a/internal/pkg/cli/dryrun_test.go b/internal/pkg/cli/dryrun_test.go index 895824014..a651783bf 100644 --- a/internal/pkg/cli/dryrun_test.go +++ b/internal/pkg/cli/dryrun_test.go @@ -2,21 +2,25 @@ package cli import ( "context" + "errors" "os" "path/filepath" "testing" "github.com/distribution/distribution/v3/registry" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/config" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" - "github.com/stretchr/testify/assert" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" ) // TestExecutorRunPrepare func TestDryRun(t *testing.T) { - var imgs = []v2alpha1.CopyImageSchema{ + imgs := []v2alpha1.CopyImageSchema{ {Source: "docker://registry/name/namespace/sometestimage-a@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", Destination: "oci:test"}, {Source: "docker://registry/name/namespace/sometestimage-b@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", Destination: "oci:test"}, {Source: "docker://registry/name/namespace/sometestimage-c@sha256:f30638f60452062aba36a26ee6c036feead2f03b28f2c47f2b0a991e41baebea", Destination: "oci:test"}, @@ -39,6 +43,9 @@ func TestDryRun(t *testing.T) { _, destOpts := mirror.ImageDestFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "dest-", "dcreds") _, retryOpts := mirror.RetryFlags() + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + t.Run("Testing Executor : dryrun M2D should pass", func(t *testing.T) { testFolder := t.TempDir() defer os.RemoveAll(testFolder) @@ -47,13 +54,9 @@ func TestDryRun(t *testing.T) { // storage cache for test regCfg, err := setupRegForTest(testFolder) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) reg, err := registry.NewRegistry(context.Background(), regCfg) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) opts := &mirror.CopyOptions{ Global: global, @@ -66,19 +69,23 @@ func TestDryRun(t *testing.T) { Dev: false, LocalStorageFQDN: regCfg.HTTP.Addr, } + cfg := v2alpha1.ImageSetConfiguration{} // read the ImageSetConfiguration res, err := config.ReadConfig(opts.Global.ConfigPath, v2alpha1.ImageSetConfigurationKind) if err != nil { - log.Error("imagesetconfig %v ", err) - } - var cfg v2alpha1.ImageSetConfiguration - if res == nil { - cfg = v2alpha1.ImageSetConfiguration{} + log.Error("imagesetconfig %v", err) } else { cfg = res.(v2alpha1.ImageSetConfiguration) } collector := &Collector{Log: log, Config: cfg, Opts: *opts, Fail: false} - mockMirror := Mirror{} + + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Check(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(true, nil). + AnyTimes() ex := &ExecutorSchema{ Log: log, @@ -86,46 +93,36 @@ func TestDryRun(t *testing.T) { Operator: collector, Release: collector, AdditionalImages: collector, - Mirror: mockMirror, + Mirror: mirrorMock, LocalStorageService: *reg, LogsDir: testFolder, MakeDir: MakeDir{}, } err = ex.DryRun(context.TODO(), imgs) - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) mappingPath := filepath.Join(testFolder, dryRunOutDir, "mapping.txt") assert.FileExists(t, mappingPath) mappingBytes, err := os.ReadFile(mappingPath) - if err != nil { - t.Fatalf("failed to read mapping file: %v", err) - } + assert.NoError(t, err, "failed to read mapping file") mapping := string(mappingBytes) for _, img := range imgs { assert.Contains(t, mapping, img.Source+"="+img.Destination) } - }) t.Run("Testing Executor : dryrun M2D - errors finding images in cache - should generate missing.txt", func(t *testing.T) { testFolder := t.TempDir() - defer os.RemoveAll(testFolder) global.WorkingDir = testFolder // storage cache for test regCfg, err := setupRegForTest(testFolder) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) reg, err := registry.NewRegistry(context.Background(), regCfg) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) opts := &mirror.CopyOptions{ Global: global, @@ -138,20 +135,23 @@ func TestDryRun(t *testing.T) { Dev: false, LocalStorageFQDN: regCfg.HTTP.Addr, } + cfg := v2alpha1.ImageSetConfiguration{} // read the ImageSetConfiguration res, err := config.ReadConfig(opts.Global.ConfigPath, v2alpha1.ImageSetConfigurationKind) if err != nil { log.Error("imagesetconfig %v ", err) - } - var cfg v2alpha1.ImageSetConfiguration - if res == nil { - cfg = v2alpha1.ImageSetConfiguration{} } else { cfg = res.(v2alpha1.ImageSetConfiguration) - log.Debug("imagesetconfig : %v", cfg) } + log.Debug("imagesetconfig : %v", cfg) collector := &Collector{Log: log, Config: cfg, Opts: *opts, Fail: false} - mockMirror := Mirror{Fail: true} + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Check(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(false, errors.New("force fail check")). + AnyTimes() ex := &ExecutorSchema{ Log: log, @@ -159,23 +159,19 @@ func TestDryRun(t *testing.T) { Operator: collector, Release: collector, AdditionalImages: collector, - Mirror: mockMirror, + Mirror: mirrorMock, LocalStorageService: *reg, LogsDir: "/tmp/", MakeDir: MakeDir{}, } err = ex.DryRun(context.TODO(), imgs) - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) mappingPath := filepath.Join(testFolder, dryRunOutDir, mappingFile) assert.FileExists(t, mappingPath) mappingBytes, err := os.ReadFile(mappingPath) - if err != nil { - t.Fatalf("failed to read mapping file: %v", err) - } + assert.NoError(t, err) mapping := string(mappingBytes) for _, img := range imgs { @@ -186,9 +182,7 @@ func TestDryRun(t *testing.T) { assert.FileExists(t, mappingPath) missingBytes, err := os.ReadFile(missingImgsPath) - if err != nil { - t.Fatalf("failed to read mapping file: %v", err) - } + assert.NoError(t, err) missing := string(missingBytes) for _, img := range imgs { diff --git a/internal/pkg/cli/executor_test.go b/internal/pkg/cli/executor_test.go index 395024db3..004988989 100644 --- a/internal/pkg/cli/executor_test.go +++ b/internal/pkg/cli/executor_test.go @@ -11,33 +11,32 @@ import ( "testing" "time" - "github.com/otiai10/copy" - "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/registry" + "github.com/otiai10/copy" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "go.uber.org/mock/gomock" + "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" "github.com/openshift/oc-mirror/v2/internal/pkg/config" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" ) // TestExecutorMirroring - test both mirrorToDisk // and diskToMirror, using mocks func TestExecutorMirroring(t *testing.T) { testFolder := t.TempDir() - defer os.RemoveAll(testFolder) defer os.Remove("../../pkg/cli/registry.log") workDir := filepath.Join(testFolder, "tests") // copy tests/hold-test-fake to working-dir err := copy.Copy(common.TestFolder+"working-dir-fake", workDir) - if err != nil { - t.Fatalf("should not fail to copy: %v", err) - } + assert.NoError(t, err) log := clog.New("trace") global := &mirror.GlobalOptions{ @@ -53,13 +52,9 @@ func TestExecutorMirroring(t *testing.T) { // storage cache for test regCfg, err := setupRegForTest(testFolder) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) reg, err := registry.NewRegistry(context.Background(), regCfg) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) cr := MockClusterResources{} opts := &mirror.CopyOptions{ @@ -73,22 +68,30 @@ func TestExecutorMirroring(t *testing.T) { Destination: workDir, LocalStorageFQDN: regCfg.HTTP.Addr, } + cfg := v2alpha1.ImageSetConfiguration{} // read the ImageSetConfiguration res, err := config.ReadConfig(opts.Global.ConfigPath, v2alpha1.ImageSetConfigurationKind) if err != nil { - log.Error("imagesetconfig %v ", err) - } - var cfg v2alpha1.ImageSetConfiguration - if res == nil { - cfg = v2alpha1.ImageSetConfiguration{} + log.Error("imagesetconfig %v", err) } else { cfg = res.(v2alpha1.ImageSetConfiguration) - log.Debug("imagesetconfig : %v", cfg) } + log.Debug("imagesetconfig : %v", cfg) nie := NormalStorageInterruptError{} nie.Is(fmt.Errorf("interrupt error")) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Check(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(true, nil). + AnyTimes() + t.Run("Testing Executor : mirrorToDisk should pass", func(t *testing.T) { collector := &Collector{Log: log, Config: cfg, Opts: *opts, Fail: false} batch := &Batch{Log: log, Config: cfg, Opts: *opts} @@ -115,10 +118,7 @@ func TestExecutorMirroring(t *testing.T) { res.SilenceUsage = true ex.Opts.Mode = mirror.MirrorToDisk err := ex.Run(res, []string{"file://" + testFolder}) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) }) t.Run("Testing Executor : mirrorToDisk --dry-run should pass", func(t *testing.T) { @@ -138,7 +138,7 @@ func TestExecutorMirroring(t *testing.T) { HelmCollector: collector, Batch: batch, MirrorArchiver: archiver, - Mirror: Mirror{}, + Mirror: mirrorMock, LocalStorageService: *reg, MakeDir: MakeDir{}, LogsDir: "/tmp/", @@ -150,10 +150,7 @@ func TestExecutorMirroring(t *testing.T) { res.SilenceUsage = true ex.Opts.Mode = mirror.MirrorToDisk err := ex.Run(res, []string{"file://" + testFolder}) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) opts.IsDryRun = false }) @@ -174,7 +171,7 @@ func TestExecutorMirroring(t *testing.T) { AdditionalImages: collector, HelmCollector: collector, Batch: batch, - Mirror: Mirror{}, + Mirror: mirrorMock, MirrorUnArchiver: archiver, LocalStorageService: *reg, ClusterResources: cr, @@ -187,10 +184,7 @@ func TestExecutorMirroring(t *testing.T) { res.SilenceUsage = true ex.Opts.Mode = mirror.DiskToMirror err := ex.Run(res, []string{"docker://test/test"}) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) opts.IsDryRun = false }) @@ -210,7 +204,7 @@ func TestExecutorMirroring(t *testing.T) { AdditionalImages: collector, HelmCollector: collector, Batch: batch, - Mirror: Mirror{}, + Mirror: mirrorMock, MirrorUnArchiver: archiver, LocalStorageService: *reg, ClusterResources: cr, @@ -223,10 +217,7 @@ func TestExecutorMirroring(t *testing.T) { res.SilenceUsage = true ex.Opts.Mode = mirror.DiskToMirror err := ex.Run(res, []string{"docker://test/test"}) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) }) t.Run("Testing Executor : diskToMirror should fail", func(t *testing.T) { @@ -245,7 +236,7 @@ func TestExecutorMirroring(t *testing.T) { AdditionalImages: collector, HelmCollector: collector, Batch: batch, - Mirror: Mirror{}, + Mirror: mirrorMock, MirrorUnArchiver: archiver, LocalStorageService: *reg, ClusterResources: cr, @@ -258,23 +249,18 @@ func TestExecutorMirroring(t *testing.T) { res.SilenceUsage = true ex.Opts.Mode = mirror.DiskToMirror err := ex.Run(res, []string{"docker://test/test"}) - if err == nil { - t.Fatalf("should fail") - } + assert.Error(t, err) }) } func TestRunMirrorToMirror(t *testing.T) { testFolder := t.TempDir() - defer os.RemoveAll(testFolder) defer os.Remove("../../pkg/cli/registry.log") workDir := filepath.Join(testFolder, "tests") // copy tests/hold-test-fake to working-dir err := copy.Copy(common.TestFolder+"working-dir-fake", workDir) - if err != nil { - t.Fatalf("should not fail to copy: %v", err) - } + assert.NoError(t, err) log := clog.New("trace") global := &mirror.GlobalOptions{ @@ -290,13 +276,9 @@ func TestRunMirrorToMirror(t *testing.T) { // storage cache for test regCfg, err := setupRegForTest(testFolder) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) reg, err := registry.NewRegistry(context.Background(), regCfg) - if err != nil { - t.Errorf("storage cache error: %v ", err) - } + assert.NoError(t, err) opts := &mirror.CopyOptions{ Global: global, @@ -310,17 +292,13 @@ func TestRunMirrorToMirror(t *testing.T) { LocalStorageFQDN: regCfg.HTTP.Addr, } + cfg := v2alpha1.ImageSetConfiguration{} // read the ImageSetConfiguration res, err := config.ReadConfig(opts.Global.ConfigPath, v2alpha1.ImageSetConfigurationKind) if err != nil { - log.Error("imagesetconfig %v ", err) - } - var cfg v2alpha1.ImageSetConfiguration - if res == nil { - cfg = v2alpha1.ImageSetConfiguration{} + log.Error("imagesetconfig %v", err) } else { cfg = res.(v2alpha1.ImageSetConfiguration) - log.Debug("imagesetconfig : %v", cfg) } log.Debug("imagesetconfig : %v", cfg) @@ -328,6 +306,11 @@ func TestRunMirrorToMirror(t *testing.T) { nie := NormalStorageInterruptError{} nie.Is(fmt.Errorf("interrupt error")) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + t.Run("Testing Executor : mirrorToMirror should pass", func(t *testing.T) { collector := &Collector{Log: log, Config: cfg, Opts: *opts, Fail: false} batch := &Batch{Log: log, Config: cfg, Opts: *opts} @@ -341,7 +324,7 @@ func TestRunMirrorToMirror(t *testing.T) { Release: collector, AdditionalImages: collector, HelmCollector: collector, - Mirror: Mirror{}, + Mirror: mirrorMock, Batch: batch, MakeDir: MakeDir{}, LogsDir: "/tmp/", @@ -353,10 +336,7 @@ func TestRunMirrorToMirror(t *testing.T) { res.SetContext(context.Background()) res.SilenceUsage = true err := ex.Run(res, []string{"docker://test"}) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) }) t.Run("Testing Executor : mirrorToMirror --dry-run should pass", func(t *testing.T) { @@ -384,10 +364,7 @@ func TestRunMirrorToMirror(t *testing.T) { res.SetContext(context.Background()) res.SilenceUsage = true err := ex.Run(res, []string{"docker://test"}) - if err != nil { - log.Error(" %v ", err) - t.Fatalf("should not fail") - } + assert.NoError(t, err) opts.IsDryRun = false }) @@ -481,7 +458,6 @@ func TestExecutorValidate(t *testing.T) { opts.Global.From = "" // reset opts.Global.WorkingDir = "file://test" assert.NoError(t, ex.Validate([]string{"docker://test"})) - }) t.Run("Testing Executor : validate should fail", func(t *testing.T) { @@ -587,7 +563,6 @@ func TestExecutorValidate(t *testing.T) { opts.Global.WorkingDir = "" // reset err = ex.Validate([]string{"docker://test"}) assert.EqualError(t, err, "when destination is docker://, either --from (assumes disk to mirror workflow) or --workspace (assumes mirror to mirror workflow) need to be provided") - }) } @@ -699,16 +674,13 @@ func TestExecutorSetupLocalStorage(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() err := ex.setupLocalStorage(ctx) - if err != nil { - t.Fatalf("should not fail %v", err) - } + assert.NoError(t, err) }) } // TestExecutorSetupWorkingDir func TestExecutorSetupWorkingDir(t *testing.T) { workingDir := t.TempDir() - defer os.RemoveAll(workingDir) t.Run("Testing Executor : setup working dir should pass", func(t *testing.T) { log := clog.New("trace") @@ -730,9 +702,7 @@ func TestExecutorSetupWorkingDir(t *testing.T) { } err := ex.setupWorkingDir() - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) ex.MakeDir = MockMakeDir{Fail: true, Dir: ""} err = ex.setupWorkingDir() @@ -776,9 +746,7 @@ func TestExecutorSetupLogsLevelAndDir(t *testing.T) { } err := ex.setupLogsLevelAndDir() - if err != nil { - t.Fatalf("should not fail") - } + assert.NoError(t, err) ex.MakeDir = MockMakeDir{Fail: true, Dir: "logs"} err = ex.setupLogsLevelAndDir() @@ -830,7 +798,7 @@ func TestExecutorCollectAll(t *testing.T) { } // force release error _, err := ex.CollectAll(context.Background()) - assert.Equal(t, "collection error: forced error release collector", err.Error()) + assert.EqualError(t, err, "collection error: forced error release collector") }) t.Run("should fail if operator collection fails", func(t *testing.T) { ex := &ExecutorSchema{ @@ -846,7 +814,7 @@ func TestExecutorCollectAll(t *testing.T) { // force operator error _, err := ex.CollectAll(context.Background()) - assert.Equal(t, "collection error: forced error operator collector", err.Error()) + assert.EqualError(t, err, "collection error: forced error operator collector") }) t.Run("should fail if additional images collection fails", func(t *testing.T) { ex := &ExecutorSchema{ @@ -862,7 +830,7 @@ func TestExecutorCollectAll(t *testing.T) { // force additionalImages error _, err := ex.CollectAll(context.Background()) - assert.Equal(t, "collection error: forced error additionalImages collector", err.Error()) + assert.EqualError(t, err, "collection error: forced error additionalImages collector") }) t.Run("should fail if helm collection fails", func(t *testing.T) { ex := &ExecutorSchema{ @@ -878,7 +846,7 @@ func TestExecutorCollectAll(t *testing.T) { // force additionalImages error _, err := ex.CollectAll(context.Background()) - assert.Equal(t, "collection error: forced error helm collector", err.Error()) + assert.EqualError(t, err, "collection error: forced error helm collector") }) }) } @@ -1071,18 +1039,6 @@ func (o MockMakeDir) makeDirAll(dir string, mode os.FileMode) error { return nil } -func (o Mirror) Check(ctx context.Context, dest string, opts *mirror.CopyOptions, asCopySrc bool) (bool, error) { - if !o.Fail { - return true, nil - } else { - return false, fmt.Errorf("fake error from check") - } -} - -func (o Mirror) Run(context.Context, string, string, mirror.Mode, *mirror.CopyOptions) error { - return nil -} - func (o MockMirrorUnArchiver) Unarchive() error { if o.Fail { return fmt.Errorf("forced unarchive error") diff --git a/internal/pkg/mirror/mirror.go b/internal/pkg/mirror/mirror.go index f53712019..b803ae6e9 100644 --- a/internal/pkg/mirror/mirror.go +++ b/internal/pkg/mirror/mirror.go @@ -24,6 +24,8 @@ import ( type Mode string +//go:generate go tool mockgen -source=./mirror.go -destination=./mock/mirror_generated.go -package=mock + // MirrorInterface used to mirror images with container/images (skopeo) type MirrorInterface interface { Run(ctx context.Context, src, dest string, mode Mode, opts *CopyOptions) (retErr error) @@ -45,8 +47,10 @@ type Mirror struct { Mode string } -type MirrorCopy struct{} -type MirrorDelete struct{} +type ( + MirrorCopy struct{} + MirrorDelete struct{} +) // New returns new Mirror instance func New(mc MirrorCopyInterface, md MirrorDeleteInterface) MirrorInterface { @@ -218,7 +222,7 @@ func (o *Mirror) copy(ctx context.Context, src, dest string, opts *CopyOptions) if err != nil { return err } - if err = os.WriteFile(opts.DigestFile, []byte(manifestDigest.String()), 0644); err != nil { + if err = os.WriteFile(opts.DigestFile, []byte(manifestDigest.String()), 0o644); err != nil { return fmt.Errorf("failed to write digest to file %q: %w", opts.DigestFile, err) } } @@ -251,7 +255,6 @@ func isErrorRetryable(err error) bool { // check exists - checks if image exists func (o *Mirror) Check(ctx context.Context, image string, opts *CopyOptions, asCopySrc bool) (bool, error) { - if err := ReexecIfNecessaryForImages([]string{image}...); err != nil { return false, err } @@ -295,7 +298,6 @@ func (o *Mirror) Check(ctx context.Context, image string, opts *CopyOptions, asC // delete - delete images func (o *Mirror) delete(ctx context.Context, image string, opts *CopyOptions) error { - if err := ReexecIfNecessaryForImages([]string{image}...); err != nil { return err } diff --git a/internal/pkg/mirror/mock/mirror_generated.go b/internal/pkg/mirror/mock/mirror_generated.go new file mode 100644 index 000000000..ae93581df --- /dev/null +++ b/internal/pkg/mirror/mock/mirror_generated.go @@ -0,0 +1,151 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./mirror.go +// +// Generated by this command: +// +// mockgen -source=./mirror.go -destination=./mock/mirror_generated.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + mirror "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" + copy "go.podman.io/image/v5/copy" + signature "go.podman.io/image/v5/signature" + types "go.podman.io/image/v5/types" + gomock "go.uber.org/mock/gomock" +) + +// MockMirrorInterface is a mock of MirrorInterface interface. +type MockMirrorInterface struct { + ctrl *gomock.Controller + recorder *MockMirrorInterfaceMockRecorder + isgomock struct{} +} + +// MockMirrorInterfaceMockRecorder is the mock recorder for MockMirrorInterface. +type MockMirrorInterfaceMockRecorder struct { + mock *MockMirrorInterface +} + +// NewMockMirrorInterface creates a new mock instance. +func NewMockMirrorInterface(ctrl *gomock.Controller) *MockMirrorInterface { + mock := &MockMirrorInterface{ctrl: ctrl} + mock.recorder = &MockMirrorInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMirrorInterface) EXPECT() *MockMirrorInterfaceMockRecorder { + return m.recorder +} + +// Check mocks base method. +func (m *MockMirrorInterface) Check(ctx context.Context, image string, opts *mirror.CopyOptions, asCopySrc bool) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Check", ctx, image, opts, asCopySrc) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Check indicates an expected call of Check. +func (mr *MockMirrorInterfaceMockRecorder) Check(ctx, image, opts, asCopySrc any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Check", reflect.TypeOf((*MockMirrorInterface)(nil).Check), ctx, image, opts, asCopySrc) +} + +// Run mocks base method. +func (m *MockMirrorInterface) Run(ctx context.Context, src, dest string, mode mirror.Mode, opts *mirror.CopyOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Run", ctx, src, dest, mode, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// Run indicates an expected call of Run. +func (mr *MockMirrorInterfaceMockRecorder) Run(ctx, src, dest, mode, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockMirrorInterface)(nil).Run), ctx, src, dest, mode, opts) +} + +// MockMirrorCopyInterface is a mock of MirrorCopyInterface interface. +type MockMirrorCopyInterface struct { + ctrl *gomock.Controller + recorder *MockMirrorCopyInterfaceMockRecorder + isgomock struct{} +} + +// MockMirrorCopyInterfaceMockRecorder is the mock recorder for MockMirrorCopyInterface. +type MockMirrorCopyInterfaceMockRecorder struct { + mock *MockMirrorCopyInterface +} + +// NewMockMirrorCopyInterface creates a new mock instance. +func NewMockMirrorCopyInterface(ctrl *gomock.Controller) *MockMirrorCopyInterface { + mock := &MockMirrorCopyInterface{ctrl: ctrl} + mock.recorder = &MockMirrorCopyInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMirrorCopyInterface) EXPECT() *MockMirrorCopyInterfaceMockRecorder { + return m.recorder +} + +// CopyImage mocks base method. +func (m *MockMirrorCopyInterface) CopyImage(ctx context.Context, pc *signature.PolicyContext, destRef, srcRef types.ImageReference, opts *copy.Options) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CopyImage", ctx, pc, destRef, srcRef, opts) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CopyImage indicates an expected call of CopyImage. +func (mr *MockMirrorCopyInterfaceMockRecorder) CopyImage(ctx, pc, destRef, srcRef, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CopyImage", reflect.TypeOf((*MockMirrorCopyInterface)(nil).CopyImage), ctx, pc, destRef, srcRef, opts) +} + +// MockMirrorDeleteInterface is a mock of MirrorDeleteInterface interface. +type MockMirrorDeleteInterface struct { + ctrl *gomock.Controller + recorder *MockMirrorDeleteInterfaceMockRecorder + isgomock struct{} +} + +// MockMirrorDeleteInterfaceMockRecorder is the mock recorder for MockMirrorDeleteInterface. +type MockMirrorDeleteInterfaceMockRecorder struct { + mock *MockMirrorDeleteInterface +} + +// NewMockMirrorDeleteInterface creates a new mock instance. +func NewMockMirrorDeleteInterface(ctrl *gomock.Controller) *MockMirrorDeleteInterface { + mock := &MockMirrorDeleteInterface{ctrl: ctrl} + mock.recorder = &MockMirrorDeleteInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMirrorDeleteInterface) EXPECT() *MockMirrorDeleteInterfaceMockRecorder { + return m.recorder +} + +// DeleteImage mocks base method. +func (m *MockMirrorDeleteInterface) DeleteImage(ctx context.Context, image string, opts *mirror.CopyOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteImage", ctx, image, opts) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteImage indicates an expected call of DeleteImage. +func (mr *MockMirrorDeleteInterfaceMockRecorder) DeleteImage(ctx, image, opts any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteImage", reflect.TypeOf((*MockMirrorDeleteInterface)(nil).DeleteImage), ctx, image, opts) +} diff --git a/internal/pkg/operator/common_test.go b/internal/pkg/operator/common_test.go index c679be8dd..d21796b0b 100644 --- a/internal/pkg/operator/common_test.go +++ b/internal/pkg/operator/common_test.go @@ -14,6 +14,7 @@ import ( clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/parser" ) @@ -27,6 +28,8 @@ func TestPrepareDeleteForV1(t *testing.T) { manifestMock := setupManifestMock(mockCtrl) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + type testCase struct { caseName string relatedImages map[string][]v2alpha1.RelatedImage @@ -71,7 +74,7 @@ func TestPrepareDeleteForV1(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock) ex.Opts.Mode = mirror.MirrorToMirror ex.generateV1DestTags = true ex.Opts.Destination = "docker://localhost:5000/test" @@ -96,6 +99,8 @@ func TestPrepareM2MCopyBatch(t *testing.T) { manifestMock := setupManifestMock(mockCtrl) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + type testCase struct { caseName string relatedImages map[string][]v2alpha1.RelatedImage @@ -431,7 +436,7 @@ func TestPrepareM2MCopyBatch(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock) ex.Opts.Mode = mirror.MirrorToMirror ex.Opts.Destination = "docker://localhost:5000/test" res, err := ex.dispatchImagesForM2M(testCase.relatedImages) diff --git a/internal/pkg/operator/filtered_collector_test.go b/internal/pkg/operator/filtered_collector_test.go index 40817bd07..1c22b979f 100644 --- a/internal/pkg/operator/filtered_collector_test.go +++ b/internal/pkg/operator/filtered_collector_test.go @@ -2,7 +2,6 @@ package operator import ( "context" - "fmt" "os" "path/filepath" "testing" @@ -18,12 +17,9 @@ import ( clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/manifest" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" ) -type MockMirror struct { - Fail bool -} - type MockHandler struct { Log clog.PluggableLoggerInterface } @@ -300,6 +296,14 @@ func TestFilterCollectorM2D(t *testing.T) { manifestMock := setupManifestMock(mockCtrl) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + testDir, err := filepath.Abs(common.TestFolder) assert.NoError(t, err, "should get tests/ absolute path") @@ -461,7 +465,7 @@ func TestFilterCollectorM2D(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock) ex = ex.withConfig(testCase.config) res, err := ex.OperatorImageCollector(ctx) if testCase.expectedError { @@ -475,7 +479,7 @@ func TestFilterCollectorM2D(t *testing.T) { // this test should cover over 80% M2D t.Run("Testing OperatorImageCollector - Mirror to disk: should pass", func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock) // ensure coverage in new.go _ = NewWithFilter(log, "working-dir", ex.Config, ex.Opts, ex.Mirror, manifestMock) }) @@ -503,6 +507,14 @@ func TestFilterCollectorD2M(t *testing.T) { manifestMock := setupManifestMock(mockCtrl) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + os.RemoveAll(common.TestFolder + "hold-operator/") os.RemoveAll(common.TestFolder + "operator-images") os.RemoveAll(common.TestFolder + "tmp/") @@ -594,7 +606,7 @@ func TestFilterCollectorD2M(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_DiskToMirror(tempDir, log, manifestMock) + ex := setupFilterCollector_DiskToMirror(tempDir, log, manifestMock, mirrorMock) ex = ex.withConfig(testCase.config) res, err := ex.OperatorImageCollector(ctx) if testCase.expectedError { @@ -629,6 +641,14 @@ func TestFilterCollectorM2M(t *testing.T) { manifestMock := setupManifestMock(mockCtrl) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + os.RemoveAll(common.TestFolder + "hold-operator/") os.RemoveAll(common.TestFolder + "operator-images") os.RemoveAll(common.TestFolder + "tmp/") @@ -778,7 +798,7 @@ func TestFilterCollectorM2M(t *testing.T) { } for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { - ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupFilterCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock) ex.Opts.Mode = mirror.MirrorToMirror ex.Opts.Destination = "docker://localhost:5000/test" ex = ex.withConfig(testCase.config) @@ -793,7 +813,7 @@ func TestFilterCollectorM2M(t *testing.T) { } } -func setupFilterCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *FilterCollector { +func setupFilterCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, mirrorIface mirror.MirrorInterface) *FilterCollector { handler := &MockHandler{Log: log} globalD2M := &mirror.GlobalOptions{ SecurePolicy: false, @@ -822,7 +842,7 @@ func setupFilterCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerI ex := &FilterCollector{ OperatorCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: mirrorIface, Config: nominalConfigD2M, Manifest: manifest, Opts: d2mOpts, @@ -834,7 +854,7 @@ func setupFilterCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerI return ex } -func setupFilterCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *FilterCollector { +func setupFilterCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, mirrorIface mirror.MirrorInterface) *FilterCollector { handler := &MockHandler{Log: log} globalM2D := &mirror.GlobalOptions{ @@ -863,7 +883,7 @@ func setupFilterCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerI ex := &FilterCollector{ OperatorCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: mirrorIface, Config: nominalConfigM2D, Manifest: manifest, Opts: m2dOpts, @@ -879,17 +899,6 @@ func (ex *FilterCollector) withConfig(cfg v2alpha1.ImageSetConfiguration) *Filte return ex } -func (o MockMirror) Run(ctx context.Context, src, dest string, mode mirror.Mode, opts *mirror.CopyOptions) error { - if o.Fail { - return fmt.Errorf("forced mirror run fail") - } - return nil -} - -func (o MockMirror) Check(ctx context.Context, image string, opts *mirror.CopyOptions, asCopySrc bool) (bool, error) { - return true, nil -} - func (o MockHandler) getCatalog(filePath string) (OperatorCatalog, error) { return OperatorCatalog{}, nil } diff --git a/internal/pkg/release/graph_test.go b/internal/pkg/release/graph_test.go index feb2283b3..8be9dcb86 100644 --- a/internal/pkg/release/graph_test.go +++ b/internal/pkg/release/graph_test.go @@ -15,6 +15,7 @@ import ( clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" ) type mockImageBuilder struct { @@ -135,11 +136,13 @@ func TestCreateGraphImage(t *testing.T) { manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + // this test should cover over 80% M2D t.Run("Testing CreateGraphImage - Mirror to disk: should pass", func(t *testing.T) { ex := &LocalStorageCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: mirrorMock, Config: cfgm2d, Manifest: manifestMock, Opts: m2dOpts, @@ -149,7 +152,7 @@ func TestCreateGraphImage(t *testing.T) { } // just to ensure we cover new.go - _ = New(log, "nada", cfgm2d, m2dOpts, &MockMirror{}, manifestMock, cincinnati, &mockImageBuilder{}) + _ = New(log, "nada", cfgm2d, m2dOpts, mirrorMock, manifestMock, cincinnati, &mockImageBuilder{}) _, err := ex.CreateGraphImage(ctx, graphURL) assert.NoError(t, err) @@ -158,7 +161,7 @@ func TestCreateGraphImage(t *testing.T) { t.Run("Testing CreateGraphImage - Mirror to disk: should fail", func(t *testing.T) { ex := &LocalStorageCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: mirrorMock, Config: cfgm2d, Manifest: manifestMock, Opts: m2dOpts, @@ -174,7 +177,7 @@ func TestCreateGraphImage(t *testing.T) { t.Run("Testing CreateGraphImage - Mirror to disk: should fail", func(t *testing.T) { ex := &LocalStorageCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: mirrorMock, Config: cfgm2d, Manifest: manifestMock, Opts: m2dOpts, diff --git a/internal/pkg/release/local_stored_collector_test.go b/internal/pkg/release/local_stored_collector_test.go index 15be70e23..d94b1dba6 100644 --- a/internal/pkg/release/local_stored_collector_test.go +++ b/internal/pkg/release/local_stored_collector_test.go @@ -20,12 +20,9 @@ import ( "github.com/openshift/oc-mirror/v2/internal/pkg/manifest" manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" + mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" ) -type MockMirror struct { - Fail bool -} - type MockCincinnati struct { Config v2alpha1.ImageSetConfiguration Opts mirror.CopyOptions @@ -106,7 +103,15 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(validReleaseSchema, nil). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) + mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + + mirrorMock. + EXPECT(). + Run(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock) err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.10-x86_64")) assert.NoError(t, err) @@ -293,7 +298,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil, errors.New("forced error image index")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) log.Debug("completed test related images %v ", res) @@ -314,7 +319,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil, errors.New("force fail error")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) @@ -342,7 +347,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(errors.New("forced extract oci fail")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) @@ -578,7 +583,7 @@ func TestHandleGraphImage(t *testing.T) { } ex := &LocalStorageCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: nil, Opts: copyOpts, Manifest: manifestMock, LocalStorageFQDN: "localhost:9999", @@ -650,7 +655,7 @@ func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterfa ex := &LocalStorageCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: nil, Config: cfgd2m, Manifest: manifest, Opts: d2mOpts, @@ -662,7 +667,7 @@ func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterfa return ex } -func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *LocalStorageCollector { +func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, mirrorIface mirror.MirrorInterface) *LocalStorageCollector { globalM2D := &mirror.GlobalOptions{ SecurePolicy: false, WorkingDir: tempDir, @@ -769,7 +774,7 @@ func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterfa cincinnati := &MockCincinnati{Config: cfgm2d, Opts: m2dOpts} ex := &LocalStorageCollector{ Log: log, - Mirror: &MockMirror{Fail: false}, + Mirror: mirrorIface, Config: cfgm2d, Manifest: manifest, Opts: m2dOpts, @@ -781,17 +786,6 @@ func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterfa return ex } -func (o MockMirror) Run(ctx context.Context, src, dest string, mode mirror.Mode, opts *mirror.CopyOptions) error { - if o.Fail { - return fmt.Errorf("forced mirror run fail") - } - return nil -} - -func (o MockMirror) Check(ctx context.Context, image string, opts *mirror.CopyOptions, asCopySrc bool) (bool, error) { - return true, nil -} - func (o MockCincinnati) GetReleaseReferenceImages(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) { var res []v2alpha1.CopyImageSchema res = append(res, v2alpha1.CopyImageSchema{Type: v2alpha1.TypeOCPRelease, Source: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64", Origin: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64"}) From e73d7037547ec4e15dc7ddd98ae9f7229e30ff3d Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Mon, 3 Nov 2025 18:22:52 +0100 Subject: [PATCH 5/9] v2: pkg/release: use generated mock --- internal/pkg/release/graph_test.go | 13 +- internal/pkg/release/interface.go | 2 + .../release/local_stored_collector_test.go | 89 ++++---- .../pkg/release/mock/interface_generated.go | 203 ++++++++++++++++++ 4 files changed, 258 insertions(+), 49 deletions(-) create mode 100644 internal/pkg/release/mock/interface_generated.go diff --git a/internal/pkg/release/graph_test.go b/internal/pkg/release/graph_test.go index 8be9dcb86..48d9fda9e 100644 --- a/internal/pkg/release/graph_test.go +++ b/internal/pkg/release/graph_test.go @@ -16,6 +16,7 @@ import ( manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" + releasemock "github.com/openshift/oc-mirror/v2/internal/pkg/release/mock" ) type mockImageBuilder struct { @@ -127,8 +128,6 @@ func TestCreateGraphImage(t *testing.T) { }, } - cincinnati := &MockCincinnati{Config: cfgm2d, Opts: m2dOpts} - ctx := context.Background() mockCtrl := gomock.NewController(t) @@ -138,6 +137,8 @@ func TestCreateGraphImage(t *testing.T) { mirrorMock := mirrormock.NewMockMirrorInterface(mockCtrl) + cincinnatiMock := releasemock.NewMockCincinnatiInterface(mockCtrl) + // this test should cover over 80% M2D t.Run("Testing CreateGraphImage - Mirror to disk: should pass", func(t *testing.T) { ex := &LocalStorageCollector{ @@ -146,13 +147,13 @@ func TestCreateGraphImage(t *testing.T) { Config: cfgm2d, Manifest: manifestMock, Opts: m2dOpts, - Cincinnati: cincinnati, + Cincinnati: cincinnatiMock, LocalStorageFQDN: "localhost:9999", ImageBuilder: &mockImageBuilder{}, } // just to ensure we cover new.go - _ = New(log, "nada", cfgm2d, m2dOpts, mirrorMock, manifestMock, cincinnati, &mockImageBuilder{}) + _ = New(log, "nada", cfgm2d, m2dOpts, mirrorMock, manifestMock, cincinnatiMock, &mockImageBuilder{}) _, err := ex.CreateGraphImage(ctx, graphURL) assert.NoError(t, err) @@ -165,7 +166,7 @@ func TestCreateGraphImage(t *testing.T) { Config: cfgm2d, Manifest: manifestMock, Opts: m2dOpts, - Cincinnati: cincinnati, + Cincinnati: cincinnatiMock, LocalStorageFQDN: "localhost:9999", ImageBuilder: &mockImageBuilder{}, } @@ -181,7 +182,7 @@ func TestCreateGraphImage(t *testing.T) { Config: cfgm2d, Manifest: manifestMock, Opts: m2dOpts, - Cincinnati: cincinnati, + Cincinnati: cincinnatiMock, LocalStorageFQDN: "localhost:9999", ImageBuilder: &mockImageBuilder{Fail: true}, } diff --git a/internal/pkg/release/interface.go b/internal/pkg/release/interface.go index 5fe7b17dd..ba4c96d7a 100644 --- a/internal/pkg/release/interface.go +++ b/internal/pkg/release/interface.go @@ -6,6 +6,8 @@ import ( "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" ) +//go:generate go tool mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock + type CollectorInterface interface { ReleaseImageCollector(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) // Returns the graphImage if generated, especially with the reference to the image diff --git a/internal/pkg/release/local_stored_collector_test.go b/internal/pkg/release/local_stored_collector_test.go index d94b1dba6..9035b7dc7 100644 --- a/internal/pkg/release/local_stored_collector_test.go +++ b/internal/pkg/release/local_stored_collector_test.go @@ -9,7 +9,6 @@ import ( "strings" "testing" - "github.com/google/uuid" "github.com/otiai10/copy" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -21,15 +20,9 @@ import ( manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" mirrormock "github.com/openshift/oc-mirror/v2/internal/pkg/mirror/mock" + releasemock "github.com/openshift/oc-mirror/v2/internal/pkg/release/mock" ) -type MockCincinnati struct { - Config v2alpha1.ImageSetConfiguration - Opts mirror.CopyOptions - Client Client - Fail bool -} - func TestReleaseLocalStoredCollector(t *testing.T) { log := clog.New("trace") @@ -72,8 +65,19 @@ func TestReleaseLocalStoredCollector(t *testing.T) { {Name: "agent-installer-orchestrator", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:4949b93b3fd0f6b22197402ba22c2775eba408b53d30ac2e3ab2dda409314f5e"}, {Name: "apiserver-network-proxy", Type: v2alpha1.TypeOCPReleaseContent, Image: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:2a0dd75b1b327a0c5b17145fc71beb2bf805e6cc3b8fc3f672ce06772caddf21"}, } + validReleaseReferenceImages = []v2alpha1.CopyImageSchema{ + {Type: v2alpha1.TypeOCPRelease, Source: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64", Origin: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64"}, + } ) + cincinnatiMock := releasemock.NewMockCincinnatiInterface(mockCtrl) + + cincinnatiMock. + EXPECT(). + GetReleaseReferenceImages(gomock.Any()). + Return(validReleaseReferenceImages, nil). + AnyTimes() + // this test should cover over 80% M2D t.Run("Testing ReleaseImageCollector - Mirror to disk: should pass", func(t *testing.T) { ctx := context.Background() @@ -111,7 +115,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock, cincinnatiMock) err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.10-x86_64")) assert.NoError(t, err) @@ -190,7 +194,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(validImageDigest, nil). AnyTimes() - ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock, cincinnatiMock) // copy tests/hold-test-fake to working-dir err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.10-x86_64")) assert.NoError(t, err) @@ -271,12 +275,19 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(validImageDigest, nil). AnyTimes() - ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) + sigMock := releasemock.NewMockSignatureInterface(mockCtrl) + + sigMock. + EXPECT(). + GenerateReleaseSignatures(gomock.Any(), gomock.Any()). + DoAndReturn(generateReleaseSignatures). + AnyTimes() + + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock, nil) client := &ocpClient{} client.SetQueryParams(ex.Config.Mirror.Platform.Architectures[0], ex.Config.Mirror.Platform.Channels[0].Name, "") - sig := MockCincinnati{} - cn := NewCincinnati(ex.Log, nil, &ex.Config, ex.Opts, client, false, sig) + cn := NewCincinnati(ex.Log, nil, &ex.Config, ex.Opts, client, false, sigMock) ex.Cincinnati = cn @@ -298,7 +309,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil, errors.New("forced error image index")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) log.Debug("completed test related images %v ", res) @@ -319,7 +330,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil, errors.New("force fail error")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) @@ -347,7 +358,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(errors.New("forced extract oci fail")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) @@ -365,7 +376,8 @@ func TestGraphImage(t *testing.T) { t.Run("Testing GraphImage : should fail", func(t *testing.T) { manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) - ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) + cincinnatiMock := releasemock.NewMockCincinnatiInterface(mockCtrl) + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock, cincinnatiMock) res, err := ex.GraphImage() assert.NoError(t, err) @@ -381,14 +393,25 @@ func TestReleaseImage(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() + validReleaseReferenceImages := []v2alpha1.CopyImageSchema{ + {Type: v2alpha1.TypeOCPRelease, Source: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64", Origin: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64"}, + } + t.Run("Testing ReleaseImage : should pass", func(t *testing.T) { os.RemoveAll(common.TestFolder + "hold-release/") os.RemoveAll(common.TestFolder + "release-images") os.RemoveAll(common.TestFolder + "tmp/") manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) + cincinnatiMock := releasemock.NewMockCincinnatiInterface(mockCtrl) - ex := setupCollector_DiskToMirror(tempDir, log, manifestMock) + cincinnatiMock. + EXPECT(). + GetReleaseReferenceImages(gomock.Any()). + Return(validReleaseReferenceImages, nil). + AnyTimes() + + ex := setupCollector_DiskToMirror(tempDir, log, manifestMock, cincinnatiMock) // copy tests/hold-test-fake to working-dir err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.9-x86_64")) assert.NoError(t, err) @@ -605,7 +628,7 @@ func TestHandleGraphImage(t *testing.T) { } } -func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface) *LocalStorageCollector { +func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, cincinnatiIface CincinnatiInterface) *LocalStorageCollector { globalD2M := &mirror.GlobalOptions{ SecurePolicy: false, WorkingDir: tempDir + "/working-dir", @@ -648,10 +671,8 @@ func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterfa }, } - cincinnati := &MockCincinnati{} client := &ocpClient{} client.SetQueryParams(cfgd2m.Mirror.Platform.Architectures[0], cfgd2m.Mirror.Platform.Channels[0].Name, "") - cincinnati.Client = client ex := &LocalStorageCollector{ Log: log, @@ -659,7 +680,7 @@ func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterfa Config: cfgd2m, Manifest: manifest, Opts: d2mOpts, - Cincinnati: cincinnati, + Cincinnati: cincinnatiIface, LocalStorageFQDN: "localhost:9999", LogsDir: "/tmp/", } @@ -667,7 +688,7 @@ func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterfa return ex } -func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, mirrorIface mirror.MirrorInterface) *LocalStorageCollector { +func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, mirrorIface mirror.MirrorInterface, cincinnatiIface CincinnatiInterface) *LocalStorageCollector { globalM2D := &mirror.GlobalOptions{ SecurePolicy: false, WorkingDir: tempDir, @@ -771,14 +792,13 @@ func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterfa }, } - cincinnati := &MockCincinnati{Config: cfgm2d, Opts: m2dOpts} ex := &LocalStorageCollector{ Log: log, Mirror: mirrorIface, Config: cfgm2d, Manifest: manifest, Opts: m2dOpts, - Cincinnati: cincinnati, + Cincinnati: cincinnatiIface, LocalStorageFQDN: "localhost:9999", ImageBuilder: &mockImageBuilder{}, LogsDir: "/tmp/", @@ -786,24 +806,7 @@ func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterfa return ex } -func (o MockCincinnati) GetReleaseReferenceImages(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) { - var res []v2alpha1.CopyImageSchema - res = append(res, v2alpha1.CopyImageSchema{Type: v2alpha1.TypeOCPRelease, Source: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64", Origin: "quay.io/openshift-release-dev/ocp-release:4.13.10-x86_64"}) - return res, nil -} - -func (o MockCincinnati) NewOCPClient(uuid uuid.UUID) (Client, error) { - if o.Fail { - return o.Client, fmt.Errorf("forced cincinnati client error") - } - return o.Client, nil -} - -func (o MockCincinnati) NewOKDClient(uuid uuid.UUID) (Client, error) { - return o.Client, nil -} - -func (o MockCincinnati) GenerateReleaseSignatures(ctx context.Context, images []v2alpha1.CopyImageSchema) ([]v2alpha1.CopyImageSchema, error) { +func generateReleaseSignatures(ctx context.Context, images []v2alpha1.CopyImageSchema) ([]v2alpha1.CopyImageSchema, error) { imagesByTag := make([]v2alpha1.CopyImageSchema, len(images)) minor := 9 for i, img := range images { diff --git a/internal/pkg/release/mock/interface_generated.go b/internal/pkg/release/mock/interface_generated.go new file mode 100644 index 000000000..4fcbcd5d5 --- /dev/null +++ b/internal/pkg/release/mock/interface_generated.go @@ -0,0 +1,203 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go +// +// Generated by this command: +// +// mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + v2alpha1 "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" + gomock "go.uber.org/mock/gomock" +) + +// MockCollectorInterface is a mock of CollectorInterface interface. +type MockCollectorInterface struct { + ctrl *gomock.Controller + recorder *MockCollectorInterfaceMockRecorder + isgomock struct{} +} + +// MockCollectorInterfaceMockRecorder is the mock recorder for MockCollectorInterface. +type MockCollectorInterfaceMockRecorder struct { + mock *MockCollectorInterface +} + +// NewMockCollectorInterface creates a new mock instance. +func NewMockCollectorInterface(ctrl *gomock.Controller) *MockCollectorInterface { + mock := &MockCollectorInterface{ctrl: ctrl} + mock.recorder = &MockCollectorInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCollectorInterface) EXPECT() *MockCollectorInterfaceMockRecorder { + return m.recorder +} + +// GraphImage mocks base method. +func (m *MockCollectorInterface) GraphImage() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GraphImage") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GraphImage indicates an expected call of GraphImage. +func (mr *MockCollectorInterfaceMockRecorder) GraphImage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GraphImage", reflect.TypeOf((*MockCollectorInterface)(nil).GraphImage)) +} + +// ReleaseImage mocks base method. +func (m *MockCollectorInterface) ReleaseImage(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReleaseImage", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReleaseImage indicates an expected call of ReleaseImage. +func (mr *MockCollectorInterfaceMockRecorder) ReleaseImage(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseImage", reflect.TypeOf((*MockCollectorInterface)(nil).ReleaseImage), arg0) +} + +// ReleaseImageCollector mocks base method. +func (m *MockCollectorInterface) ReleaseImageCollector(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReleaseImageCollector", ctx) + ret0, _ := ret[0].([]v2alpha1.CopyImageSchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReleaseImageCollector indicates an expected call of ReleaseImageCollector. +func (mr *MockCollectorInterfaceMockRecorder) ReleaseImageCollector(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseImageCollector", reflect.TypeOf((*MockCollectorInterface)(nil).ReleaseImageCollector), ctx) +} + +// MockGraphBuilderInterface is a mock of GraphBuilderInterface interface. +type MockGraphBuilderInterface struct { + ctrl *gomock.Controller + recorder *MockGraphBuilderInterfaceMockRecorder + isgomock struct{} +} + +// MockGraphBuilderInterfaceMockRecorder is the mock recorder for MockGraphBuilderInterface. +type MockGraphBuilderInterfaceMockRecorder struct { + mock *MockGraphBuilderInterface +} + +// NewMockGraphBuilderInterface creates a new mock instance. +func NewMockGraphBuilderInterface(ctrl *gomock.Controller) *MockGraphBuilderInterface { + mock := &MockGraphBuilderInterface{ctrl: ctrl} + mock.recorder = &MockGraphBuilderInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGraphBuilderInterface) EXPECT() *MockGraphBuilderInterfaceMockRecorder { + return m.recorder +} + +// CreateGraphImage mocks base method. +func (m *MockGraphBuilderInterface) CreateGraphImage(ctx context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateGraphImage", ctx) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateGraphImage indicates an expected call of CreateGraphImage. +func (mr *MockGraphBuilderInterfaceMockRecorder) CreateGraphImage(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGraphImage", reflect.TypeOf((*MockGraphBuilderInterface)(nil).CreateGraphImage), ctx) +} + +// MockCincinnatiInterface is a mock of CincinnatiInterface interface. +type MockCincinnatiInterface struct { + ctrl *gomock.Controller + recorder *MockCincinnatiInterfaceMockRecorder + isgomock struct{} +} + +// MockCincinnatiInterfaceMockRecorder is the mock recorder for MockCincinnatiInterface. +type MockCincinnatiInterfaceMockRecorder struct { + mock *MockCincinnatiInterface +} + +// NewMockCincinnatiInterface creates a new mock instance. +func NewMockCincinnatiInterface(ctrl *gomock.Controller) *MockCincinnatiInterface { + mock := &MockCincinnatiInterface{ctrl: ctrl} + mock.recorder = &MockCincinnatiInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCincinnatiInterface) EXPECT() *MockCincinnatiInterfaceMockRecorder { + return m.recorder +} + +// GetReleaseReferenceImages mocks base method. +func (m *MockCincinnatiInterface) GetReleaseReferenceImages(arg0 context.Context) ([]v2alpha1.CopyImageSchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetReleaseReferenceImages", arg0) + ret0, _ := ret[0].([]v2alpha1.CopyImageSchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetReleaseReferenceImages indicates an expected call of GetReleaseReferenceImages. +func (mr *MockCincinnatiInterfaceMockRecorder) GetReleaseReferenceImages(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReleaseReferenceImages", reflect.TypeOf((*MockCincinnatiInterface)(nil).GetReleaseReferenceImages), arg0) +} + +// MockSignatureInterface is a mock of SignatureInterface interface. +type MockSignatureInterface struct { + ctrl *gomock.Controller + recorder *MockSignatureInterfaceMockRecorder + isgomock struct{} +} + +// MockSignatureInterfaceMockRecorder is the mock recorder for MockSignatureInterface. +type MockSignatureInterfaceMockRecorder struct { + mock *MockSignatureInterface +} + +// NewMockSignatureInterface creates a new mock instance. +func NewMockSignatureInterface(ctrl *gomock.Controller) *MockSignatureInterface { + mock := &MockSignatureInterface{ctrl: ctrl} + mock.recorder = &MockSignatureInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSignatureInterface) EXPECT() *MockSignatureInterfaceMockRecorder { + return m.recorder +} + +// GenerateReleaseSignatures mocks base method. +func (m *MockSignatureInterface) GenerateReleaseSignatures(arg0 context.Context, arg1 []v2alpha1.CopyImageSchema) ([]v2alpha1.CopyImageSchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GenerateReleaseSignatures", arg0, arg1) + ret0, _ := ret[0].([]v2alpha1.CopyImageSchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GenerateReleaseSignatures indicates an expected call of GenerateReleaseSignatures. +func (mr *MockSignatureInterfaceMockRecorder) GenerateReleaseSignatures(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenerateReleaseSignatures", reflect.TypeOf((*MockSignatureInterface)(nil).GenerateReleaseSignatures), arg0, arg1) +} From 9b36389b50a2124f78b5c6fe3e07f1be712e3c4c Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Mon, 3 Nov 2025 18:53:36 +0100 Subject: [PATCH 6/9] v2: pkg/imagebuilder: use generated mock --- internal/pkg/imagebuilder/interface.go | 3 + .../imagebuilder/mock/interface_generated.go | 137 ++++++++++++++++++ internal/pkg/release/graph_test.go | 62 ++++---- .../release/local_stored_collector_test.go | 49 ++++++- 4 files changed, 212 insertions(+), 39 deletions(-) create mode 100644 internal/pkg/imagebuilder/mock/interface_generated.go diff --git a/internal/pkg/imagebuilder/interface.go b/internal/pkg/imagebuilder/interface.go index 7a4c3dea8..d37892ed7 100644 --- a/internal/pkg/imagebuilder/interface.go +++ b/internal/pkg/imagebuilder/interface.go @@ -5,9 +5,12 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" ) +//go:generate go tool mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock + type ImageBuilderInterface interface { BuildAndPush(ctx context.Context, targetRef string, layoutPath layout.Path, cmd []string, layers ...v1.Layer) (string, error) SaveImageLayoutToDir(ctx context.Context, imgRef string, layoutDir string) (layout.Path, error) diff --git a/internal/pkg/imagebuilder/mock/interface_generated.go b/internal/pkg/imagebuilder/mock/interface_generated.go new file mode 100644 index 000000000..058c46b25 --- /dev/null +++ b/internal/pkg/imagebuilder/mock/interface_generated.go @@ -0,0 +1,137 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go +// +// Generated by this command: +// +// mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + v1 "github.com/google/go-containerregistry/pkg/v1" + layout "github.com/google/go-containerregistry/pkg/v1/layout" + v2alpha1 "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" + gomock "go.uber.org/mock/gomock" +) + +// MockImageBuilderInterface is a mock of ImageBuilderInterface interface. +type MockImageBuilderInterface struct { + ctrl *gomock.Controller + recorder *MockImageBuilderInterfaceMockRecorder + isgomock struct{} +} + +// MockImageBuilderInterfaceMockRecorder is the mock recorder for MockImageBuilderInterface. +type MockImageBuilderInterfaceMockRecorder struct { + mock *MockImageBuilderInterface +} + +// NewMockImageBuilderInterface creates a new mock instance. +func NewMockImageBuilderInterface(ctrl *gomock.Controller) *MockImageBuilderInterface { + mock := &MockImageBuilderInterface{ctrl: ctrl} + mock.recorder = &MockImageBuilderInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockImageBuilderInterface) EXPECT() *MockImageBuilderInterfaceMockRecorder { + return m.recorder +} + +// BuildAndPush mocks base method. +func (m *MockImageBuilderInterface) BuildAndPush(ctx context.Context, targetRef string, layoutPath layout.Path, cmd []string, layers ...v1.Layer) (string, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, targetRef, layoutPath, cmd} + for _, a := range layers { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "BuildAndPush", varargs...) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildAndPush indicates an expected call of BuildAndPush. +func (mr *MockImageBuilderInterfaceMockRecorder) BuildAndPush(ctx, targetRef, layoutPath, cmd any, layers ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, targetRef, layoutPath, cmd}, layers...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildAndPush", reflect.TypeOf((*MockImageBuilderInterface)(nil).BuildAndPush), varargs...) +} + +// ProcessImageIndex mocks base method. +func (m *MockImageBuilderInterface) ProcessImageIndex(ctx context.Context, idx v1.ImageIndex, v2format *bool, cmd []string, targetRef string, layers ...v1.Layer) (v1.ImageIndex, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, idx, v2format, cmd, targetRef} + for _, a := range layers { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ProcessImageIndex", varargs...) + ret0, _ := ret[0].(v1.ImageIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ProcessImageIndex indicates an expected call of ProcessImageIndex. +func (mr *MockImageBuilderInterfaceMockRecorder) ProcessImageIndex(ctx, idx, v2format, cmd, targetRef any, layers ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, idx, v2format, cmd, targetRef}, layers...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProcessImageIndex", reflect.TypeOf((*MockImageBuilderInterface)(nil).ProcessImageIndex), varargs...) +} + +// SaveImageLayoutToDir mocks base method. +func (m *MockImageBuilderInterface) SaveImageLayoutToDir(ctx context.Context, imgRef, layoutDir string) (layout.Path, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SaveImageLayoutToDir", ctx, imgRef, layoutDir) + ret0, _ := ret[0].(layout.Path) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SaveImageLayoutToDir indicates an expected call of SaveImageLayoutToDir. +func (mr *MockImageBuilderInterfaceMockRecorder) SaveImageLayoutToDir(ctx, imgRef, layoutDir any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveImageLayoutToDir", reflect.TypeOf((*MockImageBuilderInterface)(nil).SaveImageLayoutToDir), ctx, imgRef, layoutDir) +} + +// MockCatalogBuilderInterface is a mock of CatalogBuilderInterface interface. +type MockCatalogBuilderInterface struct { + ctrl *gomock.Controller + recorder *MockCatalogBuilderInterfaceMockRecorder + isgomock struct{} +} + +// MockCatalogBuilderInterfaceMockRecorder is the mock recorder for MockCatalogBuilderInterface. +type MockCatalogBuilderInterfaceMockRecorder struct { + mock *MockCatalogBuilderInterface +} + +// NewMockCatalogBuilderInterface creates a new mock instance. +func NewMockCatalogBuilderInterface(ctrl *gomock.Controller) *MockCatalogBuilderInterface { + mock := &MockCatalogBuilderInterface{ctrl: ctrl} + mock.recorder = &MockCatalogBuilderInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCatalogBuilderInterface) EXPECT() *MockCatalogBuilderInterfaceMockRecorder { + return m.recorder +} + +// RebuildCatalog mocks base method. +func (m *MockCatalogBuilderInterface) RebuildCatalog(ctx context.Context, catalogCopyRefs v2alpha1.CopyImageSchema, configPath string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RebuildCatalog", ctx, catalogCopyRefs, configPath) + ret0, _ := ret[0].(error) + return ret0 +} + +// RebuildCatalog indicates an expected call of RebuildCatalog. +func (mr *MockCatalogBuilderInterfaceMockRecorder) RebuildCatalog(ctx, catalogCopyRefs, configPath any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RebuildCatalog", reflect.TypeOf((*MockCatalogBuilderInterface)(nil).RebuildCatalog), ctx, catalogCopyRefs, configPath) +} diff --git a/internal/pkg/release/graph_test.go b/internal/pkg/release/graph_test.go index 48d9fda9e..4c6dee706 100644 --- a/internal/pkg/release/graph_test.go +++ b/internal/pkg/release/graph_test.go @@ -2,16 +2,16 @@ package release import ( "context" - "fmt" + "errors" "testing" - v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" + imagebuildermock "github.com/openshift/oc-mirror/v2/internal/pkg/imagebuilder/mock" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" @@ -19,10 +19,6 @@ import ( releasemock "github.com/openshift/oc-mirror/v2/internal/pkg/release/mock" ) -type mockImageBuilder struct { - Fail bool -} - func TestCreateGraphImage(t *testing.T) { log := clog.New("trace") globalM2D := &mirror.GlobalOptions{ @@ -139,6 +135,22 @@ func TestCreateGraphImage(t *testing.T) { cincinnatiMock := releasemock.NewMockCincinnatiInterface(mockCtrl) + imgBuilderMock := imagebuildermock.NewMockImageBuilderInterface(mockCtrl) + + imgBuilderMock. + EXPECT(). + SaveImageLayoutToDir(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(context.Context, string, string) (layout.Path, error) { + return layout.FromPath(common.TestFolder + "test-untar") + }). + AnyTimes() + + imgBuilderMock. + EXPECT(). + BuildAndPush(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return("sha256:12345", nil). + AnyTimes() + // this test should cover over 80% M2D t.Run("Testing CreateGraphImage - Mirror to disk: should pass", func(t *testing.T) { ex := &LocalStorageCollector{ @@ -149,11 +161,11 @@ func TestCreateGraphImage(t *testing.T) { Opts: m2dOpts, Cincinnati: cincinnatiMock, LocalStorageFQDN: "localhost:9999", - ImageBuilder: &mockImageBuilder{}, + ImageBuilder: imgBuilderMock, } // just to ensure we cover new.go - _ = New(log, "nada", cfgm2d, m2dOpts, mirrorMock, manifestMock, cincinnatiMock, &mockImageBuilder{}) + _ = New(log, "nada", cfgm2d, m2dOpts, mirrorMock, manifestMock, cincinnatiMock, imgBuilderMock) _, err := ex.CreateGraphImage(ctx, graphURL) assert.NoError(t, err) @@ -168,7 +180,7 @@ func TestCreateGraphImage(t *testing.T) { Opts: m2dOpts, Cincinnati: cincinnatiMock, LocalStorageFQDN: "localhost:9999", - ImageBuilder: &mockImageBuilder{}, + ImageBuilder: imgBuilderMock, } _, err := ex.CreateGraphImage(ctx, "nada") @@ -176,6 +188,14 @@ func TestCreateGraphImage(t *testing.T) { }) t.Run("Testing CreateGraphImage - Mirror to disk: should fail", func(t *testing.T) { + imgBuilderMock := imagebuildermock.NewMockImageBuilderInterface(mockCtrl) + + imgBuilderMock. + EXPECT(). + SaveImageLayoutToDir(gomock.Any(), gomock.Any(), gomock.Any()). + Return(layout.Path(""), errors.New("force fail")). + AnyTimes() + ex := &LocalStorageCollector{ Log: log, Mirror: mirrorMock, @@ -184,32 +204,10 @@ func TestCreateGraphImage(t *testing.T) { Opts: m2dOpts, Cincinnati: cincinnatiMock, LocalStorageFQDN: "localhost:9999", - ImageBuilder: &mockImageBuilder{Fail: true}, + ImageBuilder: imgBuilderMock, } _, err := ex.CreateGraphImage(ctx, graphURL) assert.Error(t, err) }) } - -func (o mockImageBuilder) BuildAndPush(ctx context.Context, targetRef string, layoutPath layout.Path, cmd []string, layers ...v1.Layer) (string, error) { - if o.Fail { - return "", fmt.Errorf("forced error") - } - return "sha256:12345", nil -} - -func (o mockImageBuilder) SaveImageLayoutToDir(ctx context.Context, imgRef string, layoutDir string) (layout.Path, error) { - if o.Fail { - return layout.Path(""), fmt.Errorf("forced error") - } - return layout.FromPath(common.TestFolder + "test-untar") -} - -func (o mockImageBuilder) ProcessImageIndex(ctx context.Context, idx v1.ImageIndex, v2format *bool, cmd []string, targetRef string, layers ...v1.Layer) (v1.ImageIndex, error) { - return nil, nil -} - -func (o mockImageBuilder) RebuildCatalogs(ctx context.Context, collectorSchema v2alpha1.CollectorSchema) ([]v2alpha1.CopyImageSchema, error) { - return []v2alpha1.CopyImageSchema{}, nil -} diff --git a/internal/pkg/release/local_stored_collector_test.go b/internal/pkg/release/local_stored_collector_test.go index 9035b7dc7..06548d886 100644 --- a/internal/pkg/release/local_stored_collector_test.go +++ b/internal/pkg/release/local_stored_collector_test.go @@ -9,12 +9,15 @@ import ( "strings" "testing" + "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/otiai10/copy" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" "github.com/openshift/oc-mirror/v2/internal/pkg/common" + "github.com/openshift/oc-mirror/v2/internal/pkg/imagebuilder" + imagebuildermock "github.com/openshift/oc-mirror/v2/internal/pkg/imagebuilder/mock" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/manifest" manifestmock "github.com/openshift/oc-mirror/v2/internal/pkg/manifest/mock" @@ -115,7 +118,23 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock, cincinnatiMock) + imgBuilderMock := imagebuildermock.NewMockImageBuilderInterface(mockCtrl) + + imgBuilderMock. + EXPECT(). + BuildAndPush(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return("sha256:12345", nil). + AnyTimes() + + imgBuilderMock. + EXPECT(). + SaveImageLayoutToDir(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(context.Context, string, string) (layout.Path, error) { + return layout.FromPath(common.TestFolder + "test-untar") + }). + AnyTimes() + + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, mirrorMock, cincinnatiMock, imgBuilderMock) err := copy.Copy(common.TestFolder+"working-dir-fake/hold-release/ocp-release/4.14.1-x86_64", filepath.Join(ex.Opts.Global.WorkingDir, releaseImageExtractDir, "ocp-release/4.13.10-x86_64")) assert.NoError(t, err) @@ -309,7 +328,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil, errors.New("forced error image index")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock, nil) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) log.Debug("completed test related images %v ", res) @@ -330,7 +349,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(nil, errors.New("force fail error")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock, nil) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) @@ -358,7 +377,7 @@ func TestReleaseLocalStoredCollector(t *testing.T) { Return(errors.New("forced extract oci fail")). AnyTimes() - ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock) + ex := setupCollector_MirrorToDisk(tempDir, log, manifestMock, nil, cincinnatiMock, nil) res, err := ex.ReleaseImageCollector(context.Background()) assert.Error(t, err) @@ -540,6 +559,22 @@ func TestHandleGraphImage(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() + imgBuilderMock := imagebuildermock.NewMockImageBuilderInterface(mockCtrl) + + imgBuilderMock. + EXPECT(). + BuildAndPush(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return("sha256:12345", nil). + AnyTimes() + + imgBuilderMock. + EXPECT(). + SaveImageLayoutToDir(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(context.Context, string, string) (layout.Path, error) { + return layout.FromPath(common.TestFolder + "test-untar") + }). + AnyTimes() + for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { tempDir := t.TempDir() @@ -610,7 +645,7 @@ func TestHandleGraphImage(t *testing.T) { Opts: copyOpts, Manifest: manifestMock, LocalStorageFQDN: "localhost:9999", - ImageBuilder: &mockImageBuilder{}, + ImageBuilder: imgBuilderMock, LogsDir: "/tmp/", } graphImage, err := ex.handleGraphImage(context.Background()) @@ -688,7 +723,7 @@ func setupCollector_DiskToMirror(tempDir string, log clog.PluggableLoggerInterfa return ex } -func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, mirrorIface mirror.MirrorInterface, cincinnatiIface CincinnatiInterface) *LocalStorageCollector { +func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterface, manifest manifest.ManifestInterface, mirrorIface mirror.MirrorInterface, cincinnatiIface CincinnatiInterface, imgBuilderIface imagebuilder.ImageBuilderInterface) *LocalStorageCollector { globalM2D := &mirror.GlobalOptions{ SecurePolicy: false, WorkingDir: tempDir, @@ -800,7 +835,7 @@ func setupCollector_MirrorToDisk(tempDir string, log clog.PluggableLoggerInterfa Opts: m2dOpts, Cincinnati: cincinnatiIface, LocalStorageFQDN: "localhost:9999", - ImageBuilder: &mockImageBuilder{}, + ImageBuilder: imgBuilderIface, LogsDir: "/tmp/", } return ex From 7a2abef2646ac55c84b08760de97520b7546374b Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Tue, 4 Nov 2025 11:06:17 +0100 Subject: [PATCH 7/9] v2: pkg/release: use generated client mock --- internal/pkg/release/cincinnati_test.go | 40 ++---- internal/pkg/release/client.go | 2 + internal/pkg/release/core-cincinnati_test.go | 128 +++++++++--------- internal/pkg/release/mock/client_generated.go | 97 +++++++++++++ 4 files changed, 180 insertions(+), 87 deletions(-) create mode 100644 internal/pkg/release/mock/client_generated.go diff --git a/internal/pkg/release/cincinnati_test.go b/internal/pkg/release/cincinnati_test.go index bae5818d7..8bf76f316 100644 --- a/internal/pkg/release/cincinnati_test.go +++ b/internal/pkg/release/cincinnati_test.go @@ -8,6 +8,7 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" @@ -107,7 +108,6 @@ func TestGetReleaseReferenceImages(t *testing.T) { } t.Run("TestGetReleaseReferenceImages should pass", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) defer close(requestQuery) @@ -118,11 +118,9 @@ func TestGetReleaseReferenceImages(t *testing.T) { t.Cleanup(ts.Close) endpoint, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("should not fail endpoint parse") - } - c.url = endpoint - sch := NewCincinnati(log, nil, &cfg, opts, c, false, signature) + assert.NoError(t, err) + clientMock := newMockClient(endpoint, mockCtrl) + sch := NewCincinnati(log, nil, &cfg, opts, clientMock, false, signature) res, _ := sch.GetReleaseReferenceImages(context.Background()) if res == nil { t.Fatalf("should return a related images") @@ -130,7 +128,6 @@ func TestGetReleaseReferenceImages(t *testing.T) { }) t.Run("TestGetReleaseReferenceImages should pass (no channels)", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) defer close(requestQuery) @@ -141,21 +138,19 @@ func TestGetReleaseReferenceImages(t *testing.T) { t.Cleanup(ts.Close) endpoint, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("should not fail endpoint parse") - } - c.url = endpoint - sch := NewCincinnati(log, nil, &cfgNoChannels, opts, c, false, signature) + assert.NoError(t, err) + clientMock := newMockClient(endpoint, mockCtrl) + sch := NewCincinnati(log, nil, &cfgNoChannels, opts, clientMock, false, signature) res, err := sch.GetReleaseReferenceImages(context.Background()) + assert.NoError(t, err) log.Debug("result from cincinnati %v", res) - if res == nil || err != nil { + if res == nil { t.Fatalf("should return a related images") } }) t.Run("TestGetReleaseReferenceImages should fail", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) defer close(requestQuery) @@ -166,11 +161,9 @@ func TestGetReleaseReferenceImages(t *testing.T) { t.Cleanup(ts.Close) endpoint, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("should not fail endpoint parse") - } - c.url = endpoint - sch := NewCincinnati(log, nil, &cfg, opts, c, true, signature) + assert.NoError(t, err) + clientMock := newMockClient(endpoint, mockCtrl) + sch := NewCincinnati(log, nil, &cfg, opts, clientMock, true, signature) res, _ := sch.GetReleaseReferenceImages(context.Background()) log.Debug("result from cincinnati %v", res) @@ -180,7 +173,6 @@ func TestGetReleaseReferenceImages(t *testing.T) { }) t.Run("TestGetReleaseReferenceImages should pass (platform.release & kubevirt)", func(t *testing.T) { - c := &mockClient{} signature := &mockSignature{Log: log} requestQuery := make(chan string, 1) defer close(requestQuery) @@ -191,10 +183,8 @@ func TestGetReleaseReferenceImages(t *testing.T) { t.Cleanup(ts.Close) endpoint, err := url.Parse(ts.URL) - if err != nil { - t.Fatalf("should not fail endpoint parse") - } - c.url = endpoint + assert.NoError(t, err) + clientMock := newMockClient(endpoint, mockCtrl) manifestMock := manifestmock.NewMockManifestInterface(mockCtrl) @@ -204,7 +194,7 @@ func TestGetReleaseReferenceImages(t *testing.T) { Return("123456546546546546546546546", nil). AnyTimes() - sch := NewCincinnati(log, manifestMock, &cfgReleaseKubeVirt, opts, c, true, signature) + sch := NewCincinnati(log, manifestMock, &cfgReleaseKubeVirt, opts, clientMock, true, signature) res, _ := sch.GetReleaseReferenceImages(context.Background()) log.Debug("result from cincinnati %v", res) diff --git a/internal/pkg/release/client.go b/internal/pkg/release/client.go index 38cabb505..0bd0014ea 100644 --- a/internal/pkg/release/client.go +++ b/internal/pkg/release/client.go @@ -17,6 +17,8 @@ var ( _ Client = &okdClient{} ) +//go:generate go tool mockgen -source=./client.go -destination=./mock/client_generated.go -package=mock + // Client is a Cincinnati client which can be used to fetch update graphs from // an upstream Cincinnati stack. type Client interface { diff --git a/internal/pkg/release/core-cincinnati_test.go b/internal/pkg/release/core-cincinnati_test.go index 671095cb9..0cd7e670b 100644 --- a/internal/pkg/release/core-cincinnati_test.go +++ b/internal/pkg/release/core-cincinnati_test.go @@ -7,26 +7,20 @@ import ( "net/http" "net/http/httptest" "net/url" - "os" "testing" "github.com/blang/semver/v4" "github.com/google/uuid" - clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/stretchr/testify/require" -) - -var _ Client = &mockClient{} + "go.uber.org/mock/gomock" -type mockClient struct { - url *url.URL - Fail bool -} + clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" + "github.com/openshift/oc-mirror/v2/internal/pkg/release/mock" +) func TestGetUpdates(t *testing.T) { - - tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() arch := "test-arch" channelName := "stable-4.0" @@ -97,9 +91,10 @@ func TestGetUpdates(t *testing.T) { endpoint, err := url.Parse(ts.URL) require.NoError(t, err) - c := &mockClient{url: endpoint} - cs := CincinnatiSchema{Log: clog.New("trace"), Client: c, CincinnatiParams: CincinnatiParams{Arch: arch}} + clientMock := newMockClient(endpoint, mockCtrl) + + cs := CincinnatiSchema{Log: clog.New("trace"), Client: clientMock, CincinnatiParams: CincinnatiParams{Arch: arch}} current, requested, updates, err := GetUpdates(context.Background(), cs, channelName, semver.MustParse(test.version), semver.MustParse(test.reqVer)) if test.err == "" { @@ -130,8 +125,8 @@ func TestGetMinorMax(t *testing.T) { arch := "test-arch" channelName := "stable-4.0" - tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() tests := []struct { name string @@ -162,9 +157,9 @@ func TestGetMinorMax(t *testing.T) { endpoint, err := url.Parse(ts.URL) require.NoError(t, err) - c := &mockClient{url: endpoint} + clientMock := newMockClient(endpoint, mockCtrl) - cs := CincinnatiSchema{Log: clog.New("trace"), Client: c, CincinnatiParams: CincinnatiParams{Arch: arch}} + cs := CincinnatiSchema{Log: clog.New("trace"), Client: clientMock, CincinnatiParams: CincinnatiParams{Arch: arch}} version, err := GetChannelMinOrMax(context.Background(), cs, channelName, test.min) if test.err == "" { require.NoError(t, err) @@ -190,9 +185,8 @@ func TestGetMinorMax(t *testing.T) { } func TestGetVersions(t *testing.T) { - - tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() tests := []struct { name string @@ -229,9 +223,9 @@ func TestGetVersions(t *testing.T) { endpoint, err := url.Parse(ts.URL) require.NoError(t, err) - c := &mockClient{url: endpoint} + clientMock := newMockClient(endpoint, mockCtrl) - cs := CincinnatiSchema{Log: clog.New("trace"), Client: c, CincinnatiParams: CincinnatiParams{Arch: test.arch}} + cs := CincinnatiSchema{Log: clog.New("trace"), Client: clientMock, CincinnatiParams: CincinnatiParams{Arch: test.arch}} versions, err := GetVersions(context.Background(), cs, test.channel) if test.err == "" { require.NoError(t, err) @@ -257,8 +251,8 @@ func TestGetVersions(t *testing.T) { } func TestGetUpdatesInRange(t *testing.T) { - tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() arch := "test-arch" channelName := "stable-4.0" @@ -294,9 +288,9 @@ func TestGetUpdatesInRange(t *testing.T) { endpoint, err := url.Parse(ts.URL) require.NoError(t, err) - c := &mockClient{url: endpoint} + clientMock := newMockClient(endpoint, mockCtrl) - cs := CincinnatiSchema{Log: clog.New("trace"), Client: c, CincinnatiParams: CincinnatiParams{Arch: arch}} + cs := CincinnatiSchema{Log: clog.New("trace"), Client: clientMock, CincinnatiParams: CincinnatiParams{Arch: arch}} versions, err := GetUpdatesInRange(context.TODO(), cs, channelName, test.releaseRange) if test.err == "" { require.NoError(t, err) @@ -322,8 +316,8 @@ func TestGetUpdatesInRange(t *testing.T) { } func TestCalculateUpgrades(t *testing.T) { - tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() arch := "test-arch" @@ -428,8 +422,9 @@ func TestCalculateUpgrades(t *testing.T) { endpoint, err := url.Parse(ts.URL) require.NoError(t, err) + clientMock := newMockClient(endpoint, mockCtrl) - cs := CincinnatiSchema{Log: clog.New("trace"), Client: &mockClient{url: endpoint}, CincinnatiParams: CincinnatiParams{Arch: arch, GraphDataDir: t.TempDir()}} + cs := CincinnatiSchema{Log: clog.New("trace"), Client: clientMock, CincinnatiParams: CincinnatiParams{Arch: arch, GraphDataDir: t.TempDir()}} cur, req, updates, err := CalculateUpgrades(context.Background(), cs, test.sourceChannel, test.targetChannel, test.curr, test.req) if test.err == "" { @@ -445,8 +440,8 @@ func TestCalculateUpgrades(t *testing.T) { } func TestHandleBlockedEdges(t *testing.T) { - tempDir := t.TempDir() - defer os.RemoveAll(tempDir) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() arch := "test-arch" @@ -499,8 +494,9 @@ func TestHandleBlockedEdges(t *testing.T) { endpoint, err := url.Parse(ts.URL) require.NoError(t, err) + clientMock := newMockClient(endpoint, mockCtrl) - cs := CincinnatiSchema{Log: clog.New("trace"), Client: &mockClient{url: endpoint}, CincinnatiParams: CincinnatiParams{Arch: arch, GraphDataDir: t.TempDir()}} + cs := CincinnatiSchema{Log: clog.New("trace"), Client: clientMock, CincinnatiParams: CincinnatiParams{Arch: arch, GraphDataDir: t.TempDir()}} isBlocked, err := handleBlockedEdges(context.Background(), cs, test.targetChannel, test.last) if test.err == "" { @@ -513,32 +509,42 @@ func TestHandleBlockedEdges(t *testing.T) { } } -func (c mockClient) GetID() uuid.UUID { - return uuid.MustParse("01234567-0123-0123-0123-0123456789ab") -} - -func (c mockClient) SetQueryParams(arch, channel, version string) { - queryParams := c.url.Query() - queryParams.Add("id", c.GetID().String()) - params := map[string]string{ - "arch": arch, - "channel": channel, - "version": version, - } - for key, value := range params { - if value != "" { - queryParams.Add(key, value) - } - } - c.url.RawQuery = queryParams.Encode() -} - -func (c mockClient) GetURL() *url.URL { - return c.url -} - -func (c mockClient) GetTransport() *http.Transport { - return &http.Transport{} +func newMockClient(url *url.URL, mockCtrl *gomock.Controller) *mock.MockClient { + clientMock := mock.NewMockClient(mockCtrl) + + clientMock. + EXPECT(). + SetQueryParams(gomock.Any(), gomock.Any(), gomock.Any()). + Do(func(arch, channel, version string) { + queryParams := url.Query() + queryParams.Add("id", uuid.MustParse("01234567-0123-0123-0123-0123456789ab").String()) + params := map[string]string{ + "arch": arch, + "channel": channel, + "version": version, + } + for key, value := range params { + if value != "" { + queryParams.Add(key, value) + } + } + url.RawQuery = queryParams.Encode() + }). + AnyTimes() + + clientMock. + EXPECT(). + GetTransport(). + Return(&http.Transport{}). + AnyTimes() + + clientMock. + EXPECT(). + GetURL(). + Return(url). + AnyTimes() + + return clientMock } func TestNodeUnmarshalJSON(t *testing.T) { @@ -612,7 +618,6 @@ func TestNodeUnmarshalJSON(t *testing.T) { } func TestGetSemVerFromChannel(t *testing.T) { - tests := []struct { name string sourceChannel string @@ -650,7 +655,6 @@ func TestGetSemVerFromChannel(t *testing.T) { } }) } - } func getSemVers(stringVers []string) (vers []semver.Version) { @@ -666,7 +670,7 @@ func getHandlerMulti(t *testing.T, requestQuery chan<- string) http.HandlerFunc select { case requestQuery <- r.URL.RawQuery: default: - //t.Fatalf("received multiple requests at upstream URL") + // t.Fatalf("received multiple requests at upstream URL") } if r.Method != http.MethodGet && r.Method != http.MethodHead { diff --git a/internal/pkg/release/mock/client_generated.go b/internal/pkg/release/mock/client_generated.go new file mode 100644 index 000000000..a0d4e72f0 --- /dev/null +++ b/internal/pkg/release/mock/client_generated.go @@ -0,0 +1,97 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./client.go +// +// Generated by this command: +// +// mockgen -source=./client.go -destination=./mock/client_generated.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + http "net/http" + url "net/url" + reflect "reflect" + + uuid "github.com/google/uuid" + gomock "go.uber.org/mock/gomock" +) + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder + isgomock struct{} +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// GetID mocks base method. +func (m *MockClient) GetID() uuid.UUID { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetID") + ret0, _ := ret[0].(uuid.UUID) + return ret0 +} + +// GetID indicates an expected call of GetID. +func (mr *MockClientMockRecorder) GetID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetID", reflect.TypeOf((*MockClient)(nil).GetID)) +} + +// GetTransport mocks base method. +func (m *MockClient) GetTransport() *http.Transport { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTransport") + ret0, _ := ret[0].(*http.Transport) + return ret0 +} + +// GetTransport indicates an expected call of GetTransport. +func (mr *MockClientMockRecorder) GetTransport() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransport", reflect.TypeOf((*MockClient)(nil).GetTransport)) +} + +// GetURL mocks base method. +func (m *MockClient) GetURL() *url.URL { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetURL") + ret0, _ := ret[0].(*url.URL) + return ret0 +} + +// GetURL indicates an expected call of GetURL. +func (mr *MockClientMockRecorder) GetURL() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetURL", reflect.TypeOf((*MockClient)(nil).GetURL)) +} + +// SetQueryParams mocks base method. +func (m *MockClient) SetQueryParams(arch, channel, version string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetQueryParams", arch, channel, version) +} + +// SetQueryParams indicates an expected call of SetQueryParams. +func (mr *MockClientMockRecorder) SetQueryParams(arch, channel, version any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryParams", reflect.TypeOf((*MockClient)(nil).SetQueryParams), arch, channel, version) +} From 2a30ee560a7116409b6836db2d0971e6f6df071a Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Tue, 4 Nov 2025 11:23:11 +0100 Subject: [PATCH 8/9] v2: pkg/helm: use generated mocks --- internal/pkg/helm/interface.go | 2 + .../pkg/helm/local_stored_collector_test.go | 92 ++++----- internal/pkg/helm/mock/interface_generated.go | 176 ++++++++++++++++++ 3 files changed, 227 insertions(+), 43 deletions(-) create mode 100644 internal/pkg/helm/mock/interface_generated.go diff --git a/internal/pkg/helm/interface.go b/internal/pkg/helm/interface.go index 8b8211a90..de9a28d13 100644 --- a/internal/pkg/helm/interface.go +++ b/internal/pkg/helm/interface.go @@ -7,6 +7,8 @@ import ( "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" ) +//go:generate go tool mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock + type CollectorInterface interface { HelmImageCollector(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) } diff --git a/internal/pkg/helm/local_stored_collector_test.go b/internal/pkg/helm/local_stored_collector_test.go index 258feaa9a..c62b0f741 100644 --- a/internal/pkg/helm/local_stored_collector_test.go +++ b/internal/pkg/helm/local_stored_collector_test.go @@ -13,9 +13,11 @@ import ( "github.com/otiai10/copy" "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" helmrepo "helm.sh/helm/v3/pkg/repo" "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" + helmmock "github.com/openshift/oc-mirror/v2/internal/pkg/helm/mock" clog "github.com/openshift/oc-mirror/v2/internal/pkg/log" "github.com/openshift/oc-mirror/v2/internal/pkg/mirror" ) @@ -38,12 +40,6 @@ var ( } ) -type MockIndexDownloader struct{} - -type MockChartDownloader struct{} - -type MockHttpClient struct{} - type testCase struct { caseName string mirrorMode string @@ -666,10 +662,51 @@ func TestHelmImageCollector(t *testing.T) { } tempDir := t.TempDir() - defer os.RemoveAll(tempDir) workingDir, err := prepareFolder(tempDir) assert.NoError(t, err) + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + indexDownloaderMock := helmmock.NewMockindexDownloader(mockCtrl) + + indexDownloaderMock. + EXPECT(). + DownloadIndexFile(). + Return("", nil). + AnyTimes() + + chartDownloaderMock := helmmock.NewMockchartDownloader(mockCtrl) + + chartDownloaderMock. + EXPECT(). + DownloadTo(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(ref, version, dest string) (string, any, error) { + tgzFileName := copyChart(ref, version) + return filepath.Join(tempChartDir, tgzFileName), "", nil + }). + AnyTimes() + + httpClientMock := helmmock.NewMockwebClient(mockCtrl) + + httpClientMock. + EXPECT(). + Get(gomock.Any()). + DoAndReturn(func(url string) (*http.Response, error) { + ns := getNamespaceFromURL(url) + response := http.Response{StatusCode: http.StatusOK} + + data, err := os.ReadFile(filepath.Join(testIndexesDataPath, ns, helmIndexFile)) + if err != nil { + //nolint:wrapcheck + return &http.Response{StatusCode: http.StatusInternalServerError}, err + } + + response.Body = io.NopCloser(bytes.NewReader(data)) + return &response, nil + }). + AnyTimes() + for _, testCase := range testCases { t.Run(testCase.caseName, func(t *testing.T) { _, srcOpts := mirror.ImageSrcFlags(nil, nil, nil, "src-", "screds") @@ -685,11 +722,7 @@ func TestHelmImageCollector(t *testing.T) { ctx := context.Background() - mockIndexDownloader := MockIndexDownloader{} - mockChartDownloader := MockChartDownloader{} - mockHttpClient := MockHttpClient{} - - helmCollector := New(log, cfg, opts, mockIndexDownloader, mockChartDownloader, mockHttpClient) + helmCollector := New(log, cfg, opts, indexDownloaderMock, chartDownloaderMock, httpClientMock) if testCase.generateV1DestTags { helmCollector = WithV1Tags(helmCollector) } @@ -707,7 +740,6 @@ func TestHelmImageCollector(t *testing.T) { assert.NotEmpty(t, imgs) assert.ElementsMatch(t, testCase.expectedResult, imgs) } - }) } } @@ -747,44 +779,18 @@ func copyIndex(namespace string) { copy.Copy(filepath.Join(testIndexesDataPath, namespace, helmIndexFile), filepath.Join(tempIndexesDir, namespace, helmIndexFile)) } -func (m MockIndexDownloader) DownloadIndexFile() (string, error) { - return "", nil -} - -func (m MockChartDownloader) DownloadTo(ref, version, dest string) (string, any, error) { - tgzFileName := copyChart(ref, version) - - return filepath.Join(tempChartDir, tgzFileName), "", nil -} - -func (m MockHttpClient) Get(url string) (resp *http.Response, err error) { - ns := getNamespaceFromURL(url) - - response := http.Response{StatusCode: http.StatusOK} - - data, err := os.ReadFile(filepath.Join(testIndexesDataPath, ns, helmIndexFile)) - if err != nil { - return &http.Response{StatusCode: http.StatusInternalServerError}, err - } - - response.Body = io.NopCloser(bytes.NewReader(data)) - - return &response, nil -} - func prepareFolder(tempDir string) (string, error) { - workingDir := filepath.Join(tempDir, "/working-dir") - err := os.MkdirAll(filepath.Join(workingDir, helmDir, helmChartDir), 0755) - if err != nil { + if err := os.MkdirAll(filepath.Join(workingDir, helmDir, helmChartDir), 0o755); err != nil { + //nolint:wrapcheck return "", err } tempChartDir = filepath.Join(workingDir, helmDir, helmChartDir) - err = os.MkdirAll(filepath.Join(workingDir, helmDir, helmIndexesDir), 0755) - if err != nil { + if err := os.MkdirAll(filepath.Join(workingDir, helmDir, helmIndexesDir), 0o755); err != nil { + //nolint:wrapcheck return "", err } diff --git a/internal/pkg/helm/mock/interface_generated.go b/internal/pkg/helm/mock/interface_generated.go new file mode 100644 index 000000000..5b9768725 --- /dev/null +++ b/internal/pkg/helm/mock/interface_generated.go @@ -0,0 +1,176 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./interface.go +// +// Generated by this command: +// +// mockgen -source=./interface.go -destination=./mock/interface_generated.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + http "net/http" + reflect "reflect" + + v2alpha1 "github.com/openshift/oc-mirror/v2/internal/pkg/api/v2alpha1" + gomock "go.uber.org/mock/gomock" +) + +// MockCollectorInterface is a mock of CollectorInterface interface. +type MockCollectorInterface struct { + ctrl *gomock.Controller + recorder *MockCollectorInterfaceMockRecorder + isgomock struct{} +} + +// MockCollectorInterfaceMockRecorder is the mock recorder for MockCollectorInterface. +type MockCollectorInterfaceMockRecorder struct { + mock *MockCollectorInterface +} + +// NewMockCollectorInterface creates a new mock instance. +func NewMockCollectorInterface(ctrl *gomock.Controller) *MockCollectorInterface { + mock := &MockCollectorInterface{ctrl: ctrl} + mock.recorder = &MockCollectorInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCollectorInterface) EXPECT() *MockCollectorInterfaceMockRecorder { + return m.recorder +} + +// HelmImageCollector mocks base method. +func (m *MockCollectorInterface) HelmImageCollector(ctx context.Context) ([]v2alpha1.CopyImageSchema, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HelmImageCollector", ctx) + ret0, _ := ret[0].([]v2alpha1.CopyImageSchema) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HelmImageCollector indicates an expected call of HelmImageCollector. +func (mr *MockCollectorInterfaceMockRecorder) HelmImageCollector(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HelmImageCollector", reflect.TypeOf((*MockCollectorInterface)(nil).HelmImageCollector), ctx) +} + +// MockindexDownloader is a mock of indexDownloader interface. +type MockindexDownloader struct { + ctrl *gomock.Controller + recorder *MockindexDownloaderMockRecorder + isgomock struct{} +} + +// MockindexDownloaderMockRecorder is the mock recorder for MockindexDownloader. +type MockindexDownloaderMockRecorder struct { + mock *MockindexDownloader +} + +// NewMockindexDownloader creates a new mock instance. +func NewMockindexDownloader(ctrl *gomock.Controller) *MockindexDownloader { + mock := &MockindexDownloader{ctrl: ctrl} + mock.recorder = &MockindexDownloaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockindexDownloader) EXPECT() *MockindexDownloaderMockRecorder { + return m.recorder +} + +// DownloadIndexFile mocks base method. +func (m *MockindexDownloader) DownloadIndexFile() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DownloadIndexFile") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DownloadIndexFile indicates an expected call of DownloadIndexFile. +func (mr *MockindexDownloaderMockRecorder) DownloadIndexFile() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadIndexFile", reflect.TypeOf((*MockindexDownloader)(nil).DownloadIndexFile)) +} + +// MockchartDownloader is a mock of chartDownloader interface. +type MockchartDownloader struct { + ctrl *gomock.Controller + recorder *MockchartDownloaderMockRecorder + isgomock struct{} +} + +// MockchartDownloaderMockRecorder is the mock recorder for MockchartDownloader. +type MockchartDownloaderMockRecorder struct { + mock *MockchartDownloader +} + +// NewMockchartDownloader creates a new mock instance. +func NewMockchartDownloader(ctrl *gomock.Controller) *MockchartDownloader { + mock := &MockchartDownloader{ctrl: ctrl} + mock.recorder = &MockchartDownloaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockchartDownloader) EXPECT() *MockchartDownloaderMockRecorder { + return m.recorder +} + +// DownloadTo mocks base method. +func (m *MockchartDownloader) DownloadTo(ref, version, dest string) (string, any, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DownloadTo", ref, version, dest) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(any) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// DownloadTo indicates an expected call of DownloadTo. +func (mr *MockchartDownloaderMockRecorder) DownloadTo(ref, version, dest any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadTo", reflect.TypeOf((*MockchartDownloader)(nil).DownloadTo), ref, version, dest) +} + +// MockwebClient is a mock of webClient interface. +type MockwebClient struct { + ctrl *gomock.Controller + recorder *MockwebClientMockRecorder + isgomock struct{} +} + +// MockwebClientMockRecorder is the mock recorder for MockwebClient. +type MockwebClientMockRecorder struct { + mock *MockwebClient +} + +// NewMockwebClient creates a new mock instance. +func NewMockwebClient(ctrl *gomock.Controller) *MockwebClient { + mock := &MockwebClient{ctrl: ctrl} + mock.recorder = &MockwebClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockwebClient) EXPECT() *MockwebClientMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockwebClient) Get(url string) (*http.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", url) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockwebClientMockRecorder) Get(url any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockwebClient)(nil).Get), url) +} From 9e26d868a69a3ce568d19a984617dbab031bef74 Mon Sep 17 00:00:00 2001 From: Rafael Fonseca Date: Tue, 4 Nov 2025 11:55:51 +0100 Subject: [PATCH 9/9] v2: pkg/mirror: silence gosec lint issue --- internal/pkg/mirror/mirror.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/mirror/mirror.go b/internal/pkg/mirror/mirror.go index b803ae6e9..b2d36cbeb 100644 --- a/internal/pkg/mirror/mirror.go +++ b/internal/pkg/mirror/mirror.go @@ -222,7 +222,7 @@ func (o *Mirror) copy(ctx context.Context, src, dest string, opts *CopyOptions) if err != nil { return err } - if err = os.WriteFile(opts.DigestFile, []byte(manifestDigest.String()), 0o644); err != nil { + if err = os.WriteFile(opts.DigestFile, []byte(manifestDigest.String()), 0o644); err != nil { //nolint:gosec // digest file does not contain sensitive info return fmt.Errorf("failed to write digest to file %q: %w", opts.DigestFile, err) } }