"""Unit tests для noise_loader — OSM-источники шума + инж.сети (#44). Mock-based / pure — без Overpass и БД. Покрывает: - расширенный набор инж.-сетевых категорий (#44: gas/water/sewerage/transformer/ heat_substation) - _build_overpass_query: node / way / nwr варианты - el_type dispatch для point-like utility (nwr) vs линейных (way) """ from __future__ import annotations from app.services.site_finder.noise_loader import ( _NOISE_QUERIES, _UTILITY_POINT_VALUES, _build_overpass_query, _way_to_linestring_wkt, ) # ── расширенные инж.-сетевые категории (#44) ───────────────────────────────── def _road_classes() -> set[str | None]: return {road_class for _k, _v, src, road_class in _NOISE_QUERIES if src == "utility"} def test_new_utility_categories_present() -> None: """#44: добавлены недостающие инж.сети — газ/вода/канализация/трансформатор/ЦТП.""" classes = _road_classes() for expected in ( "gas_pipeline", "water_main", "sewerage", "transformer", "heat_substation", ): assert expected in classes, f"road_class {expected!r} должен быть в _NOISE_QUERIES" def test_existing_utility_categories_retained() -> None: """Старые категории не потеряны при расширении (regression guard).""" classes = _road_classes() for kept in ("power_line", "pipeline", "water_works", "wastewater_plant", "substation"): assert kept in classes def test_pipeline_queries_use_pipeline_key() -> None: """pipeline=gas/water/sewerage маппится на OSM tag `pipeline`, не man_made.""" pipeline_entries = [(k, v) for k, v, src, _rc in _NOISE_QUERIES if k == "pipeline"] values = {v for _k, v in pipeline_entries} assert values == {"gas", "water", "sewerage"} def test_utility_point_values_cover_new_nodes() -> None: """Точечные/площадные инж.объекты помечены для nwr-запроса.""" for v in ("substation", "transformer", "heat_substation", "water_works"): assert v in _UTILITY_POINT_VALUES # ── _build_overpass_query ───────────────────────────────────────────────────── def test_build_query_node() -> None: q = _build_overpass_query("aeroway", "aerodrome", "node") assert q.startswith("[out:json][timeout:30];") assert 'node["aeroway"="aerodrome"]' in q assert "out body;" in q def test_build_query_way_uses_out_geom() -> None: """way → out geom (нужны координаты узлов для LineString).""" q = _build_overpass_query("highway", "motorway", "way") assert 'way["highway"="motorway"]' in q assert "out geom;" in q def test_build_query_nwr_for_utility_points() -> None: """nwr ловит и node, и way (точка/площадка), с out geom.""" q = _build_overpass_query("power", "transformer", "nwr") assert 'nwr["power"="transformer"]' in q assert "out geom;" in q def test_build_query_bbox_present() -> None: """Bbox ЕКБ присутствует во всех вариантах запроса.""" q = _build_overpass_query("power", "substation", "nwr") # south,west,north,east из EKB_BBOX assert "(56.7,60.5,56.95,60.75)" in q # ── _way_to_linestring_wkt (используется для way-результатов nwr) ──────────── def test_way_to_linestring_valid() -> None: nodes = [{"lat": 56.8, "lon": 60.6}, {"lat": 56.81, "lon": 60.61}] wkt = _way_to_linestring_wkt(nodes) assert wkt == "LINESTRING(60.6 56.8, 60.61 56.81)" def test_way_to_linestring_too_few_points() -> None: assert _way_to_linestring_wkt([{"lat": 56.8, "lon": 60.6}]) is None