This is an automated email from the ASF dual-hosted git repository.

jimin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-k8s.git


The following commit(s) were added to refs/heads/master by this push:
     new 1d240de  feature: support finalizer (#44)
1d240de is described below

commit 1d240de1546a0abc9b38eff6ec7bab7543d6ba73
Author: jimin <[email protected]>
AuthorDate: Wed Dec 10 10:41:28 2025 +0800

    feature: support finalizer (#44)
---
 go.mod                                  |   1 +
 pkg/finalizer/cleanup_handler.go        | 229 +++++++++++++++++++++++
 pkg/finalizer/finalizer_manager.go      | 197 ++++++++++++++++++++
 pkg/finalizer/finalizer_manager_test.go | 312 ++++++++++++++++++++++++++++++++
 4 files changed, 739 insertions(+)

diff --git a/go.mod b/go.mod
index 9c493d7..57df014 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
        github.com/cespare/xxhash/v2 v2.2.0 // indirect
        github.com/davecgh/go-spew v1.1.1 // indirect
        github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+       github.com/evanphx/json-patch v5.6.0+incompatible // indirect
        github.com/evanphx/json-patch/v5 v5.7.0 // indirect
        github.com/fsnotify/fsnotify v1.7.0 // indirect
        github.com/go-logr/zapr v1.3.0 // indirect
diff --git a/pkg/finalizer/cleanup_handler.go b/pkg/finalizer/cleanup_handler.go
new file mode 100644
index 0000000..4cbe7ca
--- /dev/null
+++ b/pkg/finalizer/cleanup_handler.go
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package finalizer
+
+import (
+       "context"
+       "fmt"
+
+       "github.com/go-logr/logr"
+       apiv1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/labels"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+
+       seatav1 "github.com/apache/seata-k8s/api/v1"
+       seatav1alpha1 "github.com/apache/seata-k8s/api/v1alpha1"
+)
+
+// CleanupHandler handles cleanup operations when SeataServer is deleted
+type CleanupHandler struct {
+       Client client.Client
+       Log    logr.Logger
+}
+
+// NewCleanupHandler creates a new CleanupHandler instance
+func NewCleanupHandler(client client.Client, log logr.Logger) *CleanupHandler {
+       return &CleanupHandler{
+               Client: client,
+               Log:    log,
+       }
+}
+
+// HandleCleanup performs cleanup operations based on the SeataServer 
configuration
+func (ch *CleanupHandler) HandleCleanup(ctx context.Context, server 
interface{}) error {
+       switch s := server.(type) {
+       case *seatav1.SeataServer:
+               return ch.handleCleanupV1(ctx, s)
+       case *seatav1alpha1.SeataServer:
+               return ch.handleCleanupV1Alpha1(ctx, s)
+       default:
+               return fmt.Errorf("unsupported type: %T", server)
+       }
+}
+
+// handleCleanupV1 performs cleanup for v1 SeataServer
+func (ch *CleanupHandler) handleCleanupV1(ctx context.Context, s 
*seatav1.SeataServer) error {
+       ch.Log.Info("Starting cleanup for SeataServer", "name", s.Name, 
"namespace", s.Namespace)
+
+       // Only cleanup PVCs if reclaim policy is Delete
+       if s.Spec.Persistence.VolumeReclaimPolicy == 
seatav1.VolumeReclaimPolicyDelete {
+               ch.Log.Info("Cleaning up PVCs for SeataServer", "name", s.Name, 
"namespace", s.Namespace)
+               if err := ch.cleanupAllPVCs(ctx, s.Name, s.Namespace, s.UID); 
err != nil {
+                       ch.Log.Error(err, "Failed to cleanup PVCs", 
"seataserver", s.Name, "namespace", s.Namespace)
+                       return err
+               }
+       }
+
+       // Cleanup other resources as needed
+       if err := ch.cleanupConfigMaps(ctx, s.Name, s.Namespace); err != nil {
+               ch.Log.Error(err, "Failed to cleanup ConfigMaps", 
"seataserver", s.Name, "namespace", s.Namespace)
+               // Continue cleanup despite error
+       }
+
+       if err := ch.cleanupSecrets(ctx, s.Name, s.Namespace); err != nil {
+               ch.Log.Error(err, "Failed to cleanup Secrets", "seataserver", 
s.Name, "namespace", s.Namespace)
+               // Continue cleanup despite error
+       }
+
+       ch.Log.Info("Cleanup completed for SeataServer", "name", s.Name, 
"namespace", s.Namespace)
+       return nil
+}
+
+// handleCleanupV1Alpha1 performs cleanup for v1alpha1 SeataServer
+func (ch *CleanupHandler) handleCleanupV1Alpha1(ctx context.Context, s 
*seatav1alpha1.SeataServer) error {
+       ch.Log.Info("Starting cleanup for SeataServer (v1alpha1)", "name", 
s.Name, "namespace", s.Namespace)
+
+       // Only cleanup PVCs if reclaim policy is Delete
+       if s.Spec.Persistence.VolumeReclaimPolicy == 
seatav1alpha1.VolumeReclaimPolicyDelete {
+               ch.Log.Info("Cleaning up PVCs for SeataServer (v1alpha1)", 
"name", s.Name, "namespace", s.Namespace)
+               if err := ch.cleanupAllPVCs(ctx, s.Name, s.Namespace, s.UID); 
err != nil {
+                       ch.Log.Error(err, "Failed to cleanup PVCs", 
"seataserver", s.Name, "namespace", s.Namespace)
+                       return err
+               }
+       }
+
+       // Cleanup other resources as needed
+       if err := ch.cleanupConfigMaps(ctx, s.Name, s.Namespace); err != nil {
+               ch.Log.Error(err, "Failed to cleanup ConfigMaps", 
"seataserver", s.Name, "namespace", s.Namespace)
+               // Continue cleanup despite error
+       }
+
+       if err := ch.cleanupSecrets(ctx, s.Name, s.Namespace); err != nil {
+               ch.Log.Error(err, "Failed to cleanup Secrets", "seataserver", 
s.Name, "namespace", s.Namespace)
+               // Continue cleanup despite error
+       }
+
+       ch.Log.Info("Cleanup completed for SeataServer (v1alpha1)", "name", 
s.Name, "namespace", s.Namespace)
+       return nil
+}
+
+// cleanupAllPVCs deletes all PVCs associated with the SeataServer
+func (ch *CleanupHandler) cleanupAllPVCs(ctx context.Context, name, namespace 
string, uid interface{}) error {
+       pvcList, err := ch.getPVCList(ctx, name, namespace, uid)
+       if err != nil {
+               if errors.IsNotFound(err) {
+                       return nil // PVCs not found, no cleanup needed
+               }
+               return err
+       }
+
+       ch.Log.Info("Found PVCs to cleanup", "count", len(pvcList.Items), 
"seataserver", name, "namespace", namespace)
+
+       for _, pvc := range pvcList.Items {
+               if err := ch.deletePVC(ctx, &pvc); err != nil {
+                       ch.Log.Error(err, "Failed to delete PVC", "pvc", 
pvc.Name, "namespace", pvc.Namespace)
+                       // Continue deleting other PVCs despite error
+               }
+       }
+
+       return nil
+}
+
+// getPVCList retrieves all PVCs associated with the SeataServer
+func (ch *CleanupHandler) getPVCList(ctx context.Context, name, namespace 
string, uid interface{}) (*apiv1.PersistentVolumeClaimList, error) {
+       selector := labels.SelectorFromSet(map[string]string{
+               "app": name,
+               "uid": fmt.Sprintf("%v", uid),
+       })
+
+       pvcList := &apiv1.PersistentVolumeClaimList{}
+       listOpts := &client.ListOptions{
+               Namespace:     namespace,
+               LabelSelector: selector,
+       }
+
+       if err := ch.Client.List(ctx, pvcList, listOpts); err != nil {
+               ch.Log.Error(err, "Failed to list PVCs", "seataserver", name, 
"namespace", namespace)
+               return nil, err
+       }
+
+       return pvcList, nil
+}
+
+// deletePVC deletes a single PVC
+func (ch *CleanupHandler) deletePVC(ctx context.Context, pvc 
*apiv1.PersistentVolumeClaim) error {
+       ch.Log.Info("Deleting PVC", "pvc", pvc.Name, "namespace", pvc.Namespace)
+
+       if err := ch.Client.Delete(ctx, pvc); err != nil {
+               if !errors.IsNotFound(err) {
+                       ch.Log.Error(err, "Failed to delete PVC", "pvc", 
pvc.Name, "namespace", pvc.Namespace)
+                       return err
+               }
+       }
+
+       return nil
+}
+
+// cleanupConfigMaps deletes ConfigMaps associated with the SeataServer
+func (ch *CleanupHandler) cleanupConfigMaps(ctx context.Context, name, 
namespace string) error {
+       cmList := &apiv1.ConfigMapList{}
+       selector := labels.SelectorFromSet(map[string]string{"app": name})
+       listOpts := &client.ListOptions{
+               Namespace:     namespace,
+               LabelSelector: selector,
+       }
+
+       if err := ch.Client.List(ctx, cmList, listOpts); err != nil {
+               if !errors.IsNotFound(err) {
+                       return err
+               }
+               return nil
+       }
+
+       for _, cm := range cmList.Items {
+               ch.Log.Info("Deleting ConfigMap", "configmap", cm.Name, 
"namespace", cm.Namespace)
+               if err := ch.Client.Delete(ctx, &cm); err != nil {
+                       if !errors.IsNotFound(err) {
+                               ch.Log.Error(err, "Failed to delete ConfigMap", 
"configmap", cm.Name, "namespace", cm.Namespace)
+                               // Continue cleanup despite error
+                       }
+               }
+       }
+
+       return nil
+}
+
+// cleanupSecrets deletes Secrets associated with the SeataServer
+func (ch *CleanupHandler) cleanupSecrets(ctx context.Context, name, namespace 
string) error {
+       secretList := &apiv1.SecretList{}
+       selector := labels.SelectorFromSet(map[string]string{"app": name})
+       listOpts := &client.ListOptions{
+               Namespace:     namespace,
+               LabelSelector: selector,
+       }
+
+       if err := ch.Client.List(ctx, secretList, listOpts); err != nil {
+               if !errors.IsNotFound(err) {
+                       return err
+               }
+               return nil
+       }
+
+       for _, secret := range secretList.Items {
+               ch.Log.Info("Deleting Secret", "secret", secret.Name, 
"namespace", secret.Namespace)
+               if err := ch.Client.Delete(ctx, &secret); err != nil {
+                       if !errors.IsNotFound(err) {
+                               ch.Log.Error(err, "Failed to delete Secret", 
"secret", secret.Name, "namespace", secret.Namespace)
+                               // Continue cleanup despite error
+                       }
+               }
+       }
+
+       return nil
+}
diff --git a/pkg/finalizer/finalizer_manager.go 
b/pkg/finalizer/finalizer_manager.go
new file mode 100644
index 0000000..1dbe49b
--- /dev/null
+++ b/pkg/finalizer/finalizer_manager.go
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package finalizer
+
+import (
+       "context"
+       "fmt"
+
+       "github.com/go-logr/logr"
+       "k8s.io/apimachinery/pkg/types"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+
+       seatav1 "github.com/apache/seata-k8s/api/v1"
+       seatav1alpha1 "github.com/apache/seata-k8s/api/v1alpha1"
+       "github.com/apache/seata-k8s/pkg/utils"
+)
+
+const (
+       // SeataFinalizerName is the name of the finalizer for SeataServer 
resources
+       SeataFinalizerName = 
"seataserver.finalizer.operator.seata.apache.org/cleanup"
+
+       // DeprecatedSeataFinalizerName is the old finalizer name for backward 
compatibility
+       DeprecatedSeataFinalizerName = "seataserver.finalizer"
+)
+
+// FinalizerManager manages the lifecycle of SeataServer resources through 
finalizers
+type FinalizerManager struct {
+       Client client.Client
+       Log    logr.Logger
+}
+
+// NewFinalizerManager creates a new FinalizerManager instance
+func NewFinalizerManager(client client.Client, log logr.Logger) 
*FinalizerManager {
+       return &FinalizerManager{
+               Client: client,
+               Log:    log,
+       }
+}
+
+// AddFinalizer adds the finalizer to the SeataServer object if not already 
present
+func (fm *FinalizerManager) AddFinalizer(ctx context.Context, server 
interface{}, finalizerName string) error {
+       switch s := server.(type) {
+       case *seatav1.SeataServer:
+               return fm.addFinalizerToV1(ctx, s, finalizerName)
+       case *seatav1alpha1.SeataServer:
+               return fm.addFinalizerToV1Alpha1(ctx, s, finalizerName)
+       default:
+               return fmt.Errorf("unsupported type: %T", server)
+       }
+}
+
+// RemoveFinalizer removes the finalizer from the SeataServer object
+func (fm *FinalizerManager) RemoveFinalizer(ctx context.Context, server 
interface{}, finalizerName string) error {
+       switch s := server.(type) {
+       case *seatav1.SeataServer:
+               return fm.removeFinalizerFromV1(ctx, s, finalizerName)
+       case *seatav1alpha1.SeataServer:
+               return fm.removeFinalizerFromV1Alpha1(ctx, s, finalizerName)
+       default:
+               return fmt.Errorf("unsupported type: %T", server)
+       }
+}
+
+// HasFinalizer checks if the SeataServer has the specified finalizer
+func (fm *FinalizerManager) HasFinalizer(server interface{}, finalizerName 
string) bool {
+       switch s := server.(type) {
+       case *seatav1.SeataServer:
+               return utils.ContainsString(s.ObjectMeta.Finalizers, 
finalizerName)
+       case *seatav1alpha1.SeataServer:
+               return utils.ContainsString(s.ObjectMeta.Finalizers, 
finalizerName)
+       default:
+               return false
+       }
+}
+
+// IsBeingDeleted checks if the SeataServer is marked for deletion
+func (fm *FinalizerManager) IsBeingDeleted(server interface{}) bool {
+       switch s := server.(type) {
+       case *seatav1.SeataServer:
+               return !s.DeletionTimestamp.IsZero()
+       case *seatav1alpha1.SeataServer:
+               return !s.DeletionTimestamp.IsZero()
+       default:
+               return false
+       }
+}
+
+// GetDeletionTimestamp returns the deletion timestamp of the SeataServer
+func (fm *FinalizerManager) GetDeletionTimestamp(server interface{}) 
interface{} {
+       switch s := server.(type) {
+       case *seatav1.SeataServer:
+               return s.DeletionTimestamp
+       case *seatav1alpha1.SeataServer:
+               return s.DeletionTimestamp
+       default:
+               return nil
+       }
+}
+
+// GetNamespacedName returns the NamespacedName of the SeataServer
+func (fm *FinalizerManager) GetNamespacedName(server interface{}) 
types.NamespacedName {
+       switch s := server.(type) {
+       case *seatav1.SeataServer:
+               return types.NamespacedName{Name: s.Name, Namespace: 
s.Namespace}
+       case *seatav1alpha1.SeataServer:
+               return types.NamespacedName{Name: s.Name, Namespace: 
s.Namespace}
+       default:
+               return types.NamespacedName{}
+       }
+}
+
+// Helper methods for v1 API
+
+func (fm *FinalizerManager) addFinalizerToV1(ctx context.Context, s 
*seatav1.SeataServer, finalizerName string) error {
+       if utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+               return nil // Already has finalizer
+       }
+
+       fm.Log.Info("Adding finalizer", "name", finalizerName, "seataserver", 
s.Name, "namespace", s.Namespace)
+
+       s.ObjectMeta.Finalizers = append(s.ObjectMeta.Finalizers, finalizerName)
+       if err := fm.Client.Update(ctx, s); err != nil {
+               fm.Log.Error(err, "Failed to add finalizer", "seataserver", 
s.Name, "namespace", s.Namespace)
+               return err
+       }
+
+       fm.Log.Info("Finalizer added successfully", "name", finalizerName, 
"seataserver", s.Name, "namespace", s.Namespace)
+       return nil
+}
+
+func (fm *FinalizerManager) removeFinalizerFromV1(ctx context.Context, s 
*seatav1.SeataServer, finalizerName string) error {
+       if !utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+               return nil // Finalizer already removed
+       }
+
+       fm.Log.Info("Removing finalizer", "name", finalizerName, "seataserver", 
s.Name, "namespace", s.Namespace)
+
+       s.ObjectMeta.Finalizers = utils.RemoveString(s.ObjectMeta.Finalizers, 
finalizerName)
+       if err := fm.Client.Update(ctx, s); err != nil {
+               fm.Log.Error(err, "Failed to remove finalizer", "seataserver", 
s.Name, "namespace", s.Namespace)
+               return err
+       }
+
+       fm.Log.Info("Finalizer removed successfully", "name", finalizerName, 
"seataserver", s.Name, "namespace", s.Namespace)
+       return nil
+}
+
+// Helper methods for v1alpha1 API
+
+func (fm *FinalizerManager) addFinalizerToV1Alpha1(ctx context.Context, s 
*seatav1alpha1.SeataServer, finalizerName string) error {
+       if utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+               return nil // Already has finalizer
+       }
+
+       fm.Log.Info("Adding finalizer", "name", finalizerName, "seataserver", 
s.Name, "namespace", s.Namespace)
+
+       s.ObjectMeta.Finalizers = append(s.ObjectMeta.Finalizers, finalizerName)
+       if err := fm.Client.Update(ctx, s); err != nil {
+               fm.Log.Error(err, "Failed to add finalizer", "seataserver", 
s.Name, "namespace", s.Namespace)
+               return err
+       }
+
+       fm.Log.Info("Finalizer added successfully", "name", finalizerName, 
"seataserver", s.Name, "namespace", s.Namespace)
+       return nil
+}
+
+func (fm *FinalizerManager) removeFinalizerFromV1Alpha1(ctx context.Context, s 
*seatav1alpha1.SeataServer, finalizerName string) error {
+       if !utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+               return nil // Finalizer already removed
+       }
+
+       fm.Log.Info("Removing finalizer", "name", finalizerName, "seataserver", 
s.Name, "namespace", s.Namespace)
+
+       s.ObjectMeta.Finalizers = utils.RemoveString(s.ObjectMeta.Finalizers, 
finalizerName)
+       if err := fm.Client.Update(ctx, s); err != nil {
+               fm.Log.Error(err, "Failed to remove finalizer", "seataserver", 
s.Name, "namespace", s.Namespace)
+               return err
+       }
+
+       fm.Log.Info("Finalizer removed successfully", "name", finalizerName, 
"seataserver", s.Name, "namespace", s.Namespace)
+       return nil
+}
diff --git a/pkg/finalizer/finalizer_manager_test.go 
b/pkg/finalizer/finalizer_manager_test.go
new file mode 100644
index 0000000..8e8f290
--- /dev/null
+++ b/pkg/finalizer/finalizer_manager_test.go
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package finalizer
+
+import (
+       "context"
+       seatav1 "github.com/apache/seata-k8s/api/v1"
+       "github.com/go-logr/logr"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/types"
+       "sigs.k8s.io/controller-runtime/pkg/client/fake"
+       "testing"
+       "time"
+)
+
+func TestFinalizerManager_AddFinalizer(t *testing.T) {
+       testCases := []struct {
+               name        string
+               seataServer *seatav1.SeataServer
+               finalizer   string
+               expectError bool
+       }{
+               {
+                       name: "add finalizer to seataserver without finalizers",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: []string{},
+                               },
+                       },
+                       finalizer:   SeataFinalizerName,
+                       expectError: false,
+               },
+               {
+                       name: "add finalizer to seataserver with existing 
finalizers",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: []string{"other-finalizer"},
+                               },
+                       },
+                       finalizer:   SeataFinalizerName,
+                       expectError: false,
+               },
+               {
+                       name: "add duplicate finalizer",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: 
[]string{SeataFinalizerName},
+                               },
+                       },
+                       finalizer:   SeataFinalizerName,
+                       expectError: false,
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       scheme := createTestScheme()
+                       fakeClient := fake.NewClientBuilder().
+                               WithScheme(scheme).
+                               WithObjects(tc.seataServer).
+                               Build()
+
+                       fm := &FinalizerManager{
+                               Client: fakeClient,
+                               Log:    logr.Discard(),
+                       }
+
+                       ctx := context.Background()
+                       err := fm.AddFinalizer(ctx, tc.seataServer, 
tc.finalizer)
+
+                       if (err != nil) != tc.expectError {
+                               t.Errorf("expected error: %v, got: %v", 
tc.expectError, err)
+                       }
+
+                       if !tc.expectError {
+                               if !fm.HasFinalizer(tc.seataServer, 
tc.finalizer) {
+                                       t.Errorf("finalizer %s not found after 
adding", tc.finalizer)
+                               }
+                       }
+               })
+       }
+}
+
+func TestFinalizerManager_RemoveFinalizer(t *testing.T) {
+       testCases := []struct {
+               name        string
+               seataServer *seatav1.SeataServer
+               finalizer   string
+               expectError bool
+               expectFound bool
+       }{
+               {
+                       name: "remove existing finalizer",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: 
[]string{SeataFinalizerName, "other-finalizer"},
+                               },
+                       },
+                       finalizer:   SeataFinalizerName,
+                       expectError: false,
+                       expectFound: false,
+               },
+               {
+                       name: "remove non-existing finalizer",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: []string{"other-finalizer"},
+                               },
+                       },
+                       finalizer:   SeataFinalizerName,
+                       expectError: false,
+                       expectFound: false,
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       scheme := createTestScheme()
+                       fakeClient := fake.NewClientBuilder().
+                               WithScheme(scheme).
+                               WithObjects(tc.seataServer).
+                               Build()
+
+                       fm := &FinalizerManager{
+                               Client: fakeClient,
+                               Log:    logr.Discard(),
+                       }
+
+                       ctx := context.Background()
+                       err := fm.RemoveFinalizer(ctx, tc.seataServer, 
tc.finalizer)
+
+                       if (err != nil) != tc.expectError {
+                               t.Errorf("expected error: %v, got: %v", 
tc.expectError, err)
+                       }
+
+                       hasFinalizer := fm.HasFinalizer(tc.seataServer, 
tc.finalizer)
+                       if hasFinalizer != tc.expectFound {
+                               t.Errorf("expected finalizer found: %v, got: 
%v", tc.expectFound, hasFinalizer)
+                       }
+               })
+       }
+}
+
+func TestFinalizerManager_HasFinalizer(t *testing.T) {
+       testCases := []struct {
+               name        string
+               seataServer *seatav1.SeataServer
+               finalizer   string
+               expected    bool
+       }{
+               {
+                       name: "finalizer exists",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: 
[]string{SeataFinalizerName},
+                               },
+                       },
+                       finalizer: SeataFinalizerName,
+                       expected:  true,
+               },
+               {
+                       name: "finalizer does not exist",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: []string{"other-finalizer"},
+                               },
+                       },
+                       finalizer: SeataFinalizerName,
+                       expected:  false,
+               },
+               {
+                       name: "no finalizers",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:       "test-seata",
+                                       Namespace:  "default",
+                                       Finalizers: []string{},
+                               },
+                       },
+                       finalizer: SeataFinalizerName,
+                       expected:  false,
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       fm := &FinalizerManager{
+                               Client: nil,
+                               Log:    logr.Discard(),
+                       }
+
+                       result := fm.HasFinalizer(tc.seataServer, tc.finalizer)
+                       if result != tc.expected {
+                               t.Errorf("expected %v, got %v", tc.expected, 
result)
+                       }
+               })
+       }
+}
+
+func TestFinalizerManager_IsBeingDeleted(t *testing.T) {
+       testCases := []struct {
+               name        string
+               seataServer *seatav1.SeataServer
+               expected    bool
+       }{
+               {
+                       name: "seataserver not being deleted",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:      "test-seata",
+                                       Namespace: "default",
+                               },
+                       },
+                       expected: false,
+               },
+               {
+                       name: "seataserver not being deleted",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:              "test-seata",
+                                       Namespace:         "default",
+                                       DeletionTimestamp: &metav1.Time{Time: 
time.Time{}},
+                               },
+                       },
+                       expected: false,
+               },
+               {
+                       name: "seataserver being deleted",
+                       seataServer: &seatav1.SeataServer{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name:      "test-seata",
+                                       Namespace: "default",
+                                       DeletionTimestamp: &metav1.Time{
+                                               Time: time.Now(),
+                                       },
+                               },
+                       },
+                       expected: true,
+               },
+       }
+
+       for _, tc := range testCases {
+               t.Run(tc.name, func(t *testing.T) {
+                       fm := &FinalizerManager{
+                               Client: nil,
+                               Log:    logr.Discard(),
+                       }
+
+                       result := fm.IsBeingDeleted(tc.seataServer)
+                       if result != tc.expected {
+                               t.Errorf("expected %v, got %v", tc.expected, 
result)
+                       }
+               })
+       }
+}
+
+func TestFinalizerManager_GetNamespacedName(t *testing.T) {
+       seataServer := &seatav1.SeataServer{
+               ObjectMeta: metav1.ObjectMeta{
+                       Name:      "test-seata",
+                       Namespace: "default",
+               },
+       }
+
+       fm := &FinalizerManager{
+               Client: nil,
+               Log:    logr.Discard(),
+       }
+
+       result := fm.GetNamespacedName(seataServer)
+       expected := types.NamespacedName{Name: "test-seata", Namespace: 
"default"}
+
+       if result != expected {
+               t.Errorf("expected %v, got %v", expected, result)
+       }
+}
+
+func createTestScheme() *runtime.Scheme {
+       scheme := fake.NewClientBuilder().Build().Scheme()
+       _ = seatav1.AddToScheme(scheme)
+       return scheme
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to