test(04-02): add failing tests for DownloadProcessor metadata fetching
TDD RED phase: - Test DownloadProcessor fetches Bilibili video metadata correctly - Test DownloadProcessor fetches Zhihu answer metadata correctly - Test DownloadProcessor records state on success - Test DownloadProcessor records state on failure Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -125,3 +125,189 @@ class TestDownloadOutputSchema:
|
|||||||
)
|
)
|
||||||
assert output.success is False
|
assert output.success is False
|
||||||
assert output.error_message == "Network error"
|
assert output.error_message == "Network error"
|
||||||
|
|
||||||
|
|
||||||
|
class TestDownloadProcessorMetadata:
|
||||||
|
"""Tests for DownloadProcessor metadata fetching."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_bilibili_adapter(self):
|
||||||
|
"""Create a mock BilibiliAdapter."""
|
||||||
|
adapter = MagicMock()
|
||||||
|
adapter.fetch_metadata = AsyncMock(return_value={
|
||||||
|
"metadata": {
|
||||||
|
"title": "Test Video Title",
|
||||||
|
"mid": 12345,
|
||||||
|
"bvid": "BV1xx",
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"view": 1000,
|
||||||
|
"like": 100,
|
||||||
|
},
|
||||||
|
"description": "Test description"
|
||||||
|
})
|
||||||
|
adapter.get_output_filename = MagicMock(
|
||||||
|
return_value="bilibili_BV1xx_metadata.json"
|
||||||
|
)
|
||||||
|
return adapter
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_zhihu_adapter(self):
|
||||||
|
"""Create a mock ZhihuAdapter."""
|
||||||
|
adapter = MagicMock()
|
||||||
|
adapter.fetch_metadata = AsyncMock(return_value={
|
||||||
|
"title": "Test Answer Title",
|
||||||
|
"author": "Test Author",
|
||||||
|
"url": "https://www.zhihu.com/question/123/answer/456",
|
||||||
|
"content_markdown": "Test content"
|
||||||
|
})
|
||||||
|
adapter.get_output_filename = MagicMock(
|
||||||
|
return_value="zhihu_456_metadata.json"
|
||||||
|
)
|
||||||
|
return adapter
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_state_manager(self):
|
||||||
|
"""Create a mock WorkflowStateManager."""
|
||||||
|
manager = MagicMock()
|
||||||
|
manager.load = MagicMock(return_value=None)
|
||||||
|
manager.save = MagicMock()
|
||||||
|
return manager
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_metadata_fetches_bilibili_video_metadata_correctly(
|
||||||
|
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
|
||||||
|
|
||||||
|
input_data = DownloadInput(
|
||||||
|
content_id="BV1xx",
|
||||||
|
platform="bilibili",
|
||||||
|
content_dir=str(tmp_path),
|
||||||
|
content_type="video"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_adapter
|
||||||
|
):
|
||||||
|
processor = DownloadProcessor(state_manager=mock_state_manager)
|
||||||
|
result = await processor.process(input_data)
|
||||||
|
|
||||||
|
assert result.success is True
|
||||||
|
assert result.title == "Test Video Title"
|
||||||
|
assert "bilibili_BV1xx_metadata.json" in result.metadata_file
|
||||||
|
|
||||||
|
# Verify adapter was called correctly
|
||||||
|
mock_bilibili_adapter.fetch_metadata.assert_called_once_with(
|
||||||
|
"BV1xx", "video"
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_metadata_fetches_zhihu_answer_metadata_correctly(
|
||||||
|
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
|
||||||
|
|
||||||
|
input_data = DownloadInput(
|
||||||
|
content_id="456",
|
||||||
|
platform="zhihu",
|
||||||
|
content_dir=str(tmp_path),
|
||||||
|
content_type="answer"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
DownloadProcessor, '_get_adapter', return_value=mock_zhihu_adapter
|
||||||
|
):
|
||||||
|
processor = DownloadProcessor(state_manager=mock_state_manager)
|
||||||
|
result = await processor.process(input_data)
|
||||||
|
|
||||||
|
assert result.success is True
|
||||||
|
assert result.title == "Test Answer Title"
|
||||||
|
|
||||||
|
# Verify adapter was called correctly
|
||||||
|
mock_zhihu_adapter.fetch_metadata.assert_called_once_with(
|
||||||
|
"456", "answer"
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_metadata_records_state_on_success(
|
||||||
|
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
|
||||||
|
|
||||||
|
# Create a mock state that will be updated
|
||||||
|
mock_state = MagicMock(spec=WorkflowState)
|
||||||
|
mock_state.is_step_completed = MagicMock(return_value=False)
|
||||||
|
mock_state_manager.load.return_value = mock_state
|
||||||
|
|
||||||
|
input_data = DownloadInput(
|
||||||
|
content_id="BV1xx",
|
||||||
|
platform="bilibili",
|
||||||
|
content_dir=str(tmp_path),
|
||||||
|
content_type="video"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_adapter
|
||||||
|
):
|
||||||
|
processor = DownloadProcessor(state_manager=mock_state_manager)
|
||||||
|
result = await processor.process(input_data)
|
||||||
|
|
||||||
|
assert result.success is True
|
||||||
|
|
||||||
|
# Verify state was recorded
|
||||||
|
mock_state.mark_step_completed.assert_called_once()
|
||||||
|
call_args = mock_state.mark_step_completed.call_args
|
||||||
|
assert call_args[0][0] == "download" # step_name
|
||||||
|
mock_state_manager.save.assert_called_once_with(mock_state)
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_metadata_records_state_on_failure(
|
||||||
|
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
|
||||||
|
|
||||||
|
# Make adapter raise an error
|
||||||
|
mock_bilibili_adapter.fetch_metadata = AsyncMock(
|
||||||
|
side_effect=NetworkError(
|
||||||
|
step_name="download",
|
||||||
|
input_summary="BV1xx",
|
||||||
|
cause=Exception("Network timeout")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a mock state that will be updated
|
||||||
|
mock_state = MagicMock(spec=WorkflowState)
|
||||||
|
mock_state.is_step_completed = MagicMock(return_value=False)
|
||||||
|
mock_state_manager.load.return_value = mock_state
|
||||||
|
|
||||||
|
input_data = DownloadInput(
|
||||||
|
content_id="BV1xx",
|
||||||
|
platform="bilibili",
|
||||||
|
content_dir=str(tmp_path),
|
||||||
|
content_type="video"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
DownloadProcessor, '_get_adapter', return_value=mock_bilibili_adapter
|
||||||
|
):
|
||||||
|
processor = DownloadProcessor(state_manager=mock_state_manager)
|
||||||
|
result = await processor.process(input_data)
|
||||||
|
|
||||||
|
assert result.success is False
|
||||||
|
assert "Network timeout" in result.error_message
|
||||||
|
|
||||||
|
# Verify failure was recorded
|
||||||
|
mock_state.mark_step_failed.assert_called_once()
|
||||||
|
call_args = mock_state.mark_step_failed.call_args
|
||||||
|
assert call_args[0][0] == "download" # step_name
|
||||||
|
mock_state_manager.save.assert_called_once_with(mock_state)
|
||||||
|
|||||||
Reference in New Issue
Block a user