@@ -5,6 +5,7 @@ use jsonrpsee::server::HttpBody;
55use tower:: { Layer , ServiceExt } ;
66
77use crate :: server:: health:: { HealthLayer , HEALTH_PATH } ;
8+ use crate :: server:: saturation:: SaturationMonitor ;
89
910/// Inner stub returning 418 so we can tell whether `HealthLayer` short-circuited.
1011fn fallthrough_service ( ) -> impl tower:: Service <
@@ -38,7 +39,8 @@ async fn read_body(response: Response<HttpBody>) -> (StatusCode, Vec<u8>, http::
3839
3940#[ tokio:: test]
4041async fn get_health_returns_200_with_json_body ( ) {
41- let svc = HealthLayer . layer ( fallthrough_service ( ) ) ;
42+ let svc = HealthLayer :: new ( SaturationMonitor :: default ( ) , std:: time:: Duration :: from_millis ( 0 ) )
43+ . layer ( fallthrough_service ( ) ) ;
4244
4345 let response = svc. oneshot ( empty_request ( Method :: GET , HEALTH_PATH ) ) . await . unwrap ( ) ;
4446
@@ -50,7 +52,8 @@ async fn get_health_returns_200_with_json_body() {
5052
5153#[ tokio:: test]
5254async fn non_get_health_falls_through ( ) {
53- let svc = HealthLayer . layer ( fallthrough_service ( ) ) ;
55+ let svc = HealthLayer :: new ( SaturationMonitor :: default ( ) , std:: time:: Duration :: from_millis ( 0 ) )
56+ . layer ( fallthrough_service ( ) ) ;
5457
5558 let response = svc. oneshot ( empty_request ( Method :: POST , HEALTH_PATH ) ) . await . unwrap ( ) ;
5659
@@ -60,10 +63,58 @@ async fn non_get_health_falls_through() {
6063
6164#[ tokio:: test]
6265async fn get_other_path_falls_through ( ) {
63- let svc = HealthLayer . layer ( fallthrough_service ( ) ) ;
66+ let svc = HealthLayer :: new ( SaturationMonitor :: default ( ) , std:: time:: Duration :: from_millis ( 0 ) )
67+ . layer ( fallthrough_service ( ) ) ;
6468
6569 let response = svc. oneshot ( empty_request ( Method :: GET , "/" ) ) . await . unwrap ( ) ;
6670
6771 let ( status, _body, _) = read_body ( response) . await ;
6872 assert_eq ! ( status, StatusCode :: IM_A_TEAPOT ) ;
6973}
74+
75+ #[ tokio:: test]
76+ async fn unsaturated_health_returns_200_when_monitor_is_supplied ( ) {
77+ let monitor = SaturationMonitor :: default ( ) ;
78+ let svc =
79+ HealthLayer :: new ( monitor, std:: time:: Duration :: from_millis ( 0 ) ) . layer ( fallthrough_service ( ) ) ;
80+
81+ let response = svc. oneshot ( empty_request ( Method :: GET , HEALTH_PATH ) ) . await . unwrap ( ) ;
82+
83+ let ( status, body, _) = read_body ( response) . await ;
84+ assert_eq ! ( status, StatusCode :: OK ) ;
85+ assert_eq ! ( body, br#"{"status":"ok"}"# ) ;
86+ }
87+
88+ #[ tokio:: test]
89+ async fn saturated_for_at_least_threshold_returns_503_with_opaque_body ( ) {
90+ let monitor = SaturationMonitor :: default ( ) ;
91+ monitor. mark_rejected ( ) ;
92+ // Zero threshold so the saturation is immediately past it. Avoids
93+ // sleeping in the test.
94+ let svc =
95+ HealthLayer :: new ( monitor, std:: time:: Duration :: from_millis ( 0 ) ) . layer ( fallthrough_service ( ) ) ;
96+
97+ let response = svc. oneshot ( empty_request ( Method :: GET , HEALTH_PATH ) ) . await . unwrap ( ) ;
98+
99+ let ( status, body, _) = read_body ( response) . await ;
100+ assert_eq ! ( status, StatusCode :: SERVICE_UNAVAILABLE ) ;
101+ let body_text = std:: str:: from_utf8 ( & body) . unwrap ( ) ;
102+ assert ! ( body_text. contains( "saturated" ) ) ;
103+ // No internal state — no timestamps, no permits, no upstream URLs.
104+ assert ! ( !body_text. contains( "Instant" ) ) ;
105+ assert ! ( !body_text. chars( ) . any( |c| c. is_ascii_digit( ) ) , "body had digits: {body_text}" ) ;
106+ }
107+
108+ #[ tokio:: test]
109+ async fn recovery_clears_saturation_and_health_returns_to_200 ( ) {
110+ let monitor = SaturationMonitor :: default ( ) ;
111+ monitor. mark_rejected ( ) ;
112+ monitor. mark_accepted ( ) ;
113+ let svc =
114+ HealthLayer :: new ( monitor, std:: time:: Duration :: from_millis ( 0 ) ) . layer ( fallthrough_service ( ) ) ;
115+
116+ let response = svc. oneshot ( empty_request ( Method :: GET , HEALTH_PATH ) ) . await . unwrap ( ) ;
117+ let ( status, body, _) = read_body ( response) . await ;
118+ assert_eq ! ( status, StatusCode :: OK ) ;
119+ assert_eq ! ( body, br#"{"status":"ok"}"# ) ;
120+ }
0 commit comments