from unittest.mock import MagicMock, patch import pytest from src.engines.engine_manager import EngineManager, EngineDownloadWorker class TestEngineManagerSyncClass: """_sync_class propagates instance attrs to class level.""" def test_sync_class_propagates_engines_path(self, engine_manager_instance): assert EngineManager.engines_path == engine_manager_instance.engines_path def test_sync_class_noop_when_no_instance(self): orig = EngineManager._default_instance try: EngineManager._default_instance = None EngineManager.engines_path = 'original' EngineManager._sync_class() assert EngineManager.engines_path == 'original' finally: EngineManager._default_instance = orig EngineManager._sync_class() def test_supported_engines_returns_list(self): engines = EngineManager.supported_engines() assert len(engines) >= 2 class TestEngineClassMapping: """Mapping file extensions and names to engine classes.""" def test_engine_class_with_name_finds_blender(self, engine_manager_instance): cls = EngineManager.engine_class_with_name('blender') assert cls is not None assert cls.__name__ == 'Blender' def test_engine_class_with_name_finds_ffmpeg(self, engine_manager_instance): cls = EngineManager.engine_class_with_name('ffmpeg') assert cls is not None assert cls.__name__ == 'FFMPEG' def test_engine_class_with_name_returns_none_for_unknown(self, engine_manager_instance): cls = EngineManager.engine_class_with_name('nonexistent') assert cls is None def test_engine_class_with_name_case_insensitive(self, engine_manager_instance): cls = EngineManager.engine_class_with_name('BLENDER') assert cls is not None assert cls.__name__ == 'Blender' def test_engine_class_for_project_path_no_engines_path(self, engine_manager_instance): engine_manager_instance.engines_path = None with pytest.raises(FileNotFoundError): EngineManager.engine_class_for_project_path('test.blend') class TestGetInstalledEngineData: """Parsing directory listings for managed engines.""" def test_get_installed_engine_data_no_path(self, engine_manager_instance): engine_manager_instance.engines_path = None with pytest.raises(FileNotFoundError): EngineManager.get_installed_engine_data() def test_get_installed_engine_data_empty_dir(self, engine_manager_instance, tmp_path): engine_manager_instance.engines_path = str(tmp_path / 'engines') result = EngineManager.get_installed_engine_data(ignore_system=True) assert result == [] @patch('src.engines.engine_manager.os.listdir') @patch('src.engines.engine_manager.os.path.isdir') def test_parse_managed_engine_directory( self, mock_isdir, mock_listdir, engine_manager_instance, tmp_path, ): engine_manager_instance.engines_path = str(tmp_path / 'engines') mock_listdir.return_value = ['blender-3.6.0-macos-arm64'] mock_isdir.return_value = True result = EngineManager.get_installed_engine_data(ignore_system=True) assert len(result) == 1 assert result[0]['engine'] == 'blender' assert result[0]['version'] == '3.6.0' assert result[0]['system_os'] == 'macos' assert result[0]['cpu'] == 'arm64' assert result[0]['type'] == 'managed' @patch('src.engines.engine_manager.os.listdir') @patch('src.engines.engine_manager.os.path.isdir') def test_filter_by_engine_name( self, mock_isdir, mock_listdir, engine_manager_instance, tmp_path, ): engine_manager_instance.engines_path = str(tmp_path / 'engines') mock_listdir.return_value = ['blender-3.6.0-macos-arm64', 'ffmpeg-6.0-macos-arm64'] mock_isdir.return_value = True blender_only = EngineManager.get_installed_engine_data( filter_name='blender', ignore_system=True) assert len(blender_only) == 1 assert blender_only[0]['engine'] == 'blender' class TestNewestInstalledEngineData: """Filtering by system and CPU.""" @patch('src.engines.engine_manager.current_system_os') @patch('src.engines.engine_manager.current_system_cpu') def test_newest_filters_by_system_and_cpu( self, mock_cpu, mock_os, engine_manager_instance, tmp_path, ): mock_os.return_value = 'macos' mock_cpu.return_value = 'arm64' engine_manager_instance.engines_path = str(tmp_path / 'engines') dirs = [ 'blender-3.6.0-macos-arm64', 'blender-4.0.0-linux-x86_64', 'blender-4.1.0-macos-arm64', ] with (patch('src.engines.engine_manager.os.listdir', return_value=dirs), patch('src.engines.engine_manager.os.path.isdir', return_value=True)): result = EngineManager.newest_installed_engine_data( 'blender', ignore_system=True) assert result['version'] == '4.1.0' assert result['system_os'] == 'macos' assert result['cpu'] == 'arm64' def test_newest_returns_empty_on_no_match(self, engine_manager_instance, tmp_path): engine_manager_instance.engines_path = str(tmp_path / 'engines') result = EngineManager.newest_installed_engine_data('nonexistent') assert result == [] class TestIsVersionInstalled: """Checking whether a specific version is installed.""" @patch('src.engines.engine_manager.current_system_os') @patch('src.engines.engine_manager.current_system_cpu') def test_finds_matching_version( self, mock_cpu, mock_os, engine_manager_instance, tmp_path, ): mock_os.return_value = 'macos' mock_cpu.return_value = 'arm64' engine_manager_instance.engines_path = str(tmp_path / 'engines') dirs = ['blender-3.6.0-macos-arm64', 'blender-4.1.0-macos-arm64'] with (patch('src.engines.engine_manager.os.listdir', return_value=dirs), patch('src.engines.engine_manager.os.path.isdir', return_value=True)): result = EngineManager.is_version_installed('blender', '3.6.0') assert result is not False @patch('src.engines.engine_manager.current_system_os') @patch('src.engines.engine_manager.current_system_cpu') def test_returns_false_on_no_match( self, mock_cpu, mock_os, engine_manager_instance, tmp_path, ): mock_os.return_value = 'macos' mock_cpu.return_value = 'arm64' engine_manager_instance.engines_path = str(tmp_path / 'engines') result = EngineManager.is_version_installed('blender', '99.0.0') assert result is False class TestDeleteEngineDownload: """Deleting a managed engine directory.""" @patch('src.engines.engine_manager.shutil.rmtree') @patch('src.engines.engine_manager.current_system_os', return_value='macos') @patch('src.engines.engine_manager.current_system_cpu', return_value='arm64') def test_delete_managed_engine( self, mock_cpu, mock_os, mock_rmtree, engine_manager_instance, tmp_path, ): engine_manager_instance.engines_path = str(tmp_path / 'engines') engines_dir = tmp_path / 'engines' / 'blender-3.6.0-macos-arm64' engines_dir.mkdir(parents=True) (engines_dir / 'Blender').write_text('fake binary') result = EngineManager.delete_engine_download('blender', '3.6.0') assert result is True mock_rmtree.assert_called_once_with(str(engines_dir), ignore_errors=False) def test_delete_nonexistent_engine(self, engine_manager_instance, tmp_path): engine_manager_instance.engines_path = str(tmp_path / 'engines') result = EngineManager.delete_engine_download('nonexistent', '1.0.0') assert result is False class TestActiveDownloads: """Background download tracking.""" def test_active_downloads_empty_initially(self, engine_manager_instance): assert EngineManager.active_downloads() == [] def test_download_tasks_tracked(self, engine_manager_instance): task = MagicMock(spec=EngineDownloadWorker) task.is_alive.return_value = True task.name = 'blender-4.0.0-macos-arm64' engine_manager_instance.download_tasks.append(task) active = EngineManager.active_downloads() assert len(active) == 1 class TestCreateWorker: """Creating worker instances for render jobs.""" def test_create_worker_raises_when_no_engines_installed(self, engine_manager_instance, tmp_path): engine_manager_instance.engines_path = str(tmp_path / 'engines') with patch.object(engine_manager_instance, '_get_installed_engine_data', return_value=[]): with pytest.raises(FileNotFoundError, match='Cannot find any installed'): EngineManager.create_worker( 'blender', input_path=tmp_path / 'test.blend', output_path=tmp_path / 'output', ) def test_create_worker_raises_for_unknown_engine(self, engine_manager_instance): with pytest.raises(AttributeError): EngineManager.create_worker( 'nonexistent', input_path='/tmp/test.blend', output_path='/tmp/output', ) class TestDownloadableEngines: """Engines with a downloader.""" def test_downloadable_engines_returns_list(self, engine_manager_instance): engines = EngineManager.downloadable_engines() assert isinstance(engines, list)