Repository: Yoctol/bottender
Pull request #209 (2018-03-16 10:54:54)

implement getUserProfile and sendSenderAction
```js
const user = await context.getUserProfile();
await context.sendSenderAction('typing_on');
```

tw0517tw: failed because of this
https://github.com/nodejs/docker-node/issues/649

Patch:
diff --git a/src/context/MessengerContext.js b/src/context/MessengerContext.js
index 6e28027b..7e53c8e8 100644
--- a/src/context/MessengerContext.js
+++ b/src/context/MessengerContext.js
@@ -89,6 +89,48 @@ class MessengerContext extends Context implements PlatformContext {
    * https://github.com/Yoctol/messaging-apis/tree/master/packages/messaging-api-messenger#sender-actions
    */
 
+  /**
+   * https://github.com/Yoctol/messaging-apis/tree/master/packages/messaging-api-messenger#typingonuserid
+   */
+  async getUserProfile(): Promise<?Object> {
+    if (!this._session) {
+      warning(
+        false,
+        'getUserProfile: should not be called in context without session'
+      );
+      return null;
+    }
+
+    return this._client.getUserProfile(this._session.user.id, {
+      access_token: this._customAccessToken,
+    });
+  }
+
+  /**
+   * Sender Actions
+   *
+   * https://github.com/Yoctol/messaging-apis/tree/master/packages/messaging-api-messenger#sender-actions
+   */
+
+  /**
+   * https://github.com/Yoctol/messaging-apis/tree/master/packages/messaging-api-messenger#sendsenderactionuserid-action
+   */
+  async sendSenderAction(action: string): Promise<any> {
+    if (!this._session) {
+      warning(
+        false,
+        'sendSenderAction: should not be called in context without session'
+      );
+      return;
+    }
+
+    this._isHandled = true;
+
+    return this._client.sendSenderAction(this._session.user.id, action, {
+      access_token: this._customAccessToken,
+    });
+  }
+
   /**
    * https://github.com/Yoctol/messaging-apis/tree/master/packages/messaging-api-messenger#typingonuserid
    */
@@ -182,7 +224,7 @@ class MessengerContext extends Context implements PlatformContext {
   /**
    * https://github.com/Yoctol/messaging-apis/tree/master/packages/messaging-api-messenger#passthreadcontroltopageinboxuserid-metadata---official-docs
    */
-  async passThreadControlToPageInbox(): Promise<any> {
+  async passThreadControlToPageInbox(metadata?: string): Promise<any> {
     if (!this._session) {
       warning(
         false,
@@ -193,15 +235,19 @@ class MessengerContext extends Context implements PlatformContext {
 
     this._isHandled = true;
 
-    return this._client.passThreadControlToPageInbox(this._session.user.id, {
-      access_token: this._customAccessToken,
-    });
+    return this._client.passThreadControlToPageInbox(
+      this._session.user.id,
+      metadata,
+      {
+        access_token: this._customAccessToken,
+      }
+    );
   }
 
   /**
    * https://github.com/Yoctol/messaging-apis/tree/master/packages/messaging-api-messenger#takethreadcontroluserid-metadata---official-docs
    */
-  async takeThreadControl(): Promise<any> {
+  async takeThreadControl(metadata?: string): Promise<any> {
     if (!this._session) {
       warning(
         false,
@@ -212,7 +258,7 @@ class MessengerContext extends Context implements PlatformContext {
 
     this._isHandled = true;
 
-    return this._client.takeThreadControl(this._session.user.id, {
+    return this._client.takeThreadControl(this._session.user.id, metadata, {
       access_token: this._customAccessToken,
     });
   }
@@ -220,7 +266,7 @@ class MessengerContext extends Context implements PlatformContext {
   /**
    * https://github.com/Yoctol/messaging-apis/blob/master/packages/messaging-api-messenger/README.md#requestthreadcontroluserid-metadata---official-docs
    */
-  async requestThreadControl(): Promise<any> {
+  async requestThreadControl(metadata?: string): Promise<any> {
     if (!this._session) {
       warning(
         false,
@@ -231,7 +277,7 @@ class MessengerContext extends Context implements PlatformContext {
 
     this._isHandled = true;
 
-    return this._client.requestThreadControl(this._session.user.id, {
+    return this._client.requestThreadControl(this._session.user.id, metadata, {
       access_token: this._customAccessToken,
     });
   }


Test patch:
diff --git a/src/context/__tests__/MessengerContext.spec.js b/src/context/__tests__/MessengerContext.spec.js
index a6545582..b3758d37 100644
--- a/src/context/__tests__/MessengerContext.spec.js
+++ b/src/context/__tests__/MessengerContext.spec.js
@@ -21,6 +21,8 @@ afterEach(() => {
 });
 
 const createMockGraphAPIClient = () => ({
+  getUserProfile: jest.fn(),
+  sendSenderAction: jest.fn(),
   typingOn: jest.fn(),
   typingOff: jest.fn(),
   markSeen: jest.fn(),
@@ -69,6 +71,7 @@ const userSession = {
     id: 'fakeUserId',
   },
 };
+
 const setup = (
   { session, customAccessToken } = {
     session: userSession,
@@ -721,6 +724,114 @@ describe('#sendAirlineFlightUpdateTemplate', () => {
   });
 });
 
+describe('#getUserProfile', () => {
+  it('should call client sendSenderAction', async () => {
+    const { context, client, session } = setup();
+
+    const user = {
+      first_name: 'Kevin',
+      last_name: 'Durant',
+      profile_pic: 'https://example.com/pic.png',
+      locale: 'en_US',
+      timezone: 8,
+      gender: 'male',
+    };
+
+    client.getUserProfile.mockReturnValue(Promise.resolve(user));
+
+    const result = await context.getUserProfile();
+
+    expect(client.getUserProfile).toBeCalledWith(session.user.id, {
+      access_token: undefined,
+    });
+    expect(result).toEqual(user);
+  });
+
+  it('should use custom access token', async () => {
+    const { context, client, session } = setup({
+      session: userSession,
+      customAccessToken: 'anyToken',
+    });
+
+    const user = {
+      first_name: 'Kevin',
+      last_name: 'Durant',
+      profile_pic: 'https://example.com/pic.png',
+      locale: 'en_US',
+      timezone: 8,
+      gender: 'male',
+    };
+
+    client.getUserProfile.mockReturnValue(Promise.resolve(user));
+
+    const result = await context.getUserProfile();
+
+    expect(client.getUserProfile).toBeCalledWith(session.user.id, {
+      access_token: 'anyToken',
+    });
+    expect(result).toEqual(user);
+  });
+
+  it('should call warning and not to send if dont have session', async () => {
+    const { context, client } = setup({ session: false });
+
+    await context.getUserProfile();
+
+    expect(warning).toBeCalled();
+    expect(client.getUserProfile).not.toBeCalled();
+  });
+});
+
+describe('#sendSenderAction', () => {
+  it('should call client sendSenderAction', async () => {
+    const { context, client, session } = setup();
+
+    await context.sendSenderAction('typing_on');
+
+    expect(client.sendSenderAction).toBeCalledWith(
+      session.user.id,
+      'typing_on',
+      {
+        access_token: undefined,
+      }
+    );
+  });
+
+  it('should use custom access token', async () => {
+    const { context, client, session } = setup({
+      session: userSession,
+      customAccessToken: 'anyToken',
+    });
+
+    await context.sendSenderAction('typing_on');
+
+    expect(client.sendSenderAction).toBeCalledWith(
+      session.user.id,
+      'typing_on',
+      {
+        access_token: 'anyToken',
+      }
+    );
+  });
+
+  it('should mark context as handled', async () => {
+    const { context } = setup();
+
+    await context.sendSenderAction('typing_on');
+
+    expect(context.isHandled).toBe(true);
+  });
+
+  it('should call warning and not to send if dont have session', async () => {
+    const { context, client } = setup({ session: false });
+
+    await context.sendSenderAction('typing_on');
+
+    expect(warning).toBeCalled();
+    expect(client.sendSenderAction).not.toBeCalled();
+  });
+});
+
 describe('#typingOn', () => {
   it('should call client typingOn', async () => {
     const { context, client, session } = setup();
@@ -752,6 +863,7 @@ describe('#typingOn', () => {
 
     expect(context.isHandled).toBe(true);
   });
+
   it('should call warning and not to send if dont have session', async () => {
     const { context, client } = setup({ session: false });
 
@@ -903,7 +1015,7 @@ describe('#passThreadControl', () => {
   it('should call warning if dont have session', async () => {
     const { context, client } = setup({ session: false });
 
-    await context.passThreadControl(263902037430900, 'metadata');
+    await context.passThreadControl(263902037430900);
 
     expect(warning).toBeCalled();
     expect(client.passThreadControl).not.toBeCalled();
@@ -914,10 +1026,11 @@ describe('#passThreadControlToPageInbox', () => {
   it('should call to pass user thread control to page inbox', async () => {
     const { context, client, session } = setup();
 
-    await context.passThreadControlToPageInbox();
+    await context.passThreadControlToPageInbox('metadata');
 
     expect(client.passThreadControlToPageInbox).toBeCalledWith(
       session.user.id,
+      'metadata',
       {
         access_token: undefined,
       }
@@ -934,6 +1047,7 @@ describe('#passThreadControlToPageInbox', () => {
 
     expect(client.passThreadControlToPageInbox).toBeCalledWith(
       session.user.id,
+      undefined,
       {
         access_token: 'anyToken',
       }
@@ -954,11 +1068,15 @@ describe('#takeThreadControl', () => {
   it('should call to take user thread control back', async () => {
     const { context, client, session } = setup();
 
-    await context.takeThreadControl();
+    await context.takeThreadControl('metadata');
 
-    expect(client.takeThreadControl).toBeCalledWith(session.user.id, {
-      access_token: undefined,
-    });
+    expect(client.takeThreadControl).toBeCalledWith(
+      session.user.id,
+      'metadata',
+      {
+        access_token: undefined,
+      }
+    );
   });
 
   it('should call warning if dont have session', async () => {
@@ -975,11 +1093,15 @@ describe('#requestThreadControl', () => {
   it('should call to request user thread control', async () => {
     const { context, client, session } = setup();
 
-    await context.requestThreadControl();
+    await context.requestThreadControl('metadata');
 
-    expect(client.requestThreadControl).toBeCalledWith(session.user.id, {
-      access_token: undefined,
-    });
+    expect(client.requestThreadControl).toBeCalledWith(
+      session.user.id,
+      'metadata',
+      {
+        access_token: undefined,
+      }
+    );
   });
 
   it('should call warning if dont have session', async () => {