Home Manual Reference Source Test Repository

test/e2e/api.spec.js

import path from 'path';
import assert from 'assert';
import sinon from 'sinon';
import fs from 'fs-extra';

import { simpleSite } from '../utils';

import Reptar from '../../lib/index';
import { ApiService } from '../../lib/server/api';
import cache from '../../lib/cache';
import Config from '../../lib/config/index';
import log from '../../lib/log';

log.setSilent(true);

describe('api', function test() {
  this.timeout(5000);

  const generatedOutputDir = path.join(simpleSite.src, '_site');

  let sandbox;
  let instance;
  let apiService;
  let request;
  let reply;

  before(async () => {
    sandbox = sinon.sandbox.create();

    // Don't actually save cache to file system.
    sandbox.stub(cache, 'save');

    fs.removeSync(generatedOutputDir);

    sandbox.spy(Reptar.prototype, 'update');
    sandbox.spy(Config.prototype, 'update');

    instance = new Reptar({
      rootPath: simpleSite.src,
      showSpinner: false,
    });
    await instance.update();

    apiService = ApiService(instance);
  });

  beforeEach(() => {
    request = {
      query: {},
      payload: {},
      params: {},
    };
    reply = sandbox.spy();
  });

  afterEach(() => {
    reply.reset();
  });

  after(() => {
    sandbox.restore();
  });

  describe('config handlers', () => {
    beforeEach(() => apiService.config.get(request, reply));

    it('returns the config object', () => {
      assert(reply.firstCall.calledWith(instance.config.get()));
    });
  });

  describe('files.get handler', () => {
    let response;

    function commonAssertions({ expectedLength }) {
      it('returns an array', () => {
        assert.equal(typeof response.length, 'number');
      });

      it(`returns ${expectedLength} items`, () => {
        assert.equal(response.length, expectedLength);
      });
    }

    afterEach(() => {
      response = undefined;
    });

    describe('without any query params', () => {
      beforeEach(async () => {
        await apiService.files.get(request, reply);
        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 15,
      });
    });

    describe('when filtered=true', () => {
      beforeEach(async () => {
        request.query = {
          filtered: 'true',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 1,
      });

      it('every item is filtered', () => {
        response.forEach(file => assert.equal(file.filtered, true));
      });
    });

    describe('when filtered=false', () => {
      beforeEach(async () => {
        request.query = {
          filtered: 'false',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 14,
      });

      it('every item is not filtered', () => {
        response.forEach(file => assert.equal(file.filtered, false));
      });
    });

    describe('when skipProcessing=true', () => {
      beforeEach(async () => {
        request.query = {
          skipProcessing: 'true',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 7,
      });

      it('every item is skipProcessing', () => {
        response.forEach(file => assert.equal(file.skipProcessing, true));
      });
    });

    describe('when skipProcessing=false', () => {
      beforeEach(async () => {
        request.query = {
          skipProcessing: 'false',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 8,
      });

      it('every item is skipProcessing', () => {
        response.forEach(file => assert.equal(file.skipProcessing, false));
      });
    });

    describe('when assetProcessor=true', () => {
      beforeEach(async () => {
        request.query = {
          assetProcessor: 'true',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 5,
      });

      it('every item is assetProcessor', () => {
        response.forEach(file => assert(file.assetProcessor));
      });
    });

    describe('when assetProcessor=false', () => {
      beforeEach(async () => {
        request.query = {
          assetProcessor: 'false',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 10,
      });

      it('every item is assetProcessor', () => {
        response.forEach(file => assert.equal(file.assetProcessor, null));
      });
    });

    describe('when path does not match a file', () => {
      beforeEach(async () => {
        request.query = {
          path: 'badabada',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 0,
      });
    });

    describe('when path does match a file', () => {
      let filePath;

      beforeEach(async () => {
        filePath = path.join(instance.config.get('path.source'), 'about.md');

        request.query = {
          path: filePath,
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 1,
      });

      it('the file returned is the one we requested', () => {
        assert.equal(response[0].path, filePath);
      });
    });

    describe('when the path is relative and matches', () => {
      const filePath = 'about.md';
      let fullPath;

      beforeEach(async () => {
        fullPath = path.join(instance.config.get('path.source'), filePath);

        request.query = {
          path: filePath,
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 1,
      });

      it('the file returned is the one we requested', () => {
        assert.equal(response[0].path, fullPath);
      });
    });

    //
    //
    describe('when destination does not match a file', () => {
      beforeEach(async () => {
        request.query = {
          destination: 'badabada',
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 0,
      });
    });

    describe('when destination does match a file', () => {
      const filePath = '/about.html';

      beforeEach(async () => {
        request.query = {
          destination: filePath,
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 1,
      });

      it('the file returned is the one we requested', () => {
        assert.equal(response[0].destination, filePath);
      });
    });

    describe('when the destination is relative and matches', () => {
      const filePath = 'about.html';

      beforeEach(async () => {
        request.query = {
          destination: filePath,
        };

        await apiService.files.get(request, reply);

        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 1,
      });

      it('the file returned is the one we requested', () => {
        assert.equal(response[0].destination, `/${filePath}`);
      });
    });
  });

  describe('collections.get handler', () => {
    let response;

    function commonAssertions({ expectedLength }) {
      it('returns an array', () => {
        assert.equal(typeof response.length, 'number');
      });

      it(`returns ${expectedLength} items`, () => {
        assert.equal(response.length, expectedLength);
      });
    }

    afterEach(() => {
      response = undefined;
    });

    describe('without any query params', () => {
      beforeEach(async () => {
        await apiService.collections.get(request, reply);
        response = reply.firstCall.args[0];
      });

      commonAssertions({
        expectedLength: 2,
      });

      it('returns an array of just collection names', () => {
        assert.deepEqual(response, ['post', 'tag']);
      });
    });

    function commonOneCollection({ id }) {
      it('returns an object', () => {
        assert.equal(typeof response, 'object');
      });

      it('the object matches the request id', () => {
        assert.equal(response.id, id);
      });
    }

    describe('when requesting one collection', () => {
      const id = 'post';

      beforeEach(async () => {
        request.params.id = id;
        await apiService.collections.get(request, reply);
        response = reply.firstCall.args[0];
      });

      commonOneCollection({ id });

      it('the object does not have properties that excluded by default', () => {
        ['pages', 'files', 'data'].forEach(prop =>
          assert.equal(typeof response[prop], 'undefined')
        );
      });
    });

    describe('when requesting one collection with include param', () => {
      const id = 'post';

      beforeEach(async () => {
        request.params.id = id;
        request.query.include = 'pages';
        await apiService.collections.get(request, reply);
        response = reply.firstCall.args[0];
      });

      commonOneCollection({ id });

      it('the object has a pages property which is an array', () => {
        assert.equal(typeof response.pages.length, 'number');
      });

      it('pages has expected length', () => {
        assert.equal(response.pages.length, 1);
      });

      it('the object does not have properties that excluded by default', () => {
        ['files', 'data'].forEach(prop =>
          assert.equal(typeof response[prop], 'undefined')
        );
      });
    });

    // eslint-disable-next-line max-len
    describe('when requesting one collection with multiple include values', () => {
      const id = 'post';

      beforeEach(async () => {
        request.params.id = id;
        request.query.include = 'pages,files';
        await apiService.collections.get(request, reply);
        response = reply.firstCall.args[0];
      });

      commonOneCollection({ id });

      it('the object has a pages property which is an array', () => {
        assert.equal(typeof response.pages.length, 'number');
      });

      it('pages has expected length', () => {
        assert.equal(response.pages.length, 1);
      });

      it('files is an object', () => {
        assert.equal(typeof response.files, 'object');
      });

      it('files is not an array object', () => {
        assert.equal(typeof response.files.length, 'undefined');
      });

      it('the object does not have properties that excluded by default', () => {
        ['data'].forEach(prop =>
          assert.equal(typeof response[prop], 'undefined')
        );
      });
    });
  });
});