Add integration tests #27

Merged
joeybaker merged 6 commits from add-integration-tests into master 2016-10-26 21:08:38 +00:00
9 changed files with 252 additions and 3 deletions

View File

@ -32,13 +32,19 @@
"babel-plugin-transform-es2015-modules-commonjs": "^6.16.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.16.0",
"babel-preset-stage-1": "^6.16.0", "babel-preset-stage-1": "^6.16.0",
"babel-register": "^6.16.3", "babel-register": "^6.16.3",
"bluebird": "^3.4.6",
"eslint": "^3.8.1", "eslint": "^3.8.1",
"eslint-config-pichak": "^1.1.2", "eslint-config-pichak": "^1.1.2",
"eslint-plugin-ava": "^3.1.1", "eslint-plugin-ava": "^3.1.1",
"ghooks": "^1.3.2", "ghooks": "^1.3.2",
"hapi": "^15.2.0",
"hapi-sequelize": "^3.0.4",
"portfinder": "^1.0.9",
"scripty": "^1.6.0", "scripty": "^1.6.0",
"sequelize": "^3.24.6",
"sinon": "^1.17.6", "sinon": "^1.17.6",
"sinon-bluebird": "^3.1.0", "sinon-bluebird": "^3.1.0",
"sqlite3": "^3.1.7",
"tap-xunit": "^1.4.0" "tap-xunit": "^1.4.0"
}, },
"dependencies": { "dependencies": {

View File

@ -0,0 +1,85 @@
import test from 'ava';
import 'sinon-bluebird';
import setup from '../test/integration-setup.js';
setup(test);
test('belongsTo /team?include=city', async (t) => {
const { server, instances } = t.context;
const { team1, city1 } = instances;
const path = `/team/${team1.id}?include=city`;
const { result, response } = await server.inject(path);
t.falsy(response instanceof Error);
t.is(result.id, team1.id);
t.is(result.City.id, city1.id);
});
test('belongsTo /team?include=cities', async (t) => {
const { server, instances } = t.context;
const { team1, city1 } = instances;
const path = `/team/${team1.id}?include=cities`;
const { result, response } = await server.inject(path);
t.falsy(response instanceof Error);
t.is(result.id, team1.id);
t.is(result.City.id, city1.id);
});
test('hasMany /team?include=player', async (t) => {
const { server, instances } = t.context;
const { team1, player1, player2 } = instances;
const path = `/team/${team1.id}?include=player`;
const { result, response } = await server.inject(path);
t.falsy(response instanceof Error);
t.is(result.id, team1.id);
const playerIds = result.Players.map(({ id }) => id);
t.truthy(playerIds.includes(player1.id));
t.truthy(playerIds.includes(player2.id));
});
test('hasMany /team?include=players', async (t) => {
const { server, instances } = t.context;
const { team1, player1, player2 } = instances;
const path = `/team/${team1.id}?include=players`;
const { result, response } = await server.inject(path);
t.falsy(response instanceof Error);
t.is(result.id, team1.id);
const playerIds = result.Players.map(({ id }) => id);
t.truthy(playerIds.includes(player1.id));
t.truthy(playerIds.includes(player2.id));
});
test('multiple includes /team?include=players&include=city', async (t) => {
const { server, instances } = t.context;
const { team1, player1, player2, city1 } = instances;
const path = `/team/${team1.id}?include=players&include=city`;
const { result, response } = await server.inject(path);
t.falsy(response instanceof Error);
t.is(result.id, team1.id);
const playerIds = result.Players.map(({ id }) => id);
t.truthy(playerIds.includes(player1.id));
t.truthy(playerIds.includes(player2.id));
t.is(result.City.id, city1.id);
});
test('multiple includes /team?include[]=players&include[]=city', async (t) => {
const { server, instances } = t.context;
const { team1, player1, player2, city1 } = instances;
const path = `/team/${team1.id}?include[]=players&include[]=city`;
const { result, response } = await server.inject(path);
t.falsy(response instanceof Error);
t.is(result.id, team1.id);
const playerIds = result.Players.map(({ id }) => id);
t.truthy(playerIds.includes(player1.id));
t.truthy(playerIds.includes(player2.id));
t.is(result.City.id, city1.id);
});

View File

@ -0,0 +1,26 @@
import test from 'ava';
import 'sinon-bluebird';
import setup from '../test/integration-setup.js';
const { modelNames } = setup(test);
const confirmRoute = (t, { path, method }) => {
const { server } = t.context;
// there's only one connection, so just get the first table
const routes = server.table()[0].table;
t.truthy(routes.find((route) => {
return route.path = path
&& route.method === method;
}));
};
modelNames.forEach(({ singular, plural }) => {
test('get', confirmRoute, { path: `/${singular}/{id}`, method: 'get' });
test('list', confirmRoute, { path: `/${plural}/{id}`, method: 'get' });
test('scope', confirmRoute, { path: `/${plural}/{scope}`, method: 'get' });
test('create', confirmRoute, { path: `/${singular}`, method: 'post' });
test('destroy', confirmRoute, { path: `/${plural}`, method: 'delete' });
test('destroyScope', confirmRoute, { path: `/${plural}/{scope}`, method: 'delete' });
test('update', confirmRoute, { path: `/${singular}/{id}`, method: 'put' });
});

View File

@ -63,14 +63,17 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
const { _singular, _plural, _Singular, _Plural } = target; const { _singular, _plural, _Singular, _Plural } = target;
return [_singular, _plural, _Singular, _Plural]; return [_singular, _plural, _Singular, _Plural];
}), }),
]; ].filter(Boolean);
const attributeValidation = modelAttributes.reduce((params, attribute) => { const attributeValidation = modelAttributes.reduce((params, attribute) => {
// TODO: use joi-sequelize
params[attribute] = joi.any(); params[attribute] = joi.any();
return params; return params;
}, {}); }, {});
const validAssociations = joi.string().valid(...modelAssociations); const validAssociations = modelAssociations.length
? joi.string().valid(...modelAssociations)
: joi.valid(null);
const associationValidation = { const associationValidation = {
include: [joi.array().items(validAssociations), validAssociations], include: [joi.array().items(validAssociations), validAssociations],
}; };

View File

@ -7,7 +7,7 @@ import url from 'url';
import qs from 'qs'; import qs from 'qs';
const register = (server, options = {}, next) => { const register = (server, options = {}, next) => {
options.prefix = options.prefix || ''; options.prefix = options.prefix || '/';
options.name = options.name || 'db'; options.name = options.name || 'db';
const db = server.plugins['hapi-sequelize'][options.name]; const db = server.plugins['hapi-sequelize'][options.name];

18
test/fixtures/models/city.js vendored Normal file
View File

@ -0,0 +1,18 @@
export default (sequelize, DataTypes) => {
return sequelize.define('City', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: DataTypes.STRING,
}, {
classMethods: {
associate: (models) => {
models.City.hasMany(models.Team, {
foreignKey: { name: 'cityId' },
});
},
},
});
};

19
test/fixtures/models/player.js vendored Normal file
View File

@ -0,0 +1,19 @@
export default (sequelize, DataTypes) => {
return sequelize.define('Player', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: DataTypes.STRING,
teamId: DataTypes.INTEGER,
}, {
classMethods: {
associate: (models) => {
models.Player.belongsTo(models.Team, {
foreignKey: { name: 'teamId' },
});
},
},
});
};

22
test/fixtures/models/team.js vendored Normal file
View File

@ -0,0 +1,22 @@
export default (sequelize, DataTypes) => {
return sequelize.define('Team', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: DataTypes.STRING,
cityId: DataTypes.INTEGER,
}, {
classMethods: {
associate: (models) => {
models.Team.belongsTo(models.City, {
foreignKey: { name: 'cityId' },
});
models.Team.hasMany(models.Player, {
foreignKey: { name: 'teamId' },
});
},
},
});
};

70
test/integration-setup.js Normal file
View File

@ -0,0 +1,70 @@
import hapi from 'hapi';
import Sequelize from 'sequelize';
import portfinder from 'portfinder';
import path from 'path';
import Promise from 'bluebird';
const getPort = Promise.promisify(portfinder.getPort);
const modelsPath = path.join(__dirname, 'fixtures', 'models');
const modelsGlob = path.join(modelsPath, '**', '*.js');
const dbName = 'db';
// these are what's in the fixtures dir
const modelNames = [
{ Singluar: 'City', singular: 'city', Plural: 'Cities', plural: 'cities' },
{ Singluar: 'Team', singular: 'team', Plural: 'Teams', plural: 'teams' },
{ Singluar: 'Player', singular: 'player', Plural: 'Players', plural: 'players' },
];
export default (test) => {
test.beforeEach('get an open port', async (t) => {
t.context.port = await getPort();
});
test.beforeEach('setup server', async (t) => {
const sequelize = t.context.sequelize = new Sequelize({
dialect: 'sqlite',
logging: false,
});
const server = t.context.server = new hapi.Server();
server.connection({
host: '0.0.0.0',
port: t.context.port,
});
await server.register({
register: require('hapi-sequelize'),
options: {
name: dbName,
models: [modelsGlob],
sequelize,
sync: true,
forceSync: true,
},
});
await server.register({
register: require('../src/index.js'),
options: {
name: dbName,
},
},
);
});
test.beforeEach('create data', async (t) => {
const { Player, Team, City } = t.context.sequelize.models;
const city1 = await City.create({ name: 'Healdsburg' });
const team1 = await Team.create({ name: 'Baseballs', cityId: city1.id });
const player1 = await Player.create({ name: 'Pinot', teamId: team1.id });
const player2 = await Player.create({ name: 'Syrah', teamId: team1.id });
t.context.instances = { city1, team1, player1, player2 };
});
// kill the server so that we can exit and don't leak memory
test.afterEach('stop the server', (t) => t.context.server.stop());
return { modelNames };
};