98709476ab
- Test Bilibili video uses SRTProcessor (not AnalyzeProcessor) - Test Bilibili article uses AnalyzeProcessor (not SRTProcessor) - Test Zhihu answer uses AnalyzeProcessor - Test Markdown output format verification All 4 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
423 lines
15 KiB
Python
423 lines
15 KiB
Python
"""Tests for Bilibili video pipeline integration
|
|
|
|
Tests for:
|
|
- Bilibili video processing flow (uses SRTProcessor)
|
|
- Bilibili article processing flow (uses AnalyzeProcessor)
|
|
- Zhihu processing flow (uses AnalyzeProcessor)
|
|
- Platform routing logic
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from kb_workflow.pipeline.pipeline import Pipeline
|
|
from kb_workflow.pipeline.schemas import PipelineConfig
|
|
from kb_workflow.pipeline.processors import (
|
|
ParseProcessor, DownloadProcessor, ASRProcessor,
|
|
AnalyzeProcessor, ArchiveProcessor
|
|
)
|
|
from kb_workflow.pipeline.processors.srt import SRTProcessor
|
|
|
|
|
|
# =============================================================================
|
|
# Test Fixtures
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def mock_bilibili_video_url():
|
|
"""B站视频 URL"""
|
|
return "https://www.bilibili.com/video/BV1test123456"
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_bilibili_article_url():
|
|
"""B站文章 URL"""
|
|
return "https://www.bilibili.com/read/cv123456"
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_zhihu_answer_url():
|
|
"""知乎回答 URL"""
|
|
return "https://www.zhihu.com/question/123456/answer/789012"
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_metadata():
|
|
"""示例元数据"""
|
|
return {
|
|
"title": "测试视频标题",
|
|
"author": "测试作者",
|
|
"duration": 300,
|
|
"pubtime": "2024-01-01",
|
|
"bvid": "BV1test123456",
|
|
"platform": "bilibili",
|
|
"url": "https://www.bilibili.com/video/BV1test123456"
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_srt_content():
|
|
"""示例 SRT 内容"""
|
|
return """1
|
|
00:00:00,000 --> 00:00:05,000
|
|
第一句字幕内容
|
|
|
|
2
|
|
00:00:05,000 --> 00:00:10,000
|
|
第二句字幕内容
|
|
|
|
3
|
|
00:00:10,000 --> 00:00:15,000
|
|
第三句字幕内容
|
|
"""
|
|
|
|
|
|
# =============================================================================
|
|
# Test Cases
|
|
# =============================================================================
|
|
|
|
class TestBilibiliVideoPipeline:
|
|
"""B站视频处理流程测试"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_bilibili_video_uses_srt_processor(self, tmp_path, mock_bilibili_video_url, sample_metadata, sample_srt_content):
|
|
"""验证 B站视频使用 SRTProcessor 而非 AnalyzeProcessor"""
|
|
# Setup: 创建临时目录和文件
|
|
content_dir = tmp_path / "bilibili_BV1test123456"
|
|
content_dir.mkdir(parents=True)
|
|
|
|
# 创建元数据文件
|
|
metadata_file = content_dir / "metadata.json"
|
|
metadata_file.write_text(json.dumps(sample_metadata, ensure_ascii=False), encoding='utf-8')
|
|
|
|
# 创建 SRT 文件
|
|
srt_file = content_dir / "bilibili_BV1test123456_subtitle.srt"
|
|
srt_file.write_text(sample_srt_content, encoding='utf-8')
|
|
|
|
# Mock processors
|
|
with patch.object(ParseProcessor, 'process', new_callable=AsyncMock) as mock_parse, \
|
|
patch.object(DownloadProcessor, 'process', new_callable=AsyncMock) as mock_download, \
|
|
patch.object(ASRProcessor, 'process', new_callable=AsyncMock) as mock_asr, \
|
|
patch.object(SRTProcessor, 'process', new_callable=AsyncMock) as mock_srt, \
|
|
patch.object(AnalyzeProcessor, 'process', new_callable=AsyncMock) as mock_analyze, \
|
|
patch.object(ArchiveProcessor, 'process', new_callable=AsyncMock) as mock_archive:
|
|
|
|
# 配置 mock 返回值
|
|
from kb_workflow.pipeline.schemas import ParseOutput, DownloadOutput, ASROutput, SRTOutput, ArchiveOutput
|
|
|
|
mock_parse.return_value = ParseOutput(
|
|
success=True,
|
|
platform="bilibili",
|
|
content_type="video",
|
|
content_id="BV1test123456",
|
|
resolved_url=mock_bilibili_video_url,
|
|
original_url=mock_bilibili_video_url
|
|
)
|
|
|
|
mock_download.return_value = DownloadOutput(
|
|
success=True,
|
|
metadata_file=str(metadata_file),
|
|
content_file=str(content_dir / "video.mp4"),
|
|
title=sample_metadata["title"],
|
|
author=sample_metadata["author"],
|
|
duration=sample_metadata["duration"]
|
|
)
|
|
|
|
mock_asr.return_value = ASROutput(
|
|
success=True,
|
|
tool="subtitle",
|
|
has_subtitle=True,
|
|
output_files={"srt": str(srt_file), "json": str(content_dir / "subtitle.json")},
|
|
text="第一句字幕内容 第二句字幕内容 第三句字幕内容",
|
|
duration_seconds=15.0
|
|
)
|
|
|
|
mock_srt.return_value = SRTOutput(
|
|
success=True,
|
|
output_file=str(content_dir / "analysis.md"),
|
|
paragraphs=["第一句字幕内容", "第二句字幕内容", "第三句字幕内容"],
|
|
tags=[],
|
|
corrected=False
|
|
)
|
|
|
|
mock_archive.return_value = ArchiveOutput(
|
|
success=True,
|
|
archive_file=str(tmp_path / "archive" / "test.md"),
|
|
skipped=False,
|
|
platform="bilibili"
|
|
)
|
|
|
|
# 创建 Pipeline
|
|
processors = [
|
|
ParseProcessor(),
|
|
DownloadProcessor(),
|
|
ASRProcessor(),
|
|
SRTProcessor(),
|
|
AnalyzeProcessor(),
|
|
ArchiveProcessor(),
|
|
]
|
|
|
|
config = PipelineConfig(
|
|
steps=["parse", "download", "asr", "srt", "analyze", "archive"],
|
|
output_dir=tmp_path,
|
|
skip_completed=False,
|
|
)
|
|
|
|
pipeline = Pipeline(
|
|
processors=processors,
|
|
config=config,
|
|
output_dir=tmp_path,
|
|
)
|
|
|
|
# 执行
|
|
result = await pipeline.run(mock_bilibili_video_url)
|
|
|
|
# 验证
|
|
assert result.success, f"Pipeline failed: {result.error_message}"
|
|
|
|
# SRTProcessor 应该被调用
|
|
mock_srt.assert_called_once()
|
|
|
|
# AnalyzeProcessor 不应该被调用(B站视频跳过)
|
|
mock_analyze.assert_not_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_bilibili_article_uses_analyze_processor(self, tmp_path, mock_bilibili_article_url, sample_metadata):
|
|
"""验证 B站文章使用 AnalyzeProcessor"""
|
|
# Setup
|
|
content_dir = tmp_path / "bilibili_cv123456"
|
|
content_dir.mkdir(parents=True)
|
|
|
|
metadata_file = content_dir / "metadata.json"
|
|
sample_metadata["url"] = mock_bilibili_article_url
|
|
metadata_file.write_text(json.dumps(sample_metadata, ensure_ascii=False), encoding='utf-8')
|
|
|
|
content_file = content_dir / "article.txt"
|
|
content_file.write_text("文章内容测试", encoding='utf-8')
|
|
|
|
with patch.object(ParseProcessor, 'process', new_callable=AsyncMock) as mock_parse, \
|
|
patch.object(DownloadProcessor, 'process', new_callable=AsyncMock) as mock_download, \
|
|
patch.object(ASRProcessor, 'process', new_callable=AsyncMock) as mock_asr, \
|
|
patch.object(SRTProcessor, 'process', new_callable=AsyncMock) as mock_srt, \
|
|
patch.object(AnalyzeProcessor, 'process', new_callable=AsyncMock) as mock_analyze, \
|
|
patch.object(ArchiveProcessor, 'process', new_callable=AsyncMock) as mock_archive:
|
|
|
|
from kb_workflow.pipeline.schemas import ParseOutput, DownloadOutput, AnalyzeOutput, ArchiveOutput
|
|
|
|
mock_parse.return_value = ParseOutput(
|
|
success=True,
|
|
platform="bilibili",
|
|
content_type="article",
|
|
content_id="cv123456",
|
|
resolved_url=mock_bilibili_article_url,
|
|
original_url=mock_bilibili_article_url
|
|
)
|
|
|
|
mock_download.return_value = DownloadOutput(
|
|
success=True,
|
|
metadata_file=str(metadata_file),
|
|
content_file=str(content_file),
|
|
title=sample_metadata["title"],
|
|
author=sample_metadata["author"]
|
|
)
|
|
|
|
mock_analyze.return_value = AnalyzeOutput(
|
|
success=True,
|
|
analysis_file=str(content_dir / "analysis.md"),
|
|
summary="文章摘要",
|
|
main_topics=["主题1"],
|
|
tags=["标签1"]
|
|
)
|
|
|
|
mock_archive.return_value = ArchiveOutput(
|
|
success=True,
|
|
archive_file=str(tmp_path / "archive" / "test.md"),
|
|
skipped=False,
|
|
platform="bilibili"
|
|
)
|
|
|
|
processors = [
|
|
ParseProcessor(),
|
|
DownloadProcessor(),
|
|
ASRProcessor(),
|
|
SRTProcessor(),
|
|
AnalyzeProcessor(),
|
|
ArchiveProcessor(),
|
|
]
|
|
|
|
config = PipelineConfig(
|
|
steps=["parse", "download", "asr", "srt", "analyze", "archive"],
|
|
output_dir=tmp_path,
|
|
skip_completed=False,
|
|
)
|
|
|
|
pipeline = Pipeline(
|
|
processors=processors,
|
|
config=config,
|
|
output_dir=tmp_path,
|
|
)
|
|
|
|
result = await pipeline.run(mock_bilibili_article_url)
|
|
|
|
assert result.success, f"Pipeline failed: {result.error_message}"
|
|
|
|
# ASR 不应该被调用(文章类型跳过)
|
|
mock_asr.assert_not_called()
|
|
|
|
# SRTProcessor 不应该被调用(非视频跳过)
|
|
mock_srt.assert_not_called()
|
|
|
|
# AnalyzeProcessor 应该被调用
|
|
mock_analyze.assert_called_once()
|
|
|
|
|
|
class TestZhihuPipeline:
|
|
"""知乎处理流程测试"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_zhihu_answer_uses_analyze_processor(self, tmp_path, mock_zhihu_answer_url):
|
|
"""验证知乎回答使用 AnalyzeProcessor"""
|
|
content_dir = tmp_path / "zhihu_789012"
|
|
content_dir.mkdir(parents=True)
|
|
|
|
metadata = {
|
|
"title": "知乎回答测试",
|
|
"author": "知乎用户",
|
|
"platform": "zhihu",
|
|
"url": mock_zhihu_answer_url
|
|
}
|
|
metadata_file = content_dir / "metadata.json"
|
|
metadata_file.write_text(json.dumps(metadata, ensure_ascii=False), encoding='utf-8')
|
|
|
|
content_file = content_dir / "content.txt"
|
|
content_file.write_text("知乎回答内容测试", encoding='utf-8')
|
|
|
|
with patch.object(ParseProcessor, 'process', new_callable=AsyncMock) as mock_parse, \
|
|
patch.object(DownloadProcessor, 'process', new_callable=AsyncMock) as mock_download, \
|
|
patch.object(ASRProcessor, 'process', new_callable=AsyncMock) as mock_asr, \
|
|
patch.object(SRTProcessor, 'process', new_callable=AsyncMock) as mock_srt, \
|
|
patch.object(AnalyzeProcessor, 'process', new_callable=AsyncMock) as mock_analyze, \
|
|
patch.object(ArchiveProcessor, 'process', new_callable=AsyncMock) as mock_archive:
|
|
|
|
from kb_workflow.pipeline.schemas import ParseOutput, DownloadOutput, AnalyzeOutput, ArchiveOutput
|
|
|
|
mock_parse.return_value = ParseOutput(
|
|
success=True,
|
|
platform="zhihu",
|
|
content_type="answer",
|
|
content_id="789012",
|
|
resolved_url=mock_zhihu_answer_url,
|
|
original_url=mock_zhihu_answer_url
|
|
)
|
|
|
|
mock_download.return_value = DownloadOutput(
|
|
success=True,
|
|
metadata_file=str(metadata_file),
|
|
content_file=str(content_file),
|
|
title=metadata["title"],
|
|
author=metadata["author"]
|
|
)
|
|
|
|
mock_analyze.return_value = AnalyzeOutput(
|
|
success=True,
|
|
analysis_file=str(content_dir / "analysis.md"),
|
|
summary="回答摘要",
|
|
main_topics=["主题1"],
|
|
tags=["标签1"]
|
|
)
|
|
|
|
mock_archive.return_value = ArchiveOutput(
|
|
success=True,
|
|
archive_file=str(tmp_path / "archive" / "test.md"),
|
|
skipped=False,
|
|
platform="zhihu"
|
|
)
|
|
|
|
processors = [
|
|
ParseProcessor(),
|
|
DownloadProcessor(),
|
|
ASRProcessor(),
|
|
SRTProcessor(),
|
|
AnalyzeProcessor(),
|
|
ArchiveProcessor(),
|
|
]
|
|
|
|
config = PipelineConfig(
|
|
steps=["parse", "download", "asr", "srt", "analyze", "archive"],
|
|
output_dir=tmp_path,
|
|
skip_completed=False,
|
|
)
|
|
|
|
pipeline = Pipeline(
|
|
processors=processors,
|
|
config=config,
|
|
output_dir=tmp_path,
|
|
)
|
|
|
|
result = await pipeline.run(mock_zhihu_answer_url)
|
|
|
|
assert result.success, f"Pipeline failed: {result.error_message}"
|
|
|
|
# ASR 不应该被调用(文本类型跳过)
|
|
mock_asr.assert_not_called()
|
|
|
|
# SRTProcessor 不应该被调用(非B站视频跳过)
|
|
mock_srt.assert_not_called()
|
|
|
|
# AnalyzeProcessor 应该被调用
|
|
mock_analyze.assert_called_once()
|
|
|
|
|
|
class TestMarkdownOutput:
|
|
"""Markdown 输出格式测试"""
|
|
|
|
def test_srt_output_markdown_format(self, tmp_path, sample_metadata, sample_srt_content):
|
|
"""验证生成的 Markdown 文件格式正确"""
|
|
# 创建 SRT 文件
|
|
srt_file = tmp_path / "test.srt"
|
|
srt_file.write_text(sample_srt_content, encoding='utf-8')
|
|
|
|
# 创建元数据文件
|
|
metadata_file = tmp_path / "metadata.json"
|
|
metadata_file.write_text(json.dumps(sample_metadata, ensure_ascii=False), encoding='utf-8')
|
|
|
|
# 创建输出文件路径
|
|
output_file = tmp_path / "analysis.md"
|
|
|
|
# 使用 SRTProcessor 处理
|
|
from kb_workflow.pipeline.processors.srt import SRTProcessor
|
|
from kb_workflow.pipeline.schemas import SRTInput
|
|
import asyncio
|
|
|
|
processor = SRTProcessor(enable_correction=False)
|
|
step_input = SRTInput(
|
|
content_id="BV1test123456",
|
|
platform="bilibili",
|
|
srt_file=str(srt_file),
|
|
metadata_file=str(metadata_file),
|
|
output_file=str(output_file),
|
|
content_type="video"
|
|
)
|
|
|
|
result = asyncio.run(processor.process(step_input))
|
|
|
|
assert result.success
|
|
assert output_file.exists()
|
|
|
|
# 验证文件内容
|
|
content = output_file.read_text(encoding='utf-8')
|
|
|
|
# 检查 frontmatter
|
|
assert "---" in content
|
|
assert "title:" in content
|
|
assert "date:" in content
|
|
assert "author:" in content
|
|
assert "platform: bilibili" in content
|
|
|
|
# 检查内容结构
|
|
assert "## 摘要" in content or "## 内容" in content
|