#!/usr/bin/env python3 """Focused tests for the canonical consultation-domain registry.""" from __future__ import annotations import pytest from scripts.consultation_domain_registry import ( CANONICAL_DOMAINS, DEFAULT_THEMES, DOMAIN_ALIASES, normalize_domain, normalize_themes, ) def test_canonical_domain_registry_is_complete_and_ordered() -> None: assert CANONICAL_DOMAINS == ( "career", "marriage", "wealth", "health", "education", "migration", "family", "annual", "timing", "general", ) @pytest.mark.parametrize( ("value", "expected"), [ ("relationship", "marriage"), ("finance", "wealth"), ("money", "wealth"), ("health-pressure", "health"), ("health_pressure", "health"), ("home", "migration"), ("relocation", "migration"), ("varshaphala", "annual"), ], ) def test_normalize_domain_accepts_required_aliases(value: str, expected: str) -> None: assert normalize_domain(value) == expected def test_normalize_themes_accepts_canonical_and_aliases_deduplicated_in_input_order() -> None: assert normalize_themes( ["timing", "relationship", "marriage", "money", "wealth", "general", "health_pressure"] ) == ["timing", "marriage", "wealth", "general", "health"] def test_normalize_themes_keeps_product_defaults() -> None: assert normalize_themes(None) == list(DEFAULT_THEMES) assert normalize_themes("all") == list(DEFAULT_THEMES) assert normalize_themes([]) == list(DEFAULT_THEMES) @pytest.mark.parametrize("value", ["spirituality", "灵性", "unknown-domain"]) def test_normalize_themes_rejects_unknown_domains_instead_of_falling_back(value: str) -> None: with pytest.raises(ValueError, match="Unknown theme"): normalize_themes(["career", value]) def test_typescript_registry_matches_python_canonical_domains_and_aliases() -> None: import json import re from pathlib import Path source = (Path(__file__).resolve().parents[1] / "frontend/src/lib/consultation-domain-registry.ts").read_text() # 原值:TS 领域 id == Python CANONICAL_DOMAINS,TS 别名表 == DOMAIN_ALIASES。 # 新值:TS 的引擎领域(consultationEngineDomainIds)== CANONICAL_DOMAINS;TS 另有两个 # 只作用于数据卡的子领域 parents / children,引擎侧一律按 family 计算 # (strictWorkflowRoute / workflowThemes 都是 family)。别名比较按「引擎领域」进行: # Python 认识的每个别名,在 TS 里落到的领域的引擎路由必须等于 Python 的归一结果; # TS 独有的别名(父母、孩子等)只能落在 parents / children。 # 原因:TASK-consult-evidence-card-20260927 D2,家庭拆父母 / 子女,但不改 Python 引擎。 engine_match = re.search(r"consultationEngineDomainIds = \[([\s\S]*?)\] as const", source) assert engine_match is not None assert tuple(re.findall(r'"([^"\n]+)"', engine_match.group(1))) == CANONICAL_DOMAINS canonical_match = re.search(r"consultationDomainIds = \[([\s\S]*?)\] as const", source) assert canonical_match is not None typescript_domains = tuple(re.findall(r'"([^"\n]+)"', canonical_match.group(1))) card_only = ("parents", "children") assert tuple(domain for domain in typescript_domains if domain not in card_only) == CANONICAL_DOMAINS assert set(typescript_domains) - set(CANONICAL_DOMAINS) == set(card_only) definitions = re.findall( r'\{ id: "([a-z]+)"[^\n]*?aliases: (\[[^\]]*\]), strictWorkflowRoute: "([a-z]+)", workflowThemes: (\[[^\]]*\])', source, ) assert tuple(domain for domain, _aliases, _route, _themes in definitions) == typescript_domains engine_route: dict[str, str] = {} typescript_aliases: dict[str, str] = {} for domain, raw_aliases, route, raw_themes in definitions: engine_route[domain] = route if domain in card_only: assert route == "family" and json.loads(raw_themes) == ["family"], domain else: assert route == domain and json.loads(raw_themes) == [domain], domain for alias in json.loads(raw_aliases): if alias != domain: typescript_aliases[alias] = domain for alias, python_domain in DOMAIN_ALIASES.items(): assert alias in typescript_aliases or alias in typescript_domains, alias ts_domain = typescript_aliases.get(alias, alias) assert engine_route[ts_domain] == python_domain, alias for alias, ts_domain in typescript_aliases.items(): if alias not in DOMAIN_ALIASES: assert ts_domain in card_only, alias