Skip to content

Commit be5257f

Browse files
authored
Merge pull request #731 from dlaw4608/user_unit_tests
Keystone User: Mutability tests for enabled and passwordRef fields
2 parents acf8db0 + ec11043 commit be5257f

1 file changed

Lines changed: 203 additions & 1 deletion

File tree

internal/controllers/user/actuator_test.go

Lines changed: 203 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@ limitations under the License.
1717
package user
1818

1919
import (
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

2736
func 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

Comments
 (0)