Files

113 lines
4.3 KiB
Python

"""深度诊断色板规则映射与降级模板的纯函数单测(无 IO、无 LLM)。
红线:色板是规则映射常量——12 季型全覆盖、三档色数达标、hex 格式合法;
降级模板必须自带「通过三重校验」的性质(fail-close 的最后一道出口)。
"""
from __future__ import annotations
import re
from packages.report.full_copy import (
TWELVE_SEASONS,
FullReportCopy,
build_full_result,
palette_for,
render_fallback_full_copy,
resolve_season,
)
from packages.report.validation import validate_copy
_HEX_RE = re.compile(r"^#[0-9A-Fa-f]{6}$")
def test_all_twelve_seasons_have_complete_palettes() -> None:
assert len(TWELVE_SEASONS) == 12
for season in TWELVE_SEASONS:
palette = palette_for(season)
assert len(palette["best"]) >= 12, f"{season} 最佳色不足 12 色"
assert len(palette["neutral"]) >= 6, f"{season} 中性色不足 6 色"
assert len(palette["avoid"]) >= 6, f"{season} 避雷色不足 6 色"
for role in ("best", "neutral", "avoid"):
for color in palette[role]:
assert color["name"], f"{season}/{role} 缺色名"
assert _HEX_RE.match(color["hex"]), f"{season}/{role} 色值非法:{color['hex']}"
def test_unknown_season_gets_empty_palette() -> None:
assert palette_for("不存在的季型") == {"best": [], "neutral": [], "avoid": []}
def test_resolve_season_prefers_detailed_value() -> None:
assert resolve_season({"season_detailed": "柔夏", "season_code": "summer"}) == "柔夏"
def test_resolve_season_falls_back_to_season_code() -> None:
assert resolve_season({"season_code": "spring"}) == "暖春"
assert resolve_season({"season_code": "winter"}) == "冷冬"
assert resolve_season({"season_detailed": "编造季型", "season_code": "autumn"}) == "暖秋"
def test_resolve_season_returns_none_without_any_input() -> None:
assert resolve_season({}) is None
assert resolve_season({"season_code": "unknown"}) is None
def _minimal_dimensions() -> list[dict[str, object]]:
return [
{"key": "season_code", "value": "summer", "status": "ok"},
{"key": "season_detailed", "value": "柔夏", "status": "ok"},
]
def test_fallback_full_copy_passes_validation() -> None:
"""降级模板自带合规性:含「柔夏型」等十二季文案也不过三重校验的误杀线。"""
dimensions = _minimal_dimensions()
fallback = render_fallback_full_copy(
dimensions, season_detailed="柔夏", palette=palette_for("柔夏")
)
violations = validate_copy(fallback.as_report_copy(), dimensions, rule_hits=[])
assert violations == []
def test_fallback_full_copy_covers_all_scenes_and_four_weeks() -> None:
fallback = render_fallback_full_copy(
_minimal_dimensions(), season_detailed="柔夏", palette=palette_for("柔夏")
)
assert [s.key for s in fallback.scenes] == ["commute", "weekend", "formal", "date"]
assert all(len(scene.outfits) >= 1 for scene in fallback.scenes)
assert [w.week for w in fallback.plan30] == [1, 2, 3, 4]
assert all(3 <= len(week.tasks) <= 5 for week in fallback.plan30)
# 兜底文案不得出现色值(hex 只能走 full.palette 结构化字段)。
assert "#" not in fallback.as_report_copy().full_text()
def test_build_full_result_shape() -> None:
copy = render_fallback_full_copy(
_minimal_dimensions(), season_detailed="柔夏", palette=palette_for("柔夏")
)
result = build_full_result(
copy,
season_detailed="柔夏",
season_code="summer",
palette=palette_for("柔夏"),
)
assert result["season"]["name_cn"] == "柔夏型"
assert result["season"]["analysis"] == copy.season_analysis
assert len(result["palette"]["best"]) >= 12
assert result["scenes"][0]["outfits"][0]["points"]
assert result["plan30"][3]["week"] == 4
def test_as_report_copy_folds_all_free_text_into_scan_scope() -> None:
"""校验视图必须覆盖季型解析/场景/计划的全部自由文本,不是只查 summary。"""
copy = FullReportCopy(
summary="总评",
sections=[],
season_analysis="独一无二的季型解析",
scenes=[],
plan30=[],
)
violations = validate_copy(copy.as_report_copy(), [], rule_hits=[])
assert any("独一无二" in v for v in violations)