Files
xiaoxia-saas/tests/unit/test_dedup_enhanced.py
T
CI Bot 07aa03da24
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 23s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 43s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 17s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1m12s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m37s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m38s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m3s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m8s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m12s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m17s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m23s
AI Code Review / AI Code Review (pull_request) Successful in 6m26s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 16m23s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 3s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
style: auto-format with black + isort + prettier [skip ci-format-check]
2026-09-07 11:49:16 +00:00

208 lines
7.1 KiB
Python

"""Tests for enhanced dedup: text + structure dimensions (Issue #P2-后端3)."""
from __future__ import annotations
import sys
from unittest.mock import MagicMock
# ---------------------------------------------------------------------------
# Mock heavy deps before importing dedup module
# ---------------------------------------------------------------------------
_ORIGINAL_MODULES = dict(sys.modules)
_MOCKED_MODULE_NAMES: list[str] = []
def _mock_if_absent(name: str, mock_obj=None):
"""仅在模块不在 sys.modules 中时注入 mock,并记录以便清理。"""
if name not in sys.modules:
sys.modules[name] = mock_obj if mock_obj is not None else MagicMock()
_MOCKED_MODULE_NAMES.append(name)
# Mock heavy deps
_mock_if_absent("ffmpeg")
_mock_if_absent("ffmpeg.utils")
_mock_if_absent("worker_app.celery_app")
_mock_if_absent("worker_app.db")
_mock_if_absent("packages.adapters.sqlalchemy_impl.generated_video_repository")
_mock_if_absent("packages.adapters.sqlalchemy_impl.models")
_mock_if_absent("packages.shared.storage")
# Mock cv2 and numpy if not available
try:
import cv2 as _cv2
if not isinstance(_cv2, MagicMock):
_HAS_CV2 = True
else:
_HAS_CV2 = False
except ImportError:
_HAS_CV2 = False
_mock_if_absent("cv2")
_mock_if_absent("numpy")
import pytest
from apps.worker.video_processing.dedup import (
STRUCTURE_WEIGHT,
TEXT_WEIGHT,
VISUAL_WEIGHT,
compute_structure_similarity,
compute_text_similarity,
)
class TestTextSimilarity:
"""Tests for compute_text_similarity."""
def test_identical_texts(self):
"""相同文本返回 1.0。"""
assert compute_text_similarity("你好世界", "你好世界") == 1.0
def test_empty_texts(self):
"""都为空返回 1.0。"""
assert compute_text_similarity("", "") == 1.0
def test_one_empty(self):
"""一个为空返回 0.0。"""
assert compute_text_similarity("你好", "") == 0.0
assert compute_text_similarity("", "你好") == 0.0
def test_completely_different(self):
"""完全不同文本返回低相似度。"""
sim = compute_text_similarity("你好世界", "abcdefgh")
assert sim < 0.3
def test_partial_overlap(self):
"""部分重叠文本返回中等相似度。"""
sim = compute_text_similarity("今天天气真好", "今天天气不错")
assert 0.3 < sim < 0.9
def test_case_insensitive(self):
"""英文大小写不敏感。"""
sim = compute_text_similarity("Hello World", "hello world")
assert sim == 1.0
def test_whitespace_ignored(self):
"""空白字符被忽略。"""
sim = compute_text_similarity("你好 世界", "你好世界")
assert sim == 1.0
def test_punctuation_ignored(self):
"""标点符号被忽略。"""
sim = compute_text_similarity("你好,世界!", "你好世界")
assert sim == 1.0
def test_long_texts(self):
"""长文本也能计算。"""
t1 = "这是一段很长的配音文本,用于测试文案查重功能"
t2 = "这是一段较长的配音文字,用于测试文案去重功能"
sim = compute_text_similarity(t1, t2)
assert 0.0 <= sim <= 1.0
class TestStructureSimilarity:
"""Tests for compute_structure_similarity."""
def test_identical_structures(self):
"""完全相同结构返回 1.0。"""
clips = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
{"clip_type": "video", "duration": 8.0},
]
assert compute_structure_similarity(clips, clips) == 1.0
def test_empty_clips(self):
"""都为空返回 1.0。"""
assert compute_structure_similarity([], []) == 1.0
def test_one_empty(self):
"""一个为空返回 0.0。"""
clips = [{"clip_type": "video", "duration": 5.0}]
assert compute_structure_similarity(clips, []) == 0.0
assert compute_structure_similarity([], clips) == 0.0
def test_different_count(self):
"""片段数不同,相似度降低。"""
clips1 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
]
clips2 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
{"clip_type": "video", "duration": 3.0},
{"clip_type": "title", "duration": 1.0},
]
sim = compute_structure_similarity(clips1, clips2)
assert 0.0 < sim < 0.8
def test_different_types(self):
"""片段类型不同,类型相似度低。"""
clips1 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "video", "duration": 3.0},
]
clips2 = [
{"clip_type": "title", "duration": 5.0},
{"clip_type": "title", "duration": 3.0},
]
sim = compute_structure_similarity(clips1, clips2)
assert sim <= 0.6 # 类型全部不同,但数量和时长相同贡献 0.6
def test_different_duration_distribution(self):
"""时长分布不同,时长相似度低。"""
clips1 = [
{"clip_type": "video", "duration": 10.0}, # 占比 80%
{"clip_type": "title", "duration": 2.5}, # 占比 20%
]
clips2 = [
{"clip_type": "video", "duration": 2.0}, # 占比 20%
{"clip_type": "title", "duration": 8.0}, # 占比 80%
]
sim = compute_structure_similarity(clips1, clips2)
assert 0.7 < sim < 0.9 # 类型相同但时长分布不同,sim=0.82
def test_similar_structure(self):
"""相似结构返回较高相似度。"""
clips1 = [
{"clip_type": "video", "duration": 5.0},
{"clip_type": "title", "duration": 2.0},
{"clip_type": "video", "duration": 8.0},
]
clips2 = [
{"clip_type": "video", "duration": 5.5},
{"clip_type": "title", "duration": 2.2},
{"clip_type": "video", "duration": 7.5},
]
sim = compute_structure_similarity(clips1, clips2)
assert sim > 0.8
def test_single_clip(self):
"""单片段也能计算。"""
clips1 = [{"clip_type": "video", "duration": 10.0}]
clips2 = [{"clip_type": "video", "duration": 12.0}]
sim = compute_structure_similarity(clips1, clips2)
assert sim > 0.5 # 类型相同,数量相同,只是时长不同
class TestDimensionWeights:
"""Tests for dimension weight constants."""
def test_weights_sum_to_one(self):
"""多维度权重之和为 1.0。"""
assert abs(VISUAL_WEIGHT + TEXT_WEIGHT + STRUCTURE_WEIGHT - 1.0) < 1e-9
def test_visual_weight_is_half(self):
"""视觉权重为 0.5。"""
assert VISUAL_WEIGHT == 0.5
def test_text_weight_is_quarter(self):
"""文案权重为 0.25。"""
assert TEXT_WEIGHT == 0.25
def test_structure_weight_is_quarter(self):
"""结构权重为 0.25。"""
assert STRUCTURE_WEIGHT == 0.25