zackehh/oryx

View on GitHub
test/model_routes.js

Summary

Maintainability
F
3 wks
Test Coverage
var async = require('async');
var bodyParser = require('body-parser');
var express = require('express');
var merge = require('lodash.merge');
var path = require('path');
var should = require('should');
var supertest = require('supertest');

var Oryx = require('../');
var resources = './test/resources';

describe('Model Routes', function () {

  var app = express();
  var agent = supertest.agent(app);

  before('set up a Oryx instance', function (start) {
    app.use(bodyParser.json({ type: '*/*' }));

    var oryx = new Oryx(app, {
      app_root: path.resolve(resources),
      log_level: 'error'
    });

    oryx
      .models({
        path: './model_routes'
      })
      .then(function (models) {
        should(models).be.ok();
        should(models.length).eql(3);
        should(models[0]).eql('employee');
        should(models[1]).eql('manager');
        should(models[2]).eql('user');
      })
      .then(function () {
        return oryx.routes();
      })
      .then(function (apis) {
        should(apis).be.ok();
        should(apis.length).eql(1);
        should(apis[0]).eql('v1');
      })
      .then(start)
      .catch(start);
  });

  describe('Custom Routes', function () {

    it('allow a user to define custom routes on a model', function (next) {
      agent
        .get('/api/v1/user/custom_route')
        .expect('Content-Type', /json/)
        .expect(200, {
          message: 'aloha!'
        }, next);
    });

    it('allow a user to specify routes to include/exclude', function (next) {
      agent
        .get('/api/v1/manager/1')
        .expect(404, function () {
          agent
            .get('/api/v1/manager')
            .expect(200, next);
        });
    });

    it('allow a user to override predefined routes', function (next) {
      agent
        .get('/api/v1/manager')
        .expect('Content-Type', /json/)
        .expect(200, {
          model: 'manager'
        }, next);
    });

    it('allow the use of glob filters', function (next) {
      agent
        .get('/api/v1/employee/info')
        .expect('Content-Type', /json/)
        .expect(200, {
          success: true,          result: {
            name: 'employee',
            schema: {
              firstName: {
                type: 'string'
              },
              lastName: {
                type: 'string'
              },
              id: {
                autoIncrement: true,
                primaryKey: true,
                type: 'integer',
                unique: true
              }
            },
            routes: [
              'POST /',
              'PUT /',
              'GET /info',
              'POST /:id',
              'PUT /:id',
              'DELETE /:id'
            ]
          }
        }, next);
    });

  });

  describe('Predefined Routes', function () {

    function createTestUsers(cb) {
      var users = [];
      var firstNames = [ 'Bob', 'Bobby', 'Barbara' ];
      var lastNames = [ 'Loblaw', 'Loblow', 'Loblew', 'Lobluw' ];

      for(var i = 0; i < 25; i++) {
        users.push({
          id: i + 1,
          firstName: firstNames[i % 3],
          lastName: lastNames[i % 4]
        });
      }

      agent
        .post('/api/v1/user')
        .send(users)
        .expect('Content-Type', /json/)
        .expect(201, {
          success: true,          result: users
        }, cb);
    }

    afterEach('cleanup instances', function (done) {
      agent
        .delete('/api/v1/user')
        .expect(200, done);
    });

    describe('POST /', function () {

      it('creates a model instance', function (next) {
        var user = {
          id: 1,
          firstName: 'Bob',
          lastName: 'Loblaw'
        };

        agent
          .post('/api/v1/user')
          .send(user)
          .expect('Content-Type', /json/)
          .expect(201, {
            success: true,            result: user
          }, next);
      });

      it('creates several model instances', function (next) {
        var users = [
          {
            id: 1,
            firstName: 'Bob',
            lastName: 'Loblaw'
          },
          {
            id: 2,
            firstName: 'Bobby',
            lastName: 'Loblaw'
          }
        ];

        agent
          .post('/api/v1/user')
          .send(users)
          .expect('Content-Type', /json/)
          .expect(201, {
            success: true,            result: users
          }, next);
      });

    });

    describe('GET /', function () {

      it('returns 10 records by default', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(10);
            })
            .end(next);
        });
      });

      it('returns N records', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user?limit=25')
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(25);
            })
            .end(next);
        });
      });

      it('returns a projection via single param', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              select: 'id',
              limit: 1
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(1);
              should(res.body.result[0].id).be.ok();
              should(res.body.result[0].firstName).not.be.ok();
              should(res.body.result[0].lastName).not.be.ok();
            })
            .end(next);
        });
      });

      it('returns a projection via multiple params', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user?select=id&select=firstName')
            .query({
              limit: 1
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(1);
              should(res.body.result[0].id).be.ok();
              should(res.body.result[0].firstName).be.ok();
              should(res.body.result[0].lastName).not.be.ok();
            })
            .end(next);
        });
      });

      it('returns a projection via array params', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user?select[]=id&select[]=firstName')
            .query({
              limit: 1
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(1);
              should(res.body.result[0].id).be.ok();
              should(res.body.result[0].firstName).be.ok();
              should(res.body.result[0].lastName).not.be.ok();
            })
            .end(next);
        });
      });

      it('returns a projection via JSON params', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              select: '["id"]',
              limit: 1
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(1);
              should(res.body.result[0].id).be.ok();
              should(res.body.result[0].firstName).not.be.ok();
              should(res.body.result[0].lastName).not.be.ok();
            })
            .end(next);
        });
      });

      it('allows ascending sort of results using String parameters', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              limit: 3,
              sort: 'firstName'
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(3);

              for(var i = 0, j = res.body.result.length; i < j; i++) {
                should(res.body.result[i].firstName).be.ok();
                should(res.body.result[i].firstName).eql('Barbara');
              }
            })
            .end(next);
        });
      });

      it('allows descending sort of results using String parameters', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              limit: 3,
              sort: 'firstName DESC'
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(3);

              for(var i = 0, j = res.body.result.length; i < j; i++) {
                should(res.body.result[i].firstName).be.ok();
                should(res.body.result[i].firstName).eql('Bobby');
              }
            })
            .end(next);
        });
      });

      it('allows ascending sort of results using JSON parameters', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              limit: 3,
              sort: JSON.stringify({
                firstName: 'ASC'
              })
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(3);

              for(var i = 0, j = res.body.result.length; i < j; i++) {
                should(res.body.result[i].firstName).be.ok();
                should(res.body.result[i].firstName).eql('Barbara');
              }
            })
            .end(next);
        });
      });

      it('allows descending sort of results using JSON parameters', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              limit: 3,
              sort: JSON.stringify({
                firstName: 'DESC'
              })
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(3);

              for(var i = 0, j = res.body.result.length; i < j; i++) {
                should(res.body.result[i].firstName).be.ok();
                should(res.body.result[i].firstName).eql('Bobby');
              }
            })
            .end(next);
        });
      });

      it('queries across different parameters using key=value', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              firstName: 'Bob',
              select: 'firstName'
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(9);

              for(var i = 0, j = res.body.result.length; i < j; i++) {
                should(Object.keys(res.body.result[i])).have.lengthOf(1);
                should(res.body.result[i].firstName).be.ok();
                should(res.body.result[i].firstName).eql('Bob');
              }
            })
            .end(next);
        });
      });

      it('queries across different parameters using JSON', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              where: JSON.stringify({
                firstName: 'Bob'
              })
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(9);

              for(var i = 0, j = res.body.result.length; i < j; i++) {
                should(res.body.result[i].firstName).be.ok();
                should(res.body.result[i].firstName).eql('Bob');
              }
            })
            .end(next);
        });
      });

      it('provides pagination options to the user', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              limit: 5,
              skip: 10,
              sort: 'firstName'
            })
            .expect('Content-Type', /json/)
            .expect(200)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.result).be.ok();
              should(res.body.result).have.lengthOf(5);

              for(var i = 0, j = res.body.result.length; i < j; i++) {
                should(res.body.result[i].firstName).be.ok();
                should(res.body.result[i].firstName).eql('Bob');
              }
            })
            .end(next);
        });
      });

      it('returns an error for an invalid \'where\' number', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              where: 5
            })
            .expect('Content-Type', /json/)
            .expect(400)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.error).be.ok();
              should(res.body.result).not.be.ok();
              should(res.body.error.name).be.ok();
              should(res.body.error.message).be.ok();
              should(res.body.error.name).eql('OryxError');
              should(res.body.error.message).be.a.String;
            })
            .end(next);
        });
      });

      it('returns an error for an invalid \'where\' string', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user')
            .query({
              where: 'invalid string'
            })
            .expect('Content-Type', /json/)
            .expect(400)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.error).be.ok();
              should(res.body.result).not.be.ok();
              should(res.body.error.name).be.ok();
              should(res.body.error.message).be.ok();
              should(res.body.error.name).eql('OryxError');
              should(res.body.error.message).be.a.String;
            })
            .end(next);
        });
      });

    });

    describe('PUT /', function () {

      it('allows mass updates of matching documents', function (next) {
        createTestUsers(function () {
          agent
            .put('/api/v1/user')
            .query({
              where: {
                lastName: {
                  '!': 'Loblaw'
                }
              }
            })
            .send({
              lastName: 'Loblaw'
            })
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                docs_updated: 10
              }
            }, function () {

              agent
                .get('/api/v1/user?limit=25')
                .expect('Content-Type', /json/)
                .expect(200)
                .expect(function (res) {
                  should(res.body).be.ok();
                  should(res.body.result).be.ok();
                  should(res.body.result).have.lengthOf(25);

                  for(var i = 0, j = res.body.result.length; i < j; i++) {
                    should(res.body.result[i].lastName).be.ok();
                    should(res.body.result[i].lastName).eql('Loblaw');
                  }
                })
                .end(next);

            });
        });
      });

      it('allows updating of N matching documents', function (next) {
        createTestUsers(function () {
          agent
            .put('/api/v1/user?limit=1')
            .send({
              lastName: {
                '!': 'Loblaw'
              }
            })
            .send({
              lastName: 'Loblaw'
            })
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                docs_updated: 1
              }
            }, next);
        });
      });

      it('returns an error if no document is provided', function (next) {
        createTestUsers(function () {
          agent
            .put('/api/v1/user')
            .expect('Content-Type', /json/)
            .expect(400)
            .expect(function (res) {
              should(res.body).be.ok();
              should(res.body.error).be.ok();
              should(res.body.result).not.be.ok();
              should(res.body.error.name).be.ok();
              should(res.body.error.message).be.ok();
              should(res.body.error.name).eql('OryxError');
              should(res.body.error.message).eql('No body provided!');
            })
            .end(next);
        });
      });

    });

    describe('DELETE /', function () {

      it('removes all documents', function (next) {
        createTestUsers(function () {
          agent
            .delete('/api/v1/user')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                docs_removed: 25
              }
            }, next);
        });
      });

      it('removes N documents', function (next) {
        createTestUsers(function () {
          agent
            .delete('/api/v1/user?limit=10')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                docs_removed: 10
              }
            }, next);
        });
      });

      it('uses sort to remove documents', function (next) {
        createTestUsers(function () {
          agent
            .delete('/api/v1/user?limit=8&sort=firstName')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                docs_removed: 8
              }
            }, function () {

              agent
                .get('/api/v1/user?limit=25')
                .expect('Content-Type', /json/)
                .expect(200)
                .expect(function (res) {
                  should(res.body).be.ok();
                  should(res.body.result).be.ok();
                  should(res.body.result).have.lengthOf(17);

                  for(var i = 0, j = res.body.result.length; i < j; i++) {
                    should(res.body.result[i].lastName).be.ok();
                    should(res.body.result[i].lastName).not.eql('Barbara');
                  }
                })
                .end(next);
            });
        });
      });

      it('removes all documents matching a query', function (next) {
        createTestUsers(function () {
          agent
            .delete('/api/v1/user')
            .query({
              where: {
                firstName: 'Barbara'
              }
            })
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                docs_removed: 8
              }
            }, function () {

              agent
                .get('/api/v1/user?limit=25')
                .expect('Content-Type', /json/)
                .expect(200)
                .expect(function (res) {
                  should(res.body).be.ok();
                  should(res.body.result).be.ok();
                  should(res.body.result).have.lengthOf(17);

                  for(var i = 0, j = res.body.result.length; i < j; i++) {
                    should(res.body.result[i].lastName).be.ok();
                    should(res.body.result[i].lastName).not.eql('Barbara');
                  }
                })
                .end(next);
            });
        });
      });

    });

    describe('GET /:id', function () {

      it('retrieves a model by id', function (next) {
        var user = {
          id: 1,
          firstName: 'Bob',
          lastName: 'Loblaw'
        };

        async.series([
          function (cb) {
            agent
              .post('/api/v1/user')
              .send(user)
              .expect('Content-Type', /json/)
              .expect(201, {
                success: true,                result: user
              }, cb);
          },
          function (cb) {
            agent
              .get('/api/v1/user/1')
              .expect('Content-Type', /json/)
              .expect(200, {
                success: true,                result: user
              }, cb);
          }
        ], next);
      });

      it('returns 404 if the instance is missing', function (next) {
        agent
          .get('/api/v1/user/15')
          .expect('Content-Type', /json/)
          .expect(404, next);
      });

    });

    describe('PUT /:id', function () {

      it('allow updating a model by id', function (next) {
        var user = {
          id: 1,
          firstName: 'Bob',
          lastName: 'Loblaw'
        };

        agent
          .post('/api/v1/user')
          .send(user)
          .expect('Content-Type', /json/)
          .expect(201, {
            success: true,            result: user
          }, function () {

            agent
              .put('/api/v1/user/1')
              .send({
                firstName: 'Bobby'
              })
              .expect('Content-Type', /json/)
              .expect(200, {
                success: true,                result: merge({}, user, {
                  firstName: 'Bobby'
                })
              }, next);

          });
      });

      it('returns 404 if the instance is missing', function (next) {
        agent
          .put('/api/v1/user/15')
          .expect('Content-Type', /json/)
          .expect(404, next);
      });

    });

    describe('DELETE /:id', function () {

      it('deletes a model by id', function (next) {
        var user = {
          id: 1,
          firstName: 'Bob',
          lastName: 'Loblaw'
        };

        async.series([
          function (cb) {
            agent
              .post('/api/v1/user')
              .send(user)
              .expect('Content-Type', /json/)
              .expect(201, {
                success: true,                result: user
              }, cb);
          },
          function (cb) {
            agent
              .get('/api/v1/user/1')
              .expect('Content-Type', /json/)
              .expect(200, {
                success: true,                result: user
              }, cb);
          },
          function (cb) {
            agent
              .delete('/api/v1/user/1')
              .expect('Content-Type', /json/)
              .expect(200, {
                success: true,                result: {
                  docs_removed: 1
                }
              }, cb);
          },
          function (cb) {
            agent
              .get('/api/v1/user/1')
              .expect('Content-Type', /json/)
              .expect(404, {
                success: false,                result: { }
              }, cb);
          }
        ], next);
      });

      it('returns 404 if the instance is missing', function (next) {
        agent
          .delete('/api/v1/user/15')
          .expect('Content-Type', /json/)
          .expect(404, next);
      });

    });

    describe('POST /:id', function () {

      it('finds an existing model', function (next) {
        var user = {
          id: 1,
          firstName: 'Bob',
          lastName: 'Loblaw'
        };

        agent
          .post('/api/v1/user')
          .send(user)
          .expect('Content-Type', /json/)
          .expect(201, {
            success: true,            result: user
          }, function () {

            agent
              .post('/api/v1/user/1')
              .send({
                id: 2,
                firstName: 'Bobby',
                lastName: 'Loblaw'
              })
              .expect('Content-Type', /json/)
              .expect(200, {
                success: true,                result: user
              }, next);

          });
      });

      it('finds and creates a missing model', function (next) {
        var user = {
          id: 1,
          firstName: 'Bob',
          lastName: 'Loblaw'
        };

        agent
          .post('/api/v1/user/1')
          .send(user)
          .expect('Content-Type', /json/)
          .expect(200, {
            success: true,            result: user
          }, next);
      });

    });

    describe('GET /info', function () {

      it('returns basic model information', function (next) {
        agent
          .get('/api/v1/user/info')
          .expect('Content-Type', /json/)
          .expect(200, {
            success: true,            result: {
              name: 'user',
              schema: {
                firstName: {
                  type: 'string'
                },
                lastName: {
                  type: 'string'
                },
                id: {
                  autoIncrement: true,
                  primaryKey: true,
                  type: 'integer',
                  unique: true
                }
              },
              routes: [
                'GET /custom_route',
                'GET /',
                'POST /',
                'PUT /',
                'DELETE /',
                'GET /count',
                'GET /info',
                'GET /distinct/:field',
                'GET /:id',
                'POST /:id',
                'PUT /:id',
                'DELETE /:id'
              ]
            }
          }, next);
      });

    });

    describe('GET /count', function () {

      it('counts model instances', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/count')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                doc_count: 25
              }
            }, next);
        });
      });

      it('counts instances up to N', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/count?limit=10')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                doc_count: 10
              }
            }, next);
        });
      });

      it('counts instances matching a query', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/count')
            .query({
              where: {
                firstName: 'Bob'
              }
            })
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: {
                doc_count: 9
              }
            }, next);
        });
      });

    });

    describe('GET /distinct/:field', function () {

      it('finds distinct values for a field', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/distinct/firstName')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: ['Bob', 'Barbara', 'Bobby']
            }, next);
        });
      });

      it('finds N distinct values for a field', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/distinct/firstName?limit=3')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: ['Bob', 'Barbara']
            }, next);
        });
      });

      it('sorts and returns distinct fields', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/distinct/firstName?sort=firstName')
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: ['Barbara', 'Bob', 'Bobby']
            }, next);
        });
      });

      it('sorts, limits and returns distinct fields', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/distinct/firstName?sort=firstName')
            .query({
              limit: 1
            })
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: ['Barbara']
            }, next);
        });
      });

      it('returns distinct values matching a query', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/distinct/firstName')
            .query({
              where: {
                firstName: {
                  startsWith: 'Bob'
                }
              }
            })
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: ['Bob', 'Bobby']
            }, next);
        });
      });

      it('sorts distinct values matching a query', function (next) {
        createTestUsers(function () {
          agent
            .get('/api/v1/user/distinct/firstName')
            .query({
              sort: {
                firstName: 'DESC'
              },
              where: {
                firstName: {
                  startsWith: 'Bob'
                }
              }
            })
            .expect('Content-Type', /json/)
            .expect(200, {
              success: true,
              result: ['Bobby', 'Bob']
            }, next);
        });
      });

    });

  });

});