Files
ZQYY.FQ-ETL/tests/unit/test_audit_inventory_render.py

166 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
属性测试 — 清单渲染完整性与分类分组
Feature: repo-audit
- Property 2: 清单渲染完整性
- Property 8: 清单按分类分组
Validates: Requirements 1.4, 1.10
"""
from __future__ import annotations
import string
from hypothesis import given, settings
from hypothesis import strategies as st
from scripts.audit import Category, Disposition, InventoryItem
from scripts.audit.inventory_analyzer import render_inventory_report
# ---------------------------------------------------------------------------
# 生成器策略
# ---------------------------------------------------------------------------
_PATH_CHARS = string.ascii_letters + string.digits + "_-."
_path_segment = st.text(
alphabet=_PATH_CHARS,
min_size=1,
max_size=15,
)
# 随机相对路径1~3 层)
_rel_path = st.lists(
_path_segment,
min_size=1,
max_size=3,
).map(lambda parts: "/".join(parts))
# 随机非空描述(不含管道符和换行符,避免破坏 Markdown 表格解析)
_description = st.text(
alphabet=st.characters(
whitelist_categories=("L", "N", "P", "S", "Z"),
blacklist_characters="|\n\r",
),
min_size=1,
max_size=40,
)
def _inventory_item_strategy() -> st.SearchStrategy[InventoryItem]:
"""生成随机 InventoryItem 的 hypothesis 策略。"""
return st.builds(
InventoryItem,
rel_path=_rel_path,
category=st.sampled_from(list(Category)),
disposition=st.sampled_from(list(Disposition)),
description=_description,
)
# 生成 0~20 个 InventoryItem 的列表
_inventory_list = st.lists(
_inventory_item_strategy(),
min_size=0,
max_size=20,
)
# ---------------------------------------------------------------------------
# Property 2: 清单渲染完整性
# ---------------------------------------------------------------------------
@given(items=_inventory_list)
@settings(max_examples=100)
def test_render_inventory_completeness(items: list[InventoryItem]) -> None:
"""Property 2: 清单渲染完整性
Feature: repo-audit, Property 2: 清单渲染完整性
Validates: Requirements 1.4
对于任意 InventoryItem 列表render_inventory_report 生成的 Markdown 中,
每个条目的 rel_path、category.value、disposition.value 和 description
四个字段都应出现在输出文本中。
"""
report = render_inventory_report(items, "/tmp/test-repo")
for item in items:
# rel_path 出现在表格行中
assert item.rel_path in report, (
f"rel_path '{item.rel_path}' 未出现在报告中"
)
# category.value 出现在分组标题中
assert item.category.value in report, (
f"category '{item.category.value}' 未出现在报告中"
)
# disposition.value 出现在表格行中
assert item.disposition.value in report, (
f"disposition '{item.disposition.value}' 未出现在报告中"
)
# description 出现在表格行中
assert item.description in report, (
f"description '{item.description}' 未出现在报告中"
)
# ---------------------------------------------------------------------------
# Property 8: 清单按分类分组
# ---------------------------------------------------------------------------
@given(items=_inventory_list)
@settings(max_examples=100)
def test_render_inventory_grouped_by_category(items: list[InventoryItem]) -> None:
"""Property 8: 清单按分类分组
Feature: repo-audit, Property 8: 清单按分类分组
Validates: Requirements 1.10
对于任意 InventoryItem 列表render_inventory_report 生成的 Markdown 中,
同一 Category 的条目应连续出现(不应被其他 Category 的条目打断)。
"""
report = render_inventory_report(items, "/tmp/test-repo")
if not items:
return # 空列表无需验证
# 从报告中按行提取条目对应的 category 顺序
# 表格行格式: | `{rel_path}` | {disposition} | {description} |
# 分组标题格式: ## {category.value}
lines = report.split("\n")
# 收集每个分组标题下的条目,按出现顺序记录 category
categories_in_order: list[Category] = []
current_category: Category | None = None
# 建立 category.value -> Category 的映射
value_to_cat = {c.value: c for c in Category}
for line in lines:
stripped = line.strip()
# 检测分组标题 "## {category.value}"
if stripped.startswith("## ") and stripped[3:] in value_to_cat:
current_category = value_to_cat[stripped[3:]]
continue
# 检测表格数据行(跳过表头和分隔行)
if (
current_category is not None
and stripped.startswith("| `")
and not stripped.startswith("| 相对路径")
and not stripped.startswith("|---")
):
categories_in_order.append(current_category)
# 验证同一 Category 的条目连续出现
seen: set[Category] = set()
prev: Category | None = None
for cat in categories_in_order:
if cat != prev:
assert cat not in seen, (
f"Category '{cat.value}' 的条目不连续——"
f"在其他分类条目之后再次出现"
)
seen.add(cat)
prev = cat