refactor(06-02): update CLI and test imports to kb_workflow.pipeline
- Remove sys.path manipulation from kb_workflow.py - Update CLI imports to use kb_workflow.pipeline prefix - Update all test file imports and patch paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ from pathlib import Path
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from kb_pipeline.schemas import AnalyzeInput, AnalyzeOutput
|
||||
from kb_workflow.pipeline.schemas import AnalyzeInput, AnalyzeOutput
|
||||
|
||||
|
||||
# Mock AnalysisResult for testing
|
||||
@@ -213,8 +213,8 @@ class TestAnalyzeProcessorIntegration:
|
||||
self, mock_analysis_result, mock_state_manager, sample_metadata, sample_content, tmp_path
|
||||
):
|
||||
"""Test 1: AnalyzeProcessor loads metadata and content from files"""
|
||||
from kb_pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_pipeline.schemas import AnalyzeInput
|
||||
from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_workflow.pipeline.schemas import AnalyzeInput
|
||||
|
||||
output_file = str(tmp_path / "bilibili_BV1xx_analysis.md")
|
||||
|
||||
@@ -227,7 +227,7 @@ class TestAnalyzeProcessorIntegration:
|
||||
output_file=output_file
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = AnalyzeProcessor(state_manager=mock_state_manager)
|
||||
|
||||
@@ -251,8 +251,8 @@ class TestAnalyzeProcessorIntegration:
|
||||
self, mock_analysis_result, mock_state_manager, sample_metadata, sample_content, tmp_path
|
||||
):
|
||||
"""Test 2: AnalyzeProcessor calls DeepAnalyzer.analyze() with correct parameters"""
|
||||
from kb_pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_pipeline.schemas import AnalyzeInput
|
||||
from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_workflow.pipeline.schemas import AnalyzeInput
|
||||
|
||||
output_file = str(tmp_path / "bilibili_BV1xx_analysis.md")
|
||||
|
||||
@@ -265,7 +265,7 @@ class TestAnalyzeProcessorIntegration:
|
||||
output_file=output_file
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = AnalyzeProcessor(state_manager=mock_state_manager)
|
||||
|
||||
@@ -291,8 +291,8 @@ class TestAnalyzeProcessorIntegration:
|
||||
self, mock_analysis_result, mock_state_manager, sample_metadata, sample_content, tmp_path
|
||||
):
|
||||
"""Test 3: AnalyzeProcessor saves analysis report as Markdown"""
|
||||
from kb_pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_pipeline.schemas import AnalyzeInput
|
||||
from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_workflow.pipeline.schemas import AnalyzeInput
|
||||
|
||||
output_file = str(tmp_path / "bilibili_BV1xx_analysis.md")
|
||||
|
||||
@@ -305,7 +305,7 @@ class TestAnalyzeProcessorIntegration:
|
||||
output_file=output_file
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = AnalyzeProcessor(state_manager=mock_state_manager)
|
||||
|
||||
@@ -338,8 +338,8 @@ class TestAnalyzeProcessorIntegration:
|
||||
self, mock_analysis_result, mock_state_manager, sample_metadata, sample_content, tmp_path
|
||||
):
|
||||
"""Test 4: AnalyzeProcessor records state on success"""
|
||||
from kb_pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_pipeline.schemas import AnalyzeInput, WorkflowState
|
||||
from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_workflow.pipeline.schemas import AnalyzeInput, WorkflowState
|
||||
|
||||
# Create a mock state that will be updated
|
||||
mock_state = MagicMock(spec=WorkflowState)
|
||||
@@ -357,7 +357,7 @@ class TestAnalyzeProcessorIntegration:
|
||||
output_file=output_file
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = AnalyzeProcessor(state_manager=mock_state_manager)
|
||||
|
||||
@@ -381,8 +381,8 @@ class TestAnalyzeProcessorIntegration:
|
||||
self, mock_state_manager, sample_metadata, sample_content, tmp_path
|
||||
):
|
||||
"""Test 5: AnalyzeProcessor records state on failure"""
|
||||
from kb_pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_pipeline.schemas import AnalyzeInput, WorkflowState
|
||||
from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor
|
||||
from kb_workflow.pipeline.schemas import AnalyzeInput, WorkflowState
|
||||
|
||||
# Create a mock state that will be updated
|
||||
mock_state = MagicMock(spec=WorkflowState)
|
||||
@@ -400,7 +400,7 @@ class TestAnalyzeProcessorIntegration:
|
||||
output_file=output_file
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = AnalyzeProcessor(state_manager=mock_state_manager)
|
||||
|
||||
@@ -424,8 +424,8 @@ class TestAnalyzeProcessorModuleExport:
|
||||
"""Tests for AnalyzeProcessor module export."""
|
||||
|
||||
def test_analyze_processor_importable_from_processors(self):
|
||||
"""Test: AnalyzeProcessor can be imported from kb_pipeline.processors"""
|
||||
from kb_pipeline.processors import AnalyzeProcessor
|
||||
"""Test: AnalyzeProcessor can be imported from kb_workflow.pipeline.processors"""
|
||||
from kb_workflow.pipeline.processors import AnalyzeProcessor
|
||||
|
||||
assert AnalyzeProcessor is not None
|
||||
assert hasattr(AnalyzeProcessor, 'STEP_NAME')
|
||||
|
||||
@@ -13,9 +13,9 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from kb_pipeline.processors.archive import ArchiveProcessor
|
||||
from kb_pipeline.schemas import ArchiveInput, ArchiveOutput
|
||||
from kb_pipeline.state import WorkflowStateManager
|
||||
from kb_workflow.pipeline.processors.archive import ArchiveProcessor
|
||||
from kb_workflow.pipeline.schemas import ArchiveInput, ArchiveOutput
|
||||
from kb_workflow.pipeline.state import WorkflowStateManager
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -63,7 +63,7 @@ class TestArchiveProcessorCopyToPlatformDir:
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
||||
|
||||
with patch('kb_pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
mock_get_settings.return_value = mock_settings
|
||||
|
||||
processor = ArchiveProcessor()
|
||||
@@ -95,7 +95,7 @@ class TestArchiveProcessorFilenameFormat:
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
||||
|
||||
with patch('kb_pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
mock_get_settings.return_value = mock_settings
|
||||
|
||||
processor = ArchiveProcessor()
|
||||
@@ -138,7 +138,7 @@ class TestArchiveProcessorSkipsCompleted:
|
||||
state.mark_step_completed("archive")
|
||||
archive_test_env["state_manager"].save(state)
|
||||
|
||||
with patch('kb_pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
mock_get_settings.return_value = mock_settings
|
||||
|
||||
processor = ArchiveProcessor(state_manager=archive_test_env["state_manager"])
|
||||
@@ -171,7 +171,7 @@ class TestArchiveProcessorDetectsExisting:
|
||||
existing = platform_dir / "20260101_BV1test_analysis.md"
|
||||
existing.write_text("Old content")
|
||||
|
||||
with patch('kb_pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
mock_get_settings.return_value = mock_settings
|
||||
|
||||
processor = ArchiveProcessor()
|
||||
@@ -201,7 +201,7 @@ class TestArchiveProcessorHandlesMissingFile:
|
||||
mock_settings = MagicMock()
|
||||
mock_settings.archive_path = str(archive_test_env["archive_dir"])
|
||||
|
||||
with patch('kb_pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
with patch('kb_workflow.pipeline.processors.archive.get_settings') as mock_get_settings:
|
||||
mock_get_settings.return_value = mock_settings
|
||||
|
||||
processor = ArchiveProcessor()
|
||||
|
||||
@@ -12,8 +12,8 @@ from unittest.mock import AsyncMock, MagicMock, patch, mock_open
|
||||
from pydantic import ValidationError
|
||||
|
||||
# 测试将在实现后导入
|
||||
# from kb_pipeline.schemas import ASRInput, ASROutput
|
||||
# from kb_pipeline.processors.asr import ASRProcessor
|
||||
# from kb_workflow.pipeline.schemas import ASRInput, ASROutput
|
||||
# from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -25,7 +25,7 @@ class TestASRInputSchema:
|
||||
|
||||
def test_asr_input_inherits_step_input(self):
|
||||
"""ASRInput 应继承 StepInput 的字段"""
|
||||
from kb_pipeline.schemas import ASRInput, StepInput
|
||||
from kb_workflow.pipeline.schemas import ASRInput, StepInput
|
||||
|
||||
# 创建实例
|
||||
input_data = ASRInput(
|
||||
@@ -42,7 +42,7 @@ class TestASRInputSchema:
|
||||
|
||||
def test_asr_input_has_video_path(self):
|
||||
"""ASRInput 必须包含 video_path 字段"""
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
input_data = ASRInput(
|
||||
content_id="BV1xx",
|
||||
@@ -56,7 +56,7 @@ class TestASRInputSchema:
|
||||
|
||||
def test_asr_input_content_type_default(self):
|
||||
"""ASRInput content_type 默认为 'video'"""
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
input_data = ASRInput(
|
||||
content_id="BV1xx",
|
||||
@@ -73,7 +73,7 @@ class TestASROutputSchema:
|
||||
|
||||
def test_asr_output_inherits_step_output(self):
|
||||
"""ASROutput 应继承 StepOutput 的字段"""
|
||||
from kb_pipeline.schemas import ASROutput, StepOutput
|
||||
from kb_workflow.pipeline.schemas import ASROutput, StepOutput
|
||||
|
||||
output = ASROutput(
|
||||
success=True,
|
||||
@@ -90,7 +90,7 @@ class TestASROutputSchema:
|
||||
|
||||
def test_asr_output_tool_literal(self):
|
||||
"""ASROutput tool 字段只能是 'subtitle' | 'whisper' | 'whisper-diarization'"""
|
||||
from kb_pipeline.schemas import ASROutput
|
||||
from kb_workflow.pipeline.schemas import ASROutput
|
||||
|
||||
# 有效值
|
||||
for tool in ["subtitle", "whisper", "whisper-diarization"]:
|
||||
@@ -107,7 +107,7 @@ class TestASROutputSchema:
|
||||
|
||||
def test_asr_output_tool_invalid(self):
|
||||
"""ASROutput tool 字段无效值应抛出 ValidationError"""
|
||||
from kb_pipeline.schemas import ASROutput
|
||||
from kb_workflow.pipeline.schemas import ASROutput
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
ASROutput(
|
||||
@@ -122,7 +122,7 @@ class TestASROutputSchema:
|
||||
|
||||
def test_asr_output_has_required_fields(self):
|
||||
"""ASROutput 必须包含所有必需字段"""
|
||||
from kb_pipeline.schemas import ASROutput
|
||||
from kb_workflow.pipeline.schemas import ASROutput
|
||||
|
||||
output = ASROutput(
|
||||
success=True,
|
||||
@@ -143,7 +143,7 @@ class TestASROutputSchema:
|
||||
|
||||
def test_asr_output_speakers_optional(self):
|
||||
"""ASROutput speakers 字段是可选的"""
|
||||
from kb_pipeline.schemas import ASROutput
|
||||
from kb_workflow.pipeline.schemas import ASROutput
|
||||
|
||||
# 无 speakers
|
||||
output1 = ASROutput(
|
||||
@@ -180,18 +180,18 @@ class TestASRProcessorInit:
|
||||
|
||||
def test_asr_processor_init_success(self, mock_settings):
|
||||
"""ASRProcessor 初始化成功"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
assert processor is not None
|
||||
assert processor.STEP_NAME == "asr"
|
||||
|
||||
def test_asr_processor_with_state_manager(self, mock_settings, mock_state_manager):
|
||||
"""ASRProcessor 可接受 state_manager 参数"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor(state_manager=mock_state_manager)
|
||||
assert processor.state_manager is mock_state_manager
|
||||
|
||||
@@ -202,13 +202,13 @@ class TestOfficialSubtitle:
|
||||
@pytest.mark.asyncio
|
||||
async def test_official_subtitle_found(self, mock_settings, sample_subtitle_response, sample_subtitle_content, tmp_path):
|
||||
"""_try_official_subtitle 找到字幕时返回 ASROutput"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
|
||||
# Mock API 调用
|
||||
@@ -238,13 +238,13 @@ class TestOfficialSubtitle:
|
||||
@pytest.mark.asyncio
|
||||
async def test_official_subtitle_not_found(self, mock_settings, tmp_path):
|
||||
"""_try_official_subtitle 无字幕时返回 None"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
|
||||
# Mock API 调用 - 无字幕
|
||||
@@ -272,13 +272,13 @@ class TestOfficialSubtitle:
|
||||
@pytest.mark.asyncio
|
||||
async def test_process_tries_subtitle_first(self, mock_settings, tmp_path):
|
||||
"""process() 方法优先尝试官方字幕"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
|
||||
# Mock _try_official_subtitle 返回成功
|
||||
@@ -304,8 +304,8 @@ class TestWhisperBasic:
|
||||
@pytest.mark.asyncio
|
||||
async def test_whisper_basic(self, mock_settings, mock_whisper_path, tmp_path):
|
||||
"""_run_whisper 调用成功返回 ASROutput"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
@@ -318,14 +318,14 @@ class TestWhisperBasic:
|
||||
srt_path.write_text("1\n00:00:00,000 --> 00:00:03,000\ntest\n")
|
||||
json_path.write_text('{"transcription": [{"text": "test"}]}')
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_whisper_cpp_path", return_value=mock_whisper_path):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_whisper_cpp_path", return_value=mock_whisper_path):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
|
||||
with patch("kb_pipeline.processors.asr.convert_to_wav") as mock_convert:
|
||||
with patch("kb_workflow.pipeline.processors.asr.convert_to_wav") as mock_convert:
|
||||
mock_convert.return_value = str(tmp_path / "test_audio.wav")
|
||||
|
||||
input_data = ASRInput(
|
||||
@@ -344,8 +344,8 @@ class TestWhisperBasic:
|
||||
@pytest.mark.asyncio
|
||||
async def test_temp_file_cleanup(self, mock_settings, mock_whisper_path, tmp_path):
|
||||
"""临时音频文件处理后自动清理"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
@@ -353,14 +353,14 @@ class TestWhisperBasic:
|
||||
audio_path = tmp_path / "test_audio.wav"
|
||||
audio_path.write_text("fake audio")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_whisper_cpp_path", return_value=mock_whisper_path):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_whisper_cpp_path", return_value=mock_whisper_path):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
|
||||
with patch("kb_pipeline.processors.asr.convert_to_wav", return_value=str(audio_path)):
|
||||
with patch("kb_workflow.pipeline.processors.asr.convert_to_wav", return_value=str(audio_path)):
|
||||
with patch.object(processor, "_parse_whisper_output") as mock_parse:
|
||||
mock_parse.return_value = MagicMock(
|
||||
success=True,
|
||||
@@ -387,16 +387,16 @@ class TestWhisperBasic:
|
||||
@pytest.mark.asyncio
|
||||
async def test_whisper_path_not_found(self, mock_settings, tmp_path):
|
||||
"""Whisper 路径不存在时抛出 RuntimeError"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor, WhisperNotFoundError
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor, WhisperNotFoundError
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_whisper_cpp_path", side_effect=WhisperNotFoundError("Not found")):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_whisper_cpp_path", side_effect=WhisperNotFoundError("Not found")):
|
||||
input_data = ASRInput(
|
||||
content_id="BV1xx",
|
||||
platform="bilibili",
|
||||
@@ -414,20 +414,20 @@ class TestDiarization:
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_hf_token_uses_basic_whisper(self, mock_settings, mock_whisper_path, tmp_path):
|
||||
"""HF_TOKEN 未配置时使用普通 Whisper"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor()
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_whisper_cpp_path", return_value=mock_whisper_path):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_whisper_cpp_path", return_value=mock_whisper_path):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0)
|
||||
|
||||
with patch("kb_pipeline.processors.asr.convert_to_wav") as mock_convert:
|
||||
with patch("kb_workflow.pipeline.processors.asr.convert_to_wav") as mock_convert:
|
||||
mock_convert.return_value = str(tmp_path / "test_audio.wav")
|
||||
|
||||
with patch.object(processor, "_parse_whisper_output") as mock_parse:
|
||||
@@ -456,13 +456,13 @@ class TestDiarization:
|
||||
@pytest.mark.asyncio
|
||||
async def test_diarization_with_hf_token(self, mock_settings_with_hf_token, mock_whisper_path, tmp_path):
|
||||
"""HF_TOKEN 配置时自动启用说话者分离"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings_with_hf_token):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings_with_hf_token):
|
||||
processor = ASRProcessor()
|
||||
|
||||
with patch.object(processor, "_run_whisper_with_diarization") as mock_diarization:
|
||||
@@ -493,13 +493,13 @@ class TestDiarization:
|
||||
@pytest.mark.asyncio
|
||||
async def test_diarization_result_has_speaker(self, mock_settings_with_hf_token, mock_whisper_path, tmp_path):
|
||||
"""说话者分离结果包含 speaker 字段"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings_with_hf_token):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings_with_hf_token):
|
||||
processor = ASRProcessor()
|
||||
|
||||
with patch.object(processor, "_run_whisper_basic") as mock_basic:
|
||||
@@ -513,12 +513,12 @@ class TestDiarization:
|
||||
duration_seconds=3.0
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.asr.run_diarization") as mock_dia:
|
||||
with patch("kb_workflow.pipeline.processors.asr.run_diarization") as mock_dia:
|
||||
mock_dia.return_value = {
|
||||
"SPEAKER_00": [{"start": 0.0, "end": 3.0}]
|
||||
}
|
||||
|
||||
with patch("kb_pipeline.processors.asr.assign_speakers_to_segments") as mock_assign:
|
||||
with patch("kb_workflow.pipeline.processors.asr.assign_speakers_to_segments") as mock_assign:
|
||||
mock_assign.return_value = [
|
||||
{"start": 0.0, "end": 3.0, "text": "hello", "speaker": "SPEAKER_00"}
|
||||
]
|
||||
@@ -538,8 +538,8 @@ class TestStateRecording:
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_recording_success(self, mock_settings, mock_state_manager, tmp_path):
|
||||
"""ASR 成功时状态记录为 completed"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput, WorkflowState
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput, WorkflowState
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
@@ -547,7 +547,7 @@ class TestStateRecording:
|
||||
# 创建 mock state
|
||||
mock_state = MagicMock(spec=WorkflowState)
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor(state_manager=mock_state_manager)
|
||||
mock_state_manager.load.return_value = mock_state
|
||||
|
||||
@@ -575,15 +575,15 @@ class TestStateRecording:
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_recording_failure(self, mock_settings, mock_state_manager, tmp_path):
|
||||
"""ASR 失败时状态记录为 failed"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput, WorkflowState
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput, WorkflowState
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
mock_state = MagicMock(spec=WorkflowState)
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor(state_manager=mock_state_manager)
|
||||
mock_state_manager.load.return_value = mock_state
|
||||
|
||||
@@ -609,13 +609,13 @@ class TestStateRecording:
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_no_manager(self, mock_settings, tmp_path):
|
||||
"""无 state_manager 时不记录状态"""
|
||||
from kb_pipeline.processors.asr import ASRProcessor
|
||||
from kb_pipeline.schemas import ASRInput
|
||||
from kb_workflow.pipeline.processors.asr import ASRProcessor
|
||||
from kb_workflow.pipeline.schemas import ASRInput
|
||||
|
||||
video_path = tmp_path / "test.mp4"
|
||||
video_path.write_text("fake video")
|
||||
|
||||
with patch("kb_pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
with patch("kb_workflow.pipeline.processors.asr.get_settings", return_value=mock_settings):
|
||||
processor = ASRProcessor(state_manager=None)
|
||||
|
||||
with patch.object(processor, "_try_official_subtitle") as mock_sub:
|
||||
|
||||
@@ -17,21 +17,21 @@ class TestBaseProcessorAbstract:
|
||||
|
||||
def test_base_processor_cannot_be_instantiated(self):
|
||||
"""BaseProcessor 不能直接实例化"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
BaseProcessor()
|
||||
|
||||
def test_base_processor_is_abstract(self):
|
||||
"""BaseProcessor 是抽象类"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
|
||||
assert issubclass(BaseProcessor, ABC)
|
||||
|
||||
def test_subclass_must_implement_process(self):
|
||||
"""子类必须实现 process() 方法"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_pipeline.schemas import StepInput, StepOutput
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.schemas import StepInput, StepOutput
|
||||
|
||||
# 缺少 process 实现的子类无法实例化
|
||||
class IncompleteProcessor(BaseProcessor):
|
||||
@@ -42,8 +42,8 @@ class TestBaseProcessorAbstract:
|
||||
|
||||
def test_subclass_can_be_instantiated_with_process(self):
|
||||
"""实现了 process() 的子类可以实例化"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_pipeline.schemas import StepInput, StepOutput
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.schemas import StepInput, StepOutput
|
||||
|
||||
class CompleteProcessor(BaseProcessor):
|
||||
STEP_NAME = "complete"
|
||||
@@ -52,7 +52,7 @@ class TestBaseProcessorAbstract:
|
||||
pass
|
||||
|
||||
# 可以实例化
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = CompleteProcessor()
|
||||
assert processor is not None
|
||||
@@ -63,8 +63,8 @@ class TestBaseProcessorAttributes:
|
||||
|
||||
def test_base_processor_has_step_name_class_attribute(self):
|
||||
"""BaseProcessor 要求子类定义 STEP_NAME 类属性"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_pipeline.schemas import StepInput, StepOutput
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.schemas import StepInput, StepOutput
|
||||
|
||||
class TestProcessor(BaseProcessor):
|
||||
STEP_NAME = "test_step"
|
||||
@@ -72,15 +72,15 @@ class TestBaseProcessorAttributes:
|
||||
async def process(self, input_data: StepInput) -> StepOutput:
|
||||
pass
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = TestProcessor()
|
||||
assert processor.STEP_NAME == "test_step"
|
||||
|
||||
def test_base_processor_provides_state_manager(self):
|
||||
"""BaseProcessor 提供 state_manager 属性"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_pipeline.schemas import StepInput, StepOutput
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.schemas import StepInput, StepOutput
|
||||
|
||||
class TestProcessor(BaseProcessor):
|
||||
STEP_NAME = "test"
|
||||
@@ -90,15 +90,15 @@ class TestBaseProcessorAttributes:
|
||||
|
||||
mock_state_manager = MagicMock()
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = TestProcessor(state_manager=mock_state_manager)
|
||||
assert processor.state_manager is mock_state_manager
|
||||
|
||||
def test_base_processor_state_manager_optional(self):
|
||||
"""BaseProcessor state_manager 是可选的"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_pipeline.schemas import StepInput, StepOutput
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.schemas import StepInput, StepOutput
|
||||
|
||||
class TestProcessor(BaseProcessor):
|
||||
STEP_NAME = "test"
|
||||
@@ -106,15 +106,15 @@ class TestBaseProcessorAttributes:
|
||||
async def process(self, input_data: StepInput) -> StepOutput:
|
||||
pass
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
processor = TestProcessor()
|
||||
assert processor.state_manager is None
|
||||
|
||||
def test_base_processor_provides_settings(self):
|
||||
"""BaseProcessor 提供 settings 属性"""
|
||||
from kb_pipeline.processors.base import BaseProcessor
|
||||
from kb_pipeline.schemas import StepInput, StepOutput
|
||||
from kb_workflow.pipeline.processors.base import BaseProcessor
|
||||
from kb_workflow.pipeline.schemas import StepInput, StepOutput
|
||||
|
||||
class TestProcessor(BaseProcessor):
|
||||
STEP_NAME = "test"
|
||||
@@ -124,7 +124,7 @@ class TestBaseProcessorAttributes:
|
||||
|
||||
mock_settings = MagicMock()
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_get_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_get_settings:
|
||||
mock_get_settings.return_value = mock_settings
|
||||
processor = TestProcessor()
|
||||
assert processor.settings is mock_settings
|
||||
|
||||
@@ -13,7 +13,7 @@ from datetime import datetime
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from pathlib import Path
|
||||
|
||||
from kb_pipeline.schemas import DownloadInput, DownloadOutput
|
||||
from kb_workflow.pipeline.schemas import DownloadInput, DownloadOutput
|
||||
|
||||
|
||||
class TestDownloadInputSchema:
|
||||
@@ -180,8 +180,8 @@ class TestDownloadProcessorMetadata:
|
||||
self, mock_bilibili_adapter, mock_state_manager, tmp_path
|
||||
):
|
||||
"""Test 1: DownloadProcessor fetches Bilibili video metadata correctly"""
|
||||
from kb_pipeline.processors.download import DownloadProcessor
|
||||
from kb_pipeline.schemas import DownloadInput
|
||||
from kb_workflow.pipeline.processors.download import DownloadProcessor
|
||||
from kb_workflow.pipeline.schemas import DownloadInput
|
||||
|
||||
input_data = DownloadInput(
|
||||
content_id="BV1xx",
|
||||
@@ -190,7 +190,7 @@ class TestDownloadProcessorMetadata:
|
||||
content_type="video"
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
with patch.object(
|
||||
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_adapter
|
||||
@@ -212,8 +212,8 @@ class TestDownloadProcessorMetadata:
|
||||
self, mock_zhihu_adapter, mock_state_manager, tmp_path
|
||||
):
|
||||
"""Test 2: DownloadProcessor fetches Zhihu answer metadata correctly"""
|
||||
from kb_pipeline.processors.download import DownloadProcessor
|
||||
from kb_pipeline.schemas import DownloadInput
|
||||
from kb_workflow.pipeline.processors.download import DownloadProcessor
|
||||
from kb_workflow.pipeline.schemas import DownloadInput
|
||||
|
||||
input_data = DownloadInput(
|
||||
content_id="456",
|
||||
@@ -222,7 +222,7 @@ class TestDownloadProcessorMetadata:
|
||||
content_type="answer"
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
with patch.object(
|
||||
DownloadProcessor, '_get_adapter', return_value=mock_zhihu_adapter
|
||||
@@ -243,8 +243,8 @@ class TestDownloadProcessorMetadata:
|
||||
self, mock_bilibili_adapter, mock_state_manager, tmp_path
|
||||
):
|
||||
"""Test 3: DownloadProcessor records state on success"""
|
||||
from kb_pipeline.processors.download import DownloadProcessor
|
||||
from kb_pipeline.schemas import DownloadInput, WorkflowState
|
||||
from kb_workflow.pipeline.processors.download import DownloadProcessor
|
||||
from kb_workflow.pipeline.schemas import DownloadInput, WorkflowState
|
||||
|
||||
# Create a mock state that will be updated
|
||||
mock_state = MagicMock(spec=WorkflowState)
|
||||
@@ -258,7 +258,7 @@ class TestDownloadProcessorMetadata:
|
||||
content_type="video"
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
with patch.object(
|
||||
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_adapter
|
||||
@@ -279,9 +279,9 @@ class TestDownloadProcessorMetadata:
|
||||
self, mock_bilibili_adapter, mock_state_manager, tmp_path
|
||||
):
|
||||
"""Test 4: DownloadProcessor records state on failure"""
|
||||
from kb_pipeline.processors.download import DownloadProcessor
|
||||
from kb_pipeline.schemas import DownloadInput, WorkflowState
|
||||
from kb_pipeline.errors import NetworkError
|
||||
from kb_workflow.pipeline.processors.download import DownloadProcessor
|
||||
from kb_workflow.pipeline.schemas import DownloadInput, WorkflowState
|
||||
from kb_workflow.pipeline.errors import NetworkError
|
||||
|
||||
# Make adapter raise an error
|
||||
mock_bilibili_adapter.fetch_metadata = AsyncMock(
|
||||
@@ -305,7 +305,7 @@ class TestDownloadProcessorMetadata:
|
||||
content_type="video"
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
with patch.object(
|
||||
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_adapter
|
||||
@@ -369,8 +369,8 @@ class TestDownloadProcessorContent:
|
||||
self, mock_bilibili_article_adapter, mock_state_manager, tmp_path
|
||||
):
|
||||
"""Test 1: DownloadProcessor downloads Bilibili article content"""
|
||||
from kb_pipeline.processors.download import DownloadProcessor
|
||||
from kb_pipeline.schemas import DownloadInput
|
||||
from kb_workflow.pipeline.processors.download import DownloadProcessor
|
||||
from kb_workflow.pipeline.schemas import DownloadInput
|
||||
|
||||
input_data = DownloadInput(
|
||||
content_id="CV123",
|
||||
@@ -379,7 +379,7 @@ class TestDownloadProcessorContent:
|
||||
content_type="article"
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
with patch.object(
|
||||
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_article_adapter
|
||||
@@ -403,8 +403,8 @@ class TestDownloadProcessorContent:
|
||||
self, mock_bilibili_video_adapter, mock_state_manager, tmp_path
|
||||
):
|
||||
"""Test 2: DownloadProcessor skips video download (handled by ASR)"""
|
||||
from kb_pipeline.processors.download import DownloadProcessor
|
||||
from kb_pipeline.schemas import DownloadInput
|
||||
from kb_workflow.pipeline.processors.download import DownloadProcessor
|
||||
from kb_workflow.pipeline.schemas import DownloadInput
|
||||
|
||||
input_data = DownloadInput(
|
||||
content_id="BV1xx",
|
||||
@@ -413,7 +413,7 @@ class TestDownloadProcessorContent:
|
||||
content_type="video"
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
with patch.object(
|
||||
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_video_adapter
|
||||
@@ -433,8 +433,8 @@ class TestDownloadProcessorContent:
|
||||
self, mock_bilibili_article_adapter, mock_state_manager, tmp_path
|
||||
):
|
||||
"""Test 3: DownloadProcessor creates correct output file paths"""
|
||||
from kb_pipeline.processors.download import DownloadProcessor
|
||||
from kb_pipeline.schemas import DownloadInput
|
||||
from kb_workflow.pipeline.processors.download import DownloadProcessor
|
||||
from kb_workflow.pipeline.schemas import DownloadInput
|
||||
|
||||
input_data = DownloadInput(
|
||||
content_id="CV123",
|
||||
@@ -443,7 +443,7 @@ class TestDownloadProcessorContent:
|
||||
content_type="article"
|
||||
)
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
with patch.object(
|
||||
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_article_adapter
|
||||
@@ -466,16 +466,16 @@ class TestDownloadProcessorModuleExport:
|
||||
"""Tests for DownloadProcessor module export."""
|
||||
|
||||
def test_download_processor_importable_from_processors(self):
|
||||
"""Test 4: DownloadProcessor can be imported from kb_pipeline.processors"""
|
||||
from kb_pipeline.processors import DownloadProcessor
|
||||
"""Test 4: DownloadProcessor can be imported from kb_workflow.pipeline.processors"""
|
||||
from kb_workflow.pipeline.processors import DownloadProcessor
|
||||
|
||||
assert DownloadProcessor is not None
|
||||
assert hasattr(DownloadProcessor, 'STEP_NAME')
|
||||
assert DownloadProcessor.STEP_NAME == "download"
|
||||
|
||||
def test_all_processors_exported(self):
|
||||
"""All processors should be exported from kb_pipeline.processors"""
|
||||
from kb_pipeline.processors import BaseProcessor, ParseProcessor, DownloadProcessor, ASRProcessor
|
||||
"""All processors should be exported from kb_workflow.pipeline.processors"""
|
||||
from kb_workflow.pipeline.processors import BaseProcessor, ParseProcessor, DownloadProcessor, ASRProcessor
|
||||
|
||||
assert BaseProcessor is not None
|
||||
assert ParseProcessor is not None
|
||||
|
||||
@@ -5,7 +5,7 @@ Tests that ParseProcessor:
|
||||
- Parses Zhihu answer URLs correctly
|
||||
- Returns ParseOutput with success=False for unknown URLs
|
||||
- Records state when state_manager is provided
|
||||
- Can be imported from kb_pipeline.processors
|
||||
- Can be imported from kb_workflow.pipeline.processors
|
||||
"""
|
||||
|
||||
import pytest
|
||||
@@ -18,10 +18,10 @@ class TestParseProcessorBilibili:
|
||||
@pytest.mark.asyncio
|
||||
async def test_parse_bilibili_video_url(self):
|
||||
"""ParseProcessor 正确解析 Bilibili 视频 URL"""
|
||||
from kb_pipeline.processors.parse import ParseProcessor
|
||||
from kb_pipeline.schemas import ParseInput
|
||||
from kb_workflow.pipeline.processors.parse import ParseProcessor
|
||||
from kb_workflow.pipeline.schemas import ParseInput
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
|
||||
processor = ParseProcessor()
|
||||
@@ -38,10 +38,10 @@ class TestParseProcessorBilibili:
|
||||
@pytest.mark.asyncio
|
||||
async def test_parse_bilibili_article_url(self):
|
||||
"""ParseProcessor 正确解析 Bilibili 专栏 URL"""
|
||||
from kb_pipeline.processors.parse import ParseProcessor
|
||||
from kb_pipeline.schemas import ParseInput
|
||||
from kb_workflow.pipeline.processors.parse import ParseProcessor
|
||||
from kb_workflow.pipeline.schemas import ParseInput
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
|
||||
processor = ParseProcessor()
|
||||
@@ -61,10 +61,10 @@ class TestParseProcessorZhihu:
|
||||
@pytest.mark.asyncio
|
||||
async def test_parse_zhihu_answer_url(self):
|
||||
"""ParseProcessor 正确解析 Zhihu 回答 URL"""
|
||||
from kb_pipeline.processors.parse import ParseProcessor
|
||||
from kb_pipeline.schemas import ParseInput
|
||||
from kb_workflow.pipeline.processors.parse import ParseProcessor
|
||||
from kb_workflow.pipeline.schemas import ParseInput
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
|
||||
processor = ParseProcessor()
|
||||
@@ -80,10 +80,10 @@ class TestParseProcessorZhihu:
|
||||
@pytest.mark.asyncio
|
||||
async def test_parse_zhihu_zhuanlan_url(self):
|
||||
"""ParseProcessor 正确解析 Zhihu 专栏 URL"""
|
||||
from kb_pipeline.processors.parse import ParseProcessor
|
||||
from kb_pipeline.schemas import ParseInput
|
||||
from kb_workflow.pipeline.processors.parse import ParseProcessor
|
||||
from kb_workflow.pipeline.schemas import ParseInput
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
|
||||
processor = ParseProcessor()
|
||||
@@ -103,10 +103,10 @@ class TestParseProcessorUnknown:
|
||||
@pytest.mark.asyncio
|
||||
async def test_parse_unknown_url_returns_failure(self):
|
||||
"""ParseProcessor 对未知 URL 返回 success=False"""
|
||||
from kb_pipeline.processors.parse import ParseProcessor
|
||||
from kb_pipeline.schemas import ParseInput
|
||||
from kb_workflow.pipeline.processors.parse import ParseProcessor
|
||||
from kb_workflow.pipeline.schemas import ParseInput
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
|
||||
processor = ParseProcessor()
|
||||
@@ -124,14 +124,14 @@ class TestParseProcessorStateRecording:
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_recording_on_success(self):
|
||||
"""ParseProcessor 成功时记录状态"""
|
||||
from kb_pipeline.processors.parse import ParseProcessor
|
||||
from kb_pipeline.schemas import ParseInput, WorkflowState
|
||||
from kb_workflow.pipeline.processors.parse import ParseProcessor
|
||||
from kb_workflow.pipeline.schemas import ParseInput, WorkflowState
|
||||
|
||||
mock_state_manager = MagicMock()
|
||||
mock_state = MagicMock(spec=WorkflowState)
|
||||
mock_state_manager.load.return_value = mock_state
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
|
||||
processor = ParseProcessor(state_manager=mock_state_manager)
|
||||
@@ -147,10 +147,10 @@ class TestParseProcessorStateRecording:
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_state_recording_without_manager(self):
|
||||
"""无 state_manager 时不记录状态"""
|
||||
from kb_pipeline.processors.parse import ParseProcessor
|
||||
from kb_pipeline.schemas import ParseInput
|
||||
from kb_workflow.pipeline.processors.parse import ParseProcessor
|
||||
from kb_workflow.pipeline.schemas import ParseInput
|
||||
|
||||
with patch("kb_pipeline.processors.base.get_settings") as mock_settings:
|
||||
with patch("kb_workflow.pipeline.processors.base.get_settings") as mock_settings:
|
||||
mock_settings.return_value = MagicMock()
|
||||
|
||||
processor = ParseProcessor(state_manager=None)
|
||||
@@ -167,18 +167,18 @@ class TestParseProcessorModuleExport:
|
||||
|
||||
def test_parse_processor_importable_from_processors(self):
|
||||
"""ParseProcessor 可以从 kb_pipeline.processors 导入"""
|
||||
from kb_pipeline.processors import ParseProcessor
|
||||
from kb_workflow.pipeline.processors import ParseProcessor
|
||||
|
||||
assert ParseProcessor is not None
|
||||
|
||||
def test_base_processor_importable_from_processors(self):
|
||||
"""BaseProcessor 可以从 kb_pipeline.processors 导入"""
|
||||
from kb_pipeline.processors import BaseProcessor
|
||||
from kb_workflow.pipeline.processors import BaseProcessor
|
||||
|
||||
assert BaseProcessor is not None
|
||||
|
||||
def test_asr_processor_still_importable(self):
|
||||
"""ASRProcessor 仍然可以从 kb_pipeline.processors 导入"""
|
||||
from kb_pipeline.processors import ASRProcessor
|
||||
from kb_workflow.pipeline.processors import ASRProcessor
|
||||
|
||||
assert ASRProcessor is not None
|
||||
|
||||
Reference in New Issue
Block a user