test: pin the reason behind rejected document packages

The outer exception boundary also absorbs accidental TypeError and KeyError, so asserting only the exception class could not tell an explicit validation from a lucky failure. Assert the reason for nested blocks and guard every rejection against an accidental cause.
This commit is contained in:
2026-10-22 00:41:51 -04:00
parent 6691e52195
commit d80feafb93
+28 -1
View File
@@ -37,9 +37,20 @@ def _parse(pages=None, **kwargs):
return parse_document_package(_package(pages, **kwargs)) return parse_document_package(_package(pages, **kwargs))
def _assert_rejected_on_purpose(exc: ResultInvalidError) -> None:
"""拒绝必须出自显式校验,不能是 Python 自己绊倒后被外层边界吸收。
外层边界会把 TypeError/KeyError 也归为 ResultInvalidError,所以只断言异常类型,
「显式校验」与「碰巧在别处失败」无法区分(变异检查证实过: 删掉校验后测试仍绿)。
原因链里出现 TypeError/KeyError 即说明有校验缺口。
"""
assert not isinstance(exc.__cause__, TypeError | KeyError), repr(exc.__cause__)
def _invalid(pages=None, **kwargs): def _invalid(pages=None, **kwargs):
with pytest.raises(ResultInvalidError): with pytest.raises(ResultInvalidError) as ei:
_parse(pages, **kwargs) _parse(pages, **kwargs)
_assert_rejected_on_purpose(ei.value)
class TestHappyPath: class TestHappyPath:
@@ -195,6 +206,22 @@ class TestTableAndImage:
assert elements[1].bbox == (1.0, 2.0, 3.0, 4.0) assert elements[1].bbox == (1.0, 2.0, 3.0, 4.0)
assert all(e.page_index == 0 for e in elements) assert all(e.page_index == 0 for e in elements)
@pytest.mark.parametrize(
("blocks", "reason"),
[
pytest.param("not-a-list", "blocks 无效", id="blocks-not-list"),
pytest.param(["not-a-dict"], "块结构无效", id="sub-block-not-dict"),
pytest.param([{"bbox": list(_BBOX)}], "块缺少 type", id="sub-block-without-type"),
pytest.param([{"type": " ", "bbox": list(_BBOX)}], "块缺少 type", id="blank-type"),
],
)
def test_malformed_nested_blocks_are_rejected_with_their_own_reason(self, blocks, reason):
"""外层边界会把 TypeError 等也归为 ResultInvalid,所以必须断言**原因**,否则"显式校验"
与"碰巧在别处失败"无法区分(变异检查发现仅断言类型时删掉校验测试仍绿)。"""
block = {"type": "table", "bbox": list(_BBOX), "blocks": blocks}
with pytest.raises(ResultInvalidError, match=reason):
_parse([_page([block])])
def test_empty_non_body_sub_block_is_not_emitted(self): def test_empty_non_body_sub_block_is_not_emitted(self):
caption = _body("table_caption", [_span("")]) caption = _body("table_caption", [_span("")])
elements, _, _ = _parse([_page([_table_block(extra_subs=[caption])])]) elements, _, _ = _parse([_page([_table_block(extra_subs=[caption])])])