Skip to content

Commit

Permalink
feat(test): added tests for everything
Browse files Browse the repository at this point in the history
  • Loading branch information
serge1peshcoff committed Apr 28, 2019
1 parent fad3cfe commit a1e77d5
Show file tree
Hide file tree
Showing 14 changed files with 459 additions and 492 deletions.
98 changes: 0 additions & 98 deletions lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,104 +2,6 @@ const request = require('request-promise-native');

const config = require('../config');

module.exports.getMember = async (req, id) => {
const user = await request({
url: config.core.url + ':' + config.core.port + '/members/' + id,
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-Auth-Token': req.headers['x-auth-token'],
},
simple: false,
json: true
});

if (typeof user !== 'object') {
throw new Error('Malformed response when fetching users: ' + user);
}

if (!user.success) {
throw new Error('Error fetching users: ' + JSON.stringify(user));
}

return user.data;
};

module.exports.getBody = async (req, id) => {
const body = await request({
url: config.core.url + ':' + config.core.port + '/bodies/' + id,
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-Auth-Token': req.headers['x-auth-token'],
},
simple: false,
json: true
});

if (typeof body !== 'object') {
throw new Error('Malformed response when fetching bodies: ' + body);
}

if (!body.success) {
throw new Error('Error fetching body: ' + JSON.stringify(body));
}

return body.data;
};

module.exports.getApprovePermissions = async (req, event) => {
// Fetching permissions for members approval, the list of bodies
// where do you have the 'approve_members:<event_type>' permission for it.
const approveRequest = await request({
url: config.core.url + ':' + config.core.port + '/my_permissions',
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-Auth-Token': req.headers['x-auth-token'],
},
simple: false,
json: true,
body: {
action: 'approve_members',
object: event.type
}
});

if (typeof approveRequest !== 'object') {
throw new Error('Malformed response when fetching permissions for approve: ' + approveRequest);
}

if (!approveRequest.success) {
throw new Error('Error fetching permissions for approve: ' + JSON.stringify(approveRequest));
}

return approveRequest.data;
};

module.exports.getBodies = async (req) => {
const bodies = await request({
url: config.core.url + ':' + config.core.port + '/bodies',
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-Auth-Token': req.headers['x-auth-token'],
},
simple: false,
json: true
});

if (typeof bodies !== 'object') {
throw new Error('Malformed response when fetching bodies: ' + bodies);
}

if (!bodies.success) {
throw new Error('Error fetching bodies: ' + JSON.stringify(bodies));
}

return bodies.data;
};

module.exports.getMyProfile = async (req) => {
const myProfileBody = await request({
url: config.core.url + ':' + config.core.port + '/members/me',
Expand Down
1 change: 1 addition & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exports.makeError = (res, statusCode, err) => {
return res.status(statusCode).json({
success: false,
errors: err.errors.reduce((acc, val) => {
// istanbul ignore next
if (val.path in acc) {
acc[val.path].push(val.message);
} else {
Expand Down
165 changes: 4 additions & 161 deletions lib/helpers.js
Original file line number Diff line number Diff line change
@@ -1,184 +1,27 @@
const moment = require('moment');

// A helper to calculate time for plenary.
exports.calculateTimeForPlenary = (attendance, plenary) => {
if (!attendance.ends) {
return 0;
}

// We need the intersection of the attendance range and the plenary range
// because some people can come earlier to the plenary and some
// people can leave later for some reason.
const attendanceRange = moment.range(attendance.starts, attendance.ends);
const plenaryRange = moment.range(plenary.starts, plenary.ends);
const intersectRange = plenaryRange.intersect(attendanceRange);

if (!intersectRange) {
return 0;
}

const difference = intersectRange.diff('seconds', true);
return difference;
};

// A helper to calculate fee for member of memberslist with given conversion rate to EUR.
exports.calculateFeeForMember = (member, conversionRate) => {
// According to Matis (FD):
// As per the CIA, the formula for calculating the fees is "1An annual membership fee
// towards AEGEE-Europe of 25% of the part of the local annual membership fee under 30 euro
// has to be paid for each current member, with a minimum of 4 euro
// per current member plus 10% of the part of the local annual membership fee above 30 Euro"
//
// Dividing these numbers by 2 as there's 2 Agorae and locals pay fee for their
// members at each of them.

// If the fee is 0, the member doesn't pay anything to AEGEE-Europe.
if (member.fee === 0) {
return 0;
}

// First, converting to EUR.
const feeInEuro = member.fee / conversionRate;

// Then calculating fee to AEGEE-Europe using the formula above.
const feeToAEGEE = feeInEuro <= 30
? feeInEuro * 0.125 // 12.5% of fee under 30 EUR
: (30 * 0.125) + feeInEuro * 0.05; // 12.5% of 30EUR + 5% fee above 30EUR

// Minimum EUR amount is 2EUR.
return Math.max(feeToAEGEE, 2);
};

// Figure out if the value is a number or a string containing only numbers
exports.isNumber = (value) => {
/* istanbul ignore next */
if (typeof value === 'number') {
return true;
}

/* istanbul ignore else */
if (typeof value === 'string') {
const valueAsNumber = +value; // converts to number if it's all numbers or to NaN otherwise
return !Number.isNaN(valueAsNumber);
}

/* istanbul ignore next */
return false;
};

// A helper to whilelist object's properties.
exports.whitelistObject = (object, allowedFields) => {
const newObject = {};
for (const field of allowedFields) {
newObject[field] = object[field];
}

return newObject;
};

// A helper to blacklist object's properties.
exports.blacklistObject = (object, filteredFields) => {
const newObject = Object.assign({}, object);
for (const field of filteredFields) {
delete newObject[field];
}

return newObject;
};

// A helper to filter object by another object fields.
exports.filterObject = (object, targetObject) => {
for (const field in targetObject) {
if (object[field] !== targetObject[field]) {
return false;
}
}

return true;
};

// A helper to count objects in array by field.
exports.countByField = (array, key) => {
return array.reduce((acc, val) => {
const existing = acc.find(obj => obj.type === val[key]);
if (existing) {
existing.value += 1;
} else {
acc.push({ type: val[key], value: 1 });
}
return acc;
}, []);
};

// A helper to flatten the nested object. Copypasted from Google.
exports.flattenObject = (obj, prefix = '') => {
return Object.keys(obj).reduce((acc, k) => {
const pre = prefix.length ? prefix + '.' : '';
if (typeof obj[k] === 'object' && obj[k] !== null && Object.prototype.toString.call(obj[k]) !== '[object Date]') {
Object.assign(acc, exports.flattenObject(obj[k], pre + k));
} else {
acc[pre + k] = obj[k];
}

return acc;
}, {});
};

// A helper uset to pretty-format values.
exports.beautify = (value) => {
// If it's boolean, display it as Yes/No instead of true/false
if (typeof value === 'boolean') {
return value ? 'Yes' : 'No';
}

// If it's date, return date formatted.
if (Object.prototype.toString.call(value) === '[object Date]') {
return moment(value).format('YYYY-MM-DD HH:mm:ss');
}

// Else, present it as it is.
return value;
};

// A helper to check if the given application matches one of the members in memberslist.
exports.memberMatchApplication = (member, application) => {
// First, checking if user_id match.
if (member.user_id === application.user_id) {
return true;
}

// If this fails, check if the first_name and last_name match.
return member.first_name.toLowerCase() === application.first_name.toLowerCase()
&& member.last_name.toLowerCase() === application.last_name.toLowerCase();
};

// A helper to check if the memberslist has this member on it.
// Used on memberslist update and on applying/changing the application.
exports.memberslistHasMember = (memberslist, application) => {
// If no memberslist, then return false immediately.
if (!memberslist) {
return false;
}

// Otherwise, iterate through members to check if some of them match.
return memberslist.members.some(member => exports.memberMatchApplication(member, application));
};


// A helpers to determine if the user is member of a body.
exports.isMemberOf = (user, bodyId) => user.bodies.map(body => body.id).includes(bodyId);

// A helper to determine if user has permission.
function hasPermission(permissionsList, combinedPermission) {
return permissionsList.some(permission => permission.combined.endsWith(combinedPermission));
}

exports.getPermissions = (user, corePermissions) => {
return {
create_event: {
agora: hasPermission(corePermissions, 'manage_event:agora'),
epm: hasPermission(corePermissions, 'manage_event:epm')
},
edit_pax_limits: {
agora: hasPermission(corePermissions, 'manage_event:agora'),
epm: hasPermission(corePermissions, 'manage_event:epm')
}
manage_discounts: hasPermission(corePermissions, 'manage:discounts')
};
};
3 changes: 2 additions & 1 deletion lib/integrations.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { Integration } = require('../models');
const errors = require('./errors');
const helpers = require('./helpers');

exports.createIntegration = async (req, res) => {
if (!req.permissions.manage_discounts) {
Expand All @@ -24,7 +25,7 @@ exports.listAllIntegrations = async (req, res) => {
};

exports.findIntegration = async (req, res, next) => {
const isNumber = !Number.isNaN(Number(req.params.integration_id));
const isNumber = helpers.isNumber(req.params.integration_id);

const whereClause = isNumber
? { id: Number(req.params.integration_id) }
Expand Down
54 changes: 24 additions & 30 deletions lib/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,36 @@ exports.authenticateUser = async (req, res, next) => {
return errors.makeError(res, 401, 'No auth token provided');
}

try {
// Query the core for user and permissions.
const [userBody, permissionsBody] = await Promise.all([
core.getMyProfile(req),
core.getMyPermissions(req)
]);
const [userBody, permissionsBody] = await Promise.all([
core.getMyProfile(req),
core.getMyPermissions(req)
]);

if (typeof userBody !== 'object') {
throw new Error('Malformed response when fetching user: ' + userBody);
}
if (typeof userBody !== 'object') {
throw new Error('Malformed response when fetching user: ' + userBody);
}

// We only check user body here and not in the core helper
// because if not authorized, we need to return 401.
if (!userBody.success) {
// We are not authenticated
return errors.makeUnauthorizedError(res, 'Error fetching user: user is not authenticated.');
}
// We only check user body here and not in the core helper
// because if not authorized, we need to return 401.
if (!userBody.success) {
// We are not authenticated
return errors.makeUnauthorizedError(res, 'Error fetching user: user is not authenticated.');
}

if (typeof permissionsBody !== 'object') {
throw new Error('Malformed response when fetching permissions: ' + permissionsBody);
}
if (typeof permissionsBody !== 'object') {
throw new Error('Malformed response when fetching permissions: ' + permissionsBody);
}

// Same store as with user body request.
if (!permissionsBody.success) {
return errors.makeUnauthorizedError(res, 'Error fetching permissions: user is not authenticated.');
}
// Same store as with user body request.
if (!permissionsBody.success) {
return errors.makeUnauthorizedError(res, 'Error fetching permissions: user is not authenticated.');
}

req.user = userBody.data;
req.corePermissions = permissionsBody.data;
req.permissions = helpers.getPermissions(req.user, req.corePermissions);
req.user.special = ['Public']; // Everybody is included in 'Public', right?
req.user = userBody.data;
req.corePermissions = permissionsBody.data;
req.permissions = helpers.getPermissions(req.user, req.corePermissions);

return next();
} catch (err) {
return errors.makeInternalError(res, err);
}
return next();
};

/* eslint-disable no-unused-vars */
Expand Down
Loading

0 comments on commit a1e77d5

Please sign in to comment.