@@ -36,6 +36,7 @@ import (
3636 "k8s.io/apimachinery/pkg/util/uuid"
3737 dynamicfake "k8s.io/client-go/dynamic/fake"
3838 "k8s.io/client-go/kubernetes/scheme"
39+ "k8s.io/client-go/tools/cache"
3940 "k8s.io/client-go/tools/record"
4041 "k8s.io/utils/ptr"
4142 controllerruntime "sigs.k8s.io/controller-runtime"
@@ -1096,3 +1097,113 @@ func TestWorkStatusController_interpretHealth(t *testing.T) {
10961097 })
10971098 }
10981099}
1100+
1101+ type TestObject struct {
1102+ metav1.TypeMeta
1103+ metav1.ObjectMeta
1104+ Spec string
1105+ }
1106+
1107+ func (in * TestObject ) DeepCopyObject () runtime.Object {
1108+ return & TestObject {
1109+ TypeMeta : in .TypeMeta ,
1110+ ObjectMeta : in .ObjectMeta ,
1111+ Spec : in .Spec ,
1112+ }
1113+ }
1114+
1115+ // mockAsyncWorker implements util.AsyncPriorityWorker for testing onAdd/onUpdate/onDelete behavior.
1116+ type mockAsyncWorker struct {
1117+ receivedObjs []any // objects passed via EnqueueWithOpts (onAdd)
1118+ receivedPriorities []* int // priorities captured from EnqueueWithOpts
1119+ enqueuedObjs []any // objects passed via Enqueue (onUpdate/onDelete)
1120+ }
1121+
1122+ func (m * mockAsyncWorker ) EnqueueWithOpts (opts util.AddOpts , obj any ) { // capture inputs for onAdd
1123+ m .receivedObjs = append (m .receivedObjs , obj )
1124+ m .receivedPriorities = append (m .receivedPriorities , opts .Priority )
1125+ }
1126+ func (m * mockAsyncWorker ) AddWithOpts (_ util.AddOpts , _ ... any ) {}
1127+ func (m * mockAsyncWorker ) Add (_ interface {}) {}
1128+ func (m * mockAsyncWorker ) AddAfter (_ interface {}, _ time.Duration ) {}
1129+ func (m * mockAsyncWorker ) Enqueue (obj interface {}) { // capture inputs for onUpdate/onDelete
1130+ m .enqueuedObjs = append (m .enqueuedObjs , obj )
1131+ }
1132+ func (m * mockAsyncWorker ) Run (_ context.Context , _ int ) {}
1133+
1134+ func TestWorkStatusController_onAdd (t * testing.T ) {
1135+ cluster := newCluster ("cluster" , clusterv1alpha1 .ClusterConditionReady , metav1 .ConditionTrue )
1136+ c := newWorkStatusController (cluster )
1137+ mockWorker := & mockAsyncWorker {}
1138+ c .worker = mockWorker
1139+
1140+ obj := & TestObject {ObjectMeta : metav1.ObjectMeta {Name : "test-obj" }}
1141+
1142+ t .Run ("in initial list -> low priority" , func (t * testing.T ) {
1143+ c .onAdd (obj , true )
1144+ assert .Equal (t , 1 , len (mockWorker .receivedObjs ))
1145+ assert .Equal (t , obj , mockWorker .receivedObjs [0 ])
1146+ if assert .NotNil (t , mockWorker .receivedPriorities [0 ]) {
1147+ assert .Equal (t , util .LowPriority , * mockWorker .receivedPriorities [0 ])
1148+ }
1149+ })
1150+
1151+ t .Run ("not in initial list -> nil priority" , func (t * testing.T ) {
1152+ c .onAdd (obj , false )
1153+ assert .Equal (t , 2 , len (mockWorker .receivedObjs ))
1154+ assert .Equal (t , obj , mockWorker .receivedObjs [1 ])
1155+ assert .Nil (t , mockWorker .receivedPriorities [1 ])
1156+ })
1157+ }
1158+
1159+ func TestWorkStatusController_onUpdate (t * testing.T ) {
1160+ cluster := newCluster ("cluster" , clusterv1alpha1 .ClusterConditionReady , metav1 .ConditionTrue )
1161+ c := newWorkStatusController (cluster )
1162+ mockWorker := & mockAsyncWorker {}
1163+ c .worker = mockWorker
1164+
1165+ oldObj := & TestObject {ObjectMeta : metav1.ObjectMeta {Name : "test-obj" }, Spec : "same" }
1166+ // Deep copy same content
1167+ curSame := oldObj .DeepCopyObject ().(* TestObject )
1168+ // Different spec
1169+ curDiff := & TestObject {ObjectMeta : metav1.ObjectMeta {Name : "test-obj" }, Spec : "different" }
1170+
1171+ t .Run ("objects equal -> no enqueue" , func (t * testing.T ) {
1172+ c .onUpdate (oldObj , curSame )
1173+ assert .Equal (t , 0 , len (mockWorker .enqueuedObjs ))
1174+ })
1175+
1176+ t .Run ("objects differ -> enqueue" , func (t * testing.T ) {
1177+ c .onUpdate (oldObj , curDiff )
1178+ assert .Equal (t , 1 , len (mockWorker .enqueuedObjs ))
1179+ assert .Equal (t , curDiff , mockWorker .enqueuedObjs [0 ])
1180+ })
1181+ }
1182+
1183+ func TestWorkStatusController_onDelete (t * testing.T ) {
1184+ cluster := newCluster ("cluster" , clusterv1alpha1 .ClusterConditionReady , metav1 .ConditionTrue )
1185+ c := newWorkStatusController (cluster )
1186+ mockWorker := & mockAsyncWorker {}
1187+ c .worker = mockWorker
1188+
1189+ obj := & TestObject {ObjectMeta : metav1.ObjectMeta {Name : "to-delete" }}
1190+
1191+ t .Run ("direct object -> enqueue" , func (t * testing.T ) {
1192+ c .onDelete (obj )
1193+ assert .Equal (t , 1 , len (mockWorker .enqueuedObjs ))
1194+ assert .Equal (t , obj , mockWorker .enqueuedObjs [0 ])
1195+ })
1196+
1197+ t .Run ("DeletedFinalStateUnknown wrapper -> enqueue" , func (t * testing.T ) {
1198+ wrapped := cache.DeletedFinalStateUnknown {Obj : & TestObject {ObjectMeta : metav1.ObjectMeta {Name : "wrapped" }}}
1199+ c .onDelete (wrapped )
1200+ assert .Equal (t , 2 , len (mockWorker .enqueuedObjs ))
1201+ assert .Equal (t , "wrapped" , mockWorker .enqueuedObjs [1 ].(* TestObject ).Name )
1202+ })
1203+
1204+ t .Run ("DeletedFinalStateUnknown with nil Obj -> no additional enqueue" , func (t * testing.T ) {
1205+ wrappedNil := cache.DeletedFinalStateUnknown {Obj : nil }
1206+ c .onDelete (wrappedNil )
1207+ assert .Equal (t , 2 , len (mockWorker .enqueuedObjs )) // unchanged
1208+ })
1209+ }
0 commit comments