@@ -380,6 +380,86 @@ mod tests {
380380 }
381381 }
382382
383+ #[ tokio:: test]
384+ async fn get_by_slot_successful_fetch ( ) {
385+ // Create two validator pubkeys
386+ let pubkeys = vec ! [ test_pubkey( 0 ) , test_pubkey( 1 ) ] ;
387+
388+ // Set up mock server with different responses based on slot
389+ let mock_server = MockServer :: start ( ) . await ;
390+
391+ endpoint_success (
392+ "1" ,
393+ vec ! [
394+ test_validator_datum( 0 , & pubkeys[ 0 ] , ValidatorStatus :: PendingQueued ) ,
395+ test_validator_datum( 1 , & pubkeys[ 1 ] , ValidatorStatus :: ActiveOngoing ) ,
396+ ] ,
397+ )
398+ . mount ( & mock_server)
399+ . await ;
400+
401+ endpoint_success (
402+ "2" ,
403+ vec ! [
404+ test_validator_datum( 0 , & pubkeys[ 0 ] , ValidatorStatus :: ActiveOngoing ) ,
405+ test_validator_datum( 1 , & pubkeys[ 1 ] , ValidatorStatus :: ActiveOngoing ) ,
406+ ] ,
407+ )
408+ . mount ( & mock_server)
409+ . await ;
410+
411+ endpoint_success (
412+ "11" ,
413+ vec ! [
414+ test_validator_datum( 0 , & pubkeys[ 0 ] , ValidatorStatus :: PendingQueued ) ,
415+ test_validator_datum( 1 , & pubkeys[ 1 ] , ValidatorStatus :: PendingQueued ) ,
416+ ] ,
417+ )
418+ . mount ( & mock_server)
419+ . await ;
420+
421+ endpoint_not_found ( "3" ) . mount ( & mock_server) . await ;
422+ endpoint_not_found ( "head" ) . mount ( & mock_server) . await ;
423+
424+ let eth2_cl = EthBeaconNodeApiClient :: with_base_url ( mock_server. uri ( ) )
425+ . expect ( "Failed to create client" ) ;
426+
427+ let cache = ValidatorCache :: new ( eth2_cl, pubkeys. clone ( ) ) ;
428+
429+ // Test slot 1: 1 active validator (index 1), 2 complete, refreshed_by_slot=true
430+ let ( active, complete, refreshed_by_slot) = cache
431+ . get_by_slot ( 1 )
432+ . await
433+ . expect ( "`get_by_slot(1)` succeeds" ) ;
434+ assert_eq ! ( active. len( ) , 1 ) ;
435+ assert_eq ! ( active. get( & 1 ) , Some ( & pubkeys[ 1 ] ) ) ;
436+ assert_eq ! ( complete. len( ) , 2 ) ;
437+ assert ! ( refreshed_by_slot) ;
438+
439+ // Test slot 2: 2 active validators, 2 complete, refreshed_by_slot=true
440+ let ( active, complete, refreshed_by_slot) = cache
441+ . get_by_slot ( 2 )
442+ . await
443+ . expect ( "`get_by_slot(2)` succeeds" ) ;
444+ assert_eq ! ( active. len( ) , 2 ) ;
445+ assert_eq ! ( complete. len( ) , 2 ) ;
446+ assert ! ( refreshed_by_slot) ;
447+
448+ // Test slot 11: 0 active validators, 2 complete, refreshed_by_slot=true
449+ let ( active, complete, refreshed_by_slot) = cache
450+ . get_by_slot ( 11 )
451+ . await
452+ . expect ( "`get_by_slot(11)` succeeds" ) ;
453+ assert ! ( active. is_empty( ) ) ;
454+ assert_eq ! ( complete. len( ) , 2 ) ;
455+ assert ! ( refreshed_by_slot) ;
456+
457+ // Test slot 3: error (both slot and head fallback fail),
458+ // refreshed_by_slot=false
459+ let result = cache. get_by_slot ( 3 ) . await ;
460+ assert ! ( result. is_err( ) ) ;
461+ }
462+
383463 fn test_pubkey ( seed : u8 ) -> PubKey {
384464 let mut bytes = [ 0u8 ; 48 ] ;
385465 bytes[ 0 ] = seed;
@@ -410,6 +490,29 @@ mod tests {
410490 }
411491 }
412492
493+ fn endpoint_success (
494+ state_id : impl AsRef < str > ,
495+ validators : Vec < GetStateValidatorsResponseResponseDatum > ,
496+ ) -> Mock {
497+ Mock :: given ( method ( "POST" ) )
498+ . and ( path_regex ( format ! (
499+ "/eth/v1/beacon/states/{}/validators" ,
500+ state_id. as_ref( )
501+ ) ) )
502+ . respond_with (
503+ ResponseTemplate :: new ( 200 ) . set_body_json ( success_response_body ( validators) ) ,
504+ )
505+ }
506+
507+ fn endpoint_not_found ( state_id : impl AsRef < str > ) -> Mock {
508+ Mock :: given ( method ( "POST" ) )
509+ . and ( path_regex ( format ! (
510+ "/eth/v1/beacon/states/{}/validators" ,
511+ state_id. as_ref( )
512+ ) ) )
513+ . respond_with ( ResponseTemplate :: new ( 404 ) . set_body_json ( not_found_response_body ( ) ) )
514+ }
515+
413516 fn success_response_body (
414517 validators : Vec < GetStateValidatorsResponseResponseDatum > ,
415518 ) -> GetStateValidatorsResponseResponse {
0 commit comments