@@ -372,3 +372,162 @@ func TestGRPCReadyAffectedByStop(t *testing.T) {
372372		})
373373	}
374374}
375+ 
376+ func  TestStopPreventsCheckFromSettingHealthy (t  * testing.T ) {
377+ 	t .Parallel ()
378+ 	patterns  :=  []struct  {
379+ 		desc              string 
380+ 		setupFunc         func () * restChecker 
381+ 		expectedStatus    int 
382+ 		expectedStopped   bool 
383+ 		expectedInternal  Status 
384+ 	}{
385+ 		{
386+ 			desc : "check() after Stop() does not override status to Healthy" ,
387+ 			setupFunc : func () * restChecker  {
388+ 				healthyCheck  :=  func (ctx  context.Context ) Status  {
389+ 					return  Healthy 
390+ 				}
391+ 				c  :=  NewRestChecker (version , service , WithCheck ("healthy" , healthyCheck ))
392+ 				c .check (context .Background ())
393+ 				return  c 
394+ 			},
395+ 			expectedStatus :   http .StatusServiceUnavailable ,
396+ 			expectedStopped :  true ,
397+ 			expectedInternal : Unhealthy ,
398+ 		},
399+ 		{
400+ 			desc : "multiple check() calls after Stop() remain Unhealthy" ,
401+ 			setupFunc : func () * restChecker  {
402+ 				healthyCheck  :=  func (ctx  context.Context ) Status  {
403+ 					return  Healthy 
404+ 				}
405+ 				c  :=  NewRestChecker (version , service , WithCheck ("healthy" , healthyCheck ))
406+ 				c .check (context .Background ())
407+ 				return  c 
408+ 			},
409+ 			expectedStatus :   http .StatusServiceUnavailable ,
410+ 			expectedStopped :  true ,
411+ 			expectedInternal : Unhealthy ,
412+ 		},
413+ 	}
414+ 
415+ 	for  _ , p  :=  range  patterns  {
416+ 		t .Run (p .desc , func (t  * testing.T ) {
417+ 			checker  :=  p .setupFunc ()
418+ 
419+ 			// Verify initial state is healthy 
420+ 			if  checker .getStatus () !=  Healthy  {
421+ 				t .Errorf ("Initial state should be Healthy, got %v" , checker .getStatus ())
422+ 			}
423+ 
424+ 			// Call Stop() 
425+ 			checker .Stop ()
426+ 
427+ 			// Verify stopped flag is set 
428+ 			if  ! checker .isStopped () {
429+ 				t .Error ("Expected isStopped() to be true after Stop()" )
430+ 			}
431+ 
432+ 			// Verify status is Unhealthy 
433+ 			if  checker .getStatus () !=  Unhealthy  {
434+ 				t .Errorf ("Expected status to be Unhealthy after Stop(), got %v" , checker .getStatus ())
435+ 			}
436+ 
437+ 			// Call check() to simulate the race condition 
438+ 			// This should NOT override the status back to Healthy 
439+ 			checker .check (context .Background ())
440+ 
441+ 			// Verify status remains Unhealthy 
442+ 			if  checker .getStatus () !=  p .expectedInternal  {
443+ 				t .Errorf ("Expected status to remain %v after check(), got %v" ,
444+ 					p .expectedInternal , checker .getStatus ())
445+ 			}
446+ 
447+ 			// Verify HTTP response is 503 
448+ 			req  :=  httptest .NewRequest ("GET" , fmt .Sprintf ("%s%s%s" , version , service , readyPath ), nil )
449+ 			resp  :=  httptest .NewRecorder ()
450+ 			checker .ServeReadyHTTP (resp , req )
451+ 			if  resp .Code  !=  p .expectedStatus  {
452+ 				t .Errorf ("Expected HTTP status %d, got %d" , p .expectedStatus , resp .Code )
453+ 			}
454+ 
455+ 			// Call check() multiple times to ensure it stays Unhealthy 
456+ 			for  i  :=  0 ; i  <  5 ; i ++  {
457+ 				checker .check (context .Background ())
458+ 				if  checker .getStatus () !=  p .expectedInternal  {
459+ 					t .Errorf ("After %d check() calls, expected status %v, got %v" ,
460+ 						i + 1 , p .expectedInternal , checker .getStatus ())
461+ 				}
462+ 			}
463+ 		})
464+ 	}
465+ }
466+ 
467+ func  TestStopWithRunningGoroutine (t  * testing.T ) {
468+ 	t .Parallel ()
469+ 	patterns  :=  []struct  {
470+ 		desc           string 
471+ 		setupFunc      func () (* restChecker , context.CancelFunc )
472+ 		expectedErr    error 
473+ 		expectedFinal  int 
474+ 	}{
475+ 		{
476+ 			desc : "Stop() prevents Run() goroutine from setting status to Healthy" ,
477+ 			setupFunc : func () (* restChecker , context.CancelFunc ) {
478+ 				healthyCheck  :=  func (ctx  context.Context ) Status  {
479+ 					return  Healthy 
480+ 				}
481+ 				c  :=  NewRestChecker (version , service ,
482+ 					WithCheck ("healthy" , healthyCheck ),
483+ 					WithInterval (10 * time .Millisecond ))
484+ 				ctx , cancel  :=  context .WithCancel (context .Background ())
485+ 				go  c .Run (ctx )
486+ 				// Wait for first check to complete 
487+ 				time .Sleep (50  *  time .Millisecond )
488+ 				return  c , cancel 
489+ 			},
490+ 			expectedErr :   nil ,
491+ 			expectedFinal : http .StatusServiceUnavailable ,
492+ 		},
493+ 	}
494+ 
495+ 	for  _ , p  :=  range  patterns  {
496+ 		t .Run (p .desc , func (t  * testing.T ) {
497+ 			checker , cancel  :=  p .setupFunc ()
498+ 			defer  cancel ()
499+ 
500+ 			// Verify initial state is healthy 
501+ 			if  checker .getStatus () !=  Healthy  {
502+ 				t .Errorf ("Initial state should be Healthy, got %v" , checker .getStatus ())
503+ 			}
504+ 
505+ 			// Call Stop() while Run() goroutine is still running 
506+ 			checker .Stop ()
507+ 
508+ 			// Verify status is immediately Unhealthy 
509+ 			if  checker .getStatus () !=  Unhealthy  {
510+ 				t .Errorf ("Expected status to be Unhealthy immediately after Stop(), got %v" ,
511+ 					checker .getStatus ())
512+ 			}
513+ 
514+ 			// Wait for multiple check intervals to pass 
515+ 			// The Run() goroutine should NOT override status back to Healthy 
516+ 			time .Sleep (100  *  time .Millisecond )
517+ 
518+ 			// Verify status remains Unhealthy 
519+ 			if  checker .getStatus () !=  Unhealthy  {
520+ 				t .Errorf ("Expected status to remain Unhealthy after waiting, got %v" ,
521+ 					checker .getStatus ())
522+ 			}
523+ 
524+ 			// Verify HTTP response is 503 
525+ 			req  :=  httptest .NewRequest ("GET" , fmt .Sprintf ("%s%s%s" , version , service , readyPath ), nil )
526+ 			resp  :=  httptest .NewRecorder ()
527+ 			checker .ServeReadyHTTP (resp , req )
528+ 			if  resp .Code  !=  p .expectedFinal  {
529+ 				t .Errorf ("Expected HTTP status %d, got %d" , p .expectedFinal , resp .Code )
530+ 			}
531+ 		})
532+ 	}
533+ }
0 commit comments