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>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
18fe22ce8a
commit
040aada160
@@ -0,0 +1,177 @@
|
||||
"""
|
||||
Brief API 集成测试
|
||||
|
||||
TDD 测试用例 - 测试 Brief 相关 API 接口
|
||||
|
||||
接口规范参考:DevelopmentPlan.md 第 7 章
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import Any
|
||||
|
||||
# 导入待实现的模块(TDD 红灯阶段)
|
||||
# from httpx import AsyncClient
|
||||
# from app.main import app
|
||||
|
||||
|
||||
class TestBriefUploadAPI:
|
||||
"""Brief 上传 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_brief_pdf_success(self) -> None:
|
||||
"""测试 Brief PDF 上传成功"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# # 登录获取 token
|
||||
# login_response = await client.post("/api/v1/auth/login", json={
|
||||
# "email": "agency@test.com",
|
||||
# "password": "password"
|
||||
# })
|
||||
# token = login_response.json()["access_token"]
|
||||
# headers = {"Authorization": f"Bearer {token}"}
|
||||
#
|
||||
# # 上传 Brief
|
||||
# with open("tests/fixtures/briefs/sample_brief.pdf", "rb") as f:
|
||||
# response = await client.post(
|
||||
# "/api/v1/briefs/upload",
|
||||
# files={"file": ("brief.pdf", f, "application/pdf")},
|
||||
# data={"task_id": "task_001", "platform": "douyin"},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 202
|
||||
# data = response.json()
|
||||
# assert "parsing_id" in data
|
||||
# assert data["status"] == "processing"
|
||||
pytest.skip("待实现:Brief 上传 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_unsupported_format_returns_400(self) -> None:
|
||||
"""测试不支持的格式返回 400"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/briefs/upload",
|
||||
# files={"file": ("test.exe", b"content", "application/octet-stream")},
|
||||
# data={"task_id": "task_001"},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 400
|
||||
# assert "Unsupported file format" in response.json()["error"]
|
||||
pytest.skip("待实现:不支持格式测试")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_without_auth_returns_401(self) -> None:
|
||||
"""测试无认证返回 401"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/briefs/upload",
|
||||
# files={"file": ("brief.pdf", b"content", "application/pdf")},
|
||||
# data={"task_id": "task_001"}
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 401
|
||||
pytest.skip("待实现:无认证测试")
|
||||
|
||||
|
||||
class TestBriefParsingAPI:
|
||||
"""Brief 解析结果 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_parsing_result_success(self) -> None:
|
||||
"""测试获取解析结果成功"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/briefs/brief_001",
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
# assert "selling_points" in data
|
||||
# assert "forbidden_words" in data
|
||||
# assert "brand_tone" in data
|
||||
pytest.skip("待实现:获取解析结果 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nonexistent_brief_returns_404(self) -> None:
|
||||
"""测试获取不存在的 Brief 返回 404"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/briefs/nonexistent_id",
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 404
|
||||
pytest.skip("待实现:404 测试")
|
||||
|
||||
|
||||
class TestOnlineDocumentImportAPI:
|
||||
"""在线文档导入 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_import_feishu_doc_success(self) -> None:
|
||||
"""测试飞书文档导入成功"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/briefs/import",
|
||||
# json={
|
||||
# "url": "https://docs.feishu.cn/docs/valid_doc_id",
|
||||
# "task_id": "task_001"
|
||||
# },
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 202
|
||||
pytest.skip("待实现:飞书导入 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_import_unauthorized_link_returns_403(self) -> None:
|
||||
"""测试无权限链接返回 403"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/briefs/import",
|
||||
# json={
|
||||
# "url": "https://docs.feishu.cn/docs/restricted_doc",
|
||||
# "task_id": "task_001"
|
||||
# },
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 403
|
||||
# assert "access" in response.json()["error"].lower()
|
||||
pytest.skip("待实现:无权限链接测试")
|
||||
|
||||
|
||||
class TestRuleConflictAPI:
|
||||
"""规则冲突检测 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_detect_rule_conflict(self) -> None:
|
||||
"""测试规则冲突检测"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/briefs/brief_001/check_conflicts",
|
||||
# json={"platform": "douyin"},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
# assert "conflicts" in data
|
||||
pytest.skip("待实现:规则冲突检测 API")
|
||||
@@ -0,0 +1,487 @@
|
||||
"""
|
||||
审核决策 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("待实现:品牌方权限限制")
|
||||
@@ -0,0 +1,379 @@
|
||||
"""
|
||||
视频 API 集成测试
|
||||
|
||||
TDD 测试用例 - 测试视频上传、审核相关 API 接口
|
||||
|
||||
接口规范参考:DevelopmentPlan.md 第 7 章
|
||||
验收标准参考:FeatureSummary.md F-10~F-18
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from typing import Any
|
||||
|
||||
# 导入待实现的模块(TDD 红灯阶段)
|
||||
# from httpx import AsyncClient
|
||||
# from app.main import app
|
||||
|
||||
|
||||
class TestVideoUploadAPI:
|
||||
"""视频上传 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_video_success(self) -> None:
|
||||
"""测试视频上传成功 - 返回 202 和 video_id"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# # 登录获取 token
|
||||
# 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}"}
|
||||
#
|
||||
# # 上传视频
|
||||
# with open("tests/fixtures/videos/sample_video.mp4", "rb") as f:
|
||||
# response = await client.post(
|
||||
# "/api/v1/videos/upload",
|
||||
# files={"file": ("test.mp4", f, "video/mp4")},
|
||||
# data={
|
||||
# "task_id": "task_001",
|
||||
# "title": "测试视频"
|
||||
# },
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 202
|
||||
# data = response.json()
|
||||
# assert "video_id" in data
|
||||
# assert data["status"] == "processing"
|
||||
pytest.skip("待实现:视频上传 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_oversized_video_returns_413(self) -> None:
|
||||
"""测试超大视频返回 413 - 最大 100MB"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# # 创建超过 100MB 的测试数据
|
||||
# oversized_content = b"x" * (101 * 1024 * 1024)
|
||||
#
|
||||
# response = await client.post(
|
||||
# "/api/v1/videos/upload",
|
||||
# files={"file": ("large.mp4", oversized_content, "video/mp4")},
|
||||
# data={"task_id": "task_001"},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 413
|
||||
# assert "100MB" in response.json()["error"]
|
||||
pytest.skip("待实现:超大视频测试")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("mime_type,expected_status", [
|
||||
("video/mp4", 202),
|
||||
("video/quicktime", 202), # MOV
|
||||
("video/x-msvideo", 400), # AVI - 不支持
|
||||
("video/x-matroska", 400), # MKV - 不支持
|
||||
("application/pdf", 400),
|
||||
])
|
||||
async def test_upload_video_format_validation(
|
||||
self,
|
||||
mime_type: str,
|
||||
expected_status: int,
|
||||
) -> None:
|
||||
"""测试视频格式验证 - 仅支持 MP4/MOV"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/videos/upload",
|
||||
# files={"file": ("test.video", b"content", mime_type)},
|
||||
# data={"task_id": "task_001"},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == expected_status
|
||||
pytest.skip("待实现:视频格式验证")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_resumable_upload(self) -> None:
|
||||
"""测试断点续传功能"""
|
||||
# TODO: 实现断点续传测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# # 初始化上传
|
||||
# init_response = await client.post(
|
||||
# "/api/v1/videos/upload/init",
|
||||
# json={
|
||||
# "filename": "large_video.mp4",
|
||||
# "file_size": 50 * 1024 * 1024,
|
||||
# "task_id": "task_001"
|
||||
# },
|
||||
# headers=headers
|
||||
# )
|
||||
# upload_id = init_response.json()["upload_id"]
|
||||
#
|
||||
# # 上传分片
|
||||
# chunk_response = await client.post(
|
||||
# f"/api/v1/videos/upload/{upload_id}/chunk",
|
||||
# files={"chunk": ("chunk_0", b"x" * 1024 * 1024)},
|
||||
# data={"chunk_index": 0},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert chunk_response.status_code == 200
|
||||
# assert chunk_response.json()["received_chunks"] == 1
|
||||
pytest.skip("待实现:断点续传")
|
||||
|
||||
|
||||
class TestVideoAuditAPI:
|
||||
"""视频审核 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_audit_result_success(self) -> None:
|
||||
"""测试获取审核结果成功"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos/video_001/audit",
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
#
|
||||
# # 验证审核报告结构
|
||||
# assert "report_id" in data
|
||||
# assert "video_id" in data
|
||||
# assert "status" in data
|
||||
# assert "violations" in data
|
||||
# assert "brief_compliance" in data
|
||||
# assert "processing_time_ms" in data
|
||||
pytest.skip("待实现:获取审核结果 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_audit_result_processing(self) -> None:
|
||||
"""测试获取处理中的审核结果"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos/video_processing/audit",
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
# assert data["status"] == "processing"
|
||||
# assert "progress" in data
|
||||
pytest.skip("待实现:处理中状态测试")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nonexistent_video_returns_404(self) -> None:
|
||||
"""测试获取不存在的视频返回 404"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos/nonexistent_id/audit",
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 404
|
||||
pytest.skip("待实现:404 测试")
|
||||
|
||||
|
||||
class TestViolationEvidenceAPI:
|
||||
"""违规证据 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_violation_evidence(self) -> None:
|
||||
"""测试获取违规证据 - 包含截图和时间戳"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos/video_001/violations/vio_001/evidence",
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
#
|
||||
# assert "violation_id" in data
|
||||
# assert "evidence_type" in data
|
||||
# assert "screenshot_url" in data
|
||||
# assert "timestamp_start" in data
|
||||
# assert "timestamp_end" in data
|
||||
# assert "content" in data
|
||||
pytest.skip("待实现:违规证据 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_evidence_screenshot_accessible(self) -> None:
|
||||
"""测试证据截图可访问"""
|
||||
# TODO: 实现截图访问测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# # 获取证据
|
||||
# evidence_response = await client.get(
|
||||
# "/api/v1/videos/video_001/violations/vio_001/evidence",
|
||||
# headers=headers
|
||||
# )
|
||||
# screenshot_url = evidence_response.json()["screenshot_url"]
|
||||
#
|
||||
# # 访问截图
|
||||
# screenshot_response = await client.get(screenshot_url)
|
||||
# assert screenshot_response.status_code == 200
|
||||
# assert "image" in screenshot_response.headers["content-type"]
|
||||
pytest.skip("待实现:截图访问测试")
|
||||
|
||||
|
||||
class TestVideoPreviewAPI:
|
||||
"""视频预览 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_video_preview_with_timestamp(self) -> None:
|
||||
"""测试带时间戳的视频预览"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos/video_001/preview",
|
||||
# params={"start_ms": 5000, "end_ms": 10000},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
#
|
||||
# assert "preview_url" in data
|
||||
# assert "start_ms" in data
|
||||
# assert "end_ms" in data
|
||||
pytest.skip("待实现:视频预览 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_video_seek_to_violation(self) -> None:
|
||||
"""测试视频跳转到违规时间点"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# # 获取违规列表
|
||||
# violations_response = await client.get(
|
||||
# "/api/v1/videos/video_001/violations",
|
||||
# headers=headers
|
||||
# )
|
||||
# violations = violations_response.json()["violations"]
|
||||
#
|
||||
# # 每个违规项应包含可跳转的时间戳
|
||||
# for violation in violations:
|
||||
# assert "timestamp_start" in violation
|
||||
# assert violation["timestamp_start"] >= 0
|
||||
pytest.skip("待实现:视频跳转")
|
||||
|
||||
|
||||
class TestVideoResubmitAPI:
|
||||
"""视频重新提交 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_resubmit_video_success(self) -> None:
|
||||
"""测试重新提交视频"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/videos/video_001/resubmit",
|
||||
# json={
|
||||
# "modification_note": "已修改违规内容",
|
||||
# "modified_sections": ["00:05-00:10"]
|
||||
# },
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 202
|
||||
# data = response.json()
|
||||
# assert data["status"] == "processing"
|
||||
# assert "new_video_id" in data
|
||||
pytest.skip("待实现:重新提交 API")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_resubmit_without_modification_note(self) -> None:
|
||||
"""测试无修改说明的重新提交"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.post(
|
||||
# "/api/v1/videos/video_001/resubmit",
|
||||
# json={},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# # 应该允许不提供修改说明
|
||||
# assert response.status_code in [202, 400]
|
||||
pytest.skip("待实现:无修改说明测试")
|
||||
|
||||
|
||||
class TestVideoListAPI:
|
||||
"""视频列表 API 测试"""
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_videos_with_pagination(self) -> None:
|
||||
"""测试视频列表分页"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos",
|
||||
# params={"page": 1, "page_size": 10},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
#
|
||||
# assert "items" in data
|
||||
# assert "total" in data
|
||||
# assert "page" in data
|
||||
# assert "page_size" in data
|
||||
# assert len(data["items"]) <= 10
|
||||
pytest.skip("待实现:视频列表分页")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_videos_filter_by_status(self) -> None:
|
||||
"""测试按状态筛选视频"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos",
|
||||
# params={"status": "pending_review"},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
#
|
||||
# for item in data["items"]:
|
||||
# assert item["status"] == "pending_review"
|
||||
pytest.skip("待实现:状态筛选")
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_videos_filter_by_task(self) -> None:
|
||||
"""测试按任务筛选视频"""
|
||||
# TODO: 实现 API 测试
|
||||
# async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
# response = await client.get(
|
||||
# "/api/v1/videos",
|
||||
# params={"task_id": "task_001"},
|
||||
# headers=headers
|
||||
# )
|
||||
#
|
||||
# assert response.status_code == 200
|
||||
# data = response.json()
|
||||
#
|
||||
# for item in data["items"]:
|
||||
# assert item["task_id"] == "task_001"
|
||||
pytest.skip("待实现:任务筛选")
|
||||
Reference in New Issue
Block a user