feat(frontend): 优化前端性能与修复文字选择问题

- VideoAnalysis 组件性能优化:使用 memo/useMemo/useCallback,添加详情缓存和虚拟滚动
- 修复 Ant Design Modal/Descriptions/Table 内文字无法复制的问题
- 新增 AntdProvider 组件,解决 layout.tsx 不能加 'use client' 的问题
- 添加云图 API 参数测试,更新 CLAUDE.md 文档

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
zfc
2026-01-29 12:04:47 +08:00
co-authored by Claude Opus 4.5
parent 137e6dd23c
commit 70ba2f1868
7 changed files with 420 additions and 31 deletions
+316
View File
@@ -0,0 +1,316 @@
"""
Tests for Yuntu API Parameter Format (T-027)
根据 doc/temp 的正确格式:
1. 日期格式: YYYYMMDD (如 20251014),不是 YYYY-MM-DD
2. Cookie 头: 直接使用 auth_token 完整值
3. industry_id: 字符串格式 ["20"],不是整数
4. Cookie 获取: 随机选取任意一组 aadvid/auth_token
"""
import pytest
from datetime import datetime, date
from unittest.mock import AsyncMock, patch, MagicMock
import httpx
class TestYuntuAPIParameterFormat:
"""验证 API 调用参数格式正确性 (T-027)"""
async def test_date_format_yyyymmdd(self):
"""日期格式必须为 YYYYMMDD,不是 YYYY-MM-DD"""
from app.services.yuntu_api import call_yuntu_api
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"status": 0, "data": {"a3_increase_cnt": "100"}}
mock_client = AsyncMock()
mock_client.post.return_value = mock_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
with patch("httpx.AsyncClient", return_value=mock_client):
await call_yuntu_api(
item_id="video_001",
publish_time=datetime(2025, 10, 14),
industry_id="12",
aadvid="1648829117232140",
auth_token="sessionid=f9dfd57df6935afd1255bdc8f0dd0e4b",
)
call_args = mock_client.post.call_args
json_data = call_args.kwargs["json"]
# 关键验证:日期格式是 YYYYMMDD
assert json_data["start_date"] == "20251014", f"Expected '20251014', got '{json_data['start_date']}'"
assert json_data["end_date"] == "20251113", f"Expected '20251113', got '{json_data['end_date']}'"
async def test_cookie_header_uses_auth_token_directly(self):
"""Cookie 头应直接使用 auth_token 完整值"""
from app.services.yuntu_api import call_yuntu_api
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"status": 0, "data": {}}
mock_client = AsyncMock()
mock_client.post.return_value = mock_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
auth_token = "sessionid=f9dfd57df6935afd1255bdc8f0dd0e4b"
with patch("httpx.AsyncClient", return_value=mock_client):
await call_yuntu_api(
item_id="video_001",
publish_time=datetime(2025, 10, 14),
industry_id="12",
aadvid="1648829117232140",
auth_token=auth_token,
)
call_args = mock_client.post.call_args
headers = call_args.kwargs["headers"]
# 关键验证:Cookie 直接使用 auth_token 完整值
assert headers["Cookie"] == auth_token, f"Expected Cookie='{auth_token}', got '{headers['Cookie']}'"
async def test_industry_id_as_string_array(self):
"""industry_id_list 应为字符串数组 ["12"],不是整数"""
from app.services.yuntu_api import call_yuntu_api
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"status": 0, "data": {}}
mock_client = AsyncMock()
mock_client.post.return_value = mock_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
with patch("httpx.AsyncClient", return_value=mock_client):
await call_yuntu_api(
item_id="video_001",
publish_time=datetime(2025, 10, 14),
industry_id="12", # 字符串
aadvid="1648829117232140",
auth_token="sessionid=xxx",
)
call_args = mock_client.post.call_args
json_data = call_args.kwargs["json"]
# 关键验证:industry_id_list 是字符串数组
assert json_data["industry_id_list"] == ["12"], f"Expected ['12'], got {json_data['industry_id_list']}"
async def test_url_contains_aadvid(self):
"""URL 必须包含 aadvid 参数"""
from app.services.yuntu_api import call_yuntu_api
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"status": 0, "data": {}}
mock_client = AsyncMock()
mock_client.post.return_value = mock_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
aadvid = "1648829117232140"
with patch("httpx.AsyncClient", return_value=mock_client):
await call_yuntu_api(
item_id="video_001",
publish_time=datetime(2025, 10, 14),
industry_id="12",
aadvid=aadvid,
auth_token="sessionid=xxx",
)
call_args = mock_client.post.call_args
url = call_args.args[0]
# 关键验证:URL 包含 aadvid
assert f"aadvid={aadvid}" in url, f"URL should contain 'aadvid={aadvid}', got '{url}'"
async def test_fixed_parameters(self):
"""验证固定参数值正确"""
from app.services.yuntu_api import call_yuntu_api
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"status": 0, "data": {}}
mock_client = AsyncMock()
mock_client.post.return_value = mock_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
with patch("httpx.AsyncClient", return_value=mock_client):
await call_yuntu_api(
item_id="video_001",
publish_time=datetime(2025, 10, 14),
industry_id="12",
aadvid="1648829117232140",
auth_token="sessionid=xxx",
)
call_args = mock_client.post.call_args
json_data = call_args.kwargs["json"]
# 验证固定参数
assert json_data["is_my_video"] == "0"
assert json_data["object_type"] == 2
assert json_data["assist_type"] == 3
assert json_data["assist_video_type"] == 3
assert json_data["trigger_point_id_list"] == ["610000", "610300", "610301"]
async def test_end_date_is_start_plus_30_days(self):
"""end_date 应为 start_date + 30 天"""
from app.services.yuntu_api import call_yuntu_api
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"status": 0, "data": {}}
mock_client = AsyncMock()
mock_client.post.return_value = mock_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
# 测试日期:2025-01-15+30天 = 2025-02-14
with patch("httpx.AsyncClient", return_value=mock_client):
await call_yuntu_api(
item_id="video_001",
publish_time=datetime(2025, 1, 15),
industry_id="12",
aadvid="123",
auth_token="sessionid=xxx",
)
call_args = mock_client.post.call_args
json_data = call_args.kwargs["json"]
assert json_data["start_date"] == "20250115"
assert json_data["end_date"] == "20250214"
async def test_parse_a3_metrics_as_strings(self):
"""API 返回的 A3 指标是字符串类型,需正确解析"""
from app.services.yuntu_api import parse_analysis_response
# 实际 API 响应示例(A3 是字符串)
response = {
"status": 0,
"msg": "ok",
"data": {
"object_id": "7560751618711457062",
"cost": 785000,
"ad_a3_increase_cnt": "36902",
"natural_a3_increase_cnt": "1652169",
"a3_increase_cnt": "1689071",
}
}
result = parse_analysis_response(response)
# 解析后应转为整数
assert result["a3_increase_cnt"] == 1689071
assert result["ad_a3_increase_cnt"] == 36902
assert result["natural_a3_increase_cnt"] == 1652169
assert result["cost"] == 785000
class TestSessionPoolRandomSelection:
"""验证 Cookie 池随机选取逻辑 (T-027)"""
async def test_get_random_config(self):
"""应随机选取任意一组配置,不按 brand_id 匹配"""
from app.services.session_pool import SessionPool, CookieConfig
pool = SessionPool()
# 模拟刷新后的数据
pool._configs = [
CookieConfig(
brand_id="533661",
aadvid="1648829117232140",
auth_token="sessionid=aaa",
industry_id=20,
brand_name="Test1",
),
CookieConfig(
brand_id="10186612",
aadvid="1234567890",
auth_token="sessionid=bbb",
industry_id=30,
brand_name="Test2",
),
]
# 调用随机获取
config = pool.get_random_config()
# 应返回一个有效配置
assert config is not None
assert "aadvid" in config
assert "auth_token" in config
class TestIntegrationWithRealFormat:
"""集成测试:验证完整调用流程"""
async def test_full_api_call_format(self):
"""完整验证 API 调用格式"""
from app.services.yuntu_api import call_yuntu_api
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"status": 0,
"msg": "ok",
"data": {
"object_id": "7560751618711457062",
"cost": 785000,
"ad_a3_increase_cnt": "36902",
"natural_a3_increase_cnt": "1652169",
"a3_increase_cnt": "1689071",
}
}
mock_client = AsyncMock()
mock_client.post.return_value = mock_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
with patch("httpx.AsyncClient", return_value=mock_client):
result = await call_yuntu_api(
item_id="7560751618711457062",
publish_time=datetime(2025, 10, 14),
industry_id="12",
aadvid="1648829117232140",
auth_token="sessionid=f9dfd57df6935afd1255bdc8f0dd0e4b",
)
# 验证调用参数
call_args = mock_client.post.call_args
url = call_args.args[0]
json_data = call_args.kwargs["json"]
headers = call_args.kwargs["headers"]
# 1. URL 包含 aadvid
assert "aadvid=1648829117232140" in url
# 2. 日期格式 YYYYMMDD
assert json_data["start_date"] == "20251014"
assert json_data["end_date"] == "20251113"
# 3. industry_id 字符串数组
assert json_data["industry_id_list"] == ["12"]
# 4. Cookie 直接使用 auth_token
assert headers["Cookie"] == "sessionid=f9dfd57df6935afd1255bdc8f0dd0e4b"
# 验证返回结果
assert result["status"] == 0
assert result["data"]["a3_increase_cnt"] == "1689071"