@@ -481,6 +481,87 @@ mod tests {
481481 assert ! ( d. summary. contains( "Corrupted" ) ) ;
482482 }
483483
484+ #[ test]
485+ fn test_diagnose_corrupted_state_is_retryable_after_auto_cleanup ( ) {
486+ // After the auto-cleanup fix (#463), corrupted state errors should be
487+ // marked retryable because deploy_gateway_with_logs now automatically
488+ // cleans up Docker resources on failure.
489+ let d = diagnose_failure (
490+ "mygw" ,
491+ "K8s namespace not ready" ,
492+ Some ( "configmaps \" extension-apiserver-authentication\" is forbidden" ) ,
493+ )
494+ . expect ( "should match corrupted state pattern" ) ;
495+ assert ! (
496+ d. retryable,
497+ "corrupted state should be retryable after auto-cleanup"
498+ ) ;
499+ assert ! (
500+ d. explanation. contains( "automatically cleaned up" ) ,
501+ "explanation should mention automatic cleanup, got: {}" ,
502+ d. explanation
503+ ) ;
504+ }
505+
506+ #[ test]
507+ fn test_diagnose_corrupted_state_recovery_no_manual_volume_rm ( ) {
508+ // The recovery steps should no longer include a manual docker volume rm
509+ // command, since cleanup is now automatic. The first step should tell
510+ // the user to simply retry.
511+ let d = diagnose_failure ( "mygw" , "cannot get resource \" namespaces\" " , None )
512+ . expect ( "should match corrupted state pattern" ) ;
513+
514+ let all_commands: Vec < String > = d
515+ . recovery_steps
516+ . iter ( )
517+ . filter_map ( |s| s. command . clone ( ) )
518+ . collect ( ) ;
519+ let all_commands_joined = all_commands. join ( " " ) ;
520+
521+ assert ! (
522+ !all_commands_joined. contains( "docker volume rm" ) ,
523+ "recovery steps should not include manual docker volume rm, got: {all_commands_joined}"
524+ ) ;
525+
526+ // First step should be a description-only step (no command) about retrying
527+ assert ! (
528+ d. recovery_steps[ 0 ] . command. is_none( ) ,
529+ "first recovery step should be description-only (automatic cleanup)"
530+ ) ;
531+ assert ! (
532+ d. recovery_steps[ 0 ]
533+ . description
534+ . contains( "cleanup was automatic" ) ,
535+ "first recovery step should mention automatic cleanup"
536+ ) ;
537+ }
538+
539+ #[ test]
540+ fn test_diagnose_corrupted_state_fallback_step_includes_gateway_name ( ) {
541+ // The fallback recovery step should interpolate the gateway name so
542+ // users can copy-paste the command.
543+ let d = diagnose_failure ( "my-gateway" , "is forbidden" , None )
544+ . expect ( "should match corrupted state pattern" ) ;
545+
546+ assert ! (
547+ d. recovery_steps. len( ) >= 2 ,
548+ "should have at least 2 recovery steps"
549+ ) ;
550+ let fallback = & d. recovery_steps [ 1 ] ;
551+ let cmd = fallback
552+ . command
553+ . as_deref ( )
554+ . expect ( "fallback step should have a command" ) ;
555+ assert ! (
556+ cmd. contains( "my-gateway" ) ,
557+ "fallback command should contain gateway name, got: {cmd}"
558+ ) ;
559+ assert ! (
560+ cmd. contains( "openshell gateway destroy" ) ,
561+ "fallback command should include gateway destroy, got: {cmd}"
562+ ) ;
563+ }
564+
484565 #[ test]
485566 fn test_diagnose_no_default_route ( ) {
486567 let diagnosis = diagnose_failure (
0 commit comments