- 实现查询API (query.py): 支持star_id/unique_id/nickname三种查询方式 - 实现计算模块 (calculator.py): CPM/自然搜索UV/搜索成本计算 - 实现品牌API集成 (brand_api.py): 批量并发调用,10并发限制 - 实现导出服务 (export_service.py): Excel/CSV导出 - 前端组件: QueryForm/ResultTable/ExportButton - 主页面集成: 支持6种页面状态 - 测试: 44个测试全部通过,覆盖率88% Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
import pytest
|
|
from io import BytesIO
|
|
from openpyxl import load_workbook
|
|
|
|
from app.services.export_service import generate_excel, generate_csv, COLUMN_HEADERS
|
|
|
|
|
|
class TestExportService:
|
|
"""Tests for Export Service."""
|
|
|
|
@pytest.fixture
|
|
def sample_export_data(self):
|
|
"""Sample data for export testing."""
|
|
return [
|
|
{
|
|
"item_id": "item_001",
|
|
"title": "测试视频1",
|
|
"viral_type": "爆款",
|
|
"video_url": "https://example.com/1",
|
|
"star_id": "star_001",
|
|
"star_unique_id": "unique_001",
|
|
"star_nickname": "测试达人1",
|
|
"publish_time": "2026-01-28T10:00:00",
|
|
"natural_play_cnt": 100000,
|
|
"heated_play_cnt": 50000,
|
|
"total_play_cnt": 150000,
|
|
"total_interact": 5000,
|
|
"like_cnt": 3000,
|
|
"share_cnt": 1000,
|
|
"comment_cnt": 1000,
|
|
"new_a3_rate": 0.05,
|
|
"after_view_search_uv": 500,
|
|
"return_search_cnt": 200,
|
|
"industry_id": "ind_001",
|
|
"industry_name": "美妆",
|
|
"brand_id": "brand_001",
|
|
"brand_name": "测试品牌",
|
|
"estimated_video_cost": 10000.0,
|
|
"estimated_natural_cpm": 100.0,
|
|
"estimated_natural_search_uv": 333.33,
|
|
"estimated_natural_search_cost": 30.0,
|
|
}
|
|
]
|
|
|
|
def test_generate_excel_success(self, sample_export_data):
|
|
"""Test Excel generation."""
|
|
content = generate_excel(sample_export_data)
|
|
|
|
assert content is not None
|
|
assert len(content) > 0
|
|
|
|
# 验证可以被 openpyxl 读取
|
|
wb = load_workbook(BytesIO(content))
|
|
ws = wb.active
|
|
|
|
# 验证表头
|
|
assert ws.cell(row=1, column=1).value == "视频ID"
|
|
assert ws.cell(row=1, column=2).value == "视频标题"
|
|
|
|
# 验证数据行
|
|
assert ws.cell(row=2, column=1).value == "item_001"
|
|
assert ws.cell(row=2, column=2).value == "测试视频1"
|
|
|
|
def test_generate_excel_empty_data(self):
|
|
"""Test Excel generation with empty data."""
|
|
content = generate_excel([])
|
|
|
|
assert content is not None
|
|
wb = load_workbook(BytesIO(content))
|
|
ws = wb.active
|
|
|
|
# 应该只有表头
|
|
assert ws.max_row == 1
|
|
|
|
def test_generate_csv_success(self, sample_export_data):
|
|
"""Test CSV generation."""
|
|
content = generate_csv(sample_export_data)
|
|
|
|
assert content is not None
|
|
assert len(content) > 0
|
|
|
|
# 验证 CSV 内容
|
|
lines = content.decode("utf-8-sig").split("\n")
|
|
assert len(lines) >= 2 # 表头 + 至少一行数据
|
|
|
|
# 验证表头
|
|
assert "视频ID" in lines[0]
|
|
assert "视频标题" in lines[0]
|
|
|
|
def test_generate_csv_empty_data(self):
|
|
"""Test CSV generation with empty data."""
|
|
content = generate_csv([])
|
|
|
|
assert content is not None
|
|
lines = content.decode("utf-8-sig").split("\n")
|
|
|
|
# 应该只有表头
|
|
assert len(lines) == 2 # 表头 + 空行
|
|
|
|
def test_generate_csv_comma_escape(self):
|
|
"""Test CSV properly escapes commas."""
|
|
data = [
|
|
{
|
|
"item_id": "item_001",
|
|
"title": "标题,包含,逗号",
|
|
"viral_type": None,
|
|
"video_url": None,
|
|
"star_id": "star_001",
|
|
"star_unique_id": "unique_001",
|
|
"star_nickname": "测试达人",
|
|
"publish_time": None,
|
|
"natural_play_cnt": 0,
|
|
"heated_play_cnt": 0,
|
|
"total_play_cnt": 0,
|
|
"total_interact": 0,
|
|
"like_cnt": 0,
|
|
"share_cnt": 0,
|
|
"comment_cnt": 0,
|
|
"new_a3_rate": None,
|
|
"after_view_search_uv": 0,
|
|
"return_search_cnt": 0,
|
|
"industry_id": None,
|
|
"industry_name": None,
|
|
"brand_id": None,
|
|
"brand_name": None,
|
|
"estimated_video_cost": 0,
|
|
"estimated_natural_cpm": None,
|
|
"estimated_natural_search_uv": None,
|
|
"estimated_natural_search_cost": None,
|
|
}
|
|
]
|
|
content = generate_csv(data)
|
|
csv_text = content.decode("utf-8-sig")
|
|
|
|
# 包含逗号的字段应该被引号包裹
|
|
assert '"标题,包含,逗号"' in csv_text
|
|
|
|
def test_column_headers_complete(self):
|
|
"""Test that all required columns are defined."""
|
|
expected_columns = [
|
|
"视频ID",
|
|
"视频标题",
|
|
"爆文类型",
|
|
"视频链接",
|
|
"新增A3率",
|
|
"看后搜人数",
|
|
"回搜次数",
|
|
"自然曝光数",
|
|
"加热曝光数",
|
|
"总曝光数",
|
|
"总互动",
|
|
"点赞",
|
|
"转发",
|
|
"评论",
|
|
"合作行业ID",
|
|
"合作行业",
|
|
"合作品牌ID",
|
|
"合作品牌",
|
|
"发布时间",
|
|
"达人昵称",
|
|
"达人unique_id",
|
|
"预估视频价格",
|
|
"预估自然CPM",
|
|
"预估自然看后搜人数",
|
|
"预估自然看后搜人数成本",
|
|
]
|
|
|
|
for col in expected_columns:
|
|
assert col in [h[0] for h in COLUMN_HEADERS], f"Missing column: {col}"
|