mcdev 9.0.0 → 9.0.1

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 (31) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
  2. package/.github/workflows/code-test.yml +1 -0
  3. package/.husky/commit-msg +1 -1
  4. package/.husky/post-checkout +4 -6
  5. package/@types/lib/Deployer.d.ts.map +1 -1
  6. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  7. package/@types/lib/metadataTypes/Folder.d.ts.map +1 -1
  8. package/@types/lib/util/util.d.ts +1 -0
  9. package/@types/lib/util/util.d.ts.map +1 -1
  10. package/lib/Deployer.js +2 -1
  11. package/lib/metadataTypes/Asset.js +8 -1
  12. package/lib/metadataTypes/Folder.js +20 -7
  13. package/lib/metadataTypes/definitions/Folder.definition.js +1 -1
  14. package/lib/util/util.js +2 -0
  15. package/package.json +10 -10
  16. package/test/mockRoot/.mcdevrc.json +1 -1
  17. package/test/mockRoot/deploy/testInstance/testBU/asset/block/test_slash.asset-block-meta.html +12 -0
  18. package/test/mockRoot/deploy/testInstance/testBU/asset/block/test_slash.asset-block-meta.json +29 -0
  19. package/test/resources/9999999/asset/v1/content/assets/16992/get-response.json +60 -0
  20. package/test/resources/9999999/asset/v1/content/assets/post-response-key=test_slash.json +60 -0
  21. package/test/resources/9999999/asset/v1/content/assets/query/+post-response-assetType.idIN3,195,196,197,198,199,200,201,202,203,210,211,212,213-slashfolder.json +198 -0
  22. package/test/resources/9999999/asset/v1/content/categories/post-response.json +9 -0
  23. package/test/resources/9999999/asset-slashfolder-deploy/block/test_slash.asset-block-meta.html +12 -0
  24. package/test/resources/9999999/asset-slashfolder-deploy/block/test_slash.asset-block-meta.json +29 -0
  25. package/test/resources/9999999/dataFolder/+retrieve-ContentTypeINasset,asset-shared,cloudpages-slashfolder-response.xml +136 -0
  26. package/test/resources/9999999/dataFolder/create-ContentType=asset,Name=testFolder_samePath,ParentFolderID=89397-response.xml +33 -0
  27. package/test/resources/9999999/dataFolder/create-response.xml +33 -0
  28. package/test/resources/9999999/dataFolder/retrieve-samePathOtherBU-response.xml +564 -0
  29. package/test/resources/9999999/folder-deploy-samepath/Content Builder/testFolder_samePath.folder-meta.json +9 -0
  30. package/test/resources/9999999/folder-deploy-slash/Content Builder/Headers%2FFolders.folder-meta.json +9 -0
  31. package/test/type.folder.test.js +157 -0
@@ -6,6 +6,7 @@ import chaiFiles from 'chai-files';
6
6
  import cache from '../lib/util/cache.js';
7
7
  import * as testUtils from './utils.js';
8
8
  import handler from '../lib/index.js';
9
+ import { Util } from '../lib/util/util.js';
9
10
  chai.use(chaiFiles);
10
11
 
11
12
  describe('type: folder', () => {
@@ -17,6 +18,38 @@ describe('type: folder', () => {
17
18
  testUtils.mockReset();
18
19
  });
19
20
 
21
+ describe('Retrieve ================', () => {
22
+ it('Should retrieve an asset whose folder name contains a slash and escape the slash in r__folder_Path', async () => {
23
+ // GIVEN the SFMC folder "bla/blub" (child of Content Builder) and an asset in that folder
24
+ await testUtils.copyFile(
25
+ 'dataFolder/+retrieve-ContentTypeINasset,asset-shared,cloudpages-slashfolder-response.xml',
26
+ 'dataFolder/retrieve-ContentTypeINasset,asset-shared,cloudpages-response.xml'
27
+ );
28
+ await testUtils.copyFile(
29
+ 'asset/v1/content/assets/query/+post-response-assetType.idIN3,195,196,197,198,199,200,201,202,203,210,211,212,213-slashfolder.json',
30
+ 'asset/v1/content/assets/query/post-response-assetType.idIN3,195,196,197,198,199,200,201,202,203,210,211,212,213.json'
31
+ );
32
+
33
+ const retrieve = await handler.retrieve('testInstance/testBU', ['asset-block']);
34
+ // THEN
35
+ assert.equal(process.exitCode, 0, 'retrieve should not have thrown an error');
36
+ assert.equal(
37
+ retrieve['testInstance/testBU'].asset
38
+ ? Object.keys(retrieve['testInstance/testBU'].asset).length
39
+ : 0,
40
+ 5,
41
+ 'five assets expected in retrieve response (4 existing + 1 new test_slash)'
42
+ );
43
+ // Verify the retrieved asset has the escape char (∕) in r__folder_Path, not a real slash
44
+ assert.equal(
45
+ retrieve['testInstance/testBU'].asset?.test_slash?.r__folder_Path,
46
+ 'Content Builder/bla' + Util.folderNameSlashEscapeChar + 'blub',
47
+ 'r__folder_Path should use ∕ (U+2215) to escape the slash in the folder name'
48
+ );
49
+ return;
50
+ });
51
+ });
52
+
20
53
  describe('Deploy ================', () => {
21
54
  it('Should create automation & dataExtension folders', async () => {
22
55
  // prepare
@@ -103,5 +136,129 @@ describe('type: folder', () => {
103
136
  );
104
137
  return;
105
138
  });
139
+
140
+ it('Should create folder when same path exists in another Business Unit', async () => {
141
+ // prepare
142
+ // Use folder deploy data with an asset folder path
143
+ testUtils.copyToDeploy('folder-deploy-samepath', 'folder');
144
+ // Use a modified retrieve response that includes 'Content Builder/testFolder_samePath'
145
+ // from a different BU (Client.ID=1111111). ContentType 'asset' is in folderTypesFromParent
146
+ // so it will be cached even though it belongs to another BU - simulating the bug scenario
147
+ await testUtils.copyFile(
148
+ 'dataFolder/retrieve-samePathOtherBU-response.xml',
149
+ 'dataFolder/retrieve-response.xml'
150
+ );
151
+
152
+ const deployed = await handler.deploy('testInstance/testBU', ['folder']);
153
+ // THEN
154
+ assert.equal(process.exitCode, 0, 'deploy should not have thrown an error');
155
+
156
+ // check what was deployed - 'Content Builder/testFolder_samePath' should be created
157
+ // even though it exists in another BU (1111111)
158
+ const deployedFolderPaths = deployed['testInstance/testBU']?.folder
159
+ ? Object.values(deployed['testInstance/testBU']?.folder).map((f) => f.Path)
160
+ : [];
161
+ assert.include(
162
+ deployedFolderPaths,
163
+ 'Content Builder/testFolder_samePath',
164
+ "'Content Builder/testFolder_samePath' should have been created in current BU, not skipped because it exists in another BU"
165
+ );
166
+
167
+ const createRestCallouts = testUtils.getRestCallout(
168
+ 'post',
169
+ '/asset/v1/content/categories',
170
+ true
171
+ );
172
+ // 'Content Builder/testFolder_samePath' must be created in current BU via REST
173
+ // (not skipped due to same path in other BU)
174
+ assert.ok(
175
+ createRestCallouts?.some(
176
+ (c) => c.name === 'testFolder_samePath' && c.parentId === 89397
177
+ ),
178
+ "'Content Builder/testFolder_samePath' folder creation callout not found - folder was incorrectly skipped"
179
+ );
180
+ return;
181
+ });
182
+
183
+ it('Should create a folder whose name contains a slash character (direct folder deploy)', async () => {
184
+ // GIVEN a folder deploy file named Headers%2FFolders.folder-meta.json with Name "Headers/Folders"
185
+ // (the % encoding represents the actual '/' in the SFMC folder name)
186
+ testUtils.copyToDeploy('folder-deploy-slash', 'folder');
187
+
188
+ const deployed = await handler.deploy('testInstance/testBU', ['folder']);
189
+ // THEN
190
+ assert.equal(process.exitCode, 0, 'deploy should not have thrown an error');
191
+
192
+ // Verify the deployed path uses the escape char (∕) not the real slash
193
+ const deployedFolderPaths = deployed['testInstance/testBU']?.folder
194
+ ? Object.values(deployed['testInstance/testBU']?.folder).map((f) => f.Path)
195
+ : [];
196
+ assert.include(
197
+ deployedFolderPaths,
198
+ 'Content Builder/Headers' + Util.folderNameSlashEscapeChar + 'Folders',
199
+ 'deployed folder path should use escape char for the slash in folder name'
200
+ );
201
+
202
+ // Verify that the REST Create callout uses the REAL slash character in the name field
203
+ // (not the escape character), so that SFMC creates a folder named "Headers/Folders"
204
+ const createRestCallouts = testUtils.getRestCallout(
205
+ 'post',
206
+ '/asset/v1/content/categories',
207
+ true
208
+ );
209
+ assert.ok(
210
+ createRestCallouts?.some(
211
+ (c) => c.name === 'Headers/Folders' && c.parentId === 89397
212
+ ),
213
+ "REST create for 'Headers/Folders' should use the real slash in name, not the escape char"
214
+ );
215
+ // Verify the escape char was NOT sent as the folder name
216
+ assert.ok(
217
+ !createRestCallouts?.some(
218
+ (c) => c.name === 'Headers' + Util.folderNameSlashEscapeChar + 'Folders'
219
+ ),
220
+ 'REST create should NOT use the escape char in the name field'
221
+ );
222
+ return;
223
+ });
224
+
225
+ it('Should create a folder whose name contains a slash when triggered via r__folder_Path from an asset', async () => {
226
+ // GIVEN an asset deploy with r__folder_Path pointing to a subfolder whose name contains a slash
227
+ // The escape char (∕) in r__folder_Path separates the slash-in-name from path separators
228
+ testUtils.copyToDeploy('asset-slashfolder-deploy', 'asset');
229
+
230
+ const deployed = await handler.deploy('testInstance/testBU', ['asset']);
231
+ // THEN
232
+ assert.equal(process.exitCode, 0, 'deploy should not have thrown an error');
233
+
234
+ // Verify the asset was deployed
235
+ assert.equal(
236
+ deployed['testInstance/testBU']?.asset
237
+ ? Object.keys(deployed['testInstance/testBU']?.asset).length
238
+ : 0,
239
+ 1,
240
+ 'one asset should have been deployed'
241
+ );
242
+
243
+ // Verify the auto-generated folder was created via REST with the REAL slash in the name field
244
+ // (not the escape char), so SFMC creates a folder named "bla/blub"
245
+ const createRestCallouts = testUtils.getRestCallout(
246
+ 'post',
247
+ '/asset/v1/content/categories',
248
+ true
249
+ );
250
+ assert.ok(
251
+ createRestCallouts?.some((c) => c.name === 'bla/blub' && c.parentId === 89397),
252
+ "REST create for 'Content Builder/bla∕blub' should send name 'bla/blub' with real slash"
253
+ );
254
+ // Verify the escape char was NOT sent as the folder name
255
+ assert.ok(
256
+ !createRestCallouts?.some(
257
+ (c) => c.name === 'bla' + Util.folderNameSlashEscapeChar + 'blub'
258
+ ),
259
+ 'REST create should NOT use the escape char in the name field'
260
+ );
261
+ return;
262
+ });
106
263
  });
107
264
  });