From a8c90b0d952d82e4d4a962db48b7eab1444c2cf3 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 14 Jul 2026 09:20:17 +0000 Subject: [PATCH] =?UTF-8?q?Sanitise=20and=20deduplicate=20scenario=20sheet?= =?UTF-8?q?=20names=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../xlsx/test_scenario_export_workbook.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/infrastructure/xlsx/test_scenario_export_workbook.py b/tests/infrastructure/xlsx/test_scenario_export_workbook.py index d1dfaa79c..4bf13859e 100644 --- a/tests/infrastructure/xlsx/test_scenario_export_workbook.py +++ b/tests/infrastructure/xlsx/test_scenario_export_workbook.py @@ -28,3 +28,23 @@ def test_renders_a_sheet_named_for_the_scenario_with_headers_then_rows() -> None 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() -> 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 + data = render_workbook( + [(long_name, empty), (long_name, empty), (illegal_name, empty)] + ) + + # assert — three distinct, Excel-legal sheet names. + names = load_workbook(BytesIO(data)).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":/\?*[]")