sigs.k8s.io/cluster-api-provider-azure@v1.17.0/pkg/mutators/mutator_test.go (about)

     1  /*
     2  Copyright 2024 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mutators
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"testing"
    23  
    24  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    25  	"github.com/Azure/azure-service-operator/v2/pkg/common/annotations"
    26  	. "github.com/onsi/gomega"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    30  )
    31  
    32  func TestApplyMutators(t *testing.T) {
    33  	ctx := context.Background()
    34  
    35  	tests := []struct {
    36  		name        string
    37  		resources   []runtime.RawExtension
    38  		mutators    []ResourcesMutator
    39  		expected    []*unstructured.Unstructured
    40  		expectedErr error
    41  	}{
    42  		{
    43  			name: "no mutators",
    44  			resources: []runtime.RawExtension{
    45  				{Raw: []byte(`{"apiVersion": "v1", "kind": "SomeObject"}`)},
    46  			},
    47  			expected: []*unstructured.Unstructured{
    48  				{Object: map[string]interface{}{"apiVersion": "v1", "kind": "SomeObject"}},
    49  			},
    50  		},
    51  		{
    52  			name: "mutators apply in order",
    53  			resources: []runtime.RawExtension{
    54  				{Raw: []byte(`{"apiVersion": "v1", "kind": "SomeObject"}`)},
    55  			},
    56  			mutators: []ResourcesMutator{
    57  				func(_ context.Context, us []*unstructured.Unstructured) error {
    58  					us[0].Object["f1"] = "3"
    59  					us[0].Object["f2"] = "3"
    60  					us[0].Object["f3"] = "3"
    61  					return nil
    62  				},
    63  				func(_ context.Context, us []*unstructured.Unstructured) error {
    64  					us[0].Object["f1"] = "2"
    65  					us[0].Object["f2"] = "2"
    66  					return nil
    67  				},
    68  				func(_ context.Context, us []*unstructured.Unstructured) error {
    69  					us[0].Object["f1"] = "1"
    70  					return nil
    71  				},
    72  			},
    73  			expected: []*unstructured.Unstructured{
    74  				{
    75  					Object: map[string]interface{}{
    76  						"apiVersion": "v1",
    77  						"kind":       "SomeObject",
    78  						"f1":         "1",
    79  						"f2":         "2",
    80  						"f3":         "3",
    81  					},
    82  				},
    83  			},
    84  		},
    85  		{
    86  			name:      "error",
    87  			resources: []runtime.RawExtension{},
    88  			mutators: []ResourcesMutator{
    89  				func(_ context.Context, us []*unstructured.Unstructured) error {
    90  					return errors.New("mutator err")
    91  				},
    92  			},
    93  			expectedErr: errors.New("mutator err"),
    94  		},
    95  		{
    96  			name:      "incompatible is terminal",
    97  			resources: []runtime.RawExtension{},
    98  			mutators: []ResourcesMutator{
    99  				func(_ context.Context, us []*unstructured.Unstructured) error {
   100  					return Incompatible{}
   101  				},
   102  			},
   103  			expectedErr: reconcile.TerminalError(Incompatible{}),
   104  		},
   105  	}
   106  
   107  	for _, test := range tests {
   108  		t.Run(test.name, func(t *testing.T) {
   109  			g := NewGomegaWithT(t)
   110  
   111  			actual, err := ApplyMutators(ctx, test.resources, test.mutators...)
   112  			if test.expectedErr != nil {
   113  				g.Expect(err).To(MatchError(test.expectedErr))
   114  			} else {
   115  				g.Expect(err).NotTo(HaveOccurred())
   116  			}
   117  			g.Expect(actual).To(Equal(test.expected))
   118  		})
   119  	}
   120  }
   121  
   122  func TestPause(t *testing.T) {
   123  	ctx := context.Background()
   124  
   125  	tests := []struct {
   126  		name           string
   127  		policy         string
   128  		isIncompatible bool
   129  	}{
   130  		{
   131  			name:   "set from CAPI opinion",
   132  			policy: "",
   133  		},
   134  		{
   135  			name:   "user value matching CAPI ok",
   136  			policy: string(annotations.ReconcilePolicySkip),
   137  		},
   138  		{
   139  			name:           "incompatible",
   140  			policy:         string(annotations.ReconcilePolicyManage),
   141  			isIncompatible: true,
   142  		},
   143  	}
   144  
   145  	s := runtime.NewScheme()
   146  	NewGomegaWithT(t).Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed())
   147  
   148  	for _, test := range tests {
   149  		t.Run(test.name, func(t *testing.T) {
   150  			g := NewGomegaWithT(t)
   151  
   152  			u := &unstructured.Unstructured{}
   153  			if test.policy != "" {
   154  				u.SetAnnotations(map[string]string{
   155  					annotations.ReconcilePolicy: test.policy,
   156  				})
   157  			}
   158  			err := Pause(ctx, []*unstructured.Unstructured{u})
   159  			if test.isIncompatible {
   160  				g.Expect(errors.As(err, &Incompatible{})).To(BeTrue())
   161  			} else {
   162  				g.Expect(err).NotTo(HaveOccurred())
   163  				g.Expect(u.GetAnnotations()).To(HaveKeyWithValue(annotations.ReconcilePolicy, string(annotations.ReconcilePolicySkip)))
   164  			}
   165  		})
   166  	}
   167  }