package controller import ( "fmt" "reflect" "testing" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" fakedisco "k8s.io/client-go/discovery/fake" "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/rest" testcore "k8s.io/client-go/testing" "github.com/argoproj/argo-cd/common" "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" . "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/reposerver/apiclient" "github.com/argoproj/argo-cd/test" "github.com/argoproj/argo-cd/util/kube" "github.com/argoproj/argo-cd/util/kube/kubetest" ) func newTestSyncCtx(resources ...*v1.APIResourceList) *syncContext { fakeDisco := &fakedisco.FakeDiscovery{Fake: &testcore.Fake{}} fakeDisco.Resources = append(resources, &v1.APIResourceList{ GroupVersion: "v1", APIResources: []v1.APIResource{ {Kind: "Pod", Group: "", Version: "v1", Namespaced: true}, {Kind: "Service", Group: "", Version: "v1", Namespaced: true}, }, }, &v1.APIResourceList{ GroupVersion: "apps/v1", APIResources: []v1.APIResource{ {Kind: "Deployment", Group: "apps", Version: "v1", Namespaced: true}, }, }) sc := syncContext{ config: &rest.Config{}, namespace: test.FakeArgoCDNamespace, server: test.FakeClusterURL, syncRes: &v1alpha1.SyncOperationResult{ Revision: "FooBarBaz", }, syncOp: &v1alpha1.SyncOperation{ Prune: true, SyncStrategy: &v1alpha1.SyncStrategy{ Apply: &v1alpha1.SyncStrategyApply{}, }, }, proj: &v1alpha1.AppProject{ ObjectMeta: metav1.ObjectMeta{ Name: "test", }, Spec: v1alpha1.AppProjectSpec{ Destinations: []v1alpha1.ApplicationDestination{{ Server: test.FakeClusterURL, Namespace: test.FakeArgoCDNamespace, }}, ClusterResourceWhitelist: []v1.GroupKind{ {Group: "*", Kind: "*"}, }, }, }, opState: &v1alpha1.OperationState{}, disco: fakeDisco, log: log.WithFields(log.Fields{"application": "fake-app"}), } sc.kubectl = &kubetest.MockKubectlCmd{} return &sc } func newManagedResource(live *unstructured.Unstructured) managedResource { return managedResource{ Live: live, Group: live.GroupVersionKind().Group, Version: live.GroupVersionKind().Version, Kind: live.GroupVersionKind().Kind, Namespace: live.GetNamespace(), Name: live.GetName(), } } func TestSyncNotPermittedNamespace(t *testing.T) { syncCtx := newTestSyncCtx() targetPod := test.NewPod() targetPod.SetNamespace("kube-system") syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: nil, Target: targetPod, }, { Live: nil, Target: test.NewService(), }}, } syncCtx.sync() assert.Equal(t, v1alpha1.OperationFailed, syncCtx.opState.Phase) assert.Contains(t, syncCtx.syncRes.Resources[0].Message, "not permitted in project") } func TestSyncCreateInSortedOrder(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: nil, Target: test.NewPod(), }, { Live: nil, Target: test.NewService(), }}, } syncCtx.sync() assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase) assert.Len(t, syncCtx.syncRes.Resources, 2) for i := range syncCtx.syncRes.Resources { result := syncCtx.syncRes.Resources[i] if result.Kind == "Pod" { assert.Equal(t, v1alpha1.ResultCodeSynced, result.Status) assert.Equal(t, "", result.Message) } else if result.Kind == "Service" { assert.Equal(t, "", result.Message) } else { t.Error("Resource isn't a pod or a service") } } } func TestSyncCreateNotWhitelistedClusterResources(t *testing.T) { syncCtx := newTestSyncCtx(&v1.APIResourceList{ GroupVersion: v1alpha1.SchemeGroupVersion.String(), APIResources: []v1.APIResource{ {Name: "workflows", Namespaced: false, Kind: "Workflow", Group: "argoproj.io"}, {Name: "application", Namespaced: false, Kind: "Application", Group: "argoproj.io"}, }, }, &v1.APIResourceList{ GroupVersion: "rbac.authorization.k8s.io/v1", APIResources: []v1.APIResource{ {Name: "clusterroles", Namespaced: false, Kind: "ClusterRole", Group: "rbac.authorization.k8s.io"}, }, }) syncCtx.proj.Spec.ClusterResourceWhitelist = []v1.GroupKind{ {Group: "argoproj.io", Kind: "*"}, } syncCtx.kubectl = &kubetest.MockKubectlCmd{} syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: nil, Target: kube.MustToUnstructured(&rbacv1.ClusterRole{ TypeMeta: metav1.TypeMeta{Kind: "ClusterRole", APIVersion: "rbac.authorization.k8s.io/v1"}, ObjectMeta: metav1.ObjectMeta{Name: "argo-ui-cluster-role"}}), }}, } syncCtx.sync() assert.Len(t, syncCtx.syncRes.Resources, 1) result := syncCtx.syncRes.Resources[0] assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status) assert.Contains(t, result.Message, "not permitted in project") } func TestSyncBlacklistedNamespacedResources(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.proj.Spec.NamespaceResourceBlacklist = []v1.GroupKind{ {Group: "*", Kind: "Deployment"}, } syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: nil, Target: test.NewDeployment(), }}, } syncCtx.sync() assert.Len(t, syncCtx.syncRes.Resources, 1) result := syncCtx.syncRes.Resources[0] assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status) assert.Contains(t, result.Message, "not permitted in project") } func TestSyncSuccessfully(t *testing.T) { syncCtx := newTestSyncCtx() pod := test.NewPod() pod.SetNamespace(test.FakeArgoCDNamespace) syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: nil, Target: test.NewService(), }, { Live: pod, Target: nil, }}, } syncCtx.sync() assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase) assert.Len(t, syncCtx.syncRes.Resources, 2) for i := range syncCtx.syncRes.Resources { result := syncCtx.syncRes.Resources[i] if result.Kind == "Pod" { assert.Equal(t, v1alpha1.ResultCodePruned, result.Status) assert.Equal(t, "pruned", result.Message) } else if result.Kind == "Service" { assert.Equal(t, v1alpha1.ResultCodeSynced, result.Status) assert.Equal(t, "", result.Message) } else { t.Error("Resource isn't a pod or a service") } } } func TestSyncDeleteSuccessfully(t *testing.T) { syncCtx := newTestSyncCtx() svc := test.NewService() svc.SetNamespace(test.FakeArgoCDNamespace) pod := test.NewPod() pod.SetNamespace(test.FakeArgoCDNamespace) syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: svc, Target: nil, }, { Live: pod, Target: nil, }}, } syncCtx.sync() assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase) for i := range syncCtx.syncRes.Resources { result := syncCtx.syncRes.Resources[i] if result.Kind == "Pod" { assert.Equal(t, v1alpha1.ResultCodePruned, result.Status) assert.Equal(t, "pruned", result.Message) } else if result.Kind == "Service" { assert.Equal(t, v1alpha1.ResultCodePruned, result.Status) assert.Equal(t, "pruned", result.Message) } else { t.Error("Resource isn't a pod or a service") } } } func TestSyncCreateFailure(t *testing.T) { syncCtx := newTestSyncCtx() testSvc := test.NewService() syncCtx.kubectl = &kubetest.MockKubectlCmd{ Commands: map[string]kubetest.KubectlOutput{ testSvc.GetName(): { Output: "", Err: fmt.Errorf("foo"), }, }, } syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: nil, Target: testSvc, }}, } syncCtx.sync() assert.Len(t, syncCtx.syncRes.Resources, 1) result := syncCtx.syncRes.Resources[0] assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status) assert.Equal(t, "foo", result.Message) } func TestSyncPruneFailure(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.kubectl = &kubetest.MockKubectlCmd{ Commands: map[string]kubetest.KubectlOutput{ "test-service": { Output: "", Err: fmt.Errorf("foo"), }, }, } testSvc := test.NewService() testSvc.SetName("test-service") testSvc.SetNamespace(test.FakeArgoCDNamespace) syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{ Live: testSvc, Target: nil, }}, } syncCtx.sync() assert.Equal(t, v1alpha1.OperationFailed, syncCtx.opState.Phase) assert.Len(t, syncCtx.syncRes.Resources, 1) result := syncCtx.syncRes.Resources[0] assert.Equal(t, v1alpha1.ResultCodeSyncFailed, result.Status) assert.Equal(t, "foo", result.Message) } func TestDontSyncOrPruneHooks(t *testing.T) { syncCtx := newTestSyncCtx() targetPod := test.NewPod() targetPod.SetName("dont-create-me") targetPod.SetAnnotations(map[string]string{common.AnnotationKeyHook: "PreSync"}) liveSvc := test.NewService() liveSvc.SetName("dont-prune-me") liveSvc.SetNamespace(test.FakeArgoCDNamespace) liveSvc.SetAnnotations(map[string]string{common.AnnotationKeyHook: "PreSync"}) syncCtx.compareResult = &comparisonResult{ hooks: []*unstructured.Unstructured{targetPod, liveSvc}, } syncCtx.sync() assert.Len(t, syncCtx.syncRes.Resources, 0) syncCtx.sync() assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase) } // make sure that we do not prune resources with Prune=false func TestDontPrunePruneFalse(t *testing.T) { syncCtx := newTestSyncCtx() pod := test.NewPod() pod.SetAnnotations(map[string]string{common.AnnotationSyncOptions: "Prune=false"}) pod.SetNamespace(test.FakeArgoCDNamespace) syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Live: pod}}} syncCtx.sync() assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase) assert.Len(t, syncCtx.syncRes.Resources, 1) assert.Equal(t, v1alpha1.ResultCodePruneSkipped, syncCtx.syncRes.Resources[0].Status) assert.Equal(t, "ignored (no prune)", syncCtx.syncRes.Resources[0].Message) syncCtx.sync() assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase) } // make sure Validate=false means we don't validate func TestSyncOptionValidate(t *testing.T) { tests := []struct { name string annotationVal string want bool }{ {"Empty", "", true}, {"True", "Validate=true", true}, {"False", "Validate=false", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { syncCtx := newTestSyncCtx() pod := test.NewPod() pod.SetAnnotations(map[string]string{common.AnnotationSyncOptions: tt.annotationVal}) pod.SetNamespace(test.FakeArgoCDNamespace) syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Target: pod, Live: pod}}} syncCtx.sync() kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd) assert.Equal(t, tt.want, kubectl.LastValidate) }) } } func TestSelectiveSyncOnly(t *testing.T) { syncCtx := newTestSyncCtx() pod1 := test.NewPod() pod1.SetName("pod-1") pod2 := test.NewPod() pod2.SetName("pod-2") syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{Target: pod1}}, } syncCtx.syncResources = []v1alpha1.SyncOperationResource{{Kind: "Pod", Name: "pod-1"}} tasks, successful := syncCtx.getSyncTasks() assert.True(t, successful) assert.Len(t, tasks, 1) assert.Equal(t, "pod-1", tasks[0].name()) } func TestUnnamedHooksGetUniqueNames(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.syncOp.SyncStrategy.Apply = nil pod := test.NewPod() pod.SetName("") pod.SetAnnotations(map[string]string{common.AnnotationKeyHook: "PreSync,PostSync"}) syncCtx.compareResult = &comparisonResult{hooks: []*unstructured.Unstructured{pod}} tasks, successful := syncCtx.getSyncTasks() assert.True(t, successful) assert.Len(t, tasks, 2) assert.Contains(t, tasks[0].name(), "foobarb-presync-") assert.Contains(t, tasks[1].name(), "foobarb-postsync-") assert.Equal(t, "", pod.GetName()) } func TestManagedResourceAreNotNamed(t *testing.T) { syncCtx := newTestSyncCtx() pod := test.NewPod() pod.SetName("") syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Target: pod}}} tasks, successful := syncCtx.getSyncTasks() assert.True(t, successful) assert.Len(t, tasks, 1) assert.Equal(t, "", tasks[0].name()) assert.Equal(t, "", pod.GetName()) } func TestDeDupingTasks(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.syncOp.SyncStrategy.Apply = nil pod := test.NewPod() pod.SetAnnotations(map[string]string{common.AnnotationKeyHook: "Sync"}) syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{Target: pod}}, hooks: []*unstructured.Unstructured{pod}, } tasks, successful := syncCtx.getSyncTasks() assert.True(t, successful) assert.Len(t, tasks, 1) } func TestObjectsGetANamespace(t *testing.T) { syncCtx := newTestSyncCtx() pod := test.NewPod() syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Target: pod}}} tasks, successful := syncCtx.getSyncTasks() assert.True(t, successful) assert.Len(t, tasks, 1) assert.Equal(t, test.FakeArgoCDNamespace, tasks[0].namespace()) assert.Equal(t, "", pod.GetNamespace()) } func TestPersistRevisionHistory(t *testing.T) { app := newFakeApp() app.Status.OperationState = nil app.Status.History = nil defaultProject := &v1alpha1.AppProject{ ObjectMeta: v1.ObjectMeta{ Namespace: test.FakeArgoCDNamespace, Name: "default", }, } data := fakeData{ apps: []runtime.Object{app, defaultProject}, manifestResponse: &apiclient.ManifestResponse{ Manifests: []string{}, Namespace: test.FakeDestNamespace, Server: test.FakeClusterURL, Revision: "abc123", }, managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), } ctrl := newFakeController(&data) // Sync with source unspecified opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{ Sync: &v1alpha1.SyncOperation{}, }} ctrl.appStateManager.SyncAppState(app, opState) // Ensure we record spec.source into sync result assert.Equal(t, app.Spec.Source, opState.SyncResult.Source) updatedApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(app.Name, v1.GetOptions{}) assert.Nil(t, err) assert.Equal(t, 1, len(updatedApp.Status.History)) assert.Equal(t, app.Spec.Source, updatedApp.Status.History[0].Source) assert.Equal(t, "abc123", updatedApp.Status.History[0].Revision) } func TestPersistRevisionHistoryRollback(t *testing.T) { app := newFakeApp() app.Status.OperationState = nil app.Status.History = nil defaultProject := &v1alpha1.AppProject{ ObjectMeta: v1.ObjectMeta{ Namespace: test.FakeArgoCDNamespace, Name: "default", }, } data := fakeData{ apps: []runtime.Object{app, defaultProject}, manifestResponse: &apiclient.ManifestResponse{ Manifests: []string{}, Namespace: test.FakeDestNamespace, Server: test.FakeClusterURL, Revision: "abc123", }, managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured), } ctrl := newFakeController(&data) // Sync with source specified source := v1alpha1.ApplicationSource{ Helm: &v1alpha1.ApplicationSourceHelm{ Parameters: []v1alpha1.HelmParameter{ { Name: "test", Value: "123", }, }, }, } opState := &v1alpha1.OperationState{Operation: v1alpha1.Operation{ Sync: &v1alpha1.SyncOperation{ Source: &source, }, }} ctrl.appStateManager.SyncAppState(app, opState) // Ensure we record opState's source into sync result assert.Equal(t, source, opState.SyncResult.Source) updatedApp, err := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace).Get(app.Name, v1.GetOptions{}) assert.Nil(t, err) assert.Equal(t, 1, len(updatedApp.Status.History)) assert.Equal(t, source, updatedApp.Status.History[0].Source) assert.Equal(t, "abc123", updatedApp.Status.History[0].Revision) } func TestSyncFailureHookWithSuccessfulSync(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.syncOp.SyncStrategy.Apply = nil syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{Target: test.NewPod()}}, hooks: []*unstructured.Unstructured{test.NewHook(HookTypeSyncFail)}, } syncCtx.sync() assert.Equal(t, OperationSucceeded, syncCtx.opState.Phase) // only one result, we did not run the failure failureHook assert.Len(t, syncCtx.syncRes.Resources, 1) } func TestSyncFailureHookWithFailedSync(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.syncOp.SyncStrategy.Apply = nil pod := test.NewPod() syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{Target: pod}}, hooks: []*unstructured.Unstructured{test.NewHook(HookTypeSyncFail)}, } syncCtx.kubectl = &kubetest.MockKubectlCmd{ Commands: map[string]kubetest.KubectlOutput{pod.GetName(): {Err: fmt.Errorf("")}}, } syncCtx.sync() syncCtx.sync() assert.Equal(t, OperationFailed, syncCtx.opState.Phase) assert.Len(t, syncCtx.syncRes.Resources, 2) } func TestBeforeHookCreation(t *testing.T) { syncCtx := newTestSyncCtx() syncCtx.syncOp.SyncStrategy.Apply = nil hook := test.Annotate(test.Annotate(test.NewPod(), common.AnnotationKeyHook, "Sync"), common.AnnotationKeyHookDeletePolicy, "BeforeHookCreation") hook.SetNamespace(test.FakeArgoCDNamespace) syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{newManagedResource(hook)}, hooks: []*unstructured.Unstructured{hook}, } syncCtx.dynamicIf = fake.NewSimpleDynamicClient(runtime.NewScheme()) syncCtx.sync() assert.Len(t, syncCtx.syncRes.Resources, 1) assert.Empty(t, syncCtx.syncRes.Resources[0].Message) } func TestRunSyncFailHooksFailed(t *testing.T) { // Tests that other SyncFail Hooks run even if one of them fail. syncCtx := newTestSyncCtx() syncCtx.syncOp.SyncStrategy.Apply = nil pod := test.NewPod() successfulSyncFailHook := test.NewHook(HookTypeSyncFail) successfulSyncFailHook.SetName("successful-sync-fail-hook") failedSyncFailHook := test.NewHook(HookTypeSyncFail) failedSyncFailHook.SetName("failed-sync-fail-hook") syncCtx.compareResult = &comparisonResult{ managedResources: []managedResource{{Target: pod}}, hooks: []*unstructured.Unstructured{successfulSyncFailHook, failedSyncFailHook}, } syncCtx.kubectl = &kubetest.MockKubectlCmd{ Commands: map[string]kubetest.KubectlOutput{ // Fail operation pod.GetName(): {Err: fmt.Errorf("")}, // Fail a single SyncFail hook failedSyncFailHook.GetName(): {Err: fmt.Errorf("")}}, } syncCtx.sync() syncCtx.sync() fmt.Println(syncCtx.syncRes.Resources) fmt.Println(syncCtx.opState.Phase) // Operation as a whole should fail assert.Equal(t, OperationFailed, syncCtx.opState.Phase) // failedSyncFailHook should fail assert.Equal(t, OperationFailed, syncCtx.syncRes.Resources[1].HookPhase) assert.Equal(t, ResultCodeSyncFailed, syncCtx.syncRes.Resources[1].Status) // successfulSyncFailHook should be synced running (it is an nginx pod) assert.Equal(t, OperationRunning, syncCtx.syncRes.Resources[2].HookPhase) assert.Equal(t, ResultCodeSynced, syncCtx.syncRes.Resources[2].Status) } func Test_syncContext_isSelectiveSync(t *testing.T) { type fields struct { compareResult *comparisonResult syncResources []SyncOperationResource } oneSyncResource := []SyncOperationResource{{}} oneResource := func(group, kind, name string, hook bool) *comparisonResult { return &comparisonResult{resources: []v1alpha1.ResourceStatus{{Group: group, Kind: kind, Name: name, Hook: hook}}} } tests := []struct { name string fields fields want bool }{ {"Empty", fields{}, false}, {"OneCompareResult", fields{oneResource("", "", "", false), []SyncOperationResource{}}, true}, {"OneSyncResource", fields{&comparisonResult{}, oneSyncResource}, true}, {"Equal", fields{oneResource("", "", "", false), oneSyncResource}, false}, {"EqualOutOfOrder", fields{&comparisonResult{resources: []v1alpha1.ResourceStatus{{Group: "a"}, {Group: "b"}}}, []SyncOperationResource{{Group: "b"}, {Group: "a"}}}, false}, {"KindDifferent", fields{oneResource("foo", "", "", false), oneSyncResource}, true}, {"GroupDifferent", fields{oneResource("", "foo", "", false), oneSyncResource}, true}, {"NameDifferent", fields{oneResource("", "", "foo", false), oneSyncResource}, true}, {"HookIgnored", fields{oneResource("", "", "", true), []SyncOperationResource{}}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sc := &syncContext{ compareResult: tt.fields.compareResult, syncResources: tt.fields.syncResources, } if got := sc.isSelectiveSync(); got != tt.want { t.Errorf("syncContext.isSelectiveSync() = %v, want %v", got, tt.want) } }) } } func Test_syncContext_liveObj(t *testing.T) { type fields struct { compareResult *comparisonResult } type args struct { obj *unstructured.Unstructured } obj := test.NewPod() obj.SetNamespace("my-ns") found := test.NewPod() tests := []struct { name string fields fields args args want *unstructured.Unstructured }{ {"None", fields{compareResult: &comparisonResult{managedResources: []managedResource{}}}, args{obj: &unstructured.Unstructured{}}, nil}, {"Found", fields{compareResult: &comparisonResult{managedResources: []managedResource{{Group: obj.GroupVersionKind().Group, Kind: obj.GetKind(), Namespace: obj.GetNamespace(), Name: obj.GetName(), Live: found}}}}, args{obj: obj}, found}, {"EmptyNamespace", fields{compareResult: &comparisonResult{managedResources: []managedResource{{Group: obj.GroupVersionKind().Group, Kind: obj.GetKind(), Name: obj.GetName(), Live: found}}}}, args{obj: obj}, found}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sc := &syncContext{ compareResult: tt.fields.compareResult, } if got := sc.liveObj(tt.args.obj); !reflect.DeepEqual(got, tt.want) { t.Errorf("syncContext.liveObj() = %v, want %v", got, tt.want) } }) } } func Test_syncContext_hasCRDOfGroupKind(t *testing.T) { // target assert.False(t, (&syncContext{compareResult: &comparisonResult{managedResources: []managedResource{{Target: test.NewCRD()}}}}).hasCRDOfGroupKind("", "")) assert.True(t, (&syncContext{compareResult: &comparisonResult{managedResources: []managedResource{{Target: test.NewCRD()}}}}).hasCRDOfGroupKind("argoproj.io", "TestCrd")) // hook assert.False(t, (&syncContext{compareResult: &comparisonResult{hooks: []*unstructured.Unstructured{test.NewCRD()}}}).hasCRDOfGroupKind("", "")) assert.True(t, (&syncContext{compareResult: &comparisonResult{hooks: []*unstructured.Unstructured{test.NewCRD()}}}).hasCRDOfGroupKind("argoproj.io", "TestCrd")) }