@@ -880,3 +880,154 @@ func TestNewlyBlockedPRAfterGracePeriod(t *testing.T) {
880880 t .Error ("Expected FirstBlockedAt to be set for newly blocked PR in state manager" )
881881 }
882882}
883+
884+ // TestAuthRetryLoopStopsOnSuccess tests that the auth retry loop stops when auth succeeds.
885+ func TestAuthRetryLoopStopsOnSuccess (t * testing.T ) {
886+ ctx := t .Context ()
887+
888+ app := & App {
889+ mu : sync.RWMutex {},
890+ authError : "initial auth error" ,
891+ systrayInterface : & MockSystray {},
892+ }
893+
894+ // Track how many times we check authError
895+ checkCount := 0
896+ done := make (chan struct {})
897+
898+ // Start a goroutine that simulates the auth retry loop behavior
899+ go func () {
900+ defer close (done )
901+ for {
902+ select {
903+ case <- ctx .Done ():
904+ return
905+ case <- time .After (10 * time .Millisecond ): // Fast ticker for testing
906+ app .mu .RLock ()
907+ hasError := app .authError != ""
908+ app .mu .RUnlock ()
909+
910+ checkCount ++
911+
912+ if ! hasError {
913+ return // Loop should exit when auth succeeds
914+ }
915+
916+ // Simulate clearing auth error on 3rd attempt
917+ if checkCount >= 3 {
918+ app .mu .Lock ()
919+ app .authError = ""
920+ app .mu .Unlock ()
921+ }
922+ }
923+ }
924+ }()
925+
926+ // Wait for the goroutine to finish
927+ select {
928+ case <- done :
929+ // Success - loop exited
930+ case <- time .After (1 * time .Second ):
931+ t .Fatal ("Auth retry loop did not stop after auth succeeded" )
932+ }
933+
934+ // Verify auth error was cleared
935+ app .mu .RLock ()
936+ finalError := app .authError
937+ app .mu .RUnlock ()
938+
939+ if finalError != "" {
940+ t .Errorf ("Expected auth error to be cleared, got: %s" , finalError )
941+ }
942+
943+ if checkCount < 3 {
944+ t .Errorf ("Expected at least 3 retry attempts, got: %d" , checkCount )
945+ }
946+ }
947+
948+ // TestAuthRetryLoopStopsOnContextCancel tests that the auth retry loop stops on context cancellation.
949+ func TestAuthRetryLoopStopsOnContextCancel (t * testing.T ) {
950+ ctx , cancel := context .WithCancel (context .Background ())
951+
952+ app := & App {
953+ mu : sync.RWMutex {},
954+ authError : "persistent auth error" ,
955+ systrayInterface : & MockSystray {},
956+ }
957+
958+ done := make (chan struct {})
959+
960+ // Start a goroutine that simulates the auth retry loop behavior
961+ go func () {
962+ defer close (done )
963+ ticker := time .NewTicker (10 * time .Millisecond )
964+ defer ticker .Stop ()
965+
966+ for {
967+ select {
968+ case <- ctx .Done ():
969+ return
970+ case <- ticker .C :
971+ app .mu .RLock ()
972+ hasError := app .authError != ""
973+ app .mu .RUnlock ()
974+
975+ if ! hasError {
976+ return
977+ }
978+ // Auth error persists, loop continues
979+ }
980+ }
981+ }()
982+
983+ // Cancel context after a short delay
984+ time .Sleep (50 * time .Millisecond )
985+ cancel ()
986+
987+ // Wait for the goroutine to finish
988+ select {
989+ case <- done :
990+ // Success - loop exited on context cancel
991+ case <- time .After (1 * time .Second ):
992+ t .Fatal ("Auth retry loop did not stop after context cancellation" )
993+ }
994+ }
995+
996+ // TestAuthErrorStatePreservation tests that auth error state is correctly preserved and accessible.
997+ func TestAuthErrorStatePreservation (t * testing.T ) {
998+ app := & App {
999+ mu : sync.RWMutex {},
1000+ systrayInterface : & MockSystray {},
1001+ }
1002+
1003+ // Initially no auth error
1004+ app .mu .RLock ()
1005+ if app .authError != "" {
1006+ t .Errorf ("Expected no initial auth error, got: %s" , app .authError )
1007+ }
1008+ app .mu .RUnlock ()
1009+
1010+ // Set auth error
1011+ app .mu .Lock ()
1012+ app .authError = "token expired"
1013+ app .mu .Unlock ()
1014+
1015+ // Verify auth error is set
1016+ app .mu .RLock ()
1017+ if app .authError != "token expired" {
1018+ t .Errorf ("Expected auth error 'token expired', got: %s" , app .authError )
1019+ }
1020+ app .mu .RUnlock ()
1021+
1022+ // Clear auth error
1023+ app .mu .Lock ()
1024+ app .authError = ""
1025+ app .mu .Unlock ()
1026+
1027+ // Verify auth error is cleared
1028+ app .mu .RLock ()
1029+ if app .authError != "" {
1030+ t .Errorf ("Expected auth error to be cleared, got: %s" , app .authError )
1031+ }
1032+ app .mu .RUnlock ()
1033+ }
0 commit comments