diff --git a/package.json b/package.json index 9886ef5..e63de32 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "dev:local": "sh scripts/run-local.sh", "dev:noseed": "concurrently 'mongod --dbpath data' 'wait-on tcp:27017 && NODE_ENV=development DEBUG=feathers nodemon server/'", "dev:win": "npm run clear && concurrently \"mongod --dbpath /data/db\" \"wait-on tcp:27017&&cross-env NODE_ENV=development&&cross-env DEBUG=feathers&& nodemon --inspect server/\"", + "refresh": "rm -rf node_modules && yarn install && yarn dev", "mocha": "npm run clear && $npm_package_config_mocha", "mocha:mongo": "$npm_package_config_concurrently '$npm_package_config_mongoDev &>/dev/null' 'wait-on tcp:27017 && npm run mocha'", "cucumber": "npm run clear && concurrently --kill-others --success first 'cross-env NODE_ENV=test node server/' 'wait-on tcp:3030 && cross-env NODE_ENV=test cucumber-js'", @@ -89,6 +90,7 @@ "handlebars-layouts": "~3.1.4", "helmet": "~3.13.0", "html-excerpt": "~0.1.0", + "human-connection-modules": "git+https://github.com/Human-Connection/Modules.git", "mime": "^2.3.1", "mongoose": "~4.13.2", "multer": "~1.3.0", diff --git a/server/helper/seed-helpers.js b/server/helper/seed-helpers.js index 0e71a8d..d91f665 100644 --- a/server/helper/seed-helpers.js +++ b/server/helper/seed-helpers.js @@ -1,5 +1,7 @@ const _ = require('lodash'); const faker = require('faker'); +const hcModules = require('human-connection-modules'); +const channelNames = hcModules.collections.socialChannels.names; const unsplashTopics = [ 'love', 'family', @@ -35,17 +37,19 @@ const ngoLogos = [ const difficulties = ['easy', 'medium', 'hard']; +const randomItem = (items, filter) => { + let ids = filter + ? Object.keys(items) + .filter(id => { + return filter(items[id]); + }) + : _.keys(items); + let randomIds = _.shuffle(ids); + return items[randomIds.pop()]; +}; + module.exports = { - randomItem: (items, filter) => { - let ids = filter - ? Object.keys(items) - .filter(id => { - return filter(items[id]); - }) - : _.keys(items); - let randomIds = _.shuffle(ids); - return items[randomIds.pop()]; - }, + randomItem, randomItems: (items, key = '_id', min = 1, max = 1) => { let randomIds = _.shuffle(_.keys(items)); let res = []; @@ -101,12 +105,25 @@ module.exports = { zipCode: faker.address.zipCode(), street: faker.address.streetAddress(), country: faker.address.countryCode(), + email: faker.internet.email(), + phone: faker.phone.phoneNumber(), lat: 54.032726 - (Math.random() * 10), lng: 6.558838 + (Math.random() * 10) }); } return addresses; }, + randomChannels: () => { + const count = Math.round(Math.random() * 3); + let channels = []; + for (let i = 0; i < count; i++) { + channels.push({ + name: faker.internet.userName(), + type: randomItem(channelNames) + }); + } + return channels; + }, /** * Get array of ids from the given seederstore items after mapping them by the key in the values * diff --git a/server/hooks/is-adminowner-or-moderator.js b/server/hooks/is-adminowner-or-moderator.js new file mode 100644 index 0000000..d7843b0 --- /dev/null +++ b/server/hooks/is-adminowner-or-moderator.js @@ -0,0 +1,30 @@ +const { getByDot } = require('feathers-hooks-common'); + +// Check if user is owner and has admin role on this item, or is moderator +module.exports = () => hook => { + if (hook.type !== 'before') { + throw new Error('The "isAdminOwnerOrModerator" hook should only be used as a "before" hook.'); + } + + if (!getByDot(hook, 'params.before')) { + throw new Error('The "isAdminOwnerOrModerator" hook should be used after the "stashBefore()" hook'); + } + + // If no user is given -> deny + if(!hook.params || !hook.params.user) { + return false; + } + + // If user is admin or moderator -> allow + if (['admin', 'moderator'].includes(hook.params.user.role)) { + return true; + } + + // If user is owner and has admin role -> allow + const userId = getByDot(hook, 'params.user._id'); + const users = getByDot(hook, 'params.before.users'); + const owner = userId && users && + users.find(({id}) => id === userId.toString()); + + return owner && owner.role === 'admin'; +}; diff --git a/server/hooks/restrictToOwnerOrModerator.js b/server/hooks/restrictToOwnerOrModerator.js index 92ef3b0..f45e254 100644 --- a/server/hooks/restrictToOwnerOrModerator.js +++ b/server/hooks/restrictToOwnerOrModerator.js @@ -18,21 +18,22 @@ module.exports = function restrictToOwnerOrModerator (query = {}) { // eslint-di const role = getByDot(hook, 'params.user.role'); const isModOrAdmin = role && ['admin', 'moderator'].includes(role); - const userId = getByDot(hook, 'params.user._id'); - const ownerId = getByDot(hook, 'params.before.userId'); - const isOwner = userId && ownerId && ownerId.toString() === userId.toString(); - // allow for mods or admins if (isModOrAdmin) { return hook; } + const userId = getByDot(hook, 'params.user._id'); + const users = getByDot(hook, 'params.before.users'); + const isOwner = userId && users && + users.some(({id}) => id === userId.toString()); + // change the query if the method is find or get if (isFindOrGet) { // restrict to owner or given query const restrictedQuery = { $or: [ - { userId }, + { 'users.id': userId }, { ...query } ] }; diff --git a/server/models/organizations.model.js b/server/models/organizations.model.js index 0ccea85..99caae5 100644 --- a/server/models/organizations.model.js +++ b/server/models/organizations.model.js @@ -2,6 +2,11 @@ // // See http://mongoosejs.com/docs/models.html // for more of what you can do here. + +const hcModules = require('human-connection-modules'); +const channelNames = hcModules.collections.socialChannels.names; +const organizationTypes = hcModules.collections.organizationTypes.names; + module.exports = function (app) { const mongooseClient = app.get('mongooseClient'); const { Schema } = mongooseClient; @@ -15,9 +20,27 @@ module.exports = function (app) { zipCode: { type: String, required: true }, city: { type: String, required: true }, country: { type: String, required: true }, + phone: { type: String }, + email: { type: String }, lat: { type: Number, required: true }, lng: { type: Number, required: true } }); + const channelSchema = mongooseClient.Schema({ + name: { type: String, required: true }, + type: { + type: String, + enum: channelNames, + required: true + } + }); + const userSchema = mongooseClient.Schema({ + id: { type: String, required: true }, + role: { + type: String, + enum: ['admin', 'editor'], + default: 'editor' + } + }); const organizations = new Schema({ name: { type: String, required: true, index: true }, slug: { type: String, required: true, unique: true, index: true }, @@ -26,18 +49,23 @@ module.exports = function (app) { categoryIds: { type: Array, required: true, index: true }, logo: { type: String }, coverImg: { type: String }, - userId: { type: String, required: true, index: true }, + creatorId: { type: String, required: true }, + users: { type: [userSchema], default: [] }, description: { type: String, required: true }, descriptionExcerpt: { type: String }, // will be generated automatically - publicEmail: { type: String }, + phone: { type: String }, + email: { type: String }, url: { type: String }, type: { type: String, + required: true, index: true, - enum: ['ngo', 'npo', 'goodpurpose', 'ev', 'eva'] + enum: organizationTypes }, language: { type: String, required: true, default: 'de', index: true }, addresses: { type: [addressSchema], default: [] }, + primaryAddressIndex: { type: Number, default: 0 }, + channels: { type: [channelSchema], default: [] }, createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now }, isEnabled: { diff --git a/server/seeder/development/organizations.js b/server/seeder/development/organizations.js index 75ef012..fc54ec9 100644 --- a/server/seeder/development/organizations.js +++ b/server/seeder/development/organizations.js @@ -1,4 +1,6 @@ const seedHelpers = require('../../helper/seed-helpers'); +const hcModules = require('human-connection-modules'); +const organizationTypes = hcModules.collections.organizationTypes.names; module.exports = (seederstore) => { let roleAdmin = ({role}) => role === 'admin'; @@ -13,11 +15,13 @@ module.exports = (seederstore) => { logo: () => seedHelpers.randomLogo(), coverImg: () => seedHelpers.randomUnsplashUrl(), categoryIds: () => seedHelpers.randomCategories(seederstore), - userId: () => seedHelpers.randomItem(seederstore.users, roleAdmin)._id, + creatorId: () => seedHelpers.randomItem(seederstore.users, roleAdmin)._id, url: '{{internet.url}}', - publicEmail: '{{internet.email}}', + phone: '{{phone.phoneNumber}}', + email: '{{internet.email}}', addresses: () => seedHelpers.randomAddresses(), - type: () => seedHelpers.randomItem(['ngo', 'npo', 'goodpurpose', 'ev', 'eva']), + channels: () => seedHelpers.randomChannels(), + type: () => seedHelpers.randomItem(['ngo', 'npo', 'goodpurpose', 'ev', 'eva', 'other']), description: '{{lorem.text}}', deletedAt: null, isEnabled: true, @@ -36,11 +40,13 @@ module.exports = (seederstore) => { logo: () => seedHelpers.randomLogo(), coverImg: () => seedHelpers.randomItem([seedHelpers.randomUnsplashUrl(), null]), categoryIds: () => seedHelpers.randomCategories(seederstore), - userId: () => seedHelpers.randomItem(seederstore.users)._id, + creatorId: () => seedHelpers.randomItem(seederstore.users)._id, url: '{{internet.url}}', - publicEmail: '{{internet.email}}', + phone: '{{phone.phoneNumber}}', + email: '{{internet.email}}', addresses: () => seedHelpers.randomAddresses(), - type: () => seedHelpers.randomItem(['ngo', 'npo', 'goodpurpose', 'ev', 'eva']), + channels: () => seedHelpers.randomChannels(), + type: () => seedHelpers.randomItem(organizationTypes), description: '{{lorem.text}}', deletedAt: null, isEnabled: true, diff --git a/server/services/organizations/hooks/can-edit-organization.js b/server/services/organizations/hooks/can-edit-organization.js index 386ed77..6050db9 100644 --- a/server/services/organizations/hooks/can-edit-organization.js +++ b/server/services/organizations/hooks/can-edit-organization.js @@ -19,7 +19,9 @@ module.exports = (options = {field: 'organizationId'}) => async hook => { const organization = await hook.app.service('organizations').get(organizationId); // only allow when the user is assigned with the organization - if (!organization || (organization && organization.userId.toString() !== currentUserId.toString())) { + if (!organization || !organization.users.some( + ({id}) => id === currentUserId.toString() + )) { throw new errors.Forbidden('you can\'t create or edit for that organization'); } diff --git a/server/services/organizations/hooks/flag-primary-address.js b/server/services/organizations/hooks/flag-primary-address.js new file mode 100644 index 0000000..6454cae --- /dev/null +++ b/server/services/organizations/hooks/flag-primary-address.js @@ -0,0 +1,14 @@ +// Add flag on primary address +const alterItems = require('../../../helper/alter-items'); + +module.exports = () => alterItems(handleItem); + +const handleItem = item => { + if (item.addresses && item.addresses[item.primaryAddressIndex]) { + item.addresses.map((address, index) => { + address.primary = index === item.primaryAddressIndex; + return address; + }); + } + return item; +}; \ No newline at end of file diff --git a/server/services/organizations/hooks/make-users-unique.js b/server/services/organizations/hooks/make-users-unique.js new file mode 100644 index 0000000..b804e20 --- /dev/null +++ b/server/services/organizations/hooks/make-users-unique.js @@ -0,0 +1,18 @@ +// Kick out every duplicate user +const alterItems = require('../../../helper/alter-items'); + +module.exports = () => alterItems(handleItem); + +const handleItem = item => { + if (item.users) { + let ids = []; + item.users = item.users.filter(user => { + if (ids.includes(user.id)) { + return false; + } + ids.push(user.id); + return true; + }); + } + return item; +}; \ No newline at end of file diff --git a/server/services/organizations/hooks/populate-users-data.js b/server/services/organizations/hooks/populate-users-data.js new file mode 100644 index 0000000..2bd173c --- /dev/null +++ b/server/services/organizations/hooks/populate-users-data.js @@ -0,0 +1,33 @@ +// Populate user data on organization users +const alterItems = require('../../../helper/alter-items'); + +module.exports = () => alterItems(handleItem); + +// Really impressed, that this works, as alterItems is not async +const handleItem = async (item, hook) => { + if (item.users) { + const userIds = item.users.map(user => user.id); + const result = await hook.app.service('users').find({ + query: { + _id: { + $in: userIds + } + }, + _populate: 'skip' + }); + const usersData = result.data; + if (!usersData) { + return item; + } + item.users = item.users.map(user => { + const userData = usersData.find( + data => data._id.toString() === user.id.toString() + ); + user.name = userData.name; + user.slug = userData.slug; + user.avatar = userData.avatar; + return user; + }); + } + return item; +}; \ No newline at end of file diff --git a/server/services/organizations/organizations.hooks.js b/server/services/organizations/organizations.hooks.js index 46528cf..d8208c1 100644 --- a/server/services/organizations/organizations.hooks.js +++ b/server/services/organizations/organizations.hooks.js @@ -1,4 +1,4 @@ -const { unless, when, isProvider, populate, softDelete, stashBefore } = require('feathers-hooks-common'); +const { unless, when, isProvider, populate, softDelete, stashBefore, discard, disallow } = require('feathers-hooks-common'); const { isVerified } = require('feathers-authentication-management').hooks; const { authenticate } = require('@feathersjs/authentication').hooks; const { associateCurrentUser } = require('feathers-authentication-hooks'); @@ -8,8 +8,12 @@ const createExcerpt = require('../../hooks/create-excerpt'); const isModerator = require('../../hooks/is-moderator-boolean'); // const excludeDisabled = require('../../hooks/exclude-disabled'); const thumbnails = require('../../hooks/thumbnails'); +const isAdminOwnerOrModerator = require('../../hooks/is-adminowner-or-moderator'); const restrictToOwnerOrModerator = require('../../hooks/restrictToOwnerOrModerator'); const restrictReviewAndEnableChange = require('../../hooks/restrictReviewAndEnableChange'); +const flagPrimaryAddress = require('./hooks/flag-primary-address'); +const makeUsersUnique = require('./hooks/make-users-unique'); +const populateUsersData = require('./hooks/populate-users-data'); const search = require('feathers-mongodb-fuzzy-search'); const isSingleItem = require('../../hooks/is-single-item'); const xss = require('../../hooks/xss'); @@ -76,11 +80,23 @@ module.exports = { ), when(isModerator(), hook => { - hook.data.reviewedBy = hook.params.user.userId; + hook.data.reviewedBy = hook.params.user._id; return hook; } ), - associateCurrentUser(), + // Users cannot be manually added on creation + discard('users'), + // Add current user as creator and user + associateCurrentUser({ as: 'creatorId' }), + hook => { + hook.data.users = [ + { + id: hook.data.creatorId, + role: 'admin' + } + ]; + return hook; + }, createSlug({ field: 'name' }), createExcerpt({ field: 'description' }), saveRemoteImages(['logo', 'coverImg']) @@ -93,6 +109,10 @@ module.exports = { stashBefore(), restrictReviewAndEnableChange(), restrictToOwnerOrModerator({ isEnabled: true }), + unless(isAdminOwnerOrModerator(), + discard('users') + ), + makeUsersUnique(), createSlug({ field: 'name', overwrite: true }), createExcerpt({ field: 'description' }), saveRemoteImages(['logo', 'coverImg']) @@ -105,6 +125,10 @@ module.exports = { stashBefore(), restrictReviewAndEnableChange(), restrictToOwnerOrModerator({ isEnabled: true }), + unless(isAdminOwnerOrModerator(), + discard('users') + ), + makeUsersUnique(), createSlug({ field: 'name', overwrite: true }), createExcerpt({ field: 'description' }), saveRemoteImages(['logo', 'coverImg']) @@ -113,14 +137,17 @@ module.exports = { authenticate('jwt'), isVerified(), stashBefore(), - restrictToOwnerOrModerator({ isEnabled: true }) + unless(isAdminOwnerOrModerator(), + disallow() + ) ] }, after: { all: [ xss({ fields: xssFields }), - populate({ schema: reviewerSchema }) + populate({ schema: reviewerSchema }), + flagPrimaryAddress() // populate({ schema: userSchema }), // populate({ schema: followerSchema }) ], @@ -131,16 +158,21 @@ module.exports = { thumbnails(thumbnailOptions) ], get: [ + populateUsersData(), populate({schema: categoriesSchema}), thumbnails(thumbnailOptions) ], create: [ + populateUsersData(), thumbnails(thumbnailOptions) ], update: [ + populateUsersData(), thumbnails(thumbnailOptions) ], - patch: [], + patch: [ + populateUsersData() + ], remove: [] }, diff --git a/test/assets/organizations.js b/test/assets/organizations.js new file mode 100644 index 0000000..44c22d6 --- /dev/null +++ b/test/assets/organizations.js @@ -0,0 +1,40 @@ +const organizationData = { + name: 'a', + description: 'My content', + type: 'other', + language: 'en' +}; + +const organizationData2 = { + name: 'b', + description: 'My content', + type: 'other', + language: 'en' +}; + +const addressData = { + street: 'street', + zipCode: '123', + city: 'city', + country: 'country', + lat: 123, + lng: 321 +}; + +const addressData2 = { + street: 'street2', + zipCode: '456', + city: 'city2', + country: 'country2', + lat: 1234, + lng: 4321 +}; + + +module.exports = { + organizationData, + organizationData2, + addressData, + addressData2 +}; + diff --git a/test/assets/users.js b/test/assets/users.js index 553c1ec..c45667e 100644 --- a/test/assets/users.js +++ b/test/assets/users.js @@ -16,7 +16,17 @@ const userData = { role: 'user' }; +const userData2 = { + email: 'test3@test3.de', + password: '1234', + name: 'Smith', + timezone: 'Europe/Berlin', + badgeIds: [], + role: 'user' +}; + module.exports = { adminData, - userData + userData, + userData2 }; diff --git a/test/services/organizations.test.js b/test/services/organizations.test.js index 3c2c864..ade37e3 100644 --- a/test/services/organizations.test.js +++ b/test/services/organizations.test.js @@ -1,10 +1,323 @@ const assert = require('assert'); const app = require('../../server/app'); +const service = app.service('organizations'); +const userService = app.service('users'); +const categoryService = app.service('categories'); +const { + userData, + userData2, + adminData +} = require('../assets/users'); +const { + organizationData, + organizationData2, + addressData, + addressData2 +} = require('../assets/organizations'); +const { categoryData } = require('../assets/categories'); describe('\'organizations\' service', () => { + let user; + let category; + let params; + + before(function(done) { + this.server = app.listen(3031); + this.server.once('listening', () => done()); + }); + + after(function(done) { + this.server.close(done); + }); + + beforeEach(async () => { + await app.get('mongooseClient').connection.dropDatabase(); + user = await userService.create(adminData); + params = { + user + }; + category = await categoryService.create(categoryData); + organizationData.categoryIds = [category._id]; + organizationData2.categoryIds = [category._id]; + }); + + afterEach(async () => { + await app.get('mongooseClient').connection.dropDatabase(); + user = null; + params = null; + delete organizationData.categoryIds; + delete organizationData2.categoryIds; + }); + it('registered the service', () => { - const service = app.service('organizations'); + assert.ok(service, 'registered the service'); + }); + + describe('organizations create', () => { + it('runs create', async () => { + const organization = await service.create(organizationData, params); + assert.ok(organization, 'created organization'); + }); + + it('has required fields after create', async () => { + const organization = await service.create(organizationData, params); + assert.ok(organization._id, 'has _id'); + assert.ok(organization.slug, 'has slug'); + assert.ok(organization.categoryIds, 'has categoryIds'); + assert.ok(organization.creatorId, 'has creatorId'); + assert.ok(organization.language, 'has language'); + assert.ok(organization.type, 'has type'); + assert.ok(organization.users, 'has users'); + assert.ok(organization.description, 'has description'); + }); + + it('has correct _id after create with _id: null in data', async () => { + organizationData._id = null; + const organization = await service.create(organizationData, params); + assert.ok(organization._id !== null, 'has _id'); + delete organizationData._id; + }); + + it('has creator as admin user', async () => { + const organization = await service.create(organizationData, params); + const firstUser = organization.users[0]; + assert.ok(firstUser, 'has one user entry'); + assert.equal( + firstUser.id, + user._id.toString(), + 'has correct user id' + ); + assert.equal( + firstUser.role, + 'admin', + 'user is admin' + ); + }); + + it('cannot add users on creation', async () => { + const user2 = await userService.create(userData2); + organizationData.users = [{ id: user2._id }]; + const organization = await service.create(organizationData, params); + const users = organization.users; + assert.equal( + users[0].id, + user._id.toString(), + 'has correct user id' + ); + assert.equal( + users[1], + undefined, + 'does not have second user' + ); + }); + }); + + describe('organizations find', () => { + beforeEach(async () => { + await service.create(organizationData, params); + }); + + it('finds organizations', async () => { + const result = await service.find(); + assert.ok(result.data[0], 'returns data'); + }); + }); + + describe('organizations find by slug', () => { + let query; + let organization; + + beforeEach(async () => { + organization = await service.create(organizationData, params); + await service.create(organizationData2, params); + query = { + slug: organization.slug + }; + }); + + afterEach(async () => { + organization = null; + query = null; + }); + + it('returns one organization', async () => { + const result = await service.find({query}); + assert.ok(result.data[0], 'returns data'); + assert.equal(result.data.length, 1), 'returns only one entry'; + }); + }); + + describe('organizations find by user', () => { + let query; + let organization; + + beforeEach(async () => { + organization = await service.create(organizationData, params); + await service.create(organizationData2, params); + query = { + 'users.id': organization.users[0].id + }; + }); + + afterEach(async () => { + organization = null; + query = null; + }); + + it('returns organizations', async () => { + const result = await service.find({query}); + assert.ok(result.data[0], 'returns data'); + assert.equal(result.data.length, 2), 'returns two entries'; + }); + }); + + describe('organizations addresses', () => { + beforeEach(async () => { + organizationData.addresses = [ + addressData, + addressData2 + ]; + }); + + afterEach(async () => { + delete organizationData.addresses; + }); + + it('first address is primary address', async () => { + const result = await service.create(organizationData, params); + const address = result.addresses[0]; + assert.ok(address, 'has address'); + assert.strictEqual( + result.primaryAddressIndex, 0, 'has primary address index' + ); + assert.strictEqual( + address.primary, true, 'address has primary flag' + ); + }); + }); + + describe('organizations users', () => { + let user2; + let organization; + + beforeEach(async () => { + user2 = await userService.create(userData2); + organization = await service.create(organizationData, params); + }); + + afterEach(async () => { + user2 = null; + organization = null; + }); + + it('a user can only be added once', async () => { + const data = { + users: [ + ...organization.users, + { id: user2._id }, + { id: user2._id } + ] + }; + const result = await service.patch(organization._id, data, params); + assert.strictEqual(result.users[2], undefined, 'has not same user twice'); + }); + }); + + describe('organizations admin roles', () => { + let user2; + let organization; + + beforeEach(async () => { + user2 = await userService.create(userData2); + organization = await service.create(organizationData, params); + }); + + afterEach(async () => { + user2 = null; + organization = null; + }); + + it('admin can add user to organization', async () => { + const data = { + users: [ + ...organization.users, + { id: user2._id } + ] + }; + const result = await service.patch(organization._id, data, params); + const newUser = result.users[1]; + assert.ok(newUser, 'has new user'); + assert.strictEqual( + newUser.id.toString(), user2._id.toString(), 'new user has correct id' + ); + }); + + it('admin can delete organization', async () => { + const result = await service.remove(organization._id, params); + assert.strictEqual(result.deleted, true, 'organization is deleted'); + }); + }); + + describe('organizations editor roles', () => { + let editor; + let user2; + let editorParams; + let organization; + + beforeEach(async () => { + editor = await userService.create(userData); + editorParams = { + user: editor + }; + organization = await service.create(organizationData, params); + const data = { + users: [ + ...organization.users, + { + id: editor._id, + role: 'editor' + } + ] + }; + organization = await service.patch(organization._id, data, params); + user2 = await userService.create(userData2); + }); + + afterEach(async () => { + editor = null; + user2 = null; + editorParams = null; + organization = null; + }); + + it('editor cannot add user to organization', async () => { + const data = { + users: [ + ...organization.users, + { id: user2._id } + ] + }; + const result = await service.patch(organization._id, data, editorParams); + assert.strictEqual(result.users[2], undefined, 'has not added user'); + }); + + it('editor cannot change roles on user', async () => { + const data = { + users: organization.users + }; + data.users[1].role = 'admin'; + const result = await service.patch(organization._id, data, editorParams); + assert.notEqual(result.users[1].role, 'admin', 'has not changed role'); + }); - assert.ok(service, 'Registered the service'); + it('editor cannot delete organization', async () => { + let error = false; + try { + await service.remove(organization._id, editorParams); + } catch (e) { + error = e; + } + assert.ok(error, 'throws an error'); + }); }); }); diff --git a/yarn.lock b/yarn.lock index 329ee68..6427690 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3661,6 +3661,10 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +"human-connection-modules@git+https://github.com/Human-Connection/Modules.git": + version "1.0.0" + resolved "git+https://github.com/Human-Connection/Modules.git#df661d2da572f1b5a330aef25fc64b8837177cbb" + iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"