Added some new tests
This commit is contained in:
parent
0039a8b4ec
commit
918f873fea
15 changed files with 887 additions and 114 deletions
|
@ -21,6 +21,7 @@ testing:
|
|||
script:
|
||||
- yarn install
|
||||
- ./node_modules/.bin/sequelize db:migrate
|
||||
- ./node_modules/.bin/sequelize db:seed:all
|
||||
- yarn test --ci --collectCoverage=true --coverage
|
||||
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
|
||||
artifacts:
|
||||
|
|
|
@ -22,20 +22,20 @@ class Middleware extends QueryBuilder {
|
|||
*/
|
||||
createOne(req, callback) {
|
||||
// On test les droits
|
||||
if (!this._haveRight(req)) {
|
||||
if (!this.haveRight(req)) {
|
||||
callback(new ErrorBuilder(401.1, "You're not allowed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// On teste le body
|
||||
this._checkCreateOneValues(req, (err, value) => {
|
||||
this.checkCreateOneValues(req, (err, value) => {
|
||||
// Il manque un paramètre dans le body
|
||||
if (err) {
|
||||
callback(err, value);
|
||||
return false;
|
||||
}
|
||||
|
||||
this._insertItem(req, value, callback);
|
||||
this.insertItem(req, value, callback);
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
|
@ -50,7 +50,7 @@ class Middleware extends QueryBuilder {
|
|||
// On spécifie le type de requête (list ou item)
|
||||
req.getType = "list";
|
||||
|
||||
this._createQuery(req, (err, query, limit) => {
|
||||
this.createQuery(req, (err, query, limit) => {
|
||||
if (err) {
|
||||
callback(err, query);
|
||||
return false;
|
||||
|
@ -82,8 +82,8 @@ class Middleware extends QueryBuilder {
|
|||
const values = rows.slice(limit.start, limit.start + limit.limit);
|
||||
|
||||
callback(null, {
|
||||
data: this._formatItems(req, values),
|
||||
...QueryBuilder._createPagination(
|
||||
data: this.formatItems(req, values),
|
||||
...QueryBuilder.createPagination(
|
||||
req,
|
||||
total,
|
||||
limit.limit,
|
||||
|
@ -108,7 +108,7 @@ class Middleware extends QueryBuilder {
|
|||
// On spécifie le type de requête (list ou item)
|
||||
req.getType = "item";
|
||||
|
||||
this._createQuery(req, (err, query) => {
|
||||
this.createQuery(req, (err, query) => {
|
||||
if (err) {
|
||||
callback(err, query);
|
||||
return false;
|
||||
|
@ -122,13 +122,16 @@ class Middleware extends QueryBuilder {
|
|||
return false;
|
||||
}
|
||||
let formatedItem = item;
|
||||
if (this.params.format) {
|
||||
const formatRules = this.params.format[req.user.role];
|
||||
if (req.method === "GET") {
|
||||
if (this.params.format) {
|
||||
const formatRules = this.params.format[req.user.role];
|
||||
|
||||
if (formatRules) {
|
||||
formatedItem = this._formatItem(item, formatRules);
|
||||
if (formatRules) {
|
||||
formatedItem = this.formatItem(item, formatRules);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, formatedItem);
|
||||
return true;
|
||||
})
|
||||
|
@ -145,7 +148,7 @@ class Middleware extends QueryBuilder {
|
|||
*/
|
||||
patchOne(req, callback) {
|
||||
// On test les droits
|
||||
if (!this._haveRight(req)) {
|
||||
if (!this.haveRight(req)) {
|
||||
callback(new ErrorBuilder(401.1, "You're not allowed"));
|
||||
return false;
|
||||
}
|
||||
|
@ -170,7 +173,45 @@ class Middleware extends QueryBuilder {
|
|||
item
|
||||
.update(value)
|
||||
.then(updated => {
|
||||
callback(null, updated);
|
||||
if (this.params.belongsToMany) {
|
||||
const needCreate = [];
|
||||
let created = 0;
|
||||
const _next = errNext => {
|
||||
if (errNext) {
|
||||
// TODO: break and revert
|
||||
} else {
|
||||
created += 1;
|
||||
|
||||
if (created === needCreate.length) {
|
||||
callback(null, updated);
|
||||
}
|
||||
}
|
||||
};
|
||||
Object.keys(this.params.belongsToMany).map(key => {
|
||||
if (value[key]) {
|
||||
needCreate.push(key);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (needCreate.length > 0) {
|
||||
for (let i = 0; i < needCreate.length; i += 1) {
|
||||
const currentKey = needCreate[i];
|
||||
const currentValue = value[currentKey];
|
||||
this.createBelonsTo(
|
||||
updated,
|
||||
currentKey,
|
||||
currentValue,
|
||||
"add",
|
||||
_next
|
||||
);
|
||||
}
|
||||
} else {
|
||||
callback(null, updated);
|
||||
}
|
||||
} else {
|
||||
callback(null, updated);
|
||||
}
|
||||
})
|
||||
.catch(callback);
|
||||
return true;
|
||||
|
@ -186,7 +227,7 @@ class Middleware extends QueryBuilder {
|
|||
*/
|
||||
deleteOne(req, callback) {
|
||||
// On test les droits
|
||||
if (!this._haveRight(req)) {
|
||||
if (!this.haveRight(req)) {
|
||||
callback(new ErrorBuilder(401.1, "You're not allowed"));
|
||||
return false;
|
||||
}
|
||||
|
@ -194,16 +235,53 @@ class Middleware extends QueryBuilder {
|
|||
this.getOne(req, (err, item) => {
|
||||
if (err) {
|
||||
callback(err, item);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
let deletedBelongs = 0;
|
||||
const needToDeleteBelongs = [];
|
||||
|
||||
item
|
||||
.destroy()
|
||||
.then(() => {
|
||||
callback(null, {});
|
||||
})
|
||||
.catch(callback);
|
||||
return true;
|
||||
const _deleteItem = () => {
|
||||
item
|
||||
.destroy()
|
||||
.then(() => {
|
||||
callback(null, {});
|
||||
})
|
||||
.catch(callback);
|
||||
};
|
||||
|
||||
const _next = () => {
|
||||
deletedBelongs += 1;
|
||||
|
||||
if (deletedBelongs === needToDeleteBelongs.length) {
|
||||
_deleteItem();
|
||||
}
|
||||
};
|
||||
|
||||
if (this.params.belongsToMany) {
|
||||
Object.keys(this.params.belongsToMany).map(key => {
|
||||
const collection = this.params.belongsToMany[key];
|
||||
if (item[collection].length) {
|
||||
needToDeleteBelongs.push(key);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (needToDeleteBelongs.length > 0) {
|
||||
for (let i = 0; i < needToDeleteBelongs.length; i += 1) {
|
||||
const currentKey = needToDeleteBelongs[i];
|
||||
const collection = this.params.belongsToMany[currentKey];
|
||||
const valuesToDelete = [];
|
||||
for (let j = 0; j < item[collection].length; j += 1) {
|
||||
valuesToDelete.push(item[collection][j].id);
|
||||
}
|
||||
this.deleteBelongsTo(item, currentKey, valuesToDelete, _next);
|
||||
}
|
||||
} else {
|
||||
_deleteItem();
|
||||
}
|
||||
} else {
|
||||
_deleteItem();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class QueryBuilder {
|
|||
* @param {Number} maxPage
|
||||
* @param {String} page
|
||||
*/
|
||||
static _createLink(href, currentPage, maxPage, page) {
|
||||
static createLink(href, currentPage, maxPage, page) {
|
||||
if (
|
||||
(page === "first" && currentPage > 1) ||
|
||||
(page === "last" && currentPage < maxPage) ||
|
||||
|
@ -61,17 +61,17 @@ class QueryBuilder {
|
|||
* @param {Number} skip
|
||||
* @return {Object}
|
||||
*/
|
||||
static _createPagination(req, total, limit, skip) {
|
||||
static createPagination(req, total, limit, skip) {
|
||||
const maxPage = Math.ceil(total / limit); // Nombre total de pages
|
||||
const currentPage = Math.ceil(skip / limit) + 1; // Numéro de la page courante
|
||||
const href = `${req.protocol}://${req.get("host")}${req.originalUrl}`; // Lien vers la page actuelle
|
||||
|
||||
return {
|
||||
paging: {
|
||||
first: QueryBuilder._createLink(href, currentPage, maxPage, "first"),
|
||||
prev: QueryBuilder._createLink(href, currentPage, maxPage, "prev"),
|
||||
next: QueryBuilder._createLink(href, currentPage, maxPage, "next"),
|
||||
last: QueryBuilder._createLink(href, currentPage, maxPage, "last")
|
||||
first: QueryBuilder.createLink(href, currentPage, maxPage, "first"),
|
||||
prev: QueryBuilder.createLink(href, currentPage, maxPage, "prev"),
|
||||
next: QueryBuilder.createLink(href, currentPage, maxPage, "next"),
|
||||
last: QueryBuilder.createLink(href, currentPage, maxPage, "last")
|
||||
},
|
||||
total,
|
||||
maxPage
|
||||
|
@ -83,7 +83,7 @@ class QueryBuilder {
|
|||
* @param {Object} obj
|
||||
* @return {Object}
|
||||
*/
|
||||
_replaceKeys(obj) {
|
||||
replaceKeys(obj) {
|
||||
const { Op } = this.models.Sequelize;
|
||||
let newObject = {};
|
||||
|
||||
|
@ -91,13 +91,13 @@ class QueryBuilder {
|
|||
newObject = [];
|
||||
for (let i = 0; i < obj.length; i += 1) {
|
||||
const value =
|
||||
typeof obj[i] === "object" ? this._replaceKeys(obj[i]) : obj[i];
|
||||
typeof obj[i] === "object" ? this.replaceKeys(obj[i]) : obj[i];
|
||||
newObject.push(value);
|
||||
}
|
||||
} else {
|
||||
Object.keys(obj).map(key => {
|
||||
const value =
|
||||
typeof obj[key] === "object" ? this._replaceKeys(obj[key]) : obj[key];
|
||||
typeof obj[key] === "object" ? this.replaceKeys(obj[key]) : obj[key];
|
||||
if (key.indexOf("$") === 0 && key.slice(-1) !== "$") {
|
||||
switch (key) {
|
||||
case "$or":
|
||||
|
@ -129,8 +129,6 @@ class QueryBuilder {
|
|||
});
|
||||
}
|
||||
|
||||
console.log("newObject:", newObject);
|
||||
|
||||
return newObject;
|
||||
}
|
||||
|
||||
|
@ -139,7 +137,7 @@ class QueryBuilder {
|
|||
* @param {Object} req
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_haveRight(req) {
|
||||
haveRight(req) {
|
||||
let allowedRole = "all";
|
||||
if (!req.user) {
|
||||
req.user = {};
|
||||
|
@ -173,7 +171,7 @@ class QueryBuilder {
|
|||
* @param {Object} req
|
||||
* @return {Object}
|
||||
*/
|
||||
_restrictOn(req) {
|
||||
restrictOn(req) {
|
||||
const where = {};
|
||||
if (!this.params || !this.params.restrictOn) {
|
||||
return where;
|
||||
|
@ -220,11 +218,15 @@ class QueryBuilder {
|
|||
* @param {String} method
|
||||
* @return {Object}
|
||||
*/
|
||||
_override(req, method) {
|
||||
let override = {};
|
||||
const params = this.params.override ? this.params.override[method] : {};
|
||||
override(req, method) {
|
||||
let overrideValues = {};
|
||||
if (!this.params.override) {
|
||||
return override;
|
||||
return overrideValues;
|
||||
}
|
||||
const params = this.params.override ? this.params.override[method] : {};
|
||||
|
||||
if (!params) {
|
||||
return overrideValues;
|
||||
}
|
||||
|
||||
// On surcharge certains paramètres passé en query
|
||||
|
@ -239,7 +241,10 @@ class QueryBuilder {
|
|||
value
|
||||
)
|
||||
);
|
||||
override = Object.assign(override, this._replaceKeys(query));
|
||||
overrideValues = Object.assign(
|
||||
overrideValues,
|
||||
this.replaceKeys(query)
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
@ -249,12 +254,12 @@ class QueryBuilder {
|
|||
if (params.params) {
|
||||
for (let i = 0; i < params.params.length; i += 1) {
|
||||
const currentParam = params.params[i];
|
||||
override[currentParam.append] =
|
||||
overrideValues[currentParam.append] =
|
||||
req[currentParam.from][currentParam.value];
|
||||
}
|
||||
}
|
||||
|
||||
return override;
|
||||
return overrideValues;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -263,7 +268,7 @@ class QueryBuilder {
|
|||
* @param {Array} include
|
||||
* @return {Mixed}
|
||||
*/
|
||||
_setInclusions(req, include) {
|
||||
setInclusions(req, include) {
|
||||
if (!req.user) {
|
||||
req.user = {};
|
||||
}
|
||||
|
@ -299,10 +304,7 @@ class QueryBuilder {
|
|||
currentInclude.where = {};
|
||||
|
||||
if (current.include) {
|
||||
currentInclude.include = this._setInclusions(
|
||||
req,
|
||||
current.include
|
||||
);
|
||||
currentInclude.include = this.setInclusions(req, current.include);
|
||||
}
|
||||
|
||||
// On parcours la liste des règles d'inclusion pour ce modèle
|
||||
|
@ -338,14 +340,55 @@ class QueryBuilder {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction permettant de peupler une table de jointure (belongsToMany)
|
||||
* @param {Object} item
|
||||
* @param {String} key
|
||||
* @param {Array} values
|
||||
* @param {String} mode
|
||||
* @param {Function} callback
|
||||
*/
|
||||
createBelonsTo(item, key, values, mode, callback) {
|
||||
const collection = this.params.belongsToMany[key];
|
||||
const action = `${mode}${collection}`;
|
||||
|
||||
item[action](values)
|
||||
.then(() => {
|
||||
callback();
|
||||
})
|
||||
.catch(callback);
|
||||
}
|
||||
|
||||
deleteBelongsTo(item, key, values, callback) {
|
||||
const collection = this.params.belongsToMany[key];
|
||||
const action = `remove${collection}`;
|
||||
|
||||
item[action](values)
|
||||
.then(() => {
|
||||
callback();
|
||||
})
|
||||
.catch(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode interne permettant de créer un item
|
||||
* @param {Object} req
|
||||
* @param {Object} value
|
||||
* @param {Function} callback
|
||||
*/
|
||||
_insertItem(req, value, callback) {
|
||||
insertItem(req, value, callback) {
|
||||
const values = {};
|
||||
const _done = item => {
|
||||
let createdItem = item;
|
||||
if (this.params.format) {
|
||||
const formatRules = req.user ? this.params.format[req.user.role] : null;
|
||||
|
||||
if (formatRules) {
|
||||
createdItem = this.formatItem(item, formatRules);
|
||||
}
|
||||
}
|
||||
callback(null, createdItem);
|
||||
};
|
||||
// On converti les 'null' en null (formData par exemple)
|
||||
Object.keys(value).map(key => {
|
||||
if (value[key] === "null") {
|
||||
|
@ -360,17 +403,44 @@ class QueryBuilder {
|
|||
this.models[this.params.model]
|
||||
.create(values)
|
||||
.then(item => {
|
||||
let createdItem = item;
|
||||
if (this.params.format) {
|
||||
const formatRules = req.user
|
||||
? this.params.format[req.user.role]
|
||||
: null;
|
||||
if (this.params.belongsToMany) {
|
||||
const needCreate = [];
|
||||
let created = 0;
|
||||
const _next = err => {
|
||||
if (err) {
|
||||
// TODO: break and revert
|
||||
} else {
|
||||
created += 1;
|
||||
|
||||
if (formatRules) {
|
||||
createdItem = this._formatItem(item, formatRules);
|
||||
if (created === needCreate.length) {
|
||||
_done(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
Object.keys(this.params.belongsToMany).map(key => {
|
||||
if (values[key]) {
|
||||
needCreate.push(key);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (needCreate.length > 0) {
|
||||
for (let i = 0; i < needCreate.length; i += 1) {
|
||||
const currentKey = needCreate[i];
|
||||
this.createBelonsTo(
|
||||
item,
|
||||
currentKey,
|
||||
values[currentKey],
|
||||
"set",
|
||||
_next
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_done(item);
|
||||
}
|
||||
} else {
|
||||
_done(item);
|
||||
}
|
||||
callback(null, createdItem);
|
||||
})
|
||||
.catch(err => {
|
||||
switch (err.name) {
|
||||
|
@ -392,10 +462,11 @@ class QueryBuilder {
|
|||
* @param {Object} formatRule
|
||||
* @return {Object}
|
||||
*/
|
||||
_formatItem(item, formatRule) {
|
||||
formatItem(item, formatRule) {
|
||||
const formated = {};
|
||||
|
||||
Object.keys(formatRule).map(key => {
|
||||
// TODO: revoir ce switch
|
||||
switch (typeof formatRule[key]) {
|
||||
case "string":
|
||||
if (item) {
|
||||
|
@ -407,11 +478,11 @@ class QueryBuilder {
|
|||
formated[key] = [];
|
||||
for (let i = 0; i < item[key].length; i += 1) {
|
||||
formated[key].push(
|
||||
this._formatItem(item[key][i], formatRule[key])
|
||||
this.formatItem(item[key][i], formatRule[key])
|
||||
);
|
||||
}
|
||||
} else {
|
||||
formated[key] = this._formatItem(item[key], formatRule[key]);
|
||||
formated[key] = this.formatItem(item[key], formatRule[key]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -429,7 +500,7 @@ class QueryBuilder {
|
|||
* @param {Object} items
|
||||
* @return {Object}
|
||||
*/
|
||||
_formatItems(req, items) {
|
||||
formatItems(req, items) {
|
||||
if (!this.params.format) {
|
||||
return items;
|
||||
}
|
||||
|
@ -442,7 +513,7 @@ class QueryBuilder {
|
|||
|
||||
const formated = [];
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
formated.push(this._formatItem(items[i], formatRules));
|
||||
formated.push(this.formatItem(items[i], formatRules));
|
||||
}
|
||||
|
||||
return formated;
|
||||
|
@ -453,7 +524,7 @@ class QueryBuilder {
|
|||
* @param {Object} req
|
||||
* @param {Function} callback
|
||||
*/
|
||||
_checkCreateOneValues(req, callback) {
|
||||
checkCreateOneValues(req, callback) {
|
||||
// On regarde s'il faut surcharger les valeurs du body avec des valeurs dérivées (req.user, req.params...)
|
||||
if (this.params.override && this.params.override.create) {
|
||||
if (this.params.override.create.body) {
|
||||
|
@ -482,14 +553,14 @@ class QueryBuilder {
|
|||
* @param {Function} callback
|
||||
* @return {Boolean}
|
||||
*/
|
||||
_createQuery(req, callback) {
|
||||
this._setInclusions(req);
|
||||
createQuery(req, callback) {
|
||||
this.setInclusions(req);
|
||||
const query = {};
|
||||
let where = {};
|
||||
let order = [];
|
||||
|
||||
// On test les droits
|
||||
if (!this._haveRight(req)) {
|
||||
if (!this.haveRight(req)) {
|
||||
callback(new ErrorBuilder(401.1, "You're not allowed"));
|
||||
return false;
|
||||
}
|
||||
|
@ -564,11 +635,11 @@ class QueryBuilder {
|
|||
}
|
||||
|
||||
// S'il y a des restrictions (genre un utilisateur n'a le droit de voir que tel ou tel items)
|
||||
const restrict = this._restrictOn(req);
|
||||
const restrict = this.restrictOn(req);
|
||||
where = Object.assign(where, restrict);
|
||||
|
||||
// On regarde s'il n'y a pas des valeurs à overrider
|
||||
const override = this._override(req, req.getType);
|
||||
const override = this.override(req, req.getType);
|
||||
where = Object.assign(where, override);
|
||||
|
||||
if (order) {
|
||||
|
|
26
migrations/20200212092056-create-colors.js
Normal file
26
migrations/20200212092056-create-colors.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable("Colors", {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
down: queryInterface => {
|
||||
return queryInterface.dropTable("Colors");
|
||||
}
|
||||
};
|
39
migrations/20200212092136-create-cars-colors.js
Normal file
39
migrations/20200212092136-create-cars-colors.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable("CarsColors", {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
CarId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: "Cars",
|
||||
key: "id"
|
||||
}
|
||||
},
|
||||
ColorId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: "Colors",
|
||||
key: "id"
|
||||
}
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
down: queryInterface => {
|
||||
return queryInterface.dropTable("CarsColors");
|
||||
}
|
||||
};
|
15
migrations/20200212093136-setUniqueCarsColors.js
Normal file
15
migrations/20200212093136-setUniqueCarsColors.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
up: queryInterface => {
|
||||
return queryInterface.addConstraint("CarsColors", ["CarId", "ColorId"], {
|
||||
type: "unique",
|
||||
name: "CarsColors_unique_carId_colorId"
|
||||
});
|
||||
},
|
||||
|
||||
down: queryInterface => {
|
||||
return queryInterface.removeConstraint(
|
||||
"CarsColors",
|
||||
"CarsColors_unique_carId_colorId"
|
||||
);
|
||||
}
|
||||
};
|
|
@ -21,6 +21,7 @@ module.exports = (sequelize, DataTypes) => {
|
|||
as: "Brand",
|
||||
foreignKey: "brandId"
|
||||
});
|
||||
Cars.belongsToMany(models.Colors, { through: models.CarsColors });
|
||||
};
|
||||
|
||||
return Cars;
|
||||
|
|
24
models/carscolors.js
Normal file
24
models/carscolors.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
module.exports = (sequelize, DataTypes) => {
|
||||
const CarsColors = sequelize.define(
|
||||
"CarsColors",
|
||||
{
|
||||
CarId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
model: "Cars",
|
||||
key: "id"
|
||||
}
|
||||
},
|
||||
ColorId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
model: "Colors",
|
||||
key: "id"
|
||||
}
|
||||
}
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return CarsColors;
|
||||
};
|
11
models/colors.js
Normal file
11
models/colors.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = (sequelize, DataTypes) => {
|
||||
const Colors = sequelize.define(
|
||||
"Colors",
|
||||
{
|
||||
name: DataTypes.STRING
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return Colors;
|
||||
};
|
|
@ -12,10 +12,9 @@ const Rules = {
|
|||
{
|
||||
collection: "Brand",
|
||||
requiredRole: ["admin", "user"]
|
||||
// },
|
||||
// {
|
||||
// collection: "Colors",
|
||||
// requiredRole: ["admin", "user"]
|
||||
},
|
||||
{
|
||||
collection: "Colors"
|
||||
}
|
||||
],
|
||||
format: {
|
||||
|
@ -31,6 +30,7 @@ const Rules = {
|
|||
validate: {
|
||||
create: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
colorsId: Joi.array().items(Joi.number().integer()),
|
||||
active: Joi.boolean(),
|
||||
year: Joi.number()
|
||||
.integer()
|
||||
|
@ -38,7 +38,9 @@ const Rules = {
|
|||
brandId: Joi.number().integer()
|
||||
}),
|
||||
update: Joi.object({
|
||||
name: Joi.string()
|
||||
name: Joi.string(),
|
||||
year: Joi.number().integer(),
|
||||
colorsId: Joi.array().items(Joi.number().integer())
|
||||
}),
|
||||
item: Joi.object({
|
||||
carId: Joi.number().required()
|
||||
|
@ -119,6 +121,9 @@ const Rules = {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
belongsToMany: {
|
||||
colorsId: "Colors"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
47
seeders/20200213121737-createSomeColors.js
Normal file
47
seeders/20200213121737-createSomeColors.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
module.exports = {
|
||||
up: queryInterface => {
|
||||
return queryInterface.bulkInsert(
|
||||
"Colors",
|
||||
[
|
||||
{
|
||||
name: "Red",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: "Miami blue",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: "Sirius yellow",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: "Sunflower yellow",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: "Royal blue",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
{
|
||||
name: "White",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}
|
||||
],
|
||||
{}
|
||||
);
|
||||
},
|
||||
|
||||
down: queryInterface => {
|
||||
return Promise.all([
|
||||
queryInterface.bulkDelete("CarsColors", null, {}),
|
||||
queryInterface.bulkDelete("Colors", null, {})
|
||||
]);
|
||||
}
|
||||
};
|
286
test/belongsToMany.test.js
Normal file
286
test/belongsToMany.test.js
Normal file
|
@ -0,0 +1,286 @@
|
|||
/* eslint-disable jest/no-test-callback */
|
||||
import uuid from "uuid/v4";
|
||||
import { truncate } from "./utils/common";
|
||||
|
||||
import models from "../models";
|
||||
import Cars from "../rules/Cars";
|
||||
import Middelware from "../index";
|
||||
|
||||
const { Op } = models.Sequelize;
|
||||
|
||||
let colors = [];
|
||||
const colorsId = [];
|
||||
let car = {};
|
||||
|
||||
describe("createOne", () => {
|
||||
beforeAll(done => {
|
||||
models.Colors.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{
|
||||
name: "White"
|
||||
},
|
||||
{
|
||||
name: "Miami blue"
|
||||
},
|
||||
{
|
||||
name: "Sunflower yellow"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
.then(items => {
|
||||
colors = items;
|
||||
colorsId.push(items[0].id);
|
||||
colorsId.push(items[1].id);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
afterAll(done => {
|
||||
truncate(["Cars"], done);
|
||||
});
|
||||
|
||||
test("It should return new item after creation", async done => {
|
||||
const middleware = new Middelware(Cars, models);
|
||||
const req = {
|
||||
method: "POST",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
body: {
|
||||
name: uuid(),
|
||||
year: 2004,
|
||||
active: true,
|
||||
colorsId
|
||||
}
|
||||
};
|
||||
|
||||
middleware.createOne(req, (err, res) => {
|
||||
expect(err).toBeNull();
|
||||
|
||||
expect(res.name).toBe(req.body.name);
|
||||
expect(res.year).toBe(2004);
|
||||
expect(res.active).toBe(true);
|
||||
|
||||
car = res;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return item with relations", async done => {
|
||||
const middleware = new Middelware(Cars, models);
|
||||
const req = {
|
||||
method: "POST",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
carId: car.id
|
||||
}
|
||||
};
|
||||
|
||||
middleware.getOne(req, (err, res) => {
|
||||
expect(err).toBeNull();
|
||||
|
||||
expect(res.name).toBe(car.name);
|
||||
expect(res.Colors.length).toBe(2);
|
||||
expect(res.year).toBe(2004);
|
||||
expect(res.Colors[0].id).toBe(colorsId[0]);
|
||||
expect(res.Colors[1].id).toBe(colorsId[1]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return all items ", async done => {
|
||||
const middleware = new Middelware(Cars, models);
|
||||
const req = {
|
||||
method: "POST",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {},
|
||||
query: {},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.getAll(req, (err, res) => {
|
||||
expect(err).toBeNull();
|
||||
|
||||
expect(res.data[0].name).toBe(car.name);
|
||||
expect(res.data[0].Colors.length).toBe(2);
|
||||
expect(res.data[0].Colors[0].id).toBe(colorsId[0]);
|
||||
expect(res.data[0].Colors[1].id).toBe(colorsId[1]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return ok when update item", async done => {
|
||||
const middleware = new Middelware(Cars, models);
|
||||
const req = {
|
||||
method: "PATCH",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
carId: car.id
|
||||
},
|
||||
query: {},
|
||||
body: {
|
||||
year: 1998,
|
||||
colorsId: [colors[2].id]
|
||||
},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.patchOne(req, (err, res) => {
|
||||
expect(err).toBeNull();
|
||||
|
||||
expect(res.name).toBe(car.name);
|
||||
expect(res.year).toBe(1998);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return updated item", async done => {
|
||||
const middleware = new Middelware(Cars, models);
|
||||
const req = {
|
||||
method: "PATCH",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
carId: car.id
|
||||
},
|
||||
query: {},
|
||||
body: {},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.getOne(req, (err, res) => {
|
||||
expect(err).toBeNull();
|
||||
|
||||
expect(res.name).toBe(car.name);
|
||||
expect(res.year).toBe(1998);
|
||||
expect(res.Colors.length).toBe(3);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return empty object after delete", async done => {
|
||||
const middleware = new Middelware(Cars, models);
|
||||
const req = {
|
||||
method: "DELETE",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
carId: car.id
|
||||
},
|
||||
query: {},
|
||||
body: {},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.deleteOne(req, (err, res) => {
|
||||
expect(err).toBeNull();
|
||||
expect(res).toEqual({});
|
||||
|
||||
models.CarsColors.findAll({
|
||||
where: {
|
||||
CarId: car.id
|
||||
}
|
||||
})
|
||||
.then(carsColors => {
|
||||
expect(carsColors.length).toBe(0);
|
||||
|
||||
models.Cars.findAll({
|
||||
where: {
|
||||
id: car.id
|
||||
}
|
||||
}).then(items => {
|
||||
expect(items.length).toBe(0);
|
||||
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return empty item when delete item with empty belongsTo collection", async done => {
|
||||
const middleware = new Middelware(Cars, models);
|
||||
const req = {
|
||||
method: "POST",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
body: {
|
||||
name: uuid(),
|
||||
year: 2004,
|
||||
active: true
|
||||
}
|
||||
};
|
||||
|
||||
middleware.createOne(req, (err, newCar) => {
|
||||
expect(err).toBeNull();
|
||||
|
||||
expect(newCar.name).toBe(req.body.name);
|
||||
expect(newCar.year).toBe(2004);
|
||||
expect(newCar.active).toBe(true);
|
||||
|
||||
const reqItem = {
|
||||
method: "PATCH",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
carId: newCar.id
|
||||
},
|
||||
query: {},
|
||||
body: {
|
||||
year: 1998
|
||||
},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.patchOne(reqItem, errPatch => {
|
||||
expect(errPatch).toBeNull();
|
||||
|
||||
reqItem.method = "DELETE";
|
||||
|
||||
middleware.deleteOne(reqItem, errDelete => {
|
||||
expect(errDelete).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
163
test/patchOne.test.js
Normal file
163
test/patchOne.test.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
/* eslint-disable jest/no-test-callback */
|
||||
import { createBrand, truncate } from "./utils/common";
|
||||
|
||||
import models from "../models";
|
||||
import Brands from "../rules/Brands";
|
||||
import Middelware from "../index";
|
||||
|
||||
let brand = {};
|
||||
|
||||
describe("patchOne", () => {
|
||||
beforeAll(done => {
|
||||
createBrand((err, item) => {
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
brand = item;
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(done => {
|
||||
truncate(["Brands"], done);
|
||||
});
|
||||
|
||||
test("It should return 401.1 if guest tries to update item", async done => {
|
||||
const middleware = new Middelware(Brands, models);
|
||||
const req = {
|
||||
method: "PATCH",
|
||||
user: null,
|
||||
params: {
|
||||
brandId: brand.id
|
||||
},
|
||||
query: {},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.patchOne(req, (err, res) => {
|
||||
expect(res).toBeUndefined();
|
||||
expect(parseFloat(err.errorCode)).toBe(401.1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return 401.1 if bad role tries to update item", async done => {
|
||||
const middleware = new Middelware(Brands, models);
|
||||
const req = {
|
||||
method: "PATCH",
|
||||
user: {
|
||||
role: "user"
|
||||
},
|
||||
params: {
|
||||
brandId: brand.id
|
||||
},
|
||||
query: {},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.patchOne(req, (err, res) => {
|
||||
expect(res).toBeUndefined();
|
||||
expect(parseFloat(err.errorCode)).toBe(401.1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return 406.1 if allowed user tries to patch item with unallowed values", async done => {
|
||||
const middleware = new Middelware(Brands, models);
|
||||
const req = {
|
||||
method: "PATCH",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
brandId: brand.id
|
||||
},
|
||||
query: {},
|
||||
body: {
|
||||
created: "test",
|
||||
name: "TEST"
|
||||
},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.patchOne(req, (err, res) => {
|
||||
expect(res).toBeUndefined();
|
||||
expect(parseFloat(err.errorCode)).toBe(406.1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return updated item if allowed user patch item", async done => {
|
||||
const middleware = new Middelware(Brands, models);
|
||||
const req = {
|
||||
method: "PATCH",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
brandId: brand.id
|
||||
},
|
||||
query: {},
|
||||
body: {
|
||||
name: "TEST"
|
||||
},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.patchOne(req, (err, res) => {
|
||||
expect(err).toBeNull();
|
||||
expect(res.name).toBe("TEST");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test("It should return 404.1 if allowed user tries to patch not found item", async done => {
|
||||
const middleware = new Middelware(Brands, models);
|
||||
const req = {
|
||||
method: "PATCH",
|
||||
user: {
|
||||
role: "admin"
|
||||
},
|
||||
params: {
|
||||
brandId: brand.id + 1
|
||||
},
|
||||
query: {},
|
||||
body: {
|
||||
name: "TEST"
|
||||
},
|
||||
protocol: "http",
|
||||
get: () => {
|
||||
return "internal.test/";
|
||||
},
|
||||
originalUrl: "v1/"
|
||||
};
|
||||
|
||||
middleware.patchOne(req, (err, res) => {
|
||||
expect(res).toBeUndefined();
|
||||
expect(parseInt(err.errorCode, 10)).toBe(404);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
import uuid from "uuid/v4";
|
||||
import models from "../../models";
|
||||
import truncateDefault from "./truncate";
|
||||
|
||||
const _createCar = (brandId, active, year, done) => {
|
||||
models.Cars.create({
|
||||
|
@ -42,47 +43,7 @@ const _createBrands = (total, done) => {
|
|||
}
|
||||
};
|
||||
|
||||
const _truncateCars = (tables, done) => {
|
||||
if (tables.indexOf("Cars") === -1) {
|
||||
done(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
models.Cars.destroy({
|
||||
where: {}
|
||||
})
|
||||
.then(() => done(null))
|
||||
.catch(err => done(err));
|
||||
return true;
|
||||
};
|
||||
|
||||
const _truncateBrands = (tables, done) => {
|
||||
if (tables.indexOf("Brands") === -1) {
|
||||
done(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
models.Brands.destroy({
|
||||
where: {}
|
||||
})
|
||||
.then(() => done(null))
|
||||
.catch(err => done(err));
|
||||
return true;
|
||||
};
|
||||
|
||||
const _truncate = (tables, done) => {
|
||||
_truncateCars(tables, errCars => {
|
||||
if (errCars) {
|
||||
done(errCars);
|
||||
} else {
|
||||
_truncateBrands(tables, errBrands => {
|
||||
done(errBrands);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const createBrand = _createBrand;
|
||||
export const createBrands = _createBrands;
|
||||
export const createCar = _createCar;
|
||||
export const truncate = _truncate;
|
||||
export const truncate = truncateDefault;
|
||||
|
|
45
test/utils/truncate.js
Normal file
45
test/utils/truncate.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import models from "../../models";
|
||||
|
||||
const _truncateCars = (tables, done) => {
|
||||
if (tables.indexOf("Cars") === -1) {
|
||||
done(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
models.CarsColors.destroy({
|
||||
where: {}
|
||||
})
|
||||
.then(() => {
|
||||
models.Cars.destroy({
|
||||
where: {}
|
||||
}).then(() => done(null));
|
||||
})
|
||||
.catch(err => done(err));
|
||||
return true;
|
||||
};
|
||||
|
||||
const _truncateBrands = (tables, done) => {
|
||||
if (tables.indexOf("Brands") === -1) {
|
||||
done(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
models.Brands.destroy({
|
||||
where: {}
|
||||
})
|
||||
.then(() => done(null))
|
||||
.catch(err => done(err));
|
||||
return true;
|
||||
};
|
||||
|
||||
export default (tables, done) => {
|
||||
_truncateCars(tables, errCars => {
|
||||
if (errCars) {
|
||||
done(errCars);
|
||||
} else {
|
||||
_truncateBrands(tables, errBrands => {
|
||||
done(errBrands);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
Loading…
Reference in a new issue