@@ -20,29 +20,36 @@ import (
2020 "context"
2121 "fmt"
2222 "testing"
23+ "time"
2324
2425 "github.com/stretchr/testify/assert"
2526 corev1 "k8s.io/api/core/v1"
2627 apierrors "k8s.io/apimachinery/pkg/api/errors"
28+ "k8s.io/apimachinery/pkg/api/meta"
2729 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830 "k8s.io/apimachinery/pkg/runtime"
2931 "k8s.io/apimachinery/pkg/types"
3032 "sigs.k8s.io/controller-runtime/pkg/client"
3133 "sigs.k8s.io/controller-runtime/pkg/client/fake"
3234 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
35+
36+ workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1"
3337)
3438
3539func TestUpdateStatus (t * testing.T ) {
3640 scheme := runtime .NewScheme ()
3741 _ = corev1 .AddToScheme (scheme )
42+ _ = workv1alpha1 .AddToScheme (scheme )
3843
3944 tests := []struct {
4045 name string
41- setupObj * corev1.Pod
42- mutateStatus func (* corev1.Pod )
46+ setupObj client.Object
47+ updateObj client.Object
48+ mutateStatus func (client.Object )
4349 statusError bool
4450 expectedOp controllerutil.OperationResult
4551 expectedError string
52+ verify func (* testing.T , client.Client )
4653 }{
4754 {
4855 name : "successful status update" ,
@@ -55,12 +62,24 @@ func TestUpdateStatus(t *testing.T) {
5562 Phase : corev1 .PodPending ,
5663 },
5764 },
58- mutateStatus : func (pod * corev1.Pod ) {
65+ updateObj : & corev1.Pod {
66+ ObjectMeta : metav1.ObjectMeta {
67+ Name : "test-pod" ,
68+ Namespace : "default" ,
69+ },
70+ },
71+ mutateStatus : func (obj client.Object ) {
72+ pod := obj .(* corev1.Pod )
5973 pod .Status .Phase = corev1 .PodRunning
6074 },
6175 statusError : false ,
6276 expectedOp : controllerutil .OperationResultUpdatedStatusOnly ,
6377 expectedError : "" ,
78+ verify : func (t * testing.T , c client.Client ) {
79+ pod := & corev1.Pod {}
80+ assert .NoError (t , c .Get (context .TODO (), types.NamespacedName {Name : "test-pod" , Namespace : "default" }, pod ))
81+ assert .Equal (t , corev1 .PodRunning , pod .Status .Phase )
82+ },
6483 },
6584 {
6685 name : "status update error" ,
@@ -73,15 +92,22 @@ func TestUpdateStatus(t *testing.T) {
7392 Phase : corev1 .PodPending ,
7493 },
7594 },
76- mutateStatus : func (pod * corev1.Pod ) {
95+ updateObj : & corev1.Pod {
96+ ObjectMeta : metav1.ObjectMeta {
97+ Name : "test-pod" ,
98+ Namespace : "default" ,
99+ },
100+ },
101+ mutateStatus : func (obj client.Object ) {
102+ pod := obj .(* corev1.Pod )
77103 pod .Status .Phase = corev1 .PodRunning
78104 },
79105 statusError : true ,
80106 expectedOp : controllerutil .OperationResultNone ,
81107 expectedError : "Internal error occurred: status update failed" ,
82108 },
83109 {
84- name : "no changes needed " ,
110+ name : "no changes, should still update " ,
85111 setupObj : & corev1.Pod {
86112 ObjectMeta : metav1.ObjectMeta {
87113 Name : "test-pod" ,
@@ -91,20 +117,80 @@ func TestUpdateStatus(t *testing.T) {
91117 Phase : corev1 .PodRunning ,
92118 },
93119 },
94- mutateStatus : func (_ * corev1.Pod ) {
95- // No changes to status
120+ updateObj : & corev1.Pod {
121+ ObjectMeta : metav1.ObjectMeta {
122+ Name : "test-pod" ,
123+ Namespace : "default" ,
124+ },
125+ },
126+ mutateStatus : func (_ client.Object ) {
127+ // No changes
96128 },
97129 statusError : false ,
98- expectedOp : controllerutil .OperationResultNone ,
130+ expectedOp : controllerutil .OperationResultUpdatedStatusOnly ,
99131 expectedError : "" ,
132+ verify : func (t * testing.T , c client.Client ) {
133+ pod := & corev1.Pod {}
134+ assert .NoError (t , c .Get (context .TODO (), types.NamespacedName {Name : "test-pod" , Namespace : "default" }, pod ))
135+ assert .Equal (t , corev1 .PodRunning , pod .Status .Phase )
136+ },
100137 },
101138 {
102- name : "object not found" ,
103- setupObj : nil , // Not create the object
104- mutateStatus : func (pod * corev1.Pod ) {
105- pod .Status .Phase = corev1 .PodRunning
139+ name : "update with same WorkApplied condition, should still update (issue #6858)" ,
140+ setupObj : & workv1alpha1.Work {
141+ ObjectMeta : metav1.ObjectMeta {
142+ Name : "test-work" ,
143+ Namespace : "default" ,
144+ },
145+ Status : workv1alpha1.WorkStatus {
146+ Conditions : []metav1.Condition {
147+ {
148+ Type : workv1alpha1 .WorkApplied ,
149+ Status : metav1 .ConditionTrue ,
150+ Reason : "AppliedSuccessful" ,
151+ Message : "Manifest has been successfully applied" ,
152+ LastTransitionTime : metav1.Time {Time : metav1 .Now ().Add (- 1 * time .Hour )},
153+ },
154+ },
155+ },
156+ },
157+ updateObj : & workv1alpha1.Work {
158+ ObjectMeta : metav1.ObjectMeta {
159+ Name : "test-work" ,
160+ Namespace : "default" ,
161+ },
162+ },
163+ mutateStatus : func (obj client.Object ) {
164+ meta .SetStatusCondition (& obj .(* workv1alpha1.Work ).Status .Conditions , metav1.Condition {
165+ Type : workv1alpha1 .WorkApplied ,
166+ Status : metav1 .ConditionTrue ,
167+ Reason : "AppliedSuccessful" ,
168+ Message : "Manifest has been successfully applied" ,
169+ LastTransitionTime : metav1 .Now (),
170+ })
171+ },
172+ expectedOp : controllerutil .OperationResultUpdatedStatusOnly ,
173+ verify : func (t * testing.T , c client.Client ) {
174+ work := & workv1alpha1.Work {}
175+ assert .NoError (t , c .Get (context .TODO (), types.NamespacedName {Name : "test-work" , Namespace : "default" }, work ))
176+
177+ cond := meta .FindStatusCondition (work .Status .Conditions , workv1alpha1 .WorkApplied )
178+ assert .NotNil (t , cond )
179+ assert .Equal (t , metav1 .ConditionTrue , cond .Status )
180+ assert .Equal (t , "AppliedSuccessful" , cond .Reason )
181+ },
182+ },
183+ {
184+ name : "object not found" ,
185+ updateObj : & corev1.Pod {
186+ ObjectMeta : metav1.ObjectMeta {
187+ Name : "test-pod" ,
188+ Namespace : "default" ,
189+ },
190+ },
191+ mutateStatus : func (obj client.Object ) {
192+ obj .(* corev1.Pod ).Status .Phase = corev1 .PodRunning
106193 },
107- statusError : false ,
108194 expectedOp : controllerutil .OperationResultNone ,
109195 expectedError : "not found" ,
110196 },
@@ -116,9 +202,15 @@ func TestUpdateStatus(t *testing.T) {
116202 Namespace : "default" ,
117203 },
118204 },
119- mutateStatus : func (pod * corev1.Pod ) {
205+ updateObj : & corev1.Pod {
206+ ObjectMeta : metav1.ObjectMeta {
207+ Name : "test-pod" ,
208+ Namespace : "default" ,
209+ },
210+ },
211+ mutateStatus : func (obj client.Object ) {
120212 // Simulate mutation error by changing name
121- pod . Name = "different-name"
213+ obj . SetName ( "different-name" )
122214 },
123215 statusError : false ,
124216 expectedOp : controllerutil .OperationResultNone ,
@@ -131,31 +223,19 @@ func TestUpdateStatus(t *testing.T) {
131223 clientBuilder := fake .NewClientBuilder ().WithScheme (scheme )
132224 if tt .setupObj != nil {
133225 clientBuilder = clientBuilder .WithObjects (tt .setupObj )
134- }
135- fakeClient := clientBuilder .Build ()
136-
137- var client client.Client
138- if tt .statusError {
139- client = & mockClient {
140- Client : fakeClient ,
141- shouldError : true ,
226+ if _ , ok := tt .setupObj .(* workv1alpha1.Work ); ok {
227+ clientBuilder = clientBuilder .WithStatusSubresource (tt .setupObj )
142228 }
143- } else {
144- client = fakeClient
145229 }
146230
147- // Create a new object for update
148- obj := & corev1.Pod {
149- ObjectMeta : metav1.ObjectMeta {
150- Name : "test-pod" ,
151- Namespace : "default" ,
152- },
231+ c := client .Client (clientBuilder .Build ())
232+ if tt .statusError {
233+ c = & mockClient {Client : c , shouldError : true }
153234 }
154235
155- // Run the update
156- op , err := UpdateStatus (context .TODO (), client , obj , func () error {
236+ op , err := UpdateStatus (context .TODO (), c , tt .updateObj , func () error {
157237 if tt .mutateStatus != nil {
158- tt .mutateStatus (obj )
238+ tt .mutateStatus (tt . updateObj )
159239 }
160240 return nil
161241 })
@@ -171,12 +251,8 @@ func TestUpdateStatus(t *testing.T) {
171251 // Check operation result
172252 assert .Equal (t , tt .expectedOp , op )
173253
174- // If successful update, verify the status was actually changed
175- if tt .expectedOp == controllerutil .OperationResultUpdatedStatusOnly {
176- updatedPod := & corev1.Pod {}
177- err := client .Get (context .TODO (), types.NamespacedName {Name : "test-pod" , Namespace : "default" }, updatedPod )
178- assert .NoError (t , err )
179- assert .Equal (t , corev1 .PodRunning , updatedPod .Status .Phase )
254+ if tt .verify != nil {
255+ tt .verify (t , c )
180256 }
181257 })
182258 }
@@ -359,6 +435,13 @@ func (m *mockStatusWriter) Update(_ context.Context, _ client.Object, _ ...clien
359435 return nil
360436}
361437
438+ func (m * mockStatusWriter ) Patch (_ context.Context , _ client.Object , _ client.Patch , _ ... client.SubResourcePatchOption ) error {
439+ if m .shouldError {
440+ return apierrors .NewInternalError (fmt .Errorf ("status update failed" ))
441+ }
442+ return nil
443+ }
444+
362445// mockClient wraps the fake client and returns our mock status writer
363446type mockClient struct {
364447 client.Client
0 commit comments