fix: fail-loud on disk-corrupt pairs and preserve buffer on reject
read_accepted now distinguishes size==1 dangling orphans (warn+drop) from structural corruption / role duplication (raise), and runs explicit binding checks (video_id/task_type/flip_axis) that survive python -O. add() validates before evicting the buffered partner so pending_orphans can recover it.
This commit is contained in:
@@ -120,8 +120,8 @@ def test_dangling_original_stays_in_pending() -> None:
|
||||
assert orphans[0].question_id == original.question_id
|
||||
|
||||
|
||||
def test_inconsistent_pair_binding_rejected() -> None:
|
||||
"""同 pair_id 两成员 flip_axis 不一致 → 拒绝(unit_hash 语义代偿)。"""
|
||||
def test_inconsistent_pair_binding_rejected_and_partner_recoverable() -> None:
|
||||
"""两成员 flip_axis 不一致 → 拒绝;且校验失败后首成员仍留 pending 可取回。"""
|
||||
buf = PairPendingBuffer()
|
||||
original, _ = _make_pair(flip_axis="before_after")
|
||||
_, bad_mirror = _make_pair(flip_axis="left_right")
|
||||
@@ -130,6 +130,11 @@ def test_inconsistent_pair_binding_rejected() -> None:
|
||||
with pytest.raises(ValueError, match="绑定不一致"):
|
||||
buf.add(bad_mirror)
|
||||
|
||||
# Important: 先校验后删除——raise 后首成员未被驱逐,调用方仍能检测到悬挂项
|
||||
orphans = buf.pending_orphans()
|
||||
assert len(orphans) == 1
|
||||
assert orphans[0].question_id == original.question_id
|
||||
|
||||
|
||||
def test_same_role_pair_rejected() -> None:
|
||||
"""同 pair_id 两成员角色相同(两个 original)→ fail-fast 拒绝。"""
|
||||
@@ -190,6 +195,45 @@ def test_read_drops_disk_dangling_orphan(tmp_path, caplog) -> None:
|
||||
assert loaded[0].questions[0].question_id == single.question_id
|
||||
|
||||
|
||||
def test_read_raises_on_disk_corrupt_duplicate_role(tmp_path) -> None:
|
||||
"""磁盘上同 pair_id 出现两个 pair_original(角色重复)→ 数据损坏,fail-fast raise。"""
|
||||
o1, _ = _make_pair()
|
||||
o2, _ = _make_pair()
|
||||
# 同 pair_id、两条都是 pair_original(size==2 但角色非法)→ 结构损坏
|
||||
dup = GeneratedQuestion(
|
||||
question_id="p1_o2",
|
||||
video_id=o2.video_id,
|
||||
task_type=o2.task_type,
|
||||
question="重复 original",
|
||||
options=o2.options,
|
||||
answer=o2.answer,
|
||||
source_nodes=o2.source_nodes,
|
||||
difficulty=o2.difficulty,
|
||||
pair_id=o1.pair_id,
|
||||
question_role="pair_original",
|
||||
flip_axis=o1.flip_axis,
|
||||
)
|
||||
records = [_q_to_dict(o1), _q_to_dict(dup)]
|
||||
out = tmp_path / "corrupt.json"
|
||||
out.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="结构损坏"):
|
||||
read_accepted(out)
|
||||
|
||||
|
||||
def test_read_raises_on_disk_binding_mismatch(tmp_path) -> None:
|
||||
"""磁盘上合法成对但 flip_axis 绑定不一致 → 显式 raise(不依赖会被 -O 剥除的 assert)。"""
|
||||
original, _ = _make_pair(pair_id="p1", flip_axis="before_after")
|
||||
_, mirror = _make_pair(pair_id="p1", flip_axis="left_right")
|
||||
# 直接写盘绕过 write_accepted 的 from_pair 组装,模拟外部篡改的不一致成对
|
||||
records = [_q_to_dict(original), _q_to_dict(mirror)]
|
||||
out = tmp_path / "mismatch.json"
|
||||
out.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="绑定不一致"):
|
||||
read_accepted(out)
|
||||
|
||||
|
||||
def test_write_accepted_rejects_malformed_unit(tmp_path) -> None:
|
||||
"""validate_units 闸门:结构非法的 pair 单元(size≠2)落盘前 fail-fast。"""
|
||||
from core.types import QuestionUnit
|
||||
|
||||
Reference in New Issue
Block a user