Repository: Accenture/sfmc-devtools (js)

Issue:
[BUG] if keywords exist multiple times due to multiples codes not all are saved
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

basically, the keyField is incorrect. It cannot be just "keyword" but it needs to be either the ID or a combination of code & key



### Expected Behavior

![image](https://user-images.githubusercontent.com/1917227/236213168-5ea7a607-b1a1-4213-9db4-4181eb920d6b.png)
these 5 entries should actually be 10, given that they each were created for 2 codes. 

### Steps To Reproduce

1. Go to '...'
2. Click on '....'
3. Run '...'
4. See error...


### Version

4.3.4

### Environment

- OS:
- Node:
- npm:


### Participation

- [X] I am willing to submit a pull request for this issue.

### Additional comments

_No response_

Pull request:
Bugfix/889 if keywords exist multiple times due to multiples codes not all are saved
# PR details

## What changes did you make? (Give an overview)

- fixes #889 

## 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
- n/a Wiki updated (if applicable)


Patch:
diff --git a/docs/dist/documentation.md b/docs/dist/documentation.md
index c331a715..de168d7b 100644
--- a/docs/dist/documentation.md
+++ b/docs/dist/documentation.md
@@ -4006,11 +4006,13 @@ MobileKeyword MetadataType
 
 * [MobileKeyword](#MobileKeyword) ⇐ [<code>MetadataType</code>](#MetadataType)
     * [.retrieve(retrieveDir, [_], [__], [key])](#MobileKeyword.retrieve) ⇒ <code>Promise.&lt;TYPE.MetadataTypeMapObj&gt;</code> \| <code>void</code>
+    * [.parseResponseBody(body, [singleRetrieve])](#MobileKeyword.parseResponseBody) ⇒ <code>TYPE.MetadataTypeMap</code>
+    * [.createOrUpdate(metadataMap, metadataKey, hasError, metadataToUpdate, metadataToCreate)](#MobileKeyword.createOrUpdate) ⇒ <code>&#x27;create&#x27;</code> \| <code>&#x27;update&#x27;</code> \| <code>&#x27;skip&#x27;</code>
     * [.retrieveForCache(_, __, [key])](#MobileKeyword.retrieveForCache) ⇒ <code>Promise.&lt;TYPE.MetadataTypeMapObj&gt;</code>
-    * [.retrieveAsTemplate(templateDir, name, templateVariables)](#MobileKeyword.retrieveAsTemplate) ⇒ <code>Promise.&lt;TYPE.MetadataTypeItemObj&gt;</code>
-    * [.create(MobileKeyword)](#MobileKeyword.create) ⇒ <code>Promise</code>
+    * [.retrieveAsTemplate(templateDir, key, templateVariables)](#MobileKeyword.retrieveAsTemplate) ⇒ <code>Promise.&lt;TYPE.MetadataTypeItemObj&gt;</code>
+    * [.create(metadata)](#MobileKeyword.create) ⇒ <code>Promise</code>
     * [.update(metadata)](#MobileKeyword.update) ⇒ <code>Promise</code>
-    * [.postRetrieveTasks(metadata)](#MobileKeyword.postRetrieveTasks) ⇒ <code>TYPE.CodeExtractItem</code> \| <code>TYPE.MetadataTypeItem</code>
+    * [.postRetrieveTasks(metadata)](#MobileKeyword.postRetrieveTasks) ⇒ <code>TYPE.CodeExtractItem</code> \| <code>TYPE.MetadataTypeItem</code> \| <code>void</code>
     * [.prepExtractedCode(metadataScript)](#MobileKeyword.prepExtractedCode) ⇒ <code>Object</code>
     * [.buildDefinitionForNested(templateDir, targetDir, metadata, templateVariables, templateName)](#MobileKeyword.buildDefinitionForNested) ⇒ <code>Promise.&lt;Array.&lt;Array.&lt;string&gt;&gt;&gt;</code>
     * [.buildTemplateForNested(templateDir, targetDir, metadata, templateVariables, templateName)](#MobileKeyword.buildTemplateForNested) ⇒ <code>Promise.&lt;Array.&lt;Array.&lt;string&gt;&gt;&gt;</code>
@@ -4039,6 +4041,35 @@ Endpoint /legacy/v1/beta/mobile/keyword/ return all Mobile Keywords with all det
 | [__] | <code>void</code> | unused parameter |
 | [key] | <code>string</code> | customer key of single item to retrieve |
 
+<a name="MobileKeyword.parseResponseBody"></a>
+
+### MobileKeyword.parseResponseBody(body, [singleRetrieve]) ⇒ <code>TYPE.MetadataTypeMap</code>
+Builds map of metadata entries mapped to their keyfields
+
+**Kind**: static method of [<code>MobileKeyword</code>](#MobileKeyword)  
+**Returns**: <code>TYPE.MetadataTypeMap</code> - keyField => metadata map  
+
+| Param | Type | Description |
+| --- | --- | --- |
+| body | <code>object</code> | json of response body |
+| [singleRetrieve] | <code>string</code> \| <code>number</code> | key of single item to filter by |
+
+<a name="MobileKeyword.createOrUpdate"></a>
+
+### MobileKeyword.createOrUpdate(metadataMap, metadataKey, hasError, metadataToUpdate, metadataToCreate) ⇒ <code>&#x27;create&#x27;</code> \| <code>&#x27;update&#x27;</code> \| <code>&#x27;skip&#x27;</code>
+helper for [upsert](#MetadataType.upsert)
+
+**Kind**: static method of [<code>MobileKeyword</code>](#MobileKeyword)  
+**Returns**: <code>&#x27;create&#x27;</code> \| <code>&#x27;update&#x27;</code> \| <code>&#x27;skip&#x27;</code> - action to take  
+
+| Param | Type | Description |
+| --- | --- | --- |
+| metadataMap | <code>TYPE.MetadataTypeMap</code> | list of metadata |
+| metadataKey | <code>string</code> | key of item we are looking at |
+| hasError | <code>boolean</code> | error flag from previous code |
+| metadataToUpdate | <code>Array.&lt;TYPE.MetadataTypeItemDiff&gt;</code> | list of items to update |
+| metadataToCreate | <code>Array.&lt;TYPE.MetadataTypeItem&gt;</code> | list of items to create |
+
 <a name="MobileKeyword.retrieveForCache"></a>
 
 ### MobileKeyword.retrieveForCache(_, __, [key]) ⇒ <code>Promise.&lt;TYPE.MetadataTypeMapObj&gt;</code>
@@ -4055,8 +4086,8 @@ Retrieves event definition metadata for caching
 
 <a name="MobileKeyword.retrieveAsTemplate"></a>
 
-### MobileKeyword.retrieveAsTemplate(templateDir, name, templateVariables) ⇒ <code>Promise.&lt;TYPE.MetadataTypeItemObj&gt;</code>
-Retrieve a specific keyword
+### MobileKeyword.retrieveAsTemplate(templateDir, key, templateVariables) ⇒ <code>Promise.&lt;TYPE.MetadataTypeItemObj&gt;</code>
+retrieve an item and create a template from it
 
 **Kind**: static method of [<code>MobileKeyword</code>](#MobileKeyword)  
 **Returns**: <code>Promise.&lt;TYPE.MetadataTypeItemObj&gt;</code> - Promise of metadata  
@@ -4064,20 +4095,20 @@ Retrieve a specific keyword
 | Param | Type | Description |
 | --- | --- | --- |
 | templateDir | <code>string</code> | Directory where retrieved metadata directory will be saved |
-| name | <code>string</code> | name of the metadata file |
+| key | <code>string</code> | name of the metadata file |
 | templateVariables | <code>TYPE.TemplateMap</code> | variables to be replaced in the metadata |
 
 <a name="MobileKeyword.create"></a>
 
-### MobileKeyword.create(MobileKeyword) ⇒ <code>Promise</code>
-Creates a single Event Definition
+### MobileKeyword.create(metadata) ⇒ <code>Promise</code>
+Creates a single item
 
 **Kind**: static method of [<code>MobileKeyword</code>](#MobileKeyword)  
 **Returns**: <code>Promise</code> - Promise  
 
 | Param | Type | Description |
 | --- | --- | --- |
-| MobileKeyword | <code>TYPE.MetadataTypeItem</code> | a single Event Definition |
+| metadata | <code>TYPE.MetadataTypeItem</code> | a single item |
 
 <a name="MobileKeyword.update"></a>
 
@@ -4093,11 +4124,11 @@ Updates a single item
 
 <a name="MobileKeyword.postRetrieveTasks"></a>
 
-### MobileKeyword.postRetrieveTasks(metadata) ⇒ <code>TYPE.CodeExtractItem</code> \| <code>TYPE.MetadataTypeItem</code>
+### MobileKeyword.postRetrieveTasks(metadata) ⇒ <code>TYPE.CodeExtractItem</code> \| <code>TYPE.MetadataTypeItem</code> \| <code>void</code>
 manages post retrieve steps
 
 **Kind**: static method of [<code>MobileKeyword</code>](#MobileKeyword)  
-**Returns**: <code>TYPE.CodeExtractItem</code> \| <code>TYPE.MetadataTypeItem</code> - Array with one metadata object and one ssjs string  
+**Returns**: <code>TYPE.CodeExtractItem</code> \| <code>TYPE.MetadataTypeItem</code> \| <code>void</code> - Array with one metadata object and one ssjs string; or single metadata object; nothing if filtered  
 
 | Param | Type | Description |
 | --- | --- | --- |
diff --git a/lib/metadataTypes/Journey.js b/lib/metadataTypes/Journey.js
index 95fce8fe..7c20b2ee 100644
--- a/lib/metadataTypes/Journey.js
+++ b/lib/metadataTypes/Journey.js
@@ -542,12 +542,13 @@ class Journey extends MetadataType {
                     try {
                         // mobileKeyword
                         if (activity.configurationArguments?.keywordId) {
-                            activity.configurationArguments.c__mobileKeyword = cache.searchForField(
-                                'mobileKeyword',
-                                activity.configurationArguments.keywordId,
-                                'id',
-                                'keyword'
-                            );
+                            activity.configurationArguments.r__mobileKeyword_codeKeyword =
+                                cache.searchForField(
+                                    'mobileKeyword',
+                                    activity.configurationArguments.keywordId,
+                                    'id',
+                                    'c__codeKeyword'
+                                );
                             delete activity.configurationArguments.keywordId;
                         }
                     } catch (ex) {
@@ -833,14 +834,14 @@ class Journey extends MetadataType {
                         delete activity.configurationArguments.c__mobileMessage_id;
                     }
                     // mobileKeyword
-                    if (activity.configurationArguments?.c__mobileKeyword) {
+                    if (activity.configurationArguments?.r__mobileKeyword_codeKeyword) {
                         activity.configurationArguments.keywordId = cache.searchForField(
                             'mobileKeyword',
-                            activity.c__mobileKeyword,
-                            'keyword',
+                            activity.r__mobileKeyword_codeKeyword,
+                            'c__codeKeyword',
                             'id'
                         );
-                        delete activity.configurationArguments.c__mobileKeyword;
+                        delete activity.configurationArguments.r__mobileKeyword_codeKeyword;
                     }
                     if (activity.configurationArguments?.c__next_mobileKeyword) {
                         activity.configurationArguments.nextKeywordId = cache.searchForField(
diff --git a/lib/metadataTypes/MetadataType.js b/lib/metadataTypes/MetadataType.js
index 80fe60d3..4658a748 100644
--- a/lib/metadataTypes/MetadataType.js
+++ b/lib/metadataTypes/MetadataType.js
@@ -1528,7 +1528,10 @@ class MetadataType {
                 // so its in retrieve but not in save. Here we put into the clone so that the original
                 // object used for caching doesnt have the Id removed.
                 const saveClone = JSON.parse(JSON.stringify(results[originalKey]));
-                if (!this.definition.keepId) {
+                if (
+                    !this.definition.keepId &&
+                    this.definition.idField !== this.definition.keyField
+                ) {
                     delete saveClone[this.definition.idField];
                 }
 
diff --git a/lib/metadataTypes/MobileKeyword.js b/lib/metadataTypes/MobileKeyword.js
index 4ccc7af4..02ade31c 100644
--- a/lib/metadataTypes/MobileKeyword.js
+++ b/lib/metadataTypes/MobileKeyword.js
@@ -24,14 +24,12 @@ class MobileKeyword extends MetadataType {
      */
     static retrieve(retrieveDir, _, __, key) {
         try {
+            let queryParams;
+            [key, queryParams] = this.#getRetrieveKeyAndUrl(key);
+
             return super.retrieveREST(
                 retrieveDir,
-                '/legacy/v1/beta/mobile/keyword/' +
-                    (key
-                        ? key.startsWith('id:')
-                            ? key.slice(3)
-                            : `?view=simple&$where=keyword%20eq%20%27${key}%27%20`
-                        : '?view=simple'),
+                '/legacy/v1/beta/mobile/keyword/' + queryParams,
                 null,
                 key
             );
@@ -47,6 +45,109 @@ class MobileKeyword extends MetadataType {
         }
     }
 
+    /**
+     * Builds map of metadata entries mapped to their keyfields
+     *
+     * @param {object} body json of response body
+     * @param {string|number} [singleRetrieve] key of single item to filter by
+     * @returns {TYPE.MetadataTypeMap} keyField => metadata map
+     */
+    static parseResponseBody(body, singleRetrieve) {
+        const bodyIteratorField = this.definition.bodyIteratorField;
+        const keyField = this.definition.keyField;
+        const metadataStructure = {};
+        if (body !== null) {
+            if (Array.isArray(body)) {
+                // in some cases data is just an array
+                for (const item of body) {
+                    this.#createCustomKeyField(item);
+                    const key = item[keyField];
+                    metadataStructure[key] = item;
+                }
+            } else if (body[bodyIteratorField]) {
+                for (const item of body[bodyIteratorField]) {
+                    this.#createCustomKeyField(item);
+                    const key = item[keyField];
+                    metadataStructure[key] = item;
+                }
+            } else if (singleRetrieve) {
+                // some types will return a single item intead of an array if the key is supported by their api
+                this.#createCustomKeyField(body);
+                // ! currently, the id: prefix is only supported by journey (interaction)
+                if (singleRetrieve.startsWith('id:')) {
+                    singleRetrieve = body[keyField];
+                }
+                metadataStructure[singleRetrieve] = body;
+                return metadataStructure;
+            }
+            if (
+                metadataStructure[singleRetrieve] &&
+                (typeof singleRetrieve === 'string' || typeof singleRetrieve === 'number')
+            ) {
+                // in case we really just wanted one entry but couldnt do so in the api call, filter it here
+                const single = {};
+                single[singleRetrieve] = metadataStructure[singleRetrieve];
+                return single;
+            } else if (singleRetrieve) {
+                return {};
+            }
+        }
+        return metadataStructure;
+    }
+
+    /**
+     * helper for {@link parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword
+     *
+     * @private
+     * @param {TYPE.MetadataType} metadata single item
+     */
+    static #createCustomKeyField(metadata) {
+        metadata.c__codeKeyword = metadata.code.code + '.' + metadata.keyword;
+    }
+
+    /**
+     * helper for {@link preDeployTasks} and {@link createOrUpdate} to ensure we have code & keyword properly set
+     *
+     * @private
+     * @param {TYPE.MetadataType} metadata single item
+     */
+    static #setCodeAndKeyword(metadata) {
+        const [code, keyword] = metadata.c__codeKeyword.split('.');
+
+        // mobileCode
+        metadata.code = {
+            id: cache.searchForField('mobileCode', code, 'code', 'id'),
+        };
+
+        // keyword
+        metadata.keyword = keyword;
+    }
+
+    /**
+     * helper for {@link MetadataType.upsert}
+     *
+     * @param {TYPE.MetadataTypeMap} metadataMap list of metadata
+     * @param {string} metadataKey key of item we are looking at
+     * @param {boolean} hasError error flag from previous code
+     * @param {TYPE.MetadataTypeItemDiff[]} metadataToUpdate list of items to update
+     * @param {TYPE.MetadataTypeItem[]} metadataToCreate list of items to create
+     * @returns {'create' | 'update' | 'skip'} action to take
+     */
+    static createOrUpdate(metadataMap, metadataKey, hasError, metadataToUpdate, metadataToCreate) {
+        const createOrUpdateAction = super.createOrUpdate(
+            metadataMap,
+            metadataKey,
+            hasError,
+            metadataToUpdate,
+            metadataToCreate
+        );
+        if (createOrUpdateAction === 'update') {
+            // in case --changeKeyField or --changeKeyValue was used, let's ensure we set code & keyword here again
+            this.#setCodeAndKeyword(metadataMap[metadataKey]);
+        }
+        return createOrUpdateAction;
+    }
+
     /**
      * Retrieves event definition metadata for caching
      *
@@ -60,26 +161,29 @@ class MobileKeyword extends MetadataType {
     }
 
     /**
-     * Retrieve a specific keyword
+     * retrieve an item and create a template from it
      *
      * @param {string} templateDir Directory where retrieved metadata directory will be saved
-     * @param {string} name name of the metadata file
+     * @param {string} key name of the metadata file
      * @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
      * @returns {Promise.<TYPE.MetadataTypeItemObj>} Promise of metadata
      */
-    static async retrieveAsTemplate(templateDir, name, templateVariables) {
+    static async retrieveAsTemplate(templateDir, key, templateVariables) {
         try {
+            let queryParams;
+            [key, queryParams] = this.#getRetrieveKeyAndUrl(key);
+
             return super.retrieveTemplateREST(
                 templateDir,
-                `/legacy/v1/beta/mobile/keyword/?view=simple&$where=keyword%20eq%20%27${name}%27%20`,
+                `/legacy/v1/beta/mobile/keyword/` + queryParams,
                 templateVariables,
-                name
+                key
             );
         } catch (ex) {
             // if the mobileMessage does not exist, the API returns the error "Request failed with status code 400 (ERR_BAD_REQUEST)" which would otherwise bring execution to a hold
-            if (name && ex.code === 'ERR_BAD_REQUEST') {
+            if (key && ex.code === 'ERR_BAD_REQUEST') {
                 Util.logger.info(
-                    `Downloaded: ${this.definition.type} (0)${Util.getKeysString(name)}`
+                    `Downloaded: ${this.definition.type} (0)${Util.getKeysString(key)}`
                 );
             } else {
                 throw ex;
@@ -88,13 +192,43 @@ class MobileKeyword extends MetadataType {
     }
 
     /**
-     * Creates a single Event Definition
+     * helper for {@link retrieve} and {@link retrieveAsTemplate}
+     *
+     * @private
+     * @param {string} key customer key of single item to retrieve / name of the metadata file
+     * @returns {Array} key, queryParams
+     */
+    static #getRetrieveKeyAndUrl(key) {
+        let queryParams;
+        if (key) {
+            if (key.startsWith('id:')) {
+                // overwrite queryParams
+                queryParams = key.slice(3);
+            } else if (key.includes('.')) {
+                // keywords are always uppercased
+                key = key.toUpperCase();
+                // format: code.keyword
+                const [code, keyword] = key.split('.');
+                queryParams = `?view=simple&$where=keyword%20eq%20%27${keyword}%27%20and%code%20eq%20%27${code}%27%20`;
+            } else {
+                throw new Error(
+                    `key ${key} has unexpected format. Expected 'code.keyword' or 'id:yourId'`
+                );
+            }
+        } else {
+            queryParams = '?view=simple';
+        }
+        return [key, queryParams];
+    }
+
+    /**
+     * Creates a single item
      *
-     * @param {TYPE.MetadataTypeItem} MobileKeyword a single Event Definition
+     * @param {TYPE.MetadataTypeItem} metadata a single item
      * @returns {Promise} Promise
      */
-    static create(MobileKeyword) {
-        return super.createREST(MobileKeyword, '/legacy/v1/beta/mobile/keyword/');
+    static create(metadata) {
+        return super.createREST(metadata, '/legacy/v1/beta/mobile/keyword/');
     }
     /**
      * Updates a single item
@@ -114,9 +248,21 @@ class MobileKeyword extends MetadataType {
      * manages post retrieve steps
      *
      * @param {TYPE.MetadataTypeItem} metadata a single item
-     * @returns {TYPE.CodeExtractItem | TYPE.MetadataTypeItem} Array with one metadata object and one ssjs string
+     * @returns {TYPE.CodeExtractItem | TYPE.MetadataTypeItem | void} Array with one metadata object and one ssjs string; or single metadata object; nothing if filtered
      */
     static postRetrieveTasks(metadata) {
+        try {
+            cache.searchForField('mobileCode', metadata.code.code, 'code', 'id');
+        } catch {
+            // in case the the mobileCode cannot be found, do not save this keyword as its no longer accessible in the UI either
+            Util.logger.debug(
+                ` - skipping ${this.definition.type} ${
+                    metadata[this.definition.keyField]
+                }. Could not find parent mobileCode ${metadata.code.code}`
+            );
+            return;
+        }
+
         if (metadata.responseMessage) {
             // extract message body
             const codeArr = [];
@@ -295,8 +441,7 @@ class MobileKeyword extends MetadataType {
             );
         }
 
-        // mobileCode
-        metadata.code.id = cache.searchForField('mobileCode', metadata.code.code, 'code', 'id');
+        this.#setCodeAndKeyword(metadata);
         return metadata;
     }
     /**
diff --git a/lib/metadataTypes/MobileMessage.js b/lib/metadataTypes/MobileMessage.js
index 0ad0f2b3..a9a1f822 100644
--- a/lib/metadataTypes/MobileMessage.js
+++ b/lib/metadataTypes/MobileMessage.js
@@ -163,21 +163,17 @@ class MobileMessage extends MetadataType {
 
         // mobileKeyword
         try {
-            if (metadata.keyword?.keyword) {
-                cache.searchForField(
-                    'mobileKeyword',
-                    metadata.keyword.keyword,
-                    'keyword',
-                    'keyword'
-                );
-            }
-            if (metadata.subscriptionKeyword?.keyword) {
-                cache.searchForField(
-                    'mobileKeyword',
-                    metadata.subscriptionKeyword.keyword,
-                    'keyword',
-                    'keyword'
-                );
+            for (const attr of ['keyword', 'subscriptionKeyword', 'nextKeyword']) {
+                if (metadata[attr]?.id) {
+                    metadata[attr] = {
+                        c__codeKeyword: cache.searchForField(
+                            'mobileKeyword',
+                            metadata[attr].id,
+                            'id',
+                            'c__codeKeyword'
+                        ),
+                    };
+                }
             }
         } catch (ex) {
             Util.logger.warn(
@@ -265,20 +261,16 @@ class MobileMessage extends MetadataType {
         metadata.code = code;
 
         // mobileKeyword
-        if (metadata.keyword?.keyword) {
-            const keyword = cache.getByKey('mobileKeyword', metadata.keyword.keyword);
-            if (!keyword) {
-                throw new Error(`mobileKeyword ${metadata.keyword.keyword} not found in cache`);
-            }
-            metadata.keyword = keyword;
-        }
-        if (metadata.subscriptionKeyword?.keyword) {
-            const keyword = cache.getByKey('mobileKeyword', metadata.subscriptionKeyword.keyword);
-            if (!keyword) {
-                throw new Error(`mobileKeyword ${metadata.keyword.keyword} not found in cache`);
+        for (const attr of ['keyword', 'subscriptionKeyword', 'nextKeyword']) {
+            if (metadata[attr]?.c__codeKeyword) {
+                const keywordObj = cache.getByKey('mobileKeyword', metadata[attr].c__codeKeyword);
+                if (!keywordObj) {
+                    throw new Error(
+                        `mobileKeyword ${metadata[attr].c__codeKeyword} not found in cache`
+                    );
+                }
+                metadata[attr] = keywordObj;
             }
-
-            metadata.subscriptionKeyword.keyword = keyword;
         }
 
         // campaign
diff --git a/lib/metadataTypes/TransactionalSMS.js b/lib/metadataTypes/TransactionalSMS.js
index 5b429c49..4e9bfb3d 100644
--- a/lib/metadataTypes/TransactionalSMS.js
+++ b/lib/metadataTypes/TransactionalSMS.js
@@ -58,9 +58,9 @@ class TransactionalSMS extends TransactionalMessage {
             // we merely want to be able to show an error if it does not exist
             cache.searchForField(
                 'mobileKeyword',
-                metadata.subscriptions.keyword,
-                'keyword',
-                'keyword'
+                metadata.subscriptions.shortCode + '.' + metadata.subscriptions.keyword,
+                'c__codeKeyword',
+                'id'
             );
         }
         return metadata;
@@ -142,9 +142,9 @@ class TransactionalSMS extends TransactionalMessage {
                 // we merely want to be able to show a warning if it does not exist
                 cache.searchForField(
                     'mobileKeyword',
-                    metadata.subscriptions.keyword,
+                    metadata.subscriptions.shortCode + '.' + metadata.subscriptions.keyword,
                     'keyword',
-                    'keyword'
+                    'id'
                 );
             } catch {
                 Util.logger.warn(
diff --git a/lib/metadataTypes/definitions/MobileKeyword.definition.js b/lib/metadataTypes/definitions/MobileKeyword.definition.js
index 3f4ebd66..72ac918a 100644
--- a/lib/metadataTypes/definitions/MobileKeyword.definition.js
+++ b/lib/metadataTypes/definitions/MobileKeyword.definition.js
@@ -3,8 +3,8 @@ module.exports = {
     dependencies: ['mobileCode'],
     hasExtended: false,
     idField: 'id',
-    keyIsFixed: false,
-    keyField: 'keyword',
+    keyIsFixed: false, // custom field but mapped to normal fields
+    keyField: 'c__codeKeyword',
     nameField: 'keyword',
     createdDateField: 'createdDate',
     createdNameField: 'createdBy.name',
@@ -21,14 +21,20 @@ module.exports = {
             // irregular format: NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow
             isCreateable: false,
             isUpdateable: true,
-            retrieving: true,
+            retrieving: false,
             template: false,
         },
+        c__codeKeyword: {
+            isCreateable: false,
+            isUpdateable: false,
+            retrieving: true,
+            template: true,
+        },
         keyword: {
             isCreateable: true,
             isUpdateable: true,
-            retrieving: true,
-            template: true,
+            retrieving: false,
+            template: false,
         },
         createdDate: {
             isCreateable: false,
@@ -133,11 +139,17 @@ module.exports = {
             retrieving: true,
             template: false,
         },
+        code: {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: false,
+            template: false,
+        },
         'code.code': {
             isCreateable: false,
             isUpdateable: false,
-            retrieving: true,
-            template: true,
+            retrieving: false,
+            template: false,
         },
         'code.id': {
             isCreateable: true,
diff --git a/lib/metadataTypes/definitions/MobileMessage.definition.js b/lib/metadataTypes/definitions/MobileMessage.definition.js
index 46344738..93bfc9ee 100644
--- a/lib/metadataTypes/definitions/MobileMessage.definition.js
+++ b/lib/metadataTypes/definitions/MobileMessage.definition.js
@@ -316,6 +316,12 @@ module.exports = {
         'keyword.keyword': {
             isCreateable: true,
             isUpdateable: true,
+            retrieving: false,
+            template: false,
+        },
+        'keyword.c__codeKeyword': {
+            isCreateable: false,
+            isUpdateable: false,
             retrieving: true,
             template: true,
         },
@@ -620,27 +626,63 @@ module.exports = {
         'subscriptionKeyword.id': {
             isCreatable: true,
             isUpdatable: true,
-            retrieving: true,
-            template: true,
+            retrieving: false,
+            template: false,
         },
         'subscriptionKeyword.keyword': {
-            isCreatable: true,
-            isUpdatable: true,
+            isCreatable: false,
+            isUpdatable: false,
+            retrieving: false,
+            template: false,
+        },
+        'subscriptionKeyword.c__codeKeyword': {
+            isCreateable: false,
+            isUpdateable: false,
             retrieving: true,
             template: true,
         },
         'subscriptionKeyword.restriction': {
-            isCreatable: true,
-            isUpdatable: true,
-            retrieving: true,
-            template: true,
+            isCreatable: false,
+            isUpdatable: false,
+            retrieving: false,
+            template: false,
         },
         'subscriptionKeyword.isInherited': {
+            isCreatable: false,
+            isUpdatable: false,
+            retrieving: false,
+            template: false,
+        },
+        'nextKeyword.id': {
             isCreatable: true,
             isUpdatable: true,
+            retrieving: false,
+            template: false,
+        },
+        'nextKeyword.keyword': {
+            isCreatable: false,
+            isUpdatable: false,
+            retrieving: false,
+            template: false,
+        },
+        'nextKeyword.c__codeKeyword': {
+            isCreateable: false,
+            isUpdateable: false,
             retrieving: true,
             template: true,
         },
+        'nextKeyword.restriction': {
+            isCreatable: false,
+            isUpdatable: false,
+            retrieving: false,
+            template: false,
+        },
+        'nextKeyword.isInherited': {
+            isCreatable: false,
+            isUpdatable: false,
+            retrieving: false,
+            template: false,
+        },
         subscriberResponseMessage: {
             isCreatable: true,
             isUpdatable: true,
diff --git a/lib/util/util.js b/lib/util/util.js
index cc24c49b..482cbd0c 100644
--- a/lib/util/util.js
+++ b/lib/util/util.js
@@ -437,6 +437,7 @@ const Util = {
             }
         } catch {
             // avoid errors from execSync to bubble up
+            return null;
         }
     },
     /**


Test patch:
diff --git a/test/mockRoot/.mcdevrc.json b/test/mockRoot/.mcdevrc.json
index f4763aee..e87ecaa8 100644
--- a/test/mockRoot/.mcdevrc.json
+++ b/test/mockRoot/.mcdevrc.json
@@ -41,6 +41,7 @@
             "sharedFolder": "/Shared Data Extensions/test",
             "suffix": "_test",
             "prefix": "testExisting_",
+            "prefixUpper": "TESTEXISTING_",
             "description": "bla bla",
             "countryCodeIn": "'test'"
         },
@@ -51,6 +52,7 @@
             "sharedFolder": "/Shared Data Extensions/test target",
             "suffix": "_testTarget",
             "prefix": "testTemplated_",
+            "prefixUpper": "TESTTEMPLATED_",
             "description": "foobar",
             "countryCodeIn": "'testTarget'"
         }
diff --git a/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.amp b/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.amp
similarity index 100%
rename from test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.amp
rename to test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.amp
diff --git a/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.json b/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.json
similarity index 55%
rename from test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.json
rename to test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.json
index 0e352532..ee2208c0 100644
--- a/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword.mobileKeyword-meta.json
+++ b/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.json
@@ -1,8 +1,5 @@
 {
-    "code": {
-        "code": "4912312345678"
-    },
-    "keyword": "testNew_keyword",
+    "c__codeKeyword": "4912312345678.TESTNEW_KEYWORD",
     "status": "Active",
     "restriction": "NONE",
     "keywordType": "STOP",
diff --git a/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.amp b/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.amp
similarity index 100%
rename from test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.amp
rename to test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.amp
diff --git a/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.json b/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.json
similarity index 53%
rename from test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.json
rename to test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.json
index ba94f539..69e9a88a 100644
--- a/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/testNew_keyword_blocked.mobileKeyword-meta.json
+++ b/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.json
@@ -1,8 +1,5 @@
 {
-    "code": {
-        "code": "4912312345678"
-    },
-    "keyword": "testNew_keyword_blocked",
+    "c__codeKeyword": "4912312345678.TESTNEW_KEYWORD_BLOCKED",
     "status": "Active",
     "restriction": "NONE",
     "keywordType": "NORMAL",
diff --git a/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.json b/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.json
index 4cf9c739..3ec2688e 100644
--- a/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.json
+++ b/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.json
@@ -10,6 +10,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.json b/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.json
index cb2fc540..262b7600 100644
--- a/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.json
+++ b/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.json
@@ -10,6 +10,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/journey/build-expected.json b/test/resources/9999999/journey/build-expected.json
index 87036f2b..b3e4d548 100644
--- a/test/resources/9999999/journey/build-expected.json
+++ b/test/resources/9999999/journey/build-expected.json
@@ -323,7 +323,7 @@
             },
             "configurationArguments": {
                 "c__mobileMessage_id": "NTIzOjc4OjA",
-                "c__mobileKeyword": "testTemplated_keyword",
+                "r__mobileKeyword_codeKeyword": "4912312345678.TESTTEMPLATED_KEYWORD",
                 "isOptIn": false,
                 "honorBlackoutWindowEnum": 0,
                 "mobileBlackoutWindowStartTime": "",
diff --git a/test/resources/9999999/journey/get-expected.json b/test/resources/9999999/journey/get-expected.json
index ba60a360..19b1f933 100644
--- a/test/resources/9999999/journey/get-expected.json
+++ b/test/resources/9999999/journey/get-expected.json
@@ -326,7 +326,7 @@
             },
             "configurationArguments": {
                 "c__mobileMessage_id": "NTIzOjc4OjA",
-                "c__mobileKeyword": "testExisting_keyword",
+                "r__mobileKeyword_codeKeyword": "4912312345678.TESTEXISTING_KEYWORD",
                 "isOptIn": false,
                 "honorBlackoutWindowEnum": 0,
                 "mobileBlackoutWindowStartTime": "",
diff --git a/test/resources/9999999/journey/put-expected.json b/test/resources/9999999/journey/put-expected.json
index f21faf54..aa005cf6 100644
--- a/test/resources/9999999/journey/put-expected.json
+++ b/test/resources/9999999/journey/put-expected.json
@@ -326,7 +326,7 @@
             },
             "configurationArguments": {
                 "c__mobileMessage_id": "NTIzOjc4OjA",
-                "c__mobileKeyword": "testExisting_keyword",
+                "r__mobileKeyword_codeKeyword": "4912312345678.TESTEXISTING_KEYWORD",
                 "isOptIn": false,
                 "honorBlackoutWindowEnum": 0,
                 "mobileBlackoutWindowStartTime": "",
diff --git a/test/resources/9999999/journey/template-expected.json b/test/resources/9999999/journey/template-expected.json
index c474ee3e..ec0b7e58 100644
--- a/test/resources/9999999/journey/template-expected.json
+++ b/test/resources/9999999/journey/template-expected.json
@@ -323,7 +323,7 @@
             },
             "configurationArguments": {
                 "c__mobileMessage_id": "NTIzOjc4OjA",
-                "c__mobileKeyword": "{{{prefix}}}keyword",
+                "r__mobileKeyword_codeKeyword": "4912312345678.{{{prefixUpper}}}KEYWORD",
                 "isOptIn": false,
                 "honorBlackoutWindowEnum": 0,
                 "mobileBlackoutWindowStartTime": "",
diff --git a/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json b/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json
index c58232ce..cde6ad4a 100644
--- a/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json
+++ b/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json
@@ -25,7 +25,7 @@
         "isClientOwned": false,
         "isOwner": false
     },
-    "keyword": "testNew_keyword",
+    "keyword": "TESTNEW_KEYWORD",
     "startDate": "2023-03-30T14:00:11.843Z",
     "endDate": "2043-03-30T14:00:11.843Z",
     "status": "Active",
diff --git a/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json b/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json
index 95978299..b256ebe2 100644
--- a/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json
+++ b/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json
@@ -32,7 +32,7 @@
                 "isClientOwned": false,
                 "isOwner": false
             },
-            "keyword": "testExisting_keyword",
+            "keyword": "TESTEXISTING_KEYWORD",
             "startDate": "2018-03-27T12:37:18.197Z",
             "endDate": "2038-03-27T12:37:18.197Z",
             "status": "Active",
diff --git a/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json b/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json
index 22a60470..5e69ccee 100644
--- a/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json
+++ b/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json
@@ -90,7 +90,7 @@
     "isSentImmediately": true,
     "nextKeyword": {
         "id": "cTVJaG5oSDJPVUNHcUh6Z3pQT2tVdzo4Njow",
-        "keyword": "testExisting_keyword",
+        "keyword": "TESTEXISTING_KEYWORD",
         "restriction": "NONE",
         "isInherited": false
     },
diff --git a/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json b/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json
index 43ba203a..8cf07883 100644
--- a/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json
+++ b/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json
@@ -111,7 +111,7 @@
             "isSentImmediately": true,
             "nextKeyword": {
                 "id": "cTVJaG5oSDJPVUNHcUh6Z3pQT2tVdzo4Njow",
-                "keyword": "testExisting_keyword",
+                "keyword": "TESTEXISTING_KEYWORD",
                 "restriction": "NONE",
                 "isInherited": false
             },
diff --git a/test/resources/9999999/messaging/v1/sms/definitions/post-response.json b/test/resources/9999999/messaging/v1/sms/definitions/post-response.json
index c66b9bda..d78803aa 100644
--- a/test/resources/9999999/messaging/v1/sms/definitions/post-response.json
+++ b/test/resources/9999999/messaging/v1/sms/definitions/post-response.json
@@ -12,6 +12,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json b/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json
index cfca5868..e42c625f 100644
--- a/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json
+++ b/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json
@@ -13,6 +13,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json b/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json
index cfca5868..e42c625f 100644
--- a/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json
+++ b/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json
@@ -13,6 +13,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/mobileKeyword/build-expected.json b/test/resources/9999999/mobileKeyword/build-expected.json
index 75331b6b..d23e6a7c 100644
--- a/test/resources/9999999/mobileKeyword/build-expected.json
+++ b/test/resources/9999999/mobileKeyword/build-expected.json
@@ -1,8 +1,5 @@
 {
-    "code": {
-        "code": "4912312345678"
-    },
-    "keyword": "testTemplated_keyword",
+    "c__codeKeyword": "4912312345678.TESTTEMPLATED_KEYWORD",
     "keywordType": "NORMAL",
     "isInherited": false,
     "restriction": "NONE"
diff --git a/test/resources/9999999/mobileKeyword/get-expected.json b/test/resources/9999999/mobileKeyword/get-expected.json
index 398d759c..f06aecbe 100644
--- a/test/resources/9999999/mobileKeyword/get-expected.json
+++ b/test/resources/9999999/mobileKeyword/get-expected.json
@@ -1,10 +1,7 @@
 {
     "createdDate": "2018-03-27T12:37:18.12Z",
     "lastUpdated": "2018-03-27T12:37:18.167Z",
-    "code": {
-        "code": "4912312345678"
-    },
-    "keyword": "testExisting_keyword",
+    "c__codeKeyword": "4912312345678.TESTEXISTING_KEYWORD",
     "startDate": "2018-03-27T12:37:18.197Z",
     "endDate": "2038-03-27T12:37:18.197Z",
     "status": "Active",
diff --git a/test/resources/9999999/mobileKeyword/post-create-expected.json b/test/resources/9999999/mobileKeyword/post-create-expected.json
index 0c3f1b7d..bfca2dd7 100644
--- a/test/resources/9999999/mobileKeyword/post-create-expected.json
+++ b/test/resources/9999999/mobileKeyword/post-create-expected.json
@@ -1,14 +1,11 @@
 {
-    "code": {
-        "code": "4912312345678"
-    },
     "createdBy": {
         "name": "API_JornBerkefeld app user"
     },
     "createdDate": "2023-03-30T14:00:11.907Z",
     "endDate": "2043-03-30T14:00:11.843Z",
     "isInherited": false,
-    "keyword": "testNew_keyword",
+    "c__codeKeyword": "4912312345678.TESTNEW_KEYWORD",
     "keywordType": "NORMAL",
     "lastUpdated": "2023-03-30T14:00:12.295Z",
     "restriction": "NONE",
diff --git a/test/resources/9999999/mobileKeyword/template-expected.json b/test/resources/9999999/mobileKeyword/template-expected.json
index a79e3f45..d14dac5f 100644
--- a/test/resources/9999999/mobileKeyword/template-expected.json
+++ b/test/resources/9999999/mobileKeyword/template-expected.json
@@ -1,8 +1,5 @@
 {
-    "code": {
-        "code": "4912312345678"
-    },
-    "keyword": "{{{prefix}}}keyword",
+    "c__codeKeyword": "4912312345678.{{{prefixUpper}}}KEYWORD",
     "keywordType": "NORMAL",
     "isInherited": false,
     "restriction": "NONE"
diff --git a/test/resources/9999999/transactionalSMS/build-expected.json b/test/resources/9999999/transactionalSMS/build-expected.json
index 43f5c142..951d2e44 100644
--- a/test/resources/9999999/transactionalSMS/build-expected.json
+++ b/test/resources/9999999/transactionalSMS/build-expected.json
@@ -8,6 +8,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testTemplated_keyword"
+        "keyword": "TESTTEMPLATED_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/transactionalSMS/get-expected.json b/test/resources/9999999/transactionalSMS/get-expected.json
index 4cf9c739..3ec2688e 100644
--- a/test/resources/9999999/transactionalSMS/get-expected.json
+++ b/test/resources/9999999/transactionalSMS/get-expected.json
@@ -10,6 +10,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/transactionalSMS/patch-expected.json b/test/resources/9999999/transactionalSMS/patch-expected.json
index 4cf9c739..3ec2688e 100644
--- a/test/resources/9999999/transactionalSMS/patch-expected.json
+++ b/test/resources/9999999/transactionalSMS/patch-expected.json
@@ -10,6 +10,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/transactionalSMS/post-expected.json b/test/resources/9999999/transactionalSMS/post-expected.json
index cb2fc540..262b7600 100644
--- a/test/resources/9999999/transactionalSMS/post-expected.json
+++ b/test/resources/9999999/transactionalSMS/post-expected.json
@@ -10,6 +10,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "testExisting_keyword"
+        "keyword": "TESTEXISTING_KEYWORD"
     }
 }
diff --git a/test/resources/9999999/transactionalSMS/template-expected.json b/test/resources/9999999/transactionalSMS/template-expected.json
index 238056a5..302c2067 100644
--- a/test/resources/9999999/transactionalSMS/template-expected.json
+++ b/test/resources/9999999/transactionalSMS/template-expected.json
@@ -8,6 +8,6 @@
         "countryCode": "",
         "autoAddSubscriber": true,
         "updateSubscriber": true,
-        "keyword": "{{{prefix}}}keyword"
+        "keyword": "{{{prefixUpper}}}KEYWORD"
     }
 }
diff --git a/test/type.mobileKeyword.test.js b/test/type.mobileKeyword.test.js
index bac8e0ee..9f37bffc 100644
--- a/test/type.mobileKeyword.test.js
+++ b/test/type.mobileKeyword.test.js
@@ -30,12 +30,21 @@ describe('type: mobileKeyword', () => {
                 'only 1 mobileKeywords expected'
             );
             assert.deepEqual(
-                await testUtils.getActualJson('testExisting_keyword', 'mobileKeyword'),
+                await testUtils.getActualJson(
+                    '4912312345678.TESTEXISTING_KEYWORD',
+                    'mobileKeyword'
+                ),
                 await testUtils.getExpectedJson('9999999', 'mobileKeyword', 'get'),
                 'saved JSON was not equal expected'
             );
             expect(
-                file(testUtils.getActualFile('testExisting_keyword', 'mobileKeyword', 'amp'))
+                file(
+                    testUtils.getActualFile(
+                        '4912312345678.TESTEXISTING_KEYWORD',
+                        'mobileKeyword',
+                        'amp'
+                    )
+                )
             ).to.equal(file(testUtils.getExpectedFile('9999999', 'mobileKeyword', 'get', 'amp')));
             assert.equal(
                 testUtils.getAPIHistoryLength(),
@@ -51,7 +60,11 @@ describe('type: mobileKeyword', () => {
         });
         it('Should create (but not update) a mobileKeyword', async () => {
             // WHEN
-            await handler.deploy('testInstance/testBU', ['mobileKeyword'], ['testNew_keyword']);
+            await handler.deploy(
+                'testInstance/testBU',
+                ['mobileKeyword'],
+                ['4912312345678.TESTNEW_KEYWORD']
+            );
             // THEN
             assert.equal(process.exitCode, false, 'deploy should not have thrown an error');
             // get results from cache
@@ -63,12 +76,14 @@ describe('type: mobileKeyword', () => {
             );
             // confirm created item
             assert.deepEqual(
-                await testUtils.getActualJson('testNew_keyword', 'mobileKeyword'),
+                await testUtils.getActualJson('4912312345678.TESTNEW_KEYWORD', 'mobileKeyword'),
                 await testUtils.getExpectedJson('9999999', 'mobileKeyword', 'post-create'),
                 'returned JSON was not equal expected for insert mobileKeyword'
             );
             expect(
-                file(testUtils.getActualFile('testNew_keyword', 'mobileKeyword', 'amp'))
+                file(
+                    testUtils.getActualFile('4912312345678.TESTNEW_KEYWORD', 'mobileKeyword', 'amp')
+                )
             ).to.equal(
                 file(testUtils.getExpectedFile('9999999', 'mobileKeyword', 'post-create', 'amp'))
             );
@@ -92,7 +107,7 @@ describe('type: mobileKeyword', () => {
             await handler.deploy(
                 'testInstance/testBU',
                 ['mobileKeyword'],
-                ['testNew_keyword_blocked']
+                ['4912312345678.TESTNEW_KEYWORD_BLOCKED']
             );
             // THEN
             assert.equal(process.exitCode, true, 'deploy should have thrown an error');
@@ -113,7 +128,7 @@ describe('type: mobileKeyword', () => {
             const result = await handler.retrieveAsTemplate(
                 'testInstance/testBU',
                 'mobileKeyword',
-                ['testExisting_keyword'],
+                ['4912312345678.TESTEXISTING_KEYWORD'],
                 'testSourceMarket'
             );
             // WHEN
@@ -128,13 +143,20 @@ describe('type: mobileKeyword', () => {
                 'only one item expected'
             );
             assert.deepEqual(
-                await testUtils.getActualTemplateJson('testExisting_keyword', 'mobileKeyword'),
+                await testUtils.getActualTemplateJson(
+                    '4912312345678.TESTEXISTING_KEYWORD',
+                    'mobileKeyword'
+                ),
                 await testUtils.getExpectedJson('9999999', 'mobileKeyword', 'template'),
                 'returned template JSON was not equal expected'
             );
             expect(
                 file(
-                    testUtils.getActualTemplateFile('testExisting_keyword', 'mobileKeyword', 'amp')
+                    testUtils.getActualTemplateFile(
+                        '4912312345678.TESTEXISTING_KEYWORD',
+                        'mobileKeyword',
+                        'amp'
+                    )
                 )
             ).to.equal(
                 file(testUtils.getExpectedFile('9999999', 'mobileKeyword', 'template', 'amp'))
@@ -153,7 +175,7 @@ describe('type: mobileKeyword', () => {
             const result = await handler.buildTemplate(
                 'testInstance/testBU',
                 'mobileKeyword',
-                ['testExisting_keyword'],
+                ['4912312345678.TESTEXISTING_KEYWORD'],
                 'testSourceMarket'
             );
             assert.equal(process.exitCode, false, 'buildTemplate should not have thrown an error');
@@ -164,13 +186,20 @@ describe('type: mobileKeyword', () => {
                 'only one mobileKeyword expected'
             );
             assert.deepEqual(
-                await testUtils.getActualTemplateJson('testExisting_keyword', 'mobileKeyword'),
+                await testUtils.getActualTemplateJson(
+                    '4912312345678.TESTEXISTING_KEYWORD',
+                    'mobileKeyword'
+                ),
                 await testUtils.getExpectedJson('9999999', 'mobileKeyword', 'template'),
                 'returned template JSON was not equal expected'
             );
             expect(
                 file(
-                    testUtils.getActualTemplateFile('testExisting_keyword', 'mobileKeyword', 'amp')
+                    testUtils.getActualTemplateFile(
+                        '4912312345678.TESTEXISTING_KEYWORD',
+                        'mobileKeyword',
+                        'amp'
+                    )
                 )
             ).to.equal(
                 file(testUtils.getExpectedFile('9999999', 'mobileKeyword', 'template', 'amp'))
@@ -180,7 +209,7 @@ describe('type: mobileKeyword', () => {
             await handler.buildDefinition(
                 'testInstance/testBU',
                 'mobileKeyword',
-                'testExisting_keyword',
+                '4912312345678.TESTEXISTING_KEYWORD',
                 'testTargetMarket'
             );
             assert.equal(
@@ -189,12 +218,21 @@ describe('type: mobileKeyword', () => {
                 'buildDefinition should not have thrown an error'
             );
             assert.deepEqual(
-                await testUtils.getActualDeployJson('testTemplated_keyword', 'mobileKeyword'),
+                await testUtils.getActualDeployJson(
+                    '4912312345678.TESTTEMPLATED_KEYWORD',
+                    'mobileKeyword'
+                ),
                 await testUtils.getExpectedJson('9999999', 'mobileKeyword', 'build'),
                 'returned deployment JSON was not equal expected'
             );
             expect(
-                file(testUtils.getActualDeployFile('testTemplated_keyword', 'mobileKeyword', 'amp'))
+                file(
+                    testUtils.getActualDeployFile(
+                        '4912312345678.TESTTEMPLATED_KEYWORD',
+                        'mobileKeyword',
+                        'amp'
+                    )
+                )
             ).to.equal(file(testUtils.getExpectedFile('9999999', 'mobileKeyword', 'build', 'amp')));
 
             assert.equal(
@@ -209,7 +247,7 @@ describe('type: mobileKeyword', () => {
         it('Should delete the item', async () => {
             // WHEN
             const result = await handler.deleteByKey('testInstance/testBU', 'mobileKeyword', [
-                'testExisting_keyword',
+                '4912312345678.TESTEXISTING_KEYWORD',
             ]);
             // THEN
             assert.equal(process.exitCode, false, 'delete should not have thrown an error');
@@ -224,7 +262,7 @@ describe('type: mobileKeyword', () => {
             const fileList = await handler.getFilesToCommit(
                 'testInstance/testBU',
                 'mobileKeyword',
-                ['testExisting_keyword']
+                ['4912312345678.TESTEXISTING_KEYWORD']
             );
             // THEN
             assert.equal(
@@ -236,12 +274,12 @@ describe('type: mobileKeyword', () => {
 
             assert.equal(
                 fileList[0].split('\\').join('/'),
-                'retrieve/testInstance/testBU/mobileKeyword/testExisting_keyword.mobileKeyword-meta.json',
+                'retrieve/testInstance/testBU/mobileKeyword/4912312345678.TESTEXISTING_KEYWORD.mobileKeyword-meta.json',
                 'wrong JSON path'
             );
             assert.equal(
                 fileList[1].split('\\').join('/'),
-                'retrieve/testInstance/testBU/mobileKeyword/testExisting_keyword.mobileKeyword-meta.amp',
+                'retrieve/testInstance/testBU/mobileKeyword/4912312345678.TESTEXISTING_KEYWORD.mobileKeyword-meta.amp',
                 'wrong AMP path'
             );
             return;