Add integration tests #27
@ -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": {
|
||||||
|
85
src/crud-include.integration.test.js
Normal file
85
src/crud-include.integration.test.js
Normal 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);
|
||||||
|
});
|
26
src/crud-route-creation.integration.test.js
Normal file
26
src/crud-route-creation.integration.test.js
Normal 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' });
|
||||||
|
});
|
@ -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],
|
||||||
};
|
};
|
||||||
|
@ -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
18
test/fixtures/models/city.js
vendored
Normal 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
19
test/fixtures/models/player.js
vendored
Normal 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
22
test/fixtures/models/team.js
vendored
Normal 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
70
test/integration-setup.js
Normal 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 };
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user