mcdev
Version:
Accenture Salesforce Marketing Cloud DevTools
265 lines (242 loc) • 14.9 kB
JavaScript
import * as chai from 'chai';
const assert = chai.assert;
// const expect = chai.expect;
import chaiFiles from 'chai-files';
import cache from '../lib/util/cache.js';
import * as testUtils from './utils.js';
import handler from '../lib/index.js';
import { Util } from '../lib/util/util.js';
chai.use(chaiFiles);
describe('type: folder', () => {
beforeEach(() => {
testUtils.mockSetup();
});
afterEach(() => {
testUtils.mockReset();
});
describe('Retrieve ================', () => {
it('Should retrieve an asset whose folder name contains a slash and escape the slash in r__folder_Path', async () => {
// GIVEN the SFMC folder "bla/blub" (child of Content Builder) and an asset in that folder
await testUtils.copyFile(
'dataFolder/+retrieve-ContentTypeINasset,asset-shared,cloudpages-slashfolder-response.xml',
'dataFolder/retrieve-ContentTypeINasset,asset-shared,cloudpages-response.xml'
);
await testUtils.copyFile(
'asset/v1/content/assets/query/+post-response-assetType.idIN3,195,196,197,198,199,200,201,202,203,210,211,212,213-slashfolder.json',
'asset/v1/content/assets/query/post-response-assetType.idIN3,195,196,197,198,199,200,201,202,203,210,211,212,213.json'
);
const retrieve = await handler.retrieve('testInstance/testBU', ['asset-block']);
// THEN
assert.equal(process.exitCode, 0, 'retrieve should not have thrown an error');
assert.equal(
retrieve['testInstance/testBU'].asset
? Object.keys(retrieve['testInstance/testBU'].asset).length
: 0,
5,
'five assets expected in retrieve response (4 existing + 1 new test_slash)'
);
// Verify the retrieved asset has the escape char (∕) in r__folder_Path, not a real slash
assert.equal(
retrieve['testInstance/testBU'].asset?.test_slash?.r__folder_Path,
'Content Builder/bla' + Util.folderNameSlashEscapeChar + 'blub',
'r__folder_Path should use ∕ (U+2215) to escape the slash in the folder name'
);
return;
});
});
describe('Deploy ================', () => {
it('Should create automation & dataExtension folders', async () => {
// prepare
testUtils.copyToDeploy('folder-deploy', 'folder');
await testUtils.copyFile(
'dataFolder/+retrieve-response.xml',
'dataFolder/retrieve-response.xml'
);
await testUtils.copyFile(
'dataFolder/+retrieve-QAA-response.xml',
'dataFolder/retrieve-QAA-response.xml'
);
const deployed = await handler.deploy('testInstance/testBU', ['folder']);
// THEN
assert.equal(process.exitCode, 0, 'deploy should not have thrown an error');
// get results from cache
const cached = cache.getCache();
assert.equal(
cached.folder ? Object.keys(cached.folder).length : 0,
40,
'unexpected number of folders in cache'
);
// check what was deployed
assert.deepEqual(
deployed['testInstance/testBU']?.folder
? Object.values(deployed['testInstance/testBU']?.folder).map((f) => f.Path)
: null,
[
'Data Extensions/my',
'Data Extensions/testExisting_folder',
'Data Extensions/my/sub',
'Data Extensions/my/sub/path',
'Data Extensions/my/sub/path/subpath',
'my automations/my',
'my automations/my/sub',
'my automations/my/sub/path',
'my automations/my/sub/path/subpath',
],
'unexpected deployed folders'
);
// get callouts
const createRestCallouts = testUtils.getRestCallout('post', '/email/v1/category', true);
// confirm created item
assert.deepEqual(
createRestCallouts,
[
{ parentCatId: 290937, name: 'my', catType: 'automations' },
{ parentCatId: 862100, name: 'sub', catType: 'automations' },
{ parentCatId: 862101, name: 'path', catType: 'automations' },
{ parentCatId: 862102, name: 'subpath', catType: 'automations' },
],
'create-payload JSON was not equal expected'
);
const createSoapCallouts = testUtils.getSoapCallouts('Create', 'DataFolder');
const updateSoapCallouts = testUtils.getSoapCallouts('Update', 'DataFolder');
// confirm created item
assert.deepEqual(
createSoapCallouts,
[
'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Body><CreateRequest xmlns="http://exacttarget.com/wsdl/partnerAPI"><Objects xsi:type="DataFolder"><Name>my</Name><Description></Description><ContentType>dataextension</ContentType><IsActive>true</IsActive><IsEditable>true</IsEditable><AllowChildren>true</AllowChildren><ParentFolder><ID>2</ID></ParentFolder></Objects></CreateRequest></Body><Header><fueloauth xmlns="http://exacttarget.com">9999999</fueloauth></Header></Envelope>',
'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Body><CreateRequest xmlns="http://exacttarget.com/wsdl/partnerAPI"><Objects xsi:type="DataFolder"><Name>sub</Name><Description></Description><ContentType>dataextension</ContentType><IsActive>true</IsActive><IsEditable>true</IsEditable><AllowChildren>true</AllowChildren><ParentFolder><ID>862001</ID></ParentFolder></Objects></CreateRequest></Body><Header><fueloauth xmlns="http://exacttarget.com">9999999</fueloauth></Header></Envelope>',
'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Body><CreateRequest xmlns="http://exacttarget.com/wsdl/partnerAPI"><Objects xsi:type="DataFolder"><Name>path</Name><Description></Description><ContentType>dataextension</ContentType><IsActive>true</IsActive><IsEditable>true</IsEditable><AllowChildren>true</AllowChildren><ParentFolder><ID>862002</ID></ParentFolder></Objects></CreateRequest></Body><Header><fueloauth xmlns="http://exacttarget.com">9999999</fueloauth></Header></Envelope>',
'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Body><CreateRequest xmlns="http://exacttarget.com/wsdl/partnerAPI"><Objects xsi:type="DataFolder"><Name>subpath</Name><Description></Description><ContentType>dataextension</ContentType><IsActive>true</IsActive><IsEditable>true</IsEditable><AllowChildren>true</AllowChildren><ParentFolder><ID>862003</ID></ParentFolder></Objects></CreateRequest></Body><Header><fueloauth xmlns="http://exacttarget.com">9999999</fueloauth></Header></Envelope>',
],
'create-payload XL was not equal expected'
);
// confirm updated item - this should have updated despite the folder name being different (changed case). The server compares folders case insensitively
assert.deepEqual(
updateSoapCallouts,
[
'<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><Body><UpdateRequest xmlns="http://exacttarget.com/wsdl/partnerAPI"><Objects xsi:type="DataFolder"><Name>testExisting_folder</Name><IsActive>true</IsActive><IsEditable>true</IsEditable><AllowChildren>true</AllowChildren><ParentFolder><ID>2</ID></ParentFolder><ID>66666</ID></Objects></UpdateRequest></Body><Header><fueloauth xmlns="http://exacttarget.com">9999999</fueloauth></Header></Envelope>',
],
'update-payload XL was not equal expected'
);
// check number of API calls
assert.equal(
testUtils.getAPIHistoryLength(),
11,
'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
);
return;
});
it('Should create folder when same path exists in another Business Unit', async () => {
// prepare
// Use folder deploy data with an asset folder path
testUtils.copyToDeploy('folder-deploy-samepath', 'folder');
// Use a modified retrieve response that includes 'Content Builder/testFolder_samePath'
// from a different BU (Client.ID=1111111). ContentType 'asset' is in folderTypesFromParent
// so it will be cached even though it belongs to another BU - simulating the bug scenario
await testUtils.copyFile(
'dataFolder/retrieve-samePathOtherBU-response.xml',
'dataFolder/retrieve-response.xml'
);
const deployed = await handler.deploy('testInstance/testBU', ['folder']);
// THEN
assert.equal(process.exitCode, 0, 'deploy should not have thrown an error');
// check what was deployed - 'Content Builder/testFolder_samePath' should be created
// even though it exists in another BU (1111111)
const deployedFolderPaths = deployed['testInstance/testBU']?.folder
? Object.values(deployed['testInstance/testBU']?.folder).map((f) => f.Path)
: [];
assert.include(
deployedFolderPaths,
'Content Builder/testFolder_samePath',
"'Content Builder/testFolder_samePath' should have been created in current BU, not skipped because it exists in another BU"
);
const createRestCallouts = testUtils.getRestCallout(
'post',
'/asset/v1/content/categories',
true
);
// 'Content Builder/testFolder_samePath' must be created in current BU via REST
// (not skipped due to same path in other BU)
assert.ok(
createRestCallouts?.some(
(c) => c.name === 'testFolder_samePath' && c.parentId === 89397
),
"'Content Builder/testFolder_samePath' folder creation callout not found - folder was incorrectly skipped"
);
return;
});
it('Should create a folder whose name contains a slash character (direct folder deploy)', async () => {
// GIVEN a folder deploy file named Headers%2FFolders.folder-meta.json with Name "Headers/Folders"
// (the % encoding represents the actual '/' in the SFMC folder name)
testUtils.copyToDeploy('folder-deploy-slash', 'folder');
const deployed = await handler.deploy('testInstance/testBU', ['folder']);
// THEN
assert.equal(process.exitCode, 0, 'deploy should not have thrown an error');
// Verify the deployed path uses the escape char (∕) not the real slash
const deployedFolderPaths = deployed['testInstance/testBU']?.folder
? Object.values(deployed['testInstance/testBU']?.folder).map((f) => f.Path)
: [];
assert.include(
deployedFolderPaths,
'Content Builder/Headers' + Util.folderNameSlashEscapeChar + 'Folders',
'deployed folder path should use escape char for the slash in folder name'
);
// Verify that the REST Create callout uses the REAL slash character in the name field
// (not the escape character), so that SFMC creates a folder named "Headers/Folders"
const createRestCallouts = testUtils.getRestCallout(
'post',
'/asset/v1/content/categories',
true
);
assert.ok(
createRestCallouts?.some(
(c) => c.name === 'Headers/Folders' && c.parentId === 89397
),
"REST create for 'Headers/Folders' should use the real slash in name, not the escape char"
);
// Verify the escape char was NOT sent as the folder name
assert.ok(
!createRestCallouts?.some(
(c) => c.name === 'Headers' + Util.folderNameSlashEscapeChar + 'Folders'
),
'REST create should NOT use the escape char in the name field'
);
return;
});
it('Should create a folder whose name contains a slash when triggered via r__folder_Path from an asset', async () => {
// GIVEN an asset deploy with r__folder_Path pointing to a subfolder whose name contains a slash
// The escape char (∕) in r__folder_Path separates the slash-in-name from path separators
testUtils.copyToDeploy('asset-slashfolder-deploy', 'asset');
const deployed = await handler.deploy('testInstance/testBU', ['asset']);
// THEN
assert.equal(process.exitCode, 0, 'deploy should not have thrown an error');
// Verify the asset was deployed
assert.equal(
deployed['testInstance/testBU']?.asset
? Object.keys(deployed['testInstance/testBU']?.asset).length
: 0,
1,
'one asset should have been deployed'
);
// Verify the auto-generated folder was created via REST with the REAL slash in the name field
// (not the escape char), so SFMC creates a folder named "bla/blub"
const createRestCallouts = testUtils.getRestCallout(
'post',
'/asset/v1/content/categories',
true
);
assert.ok(
createRestCallouts?.some((c) => c.name === 'bla/blub' && c.parentId === 89397),
"REST create for 'Content Builder/bla∕blub' should send name 'bla/blub' with real slash"
);
// Verify the escape char was NOT sent as the folder name
assert.ok(
!createRestCallouts?.some(
(c) => c.name === 'bla' + Util.folderNameSlashEscapeChar + 'blub'
),
'REST create should NOT use the escape char in the name field'
);
return;
});
});
});