Repository: Accenture/sfmc-devtools (js)

Issue:
[FEATURE] add new method that chains buildTemplate and buildDefinition
right now one has to type in 2 commands and while doing so, update not only the command name, but also the bu and the market. 
lots that can go wrong despite this running offline only.

if we offered a new command that chains the 2 executions we could likely increase speed and also more easily integrate it into our vscode extension (@anasilva105) 

Pull request description:
Feature/1329 add new method that chains buildtemplate and builddefinition + add --metadata to buildDefinitionBulk
# PR details

## What changes did you make? (Give an overview)

- closes #1329 

## Further details (optional)

...

## Checklist

- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] test scripts updated
- [x] Wiki updated (build): https://github.com/Accenture/sfmc-devtools/pull/1330#issuecomment-2115576193
- [x] Wiki updated (bdb): https://github.com/Accenture/sfmc-devtools/pull/1330#issuecomment-2115547292


Patch:
diff --git a/lib/Builder.js b/lib/Builder.js
index db3883b6..a739faf1 100644
--- a/lib/Builder.js
+++ b/lib/Builder.js
@@ -198,7 +198,7 @@ saved
      * @param {string} listName name of list of BU-market combos
      * @param {string} type supported metadata type
      * @param {string[]} nameArr name of the metadata
-     * @returns {Promise.<MultiMetadataTypeList[]>} -
+     * @returns {Promise.<object>} -
      */
     static async buildDefinitionBulk(listName, type, nameArr) {
         const properties = await config.getProperties();
@@ -216,8 +216,8 @@ saved
             return;
         }
         let i = 0;
-        const bdPromises = [];
 
+        const responseObj = {};
         for (const businessUnit in properties.marketList[listName]) {
             if (businessUnit === 'description') {
                 // skip, it's just a metadata on this list and not a BU
@@ -235,16 +235,21 @@ saved
                 if (Util.checkMarket(market, properties)) {
                     Util.logger.info(`Executing for '${businessUnit}': '${market}'`);
                     // omitting "await" to speed up creation
-                    bdPromises.push(this.buildDefinition(businessUnit, type, nameArr, market));
+                    responseObj[businessUnit] ||= {};
+                    responseObj[businessUnit][market] = await this.buildDefinition(
+                        businessUnit,
+                        type,
+                        nameArr,
+                        market
+                    );
                 }
             }
         }
-        const response = await Promise.all(bdPromises);
 
         if (!i) {
             Util.logger.error('Please define properties.marketList in your config');
         }
-        return response;
+        return responseObj;
     }
 }
 
diff --git a/lib/cli.js b/lib/cli.js
index 9286960b..a8458fc9 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -340,6 +340,69 @@ yargs(hideBin(process.argv))
         deprecated: true,
     })
     // @ts-expect-error
+    .command({
+        command: 'build',
+        desc: 'runs buildTemplate followed by buildDefinition',
+        builder: (yargs) => {
+            yargs
+                .option('metadata', {
+                    type: 'string',
+                    alias: 'm',
+                    group: 'Required parameters for build:',
+                    describe: 'type:key combos to build template for',
+                    demandOption: true,
+                })
+                .option('buFrom', {
+                    type: 'string',
+                    alias: 'bf',
+                    group: 'Required parameters for build:',
+                    describe: 'market used for reverse building template',
+                    demandOption: true,
+                })
+                .option('buTo', {
+                    type: 'string',
+                    alias: 'bt',
+                    group: 'Required parameters for build:',
+                    describe: 'the business unit to deploy to; required unless --bulk is set',
+                })
+                .option('marketFrom', {
+                    type: 'string',
+                    alias: 'mf',
+                    group: 'Required parameters for build:',
+                    describe: 'market used for reverse building template',
+                    demandOption: true,
+                })
+                .option('marketTo', {
+                    type: 'string',
+                    alias: 'mt',
+                    group: 'Required parameters for build:',
+                    describe: 'market used for building deployable definition',
+                    demandOption: true,
+                })
+                .option('bulk', {
+                    type: 'boolean',
+                    group: 'Optional parameters for build:',
+                    describe:
+                        'if defined, the marketTo parameter has to be a marketList and buildDefinitionBulk is executed',
+                    demandOption: true,
+                });
+        },
+        handler: (argv) => {
+            Mcdev.setOptions(argv);
+            const typeKeyCombo = Mcdev.metadataToTypeKey(argv.metadata);
+            if ('undefined' !== typeof typeKeyCombo) {
+                Mcdev.build(
+                    argv.buFrom,
+                    argv.buTo,
+                    typeKeyCombo,
+                    argv.marketFrom,
+                    argv.marketTo,
+                    argv.bulk
+                );
+            }
+        },
+    })
+    // @ts-expect-error
     .command({
         command: 'buildTemplate <BU> [TYPE] [KEY] [MARKET]',
         aliases: ['bt'],
@@ -417,7 +480,7 @@ yargs(hideBin(process.argv))
                     type: 'string',
                     alias: 'm',
                     group: 'Options for buildDefinition:',
-                    describe: 'type:key combos to build template for',
+                    describe: 'type:templateName combos to build template for',
                 })
                 .option('market', {
                     type: 'string',
@@ -438,12 +501,11 @@ yargs(hideBin(process.argv))
             } else {
                 Mcdev.buildDefinition(argv.BU, typeKeyCombo, null, argv.MARKET || argv.market);
             }
-            // Mcdev.buildDefinition(argv.BU, argv.TYPE, argv.FILENAME, argv.MARKET);
         },
     })
     // @ts-expect-error
     .command({
-        command: 'buildDefinitionBulk <LISTNAME> <TYPE> <FILENAME>',
+        command: 'buildDefinitionBulk <LISTNAME> [TYPE] [FILENAME]',
         aliases: ['bdb'],
         desc: 'builds metadata definition based on template en bulk',
         builder: (yargs) => {
@@ -459,11 +521,22 @@ yargs(hideBin(process.argv))
                 .positional('FILENAME', {
                     type: 'string',
                     describe: 'File name of the metadata template without the extension',
+                })
+                .option('metadata', {
+                    type: 'string',
+                    alias: 'm',
+                    group: 'Options for buildDefinitionBulk:',
+                    describe: 'type:templateName combos to build template for',
                 });
         },
         handler: (argv) => {
             Mcdev.setOptions(argv);
-            Mcdev.buildDefinitionBulk(argv.LISTNAME, argv.TYPE, csvToArray(argv.FILENAME));
+            const typeKeyCombo = Mcdev.metadataToTypeKey(argv.metadata);
+            if ('undefined' === typeof typeKeyCombo) {
+                Mcdev.buildDefinitionBulk(argv.LISTNAME, argv.TYPE, csvToArray(argv.FILENAME));
+            } else {
+                Mcdev.buildDefinitionBulk(argv.LISTNAME, typeKeyCombo);
+            }
         },
     })
     // @ts-expect-error
@@ -732,7 +805,6 @@ yargs(hideBin(process.argv))
                     describe:
                         'optionally start existing schedule instead of running item once immediately (only works for automations)',
                 });
-            // TODO: add option --metadata
         },
         handler: (argv) => {
             Mcdev.setOptions(argv);
diff --git a/lib/index.js b/lib/index.js
index 38646935..e87500ab 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -778,6 +778,38 @@ class Mcdev {
         }
     }
 
+    /**
+     * Build a template based on a list of metadata files in the retrieve folder.
+     *
+     * @param {string} businessUnitTemplate references credentials from properties.json
+     * @param {string} businessUnitDefinition references credentials from properties.json
+     * @param {TypeKeyCombo} typeKeyCombo limit retrieval to given metadata type
+     * @param {string} marketTemplate market localizations
+     * @param {string} marketDefinition market localizations
+     * @param {boolean} [bulk] runs buildDefinitionBulk instead of buildDefinition; requires marketList to be defined and given via marketDefinition
+     * @returns {Promise.<MultiMetadataTypeList | object>} response from buildDefinition
+     */
+    static async build(
+        businessUnitTemplate,
+        businessUnitDefinition,
+        typeKeyCombo,
+        marketTemplate,
+        marketDefinition,
+        bulk
+    ) {
+        if (!bulk && !businessUnitDefinition) {
+            Util.logger.error(
+                'Please provide a business unit to deploy to via --buTo or activate --bulk'
+            );
+            return;
+        }
+
+        Util.logger.info('mcdev:: Build Template & Build Definition');
+        await this.buildTemplate(businessUnitTemplate, typeKeyCombo, null, marketTemplate);
+        return bulk
+            ? this.buildDefinitionBulk(marketDefinition, typeKeyCombo, null)
+            : this.buildDefinition(businessUnitDefinition, typeKeyCombo, null, marketDefinition);
+    }
     /**
      * Build a template based on a list of metadata files in the retrieve folder.
      *
@@ -865,14 +897,43 @@ class Mcdev {
      * Build a specific metadata file based on a template using a list of bu-market combos
      *
      * @param {string} listName name of list of BU-market combos
-     * @param {string} type supported metadata type
-     * @param {string[]} nameArr name of the metadata
-     * @returns {Promise.<MultiMetadataTypeList[]>} -
+     * @param {string | TypeKeyCombo} selectedTypes supported metadata type
+     * @param {string[]} [nameArr] name of the metadata
+     * @returns {Promise.<object>} -
      */
-    static async buildDefinitionBulk(listName, type, nameArr) {
+    static async buildDefinitionBulk(listName, selectedTypes, nameArr) {
         Util.startLogger();
         Util.logger.info('mcdev:: Build Definition from Template Bulk');
-        return Builder.buildDefinitionBulk(listName, type, nameArr);
+        let selectedTypesArr;
+        if ('string' === typeof selectedTypes) {
+            selectedTypesArr = [selectedTypes];
+        } else {
+            selectedTypesArr = Object.keys(selectedTypes);
+            // check if types are valid
+            for (const selectedType of selectedTypesArr) {
+                if (!Util._isValidType(selectedType)) {
+                    return;
+                }
+                if (!Array.isArray(selectedTypes[selectedType])) {
+                    // we need an array of keys here
+                    return;
+                }
+            }
+        }
+        /** @type {MultiMetadataTypeList} */
+        const returnObj = {};
+        for (const selectedType of selectedTypesArr) {
+            if (selectedTypesArr.length > 1) {
+                Util.logger.info(Util.getGrayMsg(`buildDefinitionBulk for ${selectedType}`));
+            }
+            const result = await Builder.buildDefinitionBulk(
+                listName,
+                selectedType,
+                'string' === typeof selectedTypes ? nameArr : selectedTypes[selectedType]
+            );
+            returnObj[selectedType] = result;
+        }
+        return returnObj;
     }
     /**
      *


Test patch:
diff --git a/test/general.test.js b/test/general.test.js
index be9751cd..686e7c5a 100644
--- a/test/general.test.js
+++ b/test/general.test.js
@@ -361,7 +361,9 @@ describe('GENERAL', () => {
 
                 // check automation
                 assert.equal(
-                    definitionResult.automation ? Object.keys(templateResult.automation).length : 0,
+                    definitionResult.automation
+                        ? Object.keys(definitionResult.automation).length
+                        : 0,
                     1,
                     'only one automation expected'
                 );
@@ -373,7 +375,7 @@ describe('GENERAL', () => {
 
                 // check query
                 assert.equal(
-                    definitionResult.query ? Object.keys(templateResult.query).length : 0,
+                    definitionResult.query ? Object.keys(definitionResult.query).length : 0,
                     1,
                     'only one query expected'
                 );
@@ -392,6 +394,483 @@ describe('GENERAL', () => {
                     'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
                 );
             });
+
+            it('build multiple type with keys', async () => {
+                // download first before we test buildTemplate
+                await handler.retrieve('testInstance/testBU', ['automation', 'query']);
+
+                const expectedApiCallsRetrieve = 25;
+                assert.equal(
+                    testUtils.getAPIHistoryLength(),
+                    expectedApiCallsRetrieve,
+                    'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
+                );
+
+                // preparation
+                const argvMetadata = [
+                    'automation:testExisting_automation',
+                    'query:testExisting_query',
+                    'query:bad',
+                ];
+                const typeKeyCombo = handler.metadataToTypeKey(argvMetadata);
+                assert.notEqual(
+                    typeof typeKeyCombo,
+                    'undefined',
+                    'typeKeyCombo should not be undefined'
+                );
+                const buName = 'testInstance/testBU';
+
+                // *** build: buildTemplate and buildDefinition chained ***
+                const definitionResult = await handler.build(
+                    buName,
+                    buName,
+                    typeKeyCombo,
+                    'testSourceMarket',
+                    'testTargetMarket'
+                );
+                assert.equal(process.exitCode, 0, 'build should not have thrown an error');
+
+                // *** buildTemplate ***
+
+                // check automation
+                assert.deepEqual(
+                    await testUtils.getActualTemplateJson('testExisting_automation', 'automation'),
+                    await testUtils.getExpectedJson('9999999', 'automation', 'template'),
+                    'returned template was not equal expected'
+                );
+                // check query
+                assert.deepEqual(
+                    await testUtils.getActualTemplateJson('testExisting_query', 'query'),
+                    await testUtils.getExpectedJson('9999999', 'query', 'template'),
+                    'returned template JSON of retrieveAsTemplate was not equal expected'
+                );
+                expect(
+                    file(testUtils.getActualTemplateFile('testExisting_query', 'query', 'sql'))
+                ).to.equal(file(testUtils.getExpectedFile('9999999', 'query', 'template', 'sql')));
+
+                // *** buildDefinition ***
+
+                // check automation
+                assert.equal(
+                    definitionResult.automation
+                        ? Object.keys(definitionResult.automation).length
+                        : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualDeployJson('testTemplated_automation', 'automation'),
+                    await testUtils.getExpectedJson('9999999', 'automation', 'build'),
+                    'returned deployment file was not equal expected'
+                );
+
+                // check query
+                assert.equal(
+                    definitionResult.query ? Object.keys(definitionResult.query).length : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualDeployJson('testTemplated_query', 'query'),
+                    await testUtils.getExpectedJson('9999999', 'query', 'build'),
+                    'returned deployment JSON was not equal expected'
+                );
+                expect(
+                    file(testUtils.getActualDeployFile('testTemplated_query', 'query', 'sql'))
+                ).to.equal(file(testUtils.getExpectedFile('9999999', 'query', 'build', 'sql')));
+
+                assert.equal(
+                    testUtils.getAPIHistoryLength() - expectedApiCallsRetrieve,
+                    0,
+                    'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
+                );
+            });
+
+            it('build multiple type with keys and --bulk', async () => {
+                // download first before we test buildTemplate
+                await handler.retrieve('testInstance/testBU', ['automation', 'query']);
+
+                const expectedApiCallsRetrieve = 25;
+                assert.equal(
+                    testUtils.getAPIHistoryLength(),
+                    expectedApiCallsRetrieve,
+                    'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
+                );
+
+                // preparation
+                const argvMetadata = [
+                    'automation:testExisting_automation',
+                    'query:testExisting_query',
+                    'query:bad',
+                ];
+                const typeKeyCombo = handler.metadataToTypeKey(argvMetadata);
+                assert.notEqual(
+                    typeof typeKeyCombo,
+                    'undefined',
+                    'typeKeyCombo should not be undefined'
+                );
+                const buName = 'testInstance/testBU';
+
+                // *** build: buildTemplate and buildDefinition chained ***
+                const definitionResult = await handler.build(
+                    buName,
+                    'ignored',
+                    typeKeyCombo,
+                    'testSourceMarket',
+                    'deployment-target',
+                    true
+                );
+                assert.equal(process.exitCode, 0, 'build should not have thrown an error');
+
+                // *** buildTemplate ***
+
+                // check automation
+                assert.deepEqual(
+                    await testUtils.getActualTemplateJson('testExisting_automation', 'automation'),
+                    await testUtils.getExpectedJson('9999999', 'automation', 'template'),
+                    'returned template was not equal expected'
+                );
+                // check query
+                assert.deepEqual(
+                    await testUtils.getActualTemplateJson('testExisting_query', 'query'),
+                    await testUtils.getExpectedJson('9999999', 'query', 'template'),
+                    'returned template JSON of retrieveAsTemplate was not equal expected'
+                );
+                expect(
+                    file(testUtils.getActualTemplateFile('testExisting_query', 'query', 'sql'))
+                ).to.equal(file(testUtils.getExpectedFile('9999999', 'query', 'template', 'sql')));
+
+                // *** buildDefinitionBulk ***
+
+                // check automation
+                assert.equal(
+                    definitionResult.automation?.['testInstance/testBU']?.testSourceMarket
+                        ? Object.keys(
+                              definitionResult.automation?.['testInstance/testBU']?.testSourceMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.equal(
+                    definitionResult.automation?.['testInstance/testBU']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.automation?.['testInstance/testBU']?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.equal(
+                    definitionResult.automation?.['testInstance/_ParentBU_']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.automation?.['testInstance/_ParentBU_']
+                                  ?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualDeployJson('testTemplated_automation', 'automation'),
+                    await testUtils.getExpectedJson('9999999', 'automation', 'build'),
+                    'returned deployment file was not equal expected'
+                );
+
+                // check if files were also created for other BU-market combos
+                // testBU: testSourceMarket
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testExisting_automation',
+                            'automation',
+                            'json'
+                        )
+                    )
+                ).to.exist;
+                // _ParentBU_: testTargetMarket
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testTemplated_automation',
+                            'automation',
+                            'json',
+                            '_ParentBU_'
+                        )
+                    )
+                ).to.exist;
+
+                // check query
+                assert.equal(
+                    definitionResult.query?.['testInstance/testBU']?.testSourceMarket
+                        ? Object.keys(
+                              definitionResult.query?.['testInstance/testBU']?.testSourceMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.equal(
+                    definitionResult.query?.['testInstance/testBU']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.query?.['testInstance/testBU']?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.equal(
+                    definitionResult.query?.['testInstance/_ParentBU_']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.query?.['testInstance/_ParentBU_']?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualDeployJson('testTemplated_query', 'query'),
+                    await testUtils.getExpectedJson('9999999', 'query', 'build'),
+                    'returned deployment JSON was not equal expected'
+                );
+                expect(
+                    file(testUtils.getActualDeployFile('testTemplated_query', 'query', 'sql'))
+                ).to.equal(file(testUtils.getExpectedFile('9999999', 'query', 'build', 'sql')));
+
+                // check if files were also created for other BU-market combos
+                // testBU: testSourceMarket
+                expect(file(testUtils.getActualDeployFile('testExisting_query', 'query', 'json')))
+                    .to.exist;
+                expect(file(testUtils.getActualDeployFile('testExisting_query', 'query', 'sql'))).to
+                    .exist;
+                // _ParentBU_: testTargetMarket
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testTemplated_query',
+                            'query',
+                            'json',
+                            '_ParentBU_'
+                        )
+                    )
+                ).to.exist;
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testTemplated_query',
+                            'query',
+                            'sql',
+                            '_ParentBU_'
+                        )
+                    )
+                ).to.exist;
+
+                assert.equal(
+                    testUtils.getAPIHistoryLength() - expectedApiCallsRetrieve,
+                    0,
+                    'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
+                );
+            });
+
+            it('buildTemplate + builDefinitionBulk multiple type with keys', async () => {
+                // download first before we test buildTemplate
+                await handler.retrieve('testInstance/testBU', ['automation', 'query']);
+
+                const expectedApiCallsRetrieve = 25;
+                assert.equal(
+                    testUtils.getAPIHistoryLength(),
+                    expectedApiCallsRetrieve,
+                    'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
+                );
+
+                // preparation
+                const argvMetadata = [
+                    'automation:testExisting_automation',
+                    'query:testExisting_query',
+                    'query:bad',
+                ];
+                const typeKeyCombo = handler.metadataToTypeKey(argvMetadata);
+                assert.notEqual(
+                    typeof typeKeyCombo,
+                    'undefined',
+                    'typeKeyCombo should not be undefined'
+                );
+                const buName = 'testInstance/testBU';
+
+                // *** buildTemplate ***
+                const templateResult = await handler.buildTemplate(
+                    buName,
+                    typeKeyCombo,
+                    null,
+                    'testSourceMarket'
+                );
+                assert.equal(process.exitCode, 0, 'buildTemplate should not have thrown an error');
+                // check automation
+                assert.equal(
+                    templateResult.automation ? Object.keys(templateResult.automation).length : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualTemplateJson('testExisting_automation', 'automation'),
+                    await testUtils.getExpectedJson('9999999', 'automation', 'template'),
+                    'returned template was not equal expected'
+                );
+                // check query
+                assert.equal(
+                    templateResult.query ? Object.keys(templateResult.query).length : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualTemplateJson('testExisting_query', 'query'),
+                    await testUtils.getExpectedJson('9999999', 'query', 'template'),
+                    'returned template JSON of retrieveAsTemplate was not equal expected'
+                );
+                expect(
+                    file(testUtils.getActualTemplateFile('testExisting_query', 'query', 'sql'))
+                ).to.equal(file(testUtils.getExpectedFile('9999999', 'query', 'template', 'sql')));
+
+                // *** buildDefinitionBulk chained ***
+                const definitionResult = await handler.buildDefinitionBulk(
+                    'deployment-target',
+                    typeKeyCombo
+                );
+                assert.equal(
+                    process.exitCode,
+                    0,
+                    'buildDefinitionBulk should not have thrown an error'
+                );
+
+                // check automation
+                assert.equal(
+                    definitionResult.automation?.['testInstance/testBU']?.testSourceMarket
+                        ? Object.keys(
+                              definitionResult.automation?.['testInstance/testBU']?.testSourceMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.equal(
+                    definitionResult.automation?.['testInstance/testBU']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.automation?.['testInstance/testBU']?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.equal(
+                    definitionResult.automation?.['testInstance/_ParentBU_']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.automation?.['testInstance/_ParentBU_']
+                                  ?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one automation expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualDeployJson('testTemplated_automation', 'automation'),
+                    await testUtils.getExpectedJson('9999999', 'automation', 'build'),
+                    'returned deployment file was not equal expected'
+                );
+
+                // check if files were also created for other BU-market combos
+                // testBU: testSourceMarket
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testExisting_automation',
+                            'automation',
+                            'json'
+                        )
+                    )
+                ).to.exist;
+                // _ParentBU_: testTargetMarket
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testTemplated_automation',
+                            'automation',
+                            'json',
+                            '_ParentBU_'
+                        )
+                    )
+                ).to.exist;
+
+                // check query
+                assert.equal(
+                    definitionResult.query?.['testInstance/testBU']?.testSourceMarket
+                        ? Object.keys(
+                              definitionResult.query?.['testInstance/testBU']?.testSourceMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.equal(
+                    definitionResult.query?.['testInstance/testBU']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.query?.['testInstance/testBU']?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.equal(
+                    definitionResult.query?.['testInstance/_ParentBU_']?.testTargetMarket
+                        ? Object.keys(
+                              definitionResult.query?.['testInstance/_ParentBU_']?.testTargetMarket
+                          ).length
+                        : 0,
+                    1,
+                    'only one query expected'
+                );
+                assert.deepEqual(
+                    await testUtils.getActualDeployJson('testTemplated_query', 'query'),
+                    await testUtils.getExpectedJson('9999999', 'query', 'build'),
+                    'returned deployment JSON was not equal expected'
+                );
+                expect(
+                    file(testUtils.getActualDeployFile('testTemplated_query', 'query', 'sql'))
+                ).to.equal(file(testUtils.getExpectedFile('9999999', 'query', 'build', 'sql')));
+
+                // check if files were also created for other BU-market combos
+                // testBU: testSourceMarket
+                expect(file(testUtils.getActualDeployFile('testExisting_query', 'query', 'json')))
+                    .to.exist;
+                expect(file(testUtils.getActualDeployFile('testExisting_query', 'query', 'sql'))).to
+                    .exist;
+                // _ParentBU_: testTargetMarket
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testTemplated_query',
+                            'query',
+                            'json',
+                            '_ParentBU_'
+                        )
+                    )
+                ).to.exist;
+                expect(
+                    file(
+                        testUtils.getActualDeployFile(
+                            'testTemplated_query',
+                            'query',
+                            'sql',
+                            '_ParentBU_'
+                        )
+                    )
+                ).to.exist;
+
+                assert.equal(
+                    testUtils.getAPIHistoryLength() - expectedApiCallsRetrieve,
+                    0,
+                    'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests'
+                );
+            });
         });
     });
 
diff --git a/test/mockRoot/.mcdevrc.json b/test/mockRoot/.mcdevrc.json
index d7811fb2..49943786 100644
--- a/test/mockRoot/.mcdevrc.json
+++ b/test/mockRoot/.mcdevrc.json
@@ -57,7 +57,17 @@
             "countryCodeIn": "'testTarget'"
         }
     },
-    "marketList": {},
+    "marketList": {
+        "deployment-source": {
+            "description": "Define one 1:1 BU-Market combo here to as source for automated creation of deployment packages; you can create more than one source market list",
+            "testInstance/testBU": "testSourceMarket"
+        },
+        "deployment-target": {
+            "description": "Define n BU-Market combo here to as target for automated creation of deployment packages; you can create more than one target market list and they can be as complex as you like",
+            "testInstance/testBU": ["testSourceMarket", "testTargetMarket"],
+            "testInstance/_ParentBU_": ["testTargetMarket"]
+        }
+    },
     "metaDataTypes": {
         "documentOnRetrieve": ["user", "automation", "dataExtension", "role"],
         "retrieve": [