javascriptnode.jsrestmiddlewareundici

NodeJs fetch failed while using get/post request


I am building a rest API using undici.

I am getting the following error:

TypeError: fetch failed

I have implemented the following code:

const crypto = require('crypto');
const OAuth = require('oauth-1.0a');
const OAuth2 = require('../utils/OAuth2');
const undici = require('undici');
const { cloneDeep, isNil } = require('lodash');
const { log } = require('../config/log');
const { rest } = require('../config/vars');

module.exports.request = async (clientName, method, path, options) => {
  // eslint-disable-next-line security/detect-object-injection
  const config = rest[clientName];
  console.log(config);
  let preparedOptions = await prepareOptions(clientName, method, config.base.prefixUrl + path, options, true);
  console.log(preparedOptions);
  const res = await undici.fetch(config.base.prefixUrl, preparedOptions)
    .catch(async (err) => {
      if (!isNil(err.response) && (err.response.statusCode === 401 || err.response.statusCode === 403)) {
        preparedOptions = await prepareOptions(clientName, method, config.base.prefixUrl + path, options, true);
        return undici.request(config.base.prefixUrl, preparedOptions);
      }
      throw err;
    });
  return res;
};

module.exports.get = async (clientName, path, options = {}) => {
  return module.exports.request(clientName, 'GET', path, options);
};

module.exports.post = async (clientName, path, body, options = {}) => {
  options.body = body;
  return module.exports.request(clientName, 'POST', path, options);
};

async function prepareOptions(clientName, method, url, options, forceRefresh = false) {
  const clientConfig = rest[clientName]; // eslint-disable-line security/detect-object-injection
  const defaultOption = isNil(options) ? {} : cloneDeep(options);
  const prepareOption = Object.assign(defaultOption, clientConfig.option);
  const config = rest[clientName]; // eslint-disable-line security/detect-object-injection

  prepareOption.method = method;

  if (!isNil(config.OAuth2)) {
    const token = await OAuth2.getToken(clientName, config.OAuth2, forceRefresh);

    if (isNil(prepareOption['headers'])) { // eslint-disable-line dot-notation
      prepareOption['headers'] = {}; // eslint-disable-line dot-notation
    }
    prepareOption['headers'].authorization = `Bearer ${ token }`; // eslint-disable-line dot-notation
  } else if (!isNil(config.OAuth1)) {
    const oauth = OAuth({
      consumer: {
        key: config.OAuth1.key,
        secret: config.OAuth1.secret,
      },
      signature_method: config.OAuth1.signatureMethod, // 'HMAC-SHA1',
      hash_function: (baseString, key) => {
        return crypto.createHmac(getHmacAlgorithm(config.OAuth1.signatureMethod), key)
          .update(baseString)
          .digest('base64');
      },
    });

    prepareOption['headers'] = oauth.toHeader(oauth.authorize({ // eslint-disable-line dot-notation
      url,
      method,
    }));
  } else if (!isNil(config.BasicAuth)) {
    const basicAuth = Buffer.from(`${ config.BasicAuth.username }:${ config.BasicAuth.password }`)
      .toString('base64');
    if (isNil(prepareOption['headers'])) { // eslint-disable-line dot-notation
      prepareOption['headers'] = {}; // eslint-disable-line dot-notation
    }
    prepareOption['headers'].authorization = `Basic ${ basicAuth }`; // eslint-disable-line dot-notation
  }

  return prepareOption;
}

I have implemented the following test:

const APIError = require('../utils/APIError');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
const { MockAgent, setGlobalDispatcher } = require('undici');

chai.use(chaiAsPromised);
const { assert } = chai; // eslint-disable-line no-shadow

const agent = new MockAgent();
agent.disableNetConnect();
setGlobalDispatcher(agent);

describe('REST', () => {
  after(() => {
    agent.close();
  });

  describe('REST pass', () => {
    it('200', async () => {
      const mockAgent = agent.get('https://test.ca');
      mockAgent
        .intercept({
          path: '/test',
          method: 'GET',
        })
        .reply(200, {
          data: 'test',
        });
      const result = await rest.get('test', 'test');
      // assert.equal(result.statusCode, 200);
      assert.deepEqual(result.body, { data: 'test' });
      agent.assertNoPendingInterceptors();
    });
  });
});
console.log(config)

{
base: { prefixUrl: 'https://test.ca'}, option: {path: '/'}
}
console.log(preparedOptions)

{ path: '/', method: 'GET' }

I am fairly new using undici, and I am not sure why this error is occurring, I would appreciate any input.

How can I overcome this error?


Solution

  • You would have to update your request function

    module.exports.request = async (clientName, method, path, options) => {
      // eslint-disable-next-line security/detect-object-injection
      const config = rest[clientName];
      console.log(config);
      let preparedOptions = await prepareOptions(clientName, method, config.base.prefixUrl + path, options, true);
      console.log(preparedOptions);
      console.log(config.base.prefixUrl + path);
      return undici.fetch(config.base.prefixUrl + path, { body: options.data, method })
        .then(async (res) => {
          return { statusCode: res.status, body: await res.json() }; // maybe text()
        })
        .catch(async (err) => {
          if (!isNil(err.response) && (err.response.statusCode === 401 || err.response.statusCode === 403)) {
            preparedOptions = await prepareOptions(clientName, method, config.base.prefixUrl + path, options, true);
            return undici.fetch(config.base.prefixUrl);
          }
          throw err;
        });
    };
    

    And also your tests to

    it('200 - undici', async () => {
          const mockAgent = agent.get('https://test.ca');
          mockAgent
            .intercept({
              path: '/test',
              method: 'GET',
            })
            .reply(200, {
              data: 'test',
            });
          const result = await rest.get('test', '/test');
          assert.equal(result.statusCode, 200);
          assert.deepEqual(result.body, { data: 'test' });
          agent.assertNoPendingInterceptors();
        });