Repository: Accenture/sfmc-devtools (js)

Issue:
[FEATURE] support transactional push
https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/routes.html#transactional-messaging-push

get list:
https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/messagingPushReadAllDefinitions.html

get single:
https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/messagingPushReadDefinition.html

create:
https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/messagingPushCreateDefinition.html

update:
https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/routes.html#transactional-messaging-sms

delete:
https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/messagingPushDeleteDefinition.html

Pull request description:
#560 support transactional push
- closes #560


**important:**
- blocked by PR #562  - close that one first


**todo:**
- [x] retrieve
- [x] deploy
- [x] during postRetrieve -> check if dependent metadata is available
- [x] during preDeploy -> check if dependent metadata is available
- [x] templating
- [x] test: retrieve
- [x] test: deploy
- [x] test: templating


Patch:
diff --git a/docs/dist/documentation.md b/docs/dist/documentation.md
index a467619e..eac121c6 100644
--- a/docs/dist/documentation.md
+++ b/docs/dist/documentation.md
@@ -107,6 +107,9 @@ Provides default functionality that can be overwritten by child metadata type cl
 <dt><a href="#TransactionalMessage">TransactionalMessage</a> ⇐ <code><a href="#MetadataType">MetadataType</a></code></dt>
 <dd><p>TransactionalMessage MetadataType</p>
 </dd>
+<dt><a href="#TransactionalPush">TransactionalPush</a> ⇐ <code><a href="#TransactionalMessage">TransactionalMessage</a></code></dt>
+<dd><p>TransactionalPush TransactionalMessage</p>
+</dd>
 <dt><a href="#TransactionalSMS">TransactionalSMS</a> ⇐ <code><a href="#TransactionalMessage">TransactionalMessage</a></code></dt>
 <dd><p>TransactionalSMS MetadataType</p>
 </dd>
@@ -4483,6 +4486,42 @@ Delete a metadata item from the specified business unit
 | buObject | <code>TYPE.BuObject</code> | references credentials |
 | key | <code>string</code> | Identifier of item |
 
+<a name="TransactionalPush"></a>
+
+## TransactionalPush ⇐ [<code>TransactionalMessage</code>](#TransactionalMessage)
+TransactionalPush TransactionalMessage
+
+**Kind**: global class  
+**Extends**: [<code>TransactionalMessage</code>](#TransactionalMessage)  
+
+* [TransactionalPush](#TransactionalPush) ⇐ [<code>TransactionalMessage</code>](#TransactionalMessage)
+    * [.preDeployTasks(metadata)](#TransactionalPush.preDeployTasks) ⇒ <code>TYPE.MetadataTypeItem</code>
+    * [.postRetrieveTasks(metadata)](#TransactionalPush.postRetrieveTasks) ⇒ <code>TYPE.MetadataTypeItem</code>
+
+<a name="TransactionalPush.preDeployTasks"></a>
+
+### TransactionalPush.preDeployTasks(metadata) ⇒ <code>TYPE.MetadataTypeItem</code>
+prepares for deployment
+
+**Kind**: static method of [<code>TransactionalPush</code>](#TransactionalPush)  
+**Returns**: <code>TYPE.MetadataTypeItem</code> - Promise  
+
+| Param | Type | Description |
+| --- | --- | --- |
+| metadata | <code>TYPE.MetadataTypeItem</code> | a single item |
+
+<a name="TransactionalPush.postRetrieveTasks"></a>
+
+### TransactionalPush.postRetrieveTasks(metadata) ⇒ <code>TYPE.MetadataTypeItem</code>
+manages post retrieve steps
+
+**Kind**: static method of [<code>TransactionalPush</code>](#TransactionalPush)  
+**Returns**: <code>TYPE.MetadataTypeItem</code> - a single item  
+
+| Param | Type | Description |
+| --- | --- | --- |
+| metadata | <code>TYPE.MetadataTypeItem</code> | a single item |
+
 <a name="TransactionalSMS"></a>
 
 ## TransactionalSMS ⇐ [<code>TransactionalMessage</code>](#TransactionalMessage)
diff --git a/lib/MetadataTypeDefinitions.js b/lib/MetadataTypeDefinitions.js
index bf274dc7..148da00f 100644
--- a/lib/MetadataTypeDefinitions.js
+++ b/lib/MetadataTypeDefinitions.js
@@ -33,6 +33,7 @@ const MetadataTypeDefinitions = {
     script: require('./metadataTypes/definitions/Script.definition'),
     setDefinition: require('./metadataTypes/definitions/SetDefinition.definition'),
     transactionalEmail: require('./metadataTypes/definitions/TransactionalEmail.definition'),
+    transactionalPush: require('./metadataTypes/definitions/TransactionalPush.definition'),
     transactionalSMS: require('./metadataTypes/definitions/TransactionalSMS.definition'),
     triggeredSendDefinition: require('./metadataTypes/definitions/TriggeredSendDefinition.definition'),
 };
diff --git a/lib/MetadataTypeInfo.js b/lib/MetadataTypeInfo.js
index 065f9b4b..053636fd 100644
--- a/lib/MetadataTypeInfo.js
+++ b/lib/MetadataTypeInfo.js
@@ -33,6 +33,7 @@ const MetadataTypeInfo = {
     script: require('./metadataTypes/Script'),
     setDefinition: require('./metadataTypes/SetDefinition'),
     transactionalEmail: require('./metadataTypes/TransactionalEmail'),
+    transactionalPush: require('./metadataTypes/TransactionalPush'),
     transactionalSMS: require('./metadataTypes/TransactionalSMS'),
     triggeredSendDefinition: require('./metadataTypes/TriggeredSendDefinition'),
 };
diff --git a/lib/metadataTypes/TransactionalPush.js b/lib/metadataTypes/TransactionalPush.js
new file mode 100644
index 00000000..1b97b692
--- /dev/null
+++ b/lib/metadataTypes/TransactionalPush.js
@@ -0,0 +1,77 @@
+'use strict';
+
+const TYPE = require('../../types/mcdev.d');
+const TransactionalMessage = require('./TransactionalMessage');
+const Util = require('../util/util');
+const cache = require('../util/cache');
+
+/**
+ * TransactionalPush TransactionalMessage
+ *
+ * @augments TransactionalMessage
+ */
+class TransactionalPush extends TransactionalMessage {
+    static subType = 'push';
+
+    /**
+     * prepares for deployment
+     *
+     * @param {TYPE.MetadataTypeItem} metadata a single item
+     * @returns {TYPE.MetadataTypeItem} Promise
+     */
+    static async preDeployTasks(metadata) {
+        // asset
+        if (metadata.content?.customerKey) {
+            // we merely want to be able to show an error if it does not exist
+            cache.searchForField(
+                'asset',
+                metadata.content.customerKey,
+                'customerKey',
+                'customerKey'
+            );
+        }
+        if (metadata.options?.badge && typeof metadata.options?.badge !== 'string') {
+            // ensure it's a string, or else the API will return an error. Our SDK turns numbers in strings into actual numbers
+            metadata.options.badge += '';
+        }
+
+        return metadata;
+    }
+    /**
+     * manages post retrieve steps
+     *
+     * @param {TYPE.MetadataTypeItem} metadata a single item
+     * @returns {TYPE.MetadataTypeItem} a single item
+     */
+    static postRetrieveTasks(metadata) {
+        // asset
+        if (metadata.content?.customerKey) {
+            try {
+                // we merely want to be able to show an error if it does not exist
+                cache.searchForField(
+                    'asset',
+                    metadata.content.customerKey,
+                    'customerKey',
+                    'customerKey'
+                );
+            } catch (ex) {
+                Util.logger.warn(
+                    ` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
+                        metadata[this.definition.keyField]
+                    }): ${ex.message}.`
+                );
+            }
+        }
+        if (metadata.options?.badge && typeof metadata.options?.badge !== 'string') {
+            // ensure it's a string, or else the API will return an error. Our SDK turns numbers in strings into actual numbers
+            metadata.options.badge += '';
+        }
+
+        return metadata;
+    }
+}
+
+// Assign definition to static attributes
+TransactionalPush.definition = require('../MetadataTypeDefinitions').transactionalPush;
+
+module.exports = TransactionalPush;
diff --git a/lib/metadataTypes/definitions/TransactionalPush.definition.js b/lib/metadataTypes/definitions/TransactionalPush.definition.js
new file mode 100644
index 00000000..21caee96
--- /dev/null
+++ b/lib/metadataTypes/definitions/TransactionalPush.definition.js
@@ -0,0 +1,109 @@
+module.exports = {
+    bodyIteratorField: 'definitions',
+    dependencies: ['asset-asset'],
+    hasExtended: false,
+    idField: 'definitionId',
+    keyField: 'definitionKey',
+    nameField: 'name',
+    createdDateField: 'createdDate',
+    createdNameField: null,
+    lastmodDateField: 'modifiedDate',
+    lastmodNameField: null,
+    restPagination: false,
+    type: 'transactionalPush',
+    typeDescription: 'Lets you send immediate Push messages via API events',
+    typeRetrieveByDefault: true,
+    typeName: 'Transactional Push',
+    fields: {
+        name: {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        definitionKey: {
+            isCreateable: true,
+            isUpdateable: false,
+            retrieving: true,
+            template: true,
+        },
+        description: {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        requestId: {
+            isCreateable: false,
+            isUpdateable: false,
+            retrieving: false,
+            template: false,
+        },
+        definitionId: {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: false,
+            template: false,
+        },
+        status: {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: false,
+        },
+        createdDate: {
+            isCreateable: false,
+            isUpdateable: false,
+            retrieving: true,
+            template: false,
+        },
+        modifiedDate: {
+            isCreateable: false,
+            isUpdateable: false,
+            retrieving: true,
+            template: false,
+        },
+        'content.customerKey': {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        'options.badge': {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        'options.sound': {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        'options.customKeys': {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        'options.customKeys[].value': {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        'options.customKeys[].key': {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+        applicationId: {
+            isCreateable: true,
+            isUpdateable: true,
+            retrieving: true,
+            template: true,
+        },
+    },
+};


Test patch:
diff --git a/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testExisting_tpush.transactionalPush-meta.json b/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testExisting_tpush.transactionalPush-meta.json
new file mode 100644
index 00000000..39407d68
--- /dev/null
+++ b/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testExisting_tpush.transactionalPush-meta.json
@@ -0,0 +1,18 @@
+{
+    "definitionKey": "testExisting_tpush",
+    "name": "testExisting_tpush",
+    "status": "Active",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "description": "updated via deploy; note that applicationId can only be manually set up in Setup - Mobile Push",
+    "content": {
+        "customerKey": "mobileMessage_test"
+    },
+    "options": {
+        "sound": "temp.wmv",
+        "badge": "1",
+        "customKeys": [
+            { "key": "key1", "value": "value1" },
+            { "key": "key2", "value": "value2" }
+        ]
+    }
+}
diff --git a/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testNew_tpush.transactionalPush-meta.json b/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testNew_tpush.transactionalPush-meta.json
new file mode 100644
index 00000000..e3c0301c
--- /dev/null
+++ b/test/mockRoot/deploy/testInstance/testBU/transactionalPush/testNew_tpush.transactionalPush-meta.json
@@ -0,0 +1,18 @@
+{
+    "definitionKey": "testNew_tpush",
+    "name": "testNew_tpush",
+    "status": "Active",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "description": "created on deploy; note that applicationId can only be manually set up in Setup - Mobile Push",
+    "content": {
+        "customerKey": "mobileMessage_test"
+    },
+    "options": {
+        "sound": "temp.wmv",
+        "badge": "1",
+        "customKeys": [
+            { "key": "key1", "value": "value1" },
+            { "key": "key2", "value": "value2" }
+        ]
+    }
+}
diff --git a/test/resources/9999999/asset/v1/content/assets/query/post-response.json b/test/resources/9999999/asset/v1/content/assets/query/post-response.json
index e80d2232..7bb8d6ea 100644
--- a/test/resources/9999999/asset/v1/content/assets/query/post-response.json
+++ b/test/resources/9999999/asset/v1/content/assets/query/post-response.json
@@ -1,5 +1,5 @@
 {
-    "count": 1,
+    "count": 2,
     "page": 1,
     "pageSize": 50,
     "links": {},
@@ -43,6 +43,30 @@
                 "legacyCategoryId": 290835
             },
             "modelVersion": 2
+        },
+        {
+            "id": 1209971,
+            "customerKey": "mobileMessage_test",
+            "assetType": { "id": 230, "name": "jsonmessage", "displayName": "JSON Message" },
+            "name": "mobileMessage_test",
+            "createdDate": "2022-12-07T02:49:36.857-06:00",
+            "createdBy": {
+                "id": 700301950,
+                "email": "joern.berkefeld@accenture.com",
+                "name": "J├Ârn Berkefeld (ASGR)",
+                "userId": "700301950"
+            },
+            "modifiedDate": "2022-12-07T02:49:57.44-06:00",
+            "modifiedBy": {
+                "id": 700301950,
+                "email": "joern.berkefeld@accenture.com",
+                "name": "J├Ârn Berkefeld (ASGR)",
+                "userId": "700301950"
+            },
+            "status": { "id": 1, "name": "Draft" },
+            "category": { "id": 283608, "name": "Content Builder", "parentId": 0 },
+            "availableViews": ["push"],
+            "modelVersion": 2
         }
     ]
 }
diff --git a/test/resources/9999999/messaging/v1/push/definitions/get-response.json b/test/resources/9999999/messaging/v1/push/definitions/get-response.json
new file mode 100644
index 00000000..2ee2ff92
--- /dev/null
+++ b/test/resources/9999999/messaging/v1/push/definitions/get-response.json
@@ -0,0 +1,15 @@
+{
+    "requestId": "4a89b42d-9e71-4ddd-9b4e-a3b00edbfdfb",
+    "definitions": [
+        {
+            "name": "testExisting_tpush",
+            "definitionKey": "testExisting_tpush",
+            "status": "Active",
+            "createdDate": "2022-12-07T02:56:00",
+            "modifiedDate": "2022-12-07T02:56:00"
+        }
+    ],
+    "count": 1,
+    "page": 1,
+    "pageSize": 50
+}
diff --git a/test/resources/9999999/messaging/v1/push/definitions/post-response.json b/test/resources/9999999/messaging/v1/push/definitions/post-response.json
new file mode 100644
index 00000000..fcb7834c
--- /dev/null
+++ b/test/resources/9999999/messaging/v1/push/definitions/post-response.json
@@ -0,0 +1,13 @@
+{
+    "requestId": "ee81df03-ab61-454d-a54a-025675cf2b41",
+    "name": "testNew_tpush",
+    "definitionKey": "testNew_tpush",
+    "definitionId": "2106d3f9-0c76-ed11-b849-48df37d1de8b",
+    "description": "created on deploy; note that applicationId can only be manually set up in Setup - Mobile Push",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "status": "Active",
+    "createdDate": "2022-12-07T02:56:00",
+    "modifiedDate": "2022-12-07T03:26:00",
+    "content": { "customerKey": "mobileMessage_test" },
+    "options": { "sound": "temp.wmv", "badge": "1" }
+}
diff --git a/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/get-response.json b/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/get-response.json
new file mode 100644
index 00000000..bb588cc0
--- /dev/null
+++ b/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/get-response.json
@@ -0,0 +1,13 @@
+{
+    "requestId": "64dac58f-f6d0-4bea-9489-eb058c491a87",
+    "name": "testExisting_tpush",
+    "definitionKey": "testExisting_tpush",
+    "definitionId": "2106d3f9-0c76-ed11-b849-48df37d1de8b",
+    "description": "bla bla. note that applicationId can only be manually set up in Setup - Mobile Push",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "status": "Active",
+    "createdDate": "2022-12-07T02:56:00",
+    "modifiedDate": "2022-12-07T02:56:00",
+    "content": { "customerKey": "mobileMessage_test" },
+    "options": { "sound": "temp.wmv", "badge": "1" }
+}
diff --git a/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/patch-response.json b/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/patch-response.json
new file mode 100644
index 00000000..8565f8f9
--- /dev/null
+++ b/test/resources/9999999/messaging/v1/push/definitions/testExisting_tpush/patch-response.json
@@ -0,0 +1,13 @@
+{
+    "requestId": "6d796b32-dff3-4564-ae36-f51edb0e5ec6",
+    "name": "testExisting_tpush",
+    "definitionKey": "testExisting_tpush",
+    "definitionId": "2106d3f9-0c76-ed11-b849-48df37d1de8b",
+    "description": "updated via deploy; note that applicationId can only be manually set up in Setup - Mobile Push",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "status": "Active",
+    "createdDate": "2022-12-07T02:56:00",
+    "modifiedDate": "2022-12-07T03:26:00",
+    "content": { "customerKey": "mobileMessage_test" },
+    "options": { "sound": "temp.wmv", "badge": "1" }
+}
diff --git a/test/resources/9999999/transactionalPush/build-expected.json b/test/resources/9999999/transactionalPush/build-expected.json
new file mode 100644
index 00000000..89036d8a
--- /dev/null
+++ b/test/resources/9999999/transactionalPush/build-expected.json
@@ -0,0 +1,8 @@
+{
+    "name": "testExisting_tpush",
+    "definitionKey": "testExisting_tpush",
+    "description": "foobar. note that applicationId can only be manually set up in Setup - Mobile Push",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "content": { "customerKey": "mobileMessage_testTarget" },
+    "options": { "sound": "temp.wmv", "badge": "1" }
+}
diff --git a/test/resources/9999999/transactionalPush/get-expected.json b/test/resources/9999999/transactionalPush/get-expected.json
new file mode 100644
index 00000000..242a9a80
--- /dev/null
+++ b/test/resources/9999999/transactionalPush/get-expected.json
@@ -0,0 +1,11 @@
+{
+    "name": "testExisting_tpush",
+    "definitionKey": "testExisting_tpush",
+    "description": "bla bla. note that applicationId can only be manually set up in Setup - Mobile Push",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "status": "Active",
+    "createdDate": "2022-12-07T02:56:00",
+    "modifiedDate": "2022-12-07T02:56:00",
+    "content": { "customerKey": "mobileMessage_test" },
+    "options": { "sound": "temp.wmv", "badge": "1" }
+}
diff --git a/test/resources/9999999/transactionalPush/patch-expected.json b/test/resources/9999999/transactionalPush/patch-expected.json
new file mode 100644
index 00000000..2cf1ecd9
--- /dev/null
+++ b/test/resources/9999999/transactionalPush/patch-expected.json
@@ -0,0 +1,16 @@
+{
+    "definitionKey": "testExisting_tpush",
+    "name": "testExisting_tpush",
+    "status": "Active",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "description": "updated via deploy; note that applicationId can only be manually set up in Setup - Mobile Push",
+    "createdDate": "2022-12-07T02:56:00",
+    "modifiedDate": "2022-12-07T03:26:00",
+    "content": {
+        "customerKey": "mobileMessage_test"
+    },
+    "options": {
+        "sound": "temp.wmv",
+        "badge": "1"
+    }
+}
diff --git a/test/resources/9999999/transactionalPush/post-expected.json b/test/resources/9999999/transactionalPush/post-expected.json
new file mode 100644
index 00000000..7f87af35
--- /dev/null
+++ b/test/resources/9999999/transactionalPush/post-expected.json
@@ -0,0 +1,16 @@
+{
+    "definitionKey": "testNew_tpush",
+    "name": "testNew_tpush",
+    "status": "Active",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "description": "created on deploy; note that applicationId can only be manually set up in Setup - Mobile Push",
+    "createdDate": "2022-12-07T02:56:00",
+    "modifiedDate": "2022-12-07T03:26:00",
+    "content": {
+        "customerKey": "mobileMessage_test"
+    },
+    "options": {
+        "sound": "temp.wmv",
+        "badge": "1"
+    }
+}
diff --git a/test/resources/9999999/transactionalPush/template-expected.json b/test/resources/9999999/transactionalPush/template-expected.json
new file mode 100644
index 00000000..26266ff3
--- /dev/null
+++ b/test/resources/9999999/transactionalPush/template-expected.json
@@ -0,0 +1,8 @@
+{
+    "name": "testExisting_tpush",
+    "definitionKey": "testExisting_tpush",
+    "description": "{{{description}}}. note that applicationId can only be manually set up in Setup - Mobile Push",
+    "applicationId": "ffbab4c9-fbf6-4b87-9c9a-10c6ce86e81d",
+    "content": { "customerKey": "mobileMessage{{{suffix}}}" },
+    "options": { "sound": "temp.wmv", "badge": "1" }
+}
diff --git a/test/transactionalPush.test.js b/test/transactionalPush.test.js
new file mode 100644
index 00000000..4fa77056
--- /dev/null
+++ b/test/transactionalPush.test.js
@@ -0,0 +1,120 @@
+const chai = require('chai');
+const chaiFiles = require('chai-files');
+const assert = chai.assert;
+chai.use(chaiFiles);
+const cache = require('../lib/util/cache');
+const testUtils = require('./utils');
+const handler = require('../lib/index');
+
+describe('transactionalPush', () => {
+    beforeEach(() => {
+        testUtils.mockSetup();
+    });
+    afterEach(() => {
+        testUtils.mockReset();
+    });
+
+    describe('Retrieve ================', () => {
+        it('Should retrieve a transactionalPush', async () => {
+            // WHEN
+            await handler.retrieve('testInstance/testBU', ['transactionalPush']);
+            // THEN
+            // get results from cache
+            const result = cache.getCache();
+            assert.equal(
+                result.transactionalPush ? Object.keys(result.transactionalPush).length : 0,
+                1,
+                'only one transactionalPush expected'
+            );
+            assert.deepEqual(
+                await testUtils.getActualJson('testExisting_tpush', 'transactionalPush'),
+                await testUtils.getExpectedJson('9999999', 'transactionalPush', 'get'),
+                'returned JSON was not equal expected'
+            );
+            assert.equal(
+                Object.values(testUtils.getAPIHistory()).flat().length,
+                3,
+                'Unexpected number of requests made'
+            );
+            return;
+        });
+    });
+    describe('Deploy ================', () => {
+        beforeEach(() => {
+            testUtils.mockSetup(true);
+        });
+        it('Should create & upsert a transactionalPush', async () => {
+            // WHEN
+            await handler.deploy('testInstance/testBU', ['transactionalPush']);
+            // THEN
+            // get results from cache
+            const result = cache.getCache();
+            assert.equal(
+                result.transactionalPush ? Object.keys(result.transactionalPush).length : 0,
+                2,
+                'two transactionalPushs expected'
+            );
+            // confirm created item
+            assert.deepEqual(
+                await testUtils.getActualJson('testNew_tpush', 'transactionalPush'),
+                await testUtils.getExpectedJson('9999999', 'transactionalPush', 'post'),
+                'returned JSON was not equal expected for insert transactionalPush'
+            );
+            // confirm updated item
+            assert.deepEqual(
+                await testUtils.getActualJson('testExisting_tpush', 'transactionalPush'),
+                await testUtils.getExpectedJson('9999999', 'transactionalPush', 'patch'),
+                'returned JSON was not equal expected for update transactionalPush'
+            );
+            // check number of API calls
+            assert.equal(
+                Object.values(testUtils.getAPIHistory()).flat().length,
+                4,
+                'Unexpected number of requests made'
+            );
+            return;
+        });
+    });
+    describe('Templating ================', () => {
+        // it.skip('Should create a transactionalPush template via retrieveAsTemplate and build it');
+        it('Should create a transactionalPush template via buildTemplate and build it', async () => {
+            // download first before we test buildTemplate
+            await handler.retrieve('testInstance/testBU', ['transactionalPush']);
+            // buildTemplate
+            const result = await handler.buildTemplate(
+                'testInstance/testBU',
+                'transactionalPush',
+                ['testExisting_tpush'],
+                'testSourceMarket'
+            );
+            assert.equal(
+                result.transactionalPush ? Object.keys(result.transactionalPush).length : 0,
+                1,
+                'only one transactionalPush expected'
+            );
+            assert.deepEqual(
+                await testUtils.getActualTemplateJson('testExisting_tpush', 'transactionalPush'),
+                await testUtils.getExpectedJson('9999999', 'transactionalPush', 'template'),
+                'returned template JSON was not equal expected'
+            );
+            // buildDefinition
+            await handler.buildDefinition(
+                'testInstance/testBU',
+                'transactionalPush',
+                'testExisting_tpush',
+                'testTargetMarket'
+            );
+            assert.deepEqual(
+                await testUtils.getActualDeployJson('testExisting_tpush', 'transactionalPush'),
+                await testUtils.getExpectedJson('9999999', 'transactionalPush', 'build'),
+                'returned deployment JSON was not equal expected'
+            );
+            assert.equal(
+                Object.values(testUtils.getAPIHistory()).flat().length,
+                3,
+                'Unexpected number of requests made'
+            );
+            return;
+        });
+    });
+});