1+ use std:: time:: Duration ;
2+
13use bytes:: Bytes ;
24use http:: { Method , Request , Response , StatusCode } ;
35use http_body_util:: { BodyExt , Full } ;
46use jsonrpsee:: server:: HttpBody ;
57use tower:: { Layer , ServiceExt } ;
68
79use crate :: server:: health:: { HealthLayer , HEALTH_PATH } ;
10+ use crate :: server:: saturation:: SaturationMonitor ;
811
912/// Inner stub returning 418 so we can tell whether `HealthLayer` short-circuited.
1013fn fallthrough_service ( ) -> impl tower:: Service <
@@ -38,7 +41,8 @@ async fn read_body(response: Response<HttpBody>) -> (StatusCode, Vec<u8>, http::
3841
3942#[ tokio:: test]
4043async fn get_health_returns_200_with_json_body ( ) {
41- let svc = HealthLayer . layer ( fallthrough_service ( ) ) ;
44+ let svc = HealthLayer :: new ( SaturationMonitor :: default ( ) , Duration :: from_millis ( 0 ) )
45+ . layer ( fallthrough_service ( ) ) ;
4246
4347 let response = svc. oneshot ( empty_request ( Method :: GET , HEALTH_PATH ) ) . await . unwrap ( ) ;
4448
@@ -50,7 +54,8 @@ async fn get_health_returns_200_with_json_body() {
5054
5155#[ tokio:: test]
5256async fn non_get_health_falls_through ( ) {
53- let svc = HealthLayer . layer ( fallthrough_service ( ) ) ;
57+ let svc = HealthLayer :: new ( SaturationMonitor :: default ( ) , Duration :: from_millis ( 0 ) )
58+ . layer ( fallthrough_service ( ) ) ;
5459
5560 let response = svc. oneshot ( empty_request ( Method :: POST , HEALTH_PATH ) ) . await . unwrap ( ) ;
5661
@@ -60,10 +65,55 @@ async fn non_get_health_falls_through() {
6065
6166#[ tokio:: test]
6267async fn get_other_path_falls_through ( ) {
63- let svc = HealthLayer . layer ( fallthrough_service ( ) ) ;
68+ let svc = HealthLayer :: new ( SaturationMonitor :: default ( ) , Duration :: from_millis ( 0 ) )
69+ . layer ( fallthrough_service ( ) ) ;
6470
6571 let response = svc. oneshot ( empty_request ( Method :: GET , "/" ) ) . await . unwrap ( ) ;
6672
6773 let ( status, _body, _) = read_body ( response) . await ;
6874 assert_eq ! ( status, StatusCode :: IM_A_TEAPOT ) ;
6975}
76+
77+ #[ tokio:: test]
78+ async fn unsaturated_health_returns_200_when_monitor_is_supplied ( ) {
79+ let monitor = SaturationMonitor :: default ( ) ;
80+ let svc = HealthLayer :: new ( monitor, Duration :: from_millis ( 0 ) ) . layer ( fallthrough_service ( ) ) ;
81+
82+ let response = svc. oneshot ( empty_request ( Method :: GET , HEALTH_PATH ) ) . await . unwrap ( ) ;
83+
84+ let ( status, body, _) = read_body ( response) . await ;
85+ assert_eq ! ( status, StatusCode :: OK ) ;
86+ assert_eq ! ( body, br#"{"status":"ok"}"# ) ;
87+ }
88+
89+ #[ tokio:: test]
90+ async fn saturated_for_at_least_threshold_returns_503_with_opaque_body ( ) {
91+ let monitor = SaturationMonitor :: default ( ) ;
92+ monitor. mark_rejected ( ) ;
93+ // Zero threshold so the saturation is immediately past it. Avoids
94+ // sleeping in the test.
95+ let svc = HealthLayer :: new ( monitor, 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 = HealthLayer :: new ( monitor, Duration :: from_millis ( 0 ) ) . layer ( fallthrough_service ( ) ) ;
114+
115+ let response = svc. oneshot ( empty_request ( Method :: GET , HEALTH_PATH ) ) . await . unwrap ( ) ;
116+ let ( status, body, _) = read_body ( response) . await ;
117+ assert_eq ! ( status, StatusCode :: OK ) ;
118+ assert_eq ! ( body, br#"{"status":"ok"}"# ) ;
119+ }
0 commit comments