import json from pathlib import Path from typing import Any import pytest import domain.magicplan.api.response as api from domain.magicplan.api.response import MagicPlanPlan, Symbol, Vec3, WallItem from domain.magicplan.mapper import ( _map_window, # pyright: ignore[reportPrivateUsage] map_address, map_plan, ) from domain.magicplan.models import Plan FIXTURE_DIR = Path(__file__).parents[3] / "tests" / "magic_plan" PLAN_ID = "72efd2e0-b2b9-48cd-b82e-41f5b3166c9a" @pytest.fixture(scope="module") def raw_data() -> dict[str, Any]: payload = json.loads( (FIXTURE_DIR / "magicplan_api_plan_response.json").read_text() ) return payload["data"] @pytest.fixture(scope="module") def mp(raw_data: dict[str, Any]) -> MagicPlanPlan: return MagicPlanPlan.model_validate(raw_data) @pytest.fixture(scope="module") def plan(mp: MagicPlanPlan) -> Plan: return map_plan(mp) # --- Plan-level --- def test_plan_uid(plan: Plan) -> None: assert plan.uid == PLAN_ID def test_plan_postcode(plan: Plan) -> None: assert plan.postcode == "BR2 8DU" def test_plan_address(plan: Plan) -> None: assert plan.address == "20 Larch Way, Bromley, GB" def test_floor_count(plan: Plan) -> None: assert len(plan.floors) == 2 # --- Room dimensions --- def test_first_room_name(plan: Plan) -> None: assert plan.floors[0].rooms[0].name == "Kitchen" def test_room_dimensions_are_floats(plan: Plan) -> None: room = plan.floors[0].rooms[0] assert isinstance(room.width_m, float) assert isinstance(room.length_m, float) assert isinstance(room.area_m2, float) def test_room_area_rounded_to_2dp(plan: Plan) -> None: room = plan.floors[0].rooms[0] assert room.area_m2 == 9.39 def test_room_dimensions_parsed_from_string(plan: Plan) -> None: room = plan.floors[0].rooms[0] assert room.width_m == 3.19 assert room.length_m == 2.94 # --- Windows --- def test_kitchen_has_windows(plan: Plan) -> None: assert len(plan.floors[0].rooms[0].windows) >= 1 def test_window_fields_are_floats(plan: Plan) -> None: window = plan.floors[0].rooms[0].windows[0] assert isinstance(window.width_m, float) assert isinstance(window.height_m, float) assert isinstance(window.area_m2, float) def test_window_opening_type_prefix_stripped(plan: Plan) -> None: window = plan.floors[0].rooms[0].windows[0] assert not window.opening_type.startswith("window") assert window.opening_type == "casement" def test_window_area_is_width_times_height(plan: Plan) -> None: window = plan.floors[0].rooms[0].windows[0] assert window.area_m2 == round(window.width_m * window.height_m, 2) def test_window_dimensions_rounded_to_2dp(plan: Plan) -> None: window = plan.floors[0].rooms[0].windows[0] assert window.width_m == 0.90 assert window.height_m == 1.00 assert window.area_m2 == 0.90 def test_hallway_has_no_windows(plan: Plan) -> None: hallway = plan.floors[0].rooms[3] assert hallway.name == "Hallway" assert hallway.windows == [] def test_floor1_window_opening_type_awning(plan: Plan) -> None: bedroom1 = plan.floors[1].rooms[0] assert bedroom1.name == "Bedroom 1" assert bedroom1.windows[0].opening_type == "awning" # --- Doors --- def test_kitchen_has_doors(plan: Plan) -> None: assert len(plan.floors[0].rooms[0].doors) >= 1 def test_door_width_is_float(plan: Plan) -> None: door = plan.floors[0].rooms[0].doors[0] assert isinstance(door.width_mm, float) def test_door_width_rounded_to_2dp(plan: Plan) -> None: door = plan.floors[0].rooms[0].doors[0] assert door.width_mm == 800.0 def test_door_height_is_correct(plan: Plan) -> None: # Kitchen doorhinged has size.z = 2.04 m = 2040 mm door = plan.floors[0].rooms[0].doors[0] assert door.height_mm == 2040.0 # --- Ventilation --- def test_window_with_no_custom_fields_has_no_ventilation() -> None: wi = WallItem( uid="test", symbol=Symbol(id="windowcasement", name="Casement Window", valid=True), size=Vec3(x=1.0, y=0.0, z=1.2), position=Vec3(x=0.0, y=0.0, z=0.0), rotation=Vec3(x=0.0, y=0.0, z=0.0), ) window = _map_window(wi) assert window.ventilation is None def test_kitchen_window_has_ventilation(plan: Plan) -> None: window = plan.floors[0].rooms[0].windows[0] assert window.ventilation is not None assert window.ventilation.trickle_vent_area_mm2 == 1700 def test_toilet_door_has_ventilation_undercut(plan: Plan) -> None: toilet_doors = plan.floors[0].rooms[2].doors hinged = next(d for d in toilet_doors if d.ventilation is not None) assert hinged.ventilation is not None assert hinged.ventilation.undercut_mm == 70.0 def test_doorglass_is_classified_as_window(plan: Plan) -> None: room = plan.floors[0].rooms[1] assert "doorglass" in [w.opening_type for w in room.windows] def test_glass_door_ventilation_opening_type(plan: Plan) -> None: room = plan.floors[0].rooms[1] glass = next(w for w in room.windows if w.opening_type == "doorglass") assert glass.ventilation is not None assert glass.ventilation.opening_type == "External.Door" # --- Address unit tests --- def test_map_address_with_street_and_number() -> None: addr = api.Address(street_number="2", street="Laburnum Way", city="Bromley", country="GB") assert map_address(addr) == "2 Laburnum Way, Bromley, GB" def test_map_address_with_street_number_only() -> None: addr = api.Address(street_number="2", city="Bromley", country="GB") assert map_address(addr) == "2, Bromley, GB" def test_map_address_with_street_only() -> None: addr = api.Address(street="Laburnum Way", city="Bromley", country="GB") assert map_address(addr) == "Laburnum Way, Bromley, GB" def test_map_address_city_absent_is_omitted() -> None: addr = api.Address(street_number="2", street="Laburnum Way", country="GB") assert map_address(addr) == "2 Laburnum Way, GB" def test_map_address_none_returns_none() -> None: assert map_address(None) is None