基于项目需求文档(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>
276 lines
9.2 KiB
Python
276 lines
9.2 KiB
Python
"""
|
||
数据验证器单元测试
|
||
|
||
TDD 测试用例 - 验证所有输入数据的格式和约束
|
||
"""
|
||
|
||
import pytest
|
||
from typing import Any
|
||
|
||
# 导入待实现的模块(TDD 红灯阶段)
|
||
# from app.utils.validators import (
|
||
# BriefValidator,
|
||
# VideoValidator,
|
||
# ReviewDecisionValidator,
|
||
# TaskValidator,
|
||
# )
|
||
|
||
|
||
class TestBriefValidator:
|
||
"""Brief 数据验证测试"""
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("platform,expected_valid", [
|
||
("douyin", True),
|
||
("xiaohongshu", True),
|
||
("bilibili", True),
|
||
("kuaishou", True),
|
||
("weibo", False), # 暂不支持
|
||
("unknown", False),
|
||
("", False),
|
||
(None, False),
|
||
])
|
||
def test_platform_validation(self, platform: str | None, expected_valid: bool) -> None:
|
||
"""测试平台验证"""
|
||
# TODO: 实现平台验证
|
||
# validator = BriefValidator()
|
||
# result = validator.validate_platform(platform)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:平台验证")
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("region,expected_valid", [
|
||
("mainland_china", True),
|
||
("hk_tw", True),
|
||
("overseas", True),
|
||
("unknown", False),
|
||
("", False),
|
||
])
|
||
def test_region_validation(self, region: str, expected_valid: bool) -> None:
|
||
"""测试区域验证"""
|
||
# TODO: 实现区域验证
|
||
# validator = BriefValidator()
|
||
# result = validator.validate_region(region)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:区域验证")
|
||
|
||
@pytest.mark.unit
|
||
def test_selling_points_structure(self) -> None:
|
||
"""测试卖点结构验证"""
|
||
valid_selling_points = [
|
||
{"text": "24小时持妆", "priority": "high"},
|
||
{"text": "天然成分", "priority": "medium"},
|
||
]
|
||
|
||
invalid_selling_points = [
|
||
{"text": ""}, # 缺少 priority,文本为空
|
||
"just a string", # 格式错误
|
||
]
|
||
|
||
# TODO: 实现卖点结构验证
|
||
# validator = BriefValidator()
|
||
#
|
||
# assert validator.validate_selling_points(valid_selling_points).is_valid
|
||
# assert not validator.validate_selling_points(invalid_selling_points).is_valid
|
||
pytest.skip("待实现:卖点结构验证")
|
||
|
||
|
||
class TestVideoValidator:
|
||
"""视频数据验证测试"""
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("duration_seconds,expected_valid", [
|
||
(30, True),
|
||
(60, True),
|
||
(300, True), # 5 分钟
|
||
(1800, True), # 30 分钟 - 边界
|
||
(3600, False), # 1 小时 - 可能需要警告
|
||
(0, False),
|
||
(-1, False),
|
||
])
|
||
def test_duration_validation(self, duration_seconds: int, expected_valid: bool) -> None:
|
||
"""测试视频时长验证"""
|
||
# TODO: 实现时长验证
|
||
# validator = VideoValidator()
|
||
# result = validator.validate_duration(duration_seconds)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:时长验证")
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("resolution,expected_valid", [
|
||
("1920x1080", True), # 1080p
|
||
("1080x1920", True), # 竖屏 1080p
|
||
("3840x2160", True), # 4K
|
||
("1280x720", True), # 720p
|
||
("640x480", False), # 480p - 太低
|
||
("320x240", False),
|
||
])
|
||
def test_resolution_validation(self, resolution: str, expected_valid: bool) -> None:
|
||
"""测试分辨率验证"""
|
||
# TODO: 实现分辨率验证
|
||
# validator = VideoValidator()
|
||
# result = validator.validate_resolution(resolution)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:分辨率验证")
|
||
|
||
|
||
class TestReviewDecisionValidator:
|
||
"""审核决策验证测试"""
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("decision,expected_valid", [
|
||
("passed", True),
|
||
("rejected", True),
|
||
("force_passed", True),
|
||
("pending", False), # 无效决策
|
||
("unknown", False),
|
||
("", False),
|
||
])
|
||
def test_decision_type_validation(self, decision: str, expected_valid: bool) -> None:
|
||
"""测试决策类型验证"""
|
||
# TODO: 实现决策验证
|
||
# validator = ReviewDecisionValidator()
|
||
# result = validator.validate_decision_type(decision)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:决策类型验证")
|
||
|
||
@pytest.mark.unit
|
||
def test_force_pass_requires_reason(self) -> None:
|
||
"""测试强制通过必须填写原因"""
|
||
# 强制通过但无原因
|
||
invalid_request = {
|
||
"decision": "force_passed",
|
||
"force_pass_reason": "",
|
||
}
|
||
|
||
# 强制通过有原因
|
||
valid_request = {
|
||
"decision": "force_passed",
|
||
"force_pass_reason": "达人玩的新梗,品牌方认可",
|
||
}
|
||
|
||
# TODO: 实现强制通过验证
|
||
# validator = ReviewDecisionValidator()
|
||
#
|
||
# assert not validator.validate(invalid_request).is_valid
|
||
# assert "原因" in validator.validate(invalid_request).error_message
|
||
#
|
||
# assert validator.validate(valid_request).is_valid
|
||
pytest.skip("待实现:强制通过原因验证")
|
||
|
||
@pytest.mark.unit
|
||
def test_rejection_requires_violations(self) -> None:
|
||
"""测试驳回必须选择违规项"""
|
||
# 驳回但无选择违规项
|
||
invalid_request = {
|
||
"decision": "rejected",
|
||
"selected_violations": [],
|
||
}
|
||
|
||
# 驳回并选择违规项
|
||
valid_request = {
|
||
"decision": "rejected",
|
||
"selected_violations": ["violation_001", "violation_002"],
|
||
}
|
||
|
||
# TODO: 实现驳回验证
|
||
# validator = ReviewDecisionValidator()
|
||
#
|
||
# assert not validator.validate(invalid_request).is_valid
|
||
# assert validator.validate(valid_request).is_valid
|
||
pytest.skip("待实现:驳回违规项验证")
|
||
|
||
|
||
class TestAppealValidator:
|
||
"""申诉验证测试"""
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("reason_length,expected_valid", [
|
||
(5, False), # < 10 字
|
||
(9, False), # < 10 字
|
||
(10, True), # = 10 字
|
||
(50, True), # > 10 字
|
||
(500, True), # 长文本
|
||
])
|
||
def test_appeal_reason_length(self, reason_length: int, expected_valid: bool) -> None:
|
||
"""测试申诉理由长度 - 必须 ≥ 10 字"""
|
||
reason = "字" * reason_length
|
||
|
||
# TODO: 实现申诉验证
|
||
# validator = AppealValidator()
|
||
# result = validator.validate_reason(reason)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:申诉理由长度验证")
|
||
|
||
@pytest.mark.unit
|
||
def test_appeal_token_check(self) -> None:
|
||
"""测试申诉令牌检查"""
|
||
# TODO: 实现令牌验证
|
||
# validator = AppealValidator()
|
||
#
|
||
# # 有令牌
|
||
# result = validator.validate_token_available(user_id="user_001")
|
||
# assert result.is_valid
|
||
# assert result.remaining_tokens > 0
|
||
#
|
||
# # 无令牌
|
||
# result = validator.validate_token_available(user_id="user_no_tokens")
|
||
# assert not result.is_valid
|
||
pytest.skip("待实现:申诉令牌验证")
|
||
|
||
|
||
class TestTimestampValidator:
|
||
"""时间戳验证测试"""
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("timestamp_ms,video_duration_ms,expected_valid", [
|
||
(0, 60000, True), # 开始
|
||
(30000, 60000, True), # 中间
|
||
(60000, 60000, True), # 结束
|
||
(-1, 60000, False), # 负数
|
||
(70000, 60000, False), # 超出视频时长
|
||
])
|
||
def test_timestamp_range_validation(
|
||
self,
|
||
timestamp_ms: int,
|
||
video_duration_ms: int,
|
||
expected_valid: bool,
|
||
) -> None:
|
||
"""测试时间戳范围验证"""
|
||
# TODO: 实现时间戳验证
|
||
# validator = TimestampValidator()
|
||
# result = validator.validate_range(timestamp_ms, video_duration_ms)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:时间戳范围验证")
|
||
|
||
@pytest.mark.unit
|
||
def test_timestamp_order_validation(self) -> None:
|
||
"""测试时间戳顺序验证 - start < end"""
|
||
# TODO: 实现顺序验证
|
||
# validator = TimestampValidator()
|
||
#
|
||
# assert validator.validate_order(start=1000, end=2000).is_valid
|
||
# assert not validator.validate_order(start=2000, end=1000).is_valid
|
||
# assert not validator.validate_order(start=1000, end=1000).is_valid
|
||
pytest.skip("待实现:时间戳顺序验证")
|
||
|
||
|
||
class TestUUIDValidator:
|
||
"""UUID 验证测试"""
|
||
|
||
@pytest.mark.unit
|
||
@pytest.mark.parametrize("uuid_str,expected_valid", [
|
||
("550e8400-e29b-41d4-a716-446655440000", True),
|
||
("550E8400-E29B-41D4-A716-446655440000", True), # 大写
|
||
("not-a-uuid", False),
|
||
("", False),
|
||
("12345", False),
|
||
])
|
||
def test_uuid_format_validation(self, uuid_str: str, expected_valid: bool) -> None:
|
||
"""测试 UUID 格式验证"""
|
||
# TODO: 实现 UUID 验证
|
||
# validator = UUIDValidator()
|
||
# result = validator.validate(uuid_str)
|
||
# assert result.is_valid == expected_valid
|
||
pytest.skip("待实现:UUID 格式验证")
|