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 } });
};