Source

lib/use.mock.js

/* global jest */
const fs = require('fs');

const readdir = require('@folder/readdir');
const path = require('path');
const _ = require('lodash');
const { InternalServerError } = require('./errors/internal-server.error');
const { UnauthorizedError } = require('./errors/unauthorized.error');

if (process.env.NODE_ENV === 'test') {
  const functionsPath = fs.existsSync(path.join(
    '.', 'src', 'functions',
  )) ? './src/functions' : './functions';

  const assetsPath = fs.existsSync(path.join(
    '.', 'src', 'assets',
  )) ? './src/assets' : './assets';

  const functions = readdir.sync(functionsPath, {
    base: functionsPath,
    recursive: true,
    nodir: true,
  });

  const assets = readdir.sync(assetsPath, {
    base: assetsPath,
    recursive: true,
    nodir: true,
  });

  const mockedValue = {};
  const SyncMapContext = function (uniqueContextName) {
    function SyncMapInstance(uniqueName) {
      this.uniqueName = uniqueName;

      function SyncMapItemsContext(itemContextSid) {
        function SyncMapItems(itemSid) {
          this.sid = itemSid;
          this.data = mockedValue;

          this.fetch = async () => this;
          this.update = async (data) => {
            await new Promise((resolve) => {
              const oldProps = Object.keys(mockedValue);

              oldProps.forEach((prop) => {
                delete mockedValue[prop];
              });

              resolve(mockedValue);
            });

            const props = Object.keys(data);

            props.forEach((prop) => {
              Object.defineProperty(
                mockedValue, prop, {
                  enumerable: true,
                  value: data[prop],
                  writable: true,
                },
              );
            });

            return this;
          };

          return this;
        }

        return new SyncMapItems(itemContextSid);
      }

      Object.defineProperty(
        SyncMapItemsContext, 'create', {
          value: async (data) => {
            if (_.isUndefined(data.key)) {
              throw new Error('Error while creating Sync, key cant be undefined!');
            }

            return data;
          },
        },
      );

      Object.defineProperty(
        SyncMapItemsContext, 'setMapItemFetchResolvedValue', {
          value: async (data) => {
            await new Promise((resolve) => {
              const oldProps = Object.keys(mockedValue);

              oldProps.forEach((prop) => {
                delete mockedValue[prop];
              });

              resolve(mockedValue);
            });

            const props = Object.keys(data);

            props.forEach((prop) => {
              Object.defineProperty(
                mockedValue, prop, {
                  enumerable: true,
                  value: data[prop],
                  writable: true,
                },
              );
            });
          },
          enumerable: true,
          configurable: true,
          writable: true,
        },
      );

      this.syncMapItems = SyncMapItemsContext;
    }

    return new SyncMapInstance(uniqueContextName);
  };

  Object.defineProperty(
    SyncMapContext, 'create', {
      value: async (data) => {
        if (_.isUndefined(data.uniqueName)) {
          throw new Error('Error while creating Sync, uniqueName cant be undefined!');
        }

        return data;
      },
      enumerable: true,
      configurable: true,
      writable: true,
    },
  );

  const syncMapItemCreator = SyncMapContext;

  Object.defineProperty(
    global, 'Runtime', {
      enumerable: true,
      get: () => ({
        getFunctions: jest.fn(() => (functions.reduce((p, c) => {
          p[`${c.replace(/\.protected\.js|\.protected\.ts/, '').replace(/\.private\.js|\.private\.ts/, '').replace(/\.js|\.ts/, '')}`] = {
            path: path.resolve(functionsPath, c).replace(/\.js|\.ts/, ''),
          };
          return p;
        }, {}))),
        getSync: (_service) => ({
          maps: syncMapItemCreator,
        }),
        getAssets: jest.fn(() => (assets.reduce((p, c) => {
          p[`/${c.replace(/\.private|\.private/, '')}`] = {
            path: path.resolve(assetsPath, c).replace(/\.js|\.ts/, ''),
          };
          return p;
        }, {}))),
      }),
    },
  );

  /**
   * @param {function} fn
   * @param {object} [params]
   * @param {object} [params.providers]
   * @param {object} [params.env]
   * @param {object} [params.client]
   */
  const useMock = (fn, params) => async function (...args) {
    const [event] = args;
    const getTwilioClient = jest.fn();

    const providers = _.isUndefined(params?.providers)
      || !_.isPlainObject(params?.providers)
      ? {} : params.providers;

    const env = _.isUndefined(params?.env)
      || !_.isPlainObject(params?.env)
      ? {} : params.env;

    const client = _.isUndefined(params?.client)
    || !_.isObject(params?.client)
      ? getTwilioClient() : params.client;

    const providerThat = {
      client,
      env,
    };

    const {
      request, cookies, ...values
    } = event;

    const providerNames = Object.keys(providers);

    const that = {
      request,
      cookies,
      env,
      providers: providerNames.reduce((p, c) => {
        Reflect.defineProperty(
          p, c, {
            value: providers[c].bind(providerThat),
            enumerable: true,
          },
        );
        return p;
      }, {}),
    };

    try {
      return await fn.apply(that, [values]);
    } catch (err) {
      if (typeof err === 'string') {
        return new UnauthorizedError(err);
      }

      return new InternalServerError(err);
    }
  };

  module.exports = { useMock };
}