@@ -552,3 +552,147 @@ def test_purge_with_cache_and_state_and_containers_with_networks_and_volumes(
552552 mock .call ("network" , ["abc" , "def" , "ghe" ]),
553553 ]
554554 )
555+
556+
557+ @mock .patch ("devservices.commands.purge.Console.confirm" )
558+ @mock .patch ("devservices.commands.purge.get_matching_containers" )
559+ @mock .patch ("devservices.commands.purge.get_volumes_for_containers" )
560+ @mock .patch ("devservices.commands.purge.stop_containers" )
561+ @mock .patch ("devservices.commands.purge.remove_docker_resources" )
562+ @mock .patch ("devservices.commands.purge._get_service_cache_paths" )
563+ def test_purge_specific_service (
564+ mock_get_service_cache_paths : mock .Mock ,
565+ mock_remove_docker_resources : mock .Mock ,
566+ mock_stop_containers : mock .Mock ,
567+ mock_get_volumes_for_containers : mock .Mock ,
568+ mock_get_matching_containers : mock .Mock ,
569+ mock_console_confirm : mock .Mock ,
570+ tmp_path : Path ,
571+ ) -> None :
572+ """Test that purging a specific service removes only that service's containers, volumes, and cache."""
573+ mock_console_confirm .return_value = True
574+ mock_get_matching_containers .return_value = [
575+ "kafka-container-1" ,
576+ "kafka-container-2" ,
577+ ]
578+ mock_get_volumes_for_containers .return_value = ["kafka-volume-1" , "kafka-volume-2" ]
579+ cache_path = tmp_path / "dependencies" / "v1" / "kafka-repo"
580+ cache_path .mkdir (parents = True , exist_ok = True )
581+ mock_get_service_cache_paths .return_value = [str (cache_path )]
582+
583+ with (
584+ mock .patch ("devservices.utils.state.STATE_DB_FILE" , str (tmp_path / "state" )),
585+ mock .patch (
586+ "devservices.utils.docker.check_docker_daemon_running" , return_value = None
587+ ),
588+ ):
589+ state = State ()
590+ # Don't add kafka to STARTED_SERVICES - it should be stopped before purging
591+ # Add redis to verify it's not affected by kafka purge
592+ state .update_service_entry ("redis" , "default" , StateTables .STARTED_SERVICES )
593+
594+ assert state .get_service_entries (StateTables .STARTED_SERVICES ) == ["redis" ]
595+ assert cache_path .exists ()
596+
597+ purge (Namespace (service_name = "kafka" ))
598+
599+ # redis should still be in state (unaffected)
600+ assert state .get_service_entries (StateTables .STARTED_SERVICES ) == ["redis" ]
601+ # Cache path should be removed
602+ assert not cache_path .exists ()
603+
604+ # Should filter containers by service name
605+ mock_get_matching_containers .assert_called_once_with (
606+ [
607+ DEVSERVICES_ORCHESTRATOR_LABEL ,
608+ "com.docker.compose.service=kafka" ,
609+ ]
610+ )
611+ mock_get_volumes_for_containers .assert_called_once_with (
612+ ["kafka-container-1" , "kafka-container-2" ]
613+ )
614+ mock_stop_containers .assert_called_once_with (
615+ ["kafka-container-1" , "kafka-container-2" ], should_remove = True
616+ )
617+ mock_remove_docker_resources .assert_called_once_with (
618+ "volume" , ["kafka-volume-1" , "kafka-volume-2" ]
619+ )
620+ mock_get_service_cache_paths .assert_called_once_with ("kafka" )
621+
622+
623+ @mock .patch ("devservices.commands.purge.Console.confirm" )
624+ @mock .patch ("devservices.commands.purge.get_matching_containers" )
625+ @mock .patch ("devservices.commands.purge._get_service_cache_paths" )
626+ def test_purge_specific_service_no_containers (
627+ mock_get_service_cache_paths : mock .Mock ,
628+ mock_get_matching_containers : mock .Mock ,
629+ mock_console_confirm : mock .Mock ,
630+ capsys : pytest .CaptureFixture [str ],
631+ tmp_path : Path ,
632+ ) -> None :
633+ """Test that purging a service with no containers or cache still removes it from state."""
634+ mock_console_confirm .return_value = True
635+ mock_get_matching_containers .return_value = []
636+ mock_get_service_cache_paths .return_value = []
637+
638+ with (
639+ mock .patch ("devservices.utils.state.STATE_DB_FILE" , str (tmp_path / "state" )),
640+ mock .patch (
641+ "devservices.utils.docker.check_docker_daemon_running" , return_value = None
642+ ),
643+ ):
644+ state = State ()
645+ # Don't add kafka to STARTED_SERVICES - it should be stopped before purging
646+
647+ args = Namespace (service_name = "kafka" )
648+ purge (args )
649+
650+ # State should remain empty (kafka was never added)
651+ assert state .get_service_entries (StateTables .STARTED_SERVICES ) == []
652+
653+ captured = capsys .readouterr ()
654+ assert "No containers found for kafka" in captured .out
655+ assert "No cache found for kafka" in captured .out
656+ assert "kafka has been purged" in captured .out
657+
658+
659+ @mock .patch ("devservices.commands.purge.Console.confirm" )
660+ @mock .patch ("devservices.commands.purge.get_matching_containers" )
661+ @mock .patch ("devservices.commands.purge._get_service_cache_paths" )
662+ def test_purge_specific_service_cancelled_by_user (
663+ mock_get_service_cache_paths : mock .Mock ,
664+ mock_get_matching_containers : mock .Mock ,
665+ mock_console_confirm : mock .Mock ,
666+ capsys : pytest .CaptureFixture [str ],
667+ tmp_path : Path ,
668+ ) -> None :
669+ """Test that purging a service can be cancelled by the user."""
670+ mock_console_confirm .return_value = False
671+ mock_get_matching_containers .return_value = []
672+ mock_get_service_cache_paths .return_value = []
673+
674+ with (
675+ mock .patch ("devservices.utils.state.STATE_DB_FILE" , str (tmp_path / "state" )),
676+ mock .patch (
677+ "devservices.utils.docker.check_docker_daemon_running" , return_value = None
678+ ),
679+ ):
680+ state = State ()
681+ # Add kafka to state
682+ state .update_service_entry ("kafka" , "default" , StateTables .STARTED_SERVICES )
683+
684+ args = Namespace (service_name = "kafka" )
685+ purge (args )
686+
687+ # Service should still be in state (purge was cancelled)
688+ assert state .get_service_entries (StateTables .STARTED_SERVICES ) == ["kafka" ]
689+
690+ # Should have prompted user
691+ mock_console_confirm .assert_called_once ()
692+
693+ # Should not have attempted to get containers
694+ mock_get_matching_containers .assert_not_called ()
695+ mock_get_service_cache_paths .assert_not_called ()
696+
697+ captured = capsys .readouterr ()
698+ assert "Purge cancelled." in captured .out
0 commit comments