From 65152d6883a62fa570e37405c35a9b65a497bef3 Mon Sep 17 00:00:00 2001 From: Tao Zou Date: Wed, 30 Oct 2024 15:38:57 +0800 Subject: [PATCH] Add ut for services/common --- pkg/nsx/services/common/compare_test.go | 98 +++++++++ pkg/nsx/services/common/store_test.go | 272 ++++++++++++++++++++++++ pkg/nsx/services/common/wrap_test.go | 259 ++++++++++++++++++++++ 3 files changed, 629 insertions(+) create mode 100644 pkg/nsx/services/common/compare_test.go create mode 100644 pkg/nsx/services/common/wrap_test.go diff --git a/pkg/nsx/services/common/compare_test.go b/pkg/nsx/services/common/compare_test.go new file mode 100644 index 000000000..c18e95d83 --- /dev/null +++ b/pkg/nsx/services/common/compare_test.go @@ -0,0 +1,98 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" +) + +type mockComparable struct { + key string + value data.DataValue +} + +func (m mockComparable) Key() string { + return m.key +} + +func (m mockComparable) Value() data.DataValue { + return m.value +} + +func TestCompareResources(t *testing.T) { + tests := []struct { + name string + existing []Comparable + expected []Comparable + wantChanged []Comparable + wantStale []Comparable + }{ + { + name: "No changes", + existing: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1")}, + mockComparable{key: "key2", value: data.NewStringValue("value2")}, + }, + expected: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1")}, + mockComparable{key: "key2", value: data.NewStringValue("value2")}, + }, + wantChanged: []Comparable{}, + wantStale: []Comparable{}, + }, + { + name: "Changed resources", + existing: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1")}, + mockComparable{key: "key2", value: data.NewStringValue("value2")}, + }, + expected: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1")}, + mockComparable{key: "key2", value: data.NewStringValue("value2_changed")}, + }, + wantChanged: []Comparable{ + mockComparable{key: "key2", value: data.NewStringValue("value2_changed")}, + }, + wantStale: []Comparable{}, + }, + { + name: "Stale resources", + existing: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1")}, + mockComparable{key: "key2", value: data.NewStringValue("value2")}, + }, + expected: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1")}, + }, + wantChanged: []Comparable{}, + wantStale: []Comparable{ + mockComparable{key: "key2", value: data.NewStringValue("value2")}, + }, + }, + { + name: "Changed and stale resources", + existing: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1")}, + mockComparable{key: "key2", value: data.NewStringValue("value2")}, + }, + expected: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1_changed")}, + }, + wantChanged: []Comparable{ + mockComparable{key: "key1", value: data.NewStringValue("value1_changed")}, + }, + wantStale: []Comparable{ + mockComparable{key: "key2", value: data.NewStringValue("value2")}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotChanged, gotStale := CompareResources(tt.existing, tt.expected) + assert.Equal(t, tt.wantChanged, gotChanged) + assert.Equal(t, tt.wantStale, gotStale) + }) + } +} diff --git a/pkg/nsx/services/common/store_test.go b/pkg/nsx/services/common/store_test.go index f7695f413..8a769c040 100644 --- a/pkg/nsx/services/common/store_test.go +++ b/pkg/nsx/services/common/store_test.go @@ -1,16 +1,20 @@ package common import ( + "fmt" "reflect" "sync" "testing" "github.com/agiledragon/gomonkey/v2" + "github.com/openlyinc/pointy" "github.com/stretchr/testify/assert" "github.com/vmware/vsphere-automation-sdk-go/lib/vapi/std/errors" "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + mp_model "github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp/nsx/model" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/cache" "github.com/vmware-tanzu/nsx-operator/pkg/config" @@ -187,3 +191,271 @@ func Test_InitializeResourceStore(t *testing.T) { assert.Empty(t, fatalErrors) assert.Equal(t, []string{"11111"}, ruleStore.ListKeys()) } + +func TestService_SearchResource(t *testing.T) { + type args struct { + resourceTypeValue string + queryParam string + store Store + filter Filter + } + tests := []struct { + name string + args args + want uint64 + wantErr bool + }{ + { + name: "Policy API with results", + args: args{ + resourceTypeValue: "testResourceType", + queryParam: "testQueryParam", + store: &fakeStore{ + isPolicyAPI: true, + }, + filter: nil, + }, + want: 1, + wantErr: false, + }, + { + name: "MP API with results", + args: args{ + resourceTypeValue: "testResourceType", + queryParam: "testQueryParam", + store: &fakeStore{ + isPolicyAPI: false, + }, + filter: nil, + }, + want: 1, + wantErr: false, + }, + { + name: "Policy API with error", + args: args{ + resourceTypeValue: "testResourceType", + queryParam: "testQueryParam", + store: &fakeStore{ + isPolicyAPI: true, + transError: true, + }, + filter: nil, + }, + want: 0, + wantErr: true, + }, + { + name: "MP API with error", + args: args{ + resourceTypeValue: "testResourceType", + queryParam: "testQueryParam", + store: &fakeStore{ + isPolicyAPI: false, + transError: true, + }, + filter: nil, + }, + want: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + service := &Service{ + NSXClient: &nsx.Client{ + QueryClient: &fakeQueryClient{}, + MPQueryClient: &fakeMPQueryClient{}, + }, + } + got, err := service.SearchResource(tt.args.resourceTypeValue, tt.args.queryParam, tt.args.store, tt.args.filter) + if (err != nil) != tt.wantErr { + t.Errorf("SearchResource() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SearchResource() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_containsTagScope(t *testing.T) { + tests := []struct { + name string + tags []model.Tag + scopes []string + want bool + }{ + { + name: "Tag with matching scope", + tags: []model.Tag{ + {Scope: pointy.String("scope1")}, + {Scope: pointy.String("scope2")}, + }, + scopes: []string{"scope1"}, + want: true, + }, + { + name: "Tag without matching scope", + tags: []model.Tag{ + {Scope: pointy.String("scope1")}, + {Scope: pointy.String("scope2")}, + }, + scopes: []string{"scope3"}, + want: false, + }, + { + name: "Empty tags", + tags: []model.Tag{}, + scopes: []string{"scope1"}, + want: false, + }, + { + name: "Empty scopes", + tags: []model.Tag{ + {Scope: pointy.String("scope1")}, + {Scope: pointy.String("scope2")}, + }, + scopes: []string{}, + want: false, + }, + { + name: "Nil scope in tag", + tags: []model.Tag{ + {Scope: nil}, + {Scope: pointy.String("scope2")}, + }, + scopes: []string{"scope1"}, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := containsTagScope(tt.tags, tt.scopes...); got != tt.want { + t.Errorf("containsTagScope() = %v, want %v", got, tt.want) + } + }) + } +} + +type fakeStore struct { + isPolicyAPI bool + transError bool +} + +func (f *fakeStore) TransResourceToStore(obj *data.StructValue) error { + if f.transError { + return fmt.Errorf("transformation error") + } + return nil +} + +func (f *fakeStore) ListIndexFuncValues(key string) sets.Set[string] { + return sets.New[string]() +} + +func (f *fakeStore) Apply(obj interface{}) error { + return nil +} + +func (f *fakeStore) IsPolicyAPI() bool { + return f.isPolicyAPI +} + +type fakeMPQueryClient struct{} + +func (_ *fakeMPQueryClient) List(_ string, _ *string, _ *string, _ *int64, _ *bool, _ *string) (mp_model.SearchResponse, error) { + cursor := "2" + resultCount := int64(2) + return mp_model.SearchResponse{ + Results: []*data.StructValue{{}}, + Cursor: &cursor, ResultCount: &resultCount, + }, nil +} + +func Test_formatTagParamScope(t *testing.T) { + tests := []struct { + name string + paramType string + value string + want string + }{ + { + name: "Simple value", + paramType: "tags.scope", + value: "simpleValue", + want: "tags.scope:simpleValue", + }, + { + name: "Value with slash", + paramType: "tags.scope", + value: "value/with/slash", + want: "tags.scope:value\\/with\\/slash", + }, + { + name: "Empty value", + paramType: "tags.scope", + value: "", + want: "tags.scope:", + }, + { + name: "Value with multiple slashes", + paramType: "tags.scope", + value: "value/with/multiple/slashes", + want: "tags.scope:value\\/with\\/multiple\\/slashes", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := formatTagParamScope(tt.paramType, tt.value); got != tt.want { + t.Errorf("formatTagParamScope() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_formatTagParamTag(t *testing.T) { + tests := []struct { + name string + paramType string + value string + want string + }{ + { + name: "Simple value", + paramType: "tags.tag", + value: "simpleValue", + want: "tags.tag:simpleValue", + }, + { + name: "Value with colon", + paramType: "tags.tag", + value: "value:with:colon", + want: "tags.tag:value\\:with\\:colon", + }, + { + name: "Empty value", + paramType: "tags.tag", + value: "", + want: "tags.tag:", + }, + { + name: "Value with multiple colons", + paramType: "tags.tag", + value: "value:with:multiple:colons", + want: "tags.tag:value\\:with\\:multiple\\:colons", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := formatTagParamTag(tt.paramType, tt.value); got != tt.want { + t.Errorf("formatTagParamTag() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/nsx/services/common/wrap_test.go b/pkg/nsx/services/common/wrap_test.go new file mode 100644 index 000000000..232abd08d --- /dev/null +++ b/pkg/nsx/services/common/wrap_test.go @@ -0,0 +1,259 @@ +package common + +import ( + "fmt" + "testing" + + "github.com/openlyinc/pointy" + "github.com/stretchr/testify/assert" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +func TestService_WrapAttachment(t *testing.T) { + service := &Service{} + attachmentId := "attachment-id" + markedForDelete := true + profile := "VpcConnectivityProfile" + attachment := &model.VpcAttachment{ + Id: &attachmentId, + MarkedForDelete: &markedForDelete, + VpcConnectivityProfile: &profile, + } + + tests := []struct { + name string + attachment *model.VpcAttachment + want []*data.StructValue + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Valid attachment", + attachment: attachment, + want: []*data.StructValue{ + {}, + }, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := service.WrapAttachment(tt.attachment) + if !tt.wantErr(t, err, fmt.Sprintf("WrapAttachment(%v)", tt.attachment)) { + return + } + assert.NotNil(t, got) + if tt.attachment != nil { + assert.Equal(t, *tt.attachment.Id, attachmentId) + } + }) + } +} + +func TestService_WrapLBS(t *testing.T) { + service := &Service{} + lbsId := "lbs-id" + markedForDelete := true + lbs := &model.LBService{ + Id: &lbsId, + MarkedForDelete: &markedForDelete, + } + + tests := []struct { + name string + lbs *model.LBService + want []*data.StructValue + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Valid LBService", + lbs: lbs, + want: []*data.StructValue{ + {}, + }, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := service.WrapLBS(tt.lbs) + if !tt.wantErr(t, err, fmt.Sprintf("WrapLBS(%v)", tt.lbs)) { + return + } + assert.NotNil(t, got) + if tt.lbs != nil { + assert.Equal(t, *tt.lbs.Id, lbsId) + } + }) + } +} + +func TestService_WrapVPC(t *testing.T) { + service := &Service{} + vpcId := "vpc-id" + markedForDelete := true + vpc := &model.Vpc{ + Id: &vpcId, + MarkedForDelete: &markedForDelete, + } + + tests := []struct { + name string + vpc *model.Vpc + want []*data.StructValue + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Valid VPC", + vpc: vpc, + want: []*data.StructValue{ + {}, + }, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := service.WrapVPC(tt.vpc) + if !tt.wantErr(t, err, fmt.Sprintf("WrapVPC(%v)", tt.vpc)) { + return + } + assert.NotNil(t, got) + if tt.vpc != nil { + assert.Equal(t, *tt.vpc.Id, vpcId) + } + }) + } +} + +func TestService_WrapInfra(t *testing.T) { + service := &Service{} + children := []*data.StructValue{ + {}, + {}, + } + + tests := []struct { + name string + children []*data.StructValue + want *model.Infra + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Valid children", + children: children, + want: &model.Infra{ + Children: children, + ResourceType: pointy.String(ResourceTypeInfra), + }, + wantErr: assert.NoError, + }, + { + name: "Nil children", + children: nil, + want: &model.Infra{ + Children: nil, + ResourceType: pointy.String(ResourceTypeInfra), + }, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := service.WrapInfra(tt.children) + if !tt.wantErr(t, err, fmt.Sprintf("WrapInfra(%v)", tt.children)) { + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestService_WrapOrgRoot(t *testing.T) { + service := &Service{} + children := []*data.StructValue{ + {}, + {}, + } + + tests := []struct { + name string + children []*data.StructValue + want *model.OrgRoot + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Valid children", + children: children, + want: &model.OrgRoot{ + Children: children, + ResourceType: pointy.String(ResourceTypeOrgRoot), + }, + wantErr: assert.NoError, + }, + { + name: "Nil children", + children: nil, + want: &model.OrgRoot{ + Children: nil, + ResourceType: pointy.String(ResourceTypeOrgRoot), + }, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := service.WrapOrgRoot(tt.children) + if !tt.wantErr(t, err, fmt.Sprintf("WrapOrgRoot(%v)", tt.children)) { + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestService_WrapOrg(t *testing.T) { + service := &Service{} + org := "org-id" + + var projectChildren []*data.StructValue + childrenProject, _ := service.WrapProject("nsxtProject", projectChildren) + + tests := []struct { + name string + org string + children []*data.StructValue + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Valid org and children", + org: org, + children: childrenProject, + wantErr: assert.NoError, + }, + { + name: "Nil children", + org: org, + children: nil, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := service.WrapOrg(tt.org, tt.children) + if !tt.wantErr(t, err, fmt.Sprintf("WrapOrg(%v, %v)", tt.org, tt.children)) { + return + } + assert.NotNil(t, got) + if tt.children != nil { + assert.Equal(t, len(tt.children), len(got)) + } + }) + } +}