@@ -287,11 +287,23 @@ impl DestroyCommandHandler {
287287 > {
288288 use crate :: domain:: environment:: state:: DestroyStep ;
289289
290- // Step 1: Destroy infrastructure via OpenTofu
291- Self :: destroy_infrastructure ( opentofu_client)
292- . map_err ( |e| ( e, DestroyStep :: DestroyInfrastructure ) ) ?;
290+ // Step 1: Conditionally destroy infrastructure via OpenTofu
291+ // Only attempt infrastructure destruction if infrastructure was provisioned
292+ if Self :: should_destroy_infrastructure ( environment) {
293+ info ! (
294+ environment = %environment. name( ) ,
295+ "Destroying provisioned infrastructure"
296+ ) ;
297+ Self :: destroy_infrastructure ( opentofu_client)
298+ . map_err ( |e| ( e, DestroyStep :: DestroyInfrastructure ) ) ?;
299+ } else {
300+ info ! (
301+ environment = %environment. name( ) ,
302+ "Skipping infrastructure destruction (environment was never provisioned)"
303+ ) ;
304+ }
293305
294- // Step 2: Clean up state files
306+ // Step 2: Clean up state files (always happens regardless of infrastructure state)
295307 Self :: cleanup_state_files ( environment) . map_err ( |e| ( e, DestroyStep :: CleanupStateFiles ) ) ?;
296308
297309 Ok ( ( ) )
@@ -357,6 +369,28 @@ impl DestroyCommandHandler {
357369
358370 // Private helper methods
359371
372+ /// Check if infrastructure should be destroyed
373+ ///
374+ /// Determines whether to attempt infrastructure destruction based on whether
375+ /// the `OpenTofu` build directory exists. If the directory doesn't exist, it means
376+ /// no infrastructure was ever provisioned (e.g., environment in Created state).
377+ ///
378+ /// # Arguments
379+ ///
380+ /// * `environment` - The environment being destroyed
381+ ///
382+ /// # Returns
383+ ///
384+ /// Returns `true` if infrastructure destruction should be attempted, `false` otherwise
385+ fn should_destroy_infrastructure (
386+ environment : & crate :: domain:: environment:: Environment <
387+ crate :: domain:: environment:: Destroying ,
388+ > ,
389+ ) -> bool {
390+ let tofu_build_dir = environment. tofu_build_dir ( ) ;
391+ tofu_build_dir. exists ( )
392+ }
393+
360394 /// Destroy the infrastructure using `OpenTofu`
361395 ///
362396 /// Executes the `OpenTofu` destroy workflow to remove all managed infrastructure.
@@ -497,4 +531,103 @@ mod tests {
497531 let destroy_error: DestroyCommandHandlerError = command_error_direct. into ( ) ;
498532 drop ( destroy_error) ;
499533 }
534+
535+ #[ test]
536+ fn it_should_skip_infrastructure_destruction_when_tofu_build_dir_does_not_exist ( ) {
537+ use crate :: domain:: environment:: testing:: EnvironmentTestBuilder ;
538+
539+ // Arrange: Create environment in Created state with no OpenTofu build directory
540+ let ( created_env, _data_dir, _build_dir, _temp_dir) =
541+ EnvironmentTestBuilder :: new ( ) . build_with_custom_paths ( ) ;
542+
543+ // Transition to Destroying state
544+ let destroying_env = created_env. start_destroying ( ) ;
545+
546+ // Verify tofu_build_dir does not exist
547+ assert ! (
548+ !destroying_env. tofu_build_dir( ) . exists( ) ,
549+ "OpenTofu build directory should not exist for Created state"
550+ ) ;
551+
552+ // Act: Check if infrastructure should be destroyed
553+ let should_destroy = DestroyCommandHandler :: should_destroy_infrastructure ( & destroying_env) ;
554+
555+ // Assert: Infrastructure destruction should be skipped
556+ assert ! (
557+ !should_destroy,
558+ "Infrastructure destruction should be skipped when tofu_build_dir does not exist"
559+ ) ;
560+ }
561+
562+ #[ test]
563+ fn it_should_attempt_infrastructure_destruction_when_tofu_build_dir_exists ( ) {
564+ use crate :: domain:: environment:: testing:: EnvironmentTestBuilder ;
565+
566+ // Arrange: Create environment with OpenTofu build directory
567+ let ( created_env, _data_dir, _build_dir, _temp_dir) =
568+ EnvironmentTestBuilder :: new ( ) . build_with_custom_paths ( ) ;
569+
570+ // Create the OpenTofu build directory to simulate provisioned state
571+ let tofu_build_dir = created_env. tofu_build_dir ( ) ;
572+ std:: fs:: create_dir_all ( & tofu_build_dir) . expect ( "Failed to create tofu build dir" ) ;
573+
574+ // Transition to Destroying state
575+ let destroying_env = created_env. start_destroying ( ) ;
576+
577+ // Verify tofu_build_dir exists
578+ assert ! (
579+ destroying_env. tofu_build_dir( ) . exists( ) ,
580+ "OpenTofu build directory should exist for provisioned environment"
581+ ) ;
582+
583+ // Act: Check if infrastructure should be destroyed
584+ let should_destroy = DestroyCommandHandler :: should_destroy_infrastructure ( & destroying_env) ;
585+
586+ // Assert: Infrastructure destruction should be attempted
587+ assert ! (
588+ should_destroy,
589+ "Infrastructure destruction should be attempted when tofu_build_dir exists"
590+ ) ;
591+ }
592+
593+ #[ test]
594+ fn it_should_clean_up_state_files_regardless_of_infrastructure_state ( ) {
595+ use crate :: domain:: environment:: testing:: EnvironmentTestBuilder ;
596+
597+ // Arrange: Create environment with data and build directories
598+ let ( created_env, data_dir, build_dir, _temp_dir) =
599+ EnvironmentTestBuilder :: new ( ) . build_with_custom_paths ( ) ;
600+
601+ // Create the directories
602+ std:: fs:: create_dir_all ( & data_dir) . expect ( "Failed to create data dir" ) ;
603+ std:: fs:: create_dir_all ( & build_dir) . expect ( "Failed to create build dir" ) ;
604+
605+ // Create some files in the directories
606+ std:: fs:: write ( data_dir. join ( "environment.json" ) , "{}" ) . expect ( "Failed to write file" ) ;
607+ std:: fs:: write ( build_dir. join ( "test.txt" ) , "test" ) . expect ( "Failed to write file" ) ;
608+
609+ // Verify directories exist before cleanup
610+ assert ! ( data_dir. exists( ) , "Data directory should exist" ) ;
611+ assert ! ( build_dir. exists( ) , "Build directory should exist" ) ;
612+
613+ // Act: Clean up state files
614+ let result = DestroyCommandHandler :: cleanup_state_files ( & created_env) ;
615+
616+ // Assert: Cleanup succeeded
617+ assert ! (
618+ result. is_ok( ) ,
619+ "State file cleanup should succeed: {:?}" ,
620+ result. err( )
621+ ) ;
622+
623+ // Assert: Directories were removed
624+ assert ! (
625+ !data_dir. exists( ) ,
626+ "Data directory should be removed after cleanup"
627+ ) ;
628+ assert ! (
629+ !build_dir. exists( ) ,
630+ "Build directory should be removed after cleanup"
631+ ) ;
632+ }
500633}
0 commit comments