Repository: Yoctol/bottender (ts)

Issue:
whitelisted_domains must be set before home_url
If set `whitelisted_domains` and `home_url` at the same time using one CLI command, Facebook will throw error. Must set `whitelisted_domains` first and then with `home_url` the 2nd CLI call.
```
> bottender messenger profile set
× Failed to set messenger_profile settings
× status: 400
× data: {
  "error": {
    "message": "(#100) The web URL provided is not whitelisted for messenger extensions",
    "type": "OAuthException",
    "code": 100,
    "error_subcode": 2018063,
    "fbtrace_id": "A9EtushnqDU"
  }
}
```

Pull request description:
Fix profile set domain whitelisting usage
Should fix #90 

Patch:
diff --git a/src/cli/providers/messenger/profile.js b/src/cli/providers/messenger/profile.js
index ae501d41..c84090a1 100644
--- a/src/cli/providers/messenger/profile.js
+++ b/src/cli/providers/messenger/profile.js
@@ -3,6 +3,7 @@ import { MessengerClient } from 'messaging-api-messenger';
 import invariant from 'invariant';
 import { diff, addedDiff, deletedDiff, updatedDiff } from 'deep-object-diff';
 import chalk from 'chalk';
+import { pick, omit } from 'lodash';
 
 import getConfig from '../../shared/getConfig';
 import { print, error, bold, log } from '../../shared/log';
@@ -116,8 +117,12 @@ export async function setMessengerProfile(ctx) {
           'messenger_profile'
         )} settings due to ${bold('--force')} option`
       );
-
-      await client.setMessengerProfile(_profile);
+      if (_profile.whitelisted_domains) {
+        await client.setMessengerProfile(pick(_profile, 'whitelisted_domains'));
+        await client.setMessengerProfile(omit(_profile, 'whitelisted_domains'));
+      } else {
+        await client.setMessengerProfile(_profile);
+      }
       print(`Successfully set ${bold('messenger_profile')} settings`);
     } else {
       const [_existedProfile] = await client.getMessengerProfile(FIELDS);
@@ -144,11 +149,18 @@ export async function setMessengerProfile(ctx) {
         }
 
         if (shouldSetFields.length > 0) {
-          const shouldSetProfile = {};
-          shouldSetFields.forEach(field => {
-            shouldSetProfile[field] = profile[field];
-          });
-          await client.setMessengerProfile(shouldSetProfile);
+          const shouldSetProfile = pick(profile, shouldSetFields);
+
+          if (shouldSetFields.includes('whitelisted_domains')) {
+            await client.setMessengerProfile(
+              pick(shouldSetProfile, 'whitelisted_domains')
+            );
+            await client.setMessengerProfile(
+              omit(shouldSetProfile, 'whitelisted_domains')
+            );
+          } else {
+            await client.setMessengerProfile(shouldSetProfile);
+          }
           const setFields = Object.keys(shouldSetProfile).join(', ');
           print(`Successfully set ${bold(setFields)} settings`);
         }


Test patch:
diff --git a/src/cli/providers/messenger/__tests__/setMessengerProfile.spec.js b/src/cli/providers/messenger/__tests__/setMessengerProfile.spec.js
index 22a78d44..e033e35b 100644
--- a/src/cli/providers/messenger/__tests__/setMessengerProfile.spec.js
+++ b/src/cli/providers/messenger/__tests__/setMessengerProfile.spec.js
@@ -78,21 +78,187 @@ it('be defined', () => {
 });
 
 describe('resolve', () => {
-  it('--fource update will delete all fields and set profile', async () => {
+  describe('--force', () => {
+    it('will delete all fields and set profile', async () => {
+      const ctx = {
+        argv: { force: true },
+      };
+      await setMessengerProfile(ctx);
+      expect(_client.deleteMessengerProfile).toBeCalledWith([
+        'account_linking_url',
+        'persistent_menu',
+        'get_started',
+        'greeting',
+        'whitelisted_domains',
+        'payment_settings',
+        'target_audience',
+        'home_url',
+      ]);
+      expect(_client.setMessengerProfile).toBeCalledWith({
+        get_started: {
+          payload: '<GET_STARTED_PAYLOAD>',
+        },
+        persistent_menu: [
+          {
+            locale: 'default',
+            composer_input_disabled: false,
+            call_to_actions: [
+              {
+                type: 'web_url',
+                title: 'Powered by Example',
+                url: 'https://www.example.com/',
+              },
+            ],
+          },
+        ],
+      });
+    });
+
+    it('should set whole profile once', async () => {
+      const ctx = {
+        argv: {
+          force: true,
+        },
+      };
+      getConfig.mockReturnValue({
+        accessToken: '__FAKE_TOKEN__',
+        profile: {
+          get_started: {
+            payload: '<GET_STARTED_PAYLOAD>',
+          },
+          persistent_menu: [
+            {
+              locale: 'default',
+              composer_input_disabled: false,
+              call_to_actions: [
+                {
+                  type: 'web_url',
+                  title: 'Open Chat Extensions',
+                  url: 'http://example.com/index.html',
+                  messenger_extensions: true,
+                },
+              ],
+            },
+          ],
+        },
+      });
+
+      await setMessengerProfile(ctx);
+
+      expect(_client.setMessengerProfile).toBeCalledWith({
+        get_started: {
+          payload: '<GET_STARTED_PAYLOAD>',
+        },
+        persistent_menu: [
+          {
+            locale: 'default',
+            composer_input_disabled: false,
+            call_to_actions: [
+              {
+                type: 'web_url',
+                title: 'Open Chat Extensions',
+                url: 'http://example.com/index.html',
+                messenger_extensions: true,
+              },
+            ],
+          },
+        ],
+      });
+    });
+
+    it('should set whitelisted_domains before other fields', async () => {
+      const ctx = {
+        argv: {
+          force: true,
+        },
+      };
+      getConfig.mockReturnValue({
+        accessToken: '__FAKE_TOKEN__',
+        profile: {
+          get_started: {
+            payload: '<GET_STARTED_PAYLOAD>',
+          },
+          persistent_menu: [
+            {
+              locale: 'default',
+              composer_input_disabled: false,
+              call_to_actions: [
+                {
+                  type: 'web_url',
+                  title: 'Open Chat Extensions',
+                  url: 'http://example.com/index.html',
+                  messenger_extensions: true,
+                },
+              ],
+            },
+          ],
+          whitelisted_domains: ['http://example.com'], // profile has `whitelisted_domains`
+          home_url: {
+            url: 'http://example.com/index.html',
+            webview_height_ratio: 'tall',
+            webview_share_button: 'show',
+            in_test: true,
+          },
+        },
+      });
+
+      await setMessengerProfile(ctx);
+
+      expect(_client.setMessengerProfile.mock.calls[0][0]).toEqual({
+        whitelisted_domains: ['http://example.com'],
+      });
+      expect(_client.setMessengerProfile.mock.calls[1][0]).toEqual({
+        get_started: {
+          payload: '<GET_STARTED_PAYLOAD>',
+        },
+        persistent_menu: [
+          {
+            locale: 'default',
+            composer_input_disabled: false,
+            call_to_actions: [
+              {
+                type: 'web_url',
+                title: 'Open Chat Extensions',
+                url: 'http://example.com/index.html',
+                messenger_extensions: true,
+              },
+            ],
+          },
+        ],
+        home_url: {
+          url: 'http://example.com/index.html',
+          webview_height_ratio: 'tall',
+          webview_share_button: 'show',
+          in_test: true,
+        },
+      });
+    });
+  });
+
+  it('successfully set diff fields `persistent_menu`', async () => {
     const ctx = {
-      argv: { force: true },
+      argv: {},
     };
-    await setMessengerProfile(ctx);
-    expect(_client.deleteMessengerProfile).toBeCalledWith([
-      'account_linking_url',
-      'persistent_menu',
-      'get_started',
-      'greeting',
-      'whitelisted_domains',
-      'payment_settings',
-      'target_audience',
-      'home_url',
+    _client.getMessengerProfile.mockReturnValue([
+      {
+        persistent_menu: [
+          {
+            locale: 'default',
+            composer_input_disabled: false,
+            call_to_actions: [
+              {
+                type: 'web_url',
+                title: 'Powered by Facebook',
+                url: 'https://www.facebook.com/',
+              },
+            ],
+          },
+        ],
+      },
     ]);
+
+    await setMessengerProfile(ctx);
+
     expect(_client.setMessengerProfile).toBeCalledWith({
       get_started: {
         payload: '<GET_STARTED_PAYLOAD>',
@@ -113,7 +279,7 @@ describe('resolve', () => {
     });
   });
 
-  it('successfully set diff fields `persistent_menu`', async () => {
+  it('should set whitelisted_domains before other fields', async () => {
     const ctx = {
       argv: {},
     };
@@ -134,10 +300,42 @@ describe('resolve', () => {
         ],
       },
     ]);
+    getConfig.mockReturnValue({
+      accessToken: '__FAKE_TOKEN__',
+      profile: {
+        get_started: {
+          payload: '<GET_STARTED_PAYLOAD>',
+        },
+        persistent_menu: [
+          {
+            locale: 'default',
+            composer_input_disabled: false,
+            call_to_actions: [
+              {
+                type: 'web_url',
+                title: 'Open Chat Extensions',
+                url: 'http://example.com/index.html',
+                messenger_extensions: true,
+              },
+            ],
+          },
+        ],
+        whitelisted_domains: ['http://example.com'],
+        home_url: {
+          url: 'http://example.com/index.html',
+          webview_height_ratio: 'tall',
+          webview_share_button: 'show',
+          in_test: true,
+        },
+      },
+    });
 
     await setMessengerProfile(ctx);
 
-    expect(_client.setMessengerProfile).toBeCalledWith({
+    expect(_client.setMessengerProfile.mock.calls[0][0]).toEqual({
+      whitelisted_domains: ['http://example.com'],
+    });
+    expect(_client.setMessengerProfile.mock.calls[1][0]).toEqual({
       get_started: {
         payload: '<GET_STARTED_PAYLOAD>',
       },
@@ -148,12 +346,19 @@ describe('resolve', () => {
           call_to_actions: [
             {
               type: 'web_url',
-              title: 'Powered by Example',
-              url: 'https://www.example.com/',
+              title: 'Open Chat Extensions',
+              url: 'http://example.com/index.html',
+              messenger_extensions: true,
             },
           ],
         },
       ],
+      home_url: {
+        url: 'http://example.com/index.html',
+        webview_height_ratio: 'tall',
+        webview_share_button: 'show',
+        in_test: true,
+      },
     });
   });