Manual Reference Source Test

test/controllers/BaseContentController.spec.js

// Import the necessary modules.
// @flow
/* eslint-disable no-unused-expressions */
import bodyParser from 'body-parser'
import { expect } from 'chai'
import express, { type $Application } from 'express'
import sinon from 'sinon'
import supertest from 'supertest'

import {
  BaseContentController,
  ContentService,
  Database,
  PopApi
} from '../../src'
import {
  ExampleModel,
  exampleModel1,
  exampleModel2
} from '../../examples'
import { name } from '../../package'

/**
 * The base endpoint to test.
 * @type {string}
 */
const content: string = 'example'

/** @test {BaseContentController} */
describe('BaseContentController', () => {
  /**
   * The base content controller object to test.
   * @type {BaseContentController}
   */
  let baseContentController: BaseContentController

  /**
   * The service for the base content controller.
   * @type {ContentService}
   */
  let service: ContentService

  /**
   * The express instance to test with.
   * @type {Express}
   */
  let app: $Application

  /**
   * The database middleware to connect to the MongoDb instance.
   * @type {Database}
   */
  let database: Database

  /**
   * The supertest object to make requests with.
   * @type {Object}
   */
  let request: Object

  /**
   * The id of the content to get.
   * @type {string}
   */
  let id: string

  /**
   * Hook for setting up the base content controller tests.
   * @type {Function}
   */
  before(done => {
    app = express()
    app.use(bodyParser.urlencoded({
      extended: true
    }))
    app.use(bodyParser.json())

    service = new ContentService({
      Model: ExampleModel,
      projection: {
        name: 1
      }
    })
    baseContentController = new BaseContentController({
      service,
      basePath: content
    })
    baseContentController.registerRoutes(app)
    request = supertest(app)

    database = new Database(PopApi, {
      database: name
    })
    database.connect()
      .then(() => done())
      .catch(done)
  })

  /** @test {BaseContentController#constructor} */
  it('should check the attributes of the BaseContentController', () => {
    expect(baseContentController.service).to.be.an('object')
    expect(baseContentController.service).to.equal(service)
    expect(baseContentController.basePath).to.a('string')
    expect(baseContentController.basePath).to.equal(content)
  })

  /** @test {BaseContentController#registerRoutes} */
  it('should register the routes with of the controller', () => {
    const app = express()

    let res = baseContentController.registerRoutes(app)
    expect(res).to.be.undefined

    // @flow-ignore reference del to delete to mock Restify.
    app.del = app.delete
    delete app.delete

    res = baseContentController.registerRoutes(app)
    expect(res).to.be.undefined
  })

  /** @test {BaseContentController} */
  describe('with an empty database', () => {
    /**
     * Hook for setting up the AudioController tests.
     * @type {Function}
     */
    before(done => {
      ExampleModel.remove({}).exec()
        .then(() => done())
        .catch(done)
    })

    /**
     * Expect a 204 result from a request.
     * @param {!string} route - The route to test.
     * @returns {undefined}
     */
    function expectNoContent(route: string): void {
      it(`should get a 204 status from the GET [/${route}] route`, done => {
        request.get(route)
          .expect(204)
          .then(() => done())
          .catch(done)
      })
    }

    // Execute the tests.
    [
      `/${content}s`,
      `/${content}/1`,
      `/${content}/id`,
      `/random/${content}`
    ].map(expectNoContent)
  })

  /** @test {BaseContentController} */
  describe('with a filled database', () => {
    /**
     * The query object passed along to the 'getAudios' tests.
     * @type {Object}
     */
    let query: Object

    /**
     * Hook for setting up the AudioController tests.
     * @type {Function}
     */
    before(done => {
      query = {
        order: -1
      }

      done()
    })

    /**
     * Expect a 200 result from a request.
     * @param {!Object} request - The request object to test with.
     * @param {!Function} done - The done function of Mocha.
     * @returns {undefined}
     */
    function testOkResponse(request: Object, done: Function): void {
      request.expect(200)
        .set('Content', 'application/json')
        .then(() => done())
        .catch(done)
    }

    /** @test {BaseContentController#createContent} */
    it(`should get a 200 status from the POST [/${content}s] route`, done => {
      const req = request.post(`/${content}s`)
        .send(exampleModel1)
      testOkResponse(req, done)
    })

    /** @test {BaseContentController#getContents} */
    it(`should get a 200 status from the GET [/${content}s] route`, done => {
      const req = request.get(`/${content}s`)
      testOkResponse(req, done)
    })

    /** @test {BaseContentController#getPage} */
    it(`should get a 200 status from the GET [/${content}s/:page] route`, done => {
      const req = request.get(`/${content}s/1`).query({
        ...query,
        sort: 'name'
      })
      testOkResponse(req, done)
    })

    /** @test {BaseContentController#getPage} */
    it(`should get a 200 status from the GET [/${content}s/:page] route`, done => {
      request.get(`/${content}s/1`).query({
        ...query
      }).expect(200)
        .then(res => {
          const random = Math.floor(Math.random() * res.body.length)
          id = res.body[random]._id

          done()
        }).catch(done)
    })

    /** @test {BaseContentController#updateContent} */
    it(`should get a 200 status from the PUT [/${content}/:id] route`, done => {
      const { name } = exampleModel2
      const req = request.put(`/${content}/${id}`)
        .send({ name })

      testOkResponse(req, done)
    })

    /** @test {BaseContentController#getContent} */
    it(`should get a 200 status from the GET [/${content}/:id] route`, done => {
      const req = request.get(`/${content}/${id}`)
      testOkResponse(req, done)
    })

    /** @test {BaseContentController#getRandomContent} */
    it(`should get a 200 status from the GET [/random/${content}] route`, done => {
      const req = request.get(`/random/${content}`)
      testOkResponse(req, done)
    })

    /** @test {BaseContentController#deleteContent} */
    it(`should get a 200 status from the DELETE [/${content}/:id] route`, done => {
      const req = request.delete(`/${content}/${id}`)
      testOkResponse(req, done)
    })
  })

  /** @test {BaseContentController} */
  describe('will throw errors', () => {
    /**
     * Expect a 500 result from a request.
     * @param {!Object} request - The request object to test with.
     * @param {!Function} done - The done function of Mocha.
     * @param {!Object} stub - The stub which made the internal server error.
     * @returns {undefined}
     */
    function testInternalServerError(
      request: Object,
      done: Function,
      stub: Object | null = null
    ): void {
      request.expect(500)
        .set('Content', 'application/json')
        .then(() => {
          if (stub) {
            stub.restore()
          }

          done()
        })
        .catch(done)
    }

    /** @test {BaseContentController#createContent} */
    it(`should get a 500 status from the POST [/${content}/:id] route`, done => {
      const req = request.post(`/${content}s`)
      testInternalServerError(req, done)
    })

    /** @test {BaseContentController#getContents} */
    it(`should get a 500 status from the GET [/${content}s] route`, done => {
      const stub = sinon.stub(ExampleModel, 'count')
      stub.rejects()

      const req = request.get(`/${content}s`)
      testInternalServerError(req, done, stub)
    })

    /** @test {BaseContentController#getPage} */
    it(`should get a 500 status from the GET [/${content}s/:page] route`, done => {
      const stub = sinon.stub(ExampleModel, 'aggregate')
      stub.rejects()

      const req = request.get(`/${content}s/1`)
      testInternalServerError(req, done, stub)
    })

    /** @test {BaseContentController#updateContent} */
    it(`should get a 500 status from the PUT [/${content}s] route`, done => {
      const stub = sinon.stub(ExampleModel, 'findOneAndUpdate')
      stub.rejects()

      const req = request.put(`/${content}/${id}`)
      testInternalServerError(req, done, stub)
    })

    /** @test {BaseContentController#getContent} */
    it(`should get a 500 status from the GET [/${content}/:id] route`, done => {
      const stub = sinon.stub(ExampleModel, 'findOne')
      stub.rejects()

      const req = request.get(`/${content}/${id}`)
      testInternalServerError(req, done, stub)
    })

    /** @test {BaseContentController#getRandomContent} */
    it(`should get a 500 status from the GET [/random/${content}] route`, done => {
      const stub = sinon.stub(ExampleModel, 'aggregate')
      stub.rejects()

      const req = request.get(`/random/${content}`)
      testInternalServerError(req, done, stub)
    })

    /** @test {BaseContentController#deleteContent} */
    it(`should get a 500 status from the DELETE [/${content}/:id] route`, done => {
      const stub = sinon.stub(ExampleModel, 'findOneAndRemove')
      stub.rejects()

      const req = request.delete(`/${content}/${id}`)
      testInternalServerError(req, done, stub)
    })
  })

  /**
   * Hook for tearing down the AudioController tests.
   * @type {Function}
   */
  after(done => {
    ExampleModel.findOneAndRemove({
      _id: exampleModel1._id
    }).exec()
      .then(() => database.disconnect())
      .then(() => done())
      .catch(done)
  })
})