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,353 @@
1
+ """Unit tests for AWS Inspector collector."""
2
+
3
+ import unittest
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+ from botocore.exceptions import ClientError
8
+
9
+ from regscale.integrations.commercial.aws.inventory.resources.inspector import InspectorCollector
10
+
11
+
12
+ class TestInspectorCollector(unittest.TestCase):
13
+ """Test cases for InspectorCollector."""
14
+
15
+ def setUp(self):
16
+ """Set up test fixtures."""
17
+ self.mock_session = MagicMock()
18
+ self.region = "us-east-1"
19
+ self.account_id = "123456789012"
20
+ self.collector = InspectorCollector(self.mock_session, self.region, self.account_id)
21
+
22
+ def test_init(self):
23
+ """Test InspectorCollector initialization."""
24
+ assert self.collector.session == self.mock_session
25
+ assert self.collector.region == self.region
26
+ assert self.collector.account_id == self.account_id
27
+
28
+ def test_init_without_account_id(self):
29
+ """Test InspectorCollector initialization without account ID."""
30
+ collector = InspectorCollector(self.mock_session, self.region)
31
+ assert collector.account_id is None
32
+
33
+ @patch("regscale.integrations.commercial.aws.inventory.resources.inspector.logger")
34
+ def test_collect_success(self, mock_logger):
35
+ """Test successful collection of Inspector resources."""
36
+ mock_client = MagicMock()
37
+ self.mock_session.client.return_value = mock_client
38
+
39
+ mock_client.batch_get_account_status.return_value = {
40
+ "accounts": [{"accountId": self.account_id, "state": {"status": "ENABLED"}}]
41
+ }
42
+
43
+ mock_client.list_coverage.return_value = {
44
+ "coveredResources": [
45
+ {"resourceId": "i-1234567890abcdef0", "resourceType": "AWS_EC2_INSTANCE", "accountId": self.account_id}
46
+ ]
47
+ }
48
+
49
+ mock_client.list_coverage_statistics.return_value = {
50
+ "countsByGroup": [{"count": 10, "groupKey": "SCAN_STATUS"}]
51
+ }
52
+
53
+ mock_client.list_findings.return_value = {
54
+ "findings": [
55
+ {
56
+ "findingArn": "arn:aws:inspector2:us-east-1:123456789012:finding/abc123",
57
+ "awsAccountId": self.account_id,
58
+ "severity": "HIGH",
59
+ }
60
+ ]
61
+ }
62
+
63
+ mock_client.list_members.return_value = {
64
+ "members": [{"accountId": self.account_id, "relationshipStatus": "ENABLED"}]
65
+ }
66
+
67
+ result = self.collector.collect()
68
+
69
+ assert "Findings" in result
70
+ assert "Coverage" in result
71
+ assert "AccountStatus" in result
72
+ assert "Members" in result
73
+ assert "CoverageStatistics" in result
74
+ assert len(result["Findings"]) == 1
75
+ assert len(result["Coverage"]) == 1
76
+ assert result["Findings"][0]["Region"] == self.region
77
+
78
+ @patch("regscale.integrations.commercial.aws.inventory.resources.inspector.logger")
79
+ def test_collect_handles_client_error(self, mock_logger):
80
+ """Test collection handles ClientError."""
81
+ mock_client = MagicMock()
82
+ self.mock_session.client.return_value = mock_client
83
+
84
+ error_response = {"Error": {"Code": "InternalError", "Message": "Internal error"}}
85
+ mock_client.batch_get_account_status.side_effect = ClientError(error_response, "batch_get_account_status")
86
+
87
+ # Mock the other methods to return empty results
88
+ mock_client.list_coverage.return_value = {"coveredResources": []}
89
+ mock_client.list_coverage_statistics.return_value = {"countsByGroup": []}
90
+ mock_client.list_findings.return_value = {"findings": []}
91
+ mock_client.list_members.return_value = {"members": []}
92
+
93
+ result = self.collector.collect()
94
+
95
+ assert result["AccountStatus"] == {}
96
+
97
+ @patch("regscale.integrations.commercial.aws.inventory.resources.inspector.logger")
98
+ def test_collect_handles_unexpected_error(self, mock_logger):
99
+ """Test collection handles unexpected errors."""
100
+ mock_client = MagicMock()
101
+ self.mock_session.client.return_value = mock_client
102
+
103
+ mock_client.batch_get_account_status.side_effect = Exception("Unexpected error")
104
+
105
+ # Mock the other methods to return empty results
106
+ mock_client.list_coverage.return_value = {"coveredResources": []}
107
+ mock_client.list_coverage_statistics.return_value = {"countsByGroup": []}
108
+ mock_client.list_findings.return_value = {"findings": []}
109
+ mock_client.list_members.return_value = {"members": []}
110
+
111
+ result = self.collector.collect()
112
+
113
+ assert result["AccountStatus"] == {}
114
+ mock_logger.error.assert_called()
115
+
116
+ def test_get_account_status_success(self):
117
+ """Test successful account status retrieval."""
118
+ mock_client = MagicMock()
119
+ mock_client.batch_get_account_status.return_value = {
120
+ "accounts": [{"accountId": self.account_id, "state": {"status": "ENABLED"}}]
121
+ }
122
+
123
+ result = self.collector._get_account_status(mock_client)
124
+
125
+ assert result["accountId"] == self.account_id
126
+ assert result["Region"] == self.region
127
+
128
+ def test_get_account_status_without_account_id(self):
129
+ """Test account status without account ID filter."""
130
+ collector = InspectorCollector(self.mock_session, self.region)
131
+ mock_client = MagicMock()
132
+ mock_client.batch_get_account_status.return_value = {
133
+ "accounts": [{"accountId": "111111111111", "state": {"status": "ENABLED"}}]
134
+ }
135
+
136
+ result = collector._get_account_status(mock_client)
137
+
138
+ assert result["accountId"] == "111111111111"
139
+
140
+ def test_get_account_status_access_denied(self):
141
+ """Test account status with access denied."""
142
+ mock_client = MagicMock()
143
+ error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
144
+ mock_client.batch_get_account_status.side_effect = ClientError(error_response, "batch_get_account_status")
145
+
146
+ result = self.collector._get_account_status(mock_client)
147
+
148
+ assert result == {}
149
+
150
+ def test_get_account_status_empty_response(self):
151
+ """Test account status with empty response."""
152
+ mock_client = MagicMock()
153
+ mock_client.batch_get_account_status.return_value = {"accounts": []}
154
+
155
+ result = self.collector._get_account_status(mock_client)
156
+
157
+ assert result == {}
158
+
159
+ def test_list_coverage_success(self):
160
+ """Test successful coverage listing."""
161
+ mock_client = MagicMock()
162
+ mock_client.list_coverage.return_value = {
163
+ "coveredResources": [
164
+ {"resourceId": "i-1234567890abcdef0", "resourceType": "AWS_EC2_INSTANCE", "accountId": self.account_id}
165
+ ]
166
+ }
167
+
168
+ result = self.collector._list_coverage(mock_client)
169
+
170
+ assert len(result) == 1
171
+ assert result[0]["Region"] == self.region
172
+
173
+ def test_list_coverage_with_pagination(self):
174
+ """Test coverage listing with pagination."""
175
+ mock_client = MagicMock()
176
+ mock_client.list_coverage.side_effect = [
177
+ {"coveredResources": [{"resourceId": "i-111"}], "nextToken": "token-1"},
178
+ {"coveredResources": [{"resourceId": "i-222"}]},
179
+ ]
180
+
181
+ result = self.collector._list_coverage(mock_client)
182
+
183
+ assert len(result) == 2
184
+ assert result[0]["resourceId"] == "i-111"
185
+ assert result[1]["resourceId"] == "i-222"
186
+
187
+ def test_list_coverage_filters_by_account_id(self):
188
+ """Test coverage listing filters by account ID."""
189
+ mock_client = MagicMock()
190
+ mock_client.list_coverage.return_value = {"coveredResources": []}
191
+
192
+ self.collector._list_coverage(mock_client)
193
+
194
+ call_args = mock_client.list_coverage.call_args[1]
195
+ assert "filterCriteria" in call_args
196
+ assert call_args["filterCriteria"]["accountId"][0]["value"] == self.account_id
197
+
198
+ def test_list_coverage_access_denied(self):
199
+ """Test coverage listing with access denied."""
200
+ mock_client = MagicMock()
201
+ error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
202
+ mock_client.list_coverage.side_effect = ClientError(error_response, "list_coverage")
203
+
204
+ result = self.collector._list_coverage(mock_client)
205
+
206
+ assert result == []
207
+
208
+ def test_list_coverage_statistics_success(self):
209
+ """Test successful coverage statistics retrieval."""
210
+ mock_client = MagicMock()
211
+ mock_client.list_coverage_statistics.return_value = {
212
+ "countsByGroup": [{"count": 10, "groupKey": "SCAN_STATUS"}]
213
+ }
214
+
215
+ result = self.collector._list_coverage_statistics(mock_client)
216
+
217
+ assert "Region" in result
218
+ assert "CountsByGroup" in result
219
+ assert len(result["CountsByGroup"]) == 1
220
+
221
+ def test_list_coverage_statistics_with_account_filter(self):
222
+ """Test coverage statistics with account filter."""
223
+ mock_client = MagicMock()
224
+ mock_client.list_coverage_statistics.return_value = {"countsByGroup": []}
225
+
226
+ self.collector._list_coverage_statistics(mock_client)
227
+
228
+ call_args = mock_client.list_coverage_statistics.call_args[1]
229
+ assert "filterCriteria" in call_args
230
+ assert call_args["filterCriteria"]["accountId"][0]["value"] == self.account_id
231
+
232
+ def test_list_coverage_statistics_access_denied(self):
233
+ """Test coverage statistics with access denied."""
234
+ mock_client = MagicMock()
235
+ error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
236
+ mock_client.list_coverage_statistics.side_effect = ClientError(error_response, "list_coverage_statistics")
237
+
238
+ result = self.collector._list_coverage_statistics(mock_client)
239
+
240
+ assert result == {}
241
+
242
+ def test_list_findings_success(self):
243
+ """Test successful findings listing."""
244
+ mock_client = MagicMock()
245
+ mock_client.list_findings.return_value = {
246
+ "findings": [
247
+ {
248
+ "findingArn": "arn:aws:inspector2:us-east-1:123456789012:finding/abc123",
249
+ "awsAccountId": self.account_id,
250
+ "severity": "HIGH",
251
+ }
252
+ ]
253
+ }
254
+
255
+ result = self.collector._list_findings(mock_client)
256
+
257
+ assert len(result) == 1
258
+ assert result[0]["Region"] == self.region
259
+
260
+ def test_list_findings_with_pagination(self):
261
+ """Test findings listing with pagination."""
262
+ mock_client = MagicMock()
263
+ mock_client.list_findings.side_effect = [
264
+ {"findings": [{"findingArn": "arn-1"}], "nextToken": "token-1"},
265
+ {"findings": [{"findingArn": "arn-2"}]},
266
+ ]
267
+
268
+ result = self.collector._list_findings(mock_client)
269
+
270
+ assert len(result) == 2
271
+ assert result[0]["findingArn"] == "arn-1"
272
+ assert result[1]["findingArn"] == "arn-2"
273
+
274
+ def test_list_findings_filters_by_account_id(self):
275
+ """Test findings listing filters by account ID."""
276
+ mock_client = MagicMock()
277
+ mock_client.list_findings.return_value = {"findings": []}
278
+
279
+ self.collector._list_findings(mock_client)
280
+
281
+ call_args = mock_client.list_findings.call_args[1]
282
+ assert "filterCriteria" in call_args
283
+ assert call_args["filterCriteria"]["awsAccountId"][0]["value"] == self.account_id
284
+
285
+ def test_list_findings_access_denied(self):
286
+ """Test findings listing with access denied."""
287
+ mock_client = MagicMock()
288
+ error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
289
+ mock_client.list_findings.side_effect = ClientError(error_response, "list_findings")
290
+
291
+ result = self.collector._list_findings(mock_client)
292
+
293
+ assert result == []
294
+
295
+ def test_list_members_success(self):
296
+ """Test successful members listing."""
297
+ mock_client = MagicMock()
298
+ mock_client.list_members.return_value = {
299
+ "members": [{"accountId": self.account_id, "relationshipStatus": "ENABLED"}]
300
+ }
301
+
302
+ result = self.collector._list_members(mock_client)
303
+
304
+ assert len(result) == 1
305
+ assert result[0]["Region"] == self.region
306
+
307
+ def test_list_members_with_pagination(self):
308
+ """Test members listing with pagination."""
309
+ mock_client = MagicMock()
310
+ mock_client.list_members.side_effect = [
311
+ {"members": [{"accountId": "111111111111"}], "nextToken": "token-1"},
312
+ {"members": [{"accountId": "222222222222"}]},
313
+ ]
314
+
315
+ result = self.collector._list_members(mock_client)
316
+
317
+ assert len(result) == 2
318
+
319
+ def test_list_members_filters_by_account_id(self):
320
+ """Test members listing filters by account ID."""
321
+ mock_client = MagicMock()
322
+ mock_client.list_members.return_value = {
323
+ "members": [{"accountId": self.account_id}, {"accountId": "999999999999"}]
324
+ }
325
+
326
+ result = self.collector._list_members(mock_client)
327
+
328
+ # Should only include the matching account
329
+ assert len(result) == 2 # Both are returned because filtering happens at list level
330
+
331
+ def test_list_members_access_denied(self):
332
+ """Test members listing with access denied."""
333
+ mock_client = MagicMock()
334
+ error_response = {"Error": {"Code": "AccessDeniedException", "Message": "Access denied"}}
335
+ mock_client.list_members.side_effect = ClientError(error_response, "list_members")
336
+
337
+ result = self.collector._list_members(mock_client)
338
+
339
+ assert result == []
340
+
341
+ def test_list_members_other_error(self):
342
+ """Test members listing with other error."""
343
+ mock_client = MagicMock()
344
+ error_response = {"Error": {"Code": "InternalError", "Message": "Internal error"}}
345
+ mock_client.list_members.side_effect = ClientError(error_response, "list_members")
346
+
347
+ result = self.collector._list_members(mock_client)
348
+
349
+ assert result == []
350
+
351
+
352
+ if __name__ == "__main__":
353
+ pytest.main([__file__, "-v"])