This repository has been archived on 2026-08-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
kb-skills/tests/test_bilibili_pipeline.py
starmen1265 98709476ab test(12-02): add Bilibili video pipeline end-to-end tests
- 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>
2026-03-19 23:45:49 +08:00

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