Search code examples
node.jshttpprotractorchakram

Undefined response returned by Chakram requests


I'm facing a strange problem in my automated tests written using Protractor. We need to test a bunch of API endpoints that return JSON over HTTP, as opposed to actual websites so instead of relying on Protractor, my team decided to use Chakram.

I have a page object responsible for accessing the API:

const qs = require('querystring')
const chakram = require('chakram');

function MyApi() {

  const domain = // read from a configuration file

  this.readImportantBusinessData = (queryParams) => {
    const serviceUrl = `${domain}/services/seriousBusiness.json`;
    const queryString = qs.stringify(queryParams);
    const fullUrl = `${serviceUrl}?${queryString}`;
    return chakram.get(fullUrl).then((response) => {
      return response;
    });
  };
};

module.exports = new MyApi();

then, in one of my specs, I call the readImportantBusinessData function to check if it returns the expected data.

return MyApi.readImportantBusinessData(validParameters).then((response) => {
        chakramExpect(response).to.have.status(HTTP_200_OK);
        chakramExpect(response).to.have.json({
            "foo" : "bar"
        });
      });

Depending on the enviornment where I run this code, the test passes or fails with an error message that basically means that no response has been received.

Failed: Cannot read property 'statusCode' of undefined

I can confirm that the server I'm hitting is running and I can get a correct response when using a web browser.

The rquest made in my tests succeeds when I use a shared server hosted in AWS and fails when I use a local server running in VirtualBox.

Why could Chakram not recieve a response at all?


Solution

  • The root cause

    The calls to expect just showed undefined but I managed to log the whole response object, as returned by chakram.get

    { error: { Error: self signed certificate
      at TLSSocket.<anonymous> (_tls_wrap.js:1103:38)
      at emitNone (events.js:106:13)
      at TLSSocket.emit (events.js:208:7)
      at TLSSocket.finishInit (_tls_wrap.js:637:8)
      at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:467:38) code: 'DEPTH_ZERO_SELF_SIGNED_CERT' },
      response: undefined,
      body: undefined,
      jar:
           RequestJar {
             jar: CookieJar { enableLooseMode: true, store: { idx: {} } } 
           },
      url: 'https://www.example.com/myService.json?foo=bar',
      responseTime: 29.524212 
    }
    

    This means Chakram had an issue with the self-signed certificate I was using for my local dev environment. The other servers are hosted in the cloud and have their certificates set up properly.

    A workaround

    A quick Google search returned a lot of suggestions to modify a global parameter that drives this behaviour by setting:

    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
    

    However, this affects the whole node process, not just my test. These are just tests that I'm running locally and not something that has to do with actual user data. Still, if I am to make the security of these calls more lax, I need this to limit the scope of such a change as much as possible.

    Thankfully, chakram.get uses the request library which makes it possible to customize requests quite heavily.

    chakram.get allows me to pass:

    params Object optional additional request options, see the popular request library for options

    These options, in turn allow me to specify:

    agentOptions - and pass its options. Note: for HTTPS see tls API doc for TLS/SSL options and the documentation above.

    Finally, in the agentOptions, one can pass a rejectUnauthorized value that allows the certificate error to be ignored for the single request being made.

    Therefore, in my page object, I could use:

    return chakram.get(fullUrl, {
      agentOptions : {
        //Hack to allow requests to the local env with a self-signed cert
        rejectUnauthorized : false
      }
    }).then((response) => {
      return response;
    });
    

    This allows the test to succeed despite using a self-signed certificate.

    The solution

    The solution is to provide a valid certificate for every environment and domain in which case the problem does not exist in the first place.