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 }