@@ -854,3 +854,97 @@ func TestIsTargetOrganizationRepository(t *testing.T) {
854854 })
855855 }
856856}
857+
858+ func TestGitRepositoryReconciler_Reconcile_SkipsRegenerationIfTokenValid (t * testing.T ) {
859+ s := scheme .Scheme
860+ require .NoError (t , sourcev1 .AddToScheme (s ))
861+
862+ // Set up a valid token expiry in the future
863+ expiresAt := time .Now ().Add (1 * time .Hour )
864+ refreshInterval := 30 * time .Minute
865+
866+ gitRepo := & sourcev1.GitRepository {
867+ ObjectMeta : metav1.ObjectMeta {
868+ Name : "test-repo" ,
869+ Namespace : "default" ,
870+ },
871+ Spec : sourcev1.GitRepositorySpec {
872+ URL : "https://github.com/testorg/test-repository" ,
873+ SecretRef : & meta.LocalObjectReference {
874+ Name : "test-secret" ,
875+ },
876+ },
877+ }
878+
879+ secret := & corev1.Secret {
880+ ObjectMeta : metav1.ObjectMeta {
881+ Name : "test-secret" ,
882+ Namespace : "default" ,
883+ Annotations : map [string ]string {
884+ kubernetes .AnnotationManagedBy : "flux-extension-controller" ,
885+ kubernetes .AnnotationTokenExpiry : expiresAt .Format (time .RFC3339 ),
886+ kubernetes .AnnotationRepositoryURL : "https://github.com/testorg/test-repository" ,
887+ },
888+ },
889+ Data : map [string ][]byte {
890+ "username" : []byte ("git" ),
891+ "password" : []byte ("existing-token" ),
892+ },
893+ Type : kubernetes .SecretTypeGitRepository ,
894+ }
895+
896+ fakeClient := fake .NewClientBuilder ().WithScheme (s ).WithObjects (gitRepo , secret ).Build ()
897+
898+ cfg := & config.Config {
899+ GitHub : config.GitHubConfig {
900+ Organization : "testorg" ,
901+ },
902+ Controller : config.ControllerConfig {
903+ ExcludedNamespaces : []string {"flux-system" },
904+ },
905+ TokenRefresh : config.TokenRefreshConfig {
906+ RefreshInterval : refreshInterval ,
907+ },
908+ }
909+
910+ mockGitHubClient := & MockGitHubClient {}
911+ // ValidateRepositoryURL should be called, but GenerateInstallationToken should NOT be called
912+ mockGitHubClient .On ("ValidateRepositoryURL" , "https://github.com/testorg/test-repository" ).Return (nil )
913+
914+ mockRefreshManager := & MockRefreshManager {}
915+ mockRefreshManager .On ("ScheduleRefresh" , mock .Anything , "default" , "test-secret" , "https://github.com/testorg/test-repository" ).Return (nil )
916+
917+ reconciler := & GitRepositoryReconciler {
918+ Client : fakeClient ,
919+ Scheme : s ,
920+ Config : cfg ,
921+ githubClient : mockGitHubClient ,
922+ secretManager : kubernetes .NewSecretManager (fakeClient ),
923+ refreshManager : mockRefreshManager ,
924+ logger : logr .Discard (),
925+ }
926+
927+ ctx := context .Background ()
928+ req := reconcile.Request {
929+ NamespacedName : types.NamespacedName {
930+ Name : "test-repo" ,
931+ Namespace : "default" ,
932+ },
933+ }
934+
935+ result , err := reconciler .Reconcile (ctx , req )
936+ require .NoError (t , err )
937+ // Should requeue just before expiry minus 5 minutes
938+ expectedRequeue := time .Until (expiresAt ) - 5 * time .Minute
939+ assert .InDelta (t , expectedRequeue .Seconds (), result .RequeueAfter .Seconds (), 2.0 ) // allow 2s drift
940+
941+ // Secret should not be changed
942+ var updatedSecret corev1.Secret
943+ err = fakeClient .Get (ctx , types.NamespacedName {Name : "test-secret" , Namespace : "default" }, & updatedSecret )
944+ require .NoError (t , err )
945+ assert .Equal (t , []byte ("existing-token" ), updatedSecret .Data ["password" ])
946+
947+ // GenerateInstallationToken should NOT be called
948+ mockGitHubClient .AssertNotCalled (t , "GenerateInstallationToken" , mock .Anything , mock .Anything )
949+ mockRefreshManager .AssertExpectations (t )
950+ }
0 commit comments