Files
ZNJJ-api-server/test/services/test_fields_registry.py
T
Eric Wang 7f52f1c4d0 feat(services): assignment 6 full - form/business key mapping and stream write-back
- fields_registry: FORM_TO_BUSINESS_KEY bidirectional mapping between
  the graph's six form keys (has_injury...) and the acd business keys
  (ywrysw...); bjrjs/ywmtc have no form source (set_info only);
  business_patch_from_form maps graph diffs to whitelisted business keys
  and filters internal/unknown keys
- app_service: chat injection now maps repository business keys back to
  form keys; both complete() and stream() write back field diffs
  (FormUpdate events captured in stream, closing the skeleton gap);
  form keys never leak into the business repository
- docs/assignment6-compat.md: mapping table documented, stream write-back
  backlog item resolved
- tests: mapping coverage, write-back mapping end-to-end, reverse
  mapping injection (bjrjs/ywmtc excluded), stream FormUpdate write-back

Verified: 205 unit tests
2026-10-22 21:07:01 +08:00

122 lines
4.8 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) == 24
assert len(GROUP_KEYS[FieldGroup.ACD]) == 8
assert len(GROUP_KEYS[FieldGroup.HUMAN1]) == 8
assert len(GROUP_KEYS[FieldGroup.HUMAN2]) == 8
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_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