diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6627203b17d769b8719beda1b188da1b25198643..6a9870968e93df0c20c5b054fc6afb5fabe2c78e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,213 +1,68 @@ -# variables: -# IMAGE_ORG: xpub -# IMAGE_NAME: xpub -# BASE_DOMAIN: gateway.xpub.semioticsquares.com -# CONFIGURATION_REPOSITORY: https://gitlab.coko.foundation/xpub/deployment-config.git +variables: + IMAGE_ORG: bogdandev + IMAGE_NAME: xpub-faraday + REPO_URL: https://gitlab.coko.foundation/xpub/xpub-faraday -# stages: -# - build -# - test -# - review -# - staging -# - production -# - demo +stages: + - build + - test + - review + - docker + - demo -# build: -# image: docker:latest -# stage: build -# script: -# - docker version -# - docker build -t $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA . -# - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi -# - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD -# - echo "Ignore warning! Cannot perform an interactive login from a non TTY device" -# - docker push $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA +build: + image: docker:latest + stage: build + script: + - docker version + - docker build -t $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA . + - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi + - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD + - echo "Ignore warning! Cannot perform an interactive login from a non TTY device" + - docker push $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA -# lint: -# image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA -# stage: test -# variables: -# GIT_STRATEGY: none -# script: -# - cd ${HOME} -# - npm run lint +lint: + image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA + stage: test + variables: + GIT_STRATEGY: none + script: + - cd ${HOME} + - npm run lint -# test: -# image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA -# stage: test -# variables: -# GIT_STRATEGY: none -# script: -# - cd ${HOME} -# - npm run test +test: + image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA + stage: test + variables: + GIT_STRATEGY: none + script: + - cd ${HOME} + - npm run test -# push:latest: -# image: docker:latest -# stage: staging -# script: -# - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi -# - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD -# - echo "Ignore warning! Cannot perform an interactive login from a non TTY device" -# - docker build -t $IMAGE_ORG/$IMAGE_NAME:latest --label COMMIT_SHA=$CI_COMMIT_SHA . -# - docker push $IMAGE_ORG/$IMAGE_NAME:latest -# only: -# - master +push:latest: + image: docker:latest + stage: docker + script: + - if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_PASSWORD" ]; then echo "Not pushing" && exit 0; fi + - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD + - echo "Ignore warning! Cannot perform an interactive login from a non TTY device" + - docker build -t $IMAGE_ORG/$IMAGE_NAME:latest --label COMMIT_SHA=$CI_COMMIT_SHA . + - docker push $IMAGE_ORG/$IMAGE_NAME:latest + only: + - master -# # ----------------------------------------------- -# # xpub-collabra --------------------------------- -# # ----------------------------------------------- - -# review:xpub-collabra: -# image: pubsweet/deployer:latest -# stage: review -# variables: -# PACKAGE_NAME: xpub-collabra -# FORCE_FRESH_DB: "yes" -# REQUIRES_PROVISIONING: "yes" -# environment: -# name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME -# # !! kube-lego will fail if domain > 64 chars -# url: "http://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# on_stop: stop_review:xpub-collabra -# except: -# - master -# script: -# - source deploy.sh -# - create_deployment - -# stop_review:xpub-collabra: -# image: pubsweet/deployer:latest -# stage: review -# variables: -# PACKAGE_NAME: xpub-collabra -# REQUIRES_PROVISIONING: "yes" -# GIT_STRATEGY: none -# environment: -# name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME -# action: stop -# when: manual -# except: -# - master -# script: -# - source deploy.sh -# - delete_deployment -# - delete_objects_in_environment pvc - -# staging:xpub-collabra: -# image: pubsweet/deployer:latest -# stage: staging -# variables: -# PACKAGE_NAME: xpub-collabra -# environment: -# name: $PACKAGE_NAME/staging -# url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# only: -# - master -# script: -# - source deploy.sh -# - create_deployment - -# production:xpub-collabra: -# image: pubsweet/deployer:latest -# stage: production -# variables: -# PACKAGE_NAME: xpub-collabra -# environment: -# name: $PACKAGE_NAME/production -# url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# when: manual -# only: -# - master -# script: -# - source deploy.sh -# - create_deployment - -# demo:xpub-collabra: -# image: pubsweet/deployer:latest -# stage: demo -# variables: -# PACKAGE_NAME: xpub-collabra -# environment: -# name: $PACKAGE_NAME/demo -# url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# when: manual -# script: -# - source deploy.sh -# - create_deployment - -# # ----------------------------------------------- -# # xpub-ui --------------------------------------- -# # ----------------------------------------------- - -# #review:xpub-ui: -# # image: pubsweet/deployer:latest -# # stage: review -# # variables: -# # PACKAGE_NAME: xpub-ui -# # environment: -# # name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME -# # # !! kube-lego will fail if domain > 63 chars -# # url: "http://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# # on_stop: stop_review:xpub-ui -# # except: -# # - master -# # script: -# # - source deploy.sh -# # - create_deployment -# # -# #stop_review:xpub-ui: -# # image: pubsweet/deployer:latest -# # stage: review -# # variables: -# # PACKAGE_NAME: xpub-ui -# # GIT_STRATEGY: none -# # when: manual -# # environment: -# # name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME -# # action: stop -# # script: -# # - source deploy.sh -# # - delete_deployment -# # -# #staging:xpub-ui: -# # image: pubsweet/deployer:latest -# # stage: staging -# # variables: -# # PACKAGE_NAME: xpub-ui -# # environment: -# # name: $PACKAGE_NAME/staging -# # url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# # only: -# # - master -# # script: -# # - source deploy.sh -# # - create_deployment -# # -# #production:xpub-ui: -# # image: pubsweet/deployer:latest -# # stage: production -# # variables: -# # PACKAGE_NAME: xpub-ui -# # environment: -# # name: $PACKAGE_NAME/production -# # url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# # when: manual -# # only: -# # - master -# # script: -# # - source deploy.sh -# # - create_deployment -# # -# #demo:xpub-ui: -# # image: pubsweet/deployer:latest -# # stage: demo -# # variables: -# # PACKAGE_NAME: xpub-ui -# # environment: -# # name: $PACKAGE_NAME/demo -# # url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}" -# # when: manual -# # script: -# # - source deploy.sh -# # - create_deployment -# # +demo:now: + image: $IMAGE_ORG/$IMAGE_NAME:latest + stage: demo + when: manual + variables: + PACKAGE_NAME: xpub-faraday + only: + - master + environment: + name: $PACKAGE_NAME/demo + url: $NOW_URL + script: + - npm i -g --unsafe-perm now + - cd ${HOME} + - npm run deploy_now \ No newline at end of file diff --git a/now/Dockerfile b/now/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..e202a9e52db54c0017a1b4cc372f3cf3b85701a6 --- /dev/null +++ b/now/Dockerfile @@ -0,0 +1 @@ +FROM bogdandev/xpub-faraday:latest \ No newline at end of file diff --git a/package.json b/package.json index 079e1d213d174a5d15d3b0943eded9c29c5e2881..d3ba020bbae08ea43170d63411b295dfcbc9a6bd 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "lint:style": "stylelint packages/**/*.scss packages/**/*.css", "precommit": "lint-staged", "styleguide": "lerna run styleguide", - "test": "lerna run test" + "test": "lerna run test", + "deploy_now": "cd now && now --public --docker --token $NOW_TOKEN -e AWS_S3_ACCESS_KEY=$AWS_S3_ACCESS_KEY -e AWS_S3_SECRET_KEY=$AWS_S3_SECRET_KEY -e AWS_S3_REGION=$AWS_S3_REGION -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_SES_SECRET_KEY=$AWS_SES_SECRET_KEY -e AWS_SES_ACCESS_KEY=$AWS_SES_ACCESS_KEY -e AWS_SES_REGION=$AWS_SES_REGION -e EMAIL_SENDER=$EMAIL_SENDER -e secret=$SECRET" }, "lint-staged": { "*.js": [ diff --git a/packages/component-invite/src/HandleInvitation.js b/packages/component-invite/src/HandleInvitation.js index 7f8c9b5bc7ad3ae280df88d21abbcaa99b322ab1..d3c5c37336388cfd5779fdd0c5f6a1fc812cdbe0 100644 --- a/packages/component-invite/src/HandleInvitation.js +++ b/packages/component-invite/src/HandleInvitation.js @@ -10,6 +10,11 @@ const HandleInvitation = app => { authBearer, require('./routes/postHandleInvitation')(app.locals.models), ) + app.get( + '/api/collections/:collectionId/users', + authBearer, + require('./routes/getCollectionUsers')(app.locals.models), + ) } module.exports = HandleInvitation diff --git a/packages/component-invite/src/controllers/assignCollectionRole.js b/packages/component-invite/src/controllers/assignCollectionRole.js index 1a87770deb0c43decea68ed059eb860c401585df..2dc68984601b5ab8ddf8a60ea8ae01db49434cb0 100644 --- a/packages/component-invite/src/controllers/assignCollectionRole.js +++ b/packages/component-invite/src/controllers/assignCollectionRole.js @@ -70,6 +70,8 @@ module.exports = async ( role, ) + // getting the updated user from the DB - creating a team also updates the user + user = await models.User.findByEmail(email) user = await teamHelper.setupInvitation(user, role, collectionId, team.id) try { diff --git a/packages/component-invite/src/controllers/inviteGlobalRole.js b/packages/component-invite/src/controllers/inviteGlobalRole.js index def996ead4d6451d31d814740ba927d417d8ed64..3858f0c87159f85ed95bacbde32b3a158565b3cb 100644 --- a/packages/component-invite/src/controllers/inviteGlobalRole.js +++ b/packages/component-invite/src/controllers/inviteGlobalRole.js @@ -17,7 +17,6 @@ module.exports = async (body, models, res, url) => { try { const user = await models.User.findByEmail(email) - if (user) { logger.error(`admin tried to invite existing user: ${email}`) return res.status(400).json({ error: 'User already exists' }) diff --git a/packages/component-invite/src/helpers/Team.js b/packages/component-invite/src/helpers/Team.js index 87c1a2dbe6a8d5b19275981e681f555f0b90db49..553b4cd75f9d84eceecca548b54a851a94c77331 100644 --- a/packages/component-invite/src/helpers/Team.js +++ b/packages/component-invite/src/helpers/Team.js @@ -1,5 +1,6 @@ const logger = require('@pubsweet/logger') const config = require('config') +const get = require('lodash/get') const configRoles = config.get('roles') @@ -134,6 +135,37 @@ const removeTeamMember = async (teamId, userId, TeamModel) => { await TeamModel.updateProperties(team) await team.save() } + +const getTeamMembersByCollection = async (collectionId, role, TeamModel) => { + const teams = await TeamModel.all() + const members = get( + teams.find( + team => + team.group === role && + team.object.type === 'collection' && + team.object.id === collectionId, + ), + 'members', + ) + + return members +} + +const getInviteData = (invitations, collectionId, role) => { + const matchingInvitation = invitations.find( + invite => invite.type === role && invite.collectionId === collectionId, + ) + let status = 'pending' + if (matchingInvitation.isAccepted) { + status = 'accepted' + } else if (matchingInvitation.hasAnswer) { + status = 'refused' + } + + const { timestamp } = matchingInvitation + return { timestamp, status } +} + module.exports = { createNewTeam, setupEiCTeams, @@ -141,4 +173,6 @@ module.exports = { getMatchingTeams, setupInvitation, removeTeamMember, + getTeamMembersByCollection, + getInviteData, } diff --git a/packages/component-invite/src/routes/getCollectionUsers.js b/packages/component-invite/src/routes/getCollectionUsers.js new file mode 100644 index 0000000000000000000000000000000000000000..c1389b98c1e00722f03a65309d4263b9d95dd693 --- /dev/null +++ b/packages/component-invite/src/routes/getCollectionUsers.js @@ -0,0 +1,43 @@ +const helpers = require('../helpers/helpers') +const teamHelper = require('../helpers/Team') + +module.exports = models => async (req, res) => { + const { role } = req.query + if (!helpers.checkForUndefinedParams(role)) { + res.status(400).json({ error: 'Role is required' }) + return + } + + const { collectionId } = req.params + try { + await models.Collection.find(collectionId) + const members = await teamHelper.getTeamMembersByCollection( + collectionId, + role, + models.Team, + ) + + const membersData = members.map(async member => { + const user = await models.User.find(member) + const { timestamp, status } = teamHelper.getInviteData( + user.invitations, + collectionId, + role, + ) + return { + name: `${user.firstName} ${user.lastName}`, + timestamp, + email: user.email, + status, + } + }) + + const resBody = await Promise.all(membersData) + res.status(200).json(resBody) + } catch (e) { + const notFoundError = await helpers.handleNotFoundError(e, 'collection') + return res.status(notFoundError.status).json({ + error: notFoundError.message, + }) + } +} diff --git a/packages/component-invite/src/routes/postHandleInvitation.js b/packages/component-invite/src/routes/postHandleInvitation.js index d9ff99d5025c8dea2c024bb38a6b46013430560d..85fd4151dcd1e929cc6f95b7cafd4435f958c482 100644 --- a/packages/component-invite/src/routes/postHandleInvitation.js +++ b/packages/component-invite/src/routes/postHandleInvitation.js @@ -18,25 +18,25 @@ module.exports = models => async (req, res) => { return } const { collectionId } = req.params - // console.log('UI', user.invitations) - const filteredInvitations = user.invitations.filter( - invitation => - invitation.collectionId === collectionId && invitation.type === type, - ) - if (filteredInvitations.length === 0) { - res.status(400).json({ - error: `Request data does not match any user invitation`, - }) - logger.error( - `Collection ${collectionId} and type '${type}' do not match any user invitation`, - ) - return - } - - const matchingInvitation = filteredInvitations[0] try { await models.Collection.find(collectionId) + const filteredInvitations = user.invitations.filter( + invitation => + invitation.collectionId === collectionId && invitation.type === type, + ) + + if (filteredInvitations.length === 0) { + res.status(400).json({ + error: `Request data does not match any user invitation`, + }) + logger.error( + `Collection ${collectionId} and type '${type}' do not match any user invitation`, + ) + return + } + + const matchingInvitation = filteredInvitations[0] matchingInvitation.hasAnswer = true if (accept === true) { matchingInvitation.isAccepted = true diff --git a/packages/component-invite/src/routes/postInvite.js b/packages/component-invite/src/routes/postInvite.js index 5e9d312a99676492e75532fa8ece95018bc4bba9..6c8d4de3a92bccf49ad4f3a4a5de8335796dafb1 100644 --- a/packages/component-invite/src/routes/postInvite.js +++ b/packages/component-invite/src/routes/postInvite.js @@ -1,6 +1,10 @@ const logger = require('@pubsweet/logger') const get = require('lodash/get') const helpers = require('../helpers/helpers') +const config = require('config') +const concat = require('lodash/concat') + +const configRoles = config.get('roles') module.exports = models => async (req, res) => { const { email, role } = req.body @@ -11,7 +15,18 @@ module.exports = models => async (req, res) => { return } + const validRolesList = concat(configRoles.global, configRoles.collection) + if (!validRolesList.includes(role)) { + res.status(400).json({ error: `Role ${role} is invalid` }) + logger.error(`invitation attempted on invalid role ${role}`) + return + } const reqUser = await models.User.find(req.user) + if (email === reqUser.email) { + res.status(400).json({ error: 'Cannot invite yourself' }) + logger.error(`${reqUser.email} tried to invite his own email`) + return + } const collectionId = get(req, 'params.collectionId') const url = `${req.protocol}://${req.get('host')}` @@ -19,7 +34,7 @@ module.exports = models => async (req, res) => { return require('../controllers/assignCollectionRole')( email, role, - req.user, + reqUser, res, collectionId, models, diff --git a/packages/component-invite/src/tests/fixtures/teamIDs.js b/packages/component-invite/src/tests/fixtures/teamIDs.js new file mode 100644 index 0000000000000000000000000000000000000000..f8e1c9f2285384a39ccb39de082abf773c7cfb02 --- /dev/null +++ b/packages/component-invite/src/tests/fixtures/teamIDs.js @@ -0,0 +1,10 @@ +const Chance = require('chance') + +const chance = new Chance() + +const teamIDs = { + heTeam: chance.guid(), + reviewerTeam: chance.guid(), +} + +module.exports = { teamIDs } diff --git a/packages/component-invite/src/tests/fixtures/teams.js b/packages/component-invite/src/tests/fixtures/teams.js index bcc5690ed8b674c625d10a8098960f48a593621b..84de7cd6b6284772d1516308692709f0031a8f6d 100644 --- a/packages/component-invite/src/tests/fixtures/teams.js +++ b/packages/component-invite/src/tests/fixtures/teams.js @@ -1,8 +1,9 @@ const users = require('./users') const collections = require('./collections') +const { heTeam, reviewerTeam } = require('./teamIDs') const { standardCollection } = collections -const { editorInChief, handlingEditor, reviewer } = users +const { editorInChief, handlingEditor, reviewer, invitedHandlingEditor } = users const teams = { eicTeam: { teamType: { @@ -30,10 +31,10 @@ const teams = { type: 'collection', id: standardCollection.id, }, - members: [handlingEditor.id], + members: [handlingEditor.id, invitedHandlingEditor.id], save: jest.fn(() => teams.heTeam), updateProperties: jest.fn(() => teams.heTeam), - id: 'he123', + id: heTeam, }, reviewerTeam: { teamType: { @@ -49,6 +50,7 @@ const teams = { members: [reviewer.id], save: jest.fn(() => teams.reviewerTeam), updateProperties: jest.fn(() => teams.reviewerTeam), + id: reviewerTeam, }, } module.exports = teams diff --git a/packages/component-invite/src/tests/fixtures/users.js b/packages/component-invite/src/tests/fixtures/users.js index 86f6c66be75d1fcc462d96b98861d003915f4e80..a04b7ff4c5e65b0b444f1a085b2c6fcc1254ed4e 100644 --- a/packages/component-invite/src/tests/fixtures/users.js +++ b/packages/component-invite/src/tests/fixtures/users.js @@ -1,5 +1,5 @@ -// const { heInvitation } = require('./invitations') const { standardCollection } = require('./collections') +const { heTeam, reviewerTeam } = require('./teamIDs') const users = { admin: { @@ -33,6 +33,8 @@ const users = { password: 'test', admin: false, id: 'handling123', + firstName: 'Handling', + lastName: 'Editor', invitations: [ { type: 'handlingEditor', @@ -40,9 +42,10 @@ const users = { isAccepted: false, collectionId: standardCollection.id, timestamp: Date.now(), - teamId: 'he123', + teamId: heTeam, }, ], + teams: [heTeam], save: jest.fn(() => users.handlingEditor), editorInChief: false, }, @@ -75,7 +78,40 @@ const users = { title: 'Ms', save: jest.fn(() => users.reviewer), isConfirmed: false, - teams: [], + teams: [reviewerTeam], + invitations: [ + { + type: 'reviewer', + hasAnswer: false, + isAccepted: false, + collectionId: '123', + timestamp: Date.now(), + teamId: reviewerTeam, + }, + ], + }, + invitedHandlingEditor: { + type: 'user', + username: 'handling', + email: 'handling@example.com', + password: 'test', + admin: false, + id: 'invitedHandling123', + firstName: 'Invited', + lastName: 'HE', + invitations: [ + { + type: 'handlingEditor', + hasAnswer: true, + isAccepted: false, + collectionId: standardCollection.id, + timestamp: Date.now(), + teamId: heTeam, + }, + ], + teams: [heTeam], + save: jest.fn(() => users.handlingEditor), + editorInChief: false, }, } diff --git a/packages/component-invite/src/tests/getCollectionUsers.test.js b/packages/component-invite/src/tests/getCollectionUsers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..2458b1044b8349bbc9f7f00fd6c8732be6cd1ba1 --- /dev/null +++ b/packages/component-invite/src/tests/getCollectionUsers.test.js @@ -0,0 +1,39 @@ +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +process.env.SUPPRESS_NO_CONFIG_WARNING = true + +const httpMocks = require('node-mocks-http') +const fixtures = require('./fixtures/fixtures') +const Model = require('./helpers/Model') + +const user = fixtures.users.editorInChief +const query = { + role: 'handlingEditor', +} +const getCollectionUsersPath = '../routes/getCollectionUsers' +describe('Get collection users route handler', () => { + it('should return success when the role is correct, the collection exists and the request user is editorInChief ', async () => { + const req = httpMocks.createRequest() + req.query = query + req.params.collectionId = '2c4fb766-a798-4c32-b857-c5d21a2ab331' + const res = httpMocks.createResponse() + const models = Model.build() + await require(getCollectionUsersPath)(models)(req, res) + + expect(res.statusCode).toBe(200) + const data = JSON.parse(res._getData()) + expect(data).toHaveLength(2) + }) + it('should return an error when the role parameter is missing', async () => { + delete query.role + const req = httpMocks.createRequest() + req.query = query + + const res = httpMocks.createResponse() + const models = Model.build(user) + await require(getCollectionUsersPath)(models)(req, res) + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual('Role is required') + query.email = 'handlingEditor' + }) +}) diff --git a/packages/component-invite/src/tests/helpers/Model.js b/packages/component-invite/src/tests/helpers/Model.js index 40fecc6e1812f276d9be4944e9cdef786b177d17..7ff2e90b59f218c2e37967701d0ea8b70d78c73b 100644 --- a/packages/component-invite/src/tests/helpers/Model.js +++ b/packages/component-invite/src/tests/helpers/Model.js @@ -3,45 +3,25 @@ const fixtures = require('../fixtures/fixtures') const UserMock = require('../mocks/User') const TeamMock = require('../mocks/Team') -const build = (collection, findUser, emailUser, team) => { +const notFoundError = new Error() +notFoundError.name = 'NotFoundError' +notFoundError.status = 404 + +const build = () => { const models = { User: {}, Collection: { - find: jest.fn( - () => - collection instanceof Error - ? Promise.reject(collection) - : Promise.resolve(collection), - ), + find: jest.fn(id => findMock(id, 'collections')), }, Team: {}, } - UserMock.find = jest.fn(user => { - const foundUser = Object.values(fixtures.users).find( - fixUser => fixUser.id === user.id, - ) - - if (foundUser === undefined) { - return Promise.reject(findUser) - } - - return Promise.resolve(findUser) - }) - - UserMock.findByEmail = jest.fn( - () => - emailUser instanceof Error - ? Promise.reject(emailUser) - : Promise.resolve(emailUser), - ) + UserMock.find = jest.fn(id => findMock(id, 'users')) - TeamMock.find = jest.fn( - () => - team instanceof Error ? Promise.reject(team) : Promise.resolve(team), - ) - TeamMock.updateProperties = jest.fn( - () => - team instanceof Error ? Promise.reject(team) : Promise.resolve(team), + UserMock.findByEmail = jest.fn(email => findByEmailMock(email)) + + TeamMock.find = jest.fn(id => findMock(id, 'teams')) + TeamMock.updateProperties = jest.fn(team => + updatePropertiesMock(team, 'teams'), ) TeamMock.all = jest.fn(() => Object.values(fixtures.teams)) @@ -50,4 +30,30 @@ const build = (collection, findUser, emailUser, team) => { return models } +const findMock = (id, type) => { + const foundObj = Object.values(fixtures[type]).find( + fixtureObj => fixtureObj.id === id, + ) + + if (foundObj === undefined) return Promise.reject(notFoundError) + return Promise.resolve(foundObj) +} + +const findByEmailMock = email => { + const foundUser = Object.values(fixtures.users).find( + fixtureUser => fixtureUser.email === email, + ) + + if (foundUser === undefined) return Promise.reject(notFoundError) + return Promise.resolve(foundUser) +} + +const updatePropertiesMock = (obj, type) => { + const foundObj = Object.values(fixtures[type]).find( + fixtureObj => fixtureObj === obj, + ) + + if (foundObj === undefined) return Promise.reject(notFoundError) + return Promise.resolve(foundObj) +} module.exports = { build } diff --git a/packages/component-invite/src/tests/postHandleInvitation.test.js b/packages/component-invite/src/tests/postHandleInvitation.test.js index 795cdb4ea8180d1a29e458677a95f36c37a157ab..ffa1215761d1b0a017f09a71f939dacb2d2a1c3c 100644 --- a/packages/component-invite/src/tests/postHandleInvitation.test.js +++ b/packages/component-invite/src/tests/postHandleInvitation.test.js @@ -3,9 +3,9 @@ process.env.SUPPRESS_NO_CONFIG_WARNING = true const httpMocks = require('node-mocks-http') const fixtures = require('./fixtures/fixtures') -const cloneDeep = require('lodash/cloneDeep') const Model = require('./helpers/Model') +const models = Model.build() jest.mock('pubsweet-component-mail-service', () => ({ setupAssignEmail: jest.fn(), })) @@ -14,14 +14,17 @@ const notFoundError = new Error() notFoundError.name = 'NotFoundError' notFoundError.status = 404 -const { handlingEditor } = fixtures.users +const { + handlingEditor, + invitedHandlingEditor, + author, + reviewer, +} = fixtures.users const { standardCollection } = fixtures.collections -const { heTeam } = fixtures.teams const postInvitationPath = '../routes/postHandleInvitation' describe('Post handle invitation route handler', () => { it('should return success when the handling editor accepts work on a collection', async () => { - const acceptingHE = cloneDeep(handlingEditor) const body = { type: 'handlingEditor', accept: true, @@ -29,17 +32,15 @@ describe('Post handle invitation route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = acceptingHE + req.user = handlingEditor.id req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, acceptingHE, null, heTeam) await require(postInvitationPath)(models)(req, res) expect(res.statusCode).toBe(204) - expect(acceptingHE.invitations[0].hasAnswer).toBeTruthy() - expect(acceptingHE.invitations[0].isAccepted).toBeTruthy() + expect(handlingEditor.invitations[0].hasAnswer).toBeTruthy() + expect(handlingEditor.invitations[0].isAccepted).toBeTruthy() }) it('should return success when the handling editor refuses work on a collection', async () => { - const refusingHE = cloneDeep(handlingEditor) const body = { type: 'handlingEditor', accept: false, @@ -47,15 +48,14 @@ describe('Post handle invitation route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = refusingHE + req.user = invitedHandlingEditor.id req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, refusingHE, null, heTeam) await require(postInvitationPath)(models)(req, res) expect(res.statusCode).toBe(204) - expect(refusingHE.invitations[0].hasAnswer).toBeTruthy() - expect(refusingHE.invitations[0].isAccepted).toBeFalsy() + expect(invitedHandlingEditor.invitations[0].hasAnswer).toBeTruthy() + expect(invitedHandlingEditor.invitations[0].isAccepted).toBeFalsy() }) it('should return an error params are missing', async () => { const body = { @@ -64,17 +64,16 @@ describe('Post handle invitation route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = handlingEditor + req.user = handlingEditor.id req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, handlingEditor) await require(postInvitationPath)(models)(req, res) expect(res.statusCode).toBe(400) const data = JSON.parse(res._getData()) expect(data.error).toEqual('Type and accept are required') }) - it('should return an error if the collection id does not exists', async () => { + it('should return an error if the collection does not exists', async () => { const body = { type: 'handlingEditor', accept: false, @@ -82,10 +81,9 @@ describe('Post handle invitation route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = handlingEditor - req.params.collectionId = standardCollection.id + req.user = handlingEditor.id + req.params.collectionId = 'invalid-id' const res = httpMocks.createResponse() - const models = Model.build(notFoundError, handlingEditor) await require(postInvitationPath)(models)(req, res) expect(res.statusCode).toBe(404) @@ -93,7 +91,6 @@ describe('Post handle invitation route handler', () => { expect(data.error).toEqual('collection not found') }) it('should return an error when the request user does not have any invitation', async () => { - const noInvitationEditor = cloneDeep(handlingEditor) const body = { type: 'handlingEditor', accept: false, @@ -101,11 +98,9 @@ describe('Post handle invitation route handler', () => { const req = httpMocks.createRequest({ body, }) - delete noInvitationEditor.invitations - req.user = noInvitationEditor + req.user = author.id req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, noInvitationEditor) await require(postInvitationPath)(models)(req, res) expect(res.statusCode).toBe(400) @@ -120,10 +115,9 @@ describe('Post handle invitation route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = handlingEditor + req.user = handlingEditor.id req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, handlingEditor) await require(postInvitationPath)(models)(req, res) expect(res.statusCode).toBe(400) @@ -134,16 +128,15 @@ describe('Post handle invitation route handler', () => { }) it('should return an error when the request collection and the user invitation collection do not match', async () => { const body = { - type: 'handlingEditor', + type: 'reviewer', accept: false, } const req = httpMocks.createRequest({ body, }) - req.user = handlingEditor - req.params.collectionId = '123' + req.user = reviewer.id + req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, handlingEditor) await require(postInvitationPath)(models)(req, res) expect(res.statusCode).toBe(400) diff --git a/packages/component-invite/src/tests/postInvite.test.js b/packages/component-invite/src/tests/postInvite.test.js index 17e7e9c12d89722970783c9e026f9aac8b1d1593..74aff28e5e9e68ecda6dd0eba2ae819d8d78f7aa 100644 --- a/packages/component-invite/src/tests/postInvite.test.js +++ b/packages/component-invite/src/tests/postInvite.test.js @@ -7,6 +7,7 @@ const fixtures = require('./fixtures/fixtures') const Chance = require('chance') const Model = require('./helpers/Model') +const models = Model.build() jest.mock('pubsweet-component-mail-service', () => ({ setupInviteEmail: jest.fn(), setupAssignEmail: jest.fn(), @@ -31,16 +32,14 @@ notFoundError.status = 404 const { admin, editorInChief, handlingEditor, author } = fixtures.users const { standardCollection } = fixtures.collections -const { heTeam } = fixtures.teams const postInvitePath = '../routes/postInvite' describe('Post invite route handler', () => { it('should return success when the admin invites a global role', async () => { const req = httpMocks.createRequest({ body, }) - req.user = admin + req.user = admin.id const res = httpMocks.createResponse() - const models = Model.build(notFoundError, admin, notFoundError) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(200) @@ -53,10 +52,9 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = admin + req.user = admin.id req.params.collectionId = '123' const res = httpMocks.createResponse() - const models = Model.build(notFoundError, admin) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) @@ -70,9 +68,8 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = admin + req.user = admin.id const res = httpMocks.createResponse() - const models = Model.build(notFoundError, admin) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) @@ -85,9 +82,8 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = admin + req.user = admin.id const res = httpMocks.createResponse() - const models = Model.build(notFoundError, admin) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(400) const data = JSON.parse(res._getData()) @@ -100,10 +96,9 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = editorInChief + req.user = editorInChief.id req.params.collectionId = '123' const res = httpMocks.createResponse() - const models = Model.build(notFoundError, editorInChief) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) @@ -115,15 +110,16 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = editorInChief + req.user = editorInChief.id const res = httpMocks.createResponse() - const models = Model.build(notFoundError, editorInChief) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) expect(data.error).toEqual( - `${req.user.username} cannot invite a ${body.role} without a collection`, + `${editorInChief.username} cannot invite a ${ + body.role + } without a collection`, ) }) it('should return an error when an handlingEditor invites a reviewer without a collection', async () => { @@ -132,26 +128,26 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = handlingEditor + req.user = handlingEditor.id const res = httpMocks.createResponse() - - const models = Model.build(notFoundError, handlingEditor) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) expect(data.error).toEqual( - `${req.user.username} cannot invite a ${body.role} without a collection`, + `${handlingEditor.username} cannot invite a ${ + body.role + } without a collection`, ) }) - it('should return an error when inviting an existing user', async () => { + it('should return an error when inviting an existing user with a global role', async () => { body.role = globalRoles[random(0, globalRoles.length - 1)] body.admin = body.role === 'admin' + body.email = author.email const req = httpMocks.createRequest({ body, }) - req.user = admin + req.user = admin.id const res = httpMocks.createResponse() - const models = Model.build(notFoundError, admin, editorInChief) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(400) @@ -166,10 +162,9 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = editorInChief - req.params.collectionId = '123' + req.user = editorInChief.id + req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, editorInChief, author) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(200) @@ -185,11 +180,9 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - handlingEditor.teams = [heTeam.id] - req.user = handlingEditor - req.params.collectionId = '123' + req.user = handlingEditor.id + req.params.collectionId = standardCollection.id const res = httpMocks.createResponse() - const models = Model.build(standardCollection, handlingEditor, author) await require(postInvitePath)(models)(req, res) expect(res.statusCode).toBe(200) @@ -197,4 +190,30 @@ describe('Post invite route handler', () => { expect(data.email).toEqual(body.email) expect(data.invitations[0].collectionId).toEqual(req.params.collectionId) }) + it('should return an error when inviting his self', async () => { + body.role = globalRoles[random(0, globalRoles.length - 1)] + body.admin = body.role === 'admin' + body.email = admin.email + const req = httpMocks.createRequest({ + body, + }) + req.user = admin.id + const res = httpMocks.createResponse() + await require(postInvitePath)(models)(req, res) + + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual('Cannot invite yourself') + }) + it('should return an error when the role is invalid', async () => { + body.role = 'someRandomRole' + const req = httpMocks.createRequest({ + body, + }) + req.user = editorInChief.id + const res = httpMocks.createResponse() + await require(postInvitePath)(models)(req, res) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual(`Role ${body.role} is invalid`) + }) }) diff --git a/yarn.lock b/yarn.lock index 78fcd80cbea0b5bec10c849f023d4fed20f55693..e24936ff287cce17c86c68a7597c79d9a3443183 100644 --- a/yarn.lock +++ b/yarn.lock @@ -164,27 +164,6 @@ redux-form "^7.0.3" styled-components "^2.4.0" -"@pubsweet/ui@^0.1.1": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@pubsweet/ui/-/ui-0.1.3.tgz#3a02eb0d679f36eaf38f87196032b46c371e5477" - dependencies: - babel-jest "^21.2.0" - classnames "^2.2.5" - enzyme "^3.2.0" - enzyme-adapter-react-15 "^1.0.5" - humps "^2.0.1" - lodash "^4.17.4" - prop-types "^15.5.10" - react "^15.6.1" - react-dom "^15.6.1" - react-feather "^1.0.7" - react-redux "^5.0.2" - react-router-dom "^4.2.2" - react-tag-autocomplete "^5.4.1" - recompose "^0.26.0" - redux "^3.6.0" - redux-form "^7.0.3" - "@pubsweet/ui@^3.0.0", "@pubsweet/ui@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@pubsweet/ui/-/ui-3.1.0.tgz#24c25c29fc36e34b9f654fe4378502232f8204fa" @@ -8368,7 +8347,7 @@ react-dnd@^2.5.4: lodash "^4.2.0" prop-types "^15.5.10" -"react-dom@>=15.0.0 <17.0.0", react-dom@^16.2.0: +"react-dom@>=15.0.0 <17.0.0", react-dom@^16.1.0, react-dom@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" dependencies: @@ -8386,7 +8365,7 @@ react-dom@^15.6.1: object-assign "^4.1.0" prop-types "^15.5.10" -react-feather@^1.0.7, react-feather@^1.0.8: +react-feather@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/react-feather/-/react-feather-1.0.8.tgz#69b13d5c729949f194d33201dee91bab67fa31a2" @@ -8480,7 +8459,7 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0: prop-types "^15.5.8" warning "^3.0.0" -"react@>=15.0.0 <17.0.0", react@^16.2.0: +"react@>=15.0.0 <17.0.0", react@^16.1.0, react@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" dependencies: