import openpyxl from datatypes.magicplan.domain.models import Door, Floor, Plan, Room, Window from backend.magic_plan.audit_script import AuditRow, _apply_section_borders, _build_rows, _write_data_rows, _write_headers def test_build_rows_opening_and_trickle_vent_fields_are_none() -> None: # Arrange — plan with one window so opening/trickle_vent fields are exercised plan = Plan( uid="x", name=None, floors=[ Floor(level=0, name=None, rooms=[ Room( name="Living Room", width_m=3.0, length_m=4.0, area_m2=12.0, windows=[Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung")], ), ]), ], ) # Act rows = _build_rows(plan) # Assert — all new forward-compatible fields default to None data_row = next(r for r in rows if r.window_label is not None) assert data_row.opening_count is None assert data_row.opening_width_m is None assert data_row.opening_height_m is None assert data_row.opening_total_area_m2 is None assert data_row.trickle_vent_blocked is None assert data_row.trickle_vent_pictured is None assert data_row.trickle_vent_effective_area is None assert data_row.trickle_vent_count is None assert data_row.trickle_vent_total_effective_area is None def test_build_rows_empty_plan_returns_empty_list() -> None: # Arrange plan = Plan(uid="x", name=None, floors=[]) # Act rows: list[AuditRow] = _build_rows(plan) # Assert assert rows == [] def test_build_rows_single_room_no_windows_or_doors() -> None: # Arrange plan = Plan( uid="x", name=None, floors=[ Floor(level=0, name=None, rooms=[ Room(name="Hall", width_m=2.0, length_m=3.0, area_m2=6.0), ]), ], ) # Act rows: list[AuditRow] = _build_rows(plan) # Assert — separator + one data row assert len(rows) == 2 separator, data = rows assert separator.floor_level == 0 assert data.room_name == "Hall" assert data.room_width_m == 2.0 assert data.room_length_m == 3.0 assert data.room_area_m2 == 6.0 assert data.window_label is None assert data.door_location is None def test_build_rows_room_with_more_windows_than_doors() -> None: # Arrange — 2 windows, 1 door → 2 data rows; room columns blank on row 2 plan = Plan( uid="x", name=None, floors=[ Floor(level=0, name=None, rooms=[ Room( name="Lounge", width_m=4.0, length_m=5.0, area_m2=20.0, windows=[ Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung"), Window(width_m=0.8, height_m=1.0, area_m2=0.8, opening_type="top_hung"), ], doors=[Door(width_mm=762.0)], ), ]), ], ) # Act rows: list[AuditRow] = _build_rows(plan) # Assert data_rows = [r for r in rows if r.floor_level is None] assert len(data_rows) == 2 # row 0 carries room columns assert data_rows[0].room_name == "Lounge" assert data_rows[0].window_label == "W1" assert data_rows[0].door_location == "Lounge" # row 1 room columns are blank assert data_rows[1].room_name is None assert data_rows[1].window_label == "W2" assert data_rows[1].door_location is None def test_build_rows_room_with_more_doors_than_windows() -> None: # Arrange — 1 window, 2 doors → 2 data rows plan = Plan( uid="x", name=None, floors=[ Floor(level=0, name=None, rooms=[ Room( name="Bedroom", width_m=3.5, length_m=4.0, area_m2=14.0, windows=[Window(width_m=1.2, height_m=1.0, area_m2=1.2, opening_type="side_hung")], doors=[Door(width_mm=762.0), Door(width_mm=686.0)], ), ]), ], ) # Act rows: list[AuditRow] = _build_rows(plan) # Assert data_rows = [r for r in rows if r.floor_level is None] assert len(data_rows) == 2 assert data_rows[0].window_label == "W1" assert data_rows[0].door_width_mm == 762.0 assert data_rows[1].window_label is None assert data_rows[1].door_width_mm == 686.0 def test_build_rows_floor_separator_precedes_rooms() -> None: # Arrange plan = Plan( uid="x", name=None, floors=[ Floor(level=0, name=None, rooms=[ Room(name="Room A", width_m=1.0, length_m=1.0, area_m2=1.0), ]), Floor(level=1, name=None, rooms=[ Room(name="Room B", width_m=1.0, length_m=1.0, area_m2=1.0), ]), ], ) # Act rows: list[AuditRow] = _build_rows(plan) # Assert — rows in order: sep(0), data, sep(1), data assert rows[0].floor_level == 0 assert rows[1].room_name == "Room A" assert rows[2].floor_level == 1 assert rows[3].room_name == "Room B" def test_build_rows_window_labels_sequential_across_floors() -> None: # Arrange — two floors, one window each; labels must be W1, W2 (no reset) plan = Plan( uid="x", name=None, floors=[ Floor(level=0, name=None, rooms=[ Room( name="Ground Room", width_m=1.0, length_m=1.0, area_m2=1.0, windows=[Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung")], ), ]), Floor(level=1, name=None, rooms=[ Room( name="First Room", width_m=1.0, length_m=1.0, area_m2=1.0, windows=[Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="top_hung")], ), ]), ], ) # Act rows: list[AuditRow] = _build_rows(plan) # Assert labels = [r.window_label for r in rows if r.window_label is not None] assert labels == ["W1", "W2"] def test_build_rows_room_name_in_location_on_every_row() -> None: # Arrange — room with 2 windows and 2 doors plan = Plan( uid="x", name=None, floors=[ Floor(level=0, name=None, rooms=[ Room( name="Kitchen", width_m=3.0, length_m=3.0, area_m2=9.0, windows=[ Window(width_m=1.0, height_m=1.0, area_m2=1.0, opening_type="side_hung"), Window(width_m=0.8, height_m=1.0, area_m2=0.8, opening_type="top_hung"), ], doors=[Door(width_mm=762.0), Door(width_mm=686.0)], ), ]), ], ) # Act rows: list[AuditRow] = _build_rows(plan) # Assert — every data row carries "Kitchen" in location fields data_rows = [r for r in rows if r.floor_level is None] for row in data_rows: assert row.window_location == "Kitchen" assert row.door_location == "Kitchen" def test_write_headers_two_rows_correct_labels_and_column_positions() -> None: # Arrange wb = openpyxl.Workbook() ws = wb.active # type: ignore[assignment] # Act _write_headers(ws) # Assert — group labels in row 1 at the new column positions assert ws.cell(row=1, column=1).value == "Room" assert ws.cell(row=1, column=6).value == "Window Area" assert ws.cell(row=1, column=12).value == "Openings" assert ws.cell(row=1, column=16).value == "Trickle Vents" assert ws.cell(row=1, column=22).value == "Doors" # Assert — "Door Undercuts" label no longer used assert ws.cell(row=1, column=28).value is None # Assert — only two header rows; row 3 is empty assert ws.cell(row=3, column=1).value is None assert ws.cell(row=3, column=13).value is None # Assert — opening columns use self-describing labels (no "Opening 1" sub-header) assert ws.cell(row=2, column=12).value == "No. of Openings" assert ws.cell(row=2, column=13).value == "Opening Width m" assert ws.cell(row=2, column=14).value == "Opening Height m" assert ws.cell(row=2, column=15).value == "Total Area m²" def test_apply_section_borders_sets_medium_right_border_on_boundary_columns() -> None: # Arrange wb = openpyxl.Workbook() ws = wb.active # type: ignore[assignment] # Act _apply_section_borders(ws, 1) # Assert — medium right-border on last col of each subtable for col in (4, 11, 15, 20): assert ws.cell(row=1, column=col).border.right.style == "medium", f"col {col}" # Assert — non-boundary columns have no medium right-border for col in (1, 6, 12, 22): right = ws.cell(row=1, column=col).border.right assert right is None or right.style != "medium", f"col {col}" def test_write_data_rows_places_door_data_at_cols_22_to_25() -> None: # Arrange wb = openpyxl.Workbook() ws = wb.active # type: ignore[assignment] rows = [ AuditRow( room_name="Hall", room_width_m=2.0, room_length_m=3.0, room_area_m2=6.0, door_location="Hall", door_width_mm=762.0, ), ] # Act _write_data_rows(ws, rows) # Assert — door columns at V–Y (22–25), not old positions 28–31 # data starts at row 3 (after 2 header rows) assert ws.cell(row=3, column=22).value == "Hall" assert ws.cell(row=3, column=23).value == 762.0 assert ws.cell(row=3, column=28).value is None assert ws.cell(row=3, column=29).value is None def test_write_headers_blank_separator_columns_have_no_fill() -> None: # Arrange wb = openpyxl.Workbook() ws = wb.active # type: ignore[assignment] # Act _write_headers(ws) # Assert — cols 5 (E) and 21 (U) have no fill in either header row for col in (5, 21): for row in (1, 2): fill = ws.cell(row=row, column=col).fill assert fill.patternType is None, f"col {col} row {row} should have no fill"