@@ -2,6 +2,7 @@ package operator
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "reflect"
78 "testing"
@@ -891,6 +892,130 @@ func TestHandlePVCResize(t *testing.T) {
891892 testPVCFinishedResizing (t , ctx , memberClient , p , reconciledResource , statefulSet , logger )
892893}
893894
895+ // ===== Test for state and vault annotations handling in replicaset controller =====
896+
897+ // TestReplicaSetAnnotations_WrittenOnSuccess verifies that lastAchievedSpec annotation is written after successful
898+ // reconciliation.
899+ func TestReplicaSetAnnotations_WrittenOnSuccess (t * testing.T ) {
900+ ctx := context .Background ()
901+ rs := DefaultReplicaSetBuilder ().Build ()
902+
903+ reconciler , client , _ := defaultReplicaSetReconciler (ctx , nil , "" , "" , rs )
904+
905+ checkReconcileSuccessful (ctx , t , reconciler , rs , client )
906+
907+ err := client .Get (ctx , rs .ObjectKey (), rs )
908+ require .NoError (t , err )
909+
910+ require .Contains (t , rs .Annotations , util .LastAchievedSpec ,
911+ "lastAchievedSpec annotation should be written on successful reconciliation" )
912+
913+ var lastSpec mdbv1.MongoDbSpec
914+ err = json .Unmarshal ([]byte (rs .Annotations [util .LastAchievedSpec ]), & lastSpec )
915+ require .NoError (t , err )
916+ assert .Equal (t , 3 , lastSpec .Members )
917+ assert .Equal (t , "4.0.0" , lastSpec .Version )
918+ }
919+
920+ // TestReplicaSetAnnotations_NotWrittenOnFailure verifies that lastAchievedSpec annotation
921+ // is NOT written when reconciliation fails.
922+ func TestReplicaSetAnnotations_NotWrittenOnFailure (t * testing.T ) {
923+ ctx := context .Background ()
924+ rs := DefaultReplicaSetBuilder ().Build ()
925+
926+ // Setup without credentials secret to cause failure
927+ kubeClient := mock .NewEmptyFakeClientBuilder ().
928+ WithObjects (rs ).
929+ WithObjects (mock .GetProjectConfigMap (mock .TestProjectConfigMapName , "testProject" , "testOrg" )).
930+ Build ()
931+
932+ reconciler := newReplicaSetReconciler (ctx , kubeClient , nil , "" , "" , false , false , nil )
933+
934+ _ , err := reconciler .Reconcile (ctx , requestFromObject (rs ))
935+ require .NoError (t , err , "Reconcile should not return error (error captured in status)" )
936+
937+ err = kubeClient .Get (ctx , rs .ObjectKey (), rs )
938+ require .NoError (t , err )
939+
940+ assert .NotEqual (t , status .PhaseRunning , rs .Status .Phase )
941+
942+ assert .NotContains (t , rs .Annotations , util .LastAchievedSpec ,
943+ "lastAchievedSpec should NOT be written when reconciliation fails" )
944+ }
945+
946+ // TestReplicaSetAnnotations_PreservedOnSubsequentFailure verifies that annotations from a previous successful
947+ // reconciliation are preserved when a later reconciliation fails.
948+ func TestReplicaSetAnnotations_PreservedOnSubsequentFailure (t * testing.T ) {
949+ ctx := context .Background ()
950+ rs := DefaultReplicaSetBuilder ().Build ()
951+
952+ kubeClient , omConnectionFactory := mock .NewDefaultFakeClient (rs )
953+ reconciler := newReplicaSetReconciler (ctx , kubeClient , nil , "" , "" , false , false , omConnectionFactory .GetConnectionFunc )
954+
955+ _ , err := reconciler .Reconcile (ctx , requestFromObject (rs ))
956+ require .NoError (t , err )
957+
958+ err = kubeClient .Get (ctx , rs .ObjectKey (), rs )
959+ require .NoError (t , err )
960+ require .Contains (t , rs .Annotations , util .LastAchievedSpec )
961+
962+ originalLastAchievedSpec := rs .Annotations [util .LastAchievedSpec ]
963+
964+ // Delete credentials to cause failure
965+ credentialsSecret := mock .GetCredentialsSecret ("testUser" , "testApiKey" )
966+ err = kubeClient .Delete (ctx , credentialsSecret )
967+ require .NoError (t , err )
968+
969+ rs .Spec .Members = 5
970+ err = kubeClient .Update (ctx , rs )
971+ require .NoError (t , err )
972+
973+ _ , err = reconciler .Reconcile (ctx , requestFromObject (rs ))
974+ require .NoError (t , err )
975+
976+ err = kubeClient .Get (ctx , rs .ObjectKey (), rs )
977+ require .NoError (t , err )
978+
979+ assert .Contains (t , rs .Annotations , util .LastAchievedSpec )
980+ assert .NotEqual (t , status .PhaseRunning , rs .Status .Phase )
981+ assert .Equal (t , originalLastAchievedSpec , rs .Annotations [util .LastAchievedSpec ],
982+ "lastAchievedSpec should remain unchanged when reconciliation fails" )
983+
984+ var lastSpec mdbv1.MongoDbSpec
985+ err = json .Unmarshal ([]byte (rs .Annotations [util .LastAchievedSpec ]), & lastSpec )
986+ require .NoError (t , err )
987+ assert .Equal (t , 3 , lastSpec .Members ,
988+ "Should still reflect previous successful state (3 members, not 5)" )
989+ }
990+
991+ // TestVaultAnnotations_NotWrittenWhenDisabled verifies that vault annotations are NOT
992+ // written when vault backend is disabled.
993+ func TestVaultAnnotations_NotWrittenWhenDisabled (t * testing.T ) {
994+ ctx := context .Background ()
995+ rs := DefaultReplicaSetBuilder ().Build ()
996+
997+ t .Setenv ("SECRET_BACKEND" , "K8S_SECRET_BACKEND" )
998+
999+ reconciler , client , _ := defaultReplicaSetReconciler (ctx , nil , "" , "" , rs )
1000+
1001+ checkReconcileSuccessful (ctx , t , reconciler , rs , client )
1002+
1003+ err := client .Get (ctx , rs .ObjectKey (), rs )
1004+ require .NoError (t , err )
1005+
1006+ require .Contains (t , rs .Annotations , util .LastAchievedSpec ,
1007+ "lastAchievedSpec should be written even when vault is disabled" )
1008+
1009+ // Vault annotations would be simple secret names like "my-secret": "5"
1010+ for key := range rs .Annotations {
1011+ if key == util .LastAchievedSpec {
1012+ continue
1013+ }
1014+ assert .NotRegexp (t , "^[a-z0-9-]+$" , key ,
1015+ "Should not have simple secret name annotations when vault disabled - found: %s" , key )
1016+ }
1017+ }
1018+
8941019func testPVCFinishedResizing (t * testing.T , ctx context.Context , memberClient kubernetesClient.Client , p * corev1.PersistentVolumeClaim , reconciledResource * mdbv1.MongoDB , statefulSet * appsv1.StatefulSet , logger * zap.SugaredLogger ) {
8951020 // Simulate that the PVC has finished resizing
8961021 setPVCWithUpdatedResource (ctx , t , memberClient , p )
0 commit comments