@@ -3,9 +3,21 @@ package catalog
33import (
44 "testing"
55
6+ "github.com/golang/mock/gomock"
67 "github.com/stretchr/testify/assert"
8+ "github.com/stretchr/testify/require"
79
10+ corev1 "k8s.io/api/core/v1"
11+ apierrors "k8s.io/apimachinery/pkg/api/errors"
812 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+ "k8s.io/apimachinery/pkg/runtime"
14+ fakedynamic "k8s.io/client-go/dynamic/fake"
15+ k8sfake "k8s.io/client-go/kubernetes/fake"
16+ k8stesting "k8s.io/client-go/testing"
17+
18+ "github.com/operator-framework/api/pkg/operators/v1alpha1"
19+ "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake"
20+ "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient/operatorclientmocks"
921)
1022
1123func TestMergedOwnerReferences (t * testing.T ) {
@@ -188,3 +200,227 @@ func TestMergedOwnerReferences(t *testing.T) {
188200 })
189201 }
190202}
203+
204+ func TestEnsureServiceAccount (t * testing.T ) {
205+ namespace := "test-namespace"
206+ saName := "test-sa"
207+
208+ tests := []struct {
209+ name string
210+ existingServiceAccount * corev1.ServiceAccount
211+ newServiceAccount * corev1.ServiceAccount
212+ expectedAnnotations map [string ]string
213+ expectedStatus v1alpha1.StepStatus
214+ expectError bool
215+ createError error
216+ getError error
217+ updateError error
218+ }{
219+ {
220+ name : "create new service account" ,
221+ newServiceAccount : & corev1.ServiceAccount {
222+ ObjectMeta : metav1.ObjectMeta {
223+ Name : saName ,
224+ Namespace : namespace ,
225+ Annotations : map [string ]string {
226+ "new-annotation" : "new-value" ,
227+ },
228+ },
229+ },
230+ expectedAnnotations : map [string ]string {
231+ "new-annotation" : "new-value" ,
232+ },
233+ expectedStatus : v1alpha1 .StepStatusCreated ,
234+ },
235+ {
236+ name : "update existing service account - preserve existing annotations" ,
237+ existingServiceAccount : & corev1.ServiceAccount {
238+ ObjectMeta : metav1.ObjectMeta {
239+ Name : saName ,
240+ Namespace : namespace ,
241+ Annotations : map [string ]string {
242+ "existing-annotation" : "existing-value" ,
243+ "override-annotation" : "old-value" ,
244+ },
245+ },
246+ Secrets : []corev1.ObjectReference {
247+ {Name : "existing-secret" },
248+ },
249+ },
250+ newServiceAccount : & corev1.ServiceAccount {
251+ ObjectMeta : metav1.ObjectMeta {
252+ Name : saName ,
253+ Namespace : namespace ,
254+ Annotations : map [string ]string {
255+ "new-annotation" : "new-value" ,
256+ "override-annotation" : "new-value" ,
257+ },
258+ },
259+ },
260+ expectedAnnotations : map [string ]string {
261+ "existing-annotation" : "existing-value" ,
262+ "new-annotation" : "new-value" ,
263+ "override-annotation" : "new-value" ,
264+ },
265+ expectedStatus : v1alpha1 .StepStatusPresent ,
266+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
267+ },
268+ {
269+ name : "update existing service account - no annotations on new SA" ,
270+ existingServiceAccount : & corev1.ServiceAccount {
271+ ObjectMeta : metav1.ObjectMeta {
272+ Name : saName ,
273+ Namespace : namespace ,
274+ Annotations : map [string ]string {
275+ "existing-annotation" : "existing-value" ,
276+ },
277+ },
278+ },
279+ newServiceAccount : & corev1.ServiceAccount {
280+ ObjectMeta : metav1.ObjectMeta {
281+ Name : saName ,
282+ Namespace : namespace ,
283+ },
284+ },
285+ expectedAnnotations : map [string ]string {
286+ "existing-annotation" : "existing-value" ,
287+ },
288+ expectedStatus : v1alpha1 .StepStatusPresent ,
289+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
290+ },
291+ {
292+ name : "update existing service account - preserve secrets" ,
293+ existingServiceAccount : & corev1.ServiceAccount {
294+ ObjectMeta : metav1.ObjectMeta {
295+ Name : saName ,
296+ Namespace : namespace ,
297+ },
298+ Secrets : []corev1.ObjectReference {
299+ {Name : "secret-1" },
300+ {Name : "secret-2" },
301+ },
302+ },
303+ newServiceAccount : & corev1.ServiceAccount {
304+ ObjectMeta : metav1.ObjectMeta {
305+ Name : saName ,
306+ Namespace : namespace ,
307+ },
308+ },
309+ expectedAnnotations : map [string ]string {},
310+ expectedStatus : v1alpha1 .StepStatusPresent ,
311+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
312+ },
313+ {
314+ name : "create error - not already exists" ,
315+ newServiceAccount : & corev1.ServiceAccount {
316+ ObjectMeta : metav1.ObjectMeta {
317+ Name : saName ,
318+ Namespace : namespace ,
319+ },
320+ },
321+ createError : apierrors .NewInternalError (assert .AnError ),
322+ expectError : true ,
323+ },
324+ {
325+ name : "update error - get existing fails" ,
326+ existingServiceAccount : & corev1.ServiceAccount {
327+ ObjectMeta : metav1.ObjectMeta {
328+ Name : saName ,
329+ Namespace : namespace ,
330+ },
331+ },
332+ newServiceAccount : & corev1.ServiceAccount {
333+ ObjectMeta : metav1.ObjectMeta {
334+ Name : saName ,
335+ Namespace : namespace ,
336+ },
337+ },
338+ createError : apierrors .NewAlreadyExists (corev1 .Resource ("serviceaccounts" ), saName ),
339+ getError : apierrors .NewInternalError (assert .AnError ),
340+ expectError : true ,
341+ },
342+ }
343+
344+ for _ , tc := range tests {
345+ t .Run (tc .name , func (t * testing.T ) {
346+ ctrl := gomock .NewController (t )
347+ defer ctrl .Finish ()
348+
349+ // Create mock client
350+ mockClient := operatorclientmocks .NewMockClientInterface (ctrl )
351+
352+ // Create fake kubernetes client
353+ var objects []runtime.Object
354+ if tc .existingServiceAccount != nil {
355+ objects = append (objects , tc .existingServiceAccount )
356+ }
357+
358+ fakeClient := k8sfake .NewSimpleClientset (objects ... )
359+
360+ // Setup expectations
361+ mockClient .EXPECT ().KubernetesInterface ().Return (fakeClient ).AnyTimes ()
362+
363+ // Mock the create call
364+ if tc .createError != nil {
365+ // We need to intercept the create call and return the error
366+ fakeClient .PrependReactor ("create" , "serviceaccounts" , func (action k8stesting.Action ) (handled bool , ret runtime.Object , err error ) {
367+ return true , nil , tc .createError
368+ })
369+ }
370+
371+ // Mock the get call if needed
372+ if tc .getError != nil {
373+ fakeClient .PrependReactor ("get" , "serviceaccounts" , func (action k8stesting.Action ) (handled bool , ret runtime.Object , err error ) {
374+ return true , nil , tc .getError
375+ })
376+ }
377+
378+ // Mock UpdateServiceAccount if the test expects an update
379+ if tc .createError != nil && apierrors .IsAlreadyExists (tc .createError ) && tc .getError == nil {
380+ // Calculate expected SA after merge
381+ expectedSA := tc .newServiceAccount .DeepCopy ()
382+ if tc .existingServiceAccount != nil {
383+ expectedSA .Secrets = tc .existingServiceAccount .Secrets
384+ // Merge annotations
385+ if expectedSA .Annotations == nil {
386+ expectedSA .Annotations = make (map [string ]string )
387+ }
388+ for k , v := range tc .existingServiceAccount .Annotations {
389+ if _ , ok := expectedSA .Annotations [k ]; ! ok {
390+ expectedSA .Annotations [k ] = v
391+ }
392+ }
393+ }
394+ expectedSA .SetNamespace (namespace )
395+
396+ mockClient .EXPECT ().UpdateServiceAccount (gomock .Any ()).DoAndReturn (func (sa * corev1.ServiceAccount ) (* corev1.ServiceAccount , error ) {
397+ // Verify the merged service account has the expected annotations
398+ assert .Equal (t , tc .expectedAnnotations , sa .Annotations )
399+ // Verify secrets were preserved if they existed
400+ if tc .existingServiceAccount != nil && len (tc .existingServiceAccount .Secrets ) > 0 {
401+ assert .Equal (t , tc .existingServiceAccount .Secrets , sa .Secrets )
402+ }
403+ return sa , tc .updateError
404+ }).MaxTimes (1 )
405+ }
406+
407+ // Create StepEnsurer
408+ ensurer := & StepEnsurer {
409+ kubeClient : mockClient ,
410+ crClient : fake .NewSimpleClientset (),
411+ dynamicClient : fakedynamic .NewSimpleDynamicClient (runtime .NewScheme ()),
412+ }
413+
414+ // Execute EnsureServiceAccount
415+ status , err := ensurer .EnsureServiceAccount (namespace , tc .newServiceAccount )
416+
417+ // Verify results
418+ if tc .expectError {
419+ require .Error (t , err )
420+ } else {
421+ require .NoError (t , err )
422+ assert .Equal (t , tc .expectedStatus , status )
423+ }
424+ })
425+ }
426+ }
0 commit comments