12f20493c1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
157 lines
5.2 KiB
Python
157 lines
5.2 KiB
Python
"""质量校验模块单元测试。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from app.tree.index import (
|
|
IndexMeta,
|
|
L1Card,
|
|
L1Node,
|
|
L2Card,
|
|
L2Node,
|
|
L3Card,
|
|
L3Node,
|
|
TreeIndex,
|
|
)
|
|
from app.tree.verify import VerifyStats, _normalize, fuzzy_match, verify_tree
|
|
|
|
|
|
class TestNormalize:
|
|
def test_lowercase(self):
|
|
assert _normalize("Hello World") == "hello world"
|
|
|
|
def test_strip_punctuation(self):
|
|
assert _normalize("Hello, World!") == "hello world"
|
|
|
|
def test_empty(self):
|
|
assert _normalize("") == ""
|
|
|
|
|
|
class TestFuzzyMatch:
|
|
def test_exact_match(self):
|
|
assert fuzzy_match("hello", "hello world")
|
|
|
|
def test_case_insensitive(self):
|
|
assert fuzzy_match("Hello", "say hello world")
|
|
|
|
def test_no_match(self):
|
|
assert not fuzzy_match("xyz", "hello world")
|
|
|
|
def test_none_entity(self):
|
|
assert not fuzzy_match(None, "hello")
|
|
|
|
def test_none_corpus(self):
|
|
assert not fuzzy_match("hello", None)
|
|
|
|
|
|
class TestVerifyTree:
|
|
def _make_tree(self):
|
|
"""构造一棵树,L2 有混合实体(有出处/无出处)。"""
|
|
l3_0 = L3Node(
|
|
id="l1_0_l2_0_l3_0",
|
|
card=L3Card(
|
|
frame_summary="一个运动员在跑步",
|
|
visible_entities=["运动员", "跑道"],
|
|
ongoing_actions=["跑步"],
|
|
visible_text=["Nike", "2024"],
|
|
spatial_layout="居中",
|
|
visual_attributes={},
|
|
),
|
|
timestamp=1.0,
|
|
subtitle="the athlete is running fast",
|
|
)
|
|
l3_1 = L3Node(
|
|
id="l1_0_l2_0_l3_1",
|
|
card=L3Card(
|
|
frame_summary="观众在欢呼",
|
|
visible_entities=["观众"],
|
|
ongoing_actions=["欢呼"],
|
|
visible_text=["Stadium"],
|
|
spatial_layout="广角",
|
|
visual_attributes={},
|
|
),
|
|
timestamp=3.0,
|
|
)
|
|
l2 = L2Node(
|
|
id="l1_0_l2_0",
|
|
card=L2Card(
|
|
event_description="比赛片段",
|
|
entities=["运动员", "裁判", "幻觉实体"], # "裁判"和"幻觉实体"无 L3 出处
|
|
actions=["跑步"],
|
|
action_subjects=["运动员"],
|
|
visible_text=["Nike", "不存在的文字"], # "不存在的文字"无 L3 出处
|
|
spatial_relations="",
|
|
state_changes=None,
|
|
),
|
|
time_range=(0.0, 10.0),
|
|
children=[l3_0, l3_1],
|
|
)
|
|
l1 = L1Node(
|
|
id="l1_0",
|
|
card=L1Card(
|
|
scene_summary="体育比赛",
|
|
main_setting="体育场",
|
|
key_entities=["运动员", "不存在的人"], # "不存在的人"无出处
|
|
main_actions=["比赛"],
|
|
topic_keywords=["体育"],
|
|
visible_text=["Nike", "Ghost"], # "Ghost"无出处
|
|
temporal_flow="从左到右",
|
|
),
|
|
time_range=(0.0, 10.0),
|
|
children=[l2],
|
|
)
|
|
return TreeIndex(metadata=IndexMeta("/test.mp4", "video"), roots=[l1])
|
|
|
|
def test_removes_ungrounded_l2_entities(self):
|
|
index = self._make_tree()
|
|
stats = verify_tree(index)
|
|
l2 = index.roots[0].children[0]
|
|
assert "运动员" in l2.card.entities
|
|
assert "幻觉实体" not in l2.card.entities
|
|
assert stats.l2_entities_removed >= 1
|
|
|
|
def test_removes_ungrounded_l2_visible_text(self):
|
|
index = self._make_tree()
|
|
stats = verify_tree(index)
|
|
l2 = index.roots[0].children[0]
|
|
assert "Nike" in l2.card.visible_text
|
|
assert "不存在的文字" not in l2.card.visible_text
|
|
assert stats.l2_visible_text_removed >= 1
|
|
|
|
def test_removes_ungrounded_l1_visible_text(self):
|
|
index = self._make_tree()
|
|
stats = verify_tree(index)
|
|
l1 = index.roots[0]
|
|
assert "Nike" in l1.card.visible_text
|
|
assert "Ghost" not in l1.card.visible_text
|
|
assert stats.l1_visible_text_removed >= 1
|
|
|
|
def test_removes_ungrounded_l1_key_entities(self):
|
|
index = self._make_tree()
|
|
stats = verify_tree(index)
|
|
l1 = index.roots[0]
|
|
assert "运动员" in l1.card.key_entities
|
|
assert "不存在的人" not in l1.card.key_entities
|
|
assert stats.l1_key_entities_removed >= 1
|
|
|
|
def test_preserves_grounded_entities(self):
|
|
index = self._make_tree()
|
|
verify_tree(index)
|
|
l2 = index.roots[0].children[0]
|
|
assert "运动员" in l2.card.entities
|
|
|
|
def test_returns_verify_stats(self):
|
|
index = self._make_tree()
|
|
stats = verify_tree(index)
|
|
assert isinstance(stats, VerifyStats)
|
|
total_kept = stats.l2_entities_kept + stats.l1_key_entities_kept
|
|
assert total_kept > 0
|
|
|
|
def test_frozen_card_replaced(self):
|
|
"""验证 Card 被替换为新实例(frozen dataclass 不能原地修改)。"""
|
|
index = self._make_tree()
|
|
old_l2_card = index.roots[0].children[0].card
|
|
verify_tree(index)
|
|
new_l2_card = index.roots[0].children[0].card
|
|
# Card should be a different object if anything was removed
|
|
assert old_l2_card is not new_l2_card
|