sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/aso/service_test.go (about) 1 /* 2 Copyright 2023 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 aso 18 19 import ( 20 "context" 21 "testing" 22 23 asoresourcesv1 "github.com/Azure/azure-service-operator/v2/api/resources/v1api20200601" 24 . "github.com/onsi/gomega" 25 "github.com/pkg/errors" 26 "go.uber.org/mock/gomock" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 30 "sigs.k8s.io/cluster-api-provider-azure/azure" 31 "sigs.k8s.io/cluster-api-provider-azure/azure/mock_azure" 32 "sigs.k8s.io/cluster-api-provider-azure/azure/services/aso/mock_aso" 33 gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock" 34 reconcilerutils "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" 35 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 36 "sigs.k8s.io/controller-runtime/pkg/client" 37 "sigs.k8s.io/controller-runtime/pkg/client/fake" 38 ) 39 40 const ( 41 serviceName = "test" 42 conditionType = clusterv1.ConditionType("Test") 43 ) 44 45 func TestServiceReconcile(t *testing.T) { 46 t.Run("no specs", func(t *testing.T) { 47 g := NewGomegaWithT(t) 48 49 mockCtrl := gomock.NewController(t) 50 postReconcileErr := errors.New("PostReconcileHook error") 51 scope := mock_aso.NewMockScope(mockCtrl) 52 scope.EXPECT().UpdatePutStatus(conditionType, serviceName, postReconcileErr) 53 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 54 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 55 56 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 57 Reconciler: reconciler, 58 Scope: scope, 59 Specs: nil, 60 name: serviceName, 61 ConditionType: conditionType, 62 PostReconcileHook: func(_ context.Context, _ *mock_aso.MockScope, _ error) error { 63 return postReconcileErr 64 }, 65 } 66 67 err := s.Reconcile(context.Background()) 68 g.Expect(err).To(MatchError(postReconcileErr)) 69 }) 70 71 t.Run("CreateOrUpdateResource returns error", func(t *testing.T) { 72 g := NewGomegaWithT(t) 73 74 mockCtrl := gomock.NewController(t) 75 76 scope := mock_aso.NewMockScope(mockCtrl) 77 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 78 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 79 } 80 81 reconcileErr := errors.New("CreateOrUpdateResource error") 82 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 83 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[0], serviceName).Return(nil, reconcileErr) 84 scope.EXPECT().UpdatePutStatus(conditionType, serviceName, reconcileErr) 85 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 86 87 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 88 Reconciler: reconciler, 89 Scope: scope, 90 Specs: specs, 91 name: serviceName, 92 ConditionType: conditionType, 93 } 94 95 err := s.Reconcile(context.Background()) 96 g.Expect(err).To(MatchError(reconcileErr)) 97 }) 98 99 t.Run("CreateOrUpdateResource succeeds for all resources", func(t *testing.T) { 100 g := NewGomegaWithT(t) 101 102 mockCtrl := gomock.NewController(t) 103 104 scope := mock_aso.NewMockScope(mockCtrl) 105 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 106 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 107 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 108 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 109 } 110 111 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 112 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[0], serviceName).Return(nil, nil) 113 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[1], serviceName).Return(nil, nil) 114 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[2], serviceName).Return(nil, nil) 115 scope.EXPECT().UpdatePutStatus(conditionType, serviceName, nil) 116 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 117 118 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 119 Reconciler: reconciler, 120 Scope: scope, 121 Specs: specs, 122 name: serviceName, 123 ConditionType: conditionType, 124 } 125 126 err := s.Reconcile(context.Background()) 127 g.Expect(err).NotTo(HaveOccurred()) 128 }) 129 130 t.Run("CreateOrUpdateResource returns not done", func(t *testing.T) { 131 g := NewGomegaWithT(t) 132 133 mockCtrl := gomock.NewController(t) 134 135 scope := mock_aso.NewMockScope(mockCtrl) 136 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 137 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 138 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 139 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 140 } 141 142 reconcileErr := azure.NewOperationNotDoneError(&infrav1.Future{}) 143 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 144 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[0], serviceName).Return(nil, nil) 145 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[1], serviceName).Return(nil, reconcileErr) 146 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[2], serviceName).Return(nil, nil) 147 scope.EXPECT().UpdatePutStatus(conditionType, serviceName, reconcileErr) 148 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 149 150 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 151 Reconciler: reconciler, 152 Scope: scope, 153 Specs: specs, 154 name: serviceName, 155 ConditionType: conditionType, 156 } 157 158 err := s.Reconcile(context.Background()) 159 g.Expect(azure.IsOperationNotDoneError(err)).To(BeTrue()) 160 }) 161 162 t.Run("CreateOrUpdateResource returns not done and another error", func(t *testing.T) { 163 g := NewGomegaWithT(t) 164 165 mockCtrl := gomock.NewController(t) 166 167 scope := mock_aso.NewMockScope(mockCtrl) 168 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 169 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 170 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 171 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 172 } 173 174 reconcileErr := errors.New("non-not done error") 175 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 176 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[0], serviceName).Return(nil, azure.NewOperationNotDoneError(&infrav1.Future{})) 177 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[1], serviceName).Return(nil, reconcileErr) 178 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[2], serviceName).Return(nil, azure.NewOperationNotDoneError(&infrav1.Future{})) 179 scope.EXPECT().UpdatePutStatus(conditionType, serviceName, reconcileErr) 180 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 181 182 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 183 Reconciler: reconciler, 184 Scope: scope, 185 Specs: specs, 186 name: serviceName, 187 ConditionType: conditionType, 188 } 189 190 err := s.Reconcile(context.Background()) 191 g.Expect(err).To(MatchError(reconcileErr)) 192 }) 193 194 t.Run("CreateOrUpdateResource returns error and runs PostCreateOrUpdateResourceHook and PostReconcileHook", func(t *testing.T) { 195 g := NewGomegaWithT(t) 196 197 mockCtrl := gomock.NewController(t) 198 199 scope := mock_aso.NewMockScope(mockCtrl) 200 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 201 mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](mockCtrl), 202 } 203 204 reconcileErr := errors.New("CreateOrUpdateResource error") 205 postResourceErr := errors.New("PostCreateOrUpdateResource error") 206 postReconcileErr := errors.New("PostReconcile error") 207 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 208 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[0], serviceName).Return(&asoresourcesv1.ResourceGroup{ 209 ObjectMeta: metav1.ObjectMeta{ 210 Name: "a very special name", 211 }, 212 }, reconcileErr) 213 scope.EXPECT().UpdatePutStatus(conditionType, serviceName, postReconcileErr) 214 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 215 216 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 217 Reconciler: reconciler, 218 Scope: scope, 219 Specs: specs, 220 name: serviceName, 221 ConditionType: conditionType, 222 PostCreateOrUpdateResourceHook: func(_ context.Context, scopeParam *mock_aso.MockScope, result *asoresourcesv1.ResourceGroup, err error) error { 223 g.Expect(scopeParam).To(BeIdenticalTo(scope)) 224 g.Expect(result.Name).To(Equal("a very special name")) 225 g.Expect(err).To(MatchError(reconcileErr)) 226 return postResourceErr 227 }, 228 PostReconcileHook: func(_ context.Context, scopeParam *mock_aso.MockScope, err error) error { 229 g.Expect(scopeParam).To(BeIdenticalTo(scope)) 230 g.Expect(err).To(MatchError(postResourceErr)) 231 return postReconcileErr 232 }, 233 } 234 235 err := s.Reconcile(context.Background()) 236 g.Expect(err).To(MatchError(postReconcileErr)) 237 }) 238 239 t.Run("stale resources are deleted", func(t *testing.T) { 240 g := NewGomegaWithT(t) 241 242 mockCtrl := gomock.NewController(t) 243 244 scope := mock_aso.NewMockScope(mockCtrl) 245 rg0 := &asoresourcesv1.ResourceGroup{ObjectMeta: metav1.ObjectMeta{Name: "spec0"}} 246 rg1 := &asoresourcesv1.ResourceGroup{ObjectMeta: metav1.ObjectMeta{Name: "spec1"}} 247 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 248 mockSpecExpectingResourceRef(mockCtrl, rg0), 249 mockSpecExpectingResourceRef(mockCtrl, rg1), 250 } 251 252 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 253 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[0], serviceName).Return(rg0, azure.NewOperationNotDoneError(&infrav1.Future{})) 254 reconciler.EXPECT().CreateOrUpdateResource(gomockinternal.AContext(), specs[1], serviceName).Return(rg1, nil) 255 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 256 257 sch := runtime.NewScheme() 258 g.Expect(asoresourcesv1.AddToScheme(sch)).To(Succeed()) 259 ctrlClient := fake.NewClientBuilder(). 260 WithScheme(sch). 261 Build() 262 263 deleteErr := errors.New("delete error") 264 265 deleteMe := &asoresourcesv1.ResourceGroup{ 266 ObjectMeta: metav1.ObjectMeta{ 267 Name: "delete-me", 268 Namespace: "namespace", 269 }, 270 } 271 deleteMeToo := &asoresourcesv1.ResourceGroup{ 272 ObjectMeta: metav1.ObjectMeta{ 273 Name: "delete-me-too", 274 Namespace: "namespace", 275 }, 276 } 277 278 scope.EXPECT().GetClient().Return(ctrlClient).AnyTimes() 279 scope.EXPECT().ASOOwner().Return(&asoresourcesv1.ResourceGroup{}).AnyTimes() 280 list := func(_ context.Context, _ client.Client, _ ...client.ListOption) ([]*asoresourcesv1.ResourceGroup, error) { 281 return []*asoresourcesv1.ResourceGroup{ 282 deleteMe, 283 rg0, 284 deleteMeToo, 285 rg1, 286 }, nil 287 } 288 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), deleteMe, serviceName).Return(deleteErr) 289 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), deleteMeToo, serviceName).Return(azure.NewOperationNotDoneError(&infrav1.Future{})) 290 scope.EXPECT().UpdatePutStatus(conditionType, serviceName, deleteErr) 291 292 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 293 Reconciler: reconciler, 294 ListFunc: list, 295 Scope: scope, 296 Specs: specs, 297 name: serviceName, 298 ConditionType: conditionType, 299 } 300 301 err := s.Reconcile(context.Background()) 302 g.Expect(err).To(MatchError(deleteErr)) 303 }) 304 } 305 306 func TestServiceDelete(t *testing.T) { 307 t.Run("no specs", func(t *testing.T) { 308 g := NewGomegaWithT(t) 309 310 mockCtrl := gomock.NewController(t) 311 scope := mock_aso.NewMockScope(mockCtrl) 312 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 313 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 314 315 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 316 Reconciler: reconciler, 317 Scope: scope, 318 Specs: nil, 319 name: serviceName, 320 ConditionType: conditionType, 321 PostDeleteHook: func(_ context.Context, _ *mock_aso.MockScope, _ error) error { 322 return errors.New("hook should not be called") 323 }, 324 } 325 326 err := s.Delete(context.Background()) 327 g.Expect(err).NotTo(HaveOccurred()) 328 }) 329 330 t.Run("DeleteResource returns error", func(t *testing.T) { 331 g := NewGomegaWithT(t) 332 333 mockCtrl := gomock.NewController(t) 334 335 scope := mock_aso.NewMockScope(mockCtrl) 336 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 337 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 338 } 339 340 deleteErr := errors.New("DeleteResource error") 341 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 342 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[0].ResourceRef(), serviceName).Return(deleteErr) 343 scope.EXPECT().UpdateDeleteStatus(conditionType, serviceName, deleteErr) 344 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 345 346 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 347 Reconciler: reconciler, 348 Scope: scope, 349 Specs: specs, 350 name: serviceName, 351 ConditionType: conditionType, 352 } 353 354 err := s.Delete(context.Background()) 355 g.Expect(err).To(MatchError(deleteErr)) 356 }) 357 358 t.Run("DeleteResource succeeds for all resources", func(t *testing.T) { 359 g := NewGomegaWithT(t) 360 361 mockCtrl := gomock.NewController(t) 362 363 scope := mock_aso.NewMockScope(mockCtrl) 364 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 365 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 366 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 367 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 368 } 369 370 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 371 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[0].ResourceRef(), serviceName).Return(nil) 372 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[1].ResourceRef(), serviceName).Return(nil) 373 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[2].ResourceRef(), serviceName).Return(nil) 374 scope.EXPECT().UpdateDeleteStatus(conditionType, serviceName, nil) 375 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 376 377 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 378 Reconciler: reconciler, 379 Scope: scope, 380 Specs: specs, 381 name: serviceName, 382 ConditionType: conditionType, 383 } 384 385 err := s.Delete(context.Background()) 386 g.Expect(err).NotTo(HaveOccurred()) 387 }) 388 389 t.Run("DeleteResource returns not done", func(t *testing.T) { 390 g := NewGomegaWithT(t) 391 392 mockCtrl := gomock.NewController(t) 393 394 scope := mock_aso.NewMockScope(mockCtrl) 395 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 396 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 397 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 398 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 399 } 400 401 deleteErr := azure.NewOperationNotDoneError(&infrav1.Future{}) 402 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 403 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[0].ResourceRef(), serviceName).Return(nil) 404 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[1].ResourceRef(), serviceName).Return(deleteErr) 405 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[2].ResourceRef(), serviceName).Return(nil) 406 scope.EXPECT().UpdateDeleteStatus(conditionType, serviceName, deleteErr) 407 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 408 409 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 410 Reconciler: reconciler, 411 Scope: scope, 412 Specs: specs, 413 name: serviceName, 414 ConditionType: conditionType, 415 } 416 417 err := s.Delete(context.Background()) 418 g.Expect(azure.IsOperationNotDoneError(err)).To(BeTrue()) 419 }) 420 421 t.Run("DeleteResource returns not done and another error", func(t *testing.T) { 422 g := NewGomegaWithT(t) 423 424 mockCtrl := gomock.NewController(t) 425 426 scope := mock_aso.NewMockScope(mockCtrl) 427 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 428 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 429 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 430 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 431 } 432 433 deleteErr := errors.New("non-not done error") 434 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 435 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[0].ResourceRef(), serviceName).Return(azure.NewOperationNotDoneError(&infrav1.Future{})) 436 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[1].ResourceRef(), serviceName).Return(deleteErr) 437 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[2].ResourceRef(), serviceName).Return(azure.NewOperationNotDoneError(&infrav1.Future{})) 438 scope.EXPECT().UpdateDeleteStatus(conditionType, serviceName, deleteErr) 439 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 440 441 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 442 Reconciler: reconciler, 443 Scope: scope, 444 Specs: specs, 445 name: serviceName, 446 ConditionType: conditionType, 447 } 448 449 err := s.Delete(context.Background()) 450 g.Expect(err).To(MatchError(deleteErr)) 451 }) 452 453 t.Run("DeleteResource returns error and runs PostDeleteHook", func(t *testing.T) { 454 g := NewGomegaWithT(t) 455 456 mockCtrl := gomock.NewController(t) 457 458 scope := mock_aso.NewMockScope(mockCtrl) 459 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 460 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 461 } 462 463 deleteErr := errors.New("DeleteResource error") 464 postErr := errors.New("PostDeleteHook error") 465 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 466 reconciler.EXPECT().DeleteResource(gomockinternal.AContext(), specs[0].ResourceRef(), serviceName).Return(deleteErr) 467 scope.EXPECT().UpdateDeleteStatus(conditionType, serviceName, postErr) 468 scope.EXPECT().DefaultedAzureServiceReconcileTimeout().Return(reconcilerutils.DefaultAzureServiceReconcileTimeout) 469 470 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 471 Reconciler: reconciler, 472 Scope: scope, 473 Specs: specs, 474 name: serviceName, 475 ConditionType: conditionType, 476 PostDeleteHook: func(_ context.Context, scopeParam *mock_aso.MockScope, err error) error { 477 g.Expect(scopeParam).To(BeIdenticalTo(scope)) 478 g.Expect(err).To(MatchError(deleteErr)) 479 return postErr 480 }, 481 } 482 483 err := s.Delete(context.Background()) 484 g.Expect(err).To(MatchError(postErr)) 485 }) 486 } 487 488 func TestServicePause(t *testing.T) { 489 t.Run("PauseResource succeeds for all resources", func(t *testing.T) { 490 g := NewGomegaWithT(t) 491 492 mockCtrl := gomock.NewController(t) 493 494 scope := mock_aso.NewMockScope(mockCtrl) 495 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 496 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 497 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 498 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 499 } 500 501 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 502 reconciler.EXPECT().PauseResource(gomockinternal.AContext(), specs[0].ResourceRef(), serviceName).Return(nil) 503 reconciler.EXPECT().PauseResource(gomockinternal.AContext(), specs[1].ResourceRef(), serviceName).Return(nil) 504 reconciler.EXPECT().PauseResource(gomockinternal.AContext(), specs[2].ResourceRef(), serviceName).Return(nil) 505 506 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 507 Reconciler: reconciler, 508 Scope: scope, 509 Specs: specs, 510 name: serviceName, 511 ConditionType: conditionType, 512 } 513 514 err := s.Pause(context.Background()) 515 g.Expect(err).NotTo(HaveOccurred()) 516 }) 517 518 t.Run("PauseResource fails for one resource", func(t *testing.T) { 519 g := NewGomegaWithT(t) 520 521 mockCtrl := gomock.NewController(t) 522 523 scope := mock_aso.NewMockScope(mockCtrl) 524 specs := []azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup]{ 525 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 526 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{ 527 ObjectMeta: metav1.ObjectMeta{ 528 Name: "name", 529 Namespace: "namespace", 530 }, 531 }), 532 mockSpecExpectingResourceRef(mockCtrl, &asoresourcesv1.ResourceGroup{}), 533 } 534 scope.EXPECT().ASOOwner().Return(&asoresourcesv1.ResourceGroup{}) 535 536 pauseErr := errors.New("Pause error") 537 reconciler := mock_aso.NewMockReconciler[*asoresourcesv1.ResourceGroup](mockCtrl) 538 reconciler.EXPECT().PauseResource(gomockinternal.AContext(), specs[0].ResourceRef(), serviceName).Return(nil) 539 reconciler.EXPECT().PauseResource(gomockinternal.AContext(), specs[1].ResourceRef(), serviceName).Return(pauseErr) 540 541 s := &Service[*asoresourcesv1.ResourceGroup, *mock_aso.MockScope]{ 542 Reconciler: reconciler, 543 Scope: scope, 544 Specs: specs, 545 name: serviceName, 546 ConditionType: conditionType, 547 } 548 549 err := s.Pause(context.Background()) 550 g.Expect(err).To(MatchError(pauseErr)) 551 }) 552 } 553 554 func mockSpecExpectingResourceRef(ctrl *gomock.Controller, resource *asoresourcesv1.ResourceGroup) azure.ASOResourceSpecGetter[*asoresourcesv1.ResourceGroup] { 555 spec := mock_azure.NewMockASOResourceSpecGetter[*asoresourcesv1.ResourceGroup](ctrl) 556 spec.EXPECT().ResourceRef().Return(resource).AnyTimes() 557 return spec 558 }