@@ -17,11 +17,20 @@ limitations under the License.
1717package user
1818
1919import (
20+ "context"
2021 "testing"
2122
2223 "github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users"
23- orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
24+ "go.uber.org/mock/gomock"
25+ corev1 "k8s.io/api/core/v1"
26+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+ "k8s.io/apimachinery/pkg/runtime"
2428 "k8s.io/utils/ptr"
29+ "sigs.k8s.io/controller-runtime/pkg/client"
30+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
31+
32+ orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
33+ "github.com/k-orc/openstack-resource-controller/v2/internal/osclients/mock"
2534)
2635
2736func TestNeedsUpdate (t * testing.T ) {
@@ -117,3 +126,196 @@ func TestHandleDescriptionUpdate(t *testing.T) {
117126
118127 }
119128}
129+
130+ func TestHandleEnabledUpdate (t * testing.T ) {
131+ ptrToBool := ptr .To [bool ]
132+ testCases := []struct {
133+ name string
134+ newValue * bool
135+ existingValue bool
136+ expectChange bool
137+ }{
138+ {name : "Identical" , newValue : ptrToBool (true ), existingValue : true , expectChange : false },
139+ {name : "Different" , newValue : ptrToBool (true ), existingValue : false , expectChange : true },
140+ {name : "No value provided, existing is set" , newValue : nil , existingValue : false , expectChange : true },
141+ {name : "No value provided, existing is default" , newValue : nil , existingValue : true , expectChange : false },
142+ }
143+
144+ for _ , tt := range testCases {
145+ t .Run (tt .name , func (t * testing.T ) {
146+ resource := & orcv1alpha1.UserResourceSpec {Enabled : tt .newValue }
147+ osResource := & users.User {Enabled : tt .existingValue }
148+
149+ updateOpts := users.UpdateOpts {}
150+ handleEnabledUpdate (& updateOpts , resource , osResource )
151+
152+ got , _ := needsUpdate (updateOpts )
153+ if got != tt .expectChange {
154+ t .Errorf ("Expected change: %v, got: %v" , tt .expectChange , got )
155+ }
156+ })
157+ }
158+ }
159+
160+ func TestReconcilePassword (t * testing.T ) {
161+ ptrToPasswordRef := ptr .To [orcv1alpha1 .KubernetesNameRef ]
162+ testCases := []struct {
163+ name string
164+ orcObject * orcv1alpha1.User
165+ osResource * users.User
166+ secret * corev1.Secret
167+ setupMock func (* mock.MockUserClientMockRecorder )
168+ wantReschedule bool
169+ wantErr bool
170+ }{
171+ {
172+ name : "No password ref set" ,
173+ orcObject : & orcv1alpha1.User {
174+ Spec : orcv1alpha1.UserSpec {
175+ Resource : & orcv1alpha1.UserResourceSpec {},
176+ },
177+ },
178+ osResource : & users.User {ID : "user-id" },
179+ wantReschedule : false ,
180+ wantErr : false ,
181+ },
182+ {
183+ name : "Resource is nil" ,
184+ orcObject : & orcv1alpha1.User {
185+ Spec : orcv1alpha1.UserSpec {},
186+ },
187+ osResource : & users.User {ID : "user-id" },
188+ wantReschedule : false ,
189+ wantErr : false ,
190+ },
191+ {
192+ name : "Password ref unchanged" ,
193+ orcObject : & orcv1alpha1.User {
194+ Spec : orcv1alpha1.UserSpec {
195+ Resource : & orcv1alpha1.UserResourceSpec {
196+ PasswordRef : ptrToPasswordRef ("my-secret" ),
197+ },
198+ },
199+ Status : orcv1alpha1.UserStatus {
200+ Resource : & orcv1alpha1.UserResourceStatus {
201+ AppliedPasswordRef : "my-secret" ,
202+ },
203+ },
204+ },
205+ osResource : & users.User {ID : "user-id" },
206+ wantReschedule : false ,
207+ wantErr : false ,
208+ },
209+ {
210+ name : "First password set - no UpdateUser call" ,
211+ orcObject : & orcv1alpha1.User {
212+ ObjectMeta : metav1.ObjectMeta {
213+ Name : "test-user" ,
214+ Namespace : "test-ns" ,
215+ UID : "test-uid" ,
216+ },
217+ Spec : orcv1alpha1.UserSpec {
218+ Resource : & orcv1alpha1.UserResourceSpec {
219+ PasswordRef : ptrToPasswordRef ("my-secret" ),
220+ },
221+ },
222+ Status : orcv1alpha1.UserStatus {
223+ Resource : & orcv1alpha1.UserResourceStatus {
224+ AppliedPasswordRef : "" ,
225+ },
226+ },
227+ },
228+ osResource : & users.User {ID : "user-id" },
229+ secret : & corev1.Secret {
230+ ObjectMeta : metav1.ObjectMeta {
231+ Name : "my-secret" ,
232+ Namespace : "test-ns" ,
233+ },
234+ Data : map [string ][]byte {
235+ "password" : []byte ("mypassword123" ),
236+ },
237+ },
238+ // No UpdateUser call expected on first reconcile
239+ setupMock : func (recorder * mock.MockUserClientMockRecorder ) {},
240+ wantReschedule : false ,
241+ wantErr : false ,
242+ },
243+ {
244+ name : "Password changed - UpdateUser called" ,
245+ orcObject : & orcv1alpha1.User {
246+ ObjectMeta : metav1.ObjectMeta {
247+ Name : "test-user" ,
248+ Namespace : "test-ns" ,
249+ UID : "test-uid" ,
250+ },
251+ Spec : orcv1alpha1.UserSpec {
252+ Resource : & orcv1alpha1.UserResourceSpec {
253+ PasswordRef : ptrToPasswordRef ("my-secret" ),
254+ },
255+ },
256+ Status : orcv1alpha1.UserStatus {
257+ Resource : & orcv1alpha1.UserResourceStatus {
258+ AppliedPasswordRef : "old-secret" ,
259+ },
260+ },
261+ },
262+ osResource : & users.User {ID : "user-id" },
263+ secret : & corev1.Secret {
264+ ObjectMeta : metav1.ObjectMeta {
265+ Name : "my-secret" ,
266+ Namespace : "test-ns" ,
267+ },
268+ Data : map [string ][]byte {
269+ "password" : []byte ("newpassword456" ),
270+ },
271+ },
272+ setupMock : func (recorder * mock.MockUserClientMockRecorder ) {
273+ recorder .UpdateUser (gomock .Any (), "user-id" , gomock .Any ()).Return (& users.User {}, nil )
274+ },
275+ wantReschedule : true , // NeedsRefresh returns true
276+ wantErr : false ,
277+ },
278+ }
279+
280+ for _ , tt := range testCases {
281+ t .Run (tt .name , func (t * testing.T ) {
282+ mockctrl := gomock .NewController (t )
283+ userClient := mock .NewMockUserClient (mockctrl )
284+
285+ // Create fake k8s client
286+ scheme := runtime .NewScheme ()
287+ _ = corev1 .AddToScheme (scheme )
288+ _ = orcv1alpha1 .AddToScheme (scheme )
289+
290+ objects := []client.Object {tt .orcObject }
291+ if tt .secret != nil {
292+ objects = append (objects , tt .secret )
293+ }
294+
295+ k8sClient := fake .NewClientBuilder ().
296+ WithScheme (scheme ).
297+ WithObjects (objects ... ).
298+ WithStatusSubresource (& orcv1alpha1.User {}).
299+ Build ()
300+
301+ actuator := userActuator {
302+ osClient : userClient ,
303+ k8sClient : k8sClient ,
304+ }
305+
306+ if tt .setupMock != nil {
307+ tt .setupMock (userClient .EXPECT ())
308+ }
309+
310+ reconcileStatus := actuator .reconcilePassword (context .TODO (), tt .orcObject , tt .osResource )
311+
312+ needsReschedule , err := reconcileStatus .NeedsReschedule ()
313+ if (err != nil ) != tt .wantErr {
314+ t .Errorf ("reconcilePassword() error = %v, wantErr %v" , err , tt .wantErr )
315+ }
316+ if needsReschedule != tt .wantReschedule {
317+ t .Errorf ("reconcilePassword() needsReschedule = %v, want %v" , needsReschedule , tt .wantReschedule )
318+ }
319+ })
320+ }
321+ }
0 commit comments