sigs.k8s.io/cluster-api@v1.7.1/util/conditions/patch_test.go (about)

     1  /*
     2  Copyright 2020 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 conditions
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    28  )
    29  
    30  func TestNewPatch(t *testing.T) {
    31  	fooTrue := TrueCondition("foo")
    32  	fooFalse := FalseCondition("foo", "reason foo", clusterv1.ConditionSeverityInfo, "message foo")
    33  
    34  	tests := []struct {
    35  		name    string
    36  		before  Getter
    37  		after   Getter
    38  		want    Patch
    39  		wantErr bool
    40  	}{
    41  		{
    42  			name:    "nil before return error",
    43  			before:  nil,
    44  			after:   getterWithConditions(),
    45  			wantErr: true,
    46  		},
    47  		{
    48  			name:    "nil after return error",
    49  			before:  getterWithConditions(),
    50  			after:   nil,
    51  			wantErr: true,
    52  		},
    53  		{
    54  			name:    "nil Interface before return error",
    55  			before:  nilGetter(),
    56  			after:   getterWithConditions(),
    57  			wantErr: true,
    58  		},
    59  		{
    60  			name:    "nil Interface after return error",
    61  			before:  getterWithConditions(),
    62  			after:   nilGetter(),
    63  			wantErr: true,
    64  		},
    65  		{
    66  			name:    "No changes return empty patch",
    67  			before:  getterWithConditions(),
    68  			after:   getterWithConditions(),
    69  			want:    nil,
    70  			wantErr: false,
    71  		},
    72  
    73  		{
    74  			name:   "No changes return empty patch",
    75  			before: getterWithConditions(fooTrue),
    76  			after:  getterWithConditions(fooTrue),
    77  			want:   nil,
    78  		},
    79  		{
    80  			name:   "Detects AddConditionPatch",
    81  			before: getterWithConditions(),
    82  			after:  getterWithConditions(fooTrue),
    83  			want: Patch{
    84  				{
    85  					Before: nil,
    86  					After:  fooTrue,
    87  					Op:     AddConditionPatch,
    88  				},
    89  			},
    90  		},
    91  		{
    92  			name:   "Detects ChangeConditionPatch",
    93  			before: getterWithConditions(fooTrue),
    94  			after:  getterWithConditions(fooFalse),
    95  			want: Patch{
    96  				{
    97  					Before: fooTrue,
    98  					After:  fooFalse,
    99  					Op:     ChangeConditionPatch,
   100  				},
   101  			},
   102  		},
   103  		{
   104  			name:   "Detects RemoveConditionPatch",
   105  			before: getterWithConditions(fooTrue),
   106  			after:  getterWithConditions(),
   107  			want: Patch{
   108  				{
   109  					Before: fooTrue,
   110  					After:  nil,
   111  					Op:     RemoveConditionPatch,
   112  				},
   113  			},
   114  		},
   115  	}
   116  
   117  	for _, tt := range tests {
   118  		t.Run(tt.name, func(t *testing.T) {
   119  			g := NewWithT(t)
   120  
   121  			got, err := NewPatch(tt.before, tt.after)
   122  			if tt.wantErr {
   123  				g.Expect(err).To(HaveOccurred())
   124  				return
   125  			}
   126  			g.Expect(err).To(Not(HaveOccurred()))
   127  			g.Expect(got).To(BeComparableTo(tt.want))
   128  		})
   129  	}
   130  }
   131  
   132  func TestApply(t *testing.T) {
   133  	fooTrue := TrueCondition("foo")
   134  	fooFalse := FalseCondition("foo", "reason foo", clusterv1.ConditionSeverityInfo, "message foo")
   135  	fooWarning := FalseCondition("foo", "reason foo", clusterv1.ConditionSeverityWarning, "message foo")
   136  
   137  	tests := []struct {
   138  		name    string
   139  		before  Getter
   140  		after   Getter
   141  		latest  Setter
   142  		options []ApplyOption
   143  		want    clusterv1.Conditions
   144  		wantErr bool
   145  	}{
   146  		{
   147  			name:    "error with nil interface Setter",
   148  			before:  getterWithConditions(fooTrue),
   149  			after:   getterWithConditions(fooFalse),
   150  			latest:  nilSetter(),
   151  			want:    conditionList(fooTrue),
   152  			wantErr: true,
   153  		},
   154  		{
   155  			name:    "error with nil Setter",
   156  			before:  getterWithConditions(fooTrue),
   157  			after:   getterWithConditions(fooFalse),
   158  			latest:  nil,
   159  			want:    conditionList(fooTrue),
   160  			wantErr: true,
   161  		},
   162  		{
   163  			name:    "No patch return same list",
   164  			before:  getterWithConditions(fooTrue),
   165  			after:   getterWithConditions(fooTrue),
   166  			latest:  setterWithConditions(fooTrue),
   167  			want:    conditionList(fooTrue),
   168  			wantErr: false,
   169  		},
   170  		{
   171  			name:    "Add: When a condition does not exists, it should add",
   172  			before:  getterWithConditions(),
   173  			after:   getterWithConditions(fooTrue),
   174  			latest:  setterWithConditions(),
   175  			want:    conditionList(fooTrue),
   176  			wantErr: false,
   177  		},
   178  		{
   179  			name:    "Add: When a condition already exists but without conflicts, it should add",
   180  			before:  getterWithConditions(),
   181  			after:   getterWithConditions(fooTrue),
   182  			latest:  setterWithConditions(fooTrue),
   183  			want:    conditionList(fooTrue),
   184  			wantErr: false,
   185  		},
   186  		{
   187  			name:    "Add: When a condition already exists but with conflicts, it should error",
   188  			before:  getterWithConditions(),
   189  			after:   getterWithConditions(fooTrue),
   190  			latest:  setterWithConditions(fooFalse),
   191  			want:    nil,
   192  			wantErr: true,
   193  		},
   194  		{
   195  			name:    "Add: When a condition already exists but with conflicts, it should not error if the condition is owned",
   196  			before:  getterWithConditions(),
   197  			after:   getterWithConditions(fooTrue),
   198  			latest:  setterWithConditions(fooFalse),
   199  			options: []ApplyOption{WithOwnedConditions("foo")},
   200  			want:    conditionList(fooTrue), // after condition should be kept in case of error
   201  			wantErr: false,
   202  		},
   203  		{
   204  			name:    "Remove: When a condition was already deleted, it should pass",
   205  			before:  getterWithConditions(fooTrue),
   206  			after:   getterWithConditions(),
   207  			latest:  setterWithConditions(),
   208  			want:    conditionList(),
   209  			wantErr: false,
   210  		},
   211  		{
   212  			name:    "Remove: When a condition already exists but without conflicts, it should delete",
   213  			before:  getterWithConditions(fooTrue),
   214  			after:   getterWithConditions(),
   215  			latest:  setterWithConditions(fooTrue),
   216  			want:    conditionList(),
   217  			wantErr: false,
   218  		},
   219  		{
   220  			name:    "Remove: When a condition already exists but with conflicts, it should error",
   221  			before:  getterWithConditions(fooTrue),
   222  			after:   getterWithConditions(),
   223  			latest:  setterWithConditions(fooFalse),
   224  			want:    nil,
   225  			wantErr: true,
   226  		},
   227  		{
   228  			name:    "Remove: When a condition already exists but with conflicts, it should not error if the condition is owned",
   229  			before:  getterWithConditions(fooTrue),
   230  			after:   getterWithConditions(),
   231  			latest:  setterWithConditions(fooFalse),
   232  			options: []ApplyOption{WithOwnedConditions("foo")},
   233  			want:    conditionList(), // after condition should be kept in case of error
   234  			wantErr: false,
   235  		},
   236  		{
   237  			name:    "Change: When a condition exists without conflicts, it should change",
   238  			before:  getterWithConditions(fooTrue),
   239  			after:   getterWithConditions(fooFalse),
   240  			latest:  setterWithConditions(fooTrue),
   241  			want:    conditionList(fooFalse),
   242  			wantErr: false,
   243  		},
   244  		{
   245  			name:    "Change: When a condition exists with conflicts but there is agreement on the final state, it should change",
   246  			before:  getterWithConditions(fooFalse),
   247  			after:   getterWithConditions(fooTrue),
   248  			latest:  setterWithConditions(fooTrue),
   249  			want:    conditionList(fooTrue),
   250  			wantErr: false,
   251  		},
   252  		{
   253  			name:    "Change: When a condition exists with conflicts but there is no agreement on the final state, it should error",
   254  			before:  getterWithConditions(fooWarning),
   255  			after:   getterWithConditions(fooFalse),
   256  			latest:  setterWithConditions(fooTrue),
   257  			want:    nil,
   258  			wantErr: true,
   259  		},
   260  		{
   261  			name:    "Change: When a condition exists with conflicts but there is no agreement on the final state, it should not error if the condition is owned",
   262  			before:  getterWithConditions(fooWarning),
   263  			after:   getterWithConditions(fooFalse),
   264  			latest:  setterWithConditions(fooTrue),
   265  			options: []ApplyOption{WithOwnedConditions("foo")},
   266  			want:    conditionList(fooFalse), // after condition should be kept in case of error
   267  			wantErr: false,
   268  		},
   269  		{
   270  			name:    "Change: When a condition was deleted, it should error",
   271  			before:  getterWithConditions(fooTrue),
   272  			after:   getterWithConditions(fooFalse),
   273  			latest:  setterWithConditions(),
   274  			want:    nil,
   275  			wantErr: true,
   276  		},
   277  		{
   278  			name:    "Change: When a condition was deleted, it should not error if the condition is owned",
   279  			before:  getterWithConditions(fooTrue),
   280  			after:   getterWithConditions(fooFalse),
   281  			latest:  setterWithConditions(),
   282  			options: []ApplyOption{WithOwnedConditions("foo")},
   283  			want:    conditionList(fooFalse), // after condition should be kept in case of error
   284  			wantErr: false,
   285  		},
   286  		{
   287  			name:    "Error when nil passed as an ApplyOption",
   288  			before:  getterWithConditions(fooTrue),
   289  			after:   getterWithConditions(fooFalse),
   290  			latest:  setterWithConditions(),
   291  			options: []ApplyOption{nil},
   292  			wantErr: true,
   293  		},
   294  	}
   295  
   296  	for _, tt := range tests {
   297  		t.Run(tt.name, func(t *testing.T) {
   298  			g := NewWithT(t)
   299  
   300  			// Ignore the error here to allow testing of patch.Apply with a nil patch
   301  			patch, _ := NewPatch(tt.before, tt.after)
   302  
   303  			err := patch.Apply(tt.latest, tt.options...)
   304  			if tt.wantErr {
   305  				g.Expect(err).To(HaveOccurred())
   306  				return
   307  			}
   308  			g.Expect(err).ToNot(HaveOccurred())
   309  
   310  			g.Expect(tt.latest.GetConditions()).To(haveSameConditionsOf(tt.want))
   311  		})
   312  	}
   313  }
   314  
   315  func TestApplyDoesNotAlterLastTransitionTime(t *testing.T) {
   316  	g := NewWithT(t)
   317  
   318  	before := &clusterv1.Cluster{}
   319  	after := &clusterv1.Cluster{
   320  		Status: clusterv1.ClusterStatus{
   321  			Conditions: clusterv1.Conditions{
   322  				clusterv1.Condition{
   323  					Type:               "foo",
   324  					Status:             corev1.ConditionTrue,
   325  					LastTransitionTime: metav1.NewTime(time.Now().UTC().Truncate(time.Second)),
   326  				},
   327  			},
   328  		},
   329  	}
   330  	latest := &clusterv1.Cluster{}
   331  
   332  	// latest has no conditions, so we are actually adding the condition but in this case we should not set the LastTransition Time
   333  	// but we should preserve the LastTransition set in after
   334  
   335  	diff, err := NewPatch(before, after)
   336  	g.Expect(err).ToNot(HaveOccurred())
   337  	err = diff.Apply(latest)
   338  
   339  	g.Expect(err).ToNot(HaveOccurred())
   340  	g.Expect(latest.GetConditions()).To(BeComparableTo(after.GetConditions()))
   341  }