regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.1.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 (113) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/utils/app_utils.py +11 -2
  3. regscale/dev/cli.py +26 -0
  4. regscale/dev/version.py +72 -0
  5. regscale/integrations/commercial/__init__.py +15 -1
  6. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  7. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  8. regscale/integrations/commercial/amazon/common.py +48 -58
  9. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  10. regscale/integrations/commercial/aws/cli.py +3093 -55
  11. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  12. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  13. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  14. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  15. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  16. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  17. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  18. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  19. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  20. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  21. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  22. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  23. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  24. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  25. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  26. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  27. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  28. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  29. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  30. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  31. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  32. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  33. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  34. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  35. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  36. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  37. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  38. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  39. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  40. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  41. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  42. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  43. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  44. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  45. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  46. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  47. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  48. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  49. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  50. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  51. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  52. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  53. regscale/integrations/commercial/aws/scanner.py +851 -206
  54. regscale/integrations/commercial/aws/security_hub.py +319 -0
  55. regscale/integrations/commercial/aws/session_manager.py +282 -0
  56. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  57. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  58. regscale/integrations/commercial/synqly/ticketing.py +27 -0
  59. regscale/integrations/compliance_integration.py +308 -38
  60. regscale/integrations/due_date_handler.py +3 -0
  61. regscale/integrations/scanner_integration.py +399 -84
  62. regscale/models/integration_models/cisa_kev_data.json +65 -5
  63. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  64. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
  65. regscale/models/regscale_models/assessment.py +2 -1
  66. regscale/models/regscale_models/control_objective.py +74 -5
  67. regscale/models/regscale_models/file.py +2 -0
  68. regscale/models/regscale_models/issue.py +2 -5
  69. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/METADATA +1 -1
  70. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/RECORD +113 -34
  71. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  72. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  73. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  74. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  75. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  76. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  77. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  78. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  79. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  80. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  81. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  82. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  83. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  84. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  85. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  86. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  87. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  88. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  89. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  90. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  91. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  92. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  93. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  94. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  95. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  96. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  97. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  98. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  99. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  100. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  101. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  102. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  103. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  104. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  105. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  106. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  107. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  108. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  109. tests/regscale/integrations/commercial/test_aws.py +55 -56
  110. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/LICENSE +0 -0
  111. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/WHEEL +0 -0
  112. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/entry_points.txt +0 -0
  113. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,588 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Unit tests for AWS Systems Manager Control Mappings."""
4
+
5
+ import pytest
6
+
7
+ from regscale.integrations.commercial.aws.ssm_control_mappings import (
8
+ SSM_CONTROL_MAPPINGS,
9
+ SSMControlMapper,
10
+ )
11
+
12
+
13
+ class TestSSMControlMappings:
14
+ """Test SSM control mappings constants."""
15
+
16
+ def test_ssm_control_mappings_exist(self):
17
+ """Test that SSM control mappings dictionary exists and has content."""
18
+ assert len(SSM_CONTROL_MAPPINGS) > 0
19
+ assert "CM-2" in SSM_CONTROL_MAPPINGS
20
+ assert "CM-6" in SSM_CONTROL_MAPPINGS
21
+ assert "SI-2" in SSM_CONTROL_MAPPINGS
22
+ assert "CM-3" in SSM_CONTROL_MAPPINGS
23
+ assert "CM-8" in SSM_CONTROL_MAPPINGS
24
+
25
+ def test_cm2_mapping_structure(self):
26
+ """Test CM-2 mapping structure."""
27
+ cm2 = SSM_CONTROL_MAPPINGS["CM-2"]
28
+ assert "name" in cm2
29
+ assert "description" in cm2
30
+ assert "checks" in cm2
31
+ assert "managed_instances" in cm2["checks"]
32
+ assert "inventory_collection" in cm2["checks"]
33
+ assert "state_manager" in cm2["checks"]
34
+
35
+ def test_cm6_mapping_structure(self):
36
+ """Test CM-6 mapping structure."""
37
+ cm6 = SSM_CONTROL_MAPPINGS["CM-6"]
38
+ assert "name" in cm6
39
+ assert "description" in cm6
40
+ assert "checks" in cm6
41
+ assert "ssm_documents" in cm6["checks"]
42
+ assert "parameters" in cm6["checks"]
43
+ assert "associations" in cm6["checks"]
44
+
45
+ def test_si2_mapping_structure(self):
46
+ """Test SI-2 mapping structure."""
47
+ si2 = SSM_CONTROL_MAPPINGS["SI-2"]
48
+ assert "name" in si2
49
+ assert "description" in si2
50
+ assert "checks" in si2
51
+ assert "patch_baselines" in si2["checks"]
52
+ assert "patch_compliance" in si2["checks"]
53
+ assert "maintenance_windows" in si2["checks"]
54
+
55
+ def test_cm3_mapping_structure(self):
56
+ """Test CM-3 mapping structure."""
57
+ cm3 = SSM_CONTROL_MAPPINGS["CM-3"]
58
+ assert "name" in cm3
59
+ assert "description" in cm3
60
+ assert "checks" in cm3
61
+ assert "automation_documents" in cm3["checks"]
62
+
63
+ def test_cm8_mapping_structure(self):
64
+ """Test CM-8 mapping structure."""
65
+ cm8 = SSM_CONTROL_MAPPINGS["CM-8"]
66
+ assert "name" in cm8
67
+ assert "description" in cm8
68
+ assert "checks" in cm8
69
+ assert "inventory_data" in cm8["checks"]
70
+
71
+
72
+ class TestSSMControlMapperInitialization:
73
+ """Test SSMControlMapper initialization."""
74
+
75
+ def test_init_with_nist_framework(self):
76
+ """Test initialization with NIST framework."""
77
+ mapper = SSMControlMapper(framework="NIST800-53R5")
78
+ assert mapper.framework == "NIST800-53R5"
79
+ assert mapper.mappings == SSM_CONTROL_MAPPINGS
80
+
81
+ def test_init_default_framework(self):
82
+ """Test initialization with default framework."""
83
+ mapper = SSMControlMapper()
84
+ assert mapper.framework == "NIST800-53R5"
85
+ assert mapper.mappings == SSM_CONTROL_MAPPINGS
86
+
87
+
88
+ class TestAssessSSMCompliance:
89
+ """Test assess_ssm_compliance method."""
90
+
91
+ def test_assess_compliant_ssm_configuration(self):
92
+ """Test assessing a fully compliant SSM configuration."""
93
+ mapper = SSMControlMapper()
94
+ ssm_data = {
95
+ "ManagedInstances": [
96
+ {"InstanceId": "i-123", "PingStatus": "Online", "PatchSummary": {"Missing": 0}},
97
+ {"InstanceId": "i-456", "PingStatus": "Online", "PatchSummary": {"Missing": 0}},
98
+ ],
99
+ "Associations": [{"AssociationId": "assoc-1"}],
100
+ "Documents": [
101
+ {"Name": "ConfigDoc", "DocumentType": "Command"},
102
+ {"Name": "AutoDoc", "DocumentType": "Automation"},
103
+ ],
104
+ "Parameters": [{"Name": "param1", "Type": "String"}],
105
+ "PatchBaselines": [{"BaselineId": "pb-123", "OperatingSystem": "AMAZON_LINUX_2"}],
106
+ "MaintenanceWindows": [{"WindowId": "mw-123"}],
107
+ }
108
+
109
+ results = mapper.assess_ssm_compliance(ssm_data)
110
+
111
+ assert results["CM-2"] == "PASS"
112
+ assert results["CM-6"] == "PASS"
113
+ assert results["SI-2"] == "PASS"
114
+ assert results["CM-3"] == "PASS"
115
+ assert results["CM-8"] == "PASS"
116
+
117
+ def test_assess_ssm_without_managed_instances(self):
118
+ """Test assessing SSM without managed instances."""
119
+ mapper = SSMControlMapper()
120
+ ssm_data = {
121
+ "ManagedInstances": [],
122
+ "Associations": [{"AssociationId": "assoc-1"}],
123
+ "Documents": [{"Name": "Doc1", "DocumentType": "Command"}],
124
+ "Parameters": [{"Name": "param1"}],
125
+ "PatchBaselines": [{"BaselineId": "pb-123"}],
126
+ }
127
+
128
+ results = mapper.assess_ssm_compliance(ssm_data)
129
+
130
+ assert results["CM-2"] == "FAIL"
131
+ assert results["CM-8"] == "FAIL"
132
+
133
+ def test_assess_ssm_without_patch_baselines(self):
134
+ """Test assessing SSM without patch baselines."""
135
+ mapper = SSMControlMapper()
136
+ ssm_data = {
137
+ "ManagedInstances": [{"InstanceId": "i-123", "PingStatus": "Online"}],
138
+ "Associations": [{"AssociationId": "assoc-1"}],
139
+ "Documents": [{"Name": "Doc1", "DocumentType": "Automation"}],
140
+ "Parameters": [{"Name": "param1"}],
141
+ "PatchBaselines": [],
142
+ }
143
+
144
+ results = mapper.assess_ssm_compliance(ssm_data)
145
+
146
+ assert results["SI-2"] == "FAIL"
147
+
148
+
149
+ class TestAssessCM2:
150
+ """Test _assess_cm2 method."""
151
+
152
+ def test_ssm_with_online_instances_and_associations_passes(self):
153
+ """Test SSM with online instances and associations passes."""
154
+ mapper = SSMControlMapper()
155
+ ssm_data = {
156
+ "ManagedInstances": [{"InstanceId": "i-123", "PingStatus": "Online"}],
157
+ "Associations": [{"AssociationId": "assoc-1"}],
158
+ }
159
+
160
+ result = mapper._assess_cm2(ssm_data)
161
+ assert result == "PASS"
162
+
163
+ def test_ssm_without_managed_instances_fails(self):
164
+ """Test SSM without managed instances fails."""
165
+ mapper = SSMControlMapper()
166
+ ssm_data = {"ManagedInstances": [], "Associations": [{"AssociationId": "assoc-1"}]}
167
+
168
+ result = mapper._assess_cm2(ssm_data)
169
+ assert result == "FAIL"
170
+
171
+ def test_ssm_with_offline_instances_fails(self):
172
+ """Test SSM with only offline instances fails."""
173
+ mapper = SSMControlMapper()
174
+ ssm_data = {
175
+ "ManagedInstances": [{"InstanceId": "i-123", "PingStatus": "ConnectionLost"}],
176
+ "Associations": [{"AssociationId": "assoc-1"}],
177
+ }
178
+
179
+ result = mapper._assess_cm2(ssm_data)
180
+ assert result == "FAIL"
181
+
182
+ def test_ssm_without_associations_fails(self):
183
+ """Test SSM without associations fails."""
184
+ mapper = SSMControlMapper()
185
+ ssm_data = {"ManagedInstances": [{"InstanceId": "i-123", "PingStatus": "Online"}], "Associations": []}
186
+
187
+ result = mapper._assess_cm2(ssm_data)
188
+ assert result == "FAIL"
189
+
190
+ def test_ssm_with_mixed_instance_status_passes(self):
191
+ """Test SSM with mixed instance status passes if at least one is online."""
192
+ mapper = SSMControlMapper()
193
+ ssm_data = {
194
+ "ManagedInstances": [
195
+ {"InstanceId": "i-123", "PingStatus": "Online"},
196
+ {"InstanceId": "i-456", "PingStatus": "ConnectionLost"},
197
+ ],
198
+ "Associations": [{"AssociationId": "assoc-1"}],
199
+ }
200
+
201
+ result = mapper._assess_cm2(ssm_data)
202
+ assert result == "PASS"
203
+
204
+
205
+ class TestAssessCM6:
206
+ """Test _assess_cm6 method."""
207
+
208
+ def test_ssm_with_documents_parameters_and_associations_passes(self):
209
+ """Test SSM with documents, parameters, and associations passes."""
210
+ mapper = SSMControlMapper()
211
+ ssm_data = {
212
+ "Documents": [{"Name": "Doc1"}],
213
+ "Parameters": [{"Name": "param1"}],
214
+ "Associations": [{"AssociationId": "assoc-1"}],
215
+ }
216
+
217
+ result = mapper._assess_cm6(ssm_data)
218
+ assert result == "PASS"
219
+
220
+ def test_ssm_without_documents_fails(self):
221
+ """Test SSM without documents fails."""
222
+ mapper = SSMControlMapper()
223
+ ssm_data = {"Documents": [], "Parameters": [{"Name": "param1"}], "Associations": [{"AssociationId": "assoc-1"}]}
224
+
225
+ result = mapper._assess_cm6(ssm_data)
226
+ assert result == "FAIL"
227
+
228
+ def test_ssm_without_parameters_fails(self):
229
+ """Test SSM without parameters fails."""
230
+ mapper = SSMControlMapper()
231
+ ssm_data = {"Documents": [{"Name": "Doc1"}], "Parameters": [], "Associations": [{"AssociationId": "assoc-1"}]}
232
+
233
+ result = mapper._assess_cm6(ssm_data)
234
+ assert result == "FAIL"
235
+
236
+ def test_ssm_without_associations_fails(self):
237
+ """Test SSM without associations fails."""
238
+ mapper = SSMControlMapper()
239
+ ssm_data = {"Documents": [{"Name": "Doc1"}], "Parameters": [{"Name": "param1"}], "Associations": []}
240
+
241
+ result = mapper._assess_cm6(ssm_data)
242
+ assert result == "FAIL"
243
+
244
+
245
+ class TestAssessSI2:
246
+ """Test _assess_si2 method."""
247
+
248
+ def test_ssm_with_patch_baselines_and_compliant_instances_passes(self):
249
+ """Test SSM with patch baselines and compliant instances passes."""
250
+ mapper = SSMControlMapper()
251
+ ssm_data = {
252
+ "PatchBaselines": [{"BaselineId": "pb-123"}],
253
+ "ManagedInstances": [{"InstanceId": "i-123", "PatchSummary": {"Missing": 0}}],
254
+ "MaintenanceWindows": [{"WindowId": "mw-123"}],
255
+ }
256
+
257
+ result = mapper._assess_si2(ssm_data)
258
+ assert result == "PASS"
259
+
260
+ def test_ssm_without_patch_baselines_fails(self):
261
+ """Test SSM without patch baselines fails."""
262
+ mapper = SSMControlMapper()
263
+ ssm_data = {
264
+ "PatchBaselines": [],
265
+ "ManagedInstances": [{"InstanceId": "i-123", "PatchSummary": {"Missing": 0}}],
266
+ "MaintenanceWindows": [],
267
+ }
268
+
269
+ result = mapper._assess_si2(ssm_data)
270
+ assert result == "FAIL"
271
+
272
+ def test_ssm_without_patch_data_fails(self):
273
+ """Test SSM without patch data on instances fails."""
274
+ mapper = SSMControlMapper()
275
+ ssm_data = {
276
+ "PatchBaselines": [{"BaselineId": "pb-123"}],
277
+ "ManagedInstances": [{"InstanceId": "i-123"}],
278
+ "MaintenanceWindows": [],
279
+ }
280
+
281
+ result = mapper._assess_si2(ssm_data)
282
+ assert result == "FAIL"
283
+
284
+ def test_ssm_with_missing_patches_fails(self):
285
+ """Test SSM with missing patches fails."""
286
+ mapper = SSMControlMapper()
287
+ ssm_data = {
288
+ "PatchBaselines": [{"BaselineId": "pb-123"}],
289
+ "ManagedInstances": [
290
+ {"InstanceId": "i-123", "PatchSummary": {"Missing": 5}},
291
+ {"InstanceId": "i-456", "PatchSummary": {"Missing": 3}},
292
+ ],
293
+ "MaintenanceWindows": [],
294
+ }
295
+
296
+ result = mapper._assess_si2(ssm_data)
297
+ assert result == "FAIL"
298
+
299
+ def test_ssm_with_patch_baselines_but_no_instances_passes(self):
300
+ """Test SSM with patch baselines but no instances passes."""
301
+ mapper = SSMControlMapper()
302
+ ssm_data = {"PatchBaselines": [{"BaselineId": "pb-123"}], "ManagedInstances": [], "MaintenanceWindows": []}
303
+
304
+ result = mapper._assess_si2(ssm_data)
305
+ assert result == "PASS"
306
+
307
+ def test_ssm_without_maintenance_windows_passes_with_note(self):
308
+ """Test SSM without maintenance windows passes but notes recommendation."""
309
+ mapper = SSMControlMapper()
310
+ ssm_data = {
311
+ "PatchBaselines": [{"BaselineId": "pb-123"}],
312
+ "ManagedInstances": [{"InstanceId": "i-123", "PatchSummary": {"Missing": 0}}],
313
+ "MaintenanceWindows": [],
314
+ }
315
+
316
+ result = mapper._assess_si2(ssm_data)
317
+ assert result == "PASS"
318
+
319
+
320
+ class TestAssessCM3:
321
+ """Test _assess_cm3 method."""
322
+
323
+ def test_ssm_with_automation_documents_passes(self):
324
+ """Test SSM with automation documents passes."""
325
+ mapper = SSMControlMapper()
326
+ ssm_data = {"Documents": [{"Name": "AutoDoc", "DocumentType": "Automation"}]}
327
+
328
+ result = mapper._assess_cm3(ssm_data)
329
+ assert result == "PASS"
330
+
331
+ def test_ssm_without_automation_documents_fails(self):
332
+ """Test SSM without automation documents fails."""
333
+ mapper = SSMControlMapper()
334
+ ssm_data = {"Documents": [{"Name": "CommandDoc", "DocumentType": "Command"}]}
335
+
336
+ result = mapper._assess_cm3(ssm_data)
337
+ assert result == "FAIL"
338
+
339
+ def test_ssm_with_no_documents_fails(self):
340
+ """Test SSM with no documents fails."""
341
+ mapper = SSMControlMapper()
342
+ ssm_data = {"Documents": []}
343
+
344
+ result = mapper._assess_cm3(ssm_data)
345
+ assert result == "FAIL"
346
+
347
+ def test_ssm_with_multiple_automation_documents_passes(self):
348
+ """Test SSM with multiple automation documents passes."""
349
+ mapper = SSMControlMapper()
350
+ ssm_data = {
351
+ "Documents": [
352
+ {"Name": "AutoDoc1", "DocumentType": "Automation"},
353
+ {"Name": "AutoDoc2", "DocumentType": "Automation"},
354
+ {"Name": "CommandDoc", "DocumentType": "Command"},
355
+ ]
356
+ }
357
+
358
+ result = mapper._assess_cm3(ssm_data)
359
+ assert result == "PASS"
360
+
361
+
362
+ class TestAssessCM8:
363
+ """Test _assess_cm8 method."""
364
+
365
+ def test_ssm_with_online_instances_passes(self):
366
+ """Test SSM with online instances passes."""
367
+ mapper = SSMControlMapper()
368
+ ssm_data = {"ManagedInstances": [{"InstanceId": "i-123", "PingStatus": "Online"}]}
369
+
370
+ result = mapper._assess_cm8(ssm_data)
371
+ assert result == "PASS"
372
+
373
+ def test_ssm_without_managed_instances_fails(self):
374
+ """Test SSM without managed instances fails."""
375
+ mapper = SSMControlMapper()
376
+ ssm_data = {"ManagedInstances": []}
377
+
378
+ result = mapper._assess_cm8(ssm_data)
379
+ assert result == "FAIL"
380
+
381
+ def test_ssm_with_offline_instances_fails(self):
382
+ """Test SSM with only offline instances fails."""
383
+ mapper = SSMControlMapper()
384
+ ssm_data = {
385
+ "ManagedInstances": [
386
+ {"InstanceId": "i-123", "PingStatus": "ConnectionLost"},
387
+ {"InstanceId": "i-456", "PingStatus": "Inactive"},
388
+ ]
389
+ }
390
+
391
+ result = mapper._assess_cm8(ssm_data)
392
+ assert result == "FAIL"
393
+
394
+ def test_ssm_with_mixed_instance_status_passes(self):
395
+ """Test SSM with mixed instance status passes if at least one is online."""
396
+ mapper = SSMControlMapper()
397
+ ssm_data = {
398
+ "ManagedInstances": [
399
+ {"InstanceId": "i-123", "PingStatus": "Online"},
400
+ {"InstanceId": "i-456", "PingStatus": "ConnectionLost"},
401
+ ]
402
+ }
403
+
404
+ result = mapper._assess_cm8(ssm_data)
405
+ assert result == "PASS"
406
+
407
+
408
+ class TestGetControlDescription:
409
+ """Test get_control_description method."""
410
+
411
+ def test_get_cm2_description(self):
412
+ """Test getting CM-2 description."""
413
+ mapper = SSMControlMapper()
414
+ description = mapper.get_control_description("CM-2")
415
+
416
+ assert description is not None
417
+ assert "Baseline Configuration" in description
418
+
419
+ def test_get_cm6_description(self):
420
+ """Test getting CM-6 description."""
421
+ mapper = SSMControlMapper()
422
+ description = mapper.get_control_description("CM-6")
423
+
424
+ assert description is not None
425
+ assert "Configuration Settings" in description
426
+
427
+ def test_get_si2_description(self):
428
+ """Test getting SI-2 description."""
429
+ mapper = SSMControlMapper()
430
+ description = mapper.get_control_description("SI-2")
431
+
432
+ assert description is not None
433
+ assert "Flaw Remediation" in description
434
+
435
+ def test_get_cm3_description(self):
436
+ """Test getting CM-3 description."""
437
+ mapper = SSMControlMapper()
438
+ description = mapper.get_control_description("CM-3")
439
+
440
+ assert description is not None
441
+ assert "Configuration Change Control" in description
442
+
443
+ def test_get_cm8_description(self):
444
+ """Test getting CM-8 description."""
445
+ mapper = SSMControlMapper()
446
+ description = mapper.get_control_description("CM-8")
447
+
448
+ assert description is not None
449
+ assert "System Component Inventory" in description
450
+
451
+ def test_get_unknown_control_description(self):
452
+ """Test getting description for unknown control."""
453
+ mapper = SSMControlMapper()
454
+ description = mapper.get_control_description("UNKNOWN-1")
455
+
456
+ assert description is None
457
+
458
+
459
+ class TestGetMappedControls:
460
+ """Test get_mapped_controls method."""
461
+
462
+ def test_get_mapped_controls(self):
463
+ """Test getting all mapped controls."""
464
+ mapper = SSMControlMapper()
465
+ controls = mapper.get_mapped_controls()
466
+
467
+ assert len(controls) == 5
468
+ assert "CM-2" in controls
469
+ assert "CM-6" in controls
470
+ assert "SI-2" in controls
471
+ assert "CM-3" in controls
472
+ assert "CM-8" in controls
473
+
474
+ def test_controls_are_unique(self):
475
+ """Test that returned controls are unique."""
476
+ mapper = SSMControlMapper()
477
+ controls = mapper.get_mapped_controls()
478
+
479
+ assert len(controls) == len(set(controls))
480
+
481
+
482
+ class TestGetCheckDetails:
483
+ """Test get_check_details method."""
484
+
485
+ def test_get_cm2_check_details(self):
486
+ """Test getting CM-2 check details."""
487
+ mapper = SSMControlMapper()
488
+ details = mapper.get_check_details("CM-2")
489
+
490
+ assert details is not None
491
+ assert "managed_instances" in details
492
+ assert "inventory_collection" in details
493
+ assert "state_manager" in details
494
+ assert details["managed_instances"]["weight"] == 100
495
+
496
+ def test_get_si2_check_details(self):
497
+ """Test getting SI-2 check details."""
498
+ mapper = SSMControlMapper()
499
+ details = mapper.get_check_details("SI-2")
500
+
501
+ assert details is not None
502
+ assert "patch_baselines" in details
503
+ assert "patch_compliance" in details
504
+ assert "maintenance_windows" in details
505
+
506
+ def test_get_cm3_check_details(self):
507
+ """Test getting CM-3 check details."""
508
+ mapper = SSMControlMapper()
509
+ details = mapper.get_check_details("CM-3")
510
+
511
+ assert details is not None
512
+ assert "automation_documents" in details
513
+
514
+ def test_get_unknown_control_check_details(self):
515
+ """Test getting check details for unknown control."""
516
+ mapper = SSMControlMapper()
517
+ details = mapper.get_check_details("UNKNOWN-1")
518
+
519
+ assert details is None
520
+
521
+ def test_check_details_structure(self):
522
+ """Test check details have required structure."""
523
+ mapper = SSMControlMapper()
524
+ details = mapper.get_check_details("CM-2")
525
+
526
+ for check_name, check_data in details.items():
527
+ assert "weight" in check_data
528
+ assert "pass_criteria" in check_data
529
+ assert "fail_criteria" in check_data
530
+
531
+
532
+ class TestEdgeCases:
533
+ """Test edge cases and error handling."""
534
+
535
+ def test_empty_ssm_data(self):
536
+ """Test assessment with completely empty SSM data."""
537
+ mapper = SSMControlMapper()
538
+ ssm_data = {}
539
+
540
+ results = mapper.assess_ssm_compliance(ssm_data)
541
+
542
+ # All controls should fail with empty data
543
+ assert results["CM-2"] == "FAIL"
544
+ assert results["CM-6"] == "FAIL"
545
+ assert results["SI-2"] == "FAIL"
546
+ assert results["CM-3"] == "FAIL"
547
+ assert results["CM-8"] == "FAIL"
548
+
549
+ def test_ssm_data_with_none_values(self):
550
+ """Test assessment with None values in SSM data."""
551
+ mapper = SSMControlMapper()
552
+ ssm_data = {
553
+ "ManagedInstances": None,
554
+ "Associations": None,
555
+ "Documents": None,
556
+ "Parameters": None,
557
+ "PatchBaselines": None,
558
+ }
559
+
560
+ # The source code doesn't handle None values, so this will raise TypeError
561
+ # Testing that the code fails appropriately when given None instead of lists
562
+ with pytest.raises(TypeError):
563
+ mapper.assess_ssm_compliance(ssm_data)
564
+
565
+ def test_missing_ping_status(self):
566
+ """Test instances without PingStatus attribute."""
567
+ mapper = SSMControlMapper()
568
+ ssm_data = {
569
+ "ManagedInstances": [{"InstanceId": "i-123"}],
570
+ "Associations": [{"AssociationId": "assoc-1"}],
571
+ }
572
+
573
+ result = mapper._assess_cm2(ssm_data)
574
+ # Should fail because no instances have Online status
575
+ assert result == "FAIL"
576
+
577
+ def test_missing_document_type(self):
578
+ """Test documents without DocumentType attribute."""
579
+ mapper = SSMControlMapper()
580
+ ssm_data = {"Documents": [{"Name": "Doc1"}]}
581
+
582
+ result = mapper._assess_cm3(ssm_data)
583
+ # Should fail because no Automation documents found
584
+ assert result == "FAIL"
585
+
586
+
587
+ if __name__ == "__main__":
588
+ pytest.main([__file__, "-v"])