Skip to content

Commit bb718f3

Browse files
committed
feat: add cleanup_all option to CleanupCommand and CLI
1 parent 3761981 commit bb718f3

5 files changed

Lines changed: 124 additions & 10 deletions

File tree

autotarcompress/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def cleanup(
185185
raise typer.Exit(1)
186186

187187
facade: BackupFacade = runner.initialize_config()
188-
success = facade.execute_command("cleanup")
188+
success = facade.execute_command("cleanup", cleanup_all=all_backups)
189189
if not success:
190190
raise typer.Exit(1)
191191

autotarcompress/commands/cleanup.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,37 @@
1717
class CleanupCommand(Command):
1818
"""Command to clean up old backup, encrypted, and decrypted files."""
1919

20-
def __init__(self, config: BackupConfig) -> None:
20+
def __init__(self, config: BackupConfig, cleanup_all: bool = False) -> None:
2121
"""Initialize CleanupCommand.
2222
2323
Args:
2424
config (BackupConfig): Backup configuration with retention and
2525
folder settings.
26+
cleanup_all (bool): If True, delete all backup files regardless
27+
of retention policy.
2628
2729
"""
2830
self.config: BackupConfig = config
31+
self.cleanup_all: bool = cleanup_all
2932
self.logger: logging.Logger = logging.getLogger(__name__)
3033

3134
def execute(self) -> bool:
3235
"""Delete old backup, encrypted, and decrypted files.
3336
34-
Per retention policy.
37+
Per retention policy or all files if cleanup_all is True.
3538
3639
Returns:
3740
bool: Always True (cleanup always completes, even if nothing
3841
to delete).
3942
4043
"""
41-
self._cleanup_files(".tar.xz", self.config.keep_backup)
42-
self._cleanup_files(".tar.xz-decrypted", self.config.keep_backup)
43-
self._cleanup_files(".tar-extracted", self.config.keep_backup)
44-
self._cleanup_files(".tar.xz.enc", self.config.keep_enc_backup)
44+
if self.cleanup_all:
45+
self._cleanup_all_files()
46+
else:
47+
self._cleanup_files(".tar.xz", self.config.keep_backup)
48+
self._cleanup_files(".tar.xz-decrypted", self.config.keep_backup)
49+
self._cleanup_files(".tar-extracted", self.config.keep_backup)
50+
self._cleanup_files(".tar.xz.enc", self.config.keep_enc_backup)
4551
return True
4652

4753
def _cleanup_files(self, ext: str, keep_count: int) -> None:
@@ -96,3 +102,15 @@ def _extract_date_from_filename(filename: str) -> datetime.datetime:
96102
self.logger.error("Failed to delete %s: %s", old_file, e)
97103
print(f"Failed to delete {old_file}: {e}")
98104
return None
105+
106+
def _cleanup_all_files(self) -> None:
107+
"""Delete all backup files regardless of retention policy.
108+
109+
This method removes all backup files of all types without
110+
respecting the keep_count configuration.
111+
112+
"""
113+
extensions = [".tar.xz", ".tar.xz-decrypted", ".tar-extracted", ".tar.xz.enc"]
114+
115+
for ext in extensions:
116+
self._cleanup_files(ext, 0) # keep_count=0 means delete all

autotarcompress/facade.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ def configure(self) -> None:
4141
self.config.save()
4242
print("\nConfiguration saved successfully!")
4343

44-
def execute_command(self, command_name: str) -> Any:
44+
def execute_command(self, command_name: str, **kwargs: Any) -> Any:
4545
"""Execute a predefined command by name.
4646
4747
Args:
4848
command_name (str): The name of the command to execute.
49+
**kwargs: Additional parameters to pass to the command constructor.
4950
5051
Returns:
5152
Any: The result of the command's execution.
@@ -54,7 +55,12 @@ def execute_command(self, command_name: str) -> Any:
5455
ValueError: If the command name is not recognized.
5556
5657
"""
57-
if command_name in self.commands:
58+
if command_name == "cleanup":
59+
# Handle cleanup command with potential parameters
60+
cleanup_all = kwargs.get("cleanup_all", False)
61+
command = CleanupCommand(self.config, cleanup_all=cleanup_all)
62+
return command.execute()
63+
elif command_name in self.commands:
5864
return self.commands[command_name].execute()
5965
raise ValueError(f"Unknown command: {command_name}")
6066

tests/commands/test_cleanup.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ def test_initialization(self, mock_config: BackupConfig) -> None:
118118
assert isinstance(command.logger, logging.Logger)
119119
assert command.logger.name == "autotarcompress.commands.cleanup"
120120

121+
def test_initialization_with_cleanup_all(self, mock_config: BackupConfig) -> None:
122+
"""Test CleanupCommand initialization with cleanup_all parameter.
123+
124+
Args:
125+
mock_config: Mock backup configuration.
126+
127+
"""
128+
command = CleanupCommand(mock_config, cleanup_all=True)
129+
130+
assert command.config is mock_config
131+
assert command.cleanup_all is True
132+
assert isinstance(command.logger, logging.Logger)
133+
134+
# Test default value
135+
command_default = CleanupCommand(mock_config)
136+
assert command_default.cleanup_all is False
137+
121138
@patch.object(CleanupCommand, "_cleanup_files")
122139
def test_execute_calls_cleanup_methods(
123140
self, mock_cleanup: Mock, cleanup_command: CleanupCommand
@@ -475,3 +492,65 @@ def test_file_extension_filtering(self, cleanup_command: CleanupCommand) -> None
475492

476493
# Should only process the 2 .tar.xz files and delete 1 (oldest)
477494
assert mock_unlink.call_count == 1
495+
496+
def test_cleanup_all_files_functionality(self, mock_config: BackupConfig) -> None:
497+
"""Test that cleanup_all deletes all files regardless of retention policy.
498+
499+
Args:
500+
mock_config: Mock backup configuration.
501+
502+
"""
503+
command = CleanupCommand(mock_config, cleanup_all=True)
504+
505+
# Mock files for different extensions
506+
test_files = [
507+
"15-12-2024.tar.xz",
508+
"14-12-2024.tar.xz",
509+
"13-12-2024.tar.xz-decrypted",
510+
"12-12-2024.tar-extracted",
511+
"11-12-2024.tar.xz.enc",
512+
]
513+
514+
with patch("os.listdir", return_value=test_files), patch(
515+
"pathlib.Path.unlink"
516+
) as mock_unlink, patch("pathlib.Path.is_dir", return_value=False):
517+
command._cleanup_all_files()
518+
519+
# Should delete all files (4 calls for 4 different extensions)
520+
# Each extension will be processed, but only matching files deleted
521+
assert mock_unlink.call_count == len(test_files)
522+
523+
def test_execute_with_cleanup_all_calls_cleanup_all_files(
524+
self, mock_config: BackupConfig
525+
) -> None:
526+
"""Test that execute() calls _cleanup_all_files when cleanup_all is True.
527+
528+
Args:
529+
mock_config: Mock backup configuration.
530+
531+
"""
532+
command = CleanupCommand(mock_config, cleanup_all=True)
533+
534+
with patch.object(command, "_cleanup_all_files") as mock_cleanup_all:
535+
result = command.execute()
536+
537+
assert result is True
538+
mock_cleanup_all.assert_called_once()
539+
540+
def test_execute_without_cleanup_all_calls_regular_cleanup(
541+
self, mock_config: BackupConfig
542+
) -> None:
543+
"""Test that execute() calls regular cleanup when cleanup_all is False.
544+
545+
Args:
546+
mock_config: Mock backup configuration.
547+
548+
"""
549+
command = CleanupCommand(mock_config, cleanup_all=False)
550+
551+
with patch.object(command, "_cleanup_files") as mock_cleanup_files:
552+
result = command.execute()
553+
554+
assert result is True
555+
# Should be called 4 times for different file extensions
556+
assert mock_cleanup_files.call_count == 4

tests/test_main.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,18 @@ def test_cleanup_command_success(self, mock_init_config: MagicMock) -> None:
236236

237237
result = self.runner.invoke(app, ["cleanup"])
238238
assert result.exit_code == 0
239-
mock_facade.execute_command.assert_called_once_with("cleanup")
239+
mock_facade.execute_command.assert_called_once_with("cleanup", cleanup_all=False)
240+
241+
@patch("autotarcompress.runner.initialize_config")
242+
def test_cleanup_command_all_success(self, mock_init_config: MagicMock) -> None:
243+
"""Test successful cleanup --all command execution."""
244+
mock_facade = MagicMock()
245+
mock_facade.execute_command.return_value = True
246+
mock_init_config.return_value = mock_facade
247+
248+
result = self.runner.invoke(app, ["cleanup", "--all"])
249+
assert result.exit_code == 0
250+
mock_facade.execute_command.assert_called_once_with("cleanup", cleanup_all=True)
240251

241252
@patch("autotarcompress.runner.run_main_loop")
242253
@patch("autotarcompress.runner.initialize_config")

0 commit comments

Comments
 (0)