videos1.0/backend/tests/integration/test_api_review.py
Your Name 040aada160 feat: 添加全面的 TDD 测试套件框架
基于项目需求文档(PRD.md, FeatureSummary.md, DevelopmentPlan.md,
UIDesign.md, User_Role_Interfaces.md)编写的 TDD 测试用例。

后端测试 (Python/pytest):
- 单元测试: rule_engine, brief_parser, timestamp_alignment,
  video_auditor, validators
- 集成测试: API Brief, Video, Review 端点
- AI 模块测试: ASR, OCR, Logo 检测服务
- 全局 fixtures 和 pytest 配置

前端测试 (TypeScript/Vitest):
- 工具函数测试: utils.test.ts
- 组件测试: Button, VideoPlayer, ViolationList
- Hooks 测试: useVideoAudit, useVideoPlayer, useAppeal
- MSW mock handlers 配置

E2E 测试 (Playwright):
- 认证流程测试
- 视频上传流程测试
- 视频审核流程测试
- 申诉流程测试

所有测试当前使用 pytest.skip() / it.skip() 作为占位符,
遵循 TDD 红灯阶段 - 等待实现代码后运行。

验收标准覆盖:
- ASR WER ≤ 10%
- OCR 准确率 ≥ 95%
- Logo F1 ≥ 0.85
- 时间戳误差 ≤ 0.5s
- 频次统计准确率 ≥ 95%

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:22:24 +08:00

488 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
审核决策 API 集成测试
TDD 测试用例 - 测试审核员操作相关 API 接口
接口规范参考DevelopmentPlan.md 第 7 章
用户角色参考User_Role_Interfaces.md
"""
import pytest
from typing import Any
# 导入待实现的模块TDD 红灯阶段)
# from httpx import AsyncClient
# from app.main import app
class TestReviewDecisionAPI:
"""审核决策 API 测试"""
@pytest.mark.integration
@pytest.mark.asyncio
async def test_submit_pass_decision(self) -> None:
"""测试提交通过决策"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# # 以审核员身份登录
# login_response = await client.post("/api/v1/auth/login", json={
# "email": "reviewer@test.com",
# "password": "password"
# })
# token = login_response.json()["access_token"]
# headers = {"Authorization": f"Bearer {token}"}
#
# # 提交通过决策
# response = await client.post(
# "/api/v1/reviews/video_001/decision",
# json={
# "decision": "passed",
# "comment": "内容符合要求"
# },
# headers=headers
# )
#
# assert response.status_code == 200
# data = response.json()
# assert data["status"] == "passed"
# assert "review_id" in data
pytest.skip("待实现:通过决策 API")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_submit_reject_decision_with_violations(self) -> None:
"""测试提交驳回决策 - 必须选择违规项"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_001/decision",
# json={
# "decision": "rejected",
# "selected_violations": ["vio_001", "vio_002"],
# "comment": "存在违规内容"
# },
# headers=headers
# )
#
# assert response.status_code == 200
# data = response.json()
# assert data["status"] == "rejected"
# assert len(data["selected_violations"]) == 2
pytest.skip("待实现:驳回决策 API")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_reject_without_violations_returns_400(self) -> None:
"""测试驳回无违规项返回 400"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_001/decision",
# json={
# "decision": "rejected",
# "selected_violations": [], # 空违规列表
# "comment": "驳回"
# },
# headers=headers
# )
#
# assert response.status_code == 400
# assert "违规项" in response.json()["error"]
pytest.skip("待实现:驳回无违规项测试")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_submit_force_pass_with_reason(self) -> None:
"""测试强制通过 - 必须填写原因"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_001/decision",
# json={
# "decision": "force_passed",
# "force_pass_reason": "达人玩的新梗,品牌方认可",
# "comment": "特殊情况强制通过"
# },
# headers=headers
# )
#
# assert response.status_code == 200
# data = response.json()
# assert data["status"] == "force_passed"
# assert data["force_pass_reason"] is not None
pytest.skip("待实现:强制通过 API")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_force_pass_without_reason_returns_400(self) -> None:
"""测试强制通过无原因返回 400"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_001/decision",
# json={
# "decision": "force_passed",
# "force_pass_reason": "", # 空原因
# },
# headers=headers
# )
#
# assert response.status_code == 400
# assert "原因" in response.json()["error"]
pytest.skip("待实现:强制通过无原因测试")
class TestViolationEditAPI:
"""违规项编辑 API 测试"""
@pytest.mark.integration
@pytest.mark.asyncio
async def test_add_manual_violation(self) -> None:
"""测试手动添加违规项"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_001/violations",
# json={
# "type": "other",
# "content": "手动发现的问题",
# "timestamp_start": 10.5,
# "timestamp_end": 15.0,
# "severity": "medium"
# },
# headers=headers
# )
#
# assert response.status_code == 201
# data = response.json()
# assert "violation_id" in data
# assert data["source"] == "manual"
pytest.skip("待实现:添加手动违规项")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_delete_ai_violation(self) -> None:
"""测试删除 AI 检测的违规项"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.delete(
# "/api/v1/reviews/video_001/violations/vio_001",
# json={
# "delete_reason": "误检"
# },
# headers=headers
# )
#
# assert response.status_code == 200
# data = response.json()
# assert data["status"] == "deleted"
pytest.skip("待实现:删除违规项")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_modify_violation_severity(self) -> None:
"""测试修改违规项严重程度"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.patch(
# "/api/v1/reviews/video_001/violations/vio_001",
# json={
# "severity": "low",
# "modify_reason": "风险较低"
# },
# headers=headers
# )
#
# assert response.status_code == 200
# data = response.json()
# assert data["severity"] == "low"
pytest.skip("待实现:修改违规严重程度")
class TestAppealAPI:
"""申诉 API 测试"""
@pytest.mark.integration
@pytest.mark.asyncio
async def test_submit_appeal_success(self) -> None:
"""测试提交申诉成功"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# # 以达人身份登录
# login_response = await client.post("/api/v1/auth/login", json={
# "email": "creator@test.com",
# "password": "password"
# })
# token = login_response.json()["access_token"]
# headers = {"Authorization": f"Bearer {token}"}
#
# response = await client.post(
# "/api/v1/reviews/video_001/appeal",
# json={
# "violation_ids": ["vio_001"],
# "reason": "这个词语在此语境下是正常使用,不应被判定为违规"
# },
# headers=headers
# )
#
# assert response.status_code == 201
# data = response.json()
# assert "appeal_id" in data
# assert data["status"] == "pending"
pytest.skip("待实现:提交申诉 API")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_appeal_reason_too_short_returns_400(self) -> None:
"""测试申诉理由过短返回 400 - 必须 ≥ 10 字"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_001/appeal",
# json={
# "violation_ids": ["vio_001"],
# "reason": "太短了" # < 10 字
# },
# headers=creator_headers
# )
#
# assert response.status_code == 400
# assert "10" in response.json()["error"]
pytest.skip("待实现:申诉理由过短测试")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_appeal_token_deduction(self) -> None:
"""测试申诉扣除令牌"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# # 获取当前令牌数
# profile_response = await client.get(
# "/api/v1/users/me",
# headers=creator_headers
# )
# initial_tokens = profile_response.json()["appeal_tokens"]
#
# # 提交申诉
# await client.post(
# "/api/v1/reviews/video_001/appeal",
# json={
# "violation_ids": ["vio_001"],
# "reason": "这个词语在此语境下是正常使用,不应被判定为违规"
# },
# headers=creator_headers
# )
#
# # 验证令牌扣除
# profile_response = await client.get(
# "/api/v1/users/me",
# headers=creator_headers
# )
# assert profile_response.json()["appeal_tokens"] == initial_tokens - 1
pytest.skip("待实现:申诉令牌扣除")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_appeal_no_token_returns_403(self) -> None:
"""测试无令牌申诉返回 403"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# # 使用无令牌的用户
# response = await client.post(
# "/api/v1/reviews/video_001/appeal",
# json={
# "violation_ids": ["vio_001"],
# "reason": "这个词语在此语境下是正常使用,不应被判定为违规"
# },
# headers=no_token_user_headers
# )
#
# assert response.status_code == 403
# assert "令牌" in response.json()["error"]
pytest.skip("待实现:无令牌申诉测试")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_process_appeal_success(self) -> None:
"""测试处理申诉 - 申诉成功"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/appeals/appeal_001/process",
# json={
# "decision": "approved",
# "comment": "申诉理由成立"
# },
# headers=reviewer_headers
# )
#
# assert response.status_code == 200
# data = response.json()
# assert data["status"] == "approved"
pytest.skip("待实现:处理申诉 API")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_appeal_success_restores_token(self) -> None:
"""测试申诉成功返还令牌"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# # 获取申诉前令牌数
# profile_response = await client.get(
# "/api/v1/users/creator_001",
# headers=admin_headers
# )
# tokens_before = profile_response.json()["appeal_tokens"]
#
# # 处理申诉为成功
# await client.post(
# "/api/v1/reviews/appeals/appeal_001/process",
# json={"decision": "approved", "comment": "申诉成立"},
# headers=reviewer_headers
# )
#
# # 验证令牌返还
# profile_response = await client.get(
# "/api/v1/users/creator_001",
# headers=admin_headers
# )
# assert profile_response.json()["appeal_tokens"] == tokens_before + 1
pytest.skip("待实现:申诉成功返还令牌")
class TestReviewHistoryAPI:
"""审核历史 API 测试"""
@pytest.mark.integration
@pytest.mark.asyncio
async def test_get_review_history(self) -> None:
"""测试获取审核历史"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.get(
# "/api/v1/reviews/video_001/history",
# headers=headers
# )
#
# assert response.status_code == 200
# data = response.json()
#
# assert "history" in data
# for entry in data["history"]:
# assert "timestamp" in entry
# assert "action" in entry
# assert "actor" in entry
pytest.skip("待实现:审核历史 API")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_review_history_includes_all_actions(self) -> None:
"""测试审核历史包含所有操作"""
# TODO: 实现 API 测试
# 应包含AI 审核、人工审核、申诉、重新提交等
pytest.skip("待实现:审核历史完整性")
class TestBatchReviewAPI:
"""批量审核 API 测试"""
@pytest.mark.integration
@pytest.mark.asyncio
async def test_batch_pass_videos(self) -> None:
"""测试批量通过视频"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/batch/decision",
# json={
# "video_ids": ["video_001", "video_002", "video_003"],
# "decision": "passed",
# "comment": "批量通过"
# },
# headers=headers
# )
#
# assert response.status_code == 200
# data = response.json()
# assert data["processed_count"] == 3
# assert data["success_count"] == 3
pytest.skip("待实现:批量通过 API")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_batch_review_partial_failure(self) -> None:
"""测试批量审核部分失败"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/batch/decision",
# json={
# "video_ids": ["video_001", "nonexistent_video"],
# "decision": "passed"
# },
# headers=headers
# )
#
# assert response.status_code == 207 # Multi-Status
# data = response.json()
# assert data["success_count"] == 1
# assert data["failure_count"] == 1
# assert "failures" in data
pytest.skip("待实现:批量审核部分失败")
class TestReviewPermissionAPI:
"""审核权限 API 测试"""
@pytest.mark.integration
@pytest.mark.asyncio
async def test_creator_cannot_review_own_video(self) -> None:
"""测试达人不能审核自己的视频"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_own/decision",
# json={"decision": "passed"},
# headers=creator_headers
# )
#
# assert response.status_code == 403
pytest.skip("待实现:达人审核权限限制")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_agency_can_review_assigned_videos(self) -> None:
"""测试 Agency 可以审核分配的视频"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# response = await client.post(
# "/api/v1/reviews/video_assigned/decision",
# json={"decision": "passed"},
# headers=agency_headers
# )
#
# assert response.status_code == 200
pytest.skip("待实现Agency 审核权限")
@pytest.mark.integration
@pytest.mark.asyncio
async def test_brand_can_view_but_not_decide(self) -> None:
"""测试品牌方可以查看但不能决策"""
# TODO: 实现 API 测试
# async with AsyncClient(app=app, base_url="http://test") as client:
# # 可以查看
# view_response = await client.get(
# "/api/v1/reviews/video_001",
# headers=brand_headers
# )
# assert view_response.status_code == 200
#
# # 不能决策
# decision_response = await client.post(
# "/api/v1/reviews/video_001/decision",
# json={"decision": "passed"},
# headers=brand_headers
# )
# assert decision_response.status_code == 403
pytest.skip("待实现:品牌方权限限制")