cumulusci-plus 5.0.19__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.
Files changed (123) hide show
  1. cumulusci/__about__.py +1 -1
  2. cumulusci/cli/logger.py +2 -2
  3. cumulusci/cli/service.py +20 -0
  4. cumulusci/cli/task.py +17 -0
  5. cumulusci/cli/tests/test_error.py +3 -1
  6. cumulusci/cli/tests/test_flow.py +279 -2
  7. cumulusci/cli/tests/test_service.py +15 -12
  8. cumulusci/cli/tests/test_task.py +88 -2
  9. cumulusci/cli/tests/utils.py +1 -4
  10. cumulusci/core/config/base_task_flow_config.py +26 -1
  11. cumulusci/core/config/project_config.py +2 -20
  12. cumulusci/core/config/tests/test_config_expensive.py +9 -3
  13. cumulusci/core/config/universal_config.py +3 -4
  14. cumulusci/core/dependencies/base.py +1 -1
  15. cumulusci/core/dependencies/dependencies.py +1 -1
  16. cumulusci/core/dependencies/github.py +1 -2
  17. cumulusci/core/dependencies/resolvers.py +1 -1
  18. cumulusci/core/dependencies/tests/test_dependencies.py +1 -1
  19. cumulusci/core/dependencies/tests/test_resolvers.py +1 -1
  20. cumulusci/core/flowrunner.py +90 -6
  21. cumulusci/core/github.py +1 -1
  22. cumulusci/core/sfdx.py +3 -1
  23. cumulusci/core/source_transforms/tests/test_transforms.py +1 -1
  24. cumulusci/core/source_transforms/transforms.py +1 -1
  25. cumulusci/core/tasks.py +13 -2
  26. cumulusci/core/tests/test_flowrunner.py +100 -0
  27. cumulusci/core/tests/test_tasks.py +65 -0
  28. cumulusci/core/utils.py +3 -1
  29. cumulusci/core/versions.py +1 -1
  30. cumulusci/cumulusci.yml +55 -0
  31. cumulusci/oauth/client.py +1 -1
  32. cumulusci/plugins/plugin_base.py +5 -3
  33. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
  34. cumulusci/salesforce_api/rest_deploy.py +1 -1
  35. cumulusci/schema/cumulusci.jsonschema.json +64 -0
  36. cumulusci/tasks/apex/anon.py +1 -1
  37. cumulusci/tasks/apex/testrunner.py +416 -142
  38. cumulusci/tasks/apex/tests/test_apex_tasks.py +917 -1
  39. cumulusci/tasks/bulkdata/extract.py +0 -1
  40. cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +1 -1
  41. cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +1 -1
  42. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +1 -1
  43. cumulusci/tasks/bulkdata/generate_and_load_data.py +136 -12
  44. cumulusci/tasks/bulkdata/mapping_parser.py +139 -44
  45. cumulusci/tasks/bulkdata/select_utils.py +1 -1
  46. cumulusci/tasks/bulkdata/snowfakery.py +100 -25
  47. cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +159 -0
  48. cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
  49. cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +763 -1
  50. cumulusci/tasks/bulkdata/tests/test_select_utils.py +26 -0
  51. cumulusci/tasks/bulkdata/tests/test_snowfakery.py +133 -0
  52. cumulusci/tasks/create_package_version.py +190 -16
  53. cumulusci/tasks/datadictionary.py +1 -1
  54. cumulusci/tasks/metadata_etl/base.py +7 -3
  55. cumulusci/tasks/metadata_etl/layouts.py +1 -1
  56. cumulusci/tasks/metadata_etl/permissions.py +1 -1
  57. cumulusci/tasks/metadata_etl/remote_site_settings.py +2 -2
  58. cumulusci/tasks/push/README.md +15 -17
  59. cumulusci/tasks/release_notes/README.md +13 -13
  60. cumulusci/tasks/release_notes/generator.py +13 -8
  61. cumulusci/tasks/robotframework/tests/test_robotframework.py +6 -1
  62. cumulusci/tasks/salesforce/Deploy.py +53 -2
  63. cumulusci/tasks/salesforce/SfPackageCommands.py +363 -0
  64. cumulusci/tasks/salesforce/__init__.py +1 -0
  65. cumulusci/tasks/salesforce/assign_ps_psg.py +448 -0
  66. cumulusci/tasks/salesforce/composite.py +1 -1
  67. cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
  68. cumulusci/tasks/salesforce/enable_prediction.py +5 -1
  69. cumulusci/tasks/salesforce/getPackageVersion.py +89 -0
  70. cumulusci/tasks/salesforce/profiles.py +13 -9
  71. cumulusci/tasks/salesforce/sourcetracking.py +1 -1
  72. cumulusci/tasks/salesforce/tests/test_Deploy.py +316 -1
  73. cumulusci/tasks/salesforce/tests/test_SfPackageCommands.py +554 -0
  74. cumulusci/tasks/salesforce/tests/test_assign_ps_psg.py +1055 -0
  75. cumulusci/tasks/salesforce/tests/test_getPackageVersion.py +651 -0
  76. cumulusci/tasks/salesforce/tests/test_profiles.py +43 -3
  77. cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
  78. cumulusci/tasks/salesforce/tests/test_update_external_credential.py +912 -0
  79. cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
  80. cumulusci/tasks/salesforce/update_dependencies.py +2 -2
  81. cumulusci/tasks/salesforce/update_external_credential.py +562 -0
  82. cumulusci/tasks/salesforce/update_named_credential.py +441 -0
  83. cumulusci/tasks/salesforce/update_profile.py +17 -13
  84. cumulusci/tasks/salesforce/users/permsets.py +62 -5
  85. cumulusci/tasks/salesforce/users/tests/test_permsets.py +237 -11
  86. cumulusci/tasks/sfdmu/__init__.py +0 -0
  87. cumulusci/tasks/sfdmu/sfdmu.py +363 -0
  88. cumulusci/tasks/sfdmu/tests/__init__.py +1 -0
  89. cumulusci/tasks/sfdmu/tests/test_runner.py +212 -0
  90. cumulusci/tasks/sfdmu/tests/test_sfdmu.py +1012 -0
  91. cumulusci/tasks/tests/test_create_package_version.py +716 -1
  92. cumulusci/tasks/tests/test_util.py +42 -0
  93. cumulusci/tasks/util.py +37 -1
  94. cumulusci/tasks/utility/copyContents.py +402 -0
  95. cumulusci/tasks/utility/credentialManager.py +256 -0
  96. cumulusci/tasks/utility/directoryRecreator.py +30 -0
  97. cumulusci/tasks/utility/env_management.py +1 -1
  98. cumulusci/tasks/utility/secretsToEnv.py +135 -0
  99. cumulusci/tasks/utility/tests/test_copyContents.py +1719 -0
  100. cumulusci/tasks/utility/tests/test_credentialManager.py +564 -0
  101. cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
  102. cumulusci/tasks/utility/tests/test_secretsToEnv.py +1091 -0
  103. cumulusci/tests/test_integration_infrastructure.py +3 -1
  104. cumulusci/tests/test_utils.py +70 -6
  105. cumulusci/utils/__init__.py +54 -9
  106. cumulusci/utils/classutils.py +5 -2
  107. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
  108. cumulusci/utils/options.py +23 -1
  109. cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +1 -1
  110. cumulusci/utils/yaml/cumulusci_yml.py +7 -3
  111. cumulusci/utils/yaml/model_parser.py +2 -2
  112. cumulusci/utils/yaml/tests/test_cumulusci_yml.py +1 -1
  113. cumulusci/utils/yaml/tests/test_model_parser.py +3 -3
  114. cumulusci/vcs/base.py +23 -15
  115. cumulusci/vcs/bootstrap.py +5 -4
  116. cumulusci/vcs/utils/list_modified_files.py +189 -0
  117. cumulusci/vcs/utils/tests/test_list_modified_files.py +588 -0
  118. {cumulusci_plus-5.0.19.dist-info → cumulusci_plus-5.0.35.dist-info}/METADATA +12 -10
  119. {cumulusci_plus-5.0.19.dist-info → cumulusci_plus-5.0.35.dist-info}/RECORD +123 -98
  120. {cumulusci_plus-5.0.19.dist-info → cumulusci_plus-5.0.35.dist-info}/WHEEL +0 -0
  121. {cumulusci_plus-5.0.19.dist-info → cumulusci_plus-5.0.35.dist-info}/entry_points.txt +0 -0
  122. {cumulusci_plus-5.0.19.dist-info → cumulusci_plus-5.0.35.dist-info}/licenses/AUTHORS.rst +0 -0
  123. {cumulusci_plus-5.0.19.dist-info → cumulusci_plus-5.0.35.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,439 @@
1
+ """Tests for directoryRecreator module."""
2
+
3
+ import os
4
+ import tempfile
5
+ from pathlib import Path
6
+ from unittest import mock
7
+
8
+ import pytest
9
+
10
+ from cumulusci.core.config import TaskConfig
11
+ from cumulusci.core.exceptions import TaskOptionsError
12
+ from cumulusci.tasks.utility.directoryRecreator import DirectoryRecreator
13
+
14
+
15
+ class TestDirectoryRecreatorOptions:
16
+ """Test cases for DirectoryRecreator options configuration."""
17
+
18
+ def test_required_path_option(self):
19
+ """Test that path option is required."""
20
+ with tempfile.TemporaryDirectory() as tmpdir:
21
+ test_dir = os.path.join(tmpdir, "test_directory")
22
+ task_config = TaskConfig({"options": {"path": test_dir}})
23
+
24
+ task = DirectoryRecreator(
25
+ project_config=mock.Mock(),
26
+ task_config=task_config,
27
+ org_config=None,
28
+ )
29
+
30
+ assert task.parsed_options.path == Path(test_dir)
31
+
32
+ def test_path_as_string(self):
33
+ """Test initialization with path as string."""
34
+ with tempfile.TemporaryDirectory() as tmpdir:
35
+ test_dir = os.path.join(tmpdir, "test_dir")
36
+ task_config = TaskConfig({"options": {"path": test_dir}})
37
+
38
+ task = DirectoryRecreator(
39
+ project_config=mock.Mock(),
40
+ task_config=task_config,
41
+ org_config=None,
42
+ )
43
+
44
+ assert isinstance(task.parsed_options.path, Path)
45
+ assert str(task.parsed_options.path) == test_dir
46
+
47
+ def test_path_as_path_object(self):
48
+ """Test initialization with path as Path object."""
49
+ with tempfile.TemporaryDirectory() as tmpdir:
50
+ test_dir = Path(tmpdir) / "test_dir"
51
+ task_config = TaskConfig({"options": {"path": test_dir}})
52
+
53
+ task = DirectoryRecreator(
54
+ project_config=mock.Mock(),
55
+ task_config=task_config,
56
+ org_config=None,
57
+ )
58
+
59
+ assert task.parsed_options.path == test_dir
60
+
61
+
62
+ class TestDirectoryRecreatorInitialization:
63
+ """Test cases for DirectoryRecreator initialization methods."""
64
+
65
+ def test_init_options_raises_error_when_path_is_file(self):
66
+ """Test that _init_options raises TaskOptionsError when path is a file."""
67
+ with tempfile.TemporaryDirectory() as tmpdir:
68
+ # Create a file, not a directory
69
+ test_file = os.path.join(tmpdir, "test_file.txt")
70
+ with open(test_file, "w") as f:
71
+ f.write("test content")
72
+
73
+ task_config = TaskConfig({"options": {"path": test_file}})
74
+
75
+ with pytest.raises(TaskOptionsError) as exc_info:
76
+ DirectoryRecreator(
77
+ project_config=mock.Mock(),
78
+ task_config=task_config,
79
+ org_config=None,
80
+ )
81
+
82
+ assert f"Path {test_file} is a file" in str(exc_info.value)
83
+
84
+ def test_init_options_succeeds_when_path_is_directory(self):
85
+ """Test that _init_options succeeds when path is an existing directory."""
86
+ with tempfile.TemporaryDirectory() as tmpdir:
87
+ test_dir = os.path.join(tmpdir, "test_directory")
88
+ os.makedirs(test_dir)
89
+
90
+ task_config = TaskConfig({"options": {"path": test_dir}})
91
+
92
+ task = DirectoryRecreator(
93
+ project_config=mock.Mock(),
94
+ task_config=task_config,
95
+ org_config=None,
96
+ )
97
+
98
+ assert task.parsed_options.path == Path(test_dir)
99
+
100
+ def test_init_options_succeeds_when_path_does_not_exist(self):
101
+ """Test that _init_options succeeds when path doesn't exist yet."""
102
+ with tempfile.TemporaryDirectory() as tmpdir:
103
+ test_dir = os.path.join(tmpdir, "non_existent_directory")
104
+
105
+ task_config = TaskConfig({"options": {"path": test_dir}})
106
+
107
+ task = DirectoryRecreator(
108
+ project_config=mock.Mock(),
109
+ task_config=task_config,
110
+ org_config=None,
111
+ )
112
+
113
+ assert task.parsed_options.path == Path(test_dir)
114
+
115
+
116
+ class TestDirectoryRecreatorRunTask:
117
+ """Test cases for _run_task method."""
118
+
119
+ def test_run_task_creates_new_directory(self):
120
+ """Test _run_task creates a new directory when path doesn't exist."""
121
+ with tempfile.TemporaryDirectory() as tmpdir:
122
+ test_dir = os.path.join(tmpdir, "new_directory")
123
+
124
+ task_config = TaskConfig({"options": {"path": test_dir}})
125
+
126
+ project_config = mock.Mock()
127
+ project_config.repo_root = tmpdir
128
+
129
+ task = DirectoryRecreator(
130
+ project_config=project_config,
131
+ task_config=task_config,
132
+ org_config=None,
133
+ )
134
+
135
+ # Directory should not exist yet
136
+ assert not os.path.exists(test_dir)
137
+
138
+ # Run the task
139
+ task()
140
+
141
+ # Directory should now exist
142
+ assert os.path.exists(test_dir)
143
+ assert os.path.isdir(test_dir)
144
+
145
+ def test_run_task_recreates_existing_directory(self):
146
+ """Test _run_task removes and recreates an existing directory."""
147
+ with tempfile.TemporaryDirectory() as tmpdir:
148
+ test_dir = os.path.join(tmpdir, "existing_directory")
149
+
150
+ # Create directory with a file inside
151
+ os.makedirs(test_dir)
152
+ test_file = os.path.join(test_dir, "test_file.txt")
153
+ with open(test_file, "w") as f:
154
+ f.write("test content")
155
+
156
+ # Verify directory and file exist
157
+ assert os.path.exists(test_dir)
158
+ assert os.path.exists(test_file)
159
+
160
+ task_config = TaskConfig({"options": {"path": test_dir}})
161
+
162
+ project_config = mock.Mock()
163
+ project_config.repo_root = tmpdir
164
+
165
+ task = DirectoryRecreator(
166
+ project_config=project_config,
167
+ task_config=task_config,
168
+ org_config=None,
169
+ )
170
+
171
+ # Run the task
172
+ task()
173
+
174
+ # Directory should exist but file should be gone
175
+ assert os.path.exists(test_dir)
176
+ assert os.path.isdir(test_dir)
177
+ assert not os.path.exists(test_file)
178
+
179
+ def test_run_task_logs_created_message_for_new_directory(self):
180
+ """Test _run_task logs 'created' message for new directory."""
181
+ with tempfile.TemporaryDirectory() as tmpdir:
182
+ test_dir = os.path.join(tmpdir, "new_directory")
183
+
184
+ task_config = TaskConfig({"options": {"path": test_dir}})
185
+
186
+ project_config = mock.Mock()
187
+ project_config.repo_root = tmpdir
188
+
189
+ task = DirectoryRecreator(
190
+ project_config=project_config,
191
+ task_config=task_config,
192
+ org_config=None,
193
+ )
194
+
195
+ with mock.patch.object(task.logger, "info") as mock_logger:
196
+ task()
197
+
198
+ # Check that the correct message was logged (may be called multiple times by BaseTask)
199
+ log_messages = [call[0][0] for call in mock_logger.call_args_list]
200
+ assert any(
201
+ "created" in msg
202
+ and "removed and created" not in msg
203
+ and str(test_dir) in msg
204
+ for msg in log_messages
205
+ )
206
+
207
+ def test_run_task_logs_removed_and_created_message_for_existing_directory(self):
208
+ """Test _run_task logs 'removed and created' message for existing directory."""
209
+ with tempfile.TemporaryDirectory() as tmpdir:
210
+ test_dir = os.path.join(tmpdir, "existing_directory")
211
+ os.makedirs(test_dir)
212
+
213
+ task_config = TaskConfig({"options": {"path": test_dir}})
214
+
215
+ project_config = mock.Mock()
216
+ project_config.repo_root = tmpdir
217
+
218
+ task = DirectoryRecreator(
219
+ project_config=project_config,
220
+ task_config=task_config,
221
+ org_config=None,
222
+ )
223
+
224
+ with mock.patch.object(task.logger, "info") as mock_logger:
225
+ task()
226
+
227
+ # Check that the correct message was logged (may be called multiple times by BaseTask)
228
+ log_messages = [call[0][0] for call in mock_logger.call_args_list]
229
+ assert any(
230
+ "removed and created" in msg and str(test_dir) in msg
231
+ for msg in log_messages
232
+ )
233
+
234
+ def test_run_task_handles_nested_directory_path(self):
235
+ """Test _run_task can create nested directory paths."""
236
+ with tempfile.TemporaryDirectory() as tmpdir:
237
+ test_dir = os.path.join(tmpdir, "parent", "child", "grandchild")
238
+
239
+ task_config = TaskConfig({"options": {"path": test_dir}})
240
+
241
+ project_config = mock.Mock()
242
+ project_config.repo_root = tmpdir
243
+
244
+ task = DirectoryRecreator(
245
+ project_config=project_config,
246
+ task_config=task_config,
247
+ org_config=None,
248
+ )
249
+
250
+ # Run the task
251
+ task()
252
+
253
+ # All nested directories should exist
254
+ assert os.path.exists(test_dir)
255
+ assert os.path.isdir(test_dir)
256
+
257
+ def test_run_task_preserves_parent_directories(self):
258
+ """Test _run_task only removes the specified directory, not parents."""
259
+ with tempfile.TemporaryDirectory() as tmpdir:
260
+ parent_dir = os.path.join(tmpdir, "parent")
261
+ child_dir = os.path.join(parent_dir, "child")
262
+
263
+ # Create parent and child directories
264
+ os.makedirs(child_dir)
265
+
266
+ # Create files in both directories
267
+ parent_file = os.path.join(parent_dir, "parent.txt")
268
+ child_file = os.path.join(child_dir, "child.txt")
269
+
270
+ with open(parent_file, "w") as f:
271
+ f.write("parent content")
272
+ with open(child_file, "w") as f:
273
+ f.write("child content")
274
+
275
+ task_config = TaskConfig({"options": {"path": child_dir}})
276
+
277
+ project_config = mock.Mock()
278
+ project_config.repo_root = tmpdir
279
+
280
+ task = DirectoryRecreator(
281
+ project_config=project_config,
282
+ task_config=task_config,
283
+ org_config=None,
284
+ )
285
+
286
+ # Run the task
287
+ task()
288
+
289
+ # Parent directory and its file should still exist
290
+ assert os.path.exists(parent_dir)
291
+ assert os.path.exists(parent_file)
292
+
293
+ # Child directory should exist but its file should be gone
294
+ assert os.path.exists(child_dir)
295
+ assert not os.path.exists(child_file)
296
+
297
+ def test_run_task_with_relative_path(self):
298
+ """Test _run_task works with relative paths."""
299
+ with tempfile.TemporaryDirectory() as tmpdir:
300
+ original_cwd = os.getcwd()
301
+ try:
302
+ os.chdir(tmpdir)
303
+ test_dir = "relative_test_dir"
304
+
305
+ task_config = TaskConfig({"options": {"path": test_dir}})
306
+
307
+ project_config = mock.Mock()
308
+ project_config.repo_root = tmpdir
309
+
310
+ task = DirectoryRecreator(
311
+ project_config=project_config,
312
+ task_config=task_config,
313
+ org_config=None,
314
+ )
315
+
316
+ # Run the task
317
+ task()
318
+
319
+ # Directory should exist
320
+ assert os.path.exists(test_dir)
321
+ assert os.path.isdir(test_dir)
322
+ finally:
323
+ os.chdir(original_cwd)
324
+
325
+ def test_run_task_with_directory_containing_subdirectories(self):
326
+ """Test _run_task removes directory with complex structure."""
327
+ with tempfile.TemporaryDirectory() as tmpdir:
328
+ test_dir = os.path.join(tmpdir, "complex_directory")
329
+
330
+ # Create complex directory structure
331
+ os.makedirs(os.path.join(test_dir, "subdir1", "subdir2"))
332
+ os.makedirs(os.path.join(test_dir, "subdir3"))
333
+
334
+ # Create files in various locations
335
+ with open(os.path.join(test_dir, "file1.txt"), "w") as f:
336
+ f.write("content1")
337
+ with open(os.path.join(test_dir, "subdir1", "file2.txt"), "w") as f:
338
+ f.write("content2")
339
+ with open(
340
+ os.path.join(test_dir, "subdir1", "subdir2", "file3.txt"), "w"
341
+ ) as f:
342
+ f.write("content3")
343
+
344
+ task_config = TaskConfig({"options": {"path": test_dir}})
345
+
346
+ project_config = mock.Mock()
347
+ project_config.repo_root = tmpdir
348
+
349
+ task = DirectoryRecreator(
350
+ project_config=project_config,
351
+ task_config=task_config,
352
+ org_config=None,
353
+ )
354
+
355
+ # Run the task
356
+ task()
357
+
358
+ # Directory should exist but be empty
359
+ assert os.path.exists(test_dir)
360
+ assert os.path.isdir(test_dir)
361
+ assert len(os.listdir(test_dir)) == 0
362
+
363
+
364
+ class TestDirectoryRecreatorEdgeCases:
365
+ """Test cases for edge cases and special scenarios."""
366
+
367
+ def test_task_with_path_containing_spaces(self):
368
+ """Test task handles paths with spaces correctly."""
369
+ with tempfile.TemporaryDirectory() as tmpdir:
370
+ test_dir = os.path.join(tmpdir, "directory with spaces")
371
+
372
+ task_config = TaskConfig({"options": {"path": test_dir}})
373
+
374
+ project_config = mock.Mock()
375
+ project_config.repo_root = tmpdir
376
+
377
+ task = DirectoryRecreator(
378
+ project_config=project_config,
379
+ task_config=task_config,
380
+ org_config=None,
381
+ )
382
+
383
+ task()
384
+
385
+ assert os.path.exists(test_dir)
386
+ assert os.path.isdir(test_dir)
387
+
388
+ def test_task_with_path_containing_special_characters(self):
389
+ """Test task handles paths with special characters."""
390
+ with tempfile.TemporaryDirectory() as tmpdir:
391
+ test_dir = os.path.join(tmpdir, "test-dir_123")
392
+
393
+ task_config = TaskConfig({"options": {"path": test_dir}})
394
+
395
+ project_config = mock.Mock()
396
+ project_config.repo_root = tmpdir
397
+
398
+ task = DirectoryRecreator(
399
+ project_config=project_config,
400
+ task_config=task_config,
401
+ org_config=None,
402
+ )
403
+
404
+ task()
405
+
406
+ assert os.path.exists(test_dir)
407
+ assert os.path.isdir(test_dir)
408
+
409
+ def test_task_called_multiple_times(self):
410
+ """Test task can be called multiple times successfully."""
411
+ with tempfile.TemporaryDirectory() as tmpdir:
412
+ test_dir = os.path.join(tmpdir, "test_directory")
413
+
414
+ task_config = TaskConfig({"options": {"path": test_dir}})
415
+
416
+ project_config = mock.Mock()
417
+ project_config.repo_root = tmpdir
418
+
419
+ task = DirectoryRecreator(
420
+ project_config=project_config,
421
+ task_config=task_config,
422
+ org_config=None,
423
+ )
424
+
425
+ # Run the task multiple times
426
+ task()
427
+ assert os.path.exists(test_dir)
428
+
429
+ # Add a file
430
+ with open(os.path.join(test_dir, "file.txt"), "w") as f:
431
+ f.write("content")
432
+
433
+ task()
434
+ assert os.path.exists(test_dir)
435
+ assert len(os.listdir(test_dir)) == 0
436
+
437
+ task()
438
+ assert os.path.exists(test_dir)
439
+ assert len(os.listdir(test_dir)) == 0