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.