cumulusci-plus 5.0.21__py3-none-any.whl → 5.0.35__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.
- cumulusci/__about__.py +1 -1
- cumulusci/cli/logger.py +2 -2
- cumulusci/cli/service.py +20 -0
- cumulusci/cli/task.py +17 -0
- cumulusci/cli/tests/test_error.py +3 -1
- cumulusci/cli/tests/test_flow.py +279 -2
- cumulusci/cli/tests/test_service.py +15 -12
- cumulusci/cli/tests/test_task.py +88 -2
- cumulusci/cli/tests/utils.py +1 -4
- cumulusci/core/config/base_task_flow_config.py +26 -1
- cumulusci/core/config/project_config.py +2 -20
- cumulusci/core/config/tests/test_config_expensive.py +9 -3
- cumulusci/core/config/universal_config.py +3 -4
- cumulusci/core/dependencies/base.py +1 -1
- cumulusci/core/dependencies/dependencies.py +1 -1
- cumulusci/core/dependencies/github.py +1 -2
- cumulusci/core/dependencies/resolvers.py +1 -1
- cumulusci/core/dependencies/tests/test_dependencies.py +1 -1
- cumulusci/core/dependencies/tests/test_resolvers.py +1 -1
- cumulusci/core/flowrunner.py +90 -6
- cumulusci/core/github.py +1 -1
- cumulusci/core/sfdx.py +3 -1
- cumulusci/core/source_transforms/tests/test_transforms.py +1 -1
- cumulusci/core/source_transforms/transforms.py +1 -1
- cumulusci/core/tasks.py +13 -2
- cumulusci/core/tests/test_flowrunner.py +100 -0
- cumulusci/core/tests/test_tasks.py +65 -0
- cumulusci/core/utils.py +3 -1
- cumulusci/core/versions.py +1 -1
- cumulusci/cumulusci.yml +55 -0
- cumulusci/oauth/client.py +1 -1
- cumulusci/plugins/plugin_base.py +5 -3
- cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
- cumulusci/salesforce_api/rest_deploy.py +1 -1
- cumulusci/schema/cumulusci.jsonschema.json +64 -0
- cumulusci/tasks/apex/anon.py +1 -1
- cumulusci/tasks/apex/testrunner.py +416 -142
- cumulusci/tasks/apex/tests/test_apex_tasks.py +917 -1
- cumulusci/tasks/bulkdata/extract.py +0 -1
- cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +1 -1
- cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +1 -1
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +1 -1
- cumulusci/tasks/bulkdata/generate_and_load_data.py +136 -12
- cumulusci/tasks/bulkdata/mapping_parser.py +139 -44
- cumulusci/tasks/bulkdata/select_utils.py +1 -1
- cumulusci/tasks/bulkdata/snowfakery.py +100 -25
- cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +159 -0
- cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
- cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +763 -1
- cumulusci/tasks/bulkdata/tests/test_select_utils.py +26 -0
- cumulusci/tasks/bulkdata/tests/test_snowfakery.py +133 -0
- cumulusci/tasks/create_package_version.py +190 -16
- cumulusci/tasks/datadictionary.py +1 -1
- cumulusci/tasks/metadata_etl/base.py +7 -3
- cumulusci/tasks/metadata_etl/layouts.py +1 -1
- cumulusci/tasks/metadata_etl/permissions.py +1 -1
- cumulusci/tasks/metadata_etl/remote_site_settings.py +2 -2
- cumulusci/tasks/push/README.md +15 -17
- cumulusci/tasks/release_notes/README.md +13 -13
- cumulusci/tasks/release_notes/generator.py +13 -8
- cumulusci/tasks/robotframework/tests/test_robotframework.py +6 -1
- cumulusci/tasks/salesforce/Deploy.py +53 -2
- cumulusci/tasks/salesforce/SfPackageCommands.py +363 -0
- cumulusci/tasks/salesforce/__init__.py +1 -0
- cumulusci/tasks/salesforce/assign_ps_psg.py +448 -0
- cumulusci/tasks/salesforce/composite.py +1 -1
- cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
- cumulusci/tasks/salesforce/enable_prediction.py +5 -1
- cumulusci/tasks/salesforce/getPackageVersion.py +89 -0
- cumulusci/tasks/salesforce/sourcetracking.py +1 -1
- cumulusci/tasks/salesforce/tests/test_Deploy.py +316 -1
- cumulusci/tasks/salesforce/tests/test_SfPackageCommands.py +554 -0
- cumulusci/tasks/salesforce/tests/test_assign_ps_psg.py +1055 -0
- cumulusci/tasks/salesforce/tests/test_getPackageVersion.py +651 -0
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
- cumulusci/tasks/salesforce/tests/test_update_external_credential.py +912 -0
- cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
- cumulusci/tasks/salesforce/update_dependencies.py +2 -2
- cumulusci/tasks/salesforce/update_external_credential.py +562 -0
- cumulusci/tasks/salesforce/update_named_credential.py +441 -0
- cumulusci/tasks/salesforce/update_profile.py +17 -13
- cumulusci/tasks/salesforce/users/permsets.py +62 -5
- cumulusci/tasks/salesforce/users/tests/test_permsets.py +237 -11
- cumulusci/tasks/sfdmu/__init__.py +0 -0
- cumulusci/tasks/sfdmu/sfdmu.py +363 -0
- cumulusci/tasks/sfdmu/tests/__init__.py +1 -0
- cumulusci/tasks/sfdmu/tests/test_runner.py +212 -0
- cumulusci/tasks/sfdmu/tests/test_sfdmu.py +1012 -0
- cumulusci/tasks/tests/test_create_package_version.py +716 -1
- cumulusci/tasks/tests/test_util.py +42 -0
- cumulusci/tasks/util.py +37 -1
- cumulusci/tasks/utility/copyContents.py +402 -0
- cumulusci/tasks/utility/credentialManager.py +256 -0
- cumulusci/tasks/utility/directoryRecreator.py +30 -0
- cumulusci/tasks/utility/env_management.py +1 -1
- cumulusci/tasks/utility/secretsToEnv.py +135 -0
- cumulusci/tasks/utility/tests/test_copyContents.py +1719 -0
- cumulusci/tasks/utility/tests/test_credentialManager.py +564 -0
- cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
- cumulusci/tasks/utility/tests/test_secretsToEnv.py +1091 -0
- cumulusci/tests/test_integration_infrastructure.py +3 -1
- cumulusci/tests/test_utils.py +70 -6
- cumulusci/utils/__init__.py +54 -9
- cumulusci/utils/classutils.py +5 -2
- cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
- cumulusci/utils/options.py +23 -1
- cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +1 -1
- cumulusci/utils/yaml/cumulusci_yml.py +7 -3
- cumulusci/utils/yaml/model_parser.py +2 -2
- cumulusci/utils/yaml/tests/test_cumulusci_yml.py +1 -1
- cumulusci/utils/yaml/tests/test_model_parser.py +3 -3
- cumulusci/vcs/base.py +23 -15
- cumulusci/vcs/bootstrap.py +5 -4
- cumulusci/vcs/utils/list_modified_files.py +189 -0
- cumulusci/vcs/utils/tests/test_list_modified_files.py +588 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/METADATA +12 -10
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/RECORD +121 -96
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/WHEEL +0 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/entry_points.txt +0 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/licenses/AUTHORS.rst +0 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.35.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
"""Tests for list_modified_files task."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from unittest import mock
|
|
4
|
+
from unittest.mock import Mock, PropertyMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from cumulusci.core.config import BaseProjectConfig, TaskConfig
|
|
9
|
+
from cumulusci.vcs.utils.list_modified_files import ListModifiedFiles
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def project_config_with_git():
|
|
14
|
+
"""Create a project config with git and package settings for testing.
|
|
15
|
+
|
|
16
|
+
This follows the pattern from cumulusci/vcs/tests/conftest.py but adds
|
|
17
|
+
the specific git and package configuration needed for these tests.
|
|
18
|
+
"""
|
|
19
|
+
from cumulusci.core.config import UniversalConfig
|
|
20
|
+
|
|
21
|
+
universal_config = UniversalConfig()
|
|
22
|
+
project_config = BaseProjectConfig(universal_config, config={"no_yaml": True})
|
|
23
|
+
project_config.config["project"] = {
|
|
24
|
+
"git": {"default_branch": "main"},
|
|
25
|
+
"package": {"path": "force-app"},
|
|
26
|
+
}
|
|
27
|
+
return project_config
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestListModifiedFiles:
|
|
31
|
+
"""Test cases for ListModifiedFiles task."""
|
|
32
|
+
|
|
33
|
+
@pytest.fixture(autouse=True)
|
|
34
|
+
def setup_project_config(self, project_config_with_git):
|
|
35
|
+
"""Auto-use fixture to set up project_config for all tests."""
|
|
36
|
+
self.project_config = project_config_with_git
|
|
37
|
+
|
|
38
|
+
def _create_task(self, options=None):
|
|
39
|
+
"""Helper to create a task instance."""
|
|
40
|
+
if options is None:
|
|
41
|
+
options = {}
|
|
42
|
+
task_config = TaskConfig({"options": options})
|
|
43
|
+
return ListModifiedFiles(self.project_config, task_config, org_config=None)
|
|
44
|
+
|
|
45
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
46
|
+
def test_no_git_repo(self, mock_subprocess):
|
|
47
|
+
"""Test behavior when no git repository is found."""
|
|
48
|
+
task = self._create_task()
|
|
49
|
+
task.project_config.get_repo = Mock(return_value=None)
|
|
50
|
+
|
|
51
|
+
task()
|
|
52
|
+
|
|
53
|
+
assert task.return_values == {"files": set(), "file_names": set()}
|
|
54
|
+
mock_subprocess.assert_not_called()
|
|
55
|
+
|
|
56
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
57
|
+
def test_git_diff_fails(self, mock_subprocess):
|
|
58
|
+
"""Test behavior when git diff command fails."""
|
|
59
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
60
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
61
|
+
|
|
62
|
+
# Mock subprocess.run to return non-zero exit code
|
|
63
|
+
mock_result = Mock()
|
|
64
|
+
mock_result.returncode = 1
|
|
65
|
+
mock_result.stderr = "fatal: ambiguous argument"
|
|
66
|
+
mock_subprocess.return_value = mock_result
|
|
67
|
+
|
|
68
|
+
task()
|
|
69
|
+
|
|
70
|
+
assert task.return_values == {"files": set(), "file_names": set()}
|
|
71
|
+
mock_subprocess.assert_called_once_with(
|
|
72
|
+
["git", "diff", "--name-only", "origin/main"],
|
|
73
|
+
capture_output=True,
|
|
74
|
+
text=True,
|
|
75
|
+
check=False,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
79
|
+
def test_git_command_not_found(self, mock_subprocess):
|
|
80
|
+
"""Test behavior when git command is not found."""
|
|
81
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
82
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
83
|
+
|
|
84
|
+
# Mock FileNotFoundError
|
|
85
|
+
mock_subprocess.side_effect = FileNotFoundError("git: command not found")
|
|
86
|
+
|
|
87
|
+
task()
|
|
88
|
+
|
|
89
|
+
assert task.return_values == {"files": set(), "file_names": set()}
|
|
90
|
+
|
|
91
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
92
|
+
def test_git_diff_exception(self, mock_subprocess):
|
|
93
|
+
"""Test behavior when git diff raises an exception."""
|
|
94
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
95
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
96
|
+
|
|
97
|
+
# Mock generic exception
|
|
98
|
+
mock_subprocess.side_effect = Exception("Unexpected error")
|
|
99
|
+
|
|
100
|
+
task()
|
|
101
|
+
|
|
102
|
+
assert task.return_values == {"files": set(), "file_names": set()}
|
|
103
|
+
|
|
104
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
105
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
106
|
+
def test_no_files_changed(self, mock_package_path, mock_subprocess):
|
|
107
|
+
"""Test behavior when no files are changed."""
|
|
108
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
109
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
110
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
111
|
+
|
|
112
|
+
# Mock subprocess.run to return empty output
|
|
113
|
+
mock_result = Mock()
|
|
114
|
+
mock_result.returncode = 0
|
|
115
|
+
mock_result.stdout = ""
|
|
116
|
+
mock_subprocess.return_value = mock_result
|
|
117
|
+
|
|
118
|
+
task()
|
|
119
|
+
|
|
120
|
+
assert task.return_values == {"files": set(), "file_names": set()}
|
|
121
|
+
|
|
122
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
123
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
124
|
+
def test_files_changed_not_in_package_dirs(
|
|
125
|
+
self, mock_package_path, mock_subprocess
|
|
126
|
+
):
|
|
127
|
+
"""Test behavior when files are changed but not in package directories."""
|
|
128
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
129
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
130
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
131
|
+
|
|
132
|
+
# Mock subprocess.run to return files outside package directories
|
|
133
|
+
mock_result = Mock()
|
|
134
|
+
mock_result.returncode = 0
|
|
135
|
+
mock_result.stdout = "README.md\n.gitignore\n"
|
|
136
|
+
mock_subprocess.return_value = mock_result
|
|
137
|
+
|
|
138
|
+
task()
|
|
139
|
+
|
|
140
|
+
assert task.return_values == {"files": set(), "file_names": set()}
|
|
141
|
+
|
|
142
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
143
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
144
|
+
def test_files_changed_in_package_dirs(self, mock_package_path, mock_subprocess):
|
|
145
|
+
"""Test behavior when files are changed in package directories."""
|
|
146
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
147
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
148
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
149
|
+
|
|
150
|
+
# Mock subprocess.run to return files in package directories
|
|
151
|
+
mock_result = Mock()
|
|
152
|
+
mock_result.returncode = 0
|
|
153
|
+
mock_result.stdout = "force-app/main/default/classes/MyClass.cls\n"
|
|
154
|
+
mock_subprocess.return_value = mock_result
|
|
155
|
+
|
|
156
|
+
task()
|
|
157
|
+
|
|
158
|
+
assert task.return_values["files"] == [
|
|
159
|
+
"force-app/main/default/classes/MyClass.cls"
|
|
160
|
+
]
|
|
161
|
+
assert task.return_values["file_names"] == set()
|
|
162
|
+
|
|
163
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
164
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
165
|
+
def test_files_changed_in_src_directory(self, mock_package_path, mock_subprocess):
|
|
166
|
+
"""Test behavior when files are changed in src directory."""
|
|
167
|
+
mock_package_path.return_value = Path("src").absolute()
|
|
168
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
169
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
170
|
+
|
|
171
|
+
# Mock subprocess.run to return files in src directory
|
|
172
|
+
mock_result = Mock()
|
|
173
|
+
mock_result.returncode = 0
|
|
174
|
+
mock_result.stdout = "src/classes/MyClass.cls\n"
|
|
175
|
+
mock_subprocess.return_value = mock_result
|
|
176
|
+
|
|
177
|
+
task()
|
|
178
|
+
|
|
179
|
+
assert task.return_values["files"] == ["src/classes/MyClass.cls"]
|
|
180
|
+
assert task.return_values["file_names"] == set()
|
|
181
|
+
|
|
182
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
183
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
184
|
+
def test_extract_file_names_with_cls_extension(
|
|
185
|
+
self, mock_package_path, mock_subprocess
|
|
186
|
+
):
|
|
187
|
+
"""Test extracting file names with .cls extension."""
|
|
188
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
189
|
+
task = self._create_task(
|
|
190
|
+
{
|
|
191
|
+
"base_ref": "origin/main",
|
|
192
|
+
"file_extensions": ["cls"],
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
196
|
+
|
|
197
|
+
# Mock subprocess.run to return .cls files
|
|
198
|
+
mock_result = Mock()
|
|
199
|
+
mock_result.returncode = 0
|
|
200
|
+
mock_result.stdout = "force-app/main/default/classes/MyClass.cls\n"
|
|
201
|
+
mock_subprocess.return_value = mock_result
|
|
202
|
+
|
|
203
|
+
task()
|
|
204
|
+
|
|
205
|
+
assert task.return_values["files"] == [
|
|
206
|
+
"force-app/main/default/classes/MyClass.cls"
|
|
207
|
+
]
|
|
208
|
+
assert task.return_values["file_names"] == {"MyClass"}
|
|
209
|
+
|
|
210
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
211
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
212
|
+
def test_extract_file_names_with_dot_cls_extension(
|
|
213
|
+
self, mock_package_path, mock_subprocess
|
|
214
|
+
):
|
|
215
|
+
"""Test extracting file names with .cls extension (with dot prefix)."""
|
|
216
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
217
|
+
task = self._create_task(
|
|
218
|
+
{
|
|
219
|
+
"base_ref": "origin/main",
|
|
220
|
+
"file_extensions": [".cls"],
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
224
|
+
|
|
225
|
+
# Mock subprocess.run to return .cls files
|
|
226
|
+
mock_result = Mock()
|
|
227
|
+
mock_result.returncode = 0
|
|
228
|
+
mock_result.stdout = "force-app/main/default/classes/MyClass.cls\n"
|
|
229
|
+
mock_subprocess.return_value = mock_result
|
|
230
|
+
|
|
231
|
+
task()
|
|
232
|
+
|
|
233
|
+
assert task.return_values["files"] == [
|
|
234
|
+
"force-app/main/default/classes/MyClass.cls"
|
|
235
|
+
]
|
|
236
|
+
assert task.return_values["file_names"] == {"MyClass"}
|
|
237
|
+
|
|
238
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
239
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
240
|
+
def test_extract_file_names_multiple_extensions(
|
|
241
|
+
self, mock_package_path, mock_subprocess
|
|
242
|
+
):
|
|
243
|
+
"""Test extracting file names with multiple extensions."""
|
|
244
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
245
|
+
task = self._create_task(
|
|
246
|
+
{
|
|
247
|
+
"base_ref": "origin/main",
|
|
248
|
+
"file_extensions": ["cls", "trigger"],
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
252
|
+
|
|
253
|
+
# Mock subprocess.run to return multiple file types
|
|
254
|
+
mock_result = Mock()
|
|
255
|
+
mock_result.returncode = 0
|
|
256
|
+
mock_result.stdout = (
|
|
257
|
+
"force-app/main/default/classes/MyClass.cls\n"
|
|
258
|
+
"force-app/main/default/triggers/MyTrigger.trigger\n"
|
|
259
|
+
)
|
|
260
|
+
mock_subprocess.return_value = mock_result
|
|
261
|
+
|
|
262
|
+
task()
|
|
263
|
+
|
|
264
|
+
assert len(task.return_values["files"]) == 2
|
|
265
|
+
assert task.return_values["file_names"] == {"MyClass", "MyTrigger"}
|
|
266
|
+
|
|
267
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
268
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
269
|
+
def test_extract_file_names_no_matching_extensions(
|
|
270
|
+
self, mock_package_path, mock_subprocess
|
|
271
|
+
):
|
|
272
|
+
"""Test extracting file names when no files match the extensions."""
|
|
273
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
274
|
+
task = self._create_task(
|
|
275
|
+
{
|
|
276
|
+
"base_ref": "origin/main",
|
|
277
|
+
"file_extensions": ["cls"],
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
281
|
+
|
|
282
|
+
# Mock subprocess.run to return files without matching extensions
|
|
283
|
+
mock_result = Mock()
|
|
284
|
+
mock_result.returncode = 0
|
|
285
|
+
mock_result.stdout = "force-app/main/default/flows/MyFlow.flow-meta.xml\n"
|
|
286
|
+
mock_subprocess.return_value = mock_result
|
|
287
|
+
|
|
288
|
+
task()
|
|
289
|
+
|
|
290
|
+
assert len(task.return_values["files"]) == 1
|
|
291
|
+
assert task.return_values["file_names"] == set()
|
|
292
|
+
|
|
293
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
294
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
295
|
+
def test_default_base_ref_from_config(self, mock_package_path, mock_subprocess):
|
|
296
|
+
"""Test that default base_ref is taken from project config."""
|
|
297
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
298
|
+
task = self._create_task() # No base_ref specified
|
|
299
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
300
|
+
|
|
301
|
+
# Mock subprocess.run
|
|
302
|
+
mock_result = Mock()
|
|
303
|
+
mock_result.returncode = 0
|
|
304
|
+
mock_result.stdout = ""
|
|
305
|
+
mock_subprocess.return_value = mock_result
|
|
306
|
+
|
|
307
|
+
task()
|
|
308
|
+
|
|
309
|
+
# Should use default branch from config
|
|
310
|
+
mock_subprocess.assert_called_once_with(
|
|
311
|
+
["git", "diff", "--name-only", "main"],
|
|
312
|
+
capture_output=True,
|
|
313
|
+
text=True,
|
|
314
|
+
check=False,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
318
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
319
|
+
def test_default_base_ref_fallback_to_main(
|
|
320
|
+
self, mock_package_path, mock_subprocess
|
|
321
|
+
):
|
|
322
|
+
"""Test that default base_ref falls back to 'main' if not in config."""
|
|
323
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
324
|
+
# Remove default_branch from config
|
|
325
|
+
self.project_config.config["project"]["git"] = {}
|
|
326
|
+
task = self._create_task() # No base_ref specified
|
|
327
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
328
|
+
|
|
329
|
+
# Mock subprocess.run
|
|
330
|
+
mock_result = Mock()
|
|
331
|
+
mock_result.returncode = 0
|
|
332
|
+
mock_result.stdout = ""
|
|
333
|
+
mock_subprocess.return_value = mock_result
|
|
334
|
+
|
|
335
|
+
task()
|
|
336
|
+
|
|
337
|
+
# Should fall back to "main"
|
|
338
|
+
mock_subprocess.assert_called_once_with(
|
|
339
|
+
["git", "diff", "--name-only", "main"],
|
|
340
|
+
capture_output=True,
|
|
341
|
+
text=True,
|
|
342
|
+
check=False,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
346
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
347
|
+
def test_custom_directories_option(self, mock_package_path, mock_subprocess):
|
|
348
|
+
"""Test behavior with custom directories option."""
|
|
349
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
350
|
+
task = self._create_task(
|
|
351
|
+
{
|
|
352
|
+
"base_ref": "origin/main",
|
|
353
|
+
"directories": ["custom-dir"],
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
357
|
+
|
|
358
|
+
# Mock subprocess.run to return files in custom directory
|
|
359
|
+
mock_result = Mock()
|
|
360
|
+
mock_result.returncode = 0
|
|
361
|
+
mock_result.stdout = "custom-dir/classes/MyClass.cls\n"
|
|
362
|
+
mock_subprocess.return_value = mock_result
|
|
363
|
+
|
|
364
|
+
task()
|
|
365
|
+
|
|
366
|
+
assert task.return_values["files"] == ["custom-dir/classes/MyClass.cls"]
|
|
367
|
+
|
|
368
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
369
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
370
|
+
def test_filter_package_changed_files_adds_default_package_dir(
|
|
371
|
+
self, mock_package_path, mock_subprocess
|
|
372
|
+
):
|
|
373
|
+
"""Test that default package directory is added to directories list."""
|
|
374
|
+
mock_package_path.return_value = Path("custom-package").absolute()
|
|
375
|
+
task = self._create_task(
|
|
376
|
+
{
|
|
377
|
+
"base_ref": "origin/main",
|
|
378
|
+
"directories": ["force-app"],
|
|
379
|
+
}
|
|
380
|
+
)
|
|
381
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
382
|
+
|
|
383
|
+
# Mock subprocess.run
|
|
384
|
+
mock_result = Mock()
|
|
385
|
+
mock_result.returncode = 0
|
|
386
|
+
mock_result.stdout = "custom-package/classes/MyClass.cls\n"
|
|
387
|
+
mock_subprocess.return_value = mock_result
|
|
388
|
+
|
|
389
|
+
task()
|
|
390
|
+
|
|
391
|
+
# Should include custom-package directory
|
|
392
|
+
assert task.return_values["files"] == ["custom-package/classes/MyClass.cls"]
|
|
393
|
+
|
|
394
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
395
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
396
|
+
def test_filter_package_changed_files_windows_paths(
|
|
397
|
+
self, mock_package_path, mock_subprocess
|
|
398
|
+
):
|
|
399
|
+
"""Test filtering with Windows-style paths."""
|
|
400
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
401
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
402
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
403
|
+
|
|
404
|
+
# Mock subprocess.run to return Windows-style paths
|
|
405
|
+
mock_result = Mock()
|
|
406
|
+
mock_result.returncode = 0
|
|
407
|
+
mock_result.stdout = "force-app\\main\\default\\classes\\MyClass.cls\n"
|
|
408
|
+
mock_subprocess.return_value = mock_result
|
|
409
|
+
|
|
410
|
+
task()
|
|
411
|
+
|
|
412
|
+
assert len(task.return_values["files"]) == 1
|
|
413
|
+
|
|
414
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
415
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
416
|
+
def test_logging_with_few_files(self, mock_package_path, mock_subprocess):
|
|
417
|
+
"""Test logging when there are few files (<= 20)."""
|
|
418
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
419
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
420
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
421
|
+
|
|
422
|
+
# Mock subprocess.run to return 5 files
|
|
423
|
+
mock_result = Mock()
|
|
424
|
+
mock_result.returncode = 0
|
|
425
|
+
mock_result.stdout = "\n".join(
|
|
426
|
+
[f"force-app/main/default/classes/Class{i}.cls" for i in range(5)]
|
|
427
|
+
)
|
|
428
|
+
mock_subprocess.return_value = mock_result
|
|
429
|
+
|
|
430
|
+
task()
|
|
431
|
+
|
|
432
|
+
assert len(task.return_values["files"]) == 5
|
|
433
|
+
|
|
434
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
435
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
436
|
+
def test_logging_with_many_files(self, mock_package_path, mock_subprocess):
|
|
437
|
+
"""Test logging when there are many files (> 20)."""
|
|
438
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
439
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
440
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
441
|
+
|
|
442
|
+
# Mock subprocess.run to return 25 files
|
|
443
|
+
mock_result = Mock()
|
|
444
|
+
mock_result.returncode = 0
|
|
445
|
+
mock_result.stdout = "\n".join(
|
|
446
|
+
[f"force-app/main/default/classes/Class{i}.cls" for i in range(25)]
|
|
447
|
+
)
|
|
448
|
+
mock_subprocess.return_value = mock_result
|
|
449
|
+
|
|
450
|
+
task()
|
|
451
|
+
|
|
452
|
+
assert len(task.return_values["files"]) == 25
|
|
453
|
+
|
|
454
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
455
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
456
|
+
def test_logging_with_few_file_names(self, mock_package_path, mock_subprocess):
|
|
457
|
+
"""Test logging when there are few file names (<= 20)."""
|
|
458
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
459
|
+
task = self._create_task(
|
|
460
|
+
{
|
|
461
|
+
"base_ref": "origin/main",
|
|
462
|
+
"file_extensions": ["cls"],
|
|
463
|
+
}
|
|
464
|
+
)
|
|
465
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
466
|
+
|
|
467
|
+
# Mock subprocess.run to return 5 files
|
|
468
|
+
mock_result = Mock()
|
|
469
|
+
mock_result.returncode = 0
|
|
470
|
+
mock_result.stdout = "\n".join(
|
|
471
|
+
[f"force-app/main/default/classes/Class{i}.cls" for i in range(5)]
|
|
472
|
+
)
|
|
473
|
+
mock_subprocess.return_value = mock_result
|
|
474
|
+
|
|
475
|
+
task()
|
|
476
|
+
|
|
477
|
+
assert len(task.return_values["file_names"]) == 5
|
|
478
|
+
|
|
479
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
480
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
481
|
+
def test_logging_with_many_file_names(self, mock_package_path, mock_subprocess):
|
|
482
|
+
"""Test logging when there are many file names (> 20)."""
|
|
483
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
484
|
+
task = self._create_task(
|
|
485
|
+
{
|
|
486
|
+
"base_ref": "origin/main",
|
|
487
|
+
"file_extensions": ["cls"],
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
491
|
+
|
|
492
|
+
# Mock subprocess.run to return 25 files
|
|
493
|
+
mock_result = Mock()
|
|
494
|
+
mock_result.returncode = 0
|
|
495
|
+
mock_result.stdout = "\n".join(
|
|
496
|
+
[f"force-app/main/default/classes/Class{i}.cls" for i in range(25)]
|
|
497
|
+
)
|
|
498
|
+
mock_subprocess.return_value = mock_result
|
|
499
|
+
|
|
500
|
+
task()
|
|
501
|
+
|
|
502
|
+
assert len(task.return_values["file_names"]) == 25
|
|
503
|
+
|
|
504
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
505
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
506
|
+
def test_multiple_files_mixed_directories(self, mock_package_path, mock_subprocess):
|
|
507
|
+
"""Test filtering with multiple files in different directories."""
|
|
508
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
509
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
510
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
511
|
+
|
|
512
|
+
# Mock subprocess.run to return files in different locations
|
|
513
|
+
mock_result = Mock()
|
|
514
|
+
mock_result.returncode = 0
|
|
515
|
+
mock_result.stdout = (
|
|
516
|
+
"force-app/main/default/classes/MyClass.cls\n"
|
|
517
|
+
"src/classes/OtherClass.cls\n"
|
|
518
|
+
"README.md\n"
|
|
519
|
+
"force-app/main/default/flows/MyFlow.flow\n"
|
|
520
|
+
)
|
|
521
|
+
mock_subprocess.return_value = mock_result
|
|
522
|
+
|
|
523
|
+
task()
|
|
524
|
+
|
|
525
|
+
# Should only include files from force-app and src
|
|
526
|
+
assert len(task.return_values["files"]) == 3
|
|
527
|
+
assert "README.md" not in task.return_values["files"]
|
|
528
|
+
|
|
529
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
530
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
531
|
+
def test_extract_file_names_with_meta_xml(self, mock_package_path, mock_subprocess):
|
|
532
|
+
"""Test extracting file names from .cls-meta.xml files."""
|
|
533
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
534
|
+
task = self._create_task(
|
|
535
|
+
{
|
|
536
|
+
"base_ref": "origin/main",
|
|
537
|
+
"file_extensions": ["cls-meta.xml"],
|
|
538
|
+
}
|
|
539
|
+
)
|
|
540
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
541
|
+
|
|
542
|
+
# Mock subprocess.run to return .cls-meta.xml files
|
|
543
|
+
mock_result = Mock()
|
|
544
|
+
mock_result.returncode = 0
|
|
545
|
+
mock_result.stdout = "force-app/main/default/classes/MyClass.cls-meta.xml\n"
|
|
546
|
+
mock_subprocess.return_value = mock_result
|
|
547
|
+
|
|
548
|
+
task()
|
|
549
|
+
|
|
550
|
+
assert task.return_values["file_names"] == {"MyClass"}
|
|
551
|
+
|
|
552
|
+
@mock.patch("cumulusci.vcs.utils.list_modified_files.subprocess.run")
|
|
553
|
+
@patch.object(BaseProjectConfig, "default_package_path", new_callable=PropertyMock)
|
|
554
|
+
def test_git_diff_with_whitespace(self, mock_package_path, mock_subprocess):
|
|
555
|
+
"""Test handling of git diff output with extra whitespace."""
|
|
556
|
+
mock_package_path.return_value = Path("force-app").absolute()
|
|
557
|
+
task = self._create_task({"base_ref": "origin/main"})
|
|
558
|
+
task.project_config.get_repo = Mock(return_value=Mock())
|
|
559
|
+
|
|
560
|
+
# Mock subprocess.run to return files with whitespace
|
|
561
|
+
mock_result = Mock()
|
|
562
|
+
mock_result.returncode = 0
|
|
563
|
+
mock_result.stdout = (
|
|
564
|
+
" force-app/main/default/classes/MyClass.cls \n"
|
|
565
|
+
"\n"
|
|
566
|
+
" force-app/main/default/classes/OtherClass.cls \n"
|
|
567
|
+
)
|
|
568
|
+
mock_subprocess.return_value = mock_result
|
|
569
|
+
|
|
570
|
+
task()
|
|
571
|
+
|
|
572
|
+
# Whitespace should be stripped
|
|
573
|
+
assert len(task.return_values["files"]) == 2
|
|
574
|
+
assert all(" " not in f for f in task.return_values["files"])
|
|
575
|
+
|
|
576
|
+
def test_init_options_sets_default_base_ref(self):
|
|
577
|
+
"""Test that _init_options sets default base_ref from config."""
|
|
578
|
+
task = self._create_task()
|
|
579
|
+
|
|
580
|
+
# base_ref should be set from config
|
|
581
|
+
assert task.parsed_options.base_ref == "main"
|
|
582
|
+
|
|
583
|
+
def test_init_options_preserves_provided_base_ref(self):
|
|
584
|
+
"""Test that _init_options preserves provided base_ref."""
|
|
585
|
+
task = self._create_task({"base_ref": "origin/develop"})
|
|
586
|
+
|
|
587
|
+
# base_ref should be preserved
|
|
588
|
+
assert task.parsed_options.base_ref == "origin/develop"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cumulusci-plus
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.35
|
|
4
4
|
Summary: Build and release tools for Salesforce developers
|
|
5
5
|
Project-URL: Homepage, https://github.com/jorgesolebur/CumulusCI
|
|
6
6
|
Project-URL: Changelog, https://cumulusci.readthedocs.io/en/stable/history.html
|
|
@@ -22,7 +22,7 @@ Requires-Python: >=3.11
|
|
|
22
22
|
Requires-Dist: click>=8.1
|
|
23
23
|
Requires-Dist: cryptography
|
|
24
24
|
Requires-Dist: defusedxml
|
|
25
|
-
Requires-Dist: docutils
|
|
25
|
+
Requires-Dist: docutils<=0.21.2
|
|
26
26
|
Requires-Dist: faker
|
|
27
27
|
Requires-Dist: fs
|
|
28
28
|
Requires-Dist: github3-py
|
|
@@ -32,9 +32,10 @@ Requires-Dist: lxml
|
|
|
32
32
|
Requires-Dist: markupsafe
|
|
33
33
|
Requires-Dist: packaging>=23.0
|
|
34
34
|
Requires-Dist: psutil
|
|
35
|
-
Requires-Dist: pydantic<2
|
|
35
|
+
Requires-Dist: pydantic<3,>=2.0
|
|
36
36
|
Requires-Dist: pyjwt
|
|
37
37
|
Requires-Dist: python-dateutil
|
|
38
|
+
Requires-Dist: python-dotenv
|
|
38
39
|
Requires-Dist: pytz
|
|
39
40
|
Requires-Dist: pyyaml
|
|
40
41
|
Requires-Dist: requests
|
|
@@ -49,17 +50,18 @@ Requires-Dist: salesforce-bulk
|
|
|
49
50
|
Requires-Dist: sarge
|
|
50
51
|
Requires-Dist: selenium<4
|
|
51
52
|
Requires-Dist: simple-salesforce==1.11.4
|
|
52
|
-
Requires-Dist: snowfakery>=4.
|
|
53
|
+
Requires-Dist: snowfakery>=4.1.0
|
|
53
54
|
Requires-Dist: sqlalchemy<2
|
|
54
55
|
Requires-Dist: xmltodict
|
|
55
56
|
Provides-Extra: select
|
|
56
57
|
Requires-Dist: annoy; extra == 'select'
|
|
58
|
+
Requires-Dist: boto3; extra == 'select'
|
|
57
59
|
Requires-Dist: numpy; extra == 'select'
|
|
58
60
|
Requires-Dist: pandas; extra == 'select'
|
|
59
61
|
Requires-Dist: scikit-learn; extra == 'select'
|
|
60
62
|
Description-Content-Type: text/markdown
|
|
61
63
|
|
|
62
|
-
# CumulusCI
|
|
64
|
+
# CumulusCI Plus
|
|
63
65
|
|
|
64
66
|
[](https://coveralls.io/github/SFDO-Tooling/CumulusCI?branch=main)
|
|
65
67
|
[](https://pypi.org/project/cumulusci-plus/)
|
|
@@ -127,14 +129,14 @@ license](https://github.com/SFDO-Tooling/CumulusCI/blob/main/LICENSE)
|
|
|
127
129
|
and is not covered by the Salesforce Master Subscription Agreement.
|
|
128
130
|
|
|
129
131
|
<!-- Changelog -->
|
|
130
|
-
## v5.0.
|
|
131
|
-
|
|
132
|
+
## v5.0.35 (2025-11-20)
|
|
132
133
|
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
|
133
134
|
|
|
134
135
|
## What's Changed
|
|
135
|
-
|
|
136
136
|
### Changes
|
|
137
|
+
* Feature/pm 2198 by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#129](https://github.com/jorgesolebur/CumulusCI/pull/129)
|
|
138
|
+
* Improvements with SFDMU when exporting data from org with namespace injection by [@jorgesolebur](https://github.com/jorgesolebur) in [#131](https://github.com/jorgesolebur/CumulusCI/pull/131)
|
|
139
|
+
* Rename project from CumulusCI to CumulusCI Plus by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#133](https://github.com/jorgesolebur/CumulusCI/pull/133)
|
|
137
140
|
|
|
138
|
-
- Fix profile query from tooling api field to simple salesforce. by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#67](https://github.com/jorgesolebur/CumulusCI/pull/67)
|
|
139
141
|
|
|
140
|
-
**Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.
|
|
142
|
+
**Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.34...v5.0.35
|