"""The Scenario Export workbook renderer (ADR-0065): one branded .xlsx with one sheet per Scenario, headers then rows, verified by re-opening the bytes.""" from __future__ import annotations from pathlib import Path from openpyxl import load_workbook from domain.scenario_export.scenario_sheet import ExportSheet from infrastructure.xlsx.branding import HEADER_FILL from infrastructure.xlsx.scenario_export_workbook import render_workbook def test_renders_a_sheet_named_for_the_scenario_with_headers_then_rows( tmp_path: Path, ) -> None: # arrange — one scenario's shaped sheet. sheet = ExportSheet( columns=("property_id", "loft_insulation"), rows=({"property_id": 1, "loft_insulation": 1200.0},), ) # act path = str(tmp_path / "export.xlsx") render_workbook([("Fabric first", sheet)], path) # assert — a real workbook with a sheet named for the scenario, the column # contract as the header row, and the data beneath it. workbook = load_workbook(path) assert workbook.sheetnames == ["Fabric first"] worksheet = workbook["Fabric first"] assert [cell.value for cell in worksheet[1]] == ["property_id", "loft_insulation"] assert [cell.value for cell in worksheet[2]] == [1, 1200.0] def test_sanitises_and_deduplicates_scenario_sheet_names(tmp_path: Path) -> None: # arrange — a name over Excel's 31-char limit (used twice, so it must be # deduplicated) and one with characters Excel forbids in a sheet title. empty = ExportSheet(columns=("property_id",), rows=()) long_name = "A really long scenario name that exceeds Excel's limit" illegal_name = "Bad:/\\?*[]name" # act path = str(tmp_path / "export.xlsx") render_workbook( [(long_name, empty), (long_name, empty), (illegal_name, empty)], path ) # assert — three distinct, Excel-legal sheet names. names = load_workbook(path).sheetnames assert len(names) == 3 assert len(set(names)) == 3 assert all(len(name) <= 31 for name in names) assert not any(ch in name for name in names for ch in r":/\?*[]") def test_brands_the_header_row_and_freezes_it(tmp_path: Path) -> None: # arrange sheet = ExportSheet( columns=("property_id", "loft_insulation"), rows=({"property_id": 1, "loft_insulation": 1200.0},), ) # act path = str(tmp_path / "export.xlsx") render_workbook([("Fabric first", sheet)], path) # assert — the header is a bold, brand-filled band and the header row is # frozen so it stays visible while scrolling. worksheet = load_workbook(path)["Fabric first"] header_cell = worksheet["A1"] assert header_cell.font.bold is True assert header_cell.fill.fgColor.rgb == HEADER_FILL assert worksheet.freeze_panes == "A2"