Source: src/lib.js


const { join } = require('path');
const { assert } = require('chai');

const { green } = require('chalk');
const rp = require('request-promise-native');

const Ajv = require('ajv');

// eslint-disable-next-line no-unused-vars
const Logger = require('./logger');

const ajv = new Ajv({ allErrors: true });

const { normalizeHeaders, getSchemaErrMsg } = require('./utils');
const {
  logSuite,
  logTest,
  logRequest,
  logResponse,
} = require('./logging');

/**
 * @param {{
 *   mode: ('exact'|'schema'),
 *   description: string,
 *   method: string,
 *   parse: boolean,
 *   body: *,
 *   headers: Record<string, string>,
 *   path: string,
 *   query: Record<string, string>,
 *   response: {body: *, status: number, headers: Record<string, string>}}} request
 * @param {{log: Logger}} opts
 * @return {Promise<void>}
 */
const runRequest = async (request, { log }) => {
  logRequest(request, log);
  log.startTime('request');
  const res = await rp({
    uri: `https://${(request.path)}`,
    method: request.method,
    headers: request.headers,
    qs: request.query,
    body: request.body,
    json: request.parse,
    resolveWithFullResponse: true,
  });
  log.endTime('request');
  res.headers = normalizeHeaders(res.headers);
  logResponse(res, log);
  assert.equal(res.statusCode, request.response.status);
  for (const k of Object.keys(request.response.headers)) {
    assert.equal(!!res.headers[k], true, `header ${k} is on response`);
    assert.equal(res.headers[k], request.response.headers[k]);
  }
  if (request.mode === 'exact') {
    assert.deepEqual(res.body, request.response.body);
  } else if (request.mode === 'schema') {
    const validate = ajv.compile(request.response.body);
    const valid = validate(res.body);
    if (!valid) {
      // @ts-ignore
      throw new Error(`schema validation failed ${getSchemaErrMsg(validate)}`);
    }
  } else {
    throw new Error(`mode ${request.mode || 'undefined'} not implemented yet`);
  }
  log.info(green('[PASS]'));
};

/**
 * @param {{
 *   name: string,
 *   description: string,
 *   mode: ('schema'|'exact'),
 *   request: Array<*>,
 *   method: string,
 *   parse: boolean,
 *   headers: Record<string, string>,
 *   query: Record<string, string>,
 *   path: string,
 *   response: {headers: Record<string, string>, status: number}
 *   }} test
 * @param {{log: Logger}} opts
 * @yields {Promise<{value: void, done: boolean}>}
 */
const runTest = async function* (test, { log }) {
  logTest(test, log);
  for (let rIdx = 0; rIdx < test.request.length; rIdx++) {
    const r = (test.request)[rIdx];
    yield runRequest({
      name: `Request #${rIdx}`,
      method: test.method,
      mode: test.mode,
      parse: test.parse,
      description: '',
      ...r,
      response: {
        status: test.response.status,
        ...(r.response || {}),
        ...(test.response),
        headers: normalizeHeaders({
          ...test.response.headers,
          ...((r.response || { headers: {} }).headers || {}),
        }),
      },
      path: join(test.path, r.path || ''),
      headers: normalizeHeaders({ ...(test.headers), ...r.headers }),
      query: { ...(test.query), ...r.query },
    }, { log });
  }
  log.log('');
};

/**
 * @param {{
 *   method: string,
 *   name: string,
 *   description: string,
 *   parse: boolean,
 *   mode: ('exact'|'schema'),
 *   response: {status: number, headers: Record<string, string>},
 *   path: string,
 *   headers: Record<string, string>,
 *   tests: Array<*>,
 *   query: Record<string, string>}} suite
 * @param {{log: Logger}} opts
 * @yields {Promise<{value: void, done: boolean}>}
 */
const runSuite = async function* (suite, { log }) {
  logSuite(suite, log);
  for (let tIdx = 0; tIdx < suite.tests.length; tIdx++) {
    const t = (suite.tests)[tIdx];
    const endpoint = join(suite.path, t.path || '');
    yield* runTest({
      name: `Test Case #${tIdx} :: ${endpoint}`,
      method: suite.method,
      parse: suite.parse,
      mode: suite.mode,
      description: '',
      ...t,
      request: Array.isArray(t.request) ? t.request : [t.request],
      response: {
        status: suite.response.status,
        ...(t.response || {}),
        ...(suite.response),
        headers: {
          ...suite.response.headers,
          ...((t.response || { headers: {} }).headers || {}),
        },
      },
      path: endpoint,
      headers: { ...(suite.headers), ...t.headers },
      query: { ...(suite.query), ...t.query },
    }, { log });
  }
};

/**
 * @param {{response: {}}} s
 * @param {{log: Logger}} opts
 * @yields {Promise<{value: void, done: boolean}>}
 */
const run = (s, { log }) => runSuite({
  name: '',
  method: 'get',
  query: {},
  description: '',
  headers: {},
  path: '',
  parse: true,
  mode: 'schema',
  tests: [],
  ...s,
  response: {
    status: 200,
    headers: {},
    ...(s.response || {}),
  },
}, { log });

module.exports = run;