|
1 | 1 | import datetime |
2 | 2 | import json |
3 | 3 | from io import BytesIO |
4 | | -from unittest.mock import Mock |
| 4 | +from unittest.mock import Mock, patch |
5 | 5 | from zipfile import BadZipFile |
6 | 6 |
|
7 | 7 | import pytest |
@@ -841,3 +841,174 @@ def test_normalize_column_range_already_normalized(self): |
841 | 841 | # 空白のみ |
842 | 842 | normalized = parser._normalize_column_range(" ", ws) |
843 | 843 | assert normalized == " " |
| 844 | + |
| 845 | + def test_no_duplicate_range_normalization(self): |
| 846 | + """ |
| 847 | + セル範囲の正規化・拡張が重複して実行されないことを確認 |
| 848 | +
|
| 849 | + 課題3-2の対応:_parse_sheetと_build_merged_cell_cacheで |
| 850 | + 重複していた計算が1回のみになったことを検証 |
| 851 | + """ |
| 852 | + # テスト用Excelを作成(結合セルあり) |
| 853 | + wb = Workbook() |
| 854 | + ws = wb.active |
| 855 | + ws.title = "TestSheet" |
| 856 | + ws["A1"] = "Header1" |
| 857 | + ws["B1"] = "Header2" |
| 858 | + ws["A2"] = "Data1" |
| 859 | + ws["B2"] = "Data2" |
| 860 | + ws["A3"] = "Data3" |
| 861 | + ws["B3"] = "Data4" |
| 862 | + |
| 863 | + # A1:B1を結合 |
| 864 | + ws.merge_cells("A1:B1") |
| 865 | + |
| 866 | + excel_bytes = BytesIO() |
| 867 | + wb.save(excel_bytes) |
| 868 | + excel_bytes.seek(0) |
| 869 | + |
| 870 | + # モックの設定 |
| 871 | + self.mock_download_client.download_file.return_value = excel_bytes.getvalue() |
| 872 | + |
| 873 | + parser = SharePointExcelParser(self.mock_download_client) |
| 874 | + |
| 875 | + # _normalize_column_rangeと_expand_axis_rangeの呼び出し回数をカウント |
| 876 | + with patch.object( |
| 877 | + parser, "_normalize_column_range", wraps=parser._normalize_column_range |
| 878 | + ) as mock_normalize, patch.object( |
| 879 | + parser, "_expand_axis_range", wraps=parser._expand_axis_range |
| 880 | + ) as mock_expand: |
| 881 | + # 列範囲指定で解析 |
| 882 | + result = parser.parse_to_json("/test/file.xlsx", cell_range="A:B") |
| 883 | + result_data = json.loads(result) |
| 884 | + |
| 885 | + # 結果が正しいことを確認 |
| 886 | + assert "sheets" in result_data |
| 887 | + assert len(result_data["sheets"]) == 1 |
| 888 | + assert result_data["sheets"][0]["name"] == "TestSheet" |
| 889 | + |
| 890 | + # _normalize_column_rangeは1回だけ呼ばれる(重複なし) |
| 891 | + assert ( |
| 892 | + mock_normalize.call_count == 1 |
| 893 | + ), f"Expected 1 call, got {mock_normalize.call_count}" |
| 894 | + |
| 895 | + # _expand_axis_rangeは1回だけ呼ばれる(重複なし) |
| 896 | + assert ( |
| 897 | + mock_expand.call_count == 1 |
| 898 | + ), f"Expected 1 call, got {mock_expand.call_count}" |
| 899 | + |
| 900 | + def test_build_merged_cell_cache_with_effective_range(self): |
| 901 | + """ |
| 902 | + _build_merged_cell_cacheにeffective_cell_rangeを渡した場合の動作確認 |
| 903 | +
|
| 904 | + 計算済みの範囲を渡すことで、内部での重複計算が回避されることを検証 |
| 905 | + """ |
| 906 | + # テスト用Excelを作成(結合セルあり) |
| 907 | + wb = Workbook() |
| 908 | + ws = wb.active |
| 909 | + ws.title = "TestSheet" |
| 910 | + ws["A1"] = "Merged" |
| 911 | + ws["A2"] = "Data1" |
| 912 | + ws["B2"] = "Data2" |
| 913 | + |
| 914 | + # A1:B1を結合 |
| 915 | + ws.merge_cells("A1:B1") |
| 916 | + |
| 917 | + parser = SharePointExcelParser(self.mock_download_client) |
| 918 | + |
| 919 | + # effective_cell_rangeを渡して呼び出し |
| 920 | + merged_cell_map, merged_anchor_value_map, merged_ranges = ( |
| 921 | + parser._build_merged_cell_cache(ws, effective_cell_range="A1:B2") |
| 922 | + ) |
| 923 | + |
| 924 | + # 結合セル情報が正しく取得されることを確認 |
| 925 | + assert merged_cell_map is not None |
| 926 | + assert merged_ranges is not None |
| 927 | + assert len(merged_ranges) == 1 |
| 928 | + assert merged_ranges[0]["range"] == "A1:B1" |
| 929 | + |
| 930 | + def test_build_merged_cell_cache_without_effective_range(self): |
| 931 | + """ |
| 932 | + _build_merged_cell_cacheにNoneを渡した場合の動作確認 |
| 933 | +
|
| 934 | + effective_cell_rangeがNoneの場合、sheet.dimensionsが使用されることを検証 |
| 935 | + """ |
| 936 | + # テスト用Excelを作成(結合セルあり) |
| 937 | + wb = Workbook() |
| 938 | + ws = wb.active |
| 939 | + ws.title = "TestSheet" |
| 940 | + ws["A1"] = "Merged" |
| 941 | + ws["B1"] = "Header" |
| 942 | + ws["A2"] = "Data1" |
| 943 | + ws["B2"] = "Data2" |
| 944 | + |
| 945 | + # A1:B1を結合 |
| 946 | + ws.merge_cells("A1:B1") |
| 947 | + |
| 948 | + parser = SharePointExcelParser(self.mock_download_client) |
| 949 | + |
| 950 | + # effective_cell_rangeにNoneを渡して呼び出し |
| 951 | + # sheet.dimensionsが使用される |
| 952 | + merged_cell_map, merged_anchor_value_map, merged_ranges = ( |
| 953 | + parser._build_merged_cell_cache(ws, effective_cell_range=None) |
| 954 | + ) |
| 955 | + |
| 956 | + # 結合セル情報が正しく取得されることを確認 |
| 957 | + assert merged_cell_map is not None |
| 958 | + assert merged_ranges is not None |
| 959 | + assert len(merged_ranges) == 1 |
| 960 | + assert merged_ranges[0]["range"] == "A1:B1" |
| 961 | + |
| 962 | + def test_range_normalization_integration(self): |
| 963 | + """ |
| 964 | + セル範囲の正規化・拡張と結合セル処理の統合テスト |
| 965 | +
|
| 966 | + 列範囲指定("A:B")が正しく正規化・拡張され、 |
| 967 | + 結合セル情報も正しく取得されることを検証 |
| 968 | + """ |
| 969 | + # テスト用Excelを作成(結合セルあり) |
| 970 | + wb = Workbook() |
| 971 | + ws = wb.active |
| 972 | + ws.title = "TestSheet" |
| 973 | + ws["A1"] = "Merged Header" |
| 974 | + ws["A2"] = "Data1" |
| 975 | + ws["B2"] = "Data2" |
| 976 | + ws["A3"] = "Data3" |
| 977 | + ws["B3"] = "Data4" |
| 978 | + |
| 979 | + # A1:B1を結合 |
| 980 | + ws.merge_cells("A1:B1") |
| 981 | + |
| 982 | + excel_bytes = BytesIO() |
| 983 | + wb.save(excel_bytes) |
| 984 | + excel_bytes.seek(0) |
| 985 | + |
| 986 | + # モックの設定 |
| 987 | + self.mock_download_client.download_file.return_value = excel_bytes.getvalue() |
| 988 | + |
| 989 | + parser = SharePointExcelParser(self.mock_download_client) |
| 990 | + |
| 991 | + # 列範囲指定で解析 |
| 992 | + result = parser.parse_to_json("/test/file.xlsx", cell_range="A:B") |
| 993 | + result_data = json.loads(result) |
| 994 | + |
| 995 | + # 結果検証 |
| 996 | + assert "sheets" in result_data |
| 997 | + assert len(result_data["sheets"]) == 1 |
| 998 | + |
| 999 | + sheet_data = result_data["sheets"][0] |
| 1000 | + assert sheet_data["name"] == "TestSheet" |
| 1001 | + |
| 1002 | + # requested_rangeとeffective_rangeが設定されている |
| 1003 | + assert sheet_data["requested_range"] == "A:B" |
| 1004 | + assert "effective_range" in sheet_data |
| 1005 | + assert sheet_data["effective_range"].startswith("A1:B") |
| 1006 | + |
| 1007 | + # 結合セル情報が取得されている |
| 1008 | + assert "merged_ranges" in sheet_data |
| 1009 | + assert len(sheet_data["merged_ranges"]) == 1 |
| 1010 | + assert sheet_data["merged_ranges"][0]["range"] == "A1:B1" |
| 1011 | + |
| 1012 | + # データも正しく取得されている |
| 1013 | + assert "rows" in sheet_data |
| 1014 | + assert len(sheet_data["rows"]) > 0 |
0 commit comments