twilio-labs / serverless-toolkit

CLI tool to develop, debug and deploy Twilio Functions

Home Page:https://www.twilio.com/docs/labs/serverless-toolkit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Better way to intercept Twilio module

dkundel opened this issue · comments

I was trying to see how this guide would fit together with the Serverless Toolkit and realized while it works there is some hackery around it.

Essentially the only way you can get this to work without modifying your source code for Functions is by using the LocalDevelopmentServer from @twilio/runtime-handler/dev and initializing it with a basePath that contains a node_modules/twilio/index.js file that contains code similar to this:

const actualTwilio = require('../../../../node_modules/twilio/index');
const { MockHttpClient } = require('../../MockHttpClient');

function interceptedTwilio(username, password, options) {
  const client = actualTwilio(username, password, {...options, httpClient: new MockHttpClient()})
  return client;
}

function fakeTwilio(...args) {
  return interceptedTwilio(...args);
}

fakeTwilio.twiml = actualTwilio.twiml;
fakeTwilio.jwt = actualTwilio.jwt;
fakeTwilio.RequestClient = actualTwilio.RequestClient;
fakeTwilio.Twilio = interceptedTwilio;

module.exports = fakeTwilio;

and then have a MockHttpClient class such as this one:

const { RequestClient } = require('../../node_modules/twilio/index');

class MockHttpClient {
  constructor() {
    this.requestClient = new RequestClient();
  }

  async request(opts) {
    opts.uri = opts.uri.replace(
      `https://sync.twilio.com`,
      'http://localhost:4001'
    );
    opts.uri = opts.uri.replace(
      `https://api.twilio.com`,
      'http://localhost:4002'
    );
    const result = await this.requestClient.request(opts);
    if (result.body.meta) {
      result.body.meta.next_page_url = null;
      result.body.meta.previous_page_url = null;
      result.body.meta.key = Object.keys(result.body)[0];
    }

    return result;
  }
}

exports.MockHttpClient = MockHttpClient;

I think while this is certainly a power-user feature we could make it at least a bit more accessible by making it easier to swap out the Twilio client that is being used. I don't think we need to offer a full mocking capability for npm module since that's what you should use jest or similar tests for but it would be nice to make this behavior easier.

There are three options in my opinion.

Option 1: The most flexible and verbose option

Provide a way to pass your own twilio module into the LocalDevelopmentServer. So rather than doing the requireFromProject() calls everywhere, we'd allow to pass the Twilio Node library as an object into LocalDevelopmentServer.

The problem with this one could be around the process forking and how to pass around the library in this case. A workaround could be to provide a path to a file instead.

Option 2: The middle ground

Add support to the LocalDevelopmentServer config object a new property called twilioDefaultOptions that can be used to provide any of the options that you'd pass otherwise to the twilio constructor call. This includes the httpClient property but also other properties such as edge, region, logLevel or userAgentExtensions.

The main benefit of this one is that we could probably re-use this in some places ourselves as part of the Toolkit but I foresee some issues around process forking again when passing a class for exampl ein the case of httpClient. Hence this wouldn't actually solve this problem when process forking is turned on but that might be fine.

Option 3: The least flexible way

In this situation we'd allow LocalDevelopmentServer to receive a requestRewriteMap that would look something like this:

	requestRewriteMap: {
		'https://sync.twilio.com': 'http://localhost:4001',
		'https://api.twilio.com': 'http://localhost:4002',
	}

And then if we detect that we inject our own MockHTTPServer similar to the one above.

This is by far the most inflexible one but would work with process forking and would require the least amount of set up from the user.