From 827459e72ac8ad3bcd328188b5c39ee43b6da3ba Mon Sep 17 00:00:00 2001 From: bot-backend Date: Wed, 17 Jun 2026 21:22:04 +0300 Subject: [PATCH] test(site-finder): cover is_residential_zone raw_props/subcategory branch (#1353 follow-up) --- backend/tests/test_gate_verdict.py | 117 +++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/backend/tests/test_gate_verdict.py b/backend/tests/test_gate_verdict.py index f4073a9a..701fdaf6 100644 --- a/backend/tests/test_gate_verdict.py +++ b/backend/tests/test_gate_verdict.py @@ -242,3 +242,120 @@ def test_nspd_subcategory_path_backward_compat(): verdict = compute_gate_verdict(nspd_zoning, overlaps, [], {"available": True}) assert verdict["can_build_mkd"] is False assert any(b["code"] == "ZOUIT_OVERLAP_SUB17" for b in verdict["blockers"]) + + +# ── is_residential_zone: raw_props / subcategory branch (#1353) ─────────────── + + +def test_rawprops_subcategory_2_is_residential(): + """raw_props subcategory=2 (Жилые зоны МКД) → True (#1353).""" + assert is_residential_zone(None, None, {"subcategory": 2}) is True + + +def test_rawprops_subcategory_3_is_residential(): + """raw_props subcategory=3 (Смешанное использование, МКД допустим по ПЗЗ ЕКБ) → True (#1353).""" + assert is_residential_zone(None, None, {"subcategory": 3}) is True + + +def test_rawprops_subcategory_1_not_residential(): + """raw_props subcategory=1 (ИЖС/малоэтажка) → False: МКД не разрешён (#1353).""" + assert is_residential_zone(None, None, {"subcategory": 1}) is False + + +def test_rawprops_subcategory_4_not_residential(): + """raw_props subcategory=4 (Общественно-деловые) → False.""" + assert is_residential_zone(None, None, {"subcategory": 4}) is False + + +def test_rawprops_subcategory_as_string_int_coercion(): + """raw_props subcategory='2' (строка из JSON) → int() coercion → True (#1353).""" + assert is_residential_zone(None, None, {"subcategory": "2"}) is True + + +def test_rawprops_subcategory_absent_fallback_to_zone_code(): + """raw_props без subcategory, но zone_code ^Ж → True (tier-1 wins).""" + assert is_residential_zone("Ж-2", None, {"other_field": "value"}) is True + + +def test_rawprops_absent_zone_code_zh_still_residential(): + """raw_props=None, zone_code ^Ж → tier-1 detects residential (no crash).""" + assert is_residential_zone("Жс", None, None) is True + + +def test_rawprops_absent_no_zone_code_no_name_not_residential(): + """raw_props=None, zone_code=None, zone_name=None → False, no crash (#1353).""" + assert is_residential_zone(None, None, None) is False + + +def test_rawprops_empty_dict_no_crash(): + """raw_props={} (пустой) → subcategory=None → переходит на tier-3 fallback, без исключения.""" + assert is_residential_zone(None, None, {}) is False + + +def test_rawprops_subcategory_invalid_value_no_crash(): + """raw_props subcategory='N/A' (нечисловое) → ValueError поглощается → False.""" + assert is_residential_zone(None, None, {"subcategory": "N/A"}) is False + + +# ── compute_gate_verdict: nspd_zoning with raw_props (#1353) ───────────────── + + +def test_verdict_nspd_zoning_rawprops_sub2_can_build(): + """nspd_zoning с raw_props subcategory=2 (без zone_code Ж) → жилая → Можно.""" + nspd_zoning = { + "zone_code": "66:41-7.14", # кадастровый номер NSPD quarter-dump (не ^Ж) + "zone_name": None, + "raw_props": {"subcategory": 2}, + } + verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "ТП-5"}], {"available": True}) + assert verdict["can_build_mkd"] is True + assert verdict["verdict_label"] == "Можно" + assert verdict["blockers"] == [] + + +def test_verdict_nspd_zoning_rawprops_sub3_can_build(): + """nspd_zoning с raw_props subcategory=3 (смешанное, без zone_code Ж) → Можно.""" + nspd_zoning = { + "zone_code": "66:41-7.21", + "zone_name": None, + "raw_props": {"subcategory": 3}, + } + verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "ТП-1"}], {"available": True}) + assert verdict["can_build_mkd"] is True + assert verdict["verdict_label"] == "Можно" + assert verdict["blockers"] == [] + + +def test_verdict_nspd_zoning_rawprops_sub1_blocks(): + """nspd_zoning с raw_props subcategory=1 (ИЖС) → PZZ_NOT_RESIDENTIAL → Нельзя (#1353).""" + nspd_zoning = { + "zone_code": "66:41-7.05", + "zone_name": "Малоэтажная застройка", + "raw_props": {"subcategory": 1}, + } + verdict = compute_gate_verdict(nspd_zoning, [], [], {"available": True}) + assert verdict["can_build_mkd"] is False + assert verdict["verdict_label"] == "Нельзя" + assert any(b["code"] == "PZZ_NOT_RESIDENTIAL" for b in verdict["blockers"]) + + +def test_verdict_nspd_zoning_no_rawprops_zh_zone_code_can_build(): + """raw_props отсутствует, zone_code=^Ж → tier-1 → Можно (регрессия: no crash).""" + nspd_zoning = {"zone_code": "Ж-1", "zone_name": None} + verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "КНС-1"}], {"available": True}) + assert verdict["can_build_mkd"] is True + assert verdict["verdict_label"] == "Можно" + assert verdict["blockers"] == [] + + +def test_verdict_nspd_zoning_no_rawprops_no_zone_blocks(): + """raw_props=None, zone_code=кадастровый (не ^Ж), zone_name=None → PZZ_NOT_RESIDENTIAL.""" + nspd_zoning = { + "zone_code": "66:41-7.99", + "zone_name": None, + "raw_props": None, + } + verdict = compute_gate_verdict(nspd_zoning, [], [], {"available": True}) + assert verdict["can_build_mkd"] is False + assert verdict["verdict_label"] == "Нельзя" + assert any(b["code"] == "PZZ_NOT_RESIDENTIAL" for b in verdict["blockers"]) -- 2.45.3