Add comprehensive CLI tool tests

- Unit tests for CLI commands (scan, search, download, rescan, display)
- Tests for user input handling, selection validation, and retry logic
- E2E tests for complete CLI workflows from user perspective
- Progress bar functionality and user feedback testing
- Error recovery and network failure handling tests
- Keyboard interrupt and invalid input scenario testing
- Environment variable configuration testing
This commit is contained in:
Lukas Pupka-Lipinski 2025-10-06 11:13:19 +02:00
parent 8f720443a4
commit 9bf8957a50
2 changed files with 830 additions and 0 deletions

View File

@ -0,0 +1,406 @@
"""
End-to-end tests for CLI flows.
Tests complete CLI workflows including progress bar functionality,
retry logic, user interactions, and error scenarios.
"""
import pytest
import sys
import os
from unittest.mock import Mock, patch
import tempfile
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
# Import after path setup
from src.cli.Main import SeriesApp # noqa: E402
@pytest.fixture
def temp_directory():
"""Create a temporary directory for testing."""
with tempfile.TemporaryDirectory() as temp_dir:
yield temp_dir
@pytest.mark.e2e
class TestCLICompleteWorkflows:
"""Test complete CLI workflows from user perspective."""
def test_search_and_download_workflow(self, temp_directory):
"""Test complete search -> select -> download workflow."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
# Mock search results
mock_search_results = [
{"name": "Test Anime", "link": "test_link"}
]
# Mock series for download
mock_episode_dict = {1: [1, 2, 3], 2: [1, 2]}
mock_series = Mock(
episodeDict=mock_episode_dict,
folder="test_anime",
key="test_key"
)
app.series_list = [mock_series]
# Mock loader
mock_loader = Mock()
mock_loader.Search.return_value = mock_search_results
mock_loader.IsLanguage.return_value = True
mock_loader.Download.return_value = None
app.Loaders.GetLoader.return_value = mock_loader
# Test search workflow
with patch('builtins.input', side_effect=['test query', '1']), \
patch('builtins.print'):
app.search_mode()
# Should have called search and add
mock_loader.Search.assert_called_with('test query')
app.List.add.assert_called_once()
# Test download workflow
with patch('rich.progress.Progress') as mock_progress_class, \
patch('time.sleep'), \
patch('builtins.input', return_value='1'):
mock_progress = Mock()
mock_progress_class.return_value = mock_progress
selected_series = app.get_user_selection()
assert selected_series is not None
app.download_series(selected_series)
# Should have set up progress tracking
mock_progress.start.assert_called_once()
mock_progress.stop.assert_called_once()
# Should have attempted downloads for all episodes
expected_downloads = sum(len(episodes) for episodes in mock_episode_dict.values())
assert mock_loader.Download.call_count == expected_downloads
def test_init_and_rescan_workflow(self, temp_directory):
"""Test initialization and rescanning workflow."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner') as mock_scanner_class, \
patch('src.cli.Main.SerieList') as mock_list_class:
mock_scanner = Mock()
mock_scanner_class.return_value = mock_scanner
mock_list = Mock()
mock_list_class.return_value = mock_list
app = SeriesApp(temp_directory)
app.SerieScanner = mock_scanner
# Test rescan workflow
with patch('rich.progress.Progress') as mock_progress_class, \
patch('builtins.print'):
mock_progress = Mock()
mock_progress_class.return_value = mock_progress
# Simulate init action
app.progress = mock_progress
app.task1 = "task1_id"
# Call reinit workflow
app.SerieScanner.Reinit()
app.SerieScanner.Scan(app.updateFromReinit)
# Should have called scanner methods
mock_scanner.Reinit.assert_called_once()
mock_scanner.Scan.assert_called_once()
def test_error_recovery_workflow(self, temp_directory):
"""Test error recovery in CLI workflows."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
# Test retry mechanism with eventual success
mock_func = Mock(side_effect=[
Exception("First failure"),
Exception("Second failure"),
None # Success on third try
])
with patch('time.sleep'), patch('builtins.print'):
result = app.retry(mock_func, max_retries=3, delay=0)
assert result is True
assert mock_func.call_count == 3
# Test retry mechanism with persistent failure
mock_func_fail = Mock(side_effect=Exception("Persistent error"))
with patch('time.sleep'), patch('builtins.print'):
result = app.retry(mock_func_fail, max_retries=2, delay=0)
assert result is False
assert mock_func_fail.call_count == 2
@pytest.mark.e2e
class TestCLIUserInteractionFlows:
"""Test CLI user interaction flows."""
def test_user_selection_validation_flow(self, temp_directory):
"""Test user selection with various invalid inputs before success."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
app.series_list = [
Mock(name="Anime 1", folder="anime1"),
Mock(name="Anime 2", folder="anime2")
]
# Test sequence: invalid text -> invalid number -> valid selection
input_sequence = ['invalid_text', '999', '1']
with patch('builtins.input', side_effect=input_sequence), \
patch('builtins.print'):
selected = app.get_user_selection()
assert selected is not None
assert len(selected) == 1
assert selected[0].name == "Anime 1"
def test_search_interaction_flow(self, temp_directory):
"""Test search interaction with various user inputs."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
mock_search_results = [
{"name": "Result 1", "link": "link1"},
{"name": "Result 2", "link": "link2"}
]
mock_loader = Mock()
mock_loader.Search.return_value = mock_search_results
app.Loaders.GetLoader.return_value = mock_loader
# Test sequence: search -> invalid selection -> valid selection
with patch('builtins.input', side_effect=['test search', '999', '1']), \
patch('builtins.print'):
app.search_mode()
# Should have added the selected item
app.List.add.assert_called_once()
def test_main_loop_interaction_flow(self, temp_directory):
"""Test main application loop with user interactions."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
app.series_list = [Mock(name="Test Anime", folder="test")]
# Mock various components
with patch.object(app, 'search_mode') as mock_search, \
patch.object(app, 'get_user_selection', return_value=[Mock()]), \
patch.object(app, 'download_series') as mock_download, \
patch('rich.progress.Progress'), \
patch('builtins.print'):
# Test sequence: search -> download -> exit
with patch('builtins.input', side_effect=['s', 'd', KeyboardInterrupt()]):
try:
app.run()
except KeyboardInterrupt:
pass
mock_search.assert_called_once()
mock_download.assert_called_once()
@pytest.mark.e2e
class TestCLIProgressAndFeedback:
"""Test CLI progress indicators and user feedback."""
def test_download_progress_flow(self, temp_directory):
"""Test download progress tracking throughout workflow."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
# Mock series with episodes
mock_series = [
Mock(
episodeDict={1: [1, 2], 2: [1]},
folder="anime1",
key="key1"
)
]
# Mock loader
mock_loader = Mock()
mock_loader.IsLanguage.return_value = True
mock_loader.Download.return_value = None
app.Loaders.GetLoader.return_value = mock_loader
with patch('rich.progress.Progress') as mock_progress_class, \
patch('time.sleep'):
mock_progress = Mock()
mock_progress_class.return_value = mock_progress
app.download_series(mock_series)
# Verify progress setup
assert mock_progress.add_task.call_count >= 3 # At least 3 tasks
mock_progress.start.assert_called_once()
mock_progress.stop.assert_called_once()
# Verify progress updates
assert mock_progress.update.call_count > 0
def test_progress_callback_integration(self, temp_directory):
"""Test progress callback integration with download system."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
app.progress = Mock()
app.task3 = "download_task"
# Test various progress states
progress_states = [
{
'status': 'downloading',
'total_bytes': 1000000,
'downloaded_bytes': 250000
},
{
'status': 'downloading',
'total_bytes': 1000000,
'downloaded_bytes': 750000
},
{
'status': 'finished'
}
]
for state in progress_states:
app.print_Download_Progress(state)
# Should have updated progress for each state
assert app.progress.update.call_count == len(progress_states)
# Last call should indicate completion
last_call = app.progress.update.call_args_list[-1]
assert last_call[1].get('completed') == 100
def test_scan_progress_integration(self, temp_directory):
"""Test scanning progress integration."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
app.progress = Mock()
app.task1 = "scan_task"
# Simulate scan progress updates
for i in range(5):
app.updateFromReinit("folder", i)
# Should have updated progress for each folder
assert app.progress.update.call_count == 5
# Each call should advance by 1
for call in app.progress.update.call_args_list:
assert call[1].get('advance') == 1
@pytest.mark.e2e
class TestCLIErrorScenarios:
"""Test CLI error scenarios and recovery."""
def test_network_error_recovery(self, temp_directory):
"""Test recovery from network errors during operations."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
# Mock network failures
network_error = Exception("Network connection failed")
mock_func = Mock(side_effect=[network_error, network_error, None])
with patch('time.sleep'), patch('builtins.print'):
result = app.retry(mock_func, max_retries=3, delay=0)
assert result is True
assert mock_func.call_count == 3
def test_invalid_directory_handling(self):
"""Test handling of invalid directory paths."""
invalid_directory = "/nonexistent/path/that/does/not/exist"
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
# Should not raise exception during initialization
app = SeriesApp(invalid_directory)
assert app.directory_to_search == invalid_directory
def test_empty_search_results_handling(self, temp_directory):
"""Test handling of empty search results."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
# Mock empty search results
mock_loader = Mock()
mock_loader.Search.return_value = []
app.Loaders.GetLoader.return_value = mock_loader
with patch('builtins.input', return_value='nonexistent anime'), \
patch('builtins.print') as mock_print:
app.search_mode()
# Should print "No results found" message
print_calls = [call[0][0] for call in mock_print.call_args_list]
assert any("No results found" in call for call in print_calls)
def test_keyboard_interrupt_handling(self, temp_directory):
"""Test graceful handling of keyboard interrupts."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp(temp_directory)
# Test that KeyboardInterrupt propagates correctly
with patch('builtins.input', side_effect=KeyboardInterrupt()):
with pytest.raises(KeyboardInterrupt):
app.run()

View File

@ -0,0 +1,424 @@
"""
Unit tests for CLI commands and functionality.
Tests CLI commands (scan, search, download, rescan, display series),
user input handling, and command-line interface logic.
"""
import pytest
import sys
import os
from unittest.mock import Mock, patch
# Add source directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
# Import after path setup
from src.cli.Main import SeriesApp, NoKeyFoundException, MatchNotFoundError # noqa: E402
@pytest.fixture
def mock_series_app():
"""Create a mock SeriesApp instance for testing."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'):
app = SeriesApp("/test/directory")
app.series_list = [
Mock(name="Test Anime 1", folder="test_anime_1"),
Mock(name="Test Anime 2", folder="test_anime_2"),
Mock(name=None, folder="unknown_anime")
]
return app
@pytest.mark.unit
class TestCLICommands:
"""Test CLI command functionality."""
def test_display_series_with_names(self, mock_series_app, capsys):
"""Test displaying series with proper names."""
mock_series_app.display_series()
captured = capsys.readouterr()
output = captured.out
assert "Current result:" in output
assert "1. Test Anime 1" in output
assert "2. Test Anime 2" in output
assert "3. unknown_anime" in output # Should show folder name when name is None
def test_search_command(self, mock_series_app):
"""Test search command functionality."""
mock_loader = Mock()
mock_loader.Search.return_value = [
{"name": "Found Anime 1", "link": "link1"},
{"name": "Found Anime 2", "link": "link2"}
]
mock_series_app.Loaders.GetLoader.return_value = mock_loader
results = mock_series_app.search("test query")
assert len(results) == 2
assert results[0]["name"] == "Found Anime 1"
mock_loader.Search.assert_called_once_with("test query")
def test_search_no_results(self, mock_series_app):
"""Test search command with no results."""
mock_loader = Mock()
mock_loader.Search.return_value = []
mock_series_app.Loaders.GetLoader.return_value = mock_loader
results = mock_series_app.search("nonexistent")
assert len(results) == 0
def test_user_selection_single(self, mock_series_app):
"""Test user selection with single series."""
with patch('builtins.input', return_value='1'):
selected = mock_series_app.get_user_selection()
assert selected is not None
assert len(selected) == 1
assert selected[0].name == "Test Anime 1"
def test_user_selection_multiple(self, mock_series_app):
"""Test user selection with multiple series."""
with patch('builtins.input', return_value='1,2'):
selected = mock_series_app.get_user_selection()
assert selected is not None
assert len(selected) == 2
assert selected[0].name == "Test Anime 1"
assert selected[1].name == "Test Anime 2"
def test_user_selection_all(self, mock_series_app):
"""Test user selection with 'all' option."""
with patch('builtins.input', return_value='all'):
selected = mock_series_app.get_user_selection()
assert selected is not None
assert len(selected) == 3 # All series
def test_user_selection_exit(self, mock_series_app):
"""Test user selection with exit command."""
with patch('builtins.input', return_value='exit'):
selected = mock_series_app.get_user_selection()
assert selected is None
def test_user_selection_invalid_input(self, mock_series_app):
"""Test user selection with invalid input followed by valid input."""
with patch('builtins.input', side_effect=['invalid', 'abc', '1']):
with patch('builtins.print'): # Suppress print output
selected = mock_series_app.get_user_selection()
assert selected is not None
assert len(selected) == 1
def test_retry_mechanism_success(self, mock_series_app):
"""Test retry mechanism with successful operation."""
mock_func = Mock()
result = mock_series_app.retry(mock_func, "arg1", max_retries=3, delay=0, key="value")
assert result is True
mock_func.assert_called_once_with("arg1", key="value")
def test_retry_mechanism_eventual_success(self, mock_series_app):
"""Test retry mechanism with failure then success."""
mock_func = Mock(side_effect=[Exception("Error"), Exception("Error"), None])
with patch('time.sleep'): # Speed up test
result = mock_series_app.retry(mock_func, max_retries=3, delay=0)
assert result is True
assert mock_func.call_count == 3
def test_retry_mechanism_failure(self, mock_series_app):
"""Test retry mechanism with persistent failure."""
mock_func = Mock(side_effect=Exception("Persistent error"))
with patch('time.sleep'), patch('builtins.print'): # Speed up test and suppress error output
result = mock_series_app.retry(mock_func, max_retries=3, delay=0)
assert result is False
assert mock_func.call_count == 3
@pytest.mark.unit
class TestCLISearchMode:
"""Test CLI search mode functionality."""
def test_search_mode_with_results(self, mock_series_app):
"""Test search mode with search results."""
mock_results = [
{"name": "Anime 1", "link": "link1"},
{"name": "Anime 2", "link": "link2"}
]
with patch('builtins.input', side_effect=['test search', '1']), \
patch.object(mock_series_app, 'search', return_value=mock_results), \
patch.object(mock_series_app.List, 'add') as mock_add, \
patch('builtins.print'):
mock_series_app.search_mode()
mock_add.assert_called_once()
def test_search_mode_no_results(self, mock_series_app):
"""Test search mode with no results."""
with patch('builtins.input', return_value='nonexistent'), \
patch.object(mock_series_app, 'search', return_value=[]), \
patch('builtins.print') as mock_print:
mock_series_app.search_mode()
# Should print "No results found"
print_calls = [call[0][0] for call in mock_print.call_args_list]
assert any("No results found" in call for call in print_calls)
def test_search_mode_empty_selection(self, mock_series_app):
"""Test search mode with empty selection (return)."""
mock_results = [{"name": "Anime 1", "link": "link1"}]
with patch('builtins.input', side_effect=['test', '']), \
patch.object(mock_series_app, 'search', return_value=mock_results), \
patch('builtins.print'):
# Should return without error
mock_series_app.search_mode()
def test_search_mode_invalid_selection(self, mock_series_app):
"""Test search mode with invalid then valid selection."""
mock_results = [{"name": "Anime 1", "link": "link1"}]
with patch('builtins.input', side_effect=['test', '999', '1']), \
patch.object(mock_series_app, 'search', return_value=mock_results), \
patch.object(mock_series_app.List, 'add'), \
patch('builtins.print'):
mock_series_app.search_mode()
@pytest.mark.unit
class TestCLIDownloadFunctionality:
"""Test CLI download functionality."""
def test_download_series_setup(self, mock_series_app):
"""Test download series initialization."""
mock_series = [
Mock(episodeDict={1: [1, 2], 2: [1]}, folder="anime1", key="key1"),
Mock(episodeDict={1: [1]}, folder="anime2", key="key2")
]
with patch('rich.progress.Progress') as mock_progress_class, \
patch('time.sleep'):
mock_progress = Mock()
mock_progress_class.return_value = mock_progress
mock_series_app.download_series(mock_series)
# Should create progress tracking
mock_progress.add_task.assert_called()
mock_progress.start.assert_called_once()
mock_progress.stop.assert_called_once()
def test_download_progress_callback(self, mock_series_app):
"""Test download progress callback functionality."""
mock_series_app.progress = Mock()
mock_series_app.task3 = "task3_id"
# Test downloading status
download_data = {
'status': 'downloading',
'total_bytes': 1000,
'downloaded_bytes': 500
}
mock_series_app.print_Download_Progress(download_data)
mock_series_app.progress.update.assert_called()
# Test finished status
download_data['status'] = 'finished'
mock_series_app.print_Download_Progress(download_data)
# Should update progress to 100%
update_calls = mock_series_app.progress.update.call_args_list
assert any(call[1].get('completed') == 100 for call in update_calls)
def test_download_progress_no_total(self, mock_series_app):
"""Test download progress with no total bytes."""
mock_series_app.progress = Mock()
mock_series_app.task3 = "task3_id"
download_data = {
'status': 'downloading',
'downloaded_bytes': 5242880 # 5MB
}
mock_series_app.print_Download_Progress(download_data)
# Should handle case where total_bytes is not available
mock_series_app.progress.update.assert_called()
@pytest.mark.unit
class TestCLIMainLoop:
"""Test CLI main application loop."""
def test_main_loop_search_action(self, mock_series_app):
"""Test main loop with search action."""
with patch('builtins.input', side_effect=['s', KeyboardInterrupt()]), \
patch.object(mock_series_app, 'search_mode') as mock_search:
try:
mock_series_app.run()
except KeyboardInterrupt:
pass
mock_search.assert_called_once()
def test_main_loop_init_action(self, mock_series_app):
"""Test main loop with init action."""
with patch('builtins.input', side_effect=['i', KeyboardInterrupt()]), \
patch('rich.progress.Progress'), \
patch('builtins.print'):
mock_series_app.SerieScanner = Mock()
mock_series_app.List = Mock()
try:
mock_series_app.run()
except KeyboardInterrupt:
pass
mock_series_app.SerieScanner.Reinit.assert_called_once()
mock_series_app.SerieScanner.Scan.assert_called_once()
def test_main_loop_download_action(self, mock_series_app):
"""Test main loop with download action."""
mock_selected = [Mock()]
with patch('builtins.input', side_effect=['d', KeyboardInterrupt()]), \
patch.object(mock_series_app, 'get_user_selection', return_value=mock_selected), \
patch.object(mock_series_app, 'download_series') as mock_download:
try:
mock_series_app.run()
except KeyboardInterrupt:
pass
mock_download.assert_called_once_with(mock_selected)
def test_main_loop_download_action_no_selection(self, mock_series_app):
"""Test main loop with download action but no series selected."""
with patch('builtins.input', side_effect=['d', KeyboardInterrupt()]), \
patch.object(mock_series_app, 'get_user_selection', return_value=None), \
patch.object(mock_series_app, 'download_series') as mock_download:
try:
mock_series_app.run()
except KeyboardInterrupt:
pass
mock_download.assert_not_called()
@pytest.mark.unit
class TestCLIExceptions:
"""Test CLI exception handling."""
def test_no_key_found_exception(self):
"""Test NoKeyFoundException creation and usage."""
exception = NoKeyFoundException("Test message")
assert str(exception) == "Test message"
assert isinstance(exception, Exception)
def test_match_not_found_error(self):
"""Test MatchNotFoundError creation and usage."""
error = MatchNotFoundError("No match found")
assert str(error) == "No match found"
assert isinstance(error, Exception)
def test_exception_handling_in_retry(self, mock_series_app):
"""Test exception handling in retry mechanism."""
def failing_function():
raise NoKeyFoundException("Key not found")
with patch('time.sleep'), patch('builtins.print'):
result = mock_series_app.retry(failing_function, max_retries=2, delay=0)
assert result is False
@pytest.mark.unit
class TestCLIInitialization:
"""Test CLI application initialization."""
def test_series_app_initialization(self):
"""Test SeriesApp initialization."""
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'), \
patch('builtins.print'):
app = SeriesApp("/test/directory")
assert app.directory_to_search == "/test/directory"
assert app.progress is None
assert hasattr(app, 'Loaders')
assert hasattr(app, 'SerieScanner')
assert hasattr(app, 'List')
def test_initialization_count_tracking(self):
"""Test that initialization count is tracked properly."""
initial_count = SeriesApp._initialization_count
with patch('src.cli.Main.Loaders'), \
patch('src.cli.Main.SerieScanner'), \
patch('src.cli.Main.SerieList'), \
patch('builtins.print'):
SeriesApp("/test1")
SeriesApp("/test2")
assert SeriesApp._initialization_count == initial_count + 2
def test_init_list_method(self, mock_series_app):
"""Test __InitList__ method."""
mock_missing_episodes = [Mock(), Mock()]
mock_series_app.List.GetMissingEpisode.return_value = mock_missing_episodes
mock_series_app._SeriesApp__InitList__()
assert mock_series_app.series_list == mock_missing_episodes
mock_series_app.List.GetMissingEpisode.assert_called_once()
@pytest.mark.unit
class TestCLIEnvironmentVariables:
"""Test CLI environment variable handling."""
def test_default_anime_directory(self):
"""Test default ANIME_DIRECTORY handling."""
with patch.dict(os.environ, {}, clear=True), \
patch('src.cli.Main.SeriesApp') as mock_app:
# Import and run main module simulation
import src.cli.Main
# The default should be the hardcoded path
default_path = "\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien"
result = os.getenv("ANIME_DIRECTORY", default_path)
assert result == default_path
def test_custom_anime_directory(self):
"""Test custom ANIME_DIRECTORY from environment."""
custom_path = "/custom/anime/directory"
with patch.dict(os.environ, {'ANIME_DIRECTORY': custom_path}):
result = os.getenv("ANIME_DIRECTORY", "default")
assert result == custom_path