Migrate all MagicPlan tests to single new-format fixture 🟪

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel Roth 2026-06-05 12:59:56 +00:00
parent 8deaba1f94
commit 0211fb8092
13 changed files with 141 additions and 820691 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -6,14 +6,14 @@ import pytest
from datatypes.magicplan.api.response import MagicPlanPlan, PlansListResponse
FIXTURE_DIR = Path(__file__).parents[4] / "backend" / "magic_plan"
PLAN_ID = "a7285ed1-878d-47eb-8aa6-85ef9e187516"
FIXTURE_DIR = Path(__file__).parents[4] / "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_example.json").read_text()
(FIXTURE_DIR / "magicplan_api_plan_response.json").read_text()
)
return payload["data"]
@ -23,66 +23,55 @@ def mp(raw_data: dict[str, Any]) -> MagicPlanPlan:
return MagicPlanPlan.model_validate(raw_data)
def test_model_validate_does_not_raise(raw_data: dict[str, Any]):
# act
def test_model_validate_does_not_raise(raw_data: dict[str, Any]) -> None:
MagicPlanPlan.model_validate(raw_data)
def test_plan_id(mp: MagicPlanPlan):
# assert
def test_plan_id(mp: MagicPlanPlan) -> None:
assert mp.plan.id == PLAN_ID
def test_url_3d_alias(mp: MagicPlanPlan):
# assert
assert mp.plan.url_3d is not None
assert mp.plan.url_3d.startswith("http")
def test_floor_count(mp: MagicPlanPlan):
# assert
def test_floor_count(mp: MagicPlanPlan) -> None:
assert len(mp.plan_detail.plan.floors) == 2
def test_first_room_name(mp: MagicPlanPlan):
# assert
def test_first_room_name(mp: MagicPlanPlan) -> None:
assert mp.plan_detail.plan.floors[0].rooms[0].name == "Kitchen"
def test_room_area_is_float(mp: MagicPlanPlan):
# arrange
def test_room_area_is_float(mp: MagicPlanPlan) -> None:
room = mp.plan_detail.plan.floors[0].rooms[0]
# assert
assert isinstance(room.area, float)
def test_wall_item_symbol_id(mp: MagicPlanPlan):
# arrange
def test_wall_item_symbol_id(mp: MagicPlanPlan) -> None:
room = mp.plan_detail.plan.floors[0].rooms[0]
# assert
assert room.wall_items[0].symbol.id != ""
def test_field_value_array(mp: MagicPlanPlan):
# arrange
def test_custom_displayable_fields_parsed(mp: MagicPlanPlan) -> None:
# Kitchen windowcasement carries custom_displayable_fields in the new fixture.
room = mp.plan_detail.plan.floors[0].rooms[0]
array_field = next(f for f in room.displayable_fields if f.value.is_array)
# assert
wi = next(w for w in room.wall_items if w.symbol.id == "windowcasement")
assert len(wi.custom_displayable_fields) > 0
def test_field_value_array(mp: MagicPlanPlan) -> None:
room = mp.plan_detail.plan.floors[0].rooms[0]
wi = next(w for w in room.wall_items if w.symbol.id == "windowcasement")
array_field = next(f for f in wi.custom_displayable_fields if f.value.is_array)
assert isinstance(array_field.value.value, list)
def test_field_value_scalar(mp: MagicPlanPlan):
# arrange
def test_field_value_scalar(mp: MagicPlanPlan) -> None:
room = mp.plan_detail.plan.floors[0].rooms[0]
scalar_field = next(f for f in room.displayable_fields if not f.value.is_array)
# assert
wi = next(w for w in room.wall_items if w.symbol.id == "windowcasement")
scalar_field = next(f for f in wi.custom_displayable_fields if not f.value.is_array)
assert isinstance(scalar_field.value.value, str)
def test_extra_fields_ignored(raw_data: dict[str, Any]):
# arrange
def test_extra_fields_ignored(raw_data: dict[str, Any]) -> None:
data_with_extra = {**raw_data, "unknown_future_field": "whatever"}
# act
MagicPlanPlan.model_validate(data_with_extra)
@ -105,39 +94,31 @@ def plans_response(plans_raw_data: dict[str, Any]) -> PlansListResponse:
def test_plans_list_model_validate_does_not_raise(
plans_raw_data: dict[str, Any],
) -> None:
# act
PlansListResponse.model_validate(plans_raw_data)
def test_plans_list_count(plans_response: PlansListResponse) -> None:
# assert
assert len(plans_response.plans) == 1
def test_plans_list_first_plan_id(plans_response: PlansListResponse) -> None:
# assert
assert plans_response.plans[0].id == "9f9889ff-793e-4e9a-a6f0-e22f5b0f5365"
def test_plans_list_paging_page(plans_response: PlansListResponse) -> None:
# assert
assert plans_response.paging.page == 1
def test_plans_list_paging_next_page_is_false(
plans_response: PlansListResponse,
) -> None:
# assert
assert plans_response.paging.next_page is False
def test_plans_list_paging_count(plans_response: PlansListResponse) -> None:
# assert
assert plans_response.paging.count == 1
def test_plans_list_unknown_keys_ignored(plans_raw_data: dict[str, Any]) -> None:
# arrange
data_with_extra = {**plans_raw_data, "unknown_future_field": "whatever"}
# act
PlansListResponse.model_validate(data_with_extra)

View file

@ -4,19 +4,19 @@ from typing import Any
import pytest
import datatypes.magicplan.api.response as api
from datatypes.magicplan.api.response import MagicPlanPlan, Symbol, Vec3, WallItem
from datatypes.magicplan.domain.mapper import _map_window, map_plan
from datatypes.magicplan.domain.mapper import _map_address, _map_window, map_plan
from datatypes.magicplan.domain.models import Plan
FIXTURE_DIR = Path(__file__).parents[4] / "backend" / "magic_plan"
PLAN_ID = "a7285ed1-878d-47eb-8aa6-85ef9e187516"
PLAN_ID_2 = "9f9889ff-793e-4e9a-a6f0-e22f5b0f5365"
FIXTURE_DIR = Path(__file__).parents[4] / "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_example.json").read_text()
(FIXTURE_DIR / "magicplan_api_plan_response.json").read_text()
)
return payload["data"]
@ -31,213 +31,112 @@ def plan(mp: MagicPlanPlan) -> Plan:
return map_plan(mp)
def test_plan_uid(plan: Plan):
# --- Plan-level ---
def test_plan_uid(plan: Plan) -> None:
assert plan.uid == PLAN_ID
def test_floor_count(plan: Plan):
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
def test_first_room_name(plan: Plan):
# --- 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):
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):
def test_room_area_rounded_to_2dp(plan: Plan) -> None:
room = plan.floors[0].rooms[0]
assert room.area_m2 == 7.95
assert room.area_m2 == 9.39
def test_room_dimensions_parsed_from_string(plan: Plan):
def test_room_dimensions_parsed_from_string(plan: Plan) -> None:
room = plan.floors[0].rooms[0]
assert room.width_m == pytest.approx(2.67)
assert room.length_m == pytest.approx(2.98)
assert room.width_m == pytest.approx(3.19)
assert room.length_m == pytest.approx(2.94)
def test_kitchen_has_windows(plan: Plan):
room = plan.floors[0].rooms[0]
assert len(room.windows) >= 1
# --- Windows ---
def test_window_fields_are_floats(plan: Plan):
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):
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):
def test_window_area_is_width_times_height(plan: Plan) -> None:
window = plan.floors[0].rooms[0].windows[0]
assert window.area_m2 == pytest.approx(window.width_m * window.height_m, rel=1e-2)
def test_window_dimensions_rounded_to_2dp(plan: Plan):
def test_window_dimensions_rounded_to_2dp(plan: Plan) -> None:
window = plan.floors[0].rooms[0].windows[0]
assert window.width_m == 1.40
assert window.height_m == 1.20
assert window.area_m2 == 1.68
assert window.width_m == 0.90
assert window.height_m == 1.00
assert window.area_m2 == 0.90
def test_door_width_rounded_to_2dp(plan: Plan):
door = plan.floors[0].rooms[0].doors[0]
assert door.width_mm == 0.79
def test_hallway_has_no_windows(plan: Plan) -> None:
hallway = plan.floors[0].rooms[3]
assert hallway.name == "Hallway"
assert hallway.windows == []
def test_kitchen_has_doors(plan: Plan):
room = plan.floors[0].rooms[0]
assert len(room.doors) >= 1
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"
def test_door_width_is_float(plan: Plan):
# --- 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)
# --- Fixture 2: magicplan_api_plan_response_example_2.json ---
def test_door_width_rounded_to_2dp(plan: Plan) -> None:
door = plan.floors[0].rooms[0].doors[0]
assert door.width_mm == 0.80
@pytest.fixture(scope="module")
def raw_data_2() -> dict[str, Any]:
payload = json.loads(
(FIXTURE_DIR / "magicplan_api_plan_response_example_2.json").read_text()
)
return payload["data"]
@pytest.fixture(scope="module")
def plan2(raw_data_2: dict[str, Any]) -> Plan:
return map_plan(MagicPlanPlan.model_validate(raw_data_2))
def test_plan2_uid(plan2: Plan):
assert plan2.uid == PLAN_ID_2
def test_plan2_floor_count(plan2: Plan):
assert len(plan2.floors) == 3
def test_plan2_first_room_name(plan2: Plan):
assert plan2.floors[0].rooms[0].name == "Toilet"
def test_plan2_room_area_rounded_to_2dp(plan2: Plan):
room = plan2.floors[0].rooms[0]
assert room.area_m2 == 0.96
def test_plan2_room_dimensions_parsed_from_string(plan2: Plan):
room = plan2.floors[0].rooms[0]
assert room.width_m == pytest.approx(1.12)
assert room.length_m == pytest.approx(0.86)
def test_plan2_room_with_no_windows(plan2: Plan):
hall = plan2.floors[0].rooms[1]
assert hall.name == "Hall"
assert hall.windows == []
def test_plan2_window_dimensions_rounded_to_2dp(plan2: Plan):
window = plan2.floors[0].rooms[0].windows[0]
assert window.width_m == 0.39
assert window.height_m == 0.67
assert window.area_m2 == 0.26
def test_plan2_window_opening_type_casement(plan2: Plan):
window = plan2.floors[0].rooms[0].windows[0]
assert window.opening_type == "casement"
def test_plan2_window_opening_type_hung(plan2: Plan):
bathroom1 = plan2.floors[1].rooms[1]
assert bathroom1.name == "Bathroom 1"
assert bathroom1.windows[0].opening_type == "hung"
def test_plan2_door_width_rounded_to_2dp(plan2: Plan):
door = plan2.floors[0].rooms[0].doors[0]
assert door.width_mm == 0.71
# --- Address and postcode fields ---
def test_plan_postcode(plan: Plan):
assert plan.postcode == "BR2 8BZ"
def test_plan_address(plan: Plan):
assert plan.address == "2 Laburnum Way, Bromley, GB"
def test_plan2_postcode(plan2: Plan):
assert plan2.postcode == "BR1 3LP"
def test_plan2_address(plan2: Plan):
assert plan2.address == "11 Station Road, Bromley, GB"
# --- Fixture 3: street_number set, city absent ---
@pytest.fixture(scope="module")
def plan3() -> Plan:
payload = json.loads(
(FIXTURE_DIR / "magicplan_api_plan_response_example_3.json").read_text()
)
return map_plan(MagicPlanPlan.model_validate(payload["data"]))
def test_plan3_address_uses_street_number_and_omits_city(plan3: Plan):
assert plan3.address == "2 Laburnum Way, GB"
def test_plan3_postcode(plan3: Plan):
assert plan3.postcode == "BR2 8BZ"
# --- Fixture 4: street_number set, street absent ---
@pytest.fixture(scope="module")
def plan4() -> Plan:
payload = json.loads(
(FIXTURE_DIR / "magicplan_api_plan_response_example_4.json").read_text()
)
return map_plan(MagicPlanPlan.model_validate(payload["data"]))
def test_plan4_address_uses_street_number_when_street_absent(plan4: Plan):
assert plan4.address == "2, Bromley, GB"
# --- New fixture: new-format API response with custom_displayable_fields ---
NEW_FIXTURE_DIR = Path(__file__).parents[4] / "tests" / "magic_plan"
@pytest.fixture(scope="module")
def plan_new() -> Plan:
payload = json.loads(
(NEW_FIXTURE_DIR / "magicplan_api_plan_response.json").read_text()
)
return map_plan(MagicPlanPlan.model_validate(payload["data"]))
# --- Ventilation ---
def test_window_with_no_custom_fields_has_no_ventilation() -> None:
@ -254,38 +153,61 @@ def test_window_with_no_custom_fields_has_no_ventilation() -> None:
assert window.ventilation is None
def test_glass_door_ventilation_opening_type(plan_new: Plan) -> None:
# The doorglass in Living/Dining Room carries Opening Type = "External.Door"
# in its custom_displayable_fields.
room = plan_new.floors[0].rooms[1]
glass = next(w for w in room.windows if w.opening_type == "doorglass")
def test_kitchen_window_has_ventilation(plan: Plan) -> None:
window = plan.floors[0].rooms[0].windows[0]
assert glass.ventilation is not None
assert glass.ventilation.opening_type == "External.Door"
assert window.ventilation is not None
assert window.ventilation.trickle_vent_area_mm2 == 1700
def test_doorglass_is_classified_as_window(plan_new: Plan) -> None:
# Living/Dining Room (floor 0, room 1) has one doorglass wall item.
# It must appear in room.windows, not room.doors.
room = plan_new.floors[0].rooms[1]
assert "doorglass" in [w.opening_type for w in room.windows]
def test_toilet_door_has_ventilation_undercut(plan_new: Plan) -> None:
# Toilet is floor 0 room 2; its doorhinged has Door Undercut (mm) = 70
toilet_doors = plan_new.floors[0].rooms[2].doors
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_kitchen_window_has_ventilation(plan_new: Plan) -> None:
# Arrange — Kitchen is floor 0 room 0; its only window is a windowcasement
# with custom_displayable_fields populated in the new fixture.
window = plan_new.floors[0].rooms[0].windows[0]
def test_doorglass_is_classified_as_window(plan: Plan) -> None:
room = plan.floors[0].rooms[1]
# Assert
assert window.ventilation is not None
assert window.ventilation.trickle_vent_area_mm2 == 1700
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

View file

@ -160,7 +160,7 @@ def test_get_plan_calls_correct_url(
client: MagicPlanClient, mock_session: MagicMock
) -> None:
# Arrange
plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"]
plan_data = _load_fixture("magicplan_api_plan_response.json")["data"]
mock_session.get.return_value.json.return_value = {
"message": "OK",
"data": plan_data,
@ -176,7 +176,7 @@ def test_get_plan_calls_raise_for_status(
client: MagicPlanClient, mock_session: MagicMock
) -> None:
# Arrange
plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"]
plan_data = _load_fixture("magicplan_api_plan_response.json")["data"]
mock_session.get.return_value.json.return_value = {
"message": "OK",
"data": plan_data,
@ -191,7 +191,7 @@ def test_get_plan_returns_magic_plan(
client: MagicPlanClient, mock_session: MagicMock
) -> None:
# Arrange
plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"]
plan_data = _load_fixture("magicplan_api_plan_response.json")["data"]
mock_session.get.return_value.json.return_value = {
"message": "OK",
"data": plan_data,
@ -200,7 +200,7 @@ def test_get_plan_returns_magic_plan(
result = client.get_plan("a7285ed1-878d-47eb-8aa6-85ef9e187516")
# Assert
assert isinstance(result, MagicPlanPlan)
assert result.plan.id == "a7285ed1-878d-47eb-8aa6-85ef9e187516"
assert result.plan.id == "72efd2e0-b2b9-48cd-b82e-41f5b3166c9a"
def test_get_plan_propagates_http_error(

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -18,14 +18,14 @@ from orchestration.magic_plan_orchestrator import MagicPlanService
from backend.magic_plan.magic_plan_trigger_request import MagicPlanTriggerRequest
FIXTURE_DIR = Path(__file__).parents[2] / "magic_plan"
PLAN_ID = "a7285ed1-878d-47eb-8aa6-85ef9e187516"
PLAN_ID = "72efd2e0-b2b9-48cd-b82e-41f5b3166c9a"
S3_BUCKET = "test-bucket"
@pytest.fixture(scope="module")
def domain_plan() -> Plan:
data = json.loads(
(FIXTURE_DIR / "magicplan_api_plan_response_example.json").read_text()
(FIXTURE_DIR / "magicplan_api_plan_response.json").read_text()
)
return map_plan(MagicPlanPlan.model_validate(data["data"]))
@ -33,7 +33,7 @@ def domain_plan() -> Plan:
@pytest.fixture(scope="module")
def api_magic_plan() -> MagicPlanPlan:
data = json.loads(
(FIXTURE_DIR / "magicplan_api_plan_response_example.json").read_text()
(FIXTURE_DIR / "magicplan_api_plan_response.json").read_text()
)
return MagicPlanPlan.model_validate(data["data"])
@ -41,7 +41,7 @@ def api_magic_plan() -> MagicPlanPlan:
@pytest.fixture(scope="module")
def plan_summary() -> PlanSummary:
data = json.loads(
(FIXTURE_DIR / "magicplan_api_plan_response_example.json").read_text()
(FIXTURE_DIR / "magicplan_api_plan_response.json").read_text()
)
return MagicPlanPlan.model_validate(data["data"]).plan
@ -50,7 +50,7 @@ def plan_summary() -> PlanSummary:
def mock_client() -> MagicMock:
client = MagicMock(spec=MagicPlanClient)
client.get_plan_raw.return_value = (
FIXTURE_DIR / "magicplan_api_plan_response_example.json"
FIXTURE_DIR / "magicplan_api_plan_response.json"
).read_bytes()
return client