"""question_units helper 单元测试:组装/展开/校验/单元正确性。 覆盖 pair 契约唯一入口的核心行为: - build_units 聚合孪生对、保留 single; - 孤儿 pair、角色缺失/重复直接报错(fail-fast); - flatten_units 无损展开; - unit_correctness 对 pair 走 AND、缺题走 KeyError(防静默)。 """ import pytest from app.harness.question_units import ( build_units, flatten_units, unit_correctness, validate_units, ) from core.types import GeneratedQuestion def _q(qid: str, role: str = "single", pid: str | None = None) -> GeneratedQuestion: """构造测试题目;pid 非空时视为孪生对成员。""" return GeneratedQuestion( question_id=qid, video_id="v", task_type="AR", question="?", options=("A. a", "B. b", "C. c", "D. d"), answer="A", source_nodes=("n",), difficulty="hard", pair_id=pid, question_role=role, unit_id=pid or qid, flip_axis="ax" if pid else None, ) def test_build_units_groups_pair_and_keeps_single(): qs = [_q("s1"), _q("po", "pair_original", "p"), _q("pm", "pair_mirror", "p")] units = build_units(qs) kinds = sorted(u.kind for u in units) assert kinds == ["pair", "single"] def test_validate_units_rejects_orphan_pair(): with pytest.raises(ValueError): validate_units(build_units([_q("po", "pair_original", "p")])) # 只 1 条 def test_flatten_roundtrip(): qs = [_q("po", "pair_original", "p"), _q("pm", "pair_mirror", "p")] assert {q.question_id for q in flatten_units(build_units(qs))} == {"po", "pm"} def test_unit_correctness_bidirectional_and(): qs = [_q("po", "pair_original", "p"), _q("pm", "pair_mirror", "p")] u = build_units(qs)[0] assert unit_correctness(u, {"po": True, "pm": True}) is True assert unit_correctness(u, {"po": True, "pm": False}) is False # AND def test_build_units_rejects_duplicate_role(): qs = [_q("po1", "pair_original", "p"), _q("po2", "pair_original", "p")] with pytest.raises(ValueError): build_units(qs) def test_unit_correctness_missing_question_raises_keyerror(): qs = [_q("po", "pair_original", "p"), _q("pm", "pair_mirror", "p")] u = build_units(qs)[0] with pytest.raises(KeyError): unit_correctness(u, {"po": True}) # 缺 pm → KeyError 防静默