regscale-cli 6.25.1.0__py3-none-any.whl → 6.27.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (146) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/application.py +19 -4
  4. regscale/core/app/internal/evidence.py +419 -2
  5. regscale/core/app/internal/login.py +0 -1
  6. regscale/core/app/utils/catalog_utils/common.py +1 -1
  7. regscale/dev/code_gen.py +24 -20
  8. regscale/integrations/commercial/jira.py +367 -126
  9. regscale/integrations/commercial/qualys/__init__.py +7 -8
  10. regscale/integrations/commercial/qualys/scanner.py +8 -3
  11. regscale/integrations/commercial/sicura/api.py +14 -13
  12. regscale/integrations/commercial/sicura/commands.py +8 -2
  13. regscale/integrations/commercial/sicura/scanner.py +49 -39
  14. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  15. regscale/integrations/commercial/synqly/assets.py +17 -0
  16. regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
  17. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  18. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  19. regscale/integrations/commercial/tenablev2/commands.py +142 -1
  20. regscale/integrations/commercial/tenablev2/scanner.py +0 -1
  21. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  22. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  23. regscale/integrations/commercial/wizv2/click.py +64 -79
  24. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  25. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  26. regscale/integrations/commercial/wizv2/compliance_report.py +161 -165
  27. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  28. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
  29. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
  30. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  31. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  32. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
  33. regscale/integrations/commercial/wizv2/issue.py +1 -1
  34. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  35. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  36. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  37. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  38. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  39. regscale/integrations/commercial/wizv2/reports.py +1 -1
  40. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  41. regscale/integrations/commercial/wizv2/scanner.py +39 -99
  42. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  43. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  44. regscale/integrations/commercial/wizv2/variables.py +89 -3
  45. regscale/integrations/compliance_integration.py +60 -41
  46. regscale/integrations/control_matcher.py +377 -0
  47. regscale/integrations/due_date_handler.py +14 -8
  48. regscale/integrations/milestone_manager.py +291 -0
  49. regscale/integrations/public/__init__.py +1 -0
  50. regscale/integrations/public/cci_importer.py +37 -38
  51. regscale/integrations/public/fedramp/click.py +60 -2
  52. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  53. regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
  54. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  55. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  56. regscale/integrations/scanner_integration.py +277 -153
  57. regscale/models/integration_models/cisa_kev_data.json +282 -9
  58. regscale/models/integration_models/nexpose.py +36 -10
  59. regscale/models/integration_models/qualys.py +3 -4
  60. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  61. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
  62. regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
  63. regscale/models/locking.py +12 -8
  64. regscale/models/platform.py +1 -2
  65. regscale/models/regscale_models/control_implementation.py +47 -22
  66. regscale/models/regscale_models/issue.py +256 -95
  67. regscale/models/regscale_models/milestone.py +1 -1
  68. regscale/models/regscale_models/regscale_model.py +6 -1
  69. regscale/templates/__init__.py +0 -0
  70. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
  71. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +145 -65
  72. tests/regscale/integrations/commercial/__init__.py +0 -0
  73. tests/regscale/integrations/commercial/conftest.py +28 -0
  74. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  75. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  76. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  77. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  78. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  79. tests/regscale/integrations/commercial/test_aws.py +3731 -0
  80. tests/regscale/integrations/commercial/test_burp.py +48 -0
  81. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  82. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  83. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  84. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  85. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  86. tests/regscale/integrations/commercial/test_jira.py +2204 -0
  87. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  88. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  89. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  90. tests/regscale/integrations/commercial/test_sicura.py +350 -0
  91. tests/regscale/integrations/commercial/test_snow.py +423 -0
  92. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  93. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  94. tests/regscale/integrations/commercial/test_stig.py +33 -0
  95. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  96. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  97. tests/regscale/integrations/commercial/test_wiz.py +1365 -0
  98. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  99. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  100. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  101. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  102. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  103. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  104. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  105. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  106. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  107. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  108. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  109. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  110. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  111. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  112. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  113. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  114. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  115. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  116. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  117. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  118. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  119. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  120. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  121. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  122. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  123. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  124. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
  125. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  126. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  127. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  128. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  129. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  130. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1132 -0
  131. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
  132. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  133. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  134. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  135. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  136. tests/regscale/integrations/public/test_fedramp.py +301 -0
  137. tests/regscale/integrations/test_control_matcher.py +1397 -0
  138. tests/regscale/integrations/test_control_matching.py +155 -0
  139. tests/regscale/integrations/test_milestone_manager.py +408 -0
  140. tests/regscale/models/test_issue.py +378 -1
  141. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
  142. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  143. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
  144. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
  145. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
  146. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
@@ -849,3 +849,304 @@ class TestFedRamp(CLITestFixture):
849
849
 
850
850
  assert asset.get("ipAddress") is None
851
851
  assert asset.get("iPv6Address") is None
852
+
853
+ @staticmethod
854
+ def test_ssp_parser_vertical_table_single_header():
855
+ """Test vertical table detection with single header (original behavior)."""
856
+ from regscale.integrations.public.fedramp.docx_parser import SSPDocParser
857
+ from unittest.mock import MagicMock
858
+
859
+ xml_content = b"""
860
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
861
+ <w:tbl>
862
+ <w:tr>
863
+ <w:tc><w:p><w:r><w:t>System Owner Information</w:t></w:r></w:p></w:tc>
864
+ </w:tr>
865
+ <w:tr>
866
+ <w:tc><w:p><w:r><w:t>Name</w:t></w:r></w:p></w:tc>
867
+ <w:tc><w:p><w:r><w:t>John Doe</w:t></w:r></w:p></w:tc>
868
+ </w:tr>
869
+ <w:tr>
870
+ <w:tc><w:p><w:r><w:t>Title</w:t></w:r></w:p></w:tc>
871
+ <w:tc><w:p><w:r><w:t>System Owner</w:t></w:r></w:p></w:tc>
872
+ </w:tr>
873
+ </w:tbl>
874
+ </w:document>
875
+ """
876
+
877
+ parser = SSPDocParser.__new__(SSPDocParser)
878
+ parser.doc = MagicMock()
879
+ parser.doc.element.body = [] # No preceding text needed for this test
880
+
881
+ tables = parser.parse_xml_for_tables(xml_content)
882
+
883
+ assert len(tables) == 1
884
+ table_data = tables[0]["table_data"]
885
+ assert len(table_data) == 2
886
+ assert table_data[0] == {"Name": "John Doe"}
887
+ assert table_data[1] == {"Title": "System Owner"}
888
+
889
+ @staticmethod
890
+ def test_ssp_parser_vertical_table_two_headers_empty_second():
891
+ """Test vertical table detection with two headers where second is empty (new fix)."""
892
+ from regscale.integrations.public.fedramp.docx_parser import SSPDocParser
893
+ from unittest.mock import MagicMock
894
+
895
+ xml_content = b"""
896
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
897
+ <w:tbl>
898
+ <w:tr>
899
+ <w:tc><w:p><w:r><w:t>System Information</w:t></w:r></w:p></w:tc>
900
+ <w:tc><w:p><w:r><w:t></w:t></w:r></w:p></w:tc>
901
+ </w:tr>
902
+ <w:tr>
903
+ <w:tc><w:p><w:r><w:t>CSP Name:</w:t></w:r></w:p></w:tc>
904
+ <w:tc><w:p><w:r><w:t>Public Safety Platform (PSP)</w:t></w:r></w:p></w:tc>
905
+ </w:tr>
906
+ <w:tr>
907
+ <w:tc><w:p><w:r><w:t>CSO Name:</w:t></w:r></w:p></w:tc>
908
+ <w:tc><w:p><w:r><w:t>Mark43, Inc.</w:t></w:r></w:p></w:tc>
909
+ </w:tr>
910
+ <w:tr>
911
+ <w:tc><w:p><w:r><w:t>Service Model:</w:t></w:r></w:p></w:tc>
912
+ <w:tc><w:p><w:r><w:t>Software-as-a-Service (SaaS)</w:t></w:r></w:p></w:tc>
913
+ </w:tr>
914
+ </w:tbl>
915
+ </w:document>
916
+ """
917
+
918
+ parser = SSPDocParser.__new__(SSPDocParser)
919
+ parser.doc = MagicMock()
920
+ parser.doc.element.body = [] # No preceding text needed for this test
921
+
922
+ tables = parser.parse_xml_for_tables(xml_content)
923
+
924
+ assert len(tables) == 1
925
+ table_data = tables[0]["table_data"]
926
+ assert len(table_data) == 3
927
+ assert table_data[0] == {"CSP Name:": "Public Safety Platform (PSP)"}
928
+ assert table_data[1] == {"CSO Name:": "Mark43, Inc."}
929
+ assert table_data[2] == {"Service Model:": "Software-as-a-Service (SaaS)"}
930
+
931
+ @staticmethod
932
+ def test_ssp_parser_vertical_table_two_headers_whitespace_second():
933
+ """Test vertical table detection with two headers where second is only whitespace."""
934
+ from regscale.integrations.public.fedramp.docx_parser import SSPDocParser
935
+ from unittest.mock import MagicMock
936
+
937
+ xml_content = b"""
938
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
939
+ <w:tbl>
940
+ <w:tr>
941
+ <w:tc><w:p><w:r><w:t>System Information</w:t></w:r></w:p></w:tc>
942
+ <w:tc><w:p><w:r><w:t> </w:t></w:r></w:p></w:tc>
943
+ </w:tr>
944
+ <w:tr>
945
+ <w:tc><w:p><w:r><w:t>FedRAMP Package ID:</w:t></w:r></w:p></w:tc>
946
+ <w:tc><w:p><w:r><w:t>FR2235965777</w:t></w:r></w:p></w:tc>
947
+ </w:tr>
948
+ <w:tr>
949
+ <w:tc><w:p><w:r><w:t>FIPS PUB 199 Level:</w:t></w:r></w:p></w:tc>
950
+ <w:tc><w:p><w:r><w:t>High</w:t></w:r></w:p></w:tc>
951
+ </w:tr>
952
+ </w:tbl>
953
+ </w:document>
954
+ """
955
+
956
+ parser = SSPDocParser.__new__(SSPDocParser)
957
+ parser.doc = MagicMock()
958
+ parser.doc.element.body = [] # No preceding text needed for this test
959
+
960
+ tables = parser.parse_xml_for_tables(xml_content)
961
+
962
+ assert len(tables) == 1
963
+ table_data = tables[0]["table_data"]
964
+ assert len(table_data) == 2
965
+ assert table_data[0] == {"FedRAMP Package ID:": "FR2235965777"}
966
+ assert table_data[1] == {"FIPS PUB 199 Level:": "High"}
967
+
968
+ @staticmethod
969
+ def test_ssp_parser_horizontal_table_not_detected_as_vertical():
970
+ """Test that horizontal tables with meaningful headers are not detected as vertical."""
971
+ from regscale.integrations.public.fedramp.docx_parser import SSPDocParser
972
+ from unittest.mock import MagicMock
973
+
974
+ xml_content = b"""
975
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
976
+ <w:tbl>
977
+ <w:tr>
978
+ <w:tc><w:p><w:r><w:t>Date</w:t></w:r></w:p></w:tc>
979
+ <w:tc><w:p><w:r><w:t>Description</w:t></w:r></w:p></w:tc>
980
+ <w:tc><w:p><w:r><w:t>Version</w:t></w:r></w:p></w:tc>
981
+ </w:tr>
982
+ <w:tr>
983
+ <w:tc><w:p><w:r><w:t>05/14/2024</w:t></w:r></w:p></w:tc>
984
+ <w:tc><w:p><w:r><w:t>Initial version</w:t></w:r></w:p></w:tc>
985
+ <w:tc><w:p><w:r><w:t>1.0</w:t></w:r></w:p></w:tc>
986
+ </w:tr>
987
+ <w:tr>
988
+ <w:tc><w:p><w:r><w:t>11/01/2024</w:t></w:r></w:p></w:tc>
989
+ <w:tc><w:p><w:r><w:t>Updated version</w:t></w:r></w:p></w:tc>
990
+ <w:tc><w:p><w:r><w:t>2.0</w:t></w:r></w:p></w:tc>
991
+ </w:tr>
992
+ </w:tbl>
993
+ </w:document>
994
+ """
995
+
996
+ parser = SSPDocParser.__new__(SSPDocParser)
997
+ parser.doc = MagicMock()
998
+ parser.doc.element.body = [] # No preceding text needed for this test
999
+
1000
+ tables = parser.parse_xml_for_tables(xml_content)
1001
+
1002
+ assert len(tables) == 1
1003
+ table_data = tables[0]["table_data"]
1004
+ assert len(table_data) == 2
1005
+ # Should be horizontal format with named columns
1006
+ assert table_data[0] == {"Date": "05/14/2024", "Description": "Initial version", "Version": "1.0"}
1007
+ assert table_data[1] == {"Date": "11/01/2024", "Description": "Updated version", "Version": "2.0"}
1008
+
1009
+ @staticmethod
1010
+ def test_ssp_parser_two_column_table_non_vertical():
1011
+ """Test that 2-column tables with meaningful second header are not detected as vertical."""
1012
+ from regscale.integrations.public.fedramp.docx_parser import SSPDocParser
1013
+ from unittest.mock import MagicMock
1014
+
1015
+ xml_content = b"""
1016
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1017
+ <w:tbl>
1018
+ <w:tr>
1019
+ <w:tc><w:p><w:r><w:t>Security Objective</w:t></w:r></w:p></w:tc>
1020
+ <w:tc><w:p><w:r><w:t>Level</w:t></w:r></w:p></w:tc>
1021
+ </w:tr>
1022
+ <w:tr>
1023
+ <w:tc><w:p><w:r><w:t>Confidentiality</w:t></w:r></w:p></w:tc>
1024
+ <w:tc><w:p><w:r><w:t>High</w:t></w:r></w:p></w:tc>
1025
+ </w:tr>
1026
+ <w:tr>
1027
+ <w:tc><w:p><w:r><w:t>Integrity</w:t></w:r></w:p></w:tc>
1028
+ <w:tc><w:p><w:r><w:t>High</w:t></w:r></w:p></w:tc>
1029
+ </w:tr>
1030
+ </w:tbl>
1031
+ </w:document>
1032
+ """
1033
+
1034
+ parser = SSPDocParser.__new__(SSPDocParser)
1035
+ parser.doc = MagicMock()
1036
+ parser.doc.element.body = [] # No preceding text needed for this test
1037
+
1038
+ tables = parser.parse_xml_for_tables(xml_content)
1039
+
1040
+ assert len(tables) == 1
1041
+ table_data = tables[0]["table_data"]
1042
+ assert len(table_data) == 2
1043
+ # Should be horizontal format since second header is not empty
1044
+ assert table_data[0] == {"Security Objective": "Confidentiality", "Level": "High"}
1045
+ assert table_data[1] == {"Security Objective": "Integrity", "Level": "High"}
1046
+
1047
+ @staticmethod
1048
+ def test_ssp_parser_vertical_table_first_header_not_in_list():
1049
+ """Test that tables with first header not in vertical_tables list are not detected as vertical."""
1050
+ from regscale.integrations.public.fedramp.docx_parser import SSPDocParser
1051
+ from unittest.mock import MagicMock
1052
+
1053
+ xml_content = b"""
1054
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1055
+ <w:tbl>
1056
+ <w:tr>
1057
+ <w:tc><w:p><w:r><w:t>Random Table Header</w:t></w:r></w:p></w:tc>
1058
+ <w:tc><w:p><w:r><w:t></w:t></w:r></w:p></w:tc>
1059
+ </w:tr>
1060
+ <w:tr>
1061
+ <w:tc><w:p><w:r><w:t>Field One:</w:t></w:r></w:p></w:tc>
1062
+ <w:tc><w:p><w:r><w:t>Value One</w:t></w:r></w:p></w:tc>
1063
+ </w:tr>
1064
+ <w:tr>
1065
+ <w:tc><w:p><w:r><w:t>Field Two:</w:t></w:r></w:p></w:tc>
1066
+ <w:tc><w:p><w:r><w:t>Value Two</w:t></w:r></w:p></w:tc>
1067
+ </w:tr>
1068
+ </w:tbl>
1069
+ </w:document>
1070
+ """
1071
+
1072
+ parser = SSPDocParser.__new__(SSPDocParser)
1073
+ parser.doc = MagicMock()
1074
+ parser.doc.element.body = [] # No preceding text needed for this test
1075
+
1076
+ tables = parser.parse_xml_for_tables(xml_content)
1077
+
1078
+ assert len(tables) == 1
1079
+ table_data = tables[0]["table_data"]
1080
+ assert len(table_data) == 2
1081
+ # Should be horizontal format since "Random Table Header" is not in vertical_tables list
1082
+ assert table_data[0] == {"Random Table Header": "Field One:", "": "Value One"}
1083
+ assert table_data[1] == {"Random Table Header": "Field Two:", "": "Value Two"}
1084
+
1085
+ @staticmethod
1086
+ def test_ssp_parser_all_vertical_table_types():
1087
+ """Test all table types that should be detected as vertical."""
1088
+ from regscale.integrations.public.fedramp.docx_parser import SSPDocParser
1089
+ from unittest.mock import MagicMock
1090
+
1091
+ vertical_table_headers = [
1092
+ "Identification of Organization that Prepared this Document",
1093
+ "Identification of Cloud Service Provider",
1094
+ "System Owner Information",
1095
+ "System Information",
1096
+ "System Component Information",
1097
+ "ISSO (or Equivalent) Point of Contact",
1098
+ ]
1099
+
1100
+ for header in vertical_table_headers:
1101
+ # Test with single header
1102
+ xml_content = f"""
1103
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1104
+ <w:tbl>
1105
+ <w:tr>
1106
+ <w:tc><w:p><w:r><w:t>{header}</w:t></w:r></w:p></w:tc>
1107
+ </w:tr>
1108
+ <w:tr>
1109
+ <w:tc><w:p><w:r><w:t>Field:</w:t></w:r></w:p></w:tc>
1110
+ <w:tc><w:p><w:r><w:t>Value</w:t></w:r></w:p></w:tc>
1111
+ </w:tr>
1112
+ </w:tbl>
1113
+ </w:document>
1114
+ """.encode()
1115
+
1116
+ parser = SSPDocParser.__new__(SSPDocParser)
1117
+ parser.doc = MagicMock()
1118
+ parser.doc.element.body = [] # No preceding text needed for this test
1119
+
1120
+ tables = parser.parse_xml_for_tables(xml_content)
1121
+
1122
+ assert len(tables) == 1
1123
+ table_data = tables[0]["table_data"]
1124
+ assert len(table_data) == 1
1125
+ assert table_data[0] == {"Field:": "Value"}
1126
+
1127
+ # Test with two headers (empty second)
1128
+ xml_content_two_headers = f"""
1129
+ <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
1130
+ <w:tbl>
1131
+ <w:tr>
1132
+ <w:tc><w:p><w:r><w:t>{header}</w:t></w:r></w:p></w:tc>
1133
+ <w:tc><w:p><w:r><w:t></w:t></w:r></w:p></w:tc>
1134
+ </w:tr>
1135
+ <w:tr>
1136
+ <w:tc><w:p><w:r><w:t>Field:</w:t></w:r></w:p></w:tc>
1137
+ <w:tc><w:p><w:r><w:t>Value</w:t></w:r></w:p></w:tc>
1138
+ </w:tr>
1139
+ </w:tbl>
1140
+ </w:document>
1141
+ """.encode()
1142
+
1143
+ parser2 = SSPDocParser.__new__(SSPDocParser)
1144
+ parser2.doc = MagicMock()
1145
+ parser2.doc.element.body = [] # No preceding text needed for this test
1146
+
1147
+ tables2 = parser2.parse_xml_for_tables(xml_content_two_headers)
1148
+
1149
+ assert len(tables2) == 1
1150
+ table_data2 = tables2[0]["table_data"]
1151
+ assert len(table_data2) == 1
1152
+ assert table_data2[0] == {"Field:": "Value"}