diff --git a/README.md b/README.md index bf52e64..60892fc 100644 --- a/README.md +++ b/README.md @@ -109,10 +109,17 @@ Getting related models is easy, just use a query parameter `include`. ```js // returns all teams with their related City model -// GET /teams?include=city +// GET /teams?include=city or +// GET /teams?include={"model": "City"} // results in a Sequelize query: Team.findAll({include: City}) + +or if association defined with an alias +// GET /players?include={"model": "Master", "as": "Couch"} + +// results in a Sequelize query: +Players.findAll({include: Master, as: 'Couch'}) ``` If you want to get multiple related models, just pass multiple `include` parameters. @@ -133,6 +140,56 @@ For models that have a many-to-many relationship, you can also pass the plural v Team.findAll({include: [Player]}) ``` +Filtering by related models property, you can pass **where** paremeter inside each **include** items object. +```js +// returns all team with their related City where City property name equals Healdsburg +// GET /teams?include={"model": "City", "where": {"name": "Healdsburg"}} + +// results in a Sequelize query: +Team.findAll({include: {model: City, where: {name: 'Healdsburg'}}}) +``` + +More complex example with nested include, association alias and association filtering. +```js +// returns all team with its players along with its couch of each player +// GET /cities?include[]={ +// "model": "Team", +// "include": { +// "model": "Player", +// "where": { +// "name": "Pinot" +// }, +// "include": { +// "model": "Master", +// "as": "Coach", +// "where": { +// "name": "Shifu" +// } +// } +// } +// } + +// results in a Sequelize query: +City.findAll({ + include: { + model: Team, + include: { + model: Player, + where: { + name: Pinot + }, + include: { + model: Master, + as: 'Coach', + where: { + name: 'Shifu' + } + } + } + } +}) +``` + ## `limit` and `offset` queries Restricting list (`GET`) and scope queries to a restricted count can be done by passing `limit=` and/or `offset=`. diff --git a/src/associations/one-to-many.js b/src/associations/one-to-many.js index d693172..37627f1 100644 --- a/src/associations/one-to-many.js +++ b/src/associations/one-to-many.js @@ -19,14 +19,14 @@ export default (server, a, b, names, options) => { update(server, a, b, names); }; -export const get = (server, a, b, names) => { +export const get = async (server, a, b, names) => { server.route({ method: 'GET', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const base = await a.findOne({ where: { @@ -51,14 +51,14 @@ export const get = (server, a, b, names) => { }); }; -export const list = (server, a, b, names) => { +export const list = async (server, a, b, names) => { server.route({ method: 'GET', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ @@ -77,16 +77,16 @@ export const list = (server, a, b, names) => { }); }; -export const scope = (server, a, b, names) => { +export const scope = async (server, a, b, names) => { const scopes = Object.keys(b.options.scopes); server.route({ method: 'GET', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}/{scope}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}/{scope}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ @@ -116,7 +116,7 @@ export const scope = (server, a, b, names) => { }); }; -export const scopeScope = (server, a, b, names) => { +export const scopeScope = async (server, a, b, names) => { const scopes = { a: Object.keys(a.options.scopes), b: Object.keys(b.options.scopes), @@ -124,11 +124,11 @@ export const scopeScope = (server, a, b, names) => { server.route({ method: 'GET', - path: `${prefix}/${names.a.plural}/{scopea}/${names.b.plural}/{scopeb}`, + path: `${prefix}${names.a.plural}/{scopea}/${names.b.plural}/{scopeb}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const list = await b.scope(request.params.scopeb).findAll({ @@ -152,14 +152,14 @@ export const scopeScope = (server, a, b, names) => { }); }; -export const destroy = (server, a, b, names) => { +export const destroy = async (server, a, b, names) => { server.route({ method: 'DELETE', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ @@ -179,16 +179,16 @@ export const destroy = (server, a, b, names) => { }); }; -export const destroyScope = (server, a, b, names) => { +export const destroyScope = async (server, a, b, names) => { const scopes = Object.keys(b.options.scopes); server.route({ method: 'DELETE', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}/{scope}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}/{scope}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ @@ -221,14 +221,14 @@ export const destroyScope = (server, a, b, names) => { }); }; -export const update = (server, a, b, names) => { +export const update = async (server, a, b, names) => { server.route({ method: 'PUT', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ diff --git a/src/associations/one-to-one.js b/src/associations/one-to-one.js index ac71e31..629de26 100644 --- a/src/associations/one-to-one.js +++ b/src/associations/one-to-one.js @@ -14,14 +14,14 @@ export default (server, a, b, names, options) => { update(server, a, b, names); }; -export const get = (server, a, b, names) => { +export const get = async (server, a, b, names) => { server.route({ method: 'GET', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ @@ -47,7 +47,7 @@ export const get = (server, a, b, names) => { export const create = (server, a, b, names) => { server.route({ method: 'POST', - path: `${prefix}/${names.a.singular}/{id}/${names.b.singular}`, + path: `${prefix}${names.a.singular}/{id}/${names.b.singular}`, @error async handler(request, reply) { @@ -67,14 +67,14 @@ export const create = (server, a, b, names) => { }); }; -export const destroy = (server, a, b, names) => { +export const destroy = async (server, a, b, names) => { server.route({ method: 'DELETE', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ @@ -99,11 +99,11 @@ export const destroy = (server, a, b, names) => { export const update = (server, a, b, names) => { server.route({ method: 'PUT', - path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`, + path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`, @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const base = await a.findOne({ diff --git a/src/crud-include.integration.test.js b/src/crud-include.integration.test.js index 169781f..df342d2 100644 --- a/src/crud-include.integration.test.js +++ b/src/crud-include.integration.test.js @@ -6,7 +6,7 @@ const STATUS_OK = 200; setup(test); -test('belongsTo /team?include=city', async (t) => { +test('belongsTo /team?include=city', async(t) => { const { server, instances } = t.context; const { team1, city1 } = instances; const path = `/team/${team1.id}?include=city`; @@ -17,7 +17,7 @@ test('belongsTo /team?include=city', async (t) => { t.is(result.City.id, city1.id); }); -test('belongsTo /team?include=cities', async (t) => { +test('belongsTo /team?include=cities', async(t) => { const { server, instances } = t.context; const { team1, city1 } = instances; const path = `/team/${team1.id}?include=cities`; @@ -28,7 +28,7 @@ test('belongsTo /team?include=cities', async (t) => { t.is(result.City.id, city1.id); }); -test('hasMany /team?include=player', async (t) => { +test('hasMany /team?include=player', async(t) => { const { server, instances } = t.context; const { team1, player1, player2 } = instances; const path = `/team/${team1.id}?include=player`; @@ -42,7 +42,7 @@ test('hasMany /team?include=player', async (t) => { t.truthy(playerIds.includes(player2.id)); }); -test('hasMany /team?include=players', async (t) => { +test('hasMany /team?include=players', async(t) => { const { server, instances } = t.context; const { team1, player1, player2 } = instances; const path = `/team/${team1.id}?include=players`; @@ -56,7 +56,18 @@ test('hasMany /team?include=players', async (t) => { t.truthy(playerIds.includes(player2.id)); }); -test('multiple includes /team?include=players&include=city', async (t) => { +test('belongsTo with alias /player?include={"model": "Master", "as": "Coach"}', async(t) => { + const { server, instances } = t.context; + const { team1, master1 } = instances; + const path = `/player/${team1.id}?include={"model": "Master", "as": "Coach"}`; + + const { result, statusCode } = await server.inject(path); + t.is(statusCode, STATUS_OK); + t.is(result.id, team1.id); + t.is(result.Coach.id, master1.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`; @@ -71,7 +82,7 @@ test('multiple includes /team?include=players&include=city', async (t) => { t.is(result.City.id, city1.id); }); -test('multiple includes /team?include[]=players&include[]=city', async (t) => { +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`; @@ -85,3 +96,67 @@ test('multiple includes /team?include[]=players&include[]=city', async (t) => { t.truthy(playerIds.includes(player2.id)); t.is(result.City.id, city1.id); }); + +test('multiple includes /team?include[]=players&include[]={"model": "City"}', async(t) => { + const { server, instances } = t.context; + const { team1, player1, player2, city1 } = instances; + const path = `/team/${team1.id}?include[]=players&include[]={"model": "City"}`; + + const { result, statusCode } = await server.inject(path); + t.is(statusCode, STATUS_OK); + 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('include filter /teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}' + , async(t) => { + const { server } = t.context; + const url = '/teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'; + const method = 'GET'; + + const { statusCode } = await server.inject({ url, method }); + t.is(statusCode, STATUS_OK); + }); + +test('nested include filter ' + + '/citiy?include[]=' + + '{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}' + , async(t) => { + const { instances, server } = t.context; + const { city1, team1, team2 } = instances; + const url = '/city?include[]=' + + '{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}'; + const method = 'GET'; + + const { result, statusCode } = await server.inject({ url, method }); + t.is(statusCode, STATUS_OK); + t.is(result.id, city1.id); + + const teamIds = result.Teams.map(({ id }) => id); + t.truthy(teamIds.includes(team1.id)); + t.truthy(teamIds.includes(team2.id)); +}); + +test('complex include ' + + '/cities?include[]={"model":"Team", ' + + '"include":{ "model":"Player", "where":{"name": "Pinot"}, ' + + '"include":{ "model":"Master", "as":"Coach", "where":{"name": "Shifu"}}}}' + , async(t) => { + const { instances, server } = t.context; + const { city1, master1, player2, team1 } = instances; + const method = 'GET'; + const url = '/cities?include[]={"model":"Team", ' + + '"include":{ "model":"Player", "where":{"name": "Pinot"}, ' + + '"include":{ "model":"Master", "as":"Coach", "where":{"name": "Shifu"}}}}'; + + const { result, statusCode } = await server.inject({ url, method }); + t.is(statusCode, STATUS_OK); + t.is(result[0].id, city1.id); + t.is(result[0].Teams[0].id, team1.id); + t.is(result[0].Teams[0].Players[0].id, player2.id); + t.is(result[0].Teams[0].Players[0].Coach.id, master1.id); +}); diff --git a/src/crud.js b/src/crud.js index be5cc6a..f7e0c72 100644 --- a/src/crud.js +++ b/src/crud.js @@ -5,16 +5,16 @@ import _ from 'lodash'; import { parseInclude, parseWhere, parseLimitAndOffset, parseOrder } from './utils'; import { notFound } from 'boom'; import * as associations from './associations/index'; -import getConfigForMethod from './get-config-for-method.js'; +import getConfigForMethod, { sequelizeOperators } from './get-config-for-method.js'; const createAll = ({ - server, - model, - prefix, - config, - attributeValidation, - associationValidation, - scopes, + server, + model, + prefix, + config, + attributeValidation, + associationValidation, + scopes, }) => { Object.keys(methods).forEach((method) => { methods[method]({ @@ -35,27 +35,28 @@ const createAll = ({ export { associations }; /* -The `models` option, becomes `permissions`, and can look like: + The `models` option, becomes `permissions`, and can look like: -``` -models: ['cat', 'dog'] -``` + ``` + models: ['cat', 'dog'] + ``` -or + or -``` -models: { - cat: ['list', 'get'] - , dog: true // all -} -``` + ``` + models: { + cat: ['list', 'get'] + , dog: true // all + } + ``` -*/ + */ export default (server, model, { prefix, defaultConfig: config, models: permissions }) => { const modelName = model._singular; const modelAttributes = Object.keys(model.attributes); const associatedModelNames = Object.keys(model.associations); + const associatedModelAliases = _.map(model.associations, (assoc => assoc.as)); const modelAssociations = [ ...associatedModelNames, ..._.flatMap(associatedModelNames, (associationName) => { @@ -71,13 +72,28 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi return params; }, {}); - const validAssociations = modelAssociations.length - ? joi.string().valid(...modelAssociations) - : joi.valid(null); + const modelsHasAssociations = modelAssociations && modelAssociations.length; + const validAssociationsString = modelsHasAssociations + ? joi.string().valid(...modelAssociations) + : joi.valid(null); + const validAssociationsObject = modelsHasAssociations + ? joi.object().keys({ + model: joi.string().valid(...modelAssociations), + where: joi.object().keys({ + ...attributeValidation, + ...sequelizeOperators, + }), + as: joi.string().valid(...associatedModelAliases), + include: joi.any(), // @Todo: should validate the same as associationValidation var below + }) + : joi.valid(null); const associationValidation = { - include: [joi.array().items(validAssociations), validAssociations], + include: [ + joi.array().items(validAssociationsString, validAssociationsObject), + validAssociationsString, + validAssociationsObject, + ], }; - const scopes = Object.keys(model.options.scopes); // if we don't have any permissions set, just create all the methods @@ -91,11 +107,11 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi associationValidation, scopes, }); - // if permissions are set, but we can't parse them, throw an error + // if permissions are set, but we can't parse them, throw an error } else if (!Array.isArray(permissions)) { throw new Error('hapi-sequelize-crud: `models` property must be an array'); - // if permissions are set, but the only thing we've got is a model name, there - // are no permissions to be set, so just create all methods and move on + // if permissions are set, but the only thing we've got is a model name, there + // are no permissions to be set, so just create all methods and move on } else if (permissions.includes(modelName)) { createAll({ server, @@ -106,7 +122,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi associationValidation, scopes, }); - // if we've gotten here, we have complex permissions and need to set them + // if we've gotten here, we have complex permissions and need to set them } else { const permissionOptions = permissions.filter((permission) => { return permission.model === modelName; @@ -147,14 +163,14 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi } }; -export const list = ({ server, model, prefix = '/', config }) => { +export const list = async ({ server, model, prefix = '/', config }) => { server.route({ method: 'GET', path: path.join(prefix, model._plural), @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const { limit, offset } = parseLimitAndOffset(request); const order = parseOrder(request); @@ -174,14 +190,14 @@ export const list = ({ server, model, prefix = '/', config }) => { }); }; -export const get = ({ server, model, prefix = '/', config }) => { +export const get = async ({ server, model, prefix = '/', config }) => { server.route({ method: 'GET', path: path.join(prefix, model._singular, '{id?}'), @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const { id } = request.params; if (id) where[model.primaryKeyField] = id; @@ -198,14 +214,14 @@ export const get = ({ server, model, prefix = '/', config }) => { }); }; -export const scope = ({ server, model, prefix = '/', config }) => { +export const scope = async ({ server, model, prefix = '/', config }) => { server.route({ method: 'GET', path: path.join(prefix, model._plural, '{scope}'), @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); const { limit, offset } = parseLimitAndOffset(request); const order = parseOrder(request); @@ -255,9 +271,9 @@ export const destroy = ({ server, model, prefix = '/', config }) => { if (!list.length) { return void reply(id - ? notFound(`${id} not found.`) - : notFound('Nothing found.') - ); + ? notFound(`${id} not found.`) + : notFound('Nothing found.') + ); } await Promise.all(list.map(instance => instance.destroy())); @@ -284,9 +300,9 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => { if (!list.length) { return void reply(id - ? notFound(`${id} not found.`) - : notFound('Nothing found.') - ); + ? notFound(`${id} not found.`) + : notFound('Nothing found.') + ); } await Promise.all(list.map(instance => instance.destroy())); @@ -299,14 +315,14 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => { }); }; -export const destroyScope = ({ server, model, prefix = '/', config }) => { +export const destroyScope = async ({ server, model, prefix = '/', config }) => { server.route({ method: 'DELETE', path: path.join(prefix, model._plural, '{scope}'), @error async handler(request, reply) { - const include = parseInclude(request); + const include = await parseInclude(request); const where = parseWhere(request); if (include instanceof Error) return void reply(include); diff --git a/src/utils.js b/src/utils.js index 6f547b5..db69992 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,7 @@ import { omit, identity, toNumber, isString, isUndefined } from 'lodash'; import { notImplemented } from 'boom'; +import joi from 'joi'; +import Promise from 'bluebird'; const sequelizeKeys = ['include', 'order', 'limit', 'offset']; @@ -8,7 +10,7 @@ const getModels = (request) => { const noRequestModels = !request.models; if (noGetDb && noRequestModels) { return notImplemented('`request.getDb` or `request.models` are not defined.' - + 'Be sure to load hapi-sequelize before hapi-sequelize-crud.'); + + 'Be sure to load hapi-sequelize before hapi-sequelize-crud.'); } const { models } = noGetDb ? request : request.getDb(); @@ -16,31 +18,68 @@ const getModels = (request) => { return models; }; -export const parseInclude = request => { +const getModelInstance = (models, includeItem) => { + return new Promise(async(resolve) => { + if (includeItem) { + if (typeof includeItem !== 'object') { + const singluarOrPluralMatch = Object.keys(models).find((modelName) => { + const { _singular, _plural } = models[modelName]; + return _singular === includeItem || _plural === includeItem; + }); + + if (singluarOrPluralMatch) { + return resolve(models[singluarOrPluralMatch]); + } + } + + if (typeof includeItem === 'string' && models.hasOwnProperty(includeItem)) { + return resolve(models[includeItem]); + } else if (typeof includeItem === 'object') { + if ( + typeof includeItem.model === 'string' && + includeItem.model.length && + models.hasOwnProperty(includeItem.model) + ) { + includeItem.model = models[includeItem.model]; + } + if (includeItem.hasOwnProperty('include')) { + includeItem.include = await getModelInstance(models, includeItem.include); + return resolve(includeItem); + } else { + return resolve(includeItem); + } + } + } + return resolve(includeItem); + }); +}; + +export const parseInclude = async(request) => { + if (typeof request.query.include === 'undefined') return []; + const include = Array.isArray(request.query.include) - ? request.query.include - : [request.query.include] + ? request.query.include + : [request.query.include] ; const models = getModels(request); if (models.isBoom) return models; - return include.map(a => { - const singluarOrPluralMatch = Object.keys(models).find((modelName) => { - const { _singular, _plural } = models[modelName]; - return _singular === a || _plural === a; - }); - - if (singluarOrPluralMatch) return models[singluarOrPluralMatch]; - - if (typeof a === 'string') return models[a]; - - if (a && typeof a.model === 'string' && a.model.length) { - a.model = models[a.model]; + const jsonValidation = joi.string().regex(/^\{.*?"model":.*?\}$/); + const includes = include.map(async(b) => { + let a = b; + try { + if (!jsonValidation.validate(a).error) { + a = JSON.parse(b); + } + } catch (e) { + // } - return a; + return getModelInstance(models, a); }).filter(identity); + + return await Promise.all(includes); }; export const parseWhere = request => { diff --git a/test/fixtures/models/master.js b/test/fixtures/models/master.js new file mode 100644 index 0000000..9e221fd --- /dev/null +++ b/test/fixtures/models/master.js @@ -0,0 +1,18 @@ +export default (sequelize, DataTypes) => { + return sequelize.define('Master', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: DataTypes.STRING, + }, { + classMethods: { + associate: (models) => { + models.Master.hasMany(models.Player, { + foreignKey: 'coachId' + }); + }, + }, + }); +}; diff --git a/test/fixtures/models/player.js b/test/fixtures/models/player.js index 0bc1d64..cf325a5 100644 --- a/test/fixtures/models/player.js +++ b/test/fixtures/models/player.js @@ -14,6 +14,10 @@ export default (sequelize, DataTypes) => { models.Player.belongsTo(models.Team, { foreignKey: { name: 'teamId' }, }); + models.Player.belongsTo(models.Master, { + foreignKey: 'coachId', + as: 'Coach', + }); }, }, scopes: { diff --git a/test/integration-setup.js b/test/integration-setup.js index 190f958..64a5c84 100644 --- a/test/integration-setup.js +++ b/test/integration-setup.js @@ -14,15 +14,16 @@ 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' }, + { Singluar: 'Master', singular: 'master', Plural: 'Masters', plural: 'masters' }, ]; export default (test) => { - test.beforeEach('get an open port', async (t) => { + test.beforeEach('get an open port', async(t) => { t.context.port = await getPort(); }); - test.beforeEach('setup server', async (t) => { + test.beforeEach('setup server', async(t) => { const sequelize = t.context.sequelize = new Sequelize({ dialect: 'sqlite', logging: false, @@ -46,25 +47,33 @@ export default (test) => { }); await server.register({ - register: require('../src/index.js'), - options: { - name: dbName, + register: require('../src/index.js'), + options: { + name: dbName, + }, }, - }, ); }); - test.beforeEach('create data', async (t) => { - const { Player, Team, City } = t.context.sequelize.models; + test.beforeEach('create data', async(t) => { + const { Player, Master, Team, City } = t.context.sequelize.models; const city1 = await City.create({ name: 'Healdsburg' }); const team1 = await Team.create({ name: 'Baseballs', cityId: city1.id }); const team2 = await Team.create({ name: 'Footballs', cityId: city1.id }); + const master1 = await Master.create({ name: 'Shifu' }); + const master2 = await Master.create({ name: 'Oogway' }); const player1 = await Player.create({ - name: 'Cat', teamId: team1.id, active: true, + name: 'Cat', teamId: team1.id, active: true, coachId: master1.id }); - const player2 = await Player.create({ name: 'Pinot', teamId: team1.id }); - const player3 = await Player.create({ name: 'Syrah', teamId: team2.id }); - t.context.instances = { city1, team1, team2, player1, player2, player3 }; + const player2 = await Player.create({ + name: 'Pinot', teamId: team1.id, coachId: master1.id + }); + const player3 = await Player.create({ + name: 'Syrah', teamId: team2.id, coachId: master2.id + }); + t.context.instances = { + city1, team1, team2, player1, player2, player3, master1, master2 + }; }); // kill the server so that we can exit and don't leak memory