@@ -599,6 +599,98 @@ async fn mock_zap_server_for_spider_timeout_grace_stop_request() -> MockServer {
599599 server
600600}
601601
602+ async fn mock_zap_server_for_spider_stop_status_change_timeout ( ) -> MockServer {
603+ let server = MockServer :: start ( ) . await ;
604+
605+ Mock :: given ( method ( "POST" ) )
606+ . and ( path ( "/JSON/context/action/newContext" ) )
607+ . respond_with (
608+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"contextId":"ctx-1"}"# , "application/json" ) ,
609+ )
610+ . mount ( & server)
611+ . await ;
612+
613+ Mock :: given ( method ( "POST" ) )
614+ . and ( path ( "/JSON/context/action/includeInContext" ) )
615+ . respond_with (
616+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"Result":"OK"}"# , "application/json" ) ,
617+ )
618+ . mount ( & server)
619+ . await ;
620+
621+ Mock :: given ( method ( "POST" ) )
622+ . and ( path ( "/JSON/ajaxSpider/action/setOptionMaxDuration" ) )
623+ . and ( body_string_contains ( "Integer=1" ) )
624+ . respond_with (
625+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"Result":"OK"}"# , "application/json" ) ,
626+ )
627+ . expect ( 1 )
628+ . mount ( & server)
629+ . await ;
630+
631+ Mock :: given ( method ( "POST" ) )
632+ . and ( path ( "/JSON/ajaxSpider/action/scan" ) )
633+ . respond_with (
634+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"Result":"OK"}"# , "application/json" ) ,
635+ )
636+ . expect ( 1 )
637+ . mount ( & server)
638+ . await ;
639+
640+ Mock :: given ( method ( "POST" ) )
641+ . and ( path ( "/JSON/ajaxSpider/view/status" ) )
642+ . respond_with (
643+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"status":"running"}"# , "application/json" ) ,
644+ )
645+ . mount ( & server)
646+ . await ;
647+
648+ Mock :: given ( method ( "POST" ) )
649+ . and ( path ( "/JSON/ajaxSpider/action/stop" ) )
650+ . respond_with (
651+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"Result":"OK"}"# , "application/json" ) ,
652+ )
653+ . expect ( 1 )
654+ . mount ( & server)
655+ . await ;
656+
657+ Mock :: given ( method ( "POST" ) )
658+ . and ( path ( "/JSON/ascan/action/scan" ) )
659+ . respond_with (
660+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"scan":"active-1"}"# , "application/json" ) ,
661+ )
662+ . expect ( 0 )
663+ . mount ( & server)
664+ . await ;
665+
666+ Mock :: given ( method ( "POST" ) )
667+ . and ( path ( "/JSON/ascan/view/status" ) )
668+ . respond_with (
669+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"status":"100"}"# , "application/json" ) ,
670+ )
671+ . expect ( 0 )
672+ . mount ( & server)
673+ . await ;
674+
675+ Mock :: given ( method ( "POST" ) )
676+ . and ( path ( "/JSON/alert/view/alerts" ) )
677+ . respond_with (
678+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"alerts":[]}"# , "application/json" ) ,
679+ )
680+ . mount ( & server)
681+ . await ;
682+
683+ Mock :: given ( method ( "POST" ) )
684+ . and ( path ( "/JSON/context/action/removeContext" ) )
685+ . respond_with (
686+ ResponseTemplate :: new ( 200 ) . set_body_raw ( r#"{"Result":"OK"}"# , "application/json" ) ,
687+ )
688+ . mount ( & server)
689+ . await ;
690+
691+ server
692+ }
693+
602694async fn mock_zap_server_with_active_status_error ( ) -> MockServer {
603695 let server = MockServer :: start ( ) . await ;
604696
@@ -1339,6 +1431,43 @@ async fn runtime_stops_ajax_spider_when_timeout_plus_grace_period_is_exceeded()
13391431 assert_eq ! ( scan. status, ScanStatus :: Stopped ) ;
13401432}
13411433
1434+ #[ tokio:: test]
1435+ async fn runtime_continues_when_ajax_spider_status_does_not_change_after_local_stop_request ( ) {
1436+ let ( storage, _temp_dir) = temporary_sqlite_storage ( ) . await . unwrap ( ) ;
1437+ let server = mock_zap_server_for_spider_stop_status_change_timeout ( ) . await ;
1438+ let zap_client = ZapClient :: new ( server. uri ( ) , "test-api-key" . to_string ( ) ) . unwrap ( ) ;
1439+ let runtime = start_scan_runtime (
1440+ storage. clone ( ) ,
1441+ zap_client,
1442+ ScanRuntimeConfig {
1443+ worker_count : 1 ,
1444+ alert_poll_interval : Duration :: from_millis ( 1 ) ,
1445+ scan_poll_interval : Duration :: from_millis ( 20 ) ,
1446+ alert_page_size : 100 ,
1447+ passive_scan_placeholder_duration : Duration :: from_millis ( 1 ) ,
1448+ ajax_spider_timeout_grace_period : Duration :: from_millis ( 50 ) ,
1449+ phase_stop_status_change_timeout : Duration :: from_millis ( 80 ) ,
1450+ stop_grace_period : Duration :: from_secs ( 5 ) ,
1451+ ..ScanRuntimeConfig :: default ( )
1452+ } ,
1453+ ) ;
1454+ let service = DefaultScanService :: new ( storage. clone ( ) , runtime) ;
1455+
1456+ let scan_id = service
1457+ . create_scan ( make_safe_mode_request_with_ajax_timeout (
1458+ "https://example.test" ,
1459+ 1 ,
1460+ ) )
1461+ . await
1462+ . unwrap ( ) ;
1463+
1464+ service. start_scan ( & scan_id) . await . unwrap ( ) ;
1465+ wait_for_status ( storage. as_ref ( ) , & scan_id, ScanStatus :: Succeeded ) . await ;
1466+
1467+ let scan = storage. get_scan ( & scan_id) . await . unwrap ( ) ;
1468+ assert_eq ! ( scan. status, ScanStatus :: Succeeded ) ;
1469+ }
1470+
13421471#[ tokio:: test]
13431472async fn runtime_transitions_running_scan_to_failed_on_worker_error ( ) {
13441473 let ( storage, _temp_dir) = temporary_sqlite_storage ( ) . await . unwrap ( ) ;
0 commit comments