cumulusci-plus 5.0.21__py3-none-any.whl → 5.0.43__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 +19 -3
- cumulusci/cli/tests/test_error.py +3 -1
- cumulusci/cli/tests/test_flow.py +279 -2
- cumulusci/cli/tests/test_org.py +5 -0
- cumulusci/cli/tests/test_service.py +15 -12
- cumulusci/cli/tests/test_task.py +122 -2
- cumulusci/cli/tests/utils.py +1 -4
- cumulusci/core/config/__init__.py +1 -0
- cumulusci/core/config/base_task_flow_config.py +26 -1
- cumulusci/core/config/org_config.py +2 -1
- cumulusci/core/config/project_config.py +14 -20
- cumulusci/core/config/scratch_org_config.py +12 -0
- cumulusci/core/config/tests/test_config.py +1 -0
- cumulusci/core/config/tests/test_config_expensive.py +9 -3
- cumulusci/core/config/universal_config.py +3 -4
- cumulusci/core/dependencies/base.py +5 -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 +73 -1
- 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 +69 -0
- cumulusci/tasks/apex/anon.py +1 -1
- cumulusci/tasks/apex/testrunner.py +421 -144
- 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 +46 -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/__init__.py +2 -0
- cumulusci/tasks/metadata_etl/applications.py +256 -0
- 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/metadata_etl/tests/test_applications.py +710 -0
- 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/insert_record.py +18 -19
- 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_enable_prediction.py +4 -2
- 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_auth_identity_provider.py +927 -0
- cumulusci/tasks/salesforce/tests/test_update_external_credential.py +1427 -0
- cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
- cumulusci/tasks/salesforce/tests/test_update_record.py +512 -0
- cumulusci/tasks/salesforce/update_dependencies.py +2 -2
- cumulusci/tasks/salesforce/update_external_auth_identity_provider.py +551 -0
- cumulusci/tasks/salesforce/update_external_credential.py +647 -0
- cumulusci/tasks/salesforce/update_named_credential.py +441 -0
- cumulusci/tasks/salesforce/update_profile.py +17 -13
- cumulusci/tasks/salesforce/update_record.py +217 -0
- 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 +376 -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 +302 -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 +1150 -0
- cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
- cumulusci/tasks/utility/tests/test_secretsToEnv.py +1118 -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 +8 -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.43.dist-info}/METADATA +11 -10
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/RECORD +135 -104
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/WHEEL +1 -1
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/entry_points.txt +0 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/licenses/AUTHORS.rst +0 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,1719 @@
|
|
|
1
|
+
"""Tests for copyContents module."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from cumulusci.core.config import BaseProjectConfig, TaskConfig, UniversalConfig
|
|
11
|
+
from cumulusci.tasks.utility.copyContents import (
|
|
12
|
+
ConsolidateUnpackagedMetadata,
|
|
13
|
+
clean_temp_directory,
|
|
14
|
+
consolidate_metadata,
|
|
15
|
+
copy_directory_contents,
|
|
16
|
+
copy_item_to_destination,
|
|
17
|
+
copy_matched_files,
|
|
18
|
+
merge_directory_contents,
|
|
19
|
+
print_directory_tree,
|
|
20
|
+
resolve_file_pattern,
|
|
21
|
+
validate_directory,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestMergeDirectoryContents:
|
|
26
|
+
"""Test cases for merge_directory_contents function."""
|
|
27
|
+
|
|
28
|
+
def test_merge_empty_directories(self):
|
|
29
|
+
"""Test merging two empty directories."""
|
|
30
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
31
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
32
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
33
|
+
os.makedirs(src_dir)
|
|
34
|
+
os.makedirs(dest_dir)
|
|
35
|
+
|
|
36
|
+
merge_directory_contents(src_dir, dest_dir)
|
|
37
|
+
assert os.path.exists(dest_dir)
|
|
38
|
+
|
|
39
|
+
def test_merge_files_from_source(self):
|
|
40
|
+
"""Test merging files from source to destination."""
|
|
41
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
42
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
43
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
44
|
+
os.makedirs(src_dir)
|
|
45
|
+
os.makedirs(dest_dir)
|
|
46
|
+
|
|
47
|
+
# Create file in source
|
|
48
|
+
src_file = os.path.join(src_dir, "test.txt")
|
|
49
|
+
with open(src_file, "w") as f:
|
|
50
|
+
f.write("source content")
|
|
51
|
+
|
|
52
|
+
merge_directory_contents(src_dir, dest_dir)
|
|
53
|
+
|
|
54
|
+
dest_file = os.path.join(dest_dir, "test.txt")
|
|
55
|
+
assert os.path.exists(dest_file)
|
|
56
|
+
with open(dest_file, "r") as f:
|
|
57
|
+
assert f.read() == "source content"
|
|
58
|
+
|
|
59
|
+
def test_merge_overwrites_existing_files(self):
|
|
60
|
+
"""Test that source files overwrite destination files."""
|
|
61
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
62
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
63
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
64
|
+
os.makedirs(src_dir)
|
|
65
|
+
os.makedirs(dest_dir)
|
|
66
|
+
|
|
67
|
+
# Create file in destination
|
|
68
|
+
dest_file = os.path.join(dest_dir, "test.txt")
|
|
69
|
+
with open(dest_file, "w") as f:
|
|
70
|
+
f.write("old content")
|
|
71
|
+
|
|
72
|
+
# Create file in source with different content
|
|
73
|
+
src_file = os.path.join(src_dir, "test.txt")
|
|
74
|
+
with open(src_file, "w") as f:
|
|
75
|
+
f.write("new content")
|
|
76
|
+
|
|
77
|
+
merge_directory_contents(src_dir, dest_dir)
|
|
78
|
+
|
|
79
|
+
with open(dest_file, "r") as f:
|
|
80
|
+
assert f.read() == "new content"
|
|
81
|
+
|
|
82
|
+
def test_merge_nested_directories(self):
|
|
83
|
+
"""Test merging nested directory structures."""
|
|
84
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
85
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
86
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
87
|
+
os.makedirs(src_dir)
|
|
88
|
+
os.makedirs(dest_dir)
|
|
89
|
+
|
|
90
|
+
# Create nested structure in source
|
|
91
|
+
nested_src = os.path.join(src_dir, "nested", "subdir")
|
|
92
|
+
os.makedirs(nested_src)
|
|
93
|
+
nested_file = os.path.join(nested_src, "file.txt")
|
|
94
|
+
with open(nested_file, "w") as f:
|
|
95
|
+
f.write("nested content")
|
|
96
|
+
|
|
97
|
+
merge_directory_contents(src_dir, dest_dir)
|
|
98
|
+
|
|
99
|
+
nested_dest = os.path.join(dest_dir, "nested", "subdir", "file.txt")
|
|
100
|
+
assert os.path.exists(nested_dest)
|
|
101
|
+
with open(nested_dest, "r") as f:
|
|
102
|
+
assert f.read() == "nested content"
|
|
103
|
+
|
|
104
|
+
def test_merge_with_overwrite_flag(self):
|
|
105
|
+
"""Test merge with overwrite flag enabled."""
|
|
106
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
107
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
108
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
109
|
+
os.makedirs(src_dir)
|
|
110
|
+
os.makedirs(dest_dir)
|
|
111
|
+
|
|
112
|
+
# Create file in destination
|
|
113
|
+
dest_file = os.path.join(dest_dir, "test.txt")
|
|
114
|
+
with open(dest_file, "w") as f:
|
|
115
|
+
f.write("old")
|
|
116
|
+
|
|
117
|
+
# Create directory with same name in source
|
|
118
|
+
src_subdir = os.path.join(src_dir, "test.txt")
|
|
119
|
+
os.makedirs(src_subdir)
|
|
120
|
+
|
|
121
|
+
merge_directory_contents(src_dir, dest_dir, overwrite=True)
|
|
122
|
+
|
|
123
|
+
# Should be replaced with directory
|
|
124
|
+
assert os.path.isdir(os.path.join(dest_dir, "test.txt"))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestCopyItemToDestination:
|
|
128
|
+
"""Test cases for copy_item_to_destination function."""
|
|
129
|
+
|
|
130
|
+
def test_copy_file_to_new_location(self):
|
|
131
|
+
"""Test copying a file to a new location."""
|
|
132
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
133
|
+
src_file = os.path.join(tmpdir, "source.txt")
|
|
134
|
+
dest_file = os.path.join(tmpdir, "dest.txt")
|
|
135
|
+
|
|
136
|
+
with open(src_file, "w") as f:
|
|
137
|
+
f.write("test content")
|
|
138
|
+
|
|
139
|
+
copy_item_to_destination(src_file, dest_file)
|
|
140
|
+
|
|
141
|
+
assert os.path.exists(dest_file)
|
|
142
|
+
with open(dest_file, "r") as f:
|
|
143
|
+
assert f.read() == "test content"
|
|
144
|
+
|
|
145
|
+
def test_copy_directory_to_new_location(self):
|
|
146
|
+
"""Test copying a directory to a new location."""
|
|
147
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
148
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
149
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
150
|
+
os.makedirs(src_dir)
|
|
151
|
+
|
|
152
|
+
src_file = os.path.join(src_dir, "file.txt")
|
|
153
|
+
with open(src_file, "w") as f:
|
|
154
|
+
f.write("content")
|
|
155
|
+
|
|
156
|
+
copy_item_to_destination(src_dir, dest_dir)
|
|
157
|
+
|
|
158
|
+
assert os.path.exists(dest_dir)
|
|
159
|
+
assert os.path.exists(os.path.join(dest_dir, "file.txt"))
|
|
160
|
+
|
|
161
|
+
def test_copy_directory_merges_with_existing(self):
|
|
162
|
+
"""Test copying directory merges with existing directory."""
|
|
163
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
164
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
165
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
166
|
+
os.makedirs(src_dir)
|
|
167
|
+
os.makedirs(dest_dir)
|
|
168
|
+
|
|
169
|
+
# File in source
|
|
170
|
+
src_file = os.path.join(src_dir, "src_file.txt")
|
|
171
|
+
with open(src_file, "w") as f:
|
|
172
|
+
f.write("src")
|
|
173
|
+
|
|
174
|
+
# File in destination
|
|
175
|
+
dest_file = os.path.join(dest_dir, "dest_file.txt")
|
|
176
|
+
with open(dest_file, "w") as f:
|
|
177
|
+
f.write("dest")
|
|
178
|
+
|
|
179
|
+
copy_item_to_destination(src_dir, dest_dir)
|
|
180
|
+
|
|
181
|
+
# Both files should exist
|
|
182
|
+
assert os.path.exists(os.path.join(dest_dir, "src_file.txt"))
|
|
183
|
+
assert os.path.exists(os.path.join(dest_dir, "dest_file.txt"))
|
|
184
|
+
|
|
185
|
+
def test_copy_file_overwrites_existing_file(self):
|
|
186
|
+
"""Test copying file overwrites existing file."""
|
|
187
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
188
|
+
src_file = os.path.join(tmpdir, "source.txt")
|
|
189
|
+
dest_file = os.path.join(tmpdir, "dest.txt")
|
|
190
|
+
|
|
191
|
+
with open(dest_file, "w") as f:
|
|
192
|
+
f.write("old")
|
|
193
|
+
|
|
194
|
+
with open(src_file, "w") as f:
|
|
195
|
+
f.write("new")
|
|
196
|
+
|
|
197
|
+
copy_item_to_destination(src_file, dest_file)
|
|
198
|
+
|
|
199
|
+
with open(dest_file, "r") as f:
|
|
200
|
+
assert f.read() == "new"
|
|
201
|
+
|
|
202
|
+
def test_copy_file_replaces_directory_with_overwrite(self):
|
|
203
|
+
"""Test copying file replaces directory when overwrite=True."""
|
|
204
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
205
|
+
src_file = os.path.join(tmpdir, "source.txt")
|
|
206
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
207
|
+
|
|
208
|
+
os.makedirs(dest_dir)
|
|
209
|
+
with open(src_file, "w") as f:
|
|
210
|
+
f.write("file content")
|
|
211
|
+
|
|
212
|
+
copy_item_to_destination(src_file, dest_dir, overwrite=True)
|
|
213
|
+
|
|
214
|
+
assert os.path.isfile(dest_dir)
|
|
215
|
+
with open(dest_dir, "r") as f:
|
|
216
|
+
assert f.read() == "file content"
|
|
217
|
+
|
|
218
|
+
def test_copy_directory_replaces_file_with_overwrite(self):
|
|
219
|
+
"""Test copying directory replaces file when overwrite=True."""
|
|
220
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
221
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
222
|
+
dest_file = os.path.join(tmpdir, "dest")
|
|
223
|
+
|
|
224
|
+
os.makedirs(src_dir)
|
|
225
|
+
src_file = os.path.join(src_dir, "file.txt")
|
|
226
|
+
with open(src_file, "w") as f:
|
|
227
|
+
f.write("content")
|
|
228
|
+
|
|
229
|
+
with open(dest_file, "w") as f:
|
|
230
|
+
f.write("old")
|
|
231
|
+
|
|
232
|
+
copy_item_to_destination(src_dir, dest_file, overwrite=True)
|
|
233
|
+
|
|
234
|
+
assert os.path.isdir(dest_file)
|
|
235
|
+
assert os.path.exists(os.path.join(dest_file, "file.txt"))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class TestCopyDirectoryContents:
|
|
239
|
+
"""Test cases for copy_directory_contents function."""
|
|
240
|
+
|
|
241
|
+
def test_copy_all_contents(self):
|
|
242
|
+
"""Test copying all contents from source to destination."""
|
|
243
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
244
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
245
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
246
|
+
os.makedirs(src_dir)
|
|
247
|
+
os.makedirs(dest_dir)
|
|
248
|
+
|
|
249
|
+
# Create files and subdirectories
|
|
250
|
+
with open(os.path.join(src_dir, "file1.txt"), "w") as f:
|
|
251
|
+
f.write("file1")
|
|
252
|
+
os.makedirs(os.path.join(src_dir, "subdir"))
|
|
253
|
+
with open(os.path.join(src_dir, "subdir", "file2.txt"), "w") as f:
|
|
254
|
+
f.write("file2")
|
|
255
|
+
|
|
256
|
+
copy_directory_contents(src_dir, dest_dir)
|
|
257
|
+
|
|
258
|
+
assert os.path.exists(os.path.join(dest_dir, "file1.txt"))
|
|
259
|
+
assert os.path.exists(os.path.join(dest_dir, "subdir", "file2.txt"))
|
|
260
|
+
|
|
261
|
+
def test_extract_src_directory(self):
|
|
262
|
+
"""Test extracting src directory contents."""
|
|
263
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
264
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
265
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
266
|
+
os.makedirs(src_dir)
|
|
267
|
+
os.makedirs(dest_dir)
|
|
268
|
+
|
|
269
|
+
# Create src subdirectory
|
|
270
|
+
src_subdir = os.path.join(src_dir, "src")
|
|
271
|
+
os.makedirs(src_subdir)
|
|
272
|
+
with open(os.path.join(src_subdir, "file.txt"), "w") as f:
|
|
273
|
+
f.write("content")
|
|
274
|
+
|
|
275
|
+
copy_directory_contents(src_dir, dest_dir, extract_src=True)
|
|
276
|
+
|
|
277
|
+
# File should be in dest_dir/src/file.txt
|
|
278
|
+
assert os.path.exists(os.path.join(dest_dir, "src", "file.txt"))
|
|
279
|
+
|
|
280
|
+
def test_extract_src_with_no_src_directory(self):
|
|
281
|
+
"""Test extract_src when src directory doesn't exist."""
|
|
282
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
283
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
284
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
285
|
+
os.makedirs(src_dir)
|
|
286
|
+
os.makedirs(dest_dir)
|
|
287
|
+
|
|
288
|
+
with open(os.path.join(src_dir, "file.txt"), "w") as f:
|
|
289
|
+
f.write("content")
|
|
290
|
+
|
|
291
|
+
copy_directory_contents(src_dir, dest_dir, extract_src=True)
|
|
292
|
+
|
|
293
|
+
# Should copy normally since no src directory
|
|
294
|
+
assert os.path.exists(os.path.join(dest_dir, "file.txt"))
|
|
295
|
+
|
|
296
|
+
def test_copy_with_overwrite(self):
|
|
297
|
+
"""Test copying with overwrite flag."""
|
|
298
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
299
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
300
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
301
|
+
os.makedirs(src_dir)
|
|
302
|
+
os.makedirs(dest_dir)
|
|
303
|
+
|
|
304
|
+
# Create conflicting file
|
|
305
|
+
dest_file = os.path.join(dest_dir, "test.txt")
|
|
306
|
+
with open(dest_file, "w") as f:
|
|
307
|
+
f.write("old")
|
|
308
|
+
|
|
309
|
+
src_file = os.path.join(src_dir, "test.txt")
|
|
310
|
+
with open(src_file, "w") as f:
|
|
311
|
+
f.write("new")
|
|
312
|
+
|
|
313
|
+
copy_directory_contents(src_dir, dest_dir, overwrite=True)
|
|
314
|
+
|
|
315
|
+
with open(dest_file, "r") as f:
|
|
316
|
+
assert f.read() == "new"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class TestResolveFilePattern:
|
|
320
|
+
"""Test cases for resolve_file_pattern function."""
|
|
321
|
+
|
|
322
|
+
def test_resolve_specific_file(self):
|
|
323
|
+
"""Test resolving a specific file path."""
|
|
324
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
325
|
+
test_file = os.path.join(tmpdir, "test.txt")
|
|
326
|
+
with open(test_file, "w") as f:
|
|
327
|
+
f.write("content")
|
|
328
|
+
|
|
329
|
+
result = resolve_file_pattern("test.txt", tmpdir)
|
|
330
|
+
assert len(result) == 1
|
|
331
|
+
assert result[0] == test_file
|
|
332
|
+
|
|
333
|
+
def test_resolve_glob_pattern(self):
|
|
334
|
+
"""Test resolving a glob pattern."""
|
|
335
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
336
|
+
# Create multiple files
|
|
337
|
+
for i in range(3):
|
|
338
|
+
with open(os.path.join(tmpdir, f"file{i}.txt"), "w") as f:
|
|
339
|
+
f.write(f"content{i}")
|
|
340
|
+
|
|
341
|
+
result = resolve_file_pattern("*.txt", tmpdir)
|
|
342
|
+
assert len(result) == 3
|
|
343
|
+
|
|
344
|
+
def test_resolve_recursive_pattern(self):
|
|
345
|
+
"""Test resolving recursive glob pattern."""
|
|
346
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
347
|
+
subdir = os.path.join(tmpdir, "subdir")
|
|
348
|
+
os.makedirs(subdir)
|
|
349
|
+
|
|
350
|
+
with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
|
|
351
|
+
f.write("content1")
|
|
352
|
+
with open(os.path.join(subdir, "file2.txt"), "w") as f:
|
|
353
|
+
f.write("content2")
|
|
354
|
+
|
|
355
|
+
result = resolve_file_pattern("**/*.txt", tmpdir)
|
|
356
|
+
assert len(result) == 2
|
|
357
|
+
|
|
358
|
+
def test_resolve_pattern_no_match_raises_error(self):
|
|
359
|
+
"""Test that resolving non-existent pattern raises ValueError."""
|
|
360
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
361
|
+
result = resolve_file_pattern("nonexistent.txt", tmpdir)
|
|
362
|
+
assert len(result) == 0
|
|
363
|
+
assert result == []
|
|
364
|
+
|
|
365
|
+
def test_resolve_pattern_with_subdirectory(self):
|
|
366
|
+
"""Test resolving pattern in subdirectory."""
|
|
367
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
368
|
+
subdir = os.path.join(tmpdir, "subdir")
|
|
369
|
+
os.makedirs(subdir)
|
|
370
|
+
test_file = os.path.join(subdir, "test.txt")
|
|
371
|
+
with open(test_file, "w") as f:
|
|
372
|
+
f.write("content")
|
|
373
|
+
|
|
374
|
+
result = resolve_file_pattern("subdir/test.txt", tmpdir)
|
|
375
|
+
assert len(result) == 1
|
|
376
|
+
assert result[0] == test_file
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class TestCopyMatchedFiles:
|
|
380
|
+
"""Test cases for copy_matched_files function."""
|
|
381
|
+
|
|
382
|
+
def test_copy_single_file(self):
|
|
383
|
+
"""Test copying a single matched file."""
|
|
384
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
385
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
386
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
387
|
+
os.makedirs(source_dir)
|
|
388
|
+
os.makedirs(dest_dir)
|
|
389
|
+
|
|
390
|
+
source_file = os.path.join(source_dir, "file.txt")
|
|
391
|
+
with open(source_file, "w") as f:
|
|
392
|
+
f.write("content")
|
|
393
|
+
|
|
394
|
+
copy_matched_files([source_file], source_dir, dest_dir)
|
|
395
|
+
|
|
396
|
+
dest_file = os.path.join(dest_dir, "file.txt")
|
|
397
|
+
assert os.path.exists(dest_file)
|
|
398
|
+
with open(dest_file, "r") as f:
|
|
399
|
+
assert f.read() == "content"
|
|
400
|
+
|
|
401
|
+
def test_copy_preserves_relative_structure(self):
|
|
402
|
+
"""Test copying preserves relative directory structure."""
|
|
403
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
404
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
405
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
406
|
+
os.makedirs(source_dir)
|
|
407
|
+
os.makedirs(dest_dir)
|
|
408
|
+
|
|
409
|
+
nested_file = os.path.join(source_dir, "subdir", "file.txt")
|
|
410
|
+
os.makedirs(os.path.dirname(nested_file))
|
|
411
|
+
with open(nested_file, "w") as f:
|
|
412
|
+
f.write("content")
|
|
413
|
+
|
|
414
|
+
copy_matched_files([nested_file], source_dir, dest_dir)
|
|
415
|
+
|
|
416
|
+
dest_file = os.path.join(dest_dir, "subdir", "file.txt")
|
|
417
|
+
assert os.path.exists(dest_file)
|
|
418
|
+
|
|
419
|
+
def test_copy_multiple_files(self):
|
|
420
|
+
"""Test copying multiple matched files."""
|
|
421
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
422
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
423
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
424
|
+
os.makedirs(source_dir)
|
|
425
|
+
os.makedirs(dest_dir)
|
|
426
|
+
|
|
427
|
+
files = []
|
|
428
|
+
for i in range(3):
|
|
429
|
+
file_path = os.path.join(source_dir, f"file{i}.txt")
|
|
430
|
+
with open(file_path, "w") as f:
|
|
431
|
+
f.write(f"content{i}")
|
|
432
|
+
files.append(file_path)
|
|
433
|
+
|
|
434
|
+
copy_matched_files(files, source_dir, dest_dir)
|
|
435
|
+
|
|
436
|
+
for i in range(3):
|
|
437
|
+
assert os.path.exists(os.path.join(dest_dir, f"file{i}.txt"))
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
class TestCleanTempDirectory:
|
|
441
|
+
"""Test cases for clean_temp_directory function."""
|
|
442
|
+
|
|
443
|
+
def test_clean_existing_directory(self):
|
|
444
|
+
"""Test cleaning an existing temporary directory."""
|
|
445
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
446
|
+
test_dir = os.path.join(tmpdir, "test")
|
|
447
|
+
os.makedirs(test_dir)
|
|
448
|
+
|
|
449
|
+
with open(os.path.join(test_dir, "file.txt"), "w") as f:
|
|
450
|
+
f.write("content")
|
|
451
|
+
|
|
452
|
+
clean_temp_directory(test_dir)
|
|
453
|
+
assert not os.path.exists(test_dir)
|
|
454
|
+
|
|
455
|
+
def test_clean_nonexistent_directory(self):
|
|
456
|
+
"""Test cleaning a non-existent directory doesn't raise error."""
|
|
457
|
+
clean_temp_directory("/nonexistent/path")
|
|
458
|
+
# Should not raise an exception
|
|
459
|
+
|
|
460
|
+
def test_clean_directory_with_nested_structure(self):
|
|
461
|
+
"""Test cleaning directory with nested structure."""
|
|
462
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
463
|
+
test_dir = os.path.join(tmpdir, "test")
|
|
464
|
+
nested = os.path.join(test_dir, "nested", "deep")
|
|
465
|
+
os.makedirs(nested)
|
|
466
|
+
|
|
467
|
+
with open(os.path.join(nested, "file.txt"), "w") as f:
|
|
468
|
+
f.write("content")
|
|
469
|
+
|
|
470
|
+
clean_temp_directory(test_dir)
|
|
471
|
+
assert not os.path.exists(test_dir)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class TestValidateDirectory:
|
|
475
|
+
"""Test cases for validate_directory function."""
|
|
476
|
+
|
|
477
|
+
def test_validate_existing_directory(self):
|
|
478
|
+
"""Test validating an existing directory."""
|
|
479
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
480
|
+
validate_directory(tmpdir)
|
|
481
|
+
# Should not raise
|
|
482
|
+
|
|
483
|
+
def test_validate_nonexistent_path_raises_error(self):
|
|
484
|
+
"""Test validating non-existent path raises ValueError."""
|
|
485
|
+
with pytest.raises(ValueError, match="does not exist"):
|
|
486
|
+
validate_directory("/nonexistent/path")
|
|
487
|
+
|
|
488
|
+
def test_validate_file_raises_error(self):
|
|
489
|
+
"""Test validating a file raises ValueError."""
|
|
490
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
491
|
+
test_file = os.path.join(tmpdir, "test.txt")
|
|
492
|
+
with open(test_file, "w") as f:
|
|
493
|
+
f.write("content")
|
|
494
|
+
|
|
495
|
+
with pytest.raises(ValueError, match="is not a directory"):
|
|
496
|
+
validate_directory(test_file)
|
|
497
|
+
|
|
498
|
+
def test_validate_with_custom_path_name(self):
|
|
499
|
+
"""Test validating with custom path name in error message."""
|
|
500
|
+
with pytest.raises(ValueError, match="custom_name does not exist"):
|
|
501
|
+
validate_directory("/nonexistent", "custom_name")
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class TestConsolidateMetadata:
|
|
505
|
+
"""Test cases for consolidate_metadata function."""
|
|
506
|
+
|
|
507
|
+
def test_consolidate_string_path(self):
|
|
508
|
+
"""Test consolidating metadata from string path."""
|
|
509
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
510
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
511
|
+
os.makedirs(source_dir)
|
|
512
|
+
|
|
513
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
514
|
+
f.write("content")
|
|
515
|
+
|
|
516
|
+
result, file_count = consolidate_metadata("source", tmpdir)
|
|
517
|
+
|
|
518
|
+
assert os.path.exists(result)
|
|
519
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
520
|
+
assert file_count == 1
|
|
521
|
+
clean_temp_directory(result)
|
|
522
|
+
|
|
523
|
+
def test_consolidate_list_paths(self):
|
|
524
|
+
"""Test consolidating metadata from list of paths."""
|
|
525
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
526
|
+
dir1 = os.path.join(tmpdir, "dir1")
|
|
527
|
+
dir2 = os.path.join(tmpdir, "dir2")
|
|
528
|
+
os.makedirs(dir1)
|
|
529
|
+
os.makedirs(dir2)
|
|
530
|
+
|
|
531
|
+
with open(os.path.join(dir1, "file1.txt"), "w") as f:
|
|
532
|
+
f.write("content1")
|
|
533
|
+
with open(os.path.join(dir2, "file2.txt"), "w") as f:
|
|
534
|
+
f.write("content2")
|
|
535
|
+
|
|
536
|
+
result, file_count = consolidate_metadata(["dir1", "dir2"], tmpdir)
|
|
537
|
+
|
|
538
|
+
assert os.path.exists(os.path.join(result, "file1.txt"))
|
|
539
|
+
assert os.path.exists(os.path.join(result, "file2.txt"))
|
|
540
|
+
assert file_count == 2
|
|
541
|
+
clean_temp_directory(result)
|
|
542
|
+
|
|
543
|
+
def test_consolidate_dict_with_wildcard(self):
|
|
544
|
+
"""Test consolidating metadata from dict with wildcard pattern."""
|
|
545
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
546
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
547
|
+
src_subdir = os.path.join(source_dir, "src")
|
|
548
|
+
os.makedirs(src_subdir)
|
|
549
|
+
|
|
550
|
+
with open(os.path.join(src_subdir, "file.txt"), "w") as f:
|
|
551
|
+
f.write("content")
|
|
552
|
+
|
|
553
|
+
result, file_count = consolidate_metadata({"source": "*.*"}, tmpdir)
|
|
554
|
+
|
|
555
|
+
assert os.path.exists(os.path.join(result, "src", "file.txt"))
|
|
556
|
+
assert file_count == 1
|
|
557
|
+
clean_temp_directory(result)
|
|
558
|
+
|
|
559
|
+
def test_consolidate_dict_with_single_pattern(self):
|
|
560
|
+
"""Test consolidating metadata from dict with single pattern."""
|
|
561
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
562
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
563
|
+
os.makedirs(source_dir)
|
|
564
|
+
|
|
565
|
+
test_file = os.path.join(source_dir, "test.txt")
|
|
566
|
+
with open(test_file, "w") as f:
|
|
567
|
+
f.write("content")
|
|
568
|
+
|
|
569
|
+
result, file_count = consolidate_metadata({"source": "test.txt"}, tmpdir)
|
|
570
|
+
|
|
571
|
+
assert os.path.exists(os.path.join(result, "test.txt"))
|
|
572
|
+
assert file_count == 1
|
|
573
|
+
clean_temp_directory(result)
|
|
574
|
+
|
|
575
|
+
def test_consolidate_dict_with_list_patterns(self):
|
|
576
|
+
"""Test consolidating metadata from dict with list of patterns."""
|
|
577
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
578
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
579
|
+
os.makedirs(source_dir)
|
|
580
|
+
|
|
581
|
+
file1 = os.path.join(source_dir, "file1.txt")
|
|
582
|
+
file2 = os.path.join(source_dir, "file2.txt")
|
|
583
|
+
with open(file1, "w") as f:
|
|
584
|
+
f.write("content1")
|
|
585
|
+
with open(file2, "w") as f:
|
|
586
|
+
f.write("content2")
|
|
587
|
+
|
|
588
|
+
result, file_count = consolidate_metadata(
|
|
589
|
+
{"source": ["file1.txt", "file2.txt"]}, tmpdir
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
assert os.path.exists(os.path.join(result, "file1.txt"))
|
|
593
|
+
assert os.path.exists(os.path.join(result, "file2.txt"))
|
|
594
|
+
assert file_count == 2
|
|
595
|
+
clean_temp_directory(result)
|
|
596
|
+
|
|
597
|
+
def test_consolidate_with_absolute_path(self):
|
|
598
|
+
"""Test consolidating with absolute path."""
|
|
599
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
600
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
601
|
+
os.makedirs(source_dir)
|
|
602
|
+
|
|
603
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
604
|
+
f.write("content")
|
|
605
|
+
|
|
606
|
+
abs_path = os.path.abspath(source_dir)
|
|
607
|
+
result, file_count = consolidate_metadata(abs_path, tmpdir)
|
|
608
|
+
|
|
609
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
610
|
+
assert file_count == 1
|
|
611
|
+
clean_temp_directory(result)
|
|
612
|
+
|
|
613
|
+
def test_consolidate_invalid_type_raises_error(self):
|
|
614
|
+
"""Test consolidating with invalid type raises TypeError or ValueError."""
|
|
615
|
+
# TypeGuard will catch type errors before ValueError
|
|
616
|
+
with pytest.raises((TypeError, ValueError)):
|
|
617
|
+
consolidate_metadata(12345) # Invalid type
|
|
618
|
+
|
|
619
|
+
def test_consolidate_invalid_pattern_type_raises_error(self):
|
|
620
|
+
"""Test consolidating with invalid pattern type raises TypeError or ValueError."""
|
|
621
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
622
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
623
|
+
os.makedirs(source_dir)
|
|
624
|
+
|
|
625
|
+
# TypeGuard will catch type errors before ValueError
|
|
626
|
+
with pytest.raises((TypeError, ValueError)):
|
|
627
|
+
consolidate_metadata({"source": 12345}, tmpdir)
|
|
628
|
+
|
|
629
|
+
def test_consolidate_nonexistent_directory_raises_error(self):
|
|
630
|
+
"""Test consolidating non-existent directory raises ValueError."""
|
|
631
|
+
with pytest.raises(ValueError):
|
|
632
|
+
consolidate_metadata("nonexistent", "/tmp")
|
|
633
|
+
|
|
634
|
+
def test_consolidate_cleans_up_on_error(self):
|
|
635
|
+
"""Test that temp directory is cleaned up on error."""
|
|
636
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
637
|
+
try:
|
|
638
|
+
consolidate_metadata("nonexistent", tmpdir)
|
|
639
|
+
except ValueError:
|
|
640
|
+
pass
|
|
641
|
+
|
|
642
|
+
# Temp directory should be cleaned up
|
|
643
|
+
# We can't directly check, but if it wasn't cleaned up,
|
|
644
|
+
# we'd have issues with temp directory accumulation
|
|
645
|
+
|
|
646
|
+
def test_consolidate_dict_with_star_pattern(self):
|
|
647
|
+
"""Test consolidating with '*' pattern."""
|
|
648
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
649
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
650
|
+
src_subdir = os.path.join(source_dir, "src")
|
|
651
|
+
os.makedirs(src_subdir)
|
|
652
|
+
|
|
653
|
+
with open(os.path.join(src_subdir, "file.txt"), "w") as f:
|
|
654
|
+
f.write("content")
|
|
655
|
+
|
|
656
|
+
result, file_count = consolidate_metadata({"source": "*"}, tmpdir)
|
|
657
|
+
|
|
658
|
+
assert os.path.exists(os.path.join(result, "src", "file.txt"))
|
|
659
|
+
assert file_count == 1
|
|
660
|
+
clean_temp_directory(result)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
class TestPrintDirectoryTree:
|
|
664
|
+
"""Test cases for print_directory_tree function."""
|
|
665
|
+
|
|
666
|
+
def test_print_tree_without_logger(self, capsys):
|
|
667
|
+
"""Test printing directory tree without logger."""
|
|
668
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
669
|
+
with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
|
|
670
|
+
f.write("content1")
|
|
671
|
+
os.makedirs(os.path.join(tmpdir, "subdir"))
|
|
672
|
+
with open(os.path.join(tmpdir, "subdir", "file2.txt"), "w") as f:
|
|
673
|
+
f.write("content2")
|
|
674
|
+
|
|
675
|
+
print_directory_tree(tmpdir)
|
|
676
|
+
|
|
677
|
+
captured = capsys.readouterr()
|
|
678
|
+
assert "file1.txt" in captured.out
|
|
679
|
+
assert "subdir" in captured.out
|
|
680
|
+
|
|
681
|
+
def test_print_tree_with_logger(self):
|
|
682
|
+
"""Test printing directory tree with logger."""
|
|
683
|
+
import logging
|
|
684
|
+
|
|
685
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
686
|
+
with open(os.path.join(tmpdir, "file.txt"), "w") as f:
|
|
687
|
+
f.write("content")
|
|
688
|
+
|
|
689
|
+
logger = logging.getLogger("test")
|
|
690
|
+
with mock.patch.object(logger, "info") as mock_info:
|
|
691
|
+
print_directory_tree(tmpdir, logger=logger)
|
|
692
|
+
|
|
693
|
+
# Verify logger.info was called
|
|
694
|
+
assert mock_info.called
|
|
695
|
+
call_args = [args[0] for args, _ in mock_info.call_args_list]
|
|
696
|
+
assert any("file.txt" in msg for msg in call_args)
|
|
697
|
+
|
|
698
|
+
def test_print_tree_respects_max_depth(self, capsys):
|
|
699
|
+
"""Test that print_tree respects max_depth parameter."""
|
|
700
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
701
|
+
# Create nested structure
|
|
702
|
+
current = tmpdir
|
|
703
|
+
for i in range(5):
|
|
704
|
+
current = os.path.join(current, f"level{i}")
|
|
705
|
+
os.makedirs(current)
|
|
706
|
+
|
|
707
|
+
print_directory_tree(tmpdir, max_depth=2)
|
|
708
|
+
|
|
709
|
+
captured = capsys.readouterr()
|
|
710
|
+
# Should only show first 2 levels
|
|
711
|
+
assert "level0" in captured.out
|
|
712
|
+
|
|
713
|
+
def test_print_tree_handles_permission_error(self, capsys):
|
|
714
|
+
"""Test that print_tree handles PermissionError gracefully."""
|
|
715
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
716
|
+
# Mock os.listdir to raise PermissionError
|
|
717
|
+
with mock.patch("os.listdir", side_effect=PermissionError("Access denied")):
|
|
718
|
+
print_directory_tree(tmpdir)
|
|
719
|
+
# Should not raise exception
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
class TestConsolidateUnpackagedMetadataTask:
|
|
723
|
+
"""Test cases for ConsolidateUnpackagedMetadata task class."""
|
|
724
|
+
|
|
725
|
+
def setup_method(self):
|
|
726
|
+
"""Set up test fixtures."""
|
|
727
|
+
self.temp_repo_root = tempfile.mkdtemp()
|
|
728
|
+
self.universal_config = UniversalConfig()
|
|
729
|
+
self.project_config = BaseProjectConfig(
|
|
730
|
+
self.universal_config,
|
|
731
|
+
config={"noyaml": True, "project": {"package": {}}},
|
|
732
|
+
repo_info={"root": self.temp_repo_root},
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
def teardown_method(self):
|
|
736
|
+
"""Clean up test fixtures."""
|
|
737
|
+
if os.path.exists(self.temp_repo_root):
|
|
738
|
+
shutil.rmtree(self.temp_repo_root)
|
|
739
|
+
|
|
740
|
+
def test_task_with_string_metadata_path(self):
|
|
741
|
+
"""Test task with string metadata path."""
|
|
742
|
+
source_dir = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
743
|
+
os.makedirs(source_dir)
|
|
744
|
+
|
|
745
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
746
|
+
f.write("content")
|
|
747
|
+
|
|
748
|
+
self.project_config.config["project"]["package"] = {
|
|
749
|
+
"unpackaged_metadata_path": "unpackaged/pre"
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
task_config = TaskConfig({"options": {"keep_temp": True}})
|
|
753
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
754
|
+
|
|
755
|
+
task()
|
|
756
|
+
|
|
757
|
+
# Task stores result in task.result (from _run_task return value)
|
|
758
|
+
result_path = task.result
|
|
759
|
+
|
|
760
|
+
assert result_path is not None
|
|
761
|
+
assert isinstance(result_path, str)
|
|
762
|
+
assert os.path.exists(result_path)
|
|
763
|
+
assert os.path.exists(os.path.join(result_path, "file.txt"))
|
|
764
|
+
clean_temp_directory(result_path)
|
|
765
|
+
|
|
766
|
+
def test_task_with_list_metadata_path(self):
|
|
767
|
+
"""Test task with list metadata path."""
|
|
768
|
+
dir1 = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
769
|
+
dir2 = os.path.join(self.temp_repo_root, "unpackaged", "post")
|
|
770
|
+
os.makedirs(dir1)
|
|
771
|
+
os.makedirs(dir2)
|
|
772
|
+
|
|
773
|
+
with open(os.path.join(dir1, "file1.txt"), "w") as f:
|
|
774
|
+
f.write("content1")
|
|
775
|
+
with open(os.path.join(dir2, "file2.txt"), "w") as f:
|
|
776
|
+
f.write("content2")
|
|
777
|
+
|
|
778
|
+
self.project_config.config["project"]["package"] = {
|
|
779
|
+
"unpackaged_metadata_path": ["unpackaged/pre", "unpackaged/post"]
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
task_config = TaskConfig({"options": {"keep_temp": True}})
|
|
783
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
784
|
+
|
|
785
|
+
task()
|
|
786
|
+
|
|
787
|
+
result_path = task.result
|
|
788
|
+
assert result_path is not None
|
|
789
|
+
assert os.path.exists(os.path.join(result_path, "file1.txt"))
|
|
790
|
+
assert os.path.exists(os.path.join(result_path, "file2.txt"))
|
|
791
|
+
clean_temp_directory(result_path)
|
|
792
|
+
|
|
793
|
+
def test_task_with_dict_metadata_path(self):
|
|
794
|
+
"""Test task with dict metadata path."""
|
|
795
|
+
source_dir = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
796
|
+
os.makedirs(source_dir)
|
|
797
|
+
|
|
798
|
+
test_file = os.path.join(source_dir, "test.txt")
|
|
799
|
+
with open(test_file, "w") as f:
|
|
800
|
+
f.write("content")
|
|
801
|
+
|
|
802
|
+
self.project_config.config["project"]["package"] = {
|
|
803
|
+
"unpackaged_metadata_path": {"unpackaged/pre": "test.txt"}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
task_config = TaskConfig({"options": {"keep_temp": True}})
|
|
807
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
808
|
+
|
|
809
|
+
task()
|
|
810
|
+
|
|
811
|
+
result_path = task.result
|
|
812
|
+
assert result_path is not None
|
|
813
|
+
assert os.path.exists(os.path.join(result_path, "test.txt"))
|
|
814
|
+
clean_temp_directory(result_path)
|
|
815
|
+
|
|
816
|
+
def test_task_with_no_metadata_path(self):
|
|
817
|
+
"""Test task with no metadata path configured."""
|
|
818
|
+
self.project_config.config["project"]["package"] = {}
|
|
819
|
+
|
|
820
|
+
task_config = TaskConfig({})
|
|
821
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
822
|
+
|
|
823
|
+
with mock.patch.object(task.logger, "warning") as mock_warning:
|
|
824
|
+
task()
|
|
825
|
+
|
|
826
|
+
mock_warning.assert_called_once()
|
|
827
|
+
assert task.return_values["path"] is None
|
|
828
|
+
|
|
829
|
+
def test_task_with_base_path_option(self):
|
|
830
|
+
"""Test task with custom base_path option."""
|
|
831
|
+
custom_base = tempfile.mkdtemp()
|
|
832
|
+
try:
|
|
833
|
+
source_dir = os.path.join(custom_base, "source")
|
|
834
|
+
os.makedirs(source_dir)
|
|
835
|
+
|
|
836
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
837
|
+
f.write("content")
|
|
838
|
+
|
|
839
|
+
self.project_config.config["project"]["package"] = {
|
|
840
|
+
"unpackaged_metadata_path": "source"
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
task_config = TaskConfig(
|
|
844
|
+
{"options": {"base_path": custom_base, "keep_temp": True}}
|
|
845
|
+
)
|
|
846
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
847
|
+
|
|
848
|
+
task()
|
|
849
|
+
|
|
850
|
+
assert task.result is not None
|
|
851
|
+
assert os.path.exists(os.path.join(task.result, "file.txt"))
|
|
852
|
+
clean_temp_directory(task.result)
|
|
853
|
+
finally:
|
|
854
|
+
shutil.rmtree(custom_base)
|
|
855
|
+
|
|
856
|
+
def test_task_with_keep_temp_option(self):
|
|
857
|
+
"""Test task with keep_temp option enabled."""
|
|
858
|
+
source_dir = os.path.join(self.project_config.repo_root, "unpackaged", "pre")
|
|
859
|
+
os.makedirs(source_dir)
|
|
860
|
+
|
|
861
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
862
|
+
f.write("content")
|
|
863
|
+
|
|
864
|
+
self.project_config.config["project"]["package"] = {
|
|
865
|
+
"unpackaged_metadata_path": "unpackaged/pre"
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
task_config = TaskConfig({"options": {"keep_temp": True}})
|
|
869
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
870
|
+
|
|
871
|
+
task()
|
|
872
|
+
|
|
873
|
+
assert task.result is not None
|
|
874
|
+
# Directory should still exist since keep_temp=True
|
|
875
|
+
assert os.path.exists(task.result)
|
|
876
|
+
clean_temp_directory(task.result)
|
|
877
|
+
|
|
878
|
+
def test_task_logs_consolidation_info(self):
|
|
879
|
+
"""Test that task logs consolidation information."""
|
|
880
|
+
source_dir = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
881
|
+
os.makedirs(source_dir)
|
|
882
|
+
|
|
883
|
+
self.project_config.config["project"]["package"] = {
|
|
884
|
+
"unpackaged_metadata_path": "unpackaged/pre"
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
task_config = TaskConfig({})
|
|
888
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
889
|
+
|
|
890
|
+
with mock.patch.object(task.logger, "info") as mock_info:
|
|
891
|
+
task()
|
|
892
|
+
|
|
893
|
+
# Check that info was called with consolidation messages
|
|
894
|
+
call_args = [call[0][0] for call in mock_info.call_args_list]
|
|
895
|
+
assert any("Consolidating unpackaged metadata" in msg for msg in call_args)
|
|
896
|
+
assert any("Found" in msg for msg in call_args)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
class TestWindowsLinuxCompatibility:
|
|
900
|
+
"""Test cases for Windows/Linux path compatibility."""
|
|
901
|
+
|
|
902
|
+
def test_path_separators_handled_correctly(self):
|
|
903
|
+
"""Test that path separators are handled correctly on both platforms."""
|
|
904
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
905
|
+
# Use os.path.join which handles platform differences
|
|
906
|
+
source_dir = os.path.join(tmpdir, "source", "subdir")
|
|
907
|
+
os.makedirs(source_dir)
|
|
908
|
+
|
|
909
|
+
test_file = os.path.join(source_dir, "file.txt")
|
|
910
|
+
with open(test_file, "w") as f:
|
|
911
|
+
f.write("content")
|
|
912
|
+
|
|
913
|
+
# Test with relative pattern from source directory
|
|
914
|
+
pattern = "file.txt"
|
|
915
|
+
result, file_count = consolidate_metadata(
|
|
916
|
+
{os.path.join(tmpdir, "source", "subdir"): pattern}, tmpdir
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
assert os.path.exists(result)
|
|
920
|
+
assert file_count == 1
|
|
921
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
922
|
+
clean_temp_directory(result)
|
|
923
|
+
|
|
924
|
+
def test_absolute_paths_work_on_both_platforms(self):
|
|
925
|
+
"""Test that absolute paths work on both platforms."""
|
|
926
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
927
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
928
|
+
os.makedirs(source_dir)
|
|
929
|
+
|
|
930
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
931
|
+
f.write("content")
|
|
932
|
+
|
|
933
|
+
abs_path = os.path.abspath(source_dir)
|
|
934
|
+
result, file_count = consolidate_metadata(abs_path, tmpdir)
|
|
935
|
+
|
|
936
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
937
|
+
assert file_count == 1
|
|
938
|
+
clean_temp_directory(result)
|
|
939
|
+
|
|
940
|
+
def test_relative_paths_work_on_both_platforms(self):
|
|
941
|
+
"""Test that relative paths work on both platforms."""
|
|
942
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
943
|
+
original_cwd = os.getcwd()
|
|
944
|
+
try:
|
|
945
|
+
os.chdir(tmpdir)
|
|
946
|
+
source_dir = "source"
|
|
947
|
+
os.makedirs(source_dir)
|
|
948
|
+
|
|
949
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
950
|
+
f.write("content")
|
|
951
|
+
|
|
952
|
+
result, file_count = consolidate_metadata(
|
|
953
|
+
source_dir, tmpdir, logger=None
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
957
|
+
assert file_count == 1
|
|
958
|
+
clean_temp_directory(result)
|
|
959
|
+
finally:
|
|
960
|
+
os.chdir(original_cwd)
|
|
961
|
+
|
|
962
|
+
def test_nested_paths_work_on_both_platforms(self):
|
|
963
|
+
"""Test that deeply nested paths work on both platforms."""
|
|
964
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
965
|
+
# Create deeply nested structure
|
|
966
|
+
nested_path = os.path.join(tmpdir, "level1", "level2", "level3")
|
|
967
|
+
os.makedirs(nested_path)
|
|
968
|
+
|
|
969
|
+
test_file = os.path.join(nested_path, "file.txt")
|
|
970
|
+
with open(test_file, "w") as f:
|
|
971
|
+
f.write("content")
|
|
972
|
+
|
|
973
|
+
# Test merging nested directories
|
|
974
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
975
|
+
os.makedirs(dest_dir)
|
|
976
|
+
|
|
977
|
+
merge_directory_contents(os.path.join(tmpdir, "level1"), dest_dir)
|
|
978
|
+
|
|
979
|
+
assert os.path.exists(
|
|
980
|
+
os.path.join(dest_dir, "level2", "level3", "file.txt")
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
def test_path_with_spaces_works_on_both_platforms(self):
|
|
984
|
+
"""Test that paths with spaces work on both platforms."""
|
|
985
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
986
|
+
source_dir = os.path.join(tmpdir, "source with spaces")
|
|
987
|
+
dest_dir = os.path.join(tmpdir, "dest with spaces")
|
|
988
|
+
os.makedirs(source_dir)
|
|
989
|
+
os.makedirs(dest_dir)
|
|
990
|
+
|
|
991
|
+
test_file = os.path.join(source_dir, "file with spaces.txt")
|
|
992
|
+
with open(test_file, "w") as f:
|
|
993
|
+
f.write("content")
|
|
994
|
+
|
|
995
|
+
copy_directory_contents(source_dir, dest_dir)
|
|
996
|
+
|
|
997
|
+
assert os.path.exists(os.path.join(dest_dir, "file with spaces.txt"))
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
class TestMergeDirectoryContentsEdgeCases:
|
|
1001
|
+
"""Additional edge case tests for merge_directory_contents."""
|
|
1002
|
+
|
|
1003
|
+
def test_merge_with_file_conflict_no_overwrite(self):
|
|
1004
|
+
"""Test merge when file exists in destination but overwrite=False."""
|
|
1005
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1006
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1007
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1008
|
+
os.makedirs(src_dir)
|
|
1009
|
+
os.makedirs(dest_dir)
|
|
1010
|
+
|
|
1011
|
+
# Create file in destination
|
|
1012
|
+
dest_file = os.path.join(dest_dir, "test.txt")
|
|
1013
|
+
with open(dest_file, "w") as f:
|
|
1014
|
+
f.write("dest content")
|
|
1015
|
+
|
|
1016
|
+
# Create file in source with different content
|
|
1017
|
+
src_file = os.path.join(src_dir, "test.txt")
|
|
1018
|
+
with open(src_file, "w") as f:
|
|
1019
|
+
f.write("src content")
|
|
1020
|
+
|
|
1021
|
+
merge_directory_contents(src_dir, dest_dir, overwrite=False)
|
|
1022
|
+
|
|
1023
|
+
# Should overwrite even without overwrite flag (default behavior)
|
|
1024
|
+
with open(dest_file, "r") as f:
|
|
1025
|
+
assert f.read() == "src content"
|
|
1026
|
+
|
|
1027
|
+
def test_merge_directory_replaces_file_with_overwrite(self):
|
|
1028
|
+
"""Test merge when source has directory but dest has file with overwrite=True."""
|
|
1029
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1030
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1031
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1032
|
+
os.makedirs(src_dir)
|
|
1033
|
+
os.makedirs(dest_dir)
|
|
1034
|
+
|
|
1035
|
+
# Create file in destination
|
|
1036
|
+
dest_file = os.path.join(dest_dir, "item")
|
|
1037
|
+
with open(dest_file, "w") as f:
|
|
1038
|
+
f.write("file")
|
|
1039
|
+
|
|
1040
|
+
# Create directory with same name in source
|
|
1041
|
+
src_subdir = os.path.join(src_dir, "item")
|
|
1042
|
+
os.makedirs(src_subdir)
|
|
1043
|
+
with open(os.path.join(src_subdir, "file.txt"), "w") as f:
|
|
1044
|
+
f.write("content")
|
|
1045
|
+
|
|
1046
|
+
merge_directory_contents(src_dir, dest_dir, overwrite=True)
|
|
1047
|
+
|
|
1048
|
+
# Should be replaced with directory
|
|
1049
|
+
assert os.path.isdir(os.path.join(dest_dir, "item"))
|
|
1050
|
+
assert os.path.exists(os.path.join(dest_dir, "item", "file.txt"))
|
|
1051
|
+
|
|
1052
|
+
def test_merge_file_replaces_directory_with_overwrite(self):
|
|
1053
|
+
"""Test merge when source has file but dest has directory with overwrite=True."""
|
|
1054
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1055
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1056
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1057
|
+
os.makedirs(src_dir)
|
|
1058
|
+
os.makedirs(dest_dir)
|
|
1059
|
+
|
|
1060
|
+
# Create directory in destination
|
|
1061
|
+
dest_subdir = os.path.join(dest_dir, "item")
|
|
1062
|
+
os.makedirs(dest_subdir)
|
|
1063
|
+
|
|
1064
|
+
# Create file with same name in source
|
|
1065
|
+
src_file = os.path.join(src_dir, "item")
|
|
1066
|
+
with open(src_file, "w") as f:
|
|
1067
|
+
f.write("file content")
|
|
1068
|
+
|
|
1069
|
+
merge_directory_contents(src_dir, dest_dir, overwrite=True)
|
|
1070
|
+
|
|
1071
|
+
# Should be replaced with file
|
|
1072
|
+
assert os.path.isfile(os.path.join(dest_dir, "item"))
|
|
1073
|
+
with open(os.path.join(dest_dir, "item"), "r") as f:
|
|
1074
|
+
assert f.read() == "file content"
|
|
1075
|
+
|
|
1076
|
+
def test_merge_empty_source_directory(self):
|
|
1077
|
+
"""Test merging empty source directory."""
|
|
1078
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1079
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1080
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1081
|
+
os.makedirs(src_dir)
|
|
1082
|
+
os.makedirs(dest_dir)
|
|
1083
|
+
|
|
1084
|
+
merge_directory_contents(src_dir, dest_dir)
|
|
1085
|
+
# Should not raise error
|
|
1086
|
+
assert os.path.exists(dest_dir)
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
class TestCopyItemToDestinationEdgeCases:
|
|
1090
|
+
"""Additional edge case tests for copy_item_to_destination."""
|
|
1091
|
+
|
|
1092
|
+
def test_copy_file_when_dest_is_directory_no_overwrite(self):
|
|
1093
|
+
"""Test copying file when destination is directory without overwrite."""
|
|
1094
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1095
|
+
src_file = os.path.join(tmpdir, "source.txt")
|
|
1096
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1097
|
+
os.makedirs(dest_dir)
|
|
1098
|
+
|
|
1099
|
+
with open(src_file, "w") as f:
|
|
1100
|
+
f.write("file content")
|
|
1101
|
+
|
|
1102
|
+
# Should raise error or handle gracefully
|
|
1103
|
+
try:
|
|
1104
|
+
copy_item_to_destination(src_file, dest_dir, overwrite=False)
|
|
1105
|
+
# If no error, file should be copied into directory
|
|
1106
|
+
assert os.path.exists(os.path.join(dest_dir, "source.txt"))
|
|
1107
|
+
except (OSError, shutil.Error):
|
|
1108
|
+
# Some systems may raise error
|
|
1109
|
+
pass
|
|
1110
|
+
|
|
1111
|
+
def test_copy_directory_when_dest_is_file_no_overwrite(self):
|
|
1112
|
+
"""Test copying directory when destination is file without overwrite."""
|
|
1113
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1114
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1115
|
+
dest_file = os.path.join(tmpdir, "dest")
|
|
1116
|
+
os.makedirs(src_dir)
|
|
1117
|
+
|
|
1118
|
+
src_file = os.path.join(src_dir, "file.txt")
|
|
1119
|
+
with open(src_file, "w") as f:
|
|
1120
|
+
f.write("content")
|
|
1121
|
+
|
|
1122
|
+
with open(dest_file, "w") as f:
|
|
1123
|
+
f.write("file")
|
|
1124
|
+
|
|
1125
|
+
# Code removes the file and copies directory
|
|
1126
|
+
copy_item_to_destination(src_dir, dest_file, overwrite=False)
|
|
1127
|
+
|
|
1128
|
+
# Should be replaced with directory
|
|
1129
|
+
assert os.path.isdir(dest_file)
|
|
1130
|
+
assert os.path.exists(os.path.join(dest_file, "file.txt"))
|
|
1131
|
+
|
|
1132
|
+
def test_copy_to_nonexistent_parent_directory(self):
|
|
1133
|
+
"""Test copying to path with nonexistent parent directory."""
|
|
1134
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1135
|
+
src_file = os.path.join(tmpdir, "source.txt")
|
|
1136
|
+
dest_file = os.path.join(tmpdir, "nonexistent", "dest.txt")
|
|
1137
|
+
|
|
1138
|
+
with open(src_file, "w") as f:
|
|
1139
|
+
f.write("content")
|
|
1140
|
+
|
|
1141
|
+
# Should raise error
|
|
1142
|
+
with pytest.raises((OSError, FileNotFoundError)):
|
|
1143
|
+
copy_item_to_destination(src_file, dest_file)
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
class TestCopyDirectoryContentsEdgeCases:
|
|
1147
|
+
"""Additional edge case tests for copy_directory_contents."""
|
|
1148
|
+
|
|
1149
|
+
def test_copy_empty_directory(self):
|
|
1150
|
+
"""Test copying empty directory."""
|
|
1151
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1152
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1153
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1154
|
+
os.makedirs(src_dir)
|
|
1155
|
+
os.makedirs(dest_dir)
|
|
1156
|
+
|
|
1157
|
+
copy_directory_contents(src_dir, dest_dir)
|
|
1158
|
+
# Should not raise error
|
|
1159
|
+
assert os.path.exists(dest_dir)
|
|
1160
|
+
|
|
1161
|
+
def test_extract_src_with_overwrite(self):
|
|
1162
|
+
"""Test extract_src with overwrite flag."""
|
|
1163
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1164
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1165
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1166
|
+
os.makedirs(src_dir)
|
|
1167
|
+
os.makedirs(dest_dir)
|
|
1168
|
+
|
|
1169
|
+
# Create src subdirectory
|
|
1170
|
+
src_subdir = os.path.join(src_dir, "src")
|
|
1171
|
+
os.makedirs(src_subdir)
|
|
1172
|
+
with open(os.path.join(src_subdir, "file.txt"), "w") as f:
|
|
1173
|
+
f.write("content")
|
|
1174
|
+
|
|
1175
|
+
# Create conflicting file in dest
|
|
1176
|
+
dest_file = os.path.join(dest_dir, "src", "file.txt")
|
|
1177
|
+
os.makedirs(os.path.dirname(dest_file), exist_ok=True)
|
|
1178
|
+
with open(dest_file, "w") as f:
|
|
1179
|
+
f.write("old")
|
|
1180
|
+
|
|
1181
|
+
copy_directory_contents(src_dir, dest_dir, extract_src=True, overwrite=True)
|
|
1182
|
+
|
|
1183
|
+
# Should overwrite
|
|
1184
|
+
with open(dest_file, "r") as f:
|
|
1185
|
+
assert f.read() == "content"
|
|
1186
|
+
|
|
1187
|
+
def test_extract_src_with_nested_src_directories(self):
|
|
1188
|
+
"""Test extract_src with nested src directories."""
|
|
1189
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1190
|
+
src_dir = os.path.join(tmpdir, "src")
|
|
1191
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1192
|
+
os.makedirs(src_dir)
|
|
1193
|
+
os.makedirs(dest_dir)
|
|
1194
|
+
|
|
1195
|
+
# Create nested src structure
|
|
1196
|
+
nested_src = os.path.join(src_dir, "src", "nested", "src")
|
|
1197
|
+
os.makedirs(nested_src)
|
|
1198
|
+
with open(os.path.join(nested_src, "file.txt"), "w") as f:
|
|
1199
|
+
f.write("content")
|
|
1200
|
+
|
|
1201
|
+
copy_directory_contents(src_dir, dest_dir, extract_src=True)
|
|
1202
|
+
|
|
1203
|
+
# Should extract only the top-level src
|
|
1204
|
+
assert os.path.exists(
|
|
1205
|
+
os.path.join(dest_dir, "src", "nested", "src", "file.txt")
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
class TestResolveFilePatternEdgeCases:
|
|
1210
|
+
"""Additional edge case tests for resolve_file_pattern."""
|
|
1211
|
+
|
|
1212
|
+
def test_resolve_pattern_with_dot_files(self):
|
|
1213
|
+
"""Test resolving pattern matching dot files."""
|
|
1214
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1215
|
+
dot_file = os.path.join(tmpdir, ".hidden")
|
|
1216
|
+
with open(dot_file, "w") as f:
|
|
1217
|
+
f.write("content")
|
|
1218
|
+
|
|
1219
|
+
result = resolve_file_pattern(".hidden", tmpdir)
|
|
1220
|
+
assert len(result) == 1
|
|
1221
|
+
assert result[0] == dot_file
|
|
1222
|
+
|
|
1223
|
+
def test_resolve_pattern_with_multiple_extensions(self):
|
|
1224
|
+
"""Test resolving pattern with multiple file extensions."""
|
|
1225
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1226
|
+
for ext in [".txt", ".xml", ".json"]:
|
|
1227
|
+
with open(os.path.join(tmpdir, f"file{ext}"), "w") as f:
|
|
1228
|
+
f.write("content")
|
|
1229
|
+
|
|
1230
|
+
result = resolve_file_pattern("*.*", tmpdir)
|
|
1231
|
+
assert len(result) >= 3
|
|
1232
|
+
|
|
1233
|
+
def test_resolve_pattern_with_question_mark(self):
|
|
1234
|
+
"""Test resolving pattern with question mark wildcard."""
|
|
1235
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1236
|
+
with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
|
|
1237
|
+
f.write("content1")
|
|
1238
|
+
with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
|
|
1239
|
+
f.write("content2")
|
|
1240
|
+
with open(os.path.join(tmpdir, "file10.txt"), "w") as f:
|
|
1241
|
+
f.write("content10")
|
|
1242
|
+
|
|
1243
|
+
result = resolve_file_pattern("file?.txt", tmpdir)
|
|
1244
|
+
assert len(result) == 2 # file1.txt and file2.txt, not file10.txt
|
|
1245
|
+
|
|
1246
|
+
def test_resolve_pattern_with_character_class(self):
|
|
1247
|
+
"""Test resolving pattern with character class."""
|
|
1248
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1249
|
+
with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
|
|
1250
|
+
f.write("content1")
|
|
1251
|
+
with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
|
|
1252
|
+
f.write("content2")
|
|
1253
|
+
with open(os.path.join(tmpdir, "filea.txt"), "w") as f:
|
|
1254
|
+
f.write("contenta")
|
|
1255
|
+
|
|
1256
|
+
result = resolve_file_pattern("file[12].txt", tmpdir)
|
|
1257
|
+
assert len(result) == 2
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
class TestCopyMatchedFilesEdgeCases:
|
|
1261
|
+
"""Additional edge case tests for copy_matched_files."""
|
|
1262
|
+
|
|
1263
|
+
def test_copy_empty_file_list(self):
|
|
1264
|
+
"""Test copying empty list of files."""
|
|
1265
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1266
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1267
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1268
|
+
os.makedirs(source_dir)
|
|
1269
|
+
os.makedirs(dest_dir)
|
|
1270
|
+
|
|
1271
|
+
copy_matched_files([], source_dir, dest_dir)
|
|
1272
|
+
# Should not raise error
|
|
1273
|
+
assert os.path.exists(dest_dir)
|
|
1274
|
+
|
|
1275
|
+
def test_copy_file_at_root_level(self):
|
|
1276
|
+
"""Test copying file at root level of source directory."""
|
|
1277
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1278
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1279
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1280
|
+
os.makedirs(source_dir)
|
|
1281
|
+
os.makedirs(dest_dir)
|
|
1282
|
+
|
|
1283
|
+
source_file = os.path.join(source_dir, "root.txt")
|
|
1284
|
+
with open(source_file, "w") as f:
|
|
1285
|
+
f.write("content")
|
|
1286
|
+
|
|
1287
|
+
copy_matched_files([source_file], source_dir, dest_dir)
|
|
1288
|
+
|
|
1289
|
+
dest_file = os.path.join(dest_dir, "root.txt")
|
|
1290
|
+
assert os.path.exists(dest_file)
|
|
1291
|
+
|
|
1292
|
+
def test_copy_files_with_same_name_different_paths(self):
|
|
1293
|
+
"""Test copying files with same name from different paths."""
|
|
1294
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1295
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1296
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1297
|
+
os.makedirs(source_dir)
|
|
1298
|
+
os.makedirs(dest_dir)
|
|
1299
|
+
|
|
1300
|
+
file1 = os.path.join(source_dir, "dir1", "file.txt")
|
|
1301
|
+
file2 = os.path.join(source_dir, "dir2", "file.txt")
|
|
1302
|
+
os.makedirs(os.path.dirname(file1))
|
|
1303
|
+
os.makedirs(os.path.dirname(file2))
|
|
1304
|
+
|
|
1305
|
+
with open(file1, "w") as f:
|
|
1306
|
+
f.write("content1")
|
|
1307
|
+
with open(file2, "w") as f:
|
|
1308
|
+
f.write("content2")
|
|
1309
|
+
|
|
1310
|
+
copy_matched_files([file1, file2], source_dir, dest_dir)
|
|
1311
|
+
|
|
1312
|
+
assert os.path.exists(os.path.join(dest_dir, "dir1", "file.txt"))
|
|
1313
|
+
assert os.path.exists(os.path.join(dest_dir, "dir2", "file.txt"))
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
class TestConsolidateMetadataEdgeCases:
|
|
1317
|
+
"""Additional edge case tests for consolidate_metadata."""
|
|
1318
|
+
|
|
1319
|
+
def test_consolidate_dict_with_empty_list_patterns(self):
|
|
1320
|
+
"""Test consolidating with empty list of patterns."""
|
|
1321
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1322
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1323
|
+
os.makedirs(source_dir)
|
|
1324
|
+
|
|
1325
|
+
result, file_count = consolidate_metadata({"source": []}, tmpdir)
|
|
1326
|
+
# Should create temp dir but no files
|
|
1327
|
+
assert os.path.exists(result)
|
|
1328
|
+
assert file_count == 0
|
|
1329
|
+
clean_temp_directory(result)
|
|
1330
|
+
|
|
1331
|
+
def test_consolidate_dict_with_multiple_wildcards(self):
|
|
1332
|
+
"""Test consolidating with multiple directories using wildcards."""
|
|
1333
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1334
|
+
dir1 = os.path.join(tmpdir, "dir1")
|
|
1335
|
+
dir2 = os.path.join(tmpdir, "dir2")
|
|
1336
|
+
os.makedirs(os.path.join(dir1, "src"))
|
|
1337
|
+
os.makedirs(os.path.join(dir2, "src"))
|
|
1338
|
+
|
|
1339
|
+
with open(os.path.join(dir1, "src", "file1.txt"), "w") as f:
|
|
1340
|
+
f.write("content1")
|
|
1341
|
+
with open(os.path.join(dir2, "src", "file2.txt"), "w") as f:
|
|
1342
|
+
f.write("content2")
|
|
1343
|
+
|
|
1344
|
+
result, file_count = consolidate_metadata(
|
|
1345
|
+
{"dir1": "*", "dir2": "*"}, tmpdir
|
|
1346
|
+
)
|
|
1347
|
+
|
|
1348
|
+
assert os.path.exists(os.path.join(result, "src", "file1.txt"))
|
|
1349
|
+
assert os.path.exists(os.path.join(result, "src", "file2.txt"))
|
|
1350
|
+
assert file_count == 2
|
|
1351
|
+
clean_temp_directory(result)
|
|
1352
|
+
|
|
1353
|
+
def test_consolidate_list_with_empty_strings(self):
|
|
1354
|
+
"""Test consolidating list with empty strings."""
|
|
1355
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1356
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1357
|
+
os.makedirs(source_dir)
|
|
1358
|
+
|
|
1359
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
1360
|
+
f.write("content")
|
|
1361
|
+
|
|
1362
|
+
# Empty string resolves to base_path, which is valid
|
|
1363
|
+
# So it will copy base_path contents, then source contents
|
|
1364
|
+
result, file_count = consolidate_metadata(["", "source"], tmpdir)
|
|
1365
|
+
|
|
1366
|
+
# Should succeed and consolidate both
|
|
1367
|
+
assert os.path.exists(result)
|
|
1368
|
+
# Source file should be present
|
|
1369
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
1370
|
+
assert file_count == 2
|
|
1371
|
+
clean_temp_directory(result)
|
|
1372
|
+
|
|
1373
|
+
def test_consolidate_verifies_cleanup_on_error(self):
|
|
1374
|
+
"""Test that temp directory is cleaned up on validation error."""
|
|
1375
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1376
|
+
temp_dirs_before = len(
|
|
1377
|
+
[
|
|
1378
|
+
d
|
|
1379
|
+
for d in os.listdir(tempfile.gettempdir())
|
|
1380
|
+
if d.startswith("metadata_consolidate_")
|
|
1381
|
+
]
|
|
1382
|
+
)
|
|
1383
|
+
|
|
1384
|
+
try:
|
|
1385
|
+
consolidate_metadata("nonexistent", tmpdir)
|
|
1386
|
+
except ValueError:
|
|
1387
|
+
pass
|
|
1388
|
+
|
|
1389
|
+
# Verify no new temp directories were left behind
|
|
1390
|
+
temp_dirs_after = len(
|
|
1391
|
+
[
|
|
1392
|
+
d
|
|
1393
|
+
for d in os.listdir(tempfile.gettempdir())
|
|
1394
|
+
if d.startswith("metadata_consolidate_")
|
|
1395
|
+
]
|
|
1396
|
+
)
|
|
1397
|
+
# Should be same or less (cleanup happened)
|
|
1398
|
+
assert temp_dirs_after <= temp_dirs_before + 1
|
|
1399
|
+
|
|
1400
|
+
def test_consolidate_dict_with_nonexistent_pattern(self):
|
|
1401
|
+
"""Test consolidating with pattern that doesn't match files."""
|
|
1402
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1403
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1404
|
+
os.makedirs(source_dir)
|
|
1405
|
+
|
|
1406
|
+
result, file_count = consolidate_metadata(
|
|
1407
|
+
{"source": "nonexistent.txt"}, tmpdir
|
|
1408
|
+
)
|
|
1409
|
+
assert file_count == 0
|
|
1410
|
+
assert result is not None
|
|
1411
|
+
|
|
1412
|
+
def test_consolidate_with_base_path_none(self):
|
|
1413
|
+
"""Test consolidate with base_path explicitly None."""
|
|
1414
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1415
|
+
original_cwd = os.getcwd()
|
|
1416
|
+
try:
|
|
1417
|
+
os.chdir(tmpdir)
|
|
1418
|
+
source_dir = "source"
|
|
1419
|
+
os.makedirs(source_dir)
|
|
1420
|
+
|
|
1421
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
1422
|
+
f.write("content")
|
|
1423
|
+
|
|
1424
|
+
result, file_count = consolidate_metadata(source_dir, base_path=None)
|
|
1425
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
1426
|
+
assert file_count == 1
|
|
1427
|
+
clean_temp_directory(result)
|
|
1428
|
+
finally:
|
|
1429
|
+
os.chdir(original_cwd)
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
class TestConsolidateUnpackagedMetadataTaskEdgeCases:
|
|
1433
|
+
"""Additional edge case tests for ConsolidateUnpackagedMetadata task."""
|
|
1434
|
+
|
|
1435
|
+
def setup_method(self):
|
|
1436
|
+
"""Set up test fixtures."""
|
|
1437
|
+
self.temp_repo_root = tempfile.mkdtemp()
|
|
1438
|
+
self.universal_config = UniversalConfig()
|
|
1439
|
+
self.project_config = BaseProjectConfig(
|
|
1440
|
+
self.universal_config,
|
|
1441
|
+
config={"noyaml": True, "project": {"package": {}}},
|
|
1442
|
+
repo_info={"root": self.temp_repo_root},
|
|
1443
|
+
)
|
|
1444
|
+
|
|
1445
|
+
def teardown_method(self):
|
|
1446
|
+
"""Clean up test fixtures."""
|
|
1447
|
+
if os.path.exists(self.temp_repo_root):
|
|
1448
|
+
shutil.rmtree(self.temp_repo_root)
|
|
1449
|
+
|
|
1450
|
+
def test_task_return_value_structure(self):
|
|
1451
|
+
"""Test that task returns correct value structure."""
|
|
1452
|
+
source_dir = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
1453
|
+
os.makedirs(source_dir)
|
|
1454
|
+
|
|
1455
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
1456
|
+
f.write("content")
|
|
1457
|
+
|
|
1458
|
+
self.project_config.config["project"]["package"] = {
|
|
1459
|
+
"unpackaged_metadata_path": "unpackaged/pre"
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
task_config = TaskConfig({"options": {"keep_temp": True}})
|
|
1463
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
1464
|
+
|
|
1465
|
+
task()
|
|
1466
|
+
|
|
1467
|
+
# Task stores result in task.result
|
|
1468
|
+
assert isinstance(task.result, str)
|
|
1469
|
+
assert os.path.exists(task.result)
|
|
1470
|
+
# With keep_temp=True, directory should still exist
|
|
1471
|
+
clean_temp_directory(task.result)
|
|
1472
|
+
|
|
1473
|
+
def test_task_with_error_during_consolidation(self):
|
|
1474
|
+
"""Test task behavior when consolidation raises error."""
|
|
1475
|
+
self.project_config.config["project"]["package"] = {
|
|
1476
|
+
"unpackaged_metadata_path": "nonexistent/path"
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
task_config = TaskConfig({})
|
|
1480
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
1481
|
+
|
|
1482
|
+
with pytest.raises(ValueError):
|
|
1483
|
+
task()
|
|
1484
|
+
|
|
1485
|
+
def test_task_cleans_up_temp_when_keep_temp_false(self):
|
|
1486
|
+
"""Test that task cleans up temp directory when keep_temp=False."""
|
|
1487
|
+
source_dir = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
1488
|
+
os.makedirs(source_dir)
|
|
1489
|
+
|
|
1490
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
1491
|
+
f.write("content")
|
|
1492
|
+
|
|
1493
|
+
self.project_config.config["project"]["package"] = {
|
|
1494
|
+
"unpackaged_metadata_path": "unpackaged/pre"
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
task_config = TaskConfig({"options": {"keep_temp": False}})
|
|
1498
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
1499
|
+
|
|
1500
|
+
task()
|
|
1501
|
+
|
|
1502
|
+
# Directory should be cleaned up (but result still contains the path)
|
|
1503
|
+
result_path = task.result
|
|
1504
|
+
assert result_path is not None
|
|
1505
|
+
assert not os.path.exists(result_path)
|
|
1506
|
+
|
|
1507
|
+
def test_task_with_dict_metadata_path_wildcard(self):
|
|
1508
|
+
"""Test task with dict metadata path using wildcard."""
|
|
1509
|
+
source_dir = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
1510
|
+
src_subdir = os.path.join(source_dir, "src")
|
|
1511
|
+
os.makedirs(src_subdir)
|
|
1512
|
+
|
|
1513
|
+
with open(os.path.join(src_subdir, "file.txt"), "w") as f:
|
|
1514
|
+
f.write("content")
|
|
1515
|
+
|
|
1516
|
+
self.project_config.config["project"]["package"] = {
|
|
1517
|
+
"unpackaged_metadata_path": {"unpackaged/pre": "*.*"}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
task_config = TaskConfig({"options": {"keep_temp": True}})
|
|
1521
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
1522
|
+
|
|
1523
|
+
task()
|
|
1524
|
+
|
|
1525
|
+
result_path = task.result
|
|
1526
|
+
assert result_path is not None
|
|
1527
|
+
assert os.path.exists(os.path.join(result_path, "src", "file.txt"))
|
|
1528
|
+
clean_temp_directory(result_path)
|
|
1529
|
+
|
|
1530
|
+
def test_task_with_list_metadata_path_multiple_dirs(self):
|
|
1531
|
+
"""Test task with list metadata path containing multiple directories."""
|
|
1532
|
+
dir1 = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
1533
|
+
dir2 = os.path.join(self.temp_repo_root, "unpackaged", "post")
|
|
1534
|
+
dir3 = os.path.join(self.temp_repo_root, "unpackaged", "default")
|
|
1535
|
+
os.makedirs(dir1)
|
|
1536
|
+
os.makedirs(dir2)
|
|
1537
|
+
os.makedirs(dir3)
|
|
1538
|
+
|
|
1539
|
+
with open(os.path.join(dir1, "file1.txt"), "w") as f:
|
|
1540
|
+
f.write("content1")
|
|
1541
|
+
with open(os.path.join(dir2, "file2.txt"), "w") as f:
|
|
1542
|
+
f.write("content2")
|
|
1543
|
+
with open(os.path.join(dir3, "file3.txt"), "w") as f:
|
|
1544
|
+
f.write("content3")
|
|
1545
|
+
|
|
1546
|
+
self.project_config.config["project"]["package"] = {
|
|
1547
|
+
"unpackaged_metadata_path": [
|
|
1548
|
+
"unpackaged/pre",
|
|
1549
|
+
"unpackaged/post",
|
|
1550
|
+
"unpackaged/default",
|
|
1551
|
+
]
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
task_config = TaskConfig({"options": {"keep_temp": True}})
|
|
1555
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
1556
|
+
|
|
1557
|
+
task()
|
|
1558
|
+
|
|
1559
|
+
result_path = task.result
|
|
1560
|
+
assert result_path is not None
|
|
1561
|
+
assert os.path.exists(os.path.join(result_path, "file1.txt"))
|
|
1562
|
+
assert os.path.exists(os.path.join(result_path, "file2.txt"))
|
|
1563
|
+
assert os.path.exists(os.path.join(result_path, "file3.txt"))
|
|
1564
|
+
clean_temp_directory(result_path)
|
|
1565
|
+
|
|
1566
|
+
def test_task_logs_tree_structure(self):
|
|
1567
|
+
"""Test that task logs directory tree structure."""
|
|
1568
|
+
source_dir = os.path.join(self.temp_repo_root, "unpackaged", "pre")
|
|
1569
|
+
os.makedirs(source_dir)
|
|
1570
|
+
|
|
1571
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
1572
|
+
f.write("content")
|
|
1573
|
+
|
|
1574
|
+
self.project_config.config["project"]["package"] = {
|
|
1575
|
+
"unpackaged_metadata_path": "unpackaged/pre"
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
task_config = TaskConfig({})
|
|
1579
|
+
task = ConsolidateUnpackagedMetadata(self.project_config, task_config, None)
|
|
1580
|
+
|
|
1581
|
+
with mock.patch.object(task.logger, "info") as mock_info:
|
|
1582
|
+
task()
|
|
1583
|
+
|
|
1584
|
+
# Check that tree printing was called
|
|
1585
|
+
call_args = [call[0][0] for call in mock_info.call_args_list]
|
|
1586
|
+
# Should have tree structure calls (containing tree characters)
|
|
1587
|
+
tree_calls = [msg for msg in call_args if "├──" in msg or "└──" in msg]
|
|
1588
|
+
assert len(tree_calls) > 0
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
class TestWindowsLinuxCompatibilityExtended:
|
|
1592
|
+
"""Extended Windows/Linux compatibility tests."""
|
|
1593
|
+
|
|
1594
|
+
def test_paths_with_special_characters(self):
|
|
1595
|
+
"""Test paths with special characters that work on both platforms."""
|
|
1596
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1597
|
+
# Use characters that are valid on both platforms
|
|
1598
|
+
source_dir = os.path.join(tmpdir, "source-dir_123")
|
|
1599
|
+
dest_dir = os.path.join(tmpdir, "dest-dir_456")
|
|
1600
|
+
os.makedirs(source_dir)
|
|
1601
|
+
os.makedirs(dest_dir)
|
|
1602
|
+
|
|
1603
|
+
test_file = os.path.join(source_dir, "file-name_123.txt")
|
|
1604
|
+
with open(test_file, "w") as f:
|
|
1605
|
+
f.write("content")
|
|
1606
|
+
|
|
1607
|
+
copy_directory_contents(source_dir, dest_dir)
|
|
1608
|
+
|
|
1609
|
+
assert os.path.exists(os.path.join(dest_dir, "file-name_123.txt"))
|
|
1610
|
+
|
|
1611
|
+
def test_paths_with_unicode_characters(self):
|
|
1612
|
+
"""Test paths with unicode characters."""
|
|
1613
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1614
|
+
source_dir = os.path.join(tmpdir, "source_测试")
|
|
1615
|
+
dest_dir = os.path.join(tmpdir, "dest_テスト")
|
|
1616
|
+
os.makedirs(source_dir)
|
|
1617
|
+
os.makedirs(dest_dir)
|
|
1618
|
+
|
|
1619
|
+
test_file = os.path.join(source_dir, "file_文件.txt")
|
|
1620
|
+
with open(test_file, "w", encoding="utf-8") as f:
|
|
1621
|
+
f.write("content")
|
|
1622
|
+
|
|
1623
|
+
copy_directory_contents(source_dir, dest_dir)
|
|
1624
|
+
|
|
1625
|
+
assert os.path.exists(os.path.join(dest_dir, "file_文件.txt"))
|
|
1626
|
+
|
|
1627
|
+
def test_relative_paths_with_dot_dot(self):
|
|
1628
|
+
"""Test relative paths with .. components."""
|
|
1629
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1630
|
+
nested_dir = os.path.join(tmpdir, "level1", "level2")
|
|
1631
|
+
os.makedirs(nested_dir)
|
|
1632
|
+
|
|
1633
|
+
test_file = os.path.join(nested_dir, "file.txt")
|
|
1634
|
+
with open(test_file, "w") as f:
|
|
1635
|
+
f.write("content")
|
|
1636
|
+
|
|
1637
|
+
# Use relative path with .. - resolve to absolute first
|
|
1638
|
+
original_cwd = os.getcwd()
|
|
1639
|
+
try:
|
|
1640
|
+
os.chdir(tmpdir)
|
|
1641
|
+
# Use absolute path or resolve the relative path properly
|
|
1642
|
+
abs_nested = os.path.abspath(nested_dir)
|
|
1643
|
+
result, file_count = consolidate_metadata(abs_nested, tmpdir)
|
|
1644
|
+
assert os.path.exists(os.path.join(result, "file.txt"))
|
|
1645
|
+
assert file_count == 1
|
|
1646
|
+
clean_temp_directory(result)
|
|
1647
|
+
finally:
|
|
1648
|
+
os.chdir(original_cwd)
|
|
1649
|
+
|
|
1650
|
+
def test_symlink_handling(self):
|
|
1651
|
+
"""Test handling of symlinks (if supported on platform)."""
|
|
1652
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1653
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1654
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1655
|
+
os.makedirs(source_dir)
|
|
1656
|
+
os.makedirs(dest_dir)
|
|
1657
|
+
|
|
1658
|
+
# Create a regular file
|
|
1659
|
+
real_file = os.path.join(tmpdir, "real.txt")
|
|
1660
|
+
with open(real_file, "w") as f:
|
|
1661
|
+
f.write("content")
|
|
1662
|
+
|
|
1663
|
+
# Try to create symlink (may not work on Windows without admin)
|
|
1664
|
+
symlink_file = os.path.join(source_dir, "link.txt")
|
|
1665
|
+
try:
|
|
1666
|
+
os.symlink(real_file, symlink_file)
|
|
1667
|
+
|
|
1668
|
+
copy_directory_contents(source_dir, dest_dir)
|
|
1669
|
+
|
|
1670
|
+
# Symlink should be copied (as file or link depending on platform)
|
|
1671
|
+
assert os.path.exists(os.path.join(dest_dir, "link.txt"))
|
|
1672
|
+
except (OSError, NotImplementedError):
|
|
1673
|
+
# Symlinks not supported on this platform
|
|
1674
|
+
pass
|
|
1675
|
+
|
|
1676
|
+
def test_long_path_names(self):
|
|
1677
|
+
"""Test handling of long path names."""
|
|
1678
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1679
|
+
# Create nested structure with long names
|
|
1680
|
+
long_path = tmpdir
|
|
1681
|
+
for i in range(5):
|
|
1682
|
+
long_path = os.path.join(long_path, f"very_long_directory_name_{i}" * 5)
|
|
1683
|
+
os.makedirs(long_path)
|
|
1684
|
+
|
|
1685
|
+
test_file = os.path.join(long_path, "file.txt")
|
|
1686
|
+
with open(test_file, "w") as f:
|
|
1687
|
+
f.write("content")
|
|
1688
|
+
|
|
1689
|
+
# Test that we can still copy
|
|
1690
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1691
|
+
os.makedirs(dest_dir)
|
|
1692
|
+
|
|
1693
|
+
copy_directory_contents(
|
|
1694
|
+
os.path.join(tmpdir, "very_long_directory_name_0" * 5), dest_dir
|
|
1695
|
+
)
|
|
1696
|
+
|
|
1697
|
+
# Should handle long paths
|
|
1698
|
+
assert os.path.exists(dest_dir)
|
|
1699
|
+
|
|
1700
|
+
def test_case_sensitivity_handling(self):
|
|
1701
|
+
"""Test handling of case-sensitive vs case-insensitive file systems."""
|
|
1702
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
1703
|
+
source_dir = os.path.join(tmpdir, "source")
|
|
1704
|
+
dest_dir = os.path.join(tmpdir, "dest")
|
|
1705
|
+
os.makedirs(source_dir)
|
|
1706
|
+
os.makedirs(dest_dir)
|
|
1707
|
+
|
|
1708
|
+
# Create files with different cases
|
|
1709
|
+
with open(os.path.join(source_dir, "File.txt"), "w") as f:
|
|
1710
|
+
f.write("content1")
|
|
1711
|
+
with open(os.path.join(source_dir, "file.txt"), "w") as f:
|
|
1712
|
+
f.write("content2")
|
|
1713
|
+
|
|
1714
|
+
copy_directory_contents(source_dir, dest_dir)
|
|
1715
|
+
|
|
1716
|
+
# Both should exist (or overwrite depending on platform)
|
|
1717
|
+
assert os.path.exists(os.path.join(dest_dir, "File.txt")) or os.path.exists(
|
|
1718
|
+
os.path.join(dest_dir, "file.txt")
|
|
1719
|
+
)
|