Files
ZNJJ-api-server/test/services/test_fields_registry.py
T
Eric Wang 20e151b097 feat(services): register workflow-native driver keys in field registry
Workflow reconciliation (see previous commit) showed the 20261022
state variable really stores person identity under driver{i}ID /
driver{i}Phonenumber (x2), while the API get_info group keys
(sfzmhm{i}/sjhm{i}) have zero references in any workflow version -
the legacy group read always returned empty for workflow-produced data.

- registry: +4 keys (driver1/2ID, driver1/2Phonenumber) in
  human1/human2 groups, SENSITIVE, documented as synonymous with
  sfzmhm{i}/sjhm{i}; 24 -> 28 specs
- driver{i}License deliberately NOT registered: the sandbox reads
  state.hphm{i} and only renames the output for confirmation prompts -
  it is not a state key, set_info rejects it as unknown (workflow
  never consumes it)
- intentional diff documented: acdhuman1/2 group output now includes
  the driver keys (legacy FastGPT output did not) - fixes the legacy
  'workflow person data unreadable by group' gap; synonym merging
  (driver1ID vs sfzmhm1) deferred pending caller confirmation
- tests: registry size 28, driver keys present with sensitive level,
  License alias excluded

Verified: 230 unit tests
2026-10-22 23:40:27 +08:00

133 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.
"""作业 6:字段注册表测试(分组完整性、类型/敏感级别、外部表示)。"""
import pytest
from src.services.fields_registry import (
FIELD_REGISTRY,
GROUP_KEYS,
INTERNAL_FIELD_KEYS,
FORM_TO_BUSINESS_KEY,
BUSINESS_TO_FORM_KEY,
FieldGroup,
SensitiveLevel,
business_patch_from_form,
parse_field_value,
to_external_string,
)
def test_registry_covers_all_three_groups():
"""注册表完整覆盖 acd/human1/human2 三个分组(作业 6-3)。"""
assert set(GROUP_KEYS) == set(FieldGroup)
assert len(FIELD_REGISTRY) == 28
assert len(GROUP_KEYS[FieldGroup.ACD]) == 8
assert len(GROUP_KEYS[FieldGroup.HUMAN1]) == 10
assert len(GROUP_KEYS[FieldGroup.HUMAN2]) == 10
def test_person_fields_include_full_set():
"""人员字段必须包含 sfzmwh/sjwh/csbw,不能只迁姓名和号码。"""
for index in (1, 2):
group_keys = set(GROUP_KEYS[FieldGroup.HUMAN1 if index == 1 else FieldGroup.HUMAN2])
for suffix in ("xm", "hpzl", "hphm", "sfzmhm", "sfzmwh", "sjhm", "sjwh", "csbw"):
assert f"{suffix}{index}" in group_keys
def test_workflow_native_driver_keys_registered():
"""workflow 20261022 state 原生人员键(对账发现)已登记,
与 API 契约键同义并存;driver1License 是 sandbox 输出别名而非
state 键,不登记。"""
for index in (1, 2):
assert f"driver{index}ID" in FIELD_REGISTRY
assert f"driver{index}Phonenumber" in FIELD_REGISTRY
assert FIELD_REGISTRY[f"driver{index}ID"].sensitive is SensitiveLevel.SENSITIVE
assert f"driver{index}License" not in FIELD_REGISTRY
def test_registry_metadata_recorded():
"""每个字段都记录类型、可写性、敏感级别(作业 6-4)。"""
for spec in FIELD_REGISTRY.values():
assert spec.value_type in (str, bool, int)
assert spec.writable is True
assert isinstance(spec.sensitive, SensitiveLevel)
assert spec.description
def test_sensitive_levels_assigned():
"""证件号/手机号 sensitive,姓名 personal,其余 public。"""
assert FIELD_REGISTRY["sfzmhm1"].sensitive is SensitiveLevel.SENSITIVE
assert FIELD_REGISTRY["sjhm2"].sensitive is SensitiveLevel.SENSITIVE
assert FIELD_REGISTRY["xm1"].sensitive is SensitiveLevel.PERSONAL
assert FIELD_REGISTRY["hphm1"].sensitive is SensitiveLevel.PUBLIC
@pytest.mark.parametrize("raw,expected", [
("1", True), ("true", True), ("是", True),
("0", False), ("false", False), ("否", False),
])
def test_parse_bool_explicit(raw, expected):
assert parse_field_value(FIELD_REGISTRY["ywrysw"], raw) is expected
@pytest.mark.parametrize("bad", ["", "maybe", "2", "有吧"])
def test_parse_bool_rejects_ambiguous(bad):
"""模糊值必须拒绝——保留 空/False/0/未知 的区分能力。"""
with pytest.raises(ValueError):
parse_field_value(FIELD_REGISTRY["ywrysw"], bad)
def test_parse_int_and_str():
assert parse_field_value(FIELD_REGISTRY["jdcsl"], "2") == 2
assert parse_field_value(FIELD_REGISTRY["jdcsl"], "0") == 0
with pytest.raises(ValueError):
parse_field_value(FIELD_REGISTRY["jdcsl"], "两辆")
assert parse_field_value(FIELD_REGISTRY["hphm1"], "苏B12345") == "苏B12345"
def test_external_string_representation():
"""对外序列化:True->1,False->0,None->空串;内部仍区分 False 与 0。"""
assert to_external_string(True) == "1"
assert to_external_string(False) == "0"
assert to_external_string(0) == "0"
assert to_external_string(None) == ""
assert to_external_string("苏B12345") == "苏B12345"
# 内部存储不混淆:False 是 bool、0 是 int
assert parse_field_value(FIELD_REGISTRY["ywrysw"], "0") is False
assert parse_field_value(FIELD_REGISTRY["jdcsl"], "0") == 0
assert isinstance(parse_field_value(FIELD_REGISTRY["jdcsl"], "0"), int)
def test_internal_field_protection_listed():
"""图内部键登记在保护清单中,set_info 白名单不会收录它们。"""
for key in INTERNAL_FIELD_KEYS:
assert key not in FIELD_REGISTRY
def test_form_business_key_mapping_covers_form_fields():
"""图表单六键全部映射到 acd 业务键(作业 6 建立的映射)。"""
assert set(FORM_TO_BUSINESS_KEY) == {
"has_injury", "has_non_motor_vehicle_hit", "is_on_scene",
"vehicle_count", "accident_time", "accident_cause",
}
for business_key in FORM_TO_BUSINESS_KEY.values():
assert business_key in FIELD_REGISTRY
assert FIELD_REGISTRY[business_key].group is FieldGroup.ACD
def test_business_patch_from_form_maps_and_filters():
"""表单键映射、人员键原样、未知键过滤。"""
patch = business_patch_from_form({
"has_injury": False, # -> ywrysw
"vehicle_count": 2, # -> jdcsl
"xm2": "李四", # 人员键原样
"stage_code": "1002", # 内部键过滤
"no_such_form_key": "x", # 未知键过滤
})
assert patch == {"ywrysw": False, "jdcsl": 2, "xm2": "李四"}
def test_business_keys_without_form_source_not_injected():
"""bjrjs/ywmtc 无表单来源:不进入注入映射(只能 set_info 直写)。"""
assert "bjrjs" not in BUSINESS_TO_FORM_KEY
assert "ywmtc" not in BUSINESS_TO_FORM_KEY