Model/tests/domain/magicplan/test_mapper.py
2026-06-05 14:07:28 +00:00

223 lines
6 KiB
Python

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