From c5045933a15de94c1d42594a0fabf4c82e77abff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=85=95=E8=96=87=E7=96=AF=E9=AD=94?= Date: Mon, 15 Apr 2019 16:32:41 +0800 Subject: [PATCH 1/2] change to testable. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 慕薇疯魔 --- src/replication/adapter/native/adapter.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/replication/adapter/native/adapter.go b/src/replication/adapter/native/adapter.go index e5994f667..10d610417 100644 --- a/src/replication/adapter/native/adapter.go +++ b/src/replication/adapter/native/adapter.go @@ -26,14 +26,7 @@ const registryTypeNative model.RegistryType = "native" func init() { if err := adp.RegisterFactory(registryTypeNative, func(registry *model.Registry) (adp.Adapter, error) { - reg, err := adp.NewDefaultImageRegistry(registry) - if err != nil { - return nil, err - } - return &native{ - registry: registry, - DefaultImageRegistry: reg, - }, nil + return newAdapter(registry) }); err != nil { log.Errorf("failed to register factory for %s: %v", registryTypeNative, err) return @@ -41,6 +34,17 @@ func init() { log.Infof("the factory for adapter %s registered", registryTypeNative) } +func newAdapter(registry *model.Registry) (*native, error) { + reg, err := adp.NewDefaultImageRegistry(registry) + if err != nil { + return nil, err + } + return &native{ + registry: registry, + DefaultImageRegistry: reg, + }, nil +} + type native struct { *adp.DefaultImageRegistry registry *model.Registry From 027cf44e829b5363c8f9994d7a4dd1e61e8da542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=85=95=E8=96=87=E7=96=AF=E9=AD=94?= Date: Mon, 15 Apr 2019 20:33:46 +0800 Subject: [PATCH 2/2] unit test for native adapter. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 慕薇疯魔 --- .../adapter/native/adapter_test.go | 153 ++++++++++ .../adapter/native/image_registry_test.go | 275 ++++++++++++++++++ 2 files changed, 428 insertions(+) create mode 100644 src/replication/adapter/native/adapter_test.go create mode 100644 src/replication/adapter/native/image_registry_test.go diff --git a/src/replication/adapter/native/adapter_test.go b/src/replication/adapter/native/adapter_test.go new file mode 100644 index 000000000..98ba2bef9 --- /dev/null +++ b/src/replication/adapter/native/adapter_test.go @@ -0,0 +1,153 @@ +package native + +import ( + "testing" + + adp "github.com/goharbor/harbor/src/replication/adapter" + "github.com/goharbor/harbor/src/replication/model" + "github.com/stretchr/testify/assert" +) + +func Test_newAdapter(t *testing.T) { + tests := []struct { + name string + registry *model.Registry + wantErr bool + }{ + {name: "Nil Registry URL", registry: &model.Registry{}, wantErr: true}, + {name: "Right", registry: &model.Registry{URL: "abc"}, wantErr: false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newAdapter(tt.registry) + if tt.wantErr { + assert.NotNil(t, err) + assert.Nil(t, got) + } else { + assert.Nil(t, err) + assert.NotNil(t, got) + } + }) + } +} + +func Test_native_Info(t *testing.T) { + var registry = &model.Registry{URL: "abc"} + var reg, _ = adp.NewDefaultImageRegistry(registry) + var adapter = native{ + DefaultImageRegistry: reg, + registry: registry, + } + assert.NotNil(t, adapter) + + var info, err = adapter.Info() + assert.Nil(t, err) + assert.NotNil(t, info) + assert.Equal(t, registryTypeNative, info.Type) + assert.Equal(t, 1, len(info.SupportedResourceTypes)) + assert.Equal(t, 2, len(info.SupportedResourceFilters)) + assert.Equal(t, 2, len(info.SupportedTriggers)) + assert.Equal(t, model.ResourceTypeRepository, info.SupportedResourceTypes[0]) +} + +func Test_native_ConvertResourceMetadata(t *testing.T) { + var registry = &model.Registry{URL: "abc"} + var reg, _ = adp.NewDefaultImageRegistry(registry) + var adapter = native{ + DefaultImageRegistry: reg, + registry: registry, + } + assert.NotNil(t, adapter) + + tests := []struct { + name string + metadata *model.ResourceMetadata + namespace *model.Namespace + want string + wantErr bool + }{ + {name: "nil metadata", metadata: nil, wantErr: true}, + { + name: "2 level", + metadata: &model.ResourceMetadata{ + Namespace: &model.Namespace{Name: "a"}, + Repository: &model.Repository{Name: "b"}, + }, + namespace: nil, + want: "a/b", + wantErr: false, + }, + { + name: "2 level rename reomte repository", + metadata: &model.ResourceMetadata{ + Namespace: &model.Namespace{Name: "a"}, + Repository: &model.Repository{Name: "b"}, + }, + namespace: &model.Namespace{Name: "c"}, + want: "c/b", + wantErr: false, + }, + { + name: "3 level", + metadata: &model.ResourceMetadata{ + Namespace: &model.Namespace{Name: "a"}, + Repository: &model.Repository{Name: "b/c"}, + }, + namespace: nil, + want: "a/b/c", + wantErr: false, + }, + { + name: "3 level rename reomte repository", + metadata: &model.ResourceMetadata{ + Namespace: &model.Namespace{Name: "a"}, + Repository: &model.Repository{Name: "b/c"}, + }, + namespace: &model.Namespace{Name: "d"}, + want: "d/b/c", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var convert, err = adapter.ConvertResourceMetadata(tt.metadata, tt.namespace) + if tt.wantErr { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + assert.NotNil(t, convert) + assert.Nil(t, convert.Namespace) + assert.Equal(t, tt.want, convert.Repository.Name) + assert.Equal(t, tt.want, convert.GetResourceName()) + } + }) + } +} + +func Test_native_PrepareForPush(t *testing.T) { + var registry = &model.Registry{URL: "abc"} + var reg, _ = adp.NewDefaultImageRegistry(registry) + var adapter = native{ + DefaultImageRegistry: reg, + registry: registry, + } + assert.NotNil(t, adapter) + + var err = adapter.PrepareForPush(nil) + assert.Nil(t, err) +} + +func Test_native_ListNamespaces(t *testing.T) { + var registry = &model.Registry{URL: "abc"} + var reg, _ = adp.NewDefaultImageRegistry(registry) + var adapter = native{ + DefaultImageRegistry: reg, + registry: registry, + } + assert.NotNil(t, adapter) + + var ns, err = adapter.ListNamespaces(nil) + assert.Nil(t, err) + assert.NotNil(t, ns) +} diff --git a/src/replication/adapter/native/image_registry_test.go b/src/replication/adapter/native/image_registry_test.go new file mode 100644 index 000000000..720fc2ded --- /dev/null +++ b/src/replication/adapter/native/image_registry_test.go @@ -0,0 +1,275 @@ +package native + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/goharbor/harbor/src/common/utils/test" + adp "github.com/goharbor/harbor/src/replication/adapter" + "github.com/goharbor/harbor/src/replication/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockNativeRegistry() (mock *httptest.Server) { + return test.NewServer( + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/v2/_catalog", + Handler: func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"repositories":["test/a1","test/b2","test/c3/3level"]}`)) + }, + }, + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/v2/test/a1/tags/list", + Handler: func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"name":"test/a1","tags":["tag11"]}`)) + }, + }, + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/v2/test/b2/tags/list", + Handler: func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"name":"test/b2","tags":["tag11","tag2","tag13"]}`)) + }, + }, + &test.RequestHandlerMapping{ + Method: http.MethodGet, + Pattern: "/v2/test/c3/3level/tags/list", + Handler: func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(`{"name":"test/c3/3level","tags":["tag4"]}`)) + }, + }, + ) +} +func Test_native_FetchImages(t *testing.T) { + var mock = mockNativeRegistry() + defer mock.Close() + fmt.Println("mockNativeRegistry URL: ", mock.URL) + + var registry = &model.Registry{ + Type: registryTypeNative, + URL: mock.URL, + Insecure: true, + } + var reg, err = adp.NewDefaultImageRegistry(registry) + assert.NotNil(t, reg) + assert.Nil(t, err) + var adapter = native{ + DefaultImageRegistry: reg, + registry: registry, + } + assert.NotNil(t, adapter) + + tests := []struct { + name string + namespaces []string + filters []*model.Filter + want []*model.Resource + wantErr bool + }{ + {name: "namespace not empty", namespaces: []string{"ns"}, wantErr: true}, + // TODO: discuss: should we report error if not found in the source native registry. + // { + // name: "repository not exist", + // filters: []*model.Filter{ + // { + // Type: model.FilterTypeName, + // Value: "b1", + // }, + // }, + // wantErr: true, + // }, + // { + // name: "tag not exist", + // filters: []*model.Filter{ + // { + // Type: model.FilterTypeTag, + // Value: "c", + // }, + // }, + // wantErr: true, + // }, + { + name: "no filters", + filters: []*model.Filter{}, + want: []*model.Resource{ + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/a1"}, + Vtags: []string{"tag11"}, + }, + }, + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/b2"}, + Vtags: []string{"tag11", "tag2", "tag13"}, + }, + }, + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/c3/3level"}, + Vtags: []string{"tag4"}, + }, + }, + }, + wantErr: false, + }, + { + name: "only special repository", + namespaces: []string{}, + filters: []*model.Filter{ + { + Type: model.FilterTypeName, + Value: "test/a1", + }, + }, + want: []*model.Resource{ + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/a1"}, + Vtags: []string{"tag11"}, + }, + }, + }, + wantErr: false, + }, + { + name: "only special tag", + namespaces: []string{}, + filters: []*model.Filter{ + { + Type: model.FilterTypeTag, + Value: "tag11", + }, + }, + want: []*model.Resource{ + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/a1"}, + Vtags: []string{"tag11"}, + }, + }, + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/b2"}, + Vtags: []string{"tag11"}, + }, + }, + }, + wantErr: false, + }, + { + name: "special repository and special tag", + namespaces: []string{}, + filters: []*model.Filter{ + { + Type: model.FilterTypeName, + Value: "test/b2", + }, + { + Type: model.FilterTypeTag, + Value: "tag2", + }, + }, + want: []*model.Resource{ + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/b2"}, + Vtags: []string{"tag2"}, + }, + }, + }, + + wantErr: false, + }, + { + name: "only wildcard repository", + namespaces: []string{}, + filters: []*model.Filter{ + { + Type: model.FilterTypeName, + Value: "test/b*", + }, + }, + want: []*model.Resource{ + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/b2"}, + Vtags: []string{"tag11", "tag2", "tag13"}, + }, + }, + }, + wantErr: false, + }, + { + name: "only wildcard tag", + namespaces: []string{}, + filters: []*model.Filter{ + { + Type: model.FilterTypeTag, + Value: "tag1*", + }, + }, + want: []*model.Resource{ + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/a1"}, + Vtags: []string{"tag11"}, + }, + }, + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/b2"}, + Vtags: []string{"tag11", "tag13"}, + }, + }, + }, + wantErr: false, + }, + { + name: "wildcard repository and wildcard tag", + namespaces: []string{}, + filters: []*model.Filter{ + { + Type: model.FilterTypeName, + Value: "test/b*", + }, + { + Type: model.FilterTypeTag, + Value: "tag1*", + }, + }, + want: []*model.Resource{ + { + Metadata: &model.ResourceMetadata{ + Repository: &model.Repository{Name: "test/b2"}, + Vtags: []string{"tag11", "tag13"}, + }, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var resources, err = adapter.FetchImages(tt.namespaces, tt.filters) + if tt.wantErr { + require.Len(t, resources, 0) + require.NotNil(t, err) + } else { + require.Equal(t, len(tt.want), len(resources)) + for i, resource := range resources { + require.NotNil(t, resource.Metadata) + assert.Equal(t, tt.want[i].Metadata.Namespace, resource.Metadata.Namespace) + assert.Equal(t, tt.want[i].Metadata.Repository, resource.Metadata.Repository) + assert.Equal(t, tt.want[i].Metadata.Vtags, resource.Metadata.Vtags) + } + } + }) + } +}