diff --git a/CHANGELOG.md b/CHANGELOG.md index 4150d188..3186c5dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# [0.5.0](https://github.com/AEGEE/oms-discounts/compare/0.4.2...0.5.0) (2019-05-18) + + +### Bug Fixes + +* **categories:** fixed categories endpoints ([08320a0](https://github.com/AEGEE/oms-discounts/commit/08320a0)) + + +### Features + +* **discounts:** added categories. Fixes MEMB-529 ([67d7380](https://github.com/AEGEE/oms-discounts/commit/67d7380)) +* **test:** testing categories CRUD ([53af8e7](https://github.com/AEGEE/oms-discounts/commit/53af8e7)) + + + ## [0.4.2](https://github.com/AEGEE/oms-discounts/compare/0.4.1...0.4.2) (2019-05-03) diff --git a/lib/categories.js b/lib/categories.js new file mode 100644 index 00000000..5dae2c5e --- /dev/null +++ b/lib/categories.js @@ -0,0 +1,75 @@ +const { Category } = require('../models'); +const errors = require('./errors'); +const helpers = require('./helpers'); + +exports.createCategory = async (req, res) => { + if (!req.permissions.manage_discounts) { + return errors.makeForbiddenError(res, 'You are not allowed to create integrations.'); + } + + const category = await Category.create(req.body); + + return res.json({ + success: true, + data: category + }); +}; + +exports.listAllCategories = async (req, res) => { + const categories = await Category.findAll({}); + + return res.json({ + success: true, + data: categories + }); +}; + +exports.findCategory = async (req, res, next) => { + const isNumber = helpers.isNumber(req.params.category_id); + + if (!isNumber) { + return errors.makeBadRequestError(res, 'The category ID is not a number.'); + } + + const category = await Category.findOne({ where: { id: Number(req.params.category_id) } }); + + if (!category) { + return errors.makeNotFoundError(res, 'The category is not found.'); + } + + req.category = category; + return next(); +}; + +exports.getCategory = async (req, res) => { + return res.json({ + success: true, + data: req.category + }); +}; + +exports.updateCategory = async (req, res) => { + if (!req.permissions.manage_discounts) { + return errors.makeForbiddenError(res, 'You are not allowed to update category.'); + } + + await req.category.update(req.body); + + return res.json({ + success: true, + data: req.category + }); +}; + +exports.deleteCategory = async (req, res) => { + if (!req.permissions.manage_discounts) { + return errors.makeForbiddenError(res, 'You are not allowed to delete category.'); + } + + await req.category.destroy(); + + return res.json({ + success: true, + data: req.category + }); +}; diff --git a/lib/helpers.js b/lib/helpers.js index 18c46efd..8a845864 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -17,7 +17,7 @@ exports.isNumber = (value) => { return false; }; -exports.getMailText = ({ code, integration, user}) => { +exports.getMailText = ({ code, integration, user }) => { return `Hey ${user.first_name},

You've claimed the code for the discount, here are the details.
@@ -29,7 +29,7 @@ Claimed on: ${moment(code.updated_at).format('YYYY-MM-DD HH:MM')}
${integration.description}

Sincerely yours,
-MyAEGEE discounts team.` +MyAEGEE discounts team.`; }; // A helper to determine if user has permission. diff --git a/lib/integrations.js b/lib/integrations.js index 54c6ea02..358e297d 100644 --- a/lib/integrations.js +++ b/lib/integrations.js @@ -67,7 +67,7 @@ exports.updateIntegration = async (req, res) => { exports.deleteIntegration = async (req, res) => { if (!req.permissions.manage_discounts) { - return errors.makeForbiddenError(res, 'You are not allowed to update integration.'); + return errors.makeForbiddenError(res, 'You are not allowed to delete integration.'); } await req.integration.destroy(); diff --git a/lib/server.js b/lib/server.js index de55d4f1..2e2085fe 100644 --- a/lib/server.js +++ b/lib/server.js @@ -8,6 +8,7 @@ const bugsnag = require('./bugsnag'); const morgan = require('./morgan'); const middlewares = require('./middlewares'); const integrations = require('./integrations'); +const categories = require('./categories'); const db = require('./sequelize'); const server = express(); @@ -26,6 +27,8 @@ process.on('unhandledRejection', (err) => { }); GeneralRouter.use(middlewares.authenticateUser); + +// integrations and codes GeneralRouter.get('/integrations', integrations.listAllIntegrations); GeneralRouter.post('/integrations', integrations.createIntegration); GeneralRouter.post('/integrations/:integration_id/codes', integrations.findIntegration, integrations.addCodesToIntegration); @@ -36,6 +39,13 @@ GeneralRouter.delete('/integrations/:integration_id', integrations.findIntegrati GeneralRouter.get('/codes/mine', integrations.getMyCodes); +// categories and discounts (for listing to members) +GeneralRouter.get('/categories', categories.listAllCategories); +GeneralRouter.post('/categories', categories.createCategory); +GeneralRouter.get('/categories/:category_id', categories.findCategory, categories.getCategory); +GeneralRouter.put('/categories/:category_id', categories.findCategory, categories.updateCategory); +GeneralRouter.delete('/categories/:category_id', categories.findCategory, categories.deleteCategory); + server.use('/', GeneralRouter); server.use(middlewares.notFound); server.use(middlewares.errorHandler); diff --git a/migrations/20190518200032-create-category.js b/migrations/20190518200032-create-category.js new file mode 100644 index 00000000..af4e4655 --- /dev/null +++ b/migrations/20190518200032-create-category.js @@ -0,0 +1,27 @@ +module.exports = { + up: (queryInterface, Sequelize) => queryInterface.createTable('categories', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER + }, + name: { + allowNull: false, + type: Sequelize.STRING + }, + discounts: { + allowNull: false, + type: Sequelize.JSONB + }, + created_at: { + allowNull: false, + type: Sequelize.DATE + }, + updated_at: { + allowNull: false, + type: Sequelize.DATE + } + }), + down: queryInterface => queryInterface.dropTable('categories') +}; diff --git a/models/Category.js b/models/Category.js new file mode 100644 index 00000000..f694348d --- /dev/null +++ b/models/Category.js @@ -0,0 +1,38 @@ +const Joi = require('joi'); +const { Sequelize, sequelize } = require('../lib/sequelize'); + +const categoriesSchema = Joi.array().min(1).items(Joi.object().keys({ + icon: Joi.string().trim().required(), + name: Joi.string().trim().required(), + shortDescription: Joi.string().trim().required(), + longDescription: Joi.string().trim().required(), +})); + +const Category = sequelize.define('category', { + name: { + type: Sequelize.STRING, + allowNull: false, + defaultValue: '', + validate: { + notEmpty: { msg: 'Name should be set.' }, + }, + }, + discounts: { + type: Sequelize.JSONB, + allowNull: false, + defaultValue: '', + validate: { + isValid(categoriesValue) { + const { error, value } = Joi.validate(categoriesValue, categoriesSchema); + if (error) { + throw error; + } + + // eslint-disable-next-line no-param-reassign + categoriesValue = value; + } + }, + } +}, { underscored: true, tableName: 'categories' }); + +module.exports = Category; diff --git a/models/index.js b/models/index.js index 64e7caa7..ec2fd3a1 100644 --- a/models/index.js +++ b/models/index.js @@ -1,10 +1,12 @@ const Integration = require('./Integration'); const Code = require('./Code'); +const Category = require('./Category'); Integration.hasMany(Code); Code.belongsTo(Integration); module.exports = { Integration, - Code + Code, + Category }; diff --git a/package-lock.json b/package-lock.json index a16e02b7..335318fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "oms-discounts", - "version": "0.4.2", + "version": "0.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -3176,7 +3176,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3197,12 +3198,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3217,17 +3220,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3344,7 +3350,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3356,6 +3363,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3370,6 +3378,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3377,12 +3386,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3401,6 +3412,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3481,7 +3493,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3493,6 +3506,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3578,7 +3592,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -3614,6 +3629,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3633,6 +3649,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3676,12 +3693,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -3901,6 +3920,11 @@ "is-stream": "^1.0.1" } }, + "hoek": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.3.tgz", + "integrity": "sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ==" + }, "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", @@ -4492,6 +4516,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "requires": { + "punycode": "2.x.x" + } + }, "iserror": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/iserror/-/iserror-0.0.2.tgz", @@ -5070,6 +5102,16 @@ } } }, + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, "js-beautify": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.9.1.tgz", @@ -8094,6 +8136,14 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "topo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", + "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", + "requires": { + "hoek": "6.x.x" + } + }, "toposort-class": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", diff --git a/package.json b/package.json index e388c6ed..26f93dc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oms-discounts", - "version": "0.4.2", + "version": "0.5.0", "description": "Discounts module for OMS, for populating codes and for distributing them to AEGEE members.", "main": "lib/run.js", "scripts": { @@ -62,6 +62,7 @@ "deep-assign": "^3.0.0", "express": "^4.16.4", "express-promise-router": "^3.0.3", + "joi": "^14.3.1", "moment": "^2.24.0", "morgan": "^1.9.1", "pg": "^7.10.0", diff --git a/test/api/categories-creation.test.js b/test/api/categories-creation.test.js new file mode 100644 index 00000000..5d4ac435 --- /dev/null +++ b/test/api/categories-creation.test.js @@ -0,0 +1,194 @@ +const { startServer, stopServer } = require('../../lib/server.js'); +const { request } = require('../scripts/helpers'); +const mock = require('../scripts/mock'); +const generator = require('../scripts/generator'); +const { Category } = require('../../models'); + +describe('Categories creation', () => { + beforeEach(async () => { + mock.mockAll(); + await startServer(); + }); + + afterEach(async () => { + await stopServer(); + mock.cleanAll(); + + await generator.clearAll(); + }); + + test('should fail if user does not have rights', async () => { + mock.mockAll({ mainPermissions: { noPermissions: true } }); + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory() + }); + + expect(res.statusCode).toEqual(403); + expect(res.body.success).toEqual(false); + }); + + test('should succeed if everything is okay', async () => { + const category = generator.generateCategory({ + name: 'test', + discounts: [ + generator.generateDiscount({ + name: 'testtest', + icon: 'fa-icon', + shortDescription: 'short', + longDescription: 'long' + }) + ] + }); + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: category + }); + + expect(res.statusCode).toEqual(200); + expect(res.body.success).toEqual(true); + expect(res.body).not.toHaveProperty('errors'); + expect(res.body).toHaveProperty('data'); + + expect(res.body.data).toHaveProperty('id'); + expect(res.body.data.name).toEqual('test'); + expect(res.body.data.discounts.length).toEqual(1); + expect(res.body.data.discounts[0].name).toEqual('testtest'); + expect(res.body.data.discounts[0].icon).toEqual('fa-icon'); + expect(res.body.data.discounts[0].shortDescription).toEqual('short'); + expect(res.body.data.discounts[0].longDescription).toEqual('long'); + + const categoryFromDB = await Category.findOne({ where: { id: res.body.data.id } }); + + expect(categoryFromDB.name).toEqual('test'); + expect(categoryFromDB.discounts.length).toEqual(1); + expect(categoryFromDB.discounts[0].name).toEqual('testtest'); + expect(categoryFromDB.discounts[0].icon).toEqual('fa-icon'); + expect(categoryFromDB.discounts[0].shortDescription).toEqual('short'); + expect(categoryFromDB.discounts[0].longDescription).toEqual('long'); + }); + + test('should fail if name is not set', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ name: null }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('name'); + }); + + test('should fail if discounts is not an array', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ discounts: false }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('discounts'); + }); + + test('should fail if discounts is an empty array', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ discounts: [] }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('discounts'); + }); + + test('should fail if discount is not an object', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ discounts: [false] }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('discounts'); + }); + + test('should fail if discount\'s name is not set', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ discounts: [ + generator.generateDiscount({ name: null }) + ] }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('discounts'); + }); + + test('should fail if discount\'s icon is not set', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ discounts: [ + generator.generateDiscount({ icon: null }) + ] }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('discounts'); + }); + + test('should fail if discount\'s shortDescription is not set', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ discounts: [ + generator.generateDiscount({ shortDescription: null }) + ] }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('discounts'); + }); + + test('should fail if discount\'s longDescription is not set', async () => { + const res = await request({ + uri: '/categories', + method: 'POST', + headers: { 'X-Auth-Token': 'blablabla' }, + body: generator.generateCategory({ discounts: [ + generator.generateDiscount({ longDescription: null }) + ] }) + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('discounts'); + }); +}); diff --git a/test/api/categories-deletion.test.js b/test/api/categories-deletion.test.js new file mode 100644 index 00000000..a56595c8 --- /dev/null +++ b/test/api/categories-deletion.test.js @@ -0,0 +1,64 @@ +const { startServer, stopServer } = require('../../lib/server.js'); +const { request } = require('../scripts/helpers'); +const mock = require('../scripts/mock'); +const generator = require('../scripts/generator'); +const { Category } = require('../../models'); + +describe('Categories deletion', () => { + beforeEach(async () => { + mock.mockAll(); + await startServer(); + }); + + afterEach(async () => { + await stopServer(); + mock.cleanAll(); + + await generator.clearAll(); + }); + + test('should fail if user does not have rights', async () => { + mock.mockAll({ mainPermissions: { noPermissions: true } }); + + const category = await generator.createCategory(); + + const res = await request({ + uri: '/categories/' + category.id, + method: 'DELETE', + headers: { 'X-Auth-Token': 'blablabla' } + }); + + expect(res.statusCode).toEqual(403); + expect(res.body.success).toEqual(false); + }); + + test('should succeed if everything is okay', async () => { + const category = await generator.createCategory(); + const res = await request({ + uri: '/categories/' + category.id, + method: 'DELETE', + headers: { 'X-Auth-Token': 'blablabla' } + }); + + expect(res.statusCode).toEqual(200); + expect(res.body.success).toEqual(true); + expect(res.body).not.toHaveProperty('errors'); + expect(res.body).toHaveProperty('data'); + + const categoryFromDb = await Category.findByPk(category.id); + expect(categoryFromDb).toBeFalsy(); + }); + + test('should return 404 if the integration is not found', async () => { + const res = await request({ + uri: '/categories/1337', + method: 'DELETE', + headers: { 'X-Auth-Token': 'blablabla' } + }); + + expect(res.statusCode).toEqual(404); + expect(res.body.success).toEqual(false); + expect(res.body).not.toHaveProperty('data'); + expect(res.body).toHaveProperty('message'); + }); +}); diff --git a/test/api/categories-display.test.js b/test/api/categories-display.test.js new file mode 100644 index 00000000..ca84a285 --- /dev/null +++ b/test/api/categories-display.test.js @@ -0,0 +1,60 @@ +const { startServer, stopServer } = require('../../lib/server.js'); +const { request } = require('../scripts/helpers'); +const mock = require('../scripts/mock'); +const generator = require('../scripts/generator'); + +describe('Categories displaying', () => { + beforeEach(async () => { + mock.mockAll(); + await startServer(); + }); + + afterEach(async () => { + await stopServer(); + mock.cleanAll(); + + await generator.clearAll(); + }); + + test('should succeed if everything is okay', async () => { + const category = await generator.createCategory(); + const res = await request({ + uri: '/categories/' + category.id, + method: 'GET', + headers: { 'X-Auth-Token': 'blablabla' } + }); + + expect(res.statusCode).toEqual(200); + expect(res.body.success).toEqual(true); + expect(res.body).not.toHaveProperty('errors'); + expect(res.body).toHaveProperty('data'); + + expect(res.body.data.id).toEqual(category.id); + }); + + test('should return 404 if the category is not found', async () => { + const res = await request({ + uri: '/categories/1337', + method: 'GET', + headers: { 'X-Auth-Token': 'blablabla' } + }); + + expect(res.statusCode).toEqual(404); + expect(res.body.success).toEqual(false); + expect(res.body).not.toHaveProperty('data'); + expect(res.body).toHaveProperty('message'); + }); + + test('should return 400 if category ID is NaN', async () => { + const res = await request({ + uri: '/categories/false', + method: 'GET', + headers: { 'X-Auth-Token': 'blablabla' } + }); + + expect(res.statusCode).toEqual(400); + expect(res.body.success).toEqual(false); + expect(res.body).not.toHaveProperty('data'); + expect(res.body).toHaveProperty('message'); + }); +}); diff --git a/test/api/categories-edition.test.js b/test/api/categories-edition.test.js new file mode 100644 index 00000000..0514e2d6 --- /dev/null +++ b/test/api/categories-edition.test.js @@ -0,0 +1,94 @@ +const { startServer, stopServer } = require('../../lib/server.js'); +const { request } = require('../scripts/helpers'); +const mock = require('../scripts/mock'); +const generator = require('../scripts/generator'); + +describe('Categories edition', () => { + beforeEach(async () => { + mock.mockAll(); + await startServer(); + }); + + afterEach(async () => { + await stopServer(); + mock.cleanAll(); + + await generator.clearAll(); + }); + + test('should fail if user does not have rights', async () => { + mock.mockAll({ mainPermissions: { noPermissions: true } }); + + const category = await generator.createCategory(); + + const res = await request({ + uri: '/categories/' + category.id, + method: 'PUT', + headers: { 'X-Auth-Token': 'blablabla' }, + body: { name: 'test' } + }); + + expect(res.statusCode).toEqual(403); + expect(res.body.success).toEqual(false); + }); + + test('should succeed if everything is okay', async () => { + const category = await generator.createCategory(); + const res = await request({ + uri: '/categories/' + category.id, + method: 'PUT', + headers: { 'X-Auth-Token': 'blablabla' }, + body: { name: 'test' } + }); + + expect(res.statusCode).toEqual(200); + expect(res.body.success).toEqual(true); + expect(res.body).not.toHaveProperty('errors'); + expect(res.body).toHaveProperty('data'); + + expect(res.body.data.name).toEqual('test'); + }); + + test('should fail on validation errors', async () => { + const category = await generator.createCategory(); + const res = await request({ + uri: '/categories/' + category.id, + method: 'PUT', + headers: { 'X-Auth-Token': 'blablabla' }, + body: { name: null } + }); + + expect(res.statusCode).toEqual(422); + expect(res.body.success).toEqual(false); + expect(res.body).toHaveProperty('errors'); + expect(res.body.errors).toHaveProperty('name'); + }); + + test('should return 404 if the integration is not found', async () => { + const res = await request({ + uri: '/categories/1337', + method: 'PUT', + headers: { 'X-Auth-Token': 'blablabla' }, + body: { name: 'test' } + }); + + expect(res.statusCode).toEqual(404); + expect(res.body.success).toEqual(false); + expect(res.body).not.toHaveProperty('data'); + expect(res.body).toHaveProperty('message'); + }); + + test('should return 400 if the integration ID is NaN', async () => { + const res = await request({ + uri: '/categories/false', + method: 'PUT', + headers: { 'X-Auth-Token': 'blablabla' }, + body: { name: 'test' } + }); + + expect(res.statusCode).toEqual(400); + expect(res.body.success).toEqual(false); + expect(res.body).not.toHaveProperty('data'); + expect(res.body).toHaveProperty('message'); + }); +}); diff --git a/test/api/categories-listing.test.js b/test/api/categories-listing.test.js new file mode 100644 index 00000000..61784c21 --- /dev/null +++ b/test/api/categories-listing.test.js @@ -0,0 +1,35 @@ +const { startServer, stopServer } = require('../../lib/server.js'); +const { request } = require('../scripts/helpers'); +const mock = require('../scripts/mock'); +const generator = require('../scripts/generator'); + +describe('Categories listing', () => { + beforeEach(async () => { + mock.mockAll(); + await startServer(); + }); + + afterEach(async () => { + await stopServer(); + mock.cleanAll(); + + await generator.clearAll(); + }); + + test('should succeed and list categories', async () => { + const category = await generator.createCategory(); + const res = await request({ + uri: '/categories', + method: 'GET', + headers: { 'X-Auth-Token': 'blablabla' } + }); + + expect(res.statusCode).toEqual(200); + expect(res.body.success).toEqual(true); + expect(res.body).not.toHaveProperty('errors'); + expect(res.body).toHaveProperty('data'); + + expect(res.body.data.length).toEqual(1); + expect(res.body.data[0].id).toEqual(category.id); + }); +}); diff --git a/test/scripts/generator.js b/test/scripts/generator.js index fb03709a..552f256d 100644 --- a/test/scripts/generator.js +++ b/test/scripts/generator.js @@ -2,7 +2,8 @@ const faker = require('faker'); const { Integration, - Code + Code, + Category } = require('../../models'); const notSet = field => typeof field === 'undefined'; @@ -26,6 +27,25 @@ exports.generateCode = (options = {}, integration = null) => { return options; }; +exports.generateDiscount = (options = {}) => { + if (notSet(options.name)) options.name = faker.random.word(); + if (notSet(options.icon)) options.icon = faker.random.word(); + if (notSet(options.shortDescription)) options.shortDescription = faker.lorem.sentence(); + if (notSet(options.longDescription)) options.longDescription = faker.lorem.sentence(); + + return options; +}; + +exports.generateCategory = (options = {}) => { + if (notSet(options.name)) options.name = faker.random.word(); + if (notSet(options.discounts)) { + const discountsCount = Math.round(Math.random() * 5) + 1; // from 1 to 6 + options.discounts = Array.from({ length: discountsCount }, () => exports.generateDiscount()); + } + + return options; +}; + exports.createIntegration = (options = {}) => { return Integration.create(exports.generateIntegration(options)); }; @@ -34,7 +54,12 @@ exports.createCode = (options = {}, integration = null) => { return Code.create(exports.generateCode(options, integration)); }; +exports.createCategory = (options = {}) => { + return Category.create(exports.generateCategory(options)); +}; + exports.clearAll = async () => { await Code.destroy({ where: {}, truncate: { cascade: true } }); await Integration.destroy({ where: {}, truncate: { cascade: true } }); + await Category.destroy({ where: {}, truncate: { cascade: true } }); };