Files
zhouxiaoka--autoclip/backend/tests/test_processing_framework.py
wehub-resource-sync 41b710f9c7
CI / Frontend checks (push) Failing after 0s
CI / Backend tests (push) Failing after 1s
I18n Documentation Sync / sync-docs (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:28:40 +08:00

541 lines
19 KiB
Python

"""
处理框架测试
使用pytest标准结构
"""
import pytest
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
import sys
import os
# 添加项目根目录到Python路径
project_root = Path(__file__).parent.parent.parent
sys.path.append(str(project_root))
from backend.services.config_manager import ProjectConfigManager, ProcessingStep
from backend.services.pipeline_adapter import PipelineAdapter
from backend.services.processing_orchestrator import ProcessingOrchestrator
from backend.services.processing_service import ProcessingService
from backend.services.processing_context import ProcessingContext
from backend.services.exceptions import ServiceError, ConfigurationError, FileOperationError, ProcessingError
class TestProjectConfigManager:
"""项目配置管理器测试"""
@pytest.fixture
def temp_project_dir(self, tmp_path):
"""创建临时项目目录"""
project_dir = tmp_path / "test_project"
project_dir.mkdir()
return project_dir
@pytest.fixture
def config_manager(self, temp_project_dir):
"""创建配置管理器实例"""
return ProjectConfigManager(str(temp_project_dir))
def test_config_manager_initialization(self, config_manager):
"""测试配置管理器初始化"""
assert config_manager.project_id is not None
assert config_manager.config_path.parent.exists()
def test_load_default_config(self, config_manager):
"""测试加载默认配置"""
# 设置测试环境变量
import os
os.environ['DASHSCOPE_API_KEY'] = 'test_api_key'
config = config_manager.config
# 新项目配置为空,这是正常的
assert isinstance(config, dict)
def test_update_processing_params(self, config_manager):
"""测试更新处理参数"""
new_params = {
"max_clips": 50,
"min_duration": 10.0,
"max_duration": 300.0
}
config_manager.update_processing_params(**new_params)
config = config_manager.config
for key, value in new_params.items():
assert config["processing_params"][key] == value
def test_update_llm_config(self, config_manager):
"""测试更新LLM配置"""
# 设置测试环境变量
import os
os.environ['DASHSCOPE_API_KEY'] = 'test_api_key'
llm_config = {
"api_key": "test_key",
"model_name": "gpt-4",
"max_retries": 3,
"timeout_seconds": 30
}
config_manager.update_llm_config(**llm_config)
config = config_manager.config
# 检查配置是否已更新
assert "llm" in config
def test_export_config(self, config_manager):
"""测试导出配置"""
# 设置测试环境变量
import os
os.environ['DASHSCOPE_API_KEY'] = 'test_api_key'
exported = config_manager.export_config()
# 检查导出的配置包含必要字段
assert isinstance(exported, dict)
def test_config_validation(self, config_manager):
"""测试配置验证"""
# 设置测试环境变量
import os
os.environ['DASHSCOPE_API_KEY'] = 'test_api_key'
# 测试配置验证
validation_result = config_manager.validate_config()
assert isinstance(validation_result, dict)
class TestPipelineAdapter:
"""流水线适配器测试"""
@pytest.fixture
def temp_project_dir(self, tmp_path):
"""创建临时项目目录"""
project_dir = tmp_path / "test_project"
project_dir.mkdir()
return project_dir
@pytest.fixture
def mock_srt_file(self, tmp_path):
"""创建模拟SRT文件"""
srt_file = tmp_path / "test.srt"
srt_content = """1
00:00:01,000 --> 00:00:05,000
这是第一段字幕
2
00:00:05,000 --> 00:00:10,000
这是第二段字幕
"""
srt_file.write_text(srt_content, encoding='utf-8')
return srt_file
@pytest.fixture
def adapter(self, temp_project_dir):
"""创建适配器实例"""
return PipelineAdapter(str(temp_project_dir))
def test_adapter_initialization(self, adapter):
"""测试适配器初始化"""
assert adapter.project_id is not None
def test_validate_pipeline_prerequisites_success(self, adapter, mock_srt_file):
"""测试流水线前置条件验证成功"""
# 设置测试环境变量
import os
os.environ['DASHSCOPE_API_KEY'] = 'test_api_key'
# 确保目录结构存在
adapter.path_manager.ensure_directories()
# 复制SRT文件到正确位置
srt_target_path = adapter.path_manager.get_srt_path()
srt_target_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(mock_srt_file, srt_target_path)
errors = adapter.validate_pipeline_prerequisites()
assert len(errors) == 0
def test_validate_pipeline_prerequisites_missing_srt(self, adapter):
"""测试流水线前置条件验证失败 - 缺少SRT文件"""
# 确保目录结构存在但不创建SRT文件
adapter.path_manager.ensure_directories()
errors = adapter.validate_pipeline_prerequisites()
assert len(errors) > 0
assert any("SRT文件" in error for error in errors)
def test_validate_pipeline_prerequisites_invalid_srt(self, adapter, tmp_path):
"""测试流水线前置条件验证 - SRT文件存在但格式无效"""
# 设置测试环境变量
import os
os.environ['DASHSCOPE_API_KEY'] = 'test_api_key'
# 确保目录结构存在
adapter.path_manager.ensure_directories()
# 创建无效的SRT文件(但文件存在)
srt_target_path = adapter.path_manager.get_srt_path()
srt_target_path.write_text("这不是有效的SRT格式")
# validate_pipeline_prerequisites只检查文件是否存在,不验证格式
# 所以这个测试应该通过(没有错误)
errors = adapter.validate_pipeline_prerequisites()
assert len(errors) == 0 # 文件存在,所以没有错误
def test_execute_step_success(self, adapter, mock_srt_file):
"""测试步骤执行成功"""
# 测试adapt_step方法
result = adapter.adapt_step("step1_outline", srt_path=mock_srt_file)
assert isinstance(result, dict)
assert "srt_path" in result or "input_srt" in result
def test_execute_step_failure(self, adapter, mock_srt_file):
"""测试步骤执行失败"""
# 测试无效步骤名称
with pytest.raises(ValueError):
adapter.adapt_step("invalid_step", srt_path=mock_srt_file)
class TestProcessingOrchestrator:
"""处理编排器测试"""
@pytest.fixture
def temp_project_dir(self, tmp_path):
"""创建临时项目目录"""
project_dir = tmp_path / "test_project"
project_dir.mkdir()
return project_dir
@pytest.fixture
def mock_db_session(self):
"""创建模拟数据库会话"""
return Mock()
@pytest.fixture
def orchestrator(self, temp_project_dir, mock_db_session):
"""创建编排器实例"""
return ProcessingOrchestrator(str(temp_project_dir), "test_task", mock_db_session)
def test_orchestrator_initialization(self, orchestrator):
"""测试编排器初始化"""
assert orchestrator.project_id is not None
assert orchestrator.task_id == "test_task"
def test_get_pipeline_status(self, orchestrator):
"""测试获取流水线状态"""
status = orchestrator.get_pipeline_status()
assert "project_id" in status
assert "task_id" in status
assert "pipeline_status" in status
def test_execute_step_success(self, orchestrator, tmp_path):
"""测试执行步骤成功"""
# 创建模拟SRT文件
srt_file = tmp_path / "test.srt"
srt_file.write_text("1\n00:00:01,000 --> 00:00:05,000\n测试字幕")
with patch('backend.services.processing_orchestrator.PipelineAdapter') as mock_adapter_class:
mock_adapter = Mock()
mock_adapter.execute_step.return_value = {"status": "completed"}
mock_adapter_class.return_value = mock_adapter
result = orchestrator.execute_step(ProcessingStep.STEP1_OUTLINE, srt_path=srt_file)
assert result["status"] == "completed"
def test_execute_step_failure(self, orchestrator, tmp_path):
"""测试执行步骤失败"""
srt_file = tmp_path / "test.srt"
srt_file.write_text("1\n00:00:01,000 --> 00:00:05,000\n测试字幕")
# 模拟步骤函数抛出异常
with patch.object(orchestrator, 'step_functions') as mock_step_functions:
mock_step_functions.__getitem__.return_value = Mock(side_effect=Exception("执行失败"))
with pytest.raises(Exception):
orchestrator.execute_step(ProcessingStep.STEP1_OUTLINE, srt_path=srt_file)
class TestProcessingService:
"""处理服务测试"""
@pytest.fixture
def mock_db_session(self):
"""创建模拟数据库会话"""
return Mock()
@pytest.fixture
def mock_task_repository(self):
"""创建模拟任务仓库"""
mock_repo = Mock()
mock_task = Mock()
mock_task.id = "test_task_001"
mock_repo.create.return_value = mock_task
return mock_repo
@pytest.fixture
def service(self, mock_db_session, mock_task_repository):
"""创建服务实例"""
service = ProcessingService(mock_db_session)
service.task_repo = mock_task_repository
return service
def test_service_initialization(self, service):
"""测试服务初始化"""
assert service.db is not None
assert service.task_repo is not None
def test_start_processing_success(self, service, tmp_path):
"""测试开始处理成功"""
srt_file = tmp_path / "test.srt"
srt_file.write_text("1\n00:00:01,000 --> 00:00:05,000\n测试字幕")
with patch('backend.services.processing_service.ProcessingOrchestrator') as mock_orchestrator_class:
mock_orchestrator = Mock()
mock_orchestrator.execute_pipeline.return_value = {"success": True}
mock_orchestrator_class.return_value = mock_orchestrator
result = service.start_processing("test_project", srt_file)
assert result["success"] is True
assert "task_id" in result
def test_start_processing_failure(self, service, tmp_path):
"""测试开始处理失败"""
srt_file = tmp_path / "test.srt"
srt_file.write_text("1\n00:00:01,000 --> 00:00:05,000\n测试字幕")
with patch('backend.services.processing_service.ProcessingOrchestrator') as mock_orchestrator_class:
mock_orchestrator = Mock()
mock_orchestrator.execute_pipeline.side_effect = ServiceError("处理失败")
mock_orchestrator_class.return_value = mock_orchestrator
with pytest.raises(ServiceError):
service.start_processing("test_project", srt_file)
def test_execute_single_step_success(self, service, tmp_path):
"""测试执行单个步骤成功"""
srt_file = tmp_path / "test.srt"
srt_file.write_text("1\n00:00:01,000 --> 00:00:05,000\n测试字幕")
with patch('backend.services.processing_service.ProcessingOrchestrator') as mock_orchestrator_class:
mock_orchestrator = Mock()
mock_orchestrator.execute_step.return_value = {"success": True}
mock_orchestrator_class.return_value = mock_orchestrator
result = service.execute_single_step("test_project", ProcessingStep.STEP1_OUTLINE, srt_file)
assert result["success"] is True
assert "step" in result
def test_get_processing_status(self, service):
"""测试获取处理状态"""
with patch('backend.services.processing_service.ProcessingOrchestrator') as mock_orchestrator_class:
mock_orchestrator = Mock()
mock_orchestrator.get_pipeline_status.return_value = {"pipeline_status": {"step1_outline": {"completed": True}}}
mock_orchestrator_class.return_value = mock_orchestrator
status = service.get_processing_status("test_project", "test_task")
assert "pipeline_status" in status
class TestProcessingContext:
"""处理上下文测试"""
@pytest.fixture
def mock_db_session(self):
"""创建模拟数据库会话"""
return Mock()
@pytest.fixture
def context(self, mock_db_session):
"""创建上下文实例"""
return ProcessingContext("test_project", "test_task", mock_db_session)
def test_context_initialization(self, context):
"""测试上下文初始化"""
assert context.project_id == "test_project"
assert context.task_id == "test_task"
assert context.is_initialized is False
assert context.is_completed is False
def test_context_validation(self, context):
"""测试上下文验证"""
# 测试有效上下文
assert context.is_valid_for_execution() is False # 未初始化
context.mark_initialized()
assert context.is_valid_for_execution() is True
def test_context_with_invalid_project_id(self, mock_db_session):
"""测试无效项目ID"""
with pytest.raises(ValueError):
ProcessingContext("", "test_task", mock_db_session)
def test_context_with_invalid_task_id(self, mock_db_session):
"""测试无效任务ID"""
with pytest.raises(ValueError):
ProcessingContext("test_project", "", mock_db_session)
def test_set_srt_path(self, context, tmp_path):
"""测试设置SRT路径"""
srt_file = tmp_path / "test.srt"
srt_file.write_text("测试内容")
context.set_srt_path(srt_file)
assert context.srt_path == srt_file
def test_set_srt_path_nonexistent(self, context):
"""测试设置不存在的SRT路径"""
with pytest.raises(FileNotFoundError):
context.set_srt_path(Path("nonexistent.srt"))
def test_context_state_management(self, context):
"""测试上下文状态管理"""
# 初始状态
assert context.is_initialized is False
assert context.is_completed is False
assert context.error_message is None
# 初始化
context.mark_initialized()
assert context.is_initialized is True
assert context.is_valid_for_execution() is True
# 设置错误
context.set_error("测试错误")
assert context.error_message == "测试错误"
assert context.is_valid_for_execution() is False
# 完成
context.mark_completed()
assert context.is_completed is True
assert context.is_valid_for_execution() is False
def test_context_summary(self, context):
"""测试上下文摘要"""
context.mark_initialized()
context.set_debug_mode(True)
summary = context.get_context_summary()
assert "project_id" in summary
assert "task_id" in summary
assert "debug_mode" in summary
assert "is_initialized" in summary
def test_context_clone(self, context, tmp_path):
"""测试上下文克隆"""
srt_file = tmp_path / "test.srt"
srt_file.write_text("测试内容")
context.set_srt_path(srt_file)
context.set_debug_mode(True)
context.mark_initialized()
cloned = context.clone()
assert cloned.project_id == context.project_id
assert cloned.task_id == context.task_id
assert cloned.srt_path == context.srt_path
assert cloned.debug_mode == context.debug_mode
assert cloned.is_initialized == context.is_initialized
class TestErrorScenarios:
"""错误场景测试"""
def test_configuration_error(self):
"""测试配置错误"""
error = ConfigurationError("配置无效", details={"field": "api_key"})
assert error.error_code.value == "CONFIG_INVALID"
assert "api_key" in error.details["field"]
def test_file_operation_error(self):
"""测试文件操作错误"""
error = FileOperationError("文件不存在", file_path="/invalid/path")
assert error.error_code.value == "FILE_NOT_FOUND"
assert error.details["file_path"] == "/invalid/path"
def test_processing_error(self):
"""测试处理错误"""
error = ProcessingError("步骤执行失败", step_name="step1_outline")
assert error.error_code.value == "PROCESSING_FAILED"
assert error.details["step_name"] == "step1_outline"
def test_error_to_dict(self):
"""测试错误转字典"""
error = ServiceError("测试错误", details={"key": "value"})
error_dict = error.to_dict()
assert "error_code" in error_dict
assert "message" in error_dict
assert "details" in error_dict
@pytest.fixture(scope="session")
def test_data_dir(tmp_path_factory):
"""创建测试数据目录"""
return tmp_path_factory.mktemp("test_data")
@pytest.fixture
def sample_srt_file(test_data_dir):
"""创建示例SRT文件"""
srt_file = test_data_dir / "sample.srt"
srt_content = """1
00:00:01,000 --> 00:00:05,000
这是第一段字幕内容
2
00:00:05,000 --> 00:00:10,000
这是第二段字幕内容
3
00:00:10,000 --> 00:00:15,000
这是第三段字幕内容
"""
srt_file.write_text(srt_content, encoding='utf-8')
return srt_file
def test_integration_basic_flow(test_data_dir, sample_srt_file):
"""测试基本流程集成"""
# 设置测试环境变量
import os
os.environ['DASHSCOPE_API_KEY'] = 'test_api_key'
# 创建项目目录
project_dir = test_data_dir / "integration_project"
project_dir.mkdir()
# 测试配置管理器
config_manager = ProjectConfigManager(str(project_dir))
config = config_manager.config
assert isinstance(config, dict)
# 测试流水线适配器
adapter = PipelineAdapter(str(project_dir))
# 复制SRT文件到项目目录
project_raw_dir = project_dir / "raw"
project_raw_dir.mkdir(parents=True, exist_ok=True)
shutil.copy(sample_srt_file, project_raw_dir / "transcript.srt")
# 验证前置条件
errors = adapter.validate_pipeline_prerequisites()
assert len(errors) == 0
def test_error_handling_scenarios():
"""测试错误处理场景"""
# 测试配置错误
with pytest.raises(ConfigurationError):
raise ConfigurationError("配置错误")
# 测试文件操作错误
with pytest.raises(FileOperationError):
raise FileOperationError("文件不存在", file_path="/invalid/path")
# 测试处理错误
with pytest.raises(ProcessingError):
raise ProcessingError("处理失败", step_name="step1")
if __name__ == "__main__":
pytest.main([__file__, "-v"])