test: 记录双平台小规模真实验收

This commit is contained in:
meijiali
2026-07-03 14:53:54 +08:00
parent eedb1db4da
commit 46037a217b
3 changed files with 78 additions and 1 deletions
+22
View File
@@ -345,6 +345,28 @@ feat: 打磨报告页和内容详情页
test: 记录双平台小规模真实验收 test: 记录双平台小规模真实验收
``` ```
完成记录:
```text
完成日期:2026-07-03
相关 commit:test: 记录双平台小规模真实验收
验证命令:
- .venv/bin/python -m pytest tests/unit tests/integration -q
- .venv/bin/python -m pytest tests/unit tests/integration --cov=app --cov-branch --cov-report=term-missing
- docker compose up -d --build
- curl -f http://localhost:8000/health
真实任务 ID
- 小红书:8ae106f4-64dc-4675-a021-f78092926ce6
- 抖音:b295f4b2-d546-4121-99bc-48d166d86bf4
验收结论:
- 小红书 1×1×10status=success,热点 1,内容 1,评论 10,报告 2,AI 成功率 100%。
- 抖音 1×1×10status=success,热点 1,内容 1,评论 10,报告 2,AI 成功率 100%。
- 页面可查看任务详情、热点报告、内容详情和评论明细。
- 自动化测试补充了连续 429 的 1s→2s→4s 退避断言,以及单个内容条目失败后继续处理后续条目的容错测试。
- 代码中未发现 httpx.AsyncClient 使用。
遗留问题:无;进入 WO-04 默认规模验收。
```
### WO-04 双平台默认规模验收 ### WO-04 双平台默认规模验收
优先级:P0 优先级:P0
@@ -1,5 +1,6 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.models import ContentItem, Task
from app.platforms.base import PlatformAPIError from app.platforms.base import PlatformAPIError
from app.schemas import CreateTaskRequest from app.schemas import CreateTaskRequest
from app.services.task_service import create_task, run_task from app.services.task_service import create_task, run_task
@@ -11,6 +12,28 @@ class FailingHotspotPlatform:
raise PlatformAPIError("hotspot failed", error_type="api_error", status_code=500) raise PlatformAPIError("hotspot failed", error_type="api_error", status_code=500)
class OneFailedOneSuccessfulItemPlatform:
def fetch_hotspots(self, *, limit):
from app.platforms.base import HotspotData
return [HotspotData(source_hot_id="h1", title="热点一", rank=1)]
def search_items_by_hotspot(self, keyword, *, limit):
from app.platforms.base import ContentItemData
return [
ContentItemData(source_item_id="bad-item", item_type="video", title="失败内容"),
ContentItemData(source_item_id="good-item", item_type="video", title="成功内容"),
]
def fetch_comments(self, source_item_id, *, limit):
from app.platforms.base import CommentData
if source_item_id == "bad-item":
raise PlatformAPIError("comments failed", error_type="rate_limited", status_code=429)
return [CommentData(source_comment_id="c1", content="继续成功")]
def test_hotspot_failure_marks_task_failed(monkeypatch): def test_hotspot_failure_marks_task_failed(monkeypatch):
with make_test_client() as (_client, engine): with make_test_client() as (_client, engine):
monkeypatch.setattr("app.services.task_service.build_platform", lambda _platform: FailingHotspotPlatform()) monkeypatch.setattr("app.services.task_service.build_platform", lambda _platform: FailingHotspotPlatform())
@@ -49,3 +72,33 @@ def test_unexpected_task_exception_marks_task_failed(monkeypatch):
assert task.error_stage == "system" assert task.error_stage == "system"
assert task.error_type == "unexpected_error" assert task.error_type == "unexpected_error"
assert task.error_message == "boom" assert task.error_message == "boom"
def test_failed_content_item_is_recorded_and_following_item_continues(monkeypatch):
with make_test_client() as (_client, engine):
monkeypatch.setattr("app.services.task_service.build_platform", lambda _platform: OneFailedOneSuccessfulItemPlatform())
monkeypatch.setattr("app.services.task_service.build_ai_dependencies", lambda: (lambda _prompt: "[]", None))
monkeypatch.setattr("app.services.ai_service.time.sleep", lambda _seconds: None)
with Session(engine) as session:
task = create_task(
session,
CreateTaskRequest(platform="douyin", hotspot_limit=1, item_limit_per_hotspot=2, comment_limit_per_item=10),
submit_background=False,
)
task_id = task.id
run_task(task_id, session_factory=lambda: session)
persisted = session.get(Task, task_id)
failed_item = session.query(ContentItem).filter_by(task_id=task_id, source_item_id="bad-item").one()
successful_item = session.query(ContentItem).filter_by(task_id=task_id, source_item_id="good-item").one()
assert persisted.status == "success"
assert persisted.total_items_count == 2
assert persisted.processed_items_count == 2
assert persisted.successful_items_count == 1
assert persisted.failed_items_count == 1
assert failed_item.status == "failed"
assert failed_item.error_stage == "crawl_comments"
assert failed_item.error_type == "rate_limited"
assert successful_item.status == "success"
+3 -1
View File
@@ -38,7 +38,8 @@ def test_tikhub_client_retries_429_with_exponential_backoff(monkeypatch):
def test_tikhub_client_raises_structured_error_after_retries(monkeypatch): def test_tikhub_client_raises_structured_error_after_retries(monkeypatch):
monkeypatch.setattr("app.platforms.base.time.sleep", lambda _seconds: None) sleeps = []
monkeypatch.setattr("app.platforms.base.time.sleep", sleeps.append)
transport = SequenceTransport([httpx.Response(429, json={"message": "Too Many Requests"}) for _ in range(4)]) transport = SequenceTransport([httpx.Response(429, json={"message": "Too Many Requests"}) for _ in range(4)])
http_client = httpx.Client(transport=httpx.MockTransport(transport)) http_client = httpx.Client(transport=httpx.MockTransport(transport))
client = TikHubClient(base_url="https://api.test", api_key="secret-token", http_client=http_client) client = TikHubClient(base_url="https://api.test", api_key="secret-token", http_client=http_client)
@@ -49,3 +50,4 @@ def test_tikhub_client_raises_structured_error_after_retries(monkeypatch):
assert exc_info.value.error_type == "rate_limited" assert exc_info.value.error_type == "rate_limited"
assert exc_info.value.status_code == 429 assert exc_info.value.status_code == 429
assert "secret-token" not in str(exc_info.value) assert "secret-token" not in str(exc_info.value)
assert sleeps == [1, 2, 4]