From 918f873fea09f4cb7c59d2840f533166f7004d98 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Thu, 13 Feb 2020 22:36:14 +0100 Subject: [PATCH] Added some new tests --- .gitlab-ci.yml | 1 + libs/Middleware.js | 124 ++++++-- libs/QueryBuilder.js | 161 +++++++--- migrations/20200212092056-create-colors.js | 26 ++ .../20200212092136-create-cars-colors.js | 39 +++ .../20200212093136-setUniqueCarsColors.js | 15 + models/cars.js | 1 + models/carscolors.js | 24 ++ models/colors.js | 11 + rules/Cars.js | 15 +- seeders/20200213121737-createSomeColors.js | 47 +++ test/belongsToMany.test.js | 286 ++++++++++++++++++ test/patchOne.test.js | 163 ++++++++++ test/utils/common.js | 43 +-- test/utils/truncate.js | 45 +++ 15 files changed, 887 insertions(+), 114 deletions(-) create mode 100644 migrations/20200212092056-create-colors.js create mode 100644 migrations/20200212092136-create-cars-colors.js create mode 100644 migrations/20200212093136-setUniqueCarsColors.js create mode 100644 models/carscolors.js create mode 100644 models/colors.js create mode 100644 seeders/20200213121737-createSomeColors.js create mode 100644 test/belongsToMany.test.js create mode 100644 test/patchOne.test.js create mode 100644 test/utils/truncate.js diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ecbf7ef..b62538b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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: diff --git a/libs/Middleware.js b/libs/Middleware.js index 2d4bd28..507c90e 100644 --- a/libs/Middleware.js +++ b/libs/Middleware.js @@ -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; } diff --git a/libs/QueryBuilder.js b/libs/QueryBuilder.js index 058c022..2162aca 100644 --- a/libs/QueryBuilder.js +++ b/libs/QueryBuilder.js @@ -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) { diff --git a/migrations/20200212092056-create-colors.js b/migrations/20200212092056-create-colors.js new file mode 100644 index 0000000..4093aa7 --- /dev/null +++ b/migrations/20200212092056-create-colors.js @@ -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"); + } +}; diff --git a/migrations/20200212092136-create-cars-colors.js b/migrations/20200212092136-create-cars-colors.js new file mode 100644 index 0000000..14b2ab2 --- /dev/null +++ b/migrations/20200212092136-create-cars-colors.js @@ -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"); + } +}; diff --git a/migrations/20200212093136-setUniqueCarsColors.js b/migrations/20200212093136-setUniqueCarsColors.js new file mode 100644 index 0000000..0b4039e --- /dev/null +++ b/migrations/20200212093136-setUniqueCarsColors.js @@ -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" + ); + } +}; diff --git a/models/cars.js b/models/cars.js index 0abf8d7..970bfac 100644 --- a/models/cars.js +++ b/models/cars.js @@ -21,6 +21,7 @@ module.exports = (sequelize, DataTypes) => { as: "Brand", foreignKey: "brandId" }); + Cars.belongsToMany(models.Colors, { through: models.CarsColors }); }; return Cars; diff --git a/models/carscolors.js b/models/carscolors.js new file mode 100644 index 0000000..0b62a43 --- /dev/null +++ b/models/carscolors.js @@ -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; +}; diff --git a/models/colors.js b/models/colors.js new file mode 100644 index 0000000..07d76df --- /dev/null +++ b/models/colors.js @@ -0,0 +1,11 @@ +module.exports = (sequelize, DataTypes) => { + const Colors = sequelize.define( + "Colors", + { + name: DataTypes.STRING + }, + {} + ); + + return Colors; +}; diff --git a/rules/Cars.js b/rules/Cars.js index b9a9d3d..6c4ac55 100644 --- a/rules/Cars.js +++ b/rules/Cars.js @@ -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" } }; diff --git a/seeders/20200213121737-createSomeColors.js b/seeders/20200213121737-createSomeColors.js new file mode 100644 index 0000000..9797000 --- /dev/null +++ b/seeders/20200213121737-createSomeColors.js @@ -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, {}) + ]); + } +}; diff --git a/test/belongsToMany.test.js b/test/belongsToMany.test.js new file mode 100644 index 0000000..028942d --- /dev/null +++ b/test/belongsToMany.test.js @@ -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(); + }); + }); + }); + }); +}); diff --git a/test/patchOne.test.js b/test/patchOne.test.js new file mode 100644 index 0000000..833d99a --- /dev/null +++ b/test/patchOne.test.js @@ -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(); + }); + }); +}); diff --git a/test/utils/common.js b/test/utils/common.js index 4ca535c..e99c934 100644 --- a/test/utils/common.js +++ b/test/utils/common.js @@ -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; diff --git a/test/utils/truncate.js b/test/utils/truncate.js new file mode 100644 index 0000000..9e0a706 --- /dev/null +++ b/test/utils/truncate.js @@ -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); + }); + } + }); +};