diff --git a/packages/component-fixture-manager/.gitignore b/packages/component-fixture-manager/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..3614a810088d89d9ccaa28d82401545634874a18
--- /dev/null
+++ b/packages/component-fixture-manager/.gitignore
@@ -0,0 +1,8 @@
+_build/
+api/
+logs/
+node_modules/
+uploads/
+.env.*
+.env
+config/local*.*
\ No newline at end of file
diff --git a/packages/component-fixture-manager/README.md b/packages/component-fixture-manager/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a0ac7afbf2c8255c8a94f4f2ff525dd245cc3c93
--- /dev/null
+++ b/packages/component-fixture-manager/README.md
@@ -0,0 +1,2 @@
+# Helper Service
+
diff --git a/packages/component-fixture-manager/index.js b/packages/component-fixture-manager/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..dd95e77d96237d61fc1a8f03d5f2d7d2f80a7e0f
--- /dev/null
+++ b/packages/component-fixture-manager/index.js
@@ -0,0 +1 @@
+module.exports = require('./src/Fixture')
diff --git a/packages/component-fixture-manager/package.json b/packages/component-fixture-manager/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..fe566ea163b877842eba597a058293b0a0af7fbe
--- /dev/null
+++ b/packages/component-fixture-manager/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "pubsweet-component-fixture-service",
+  "version": "0.0.1",
+  "description": "fixture service component for pubsweet",
+  "license": "MIT",
+  "author": "Collaborative Knowledge Foundation",
+  "files": [
+    "src"
+  ],
+  "main": "index.js",
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.coko.foundation/xpub/xpub-faraday",
+    "path": "component-fixture-service"
+  },
+  "dependencies": {
+  },
+  "peerDependencies": {
+    "@pubsweet/logger": "^0.0.1",
+    "pubsweet-server": "^1.0.1"
+  },
+  "publishConfig": {
+    "access": "public"
+  }
+}
diff --git a/packages/component-fixture-manager/src/Fixture.js b/packages/component-fixture-manager/src/Fixture.js
new file mode 100644
index 0000000000000000000000000000000000000000..c2c53d05d677167696b1dad66e7b1f294a23ae10
--- /dev/null
+++ b/packages/component-fixture-manager/src/Fixture.js
@@ -0,0 +1,7 @@
+const fixtures = require('./fixtures/fixtures')
+const Model = require('./helpers/Model')
+
+module.exports = {
+  fixtures,
+  Model,
+}
diff --git a/packages/component-fixture-manager/src/fixtures/collectionIDs.js b/packages/component-fixture-manager/src/fixtures/collectionIDs.js
new file mode 100644
index 0000000000000000000000000000000000000000..0633f816e39359721e73dd32252279f05bcdff5e
--- /dev/null
+++ b/packages/component-fixture-manager/src/fixtures/collectionIDs.js
@@ -0,0 +1,8 @@
+const Chance = require('chance')
+
+const chance = new Chance()
+const collId = chance.guid()
+
+module.exports = {
+  standardCollID: collId,
+}
diff --git a/packages/component-manuscript-manager/src/tests/fixtures/collections.js b/packages/component-fixture-manager/src/fixtures/collections.js
similarity index 65%
rename from packages/component-manuscript-manager/src/tests/fixtures/collections.js
rename to packages/component-fixture-manager/src/fixtures/collections.js
index bef6132199981a79b87ea86440146eaab5891883..ead1ed38cabd8e5eaa615a1fa7a9e5b84d6d3052 100644
--- a/packages/component-manuscript-manager/src/tests/fixtures/collections.js
+++ b/packages/component-fixture-manager/src/fixtures/collections.js
@@ -1,29 +1,17 @@
 const Chance = require('chance')
-const {
-  user,
-  handlingEditor,
-  author,
-  reviewer,
-  answerReviewer,
-} = require('./userData')
+const { user, handlingEditor, answerHE } = require('./userData')
 const { fragment } = require('./fragments')
+const { standardCollID } = require('./collectionIDs')
 
 const chance = new Chance()
 const collections = {
   collection: {
-    id: chance.guid(),
+    id: standardCollID,
     title: chance.sentence(),
     type: 'collection',
     fragments: [fragment.id],
     owners: [user.id],
     save: jest.fn(),
-    authors: [
-      {
-        userId: author.id,
-        isSubmitting: true,
-        isCorresponding: false,
-      },
-    ],
     invitations: [
       {
         id: chance.guid(),
@@ -36,19 +24,10 @@ const collections = {
       },
       {
         id: chance.guid(),
-        role: 'reviewer',
-        hasAnswer: false,
-        isAccepted: false,
-        userId: reviewer.id,
-        invitedOn: chance.timestamp(),
-        respondedOn: null,
-      },
-      {
-        id: chance.guid(),
-        role: 'reviewer',
+        role: 'handlingEditor',
         hasAnswer: true,
         isAccepted: false,
-        userId: answerReviewer.id,
+        userId: answerHE.id,
         invitedOn: chance.timestamp(),
         respondedOn: chance.timestamp(),
       },
diff --git a/packages/component-invite/src/tests/fixtures/fixtures.js b/packages/component-fixture-manager/src/fixtures/fixtures.js
similarity index 100%
rename from packages/component-invite/src/tests/fixtures/fixtures.js
rename to packages/component-fixture-manager/src/fixtures/fixtures.js
diff --git a/packages/component-fixture-manager/src/fixtures/fragments.js b/packages/component-fixture-manager/src/fixtures/fragments.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab41ab42856c37cf396c947897df22c0649e73ce
--- /dev/null
+++ b/packages/component-fixture-manager/src/fixtures/fragments.js
@@ -0,0 +1,73 @@
+const Chance = require('chance')
+const {
+  submittingAuthor,
+  reviewer,
+  answerReviewer,
+  recReviewer,
+} = require('./userData')
+const { standardCollID } = require('./collectionIDs')
+
+const chance = new Chance()
+const fragments = {
+  fragment: {
+    id: chance.guid(),
+    collectionId: standardCollID,
+    metadata: {
+      title: chance.sentence(),
+      abstract: chance.paragraph(),
+    },
+    recommendations: [
+      {
+        recommendation: 'publish',
+        recommendationType: 'review',
+        comments: [
+          {
+            content: chance.paragraph(),
+            public: chance.bool(),
+            files: [
+              {
+                id: chance.guid(),
+                name: 'file.pdf',
+                size: chance.natural(),
+              },
+            ],
+          },
+        ],
+        id: chance.guid(),
+        userId: recReviewer.id,
+        createdOn: chance.timestamp(),
+        updatedOn: chance.timestamp(),
+      },
+    ],
+    authors: [
+      {
+        userId: submittingAuthor.id,
+        isSubmitting: true,
+        isCorresponding: false,
+      },
+    ],
+    invitations: [
+      {
+        id: chance.guid(),
+        role: 'reviewer',
+        hasAnswer: false,
+        isAccepted: false,
+        userId: reviewer.id,
+        invitedOn: chance.timestamp(),
+        respondedOn: null,
+      },
+      {
+        id: chance.guid(),
+        role: 'reviewer',
+        hasAnswer: true,
+        isAccepted: false,
+        userId: answerReviewer.id,
+        invitedOn: chance.timestamp(),
+        respondedOn: chance.timestamp(),
+      },
+    ],
+    save: jest.fn(() => fragments.fragment),
+  },
+}
+
+module.exports = fragments
diff --git a/packages/component-user-manager/src/tests/fixtures/teamIDs.js b/packages/component-fixture-manager/src/fixtures/teamIDs.js
similarity index 79%
rename from packages/component-user-manager/src/tests/fixtures/teamIDs.js
rename to packages/component-fixture-manager/src/fixtures/teamIDs.js
index f8cfc51464701c2ab93b312dd73ed3ea5e2f7ea3..83ce3a556417bc4650efdc9ff478b8bad64a9f13 100644
--- a/packages/component-user-manager/src/tests/fixtures/teamIDs.js
+++ b/packages/component-fixture-manager/src/fixtures/teamIDs.js
@@ -2,9 +2,11 @@ const Chance = require('chance')
 
 const chance = new Chance()
 const heID = chance.guid()
+const revId = chance.guid()
 const authorID = chance.guid()
 
 module.exports = {
   heTeamID: heID,
+  revTeamID: revId,
   authorTeamID: authorID,
 }
diff --git a/packages/component-invite/src/tests/fixtures/teams.js b/packages/component-fixture-manager/src/fixtures/teams.js
similarity index 59%
rename from packages/component-invite/src/tests/fixtures/teams.js
rename to packages/component-fixture-manager/src/fixtures/teams.js
index 7e4611da3489cceb1308e9ffaf072a85866f4ca2..84d784b4b4be4c38a4757fccada612705bc8b1b1 100644
--- a/packages/component-invite/src/tests/fixtures/teams.js
+++ b/packages/component-fixture-manager/src/fixtures/teams.js
@@ -1,8 +1,12 @@
 const users = require('./users')
 const collections = require('./collections')
-const { heTeamID, revTeamID } = require('./teamIDs')
+const fragments = require('./fragments')
+
+const { heTeamID, revTeamID, authorTeamID } = require('./teamIDs')
+const { submittingAuthor } = require('./userData')
 
 const { collection } = collections
+const { fragment } = fragments
 const { handlingEditor, reviewer } = users
 const teams = {
   heTeam: {
@@ -29,13 +33,29 @@ const teams = {
     group: 'reviewer',
     name: 'reviewer',
     object: {
-      type: 'collection',
-      id: collection.id,
+      type: 'fragment',
+      id: fragment.id,
     },
     members: [reviewer.id],
     save: jest.fn(() => teams.revTeam),
     updateProperties: jest.fn(() => teams.revTeam),
     id: revTeamID,
   },
+  authorTeam: {
+    teamType: {
+      name: 'author',
+      permissions: 'author',
+    },
+    group: 'author',
+    name: 'author',
+    object: {
+      type: 'fragment',
+      id: fragment.id,
+    },
+    members: [submittingAuthor.id],
+    save: jest.fn(() => teams.authorTeam),
+    updateProperties: jest.fn(() => teams.authorTeam),
+    id: authorTeamID,
+  },
 }
 module.exports = teams
diff --git a/packages/component-manuscript-manager/src/tests/fixtures/userData.js b/packages/component-fixture-manager/src/fixtures/userData.js
similarity index 86%
rename from packages/component-manuscript-manager/src/tests/fixtures/userData.js
rename to packages/component-fixture-manager/src/fixtures/userData.js
index 4e3b994a70b782f25ec85e0f41410824dd4307c4..9920552365178966754bf7681df61eaa7a15878c 100644
--- a/packages/component-manuscript-manager/src/tests/fixtures/userData.js
+++ b/packages/component-fixture-manager/src/fixtures/userData.js
@@ -15,5 +15,7 @@ module.exports = {
   author: generateUserData(),
   reviewer: generateUserData(),
   answerReviewer: generateUserData(),
+  submittingAuthor: generateUserData(),
   recReviewer: generateUserData(),
+  answerHE: generateUserData(),
 }
diff --git a/packages/component-invite/src/tests/fixtures/users.js b/packages/component-fixture-manager/src/fixtures/users.js
similarity index 65%
rename from packages/component-invite/src/tests/fixtures/users.js
rename to packages/component-fixture-manager/src/fixtures/users.js
index c5f0a5e41f8bca54d62c6534ef263a6de77c99bc..29c50f9327b03f6551aca7cbcf48526446726a0f 100644
--- a/packages/component-invite/src/tests/fixtures/users.js
+++ b/packages/component-fixture-manager/src/fixtures/users.js
@@ -1,4 +1,4 @@
-const { heTeamID, revTeamID } = require('./teamIDs')
+const { heTeamID, revTeamID, authorTeamID } = require('./teamIDs')
 const {
   handlingEditor,
   user,
@@ -6,6 +6,9 @@ const {
   author,
   reviewer,
   answerReviewer,
+  submittingAuthor,
+  recReviewer,
+  answerHE,
 } = require('./userData')
 const Chance = require('chance')
 
@@ -51,6 +54,21 @@ const users = {
     handlingEditor: true,
     title: 'Mr',
   },
+  answerHE: {
+    type: 'user',
+    username: chance.word(),
+    email: answerHE.email,
+    password: 'password',
+    admin: false,
+    id: answerHE.id,
+    firstName: answerHE.firstName,
+    lastName: answerHE.lastName,
+    teams: [heTeamID],
+    save: jest.fn(() => users.answerHE),
+    editorInChief: false,
+    handlingEditor: true,
+    title: 'Mr',
+  },
   user: {
     type: 'user',
     username: chance.word(),
@@ -65,6 +83,8 @@ const users = {
     title: 'Mr',
     save: jest.fn(() => users.user),
     isConfirmed: false,
+    updateProperties: jest.fn(() => users.user),
+    teams: [],
   },
   author: {
     type: 'user',
@@ -79,6 +99,8 @@ const users = {
     title: 'Mr',
     save: jest.fn(() => users.author),
     isConfirmed: true,
+    passwordResetToken: chance.hash(),
+    teams: [authorTeamID],
   },
   reviewer: {
     type: 'user',
@@ -112,6 +134,36 @@ const users = {
     teams: [revTeamID],
     invitationToken: 'inv-token-123',
   },
+  submittingAuthor: {
+    type: 'user',
+    username: 'sauthor',
+    email: submittingAuthor.email,
+    password: 'password',
+    admin: false,
+    id: submittingAuthor.id,
+    passwordResetToken: chance.hash(),
+    firstName: submittingAuthor.firstName,
+    lastName: submittingAuthor.lastName,
+    affiliation: chance.company(),
+    title: 'Mr',
+    save: jest.fn(() => users.submittingAuthor),
+    isConfirmed: false,
+  },
+  recReviewer: {
+    type: 'user',
+    username: chance.word(),
+    email: recReviewer.email,
+    password: 'password',
+    admin: false,
+    id: recReviewer.id,
+    firstName: recReviewer.firstName,
+    lastName: recReviewer.lastName,
+    affiliation: chance.company(),
+    title: 'Mr',
+    save: jest.fn(() => users.recReviewer),
+    isConfirmed: true,
+    teams: [revTeamID],
+  },
 }
 
 module.exports = users
diff --git a/packages/component-invite/src/tests/helpers/Model.js b/packages/component-fixture-manager/src/helpers/Model.js
similarity index 95%
rename from packages/component-invite/src/tests/helpers/Model.js
rename to packages/component-fixture-manager/src/helpers/Model.js
index df543155a679c4c7e2bb91a8304835c458e38d44..04bdb01f94441d836aa475fc702d818823405fcd 100644
--- a/packages/component-invite/src/tests/helpers/Model.js
+++ b/packages/component-fixture-manager/src/helpers/Model.js
@@ -1,5 +1,3 @@
-// const fixtures = require('../fixtures/fixtures')
-
 const UserMock = require('../mocks/User')
 const TeamMock = require('../mocks/Team')
 
@@ -24,6 +22,9 @@ const build = fixtures => {
   UserMock.findOneByField = jest.fn((field, value) =>
     findOneByFieldMock(field, value, 'users', fixtures),
   )
+  UserMock.updateProperties = jest.fn(user =>
+    updatePropertiesMock(user, 'users'),
+  )
   TeamMock.find = jest.fn(id => findMock(id, 'teams', fixtures))
   TeamMock.updateProperties = jest.fn(team =>
     updatePropertiesMock(team, 'teams', fixtures),
diff --git a/packages/component-invite/src/tests/mocks/Team.js b/packages/component-fixture-manager/src/mocks/Team.js
similarity index 100%
rename from packages/component-invite/src/tests/mocks/Team.js
rename to packages/component-fixture-manager/src/mocks/Team.js
diff --git a/packages/component-invite/src/tests/mocks/User.js b/packages/component-fixture-manager/src/mocks/User.js
similarity index 100%
rename from packages/component-invite/src/tests/mocks/User.js
rename to packages/component-fixture-manager/src/mocks/User.js
diff --git a/packages/component-helper-service/src/services/Collection.js b/packages/component-helper-service/src/services/Collection.js
index 6e502735b7ae9ce777bbda52be940b4e06abf953..c0fb4fddd1d9e8a79d587f9e53a3c623837f5c2a 100644
--- a/packages/component-helper-service/src/services/Collection.js
+++ b/packages/component-helper-service/src/services/Collection.js
@@ -40,38 +40,6 @@ class Collection {
     await this.collection.save()
   }
 
-  async getAuthorData({ UserModel }) {
-    const { collection: { authors } } = this
-    const submittingAuthorData = authors.find(
-      author => author.isSubmitting === true,
-    )
-    const submittingAuthor = await UserModel.find(submittingAuthorData.userId)
-    const authorsPromises = authors.map(async author => {
-      const user = await UserModel.find(author.userId)
-      return `${user.firstName} ${user.lastName}`
-    })
-    const authorsList = await Promise.all(authorsPromises)
-
-    return {
-      authorsList,
-      submittingAuthor,
-    }
-  }
-
-  getReviewerInvitations({ agree = true }) {
-    const { collection: { invitations } } = this
-    return agree
-      ? invitations.filter(
-          inv =>
-            inv.role === 'reviewer' &&
-            inv.hasAnswer === true &&
-            inv.isAccepted === true,
-        )
-      : invitations.filter(
-          inv => inv.role === 'reviewer' && inv.hasAnswer === false,
-        )
-  }
-
   async addHandlingEditor({ user, invitation }) {
     this.collection.handlingEditor = {
       id: user.id,
diff --git a/packages/component-helper-service/src/services/Email.js b/packages/component-helper-service/src/services/Email.js
index 49a106d8d72305dcf029accd2bd0a094a52cab45..c375875f8157884196f93b2b4e5abf75cd730c03 100644
--- a/packages/component-helper-service/src/services/Email.js
+++ b/packages/component-helper-service/src/services/Email.js
@@ -1,4 +1,4 @@
-const Collection = require('./Collection')
+const Fragment = require('./Fragment')
 const User = require('./User')
 const get = require('lodash/get')
 const config = require('config')
@@ -29,15 +29,17 @@ class Email {
     recommendation = {},
     isSubmitted = false,
     agree = false,
+    FragmentModel,
   }) {
     const {
       UserModel,
       collection,
-      parsedFragment: { recommendations, title, type },
+      parsedFragment: { recommendations, title, type, id },
       authors: { submittingAuthor: { firstName = '', lastName = '' } },
     } = this
-    const collectionHelper = new Collection({ collection })
-    const reviewerInvitations = collectionHelper.getReviewerInvitations({
+    const fragment = await FragmentModel.find(id)
+    const fragmentHelper = new Fragment({ fragment })
+    const reviewerInvitations = fragmentHelper.getReviewerInvitations({
       agree,
     })
 
@@ -98,7 +100,11 @@ class Email {
     )
   }
 
-  async setupAuthorsEmail({ requestToRevision = false, publish = false }) {
+  async setupAuthorsEmail({
+    requestToRevision = false,
+    publish = false,
+    FragmentModel,
+  }) {
     const {
       baseUrl,
       collection,
@@ -123,7 +129,8 @@ class Email {
         },
       ]
     } else {
-      toAuthors = collection.authors.map(author => ({
+      const fragment = await FragmentModel.find(id)
+      toAuthors = fragment.authors.map(author => ({
         email: author.email,
         name: `${author.firstName} ${author.lastName}`,
       }))
diff --git a/packages/component-helper-service/src/services/Fragment.js b/packages/component-helper-service/src/services/Fragment.js
index d4be48eec23c41fd8e08e2d5695a34a705454fb1..80b38b4b6caafc3a5f96ac36253fa9f44cfd239b 100644
--- a/packages/component-helper-service/src/services/Fragment.js
+++ b/packages/component-helper-service/src/services/Fragment.js
@@ -21,6 +21,57 @@ class Fragment {
       heRecommendation,
     }
   }
+
+  async addAuthor({ user, isSubmitting, isCorresponding }) {
+    const { fragment } = this
+    fragment.authors = fragment.authors || []
+    const author = {
+      id: user.id,
+      firstName: user.firstName || '',
+      lastName: user.lastName || '',
+      email: user.email,
+      title: user.title || '',
+      affiliation: user.affiliation || '',
+      isSubmitting,
+      isCorresponding,
+    }
+    fragment.authors.push(author)
+    await fragment.save()
+
+    return author
+  }
+
+  async getAuthorData({ UserModel }) {
+    const { fragment: { authors } } = this
+    const submittingAuthorData = authors.find(
+      author => author.isSubmitting === true,
+    )
+    const submittingAuthor = await UserModel.find(submittingAuthorData.userId)
+    const authorsPromises = authors.map(async author => {
+      const user = await UserModel.find(author.userId)
+      return `${user.firstName} ${user.lastName}`
+    })
+    const authorsList = await Promise.all(authorsPromises)
+
+    return {
+      authorsList,
+      submittingAuthor,
+    }
+  }
+
+  getReviewerInvitations({ agree = true }) {
+    const { fragment: { invitations } } = this
+    return agree
+      ? invitations.filter(
+          inv =>
+            inv.role === 'reviewer' &&
+            inv.hasAnswer === true &&
+            inv.isAccepted === true,
+        )
+      : invitations.filter(
+          inv => inv.role === 'reviewer' && inv.hasAnswer === false,
+        )
+  }
 }
 
 module.exports = Fragment
diff --git a/packages/component-helper-service/src/services/Invitation.js b/packages/component-helper-service/src/services/Invitation.js
index ac595dabf0ee0f4880a343f29c8c4376c171a1e6..ce06b5ad4d8617a2c97506bc5759cdbb10200906 100644
--- a/packages/component-helper-service/src/services/Invitation.js
+++ b/packages/component-helper-service/src/services/Invitation.js
@@ -33,7 +33,7 @@ class Invitation {
     return { invitedOn, respondedOn, status, id }
   }
 
-  async setupInvitation({ collection }) {
+  async createInvitation({ parentObject }) {
     const { userId, role } = this
     const invitation = {
       role,
@@ -44,9 +44,10 @@ class Invitation {
       userId,
       respondedOn: null,
     }
-    collection.invitations = collection.invitations || []
-    collection.invitations.push(invitation)
-    collection = await collection.save()
+    parentObject.invitations = parentObject.invitations || []
+    parentObject.invitations.push(invitation)
+    await parentObject.save()
+
     return invitation
   }
 
@@ -56,6 +57,22 @@ class Invitation {
         invitation.userId === this.userId && invitation.role === this.role,
     )
   }
+
+  validateInvitation({ invitation }) {
+    if (invitation === undefined)
+      return { status: 404, error: 'Invitation not found.' }
+
+    if (invitation.hasAnswer)
+      return { status: 400, error: 'Invitation has already been answered.' }
+
+    if (invitation.userId !== this.userId)
+      return {
+        status: 403,
+        error: 'User is not allowed to modify this invitation.',
+      }
+
+    return { error: null }
+  }
 }
 
 module.exports = Invitation
diff --git a/packages/component-helper-service/src/services/Team.js b/packages/component-helper-service/src/services/Team.js
index c53eec64bdcdb7fa2b3b08a1749541ca360dd8bc..9d8d6148a8159b24180e22164247fcf3874aa2fa 100644
--- a/packages/component-helper-service/src/services/Team.js
+++ b/packages/component-helper-service/src/services/Team.js
@@ -99,6 +99,7 @@ class Team {
     const objectId = objectType === 'collection' ? collectionId : fragmentId
 
     const teams = await TeamModel.all()
+
     const members = get(
       teams.find(
         team =>
@@ -124,7 +125,7 @@ class Team {
     )
   }
 
-  async updateHETeam({ collection, role, user }) {
+  async deleteHandlingEditor({ collection, role, user }) {
     const team = await this.getTeam({
       role,
       objectType: 'collection',
diff --git a/packages/component-helper-service/src/services/authsome.js b/packages/component-helper-service/src/services/authsome.js
index 212cee2a3ea23a424b1f77dde2f7dd4cf888b2a0..b2215bb20f4d5d50cd0f75fff2607ba79f514443 100644
--- a/packages/component-helper-service/src/services/authsome.js
+++ b/packages/component-helper-service/src/services/authsome.js
@@ -12,6 +12,7 @@ const getAuthsome = models =>
       models: {
         Collection: {
           find: id => models.Collection.find(id),
+          all: () => models.Collection.all(),
         },
         Fragment: {
           find: id => models.Fragment.find(id),
diff --git a/packages/component-invite/config/authsome-helpers.js b/packages/component-invite/config/authsome-helpers.js
index b5f42492b077cee12d5a09d97404fb559e601f5e..164a9ce8ae550d9cd1709543ceb852169220cdc6 100644
--- a/packages/component-invite/config/authsome-helpers.js
+++ b/packages/component-invite/config/authsome-helpers.js
@@ -14,9 +14,9 @@ const parseAuthorsData = (coll, matchingCollPerm) => {
 
 const setPublicStatuses = (coll, matchingCollPerm) => {
   const status = get(coll, 'status') || 'draft'
-  coll.visibleStatus = statuses[status].public
-  if (!publicStatusesPermissions.includes(matchingCollPerm.permission)) {
-    coll.visibleStatus = statuses[coll.status].private
+  // coll.visibleStatus = statuses[status].public
+  if (publicStatusesPermissions.includes(matchingCollPerm.permission)) {
+    coll.visibleStatus = statuses[status].public
   }
 }
 
@@ -44,7 +44,10 @@ const filterObjectData = (
           rec => rec.userId === user.id,
         )
     }
-
+    parseAuthorsData(object, matchingCollPerm)
+    if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
+      return filterRefusedInvitations(object, user)
+    }
     return object
   }
   const matchingCollPerm = collectionsPermissions.find(
@@ -52,22 +55,18 @@ const filterObjectData = (
   )
   if (matchingCollPerm === undefined) return null
   setPublicStatuses(object, matchingCollPerm)
-  parseAuthorsData(object, matchingCollPerm)
-  if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
-    return filterRefusedInvitations(object, user)
-  }
 
   return object
 }
 
-const getTeamsByPermissions = async (teamIds, permissions, TeamModel) => {
+const getTeamsByPermissions = async (teamIds = [], permissions, TeamModel) => {
   const teams = await Promise.all(
     teamIds.map(async teamId => {
       const team = await TeamModel.find(teamId)
-      if (permissions.includes(team.teamType.permissions)) {
-        return team
+      if (!permissions.includes(team.teamType.permissions)) {
+        return null
       }
-      return null
+      return team
     }),
   )
 
diff --git a/packages/component-invite/config/authsome-mode.js b/packages/component-invite/config/authsome-mode.js
index 762998f83e80d5e678595bb8bf7e57072607adbe..8a92a1301df7ea1bed2dc105beb44834cdb9b16f 100644
--- a/packages/component-invite/config/authsome-mode.js
+++ b/packages/component-invite/config/authsome-mode.js
@@ -4,6 +4,7 @@ const omit = require('lodash/omit')
 const helpers = require('./authsome-helpers')
 
 async function teamPermissions(user, operation, object, context) {
+  const { models } = context
   const permissions = ['handlingEditor', 'author', 'reviewer']
   const teams = await helpers.getTeamsByPermissions(
     user.teams,
@@ -11,22 +12,38 @@ async function teamPermissions(user, operation, object, context) {
     context.models.Team,
   )
 
-  const collectionsPermissions = await Promise.all(
+  let collectionsPermissions = await Promise.all(
     teams.map(async team => {
-      const collection = await context.models.Collection.find(team.object.id)
+      let collection
+      if (team.object.type === 'collection') {
+        collection = await models.Collection.find(team.object.id)
+      } else if (team.object.type === 'fragment') {
+        const fragment = await models.Fragment.find(team.object.id)
+        collection = await models.Collection.find(fragment.collectionId)
+      }
+      if (
+        collection.status === 'rejected' &&
+        team.teamType.permissions === 'reviewer'
+      )
+        return null
       const collPerm = {
         id: collection.id,
         permission: team.teamType.permissions,
       }
       const objectType = get(object, 'type')
-      if (objectType === 'fragment' && collection.fragments.includes(object.id))
-        collPerm.fragmentId = object.id
+      if (objectType === 'fragment') {
+        if (collection.fragments.includes(object.id))
+          collPerm.fragmentId = object.id
+        else return null
+      }
 
+      if (objectType === 'collection')
+        if (object.id !== collection.id) return null
       return collPerm
     }),
   )
-
-  if (collectionsPermissions.length === 0) return {}
+  collectionsPermissions = collectionsPermissions.filter(cp => cp !== null)
+  if (collectionsPermissions.length === 0) return false
 
   return {
     filter: filterParam => {
@@ -109,11 +126,16 @@ async function authenticatedUser(user, operation, object, context) {
     return true
   }
 
-  // Allow the authenticated user to GET collections they own
-  if (operation === 'GET' && object === '/collections/') {
-    return {
-      filter: collection => collection.owners.includes(user.id),
+  // allow authenticate owners full pass for a collection
+  if (get(object, 'type') === 'collection') {
+    if (operation === 'PATCH') {
+      return {
+        filter: collection => omit(collection, 'filtered'),
+      }
     }
+    if (object.owners.includes(user.id)) return true
+    const owner = object.owners.find(own => own.id === user.id)
+    if (owner !== undefined) return true
   }
 
   // Allow owners of a collection to GET its teams, e.g.
@@ -170,32 +192,36 @@ async function authenticatedUser(user, operation, object, context) {
     return false
   }
 
-  // only allow a reviewer to submit and to modify a recommendation
+  // only allow a reviewer and an HE to submit and to modify a recommendation
   if (
     ['POST', 'PATCH'].includes(operation) &&
-    get(object.collection, 'type') === 'collection' &&
     object.path.includes('recommendations')
   ) {
-    const collection = await context.models.Collection.find(
-      get(object.collection, 'id'),
-    )
+    const authsomeObject = get(object, 'authsomeObject')
+
     const teams = await helpers.getTeamsByPermissions(
       user.teams,
-      ['reviewer'],
+      ['reviewer', 'handlingEditor'],
       context.models.Team,
     )
+
     if (teams.length === 0) return false
-    const matchingTeam = teams.find(team => team.object.id === collection.id)
+    const matchingTeam = teams.find(
+      team => team.object.id === authsomeObject.id,
+    )
+
     if (matchingTeam) return true
     return false
   }
 
-  if (user.teams.length !== 0 && operation === 'GET') {
+  if (user.teams.length !== 0 && ['GET'].includes(operation)) {
     const permissions = await teamPermissions(user, operation, object, context)
 
     if (permissions) {
       return permissions
     }
+
+    return false
   }
 
   if (get(object, 'type') === 'fragment') {
@@ -206,19 +232,6 @@ async function authenticatedUser(user, operation, object, context) {
     }
   }
 
-  if (get(object, 'type') === 'collection') {
-    if (['GET', 'DELETE'].includes(operation)) {
-      return true
-    }
-
-    // Only allow filtered updating (mirroring filtered creation) for non-admin users)
-    if (operation === 'PATCH') {
-      return {
-        filter: collection => omit(collection, 'filtered'),
-      }
-    }
-  }
-
   // A user can GET, DELETE and PATCH itself
   if (get(object, 'type') === 'user' && get(object, 'id') === user.id) {
     if (['GET', 'DELETE', 'PATCH'].includes(operation)) {
@@ -240,7 +253,7 @@ const authsomeMode = async (userId, operation, object, context) => {
   const user = await context.models.User.find(userId)
 
   // Admins and editor in chiefs can do anything
-  if (user && (user.admin === true || user.editorInChief === true)) return true
+  if (user && (user.admin || user.editorInChief)) return true
 
   if (user) {
     return authenticatedUser(user, operation, object, context)
diff --git a/packages/component-invite/index.js b/packages/component-invite/index.js
index 0e21fe5223f0a0a71bebd53d509b0bef3a5e3e02..f9bc6f5a6ef371301ad40dc80c6f06c59b67577f 100644
--- a/packages/component-invite/index.js
+++ b/packages/component-invite/index.js
@@ -1,5 +1,6 @@
 module.exports = {
   backend: () => app => {
     require('./src/CollectionsInvitations')(app)
+    require('./src/FragmentsInvitations')(app)
   },
 }
diff --git a/packages/component-invite/src/CollectionsInvitations.js b/packages/component-invite/src/CollectionsInvitations.js
index ba6fa2d852bf1a14b39b182297eff50f2c8661b7..117d14f742928f49b3f3704f051705057615a3ba 100644
--- a/packages/component-invite/src/CollectionsInvitations.js
+++ b/packages/component-invite/src/CollectionsInvitations.js
@@ -14,13 +14,13 @@ const CollectionsInvitations = app => {
    * @apiParamExample {json} Body
    *    {
    *      "email": "email@example.com",
-   *      "role": "handlingEditor", [acceptedValues: handlingEditor, reviewer]
+   *      "role": "handlingEditor", [acceptedValues: handlingEditor]
    *    }
    * @apiSuccessExample {json} Success
    *    HTTP/1.1 200 OK
    *   {
    *     "id": "7b2431af-210c-49f9-a69a-e19271066ebd",
-   *     "role": "reviewer",
+   *     "role": "handlingEditor",
    *     "userId": "4c3f8ee1-785b-4adb-87b4-407a27f652c6",
    *     "hasAnswer": false,
    *     "invitedOn": 1525428890167,
@@ -43,7 +43,7 @@ const CollectionsInvitations = app => {
    * @apiGroup CollectionsInvitations
    * @apiParam {id} collectionId Collection id
    * @apiParam {id} [invitationId] Invitation id
-   * @apiParam {String} role The role to search for: handlingEditor, reviewer, author
+   * @apiParam {String} role The role to search for: handlingEditor
    * @apiSuccessExample {json} Success
    *    HTTP/1.1 200 OK
    *    [{
@@ -95,7 +95,7 @@ const CollectionsInvitations = app => {
    *    HTTP/1.1 200 OK
    *   {
    *     "id": "7b2431af-210c-49f9-a69a-e19271066ebd",
-   *     "role": "reviewer",
+   *     "role": "handlingEditor",
    *     "userId": "4c3f8ee1-785b-4adb-87b4-407a27f652c6",
    *     "hasAnswer": true,
    *     "invitedOn": 1525428890167,
@@ -113,28 +113,6 @@ const CollectionsInvitations = app => {
     authBearer,
     require(`${routePath}/patch`)(app.locals.models),
   )
-  /**
-   * @api {patch} /api/collections/:collectionId/invitations/:invitationId/decline Decline an invitation as a reviewer
-   * @apiGroup CollectionsInvitations
-   * @apiParam {collectionId} collectionId Collection id
-   * @apiParam {invitationId} invitationId Invitation id
-   * @apiParamExample {json} Body
-   *    {
-   *      "invitationToken": "f2d814f0-67a5-4590-ba4f-6a83565feb4f",
-   *    }
-   * @apiSuccessExample {json} Success
-   *    HTTP/1.1 200 OK
-   *    {}
-   * @apiErrorExample {json} Update invitations errors
-   *    HTTP/1.1 403 Forbidden
-   *    HTTP/1.1 400 Bad Request
-   *    HTTP/1.1 404 Not Found
-   *    HTTP/1.1 500 Internal Server Error
-   */
-  app.patch(
-    `${basePath}/:invitationId/decline`,
-    require(`${routePath}/decline`)(app.locals.models),
-  )
 }
 
 module.exports = CollectionsInvitations
diff --git a/packages/component-invite/src/FragmentsInvitations.js b/packages/component-invite/src/FragmentsInvitations.js
new file mode 100644
index 0000000000000000000000000000000000000000..7a4d2507424cbeb5a5a0c0357779508fed009a8d
--- /dev/null
+++ b/packages/component-invite/src/FragmentsInvitations.js
@@ -0,0 +1,145 @@
+const bodyParser = require('body-parser')
+
+const FragmentsInvitations = app => {
+  app.use(bodyParser.json())
+  const basePath =
+    '/api/collections/:collectionId/fragments/:fragmentId/invitations'
+  const routePath = './routes/fragmentsInvitations'
+  const authBearer = app.locals.passport.authenticate('bearer', {
+    session: false,
+  })
+  /**
+   * @api {post} /api/collections/:collectionId/fragments/:fragmentId/invitations Invite a user to a fragment
+   * @apiGroup FragmentsInvitations
+   * @apiParam {collectionId} collectionId Collection id
+   * @apiParam {fragmentId} fragmentId Fragment id
+   * @apiParamExample {json} Body
+   *    {
+   *      "email": "email@example.com",
+   *      "role": "reviewer", [acceptedValues: reviewer]
+   *    }
+   * @apiSuccessExample {json} Success
+   *    HTTP/1.1 200 OK
+   *   {
+   *     "id": "7b2431af-210c-49f9-a69a-e19271066ebd",
+   *     "role": "reviewer",
+   *     "userId": "4c3f8ee1-785b-4adb-87b4-407a27f652c6",
+   *     "hasAnswer": false,
+   *     "invitedOn": 1525428890167,
+   *     "isAccepted": false,
+   *     "respondedOn": null
+   *    }
+   * @apiErrorExample {json} Invite user errors
+   *    HTTP/1.1 403 Forbidden
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   *    HTTP/1.1 500 Internal Server Error
+   */
+  app.post(
+    basePath,
+    authBearer,
+    require(`${routePath}/post`)(app.locals.models),
+  )
+  /**
+   * @api {get} /api/collections/:collectionId/fragments/:fragmentId/invitations/[:invitationId]?role=:role List fragment invitations
+   * @apiGroup FragmentsInvitations
+   * @apiParam {id} collectionId Collection id
+   * @apiParam {id} fragmentId Fragment id
+   * @apiParam {id} [invitationId] Invitation id
+   * @apiParam {String} role The role to search for: reviewer
+   * @apiSuccessExample {json} Success
+   *    HTTP/1.1 200 OK
+   *    [{
+   *      "name": "John Smith",
+   *     "invitedOn": 1525428890167,
+   *     "respondedOn": 1525428890299,
+   *      "email": "email@example.com",
+   *      "status": "pending",
+   *      "invitationId": "1990881"
+   *    }]
+   * @apiErrorExample {json} List errors
+   *    HTTP/1.1 403 Forbidden
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   */
+  app.get(
+    `${basePath}/:invitationId?`,
+    authBearer,
+    require(`${routePath}/get`)(app.locals.models),
+  )
+  /**
+   * @api {delete} /api/collections/:collectionId/fragments/:fragmentId/invitations/:invitationId Delete invitation
+   * @apiGroup FragmentsInvitations
+   * @apiParam {collectionId} collectionId Collection id
+   * @apiParam {fragmentId} fragmentId Fragment id
+   * @apiParam {invitationId} invitationId Invitation id
+   * @apiSuccessExample {json} Success
+   *    HTTP/1.1 204 No Content
+   * @apiErrorExample {json} Delete errors
+   *    HTTP/1.1 403 Forbidden
+   *    HTTP/1.1 404 Not Found
+   *    HTTP/1.1 500 Internal Server Error
+   */
+  app.delete(
+    `${basePath}/:invitationId`,
+    authBearer,
+    require(`${routePath}/delete`)(app.locals.models),
+  )
+  /**
+   * @api {patch} /api/collections/:collectionId/fragments/:fragmentId/invitations/:invitationId Update an invitation
+   * @apiGroup FragmentsInvitations
+   * @apiParam {collectionId} collectionId Collection id
+   * @apiParam {invitationId} invitationId Invitation id
+   * @apiParam {fragmentId} fragmentId Fragment id
+   * @apiParamExample {json} Body
+   *    {
+   *      "isAccepted": false,
+   *      "reason": "I am not ready" [optional]
+   *    }
+   * @apiSuccessExample {json} Success
+   *    HTTP/1.1 200 OK
+   *   {
+   *     "id": "7b2431af-210c-49f9-a69a-e19271066ebd",
+   *     "role": "reviewer",
+   *     "userId": "4c3f8ee1-785b-4adb-87b4-407a27f652c6",
+   *     "hasAnswer": true,
+   *     "invitedOn": 1525428890167,
+   *     "isAccepted": false,
+   *     "respondedOn": 1525428890299
+   *    }
+   * @apiErrorExample {json} Update invitations errors
+   *    HTTP/1.1 403 Forbidden
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   *    HTTP/1.1 500 Internal Server Error
+   */
+  app.patch(
+    `${basePath}/:invitationId`,
+    authBearer,
+    require(`${routePath}/patch`)(app.locals.models),
+  )
+  /**
+   * @api {patch} /api/collections/:collectionId/fragments/:fragmentId/invitations/:invitationId/decline Decline an invitation as a reviewer
+   * @apiGroup FragmentsInvitations
+   * @apiParam {collectionId} collectionId Collection id
+   * @apiParam {invitationId} invitationId Invitation id
+   * @apiParamExample {json} Body
+   *    {
+   *      "invitationToken": "f2d814f0-67a5-4590-ba4f-6a83565feb4f",
+   *    }
+   * @apiSuccessExample {json} Success
+   *    HTTP/1.1 200 OK
+   *    {}
+   * @apiErrorExample {json} Update invitations errors
+   *    HTTP/1.1 403 Forbidden
+   *    HTTP/1.1 400 Bad Request
+   *    HTTP/1.1 404 Not Found
+   *    HTTP/1.1 500 Internal Server Error
+   */
+  app.patch(
+    `${basePath}/:invitationId/decline`,
+    require(`${routePath}/decline`)(app.locals.models),
+  )
+}
+
+module.exports = FragmentsInvitations
diff --git a/packages/component-invite/src/routes/collectionsInvitations/delete.js b/packages/component-invite/src/routes/collectionsInvitations/delete.js
index e3e69c0ecc26e772b0428004df9fa9134010d141..b7c77e4f41780107d756bb3637ed55b45ac0eff9 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/delete.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/delete.js
@@ -1,14 +1,9 @@
 const config = require('config')
-const logger = require('@pubsweet/logger')
-const last = require('lodash/last')
 const mailService = require('pubsweet-component-mail-service')
 
 const {
   services,
   Team,
-  Email,
-  Fragment,
-  Collection,
   authsome: authsomeHelper,
 } = require('pubsweet-component-helper-service')
 
@@ -19,7 +14,7 @@ module.exports = models => async (req, res) => {
 
   try {
     const collection = await models.Collection.find(collectionId)
-    const collectionHelper = new Collection({ collection })
+
     const authsome = authsomeHelper.getAuthsome(models)
     const target = {
       collection,
@@ -30,79 +25,48 @@ module.exports = models => async (req, res) => {
       return res.status(403).json({
         error: 'Unauthorized.',
       })
-    const invitation = await collection.invitations.find(
+
+    collection.invitations = collection.invitations || []
+    const invitation = collection.invitations.find(
       invitation => invitation.id === invitationId,
     )
-    if (invitation === undefined) {
-      res.status(404).json({
+    if (!invitation)
+      return res.status(404).json({
         error: `Invitation ${invitationId} not found`,
       })
-      return
-    }
 
-    const team = await teamHelper.getTeamByGroupAndCollection({
+    const team = await teamHelper.getTeam({
       role: invitation.role,
+      objectType: 'collection',
     })
 
     collection.invitations = collection.invitations.filter(
       inv => inv.id !== invitation.id,
     )
-    if (invitation.role === 'handlingEditor') {
-      collection.status = 'submitted'
-      collection.visibleStatus = statuses[collection.status].private
-      delete collection.handlingEditor
-    } else if (invitation.role === 'reviewer') {
-      await collectionHelper.updateStatusByNumberOfReviewers()
-    }
+
+    collection.status = 'submitted'
+    collection.visibleStatus = statuses[collection.status].private
+    delete collection.handlingEditor
     await collection.save()
+
     await teamHelper.removeTeamMember({
       teamId: team.id,
       userId: invitation.userId,
     })
+
     const UserModel = models.User
     const user = await UserModel.find(invitation.userId)
     user.teams = user.teams.filter(userTeamId => team.id !== userTeamId)
     await user.save()
-    try {
-      if (invitation.role === 'handlingEditor') {
-        await mailService.sendSimpleEmail({
-          toEmail: user.email,
-          emailType: 'revoke-handling-editor',
-        })
-      } else if (invitation.role === 'reviewer') {
-        const collectionHelper = new Collection({ collection })
-        const fragment = await models.Fragment.find(last(collection.fragments))
-        const fragmentHelper = new Fragment({ fragment })
-        const parsedFragment = await fragmentHelper.getFragmentData({
-          handlingEditor: collection.handlingEditor,
-        })
-        const baseUrl = services.getBaseUrl(req)
-        const {
-          authorsList: authors,
-          submittingAuthor,
-        } = await collectionHelper.getAuthorData({ UserModel })
-        const emailHelper = new Email({
-          UserModel,
-          collection,
-          parsedFragment,
-          baseUrl,
-          authors,
-        })
-        emailHelper.setupReviewerUnassignEmail({
-          user,
-          authorName: `${submittingAuthor.firstName} ${
-            submittingAuthor.lastName
-          }`,
-        })
-      }
 
-      return res.status(200).json({})
-    } catch (e) {
-      logger.error(e.message)
-      return res.status(500).json({ error: 'Email could not be sent.' })
-    }
+    mailService.sendSimpleEmail({
+      toEmail: user.email,
+      emailType: 'revoke-handling-editor',
+    })
+
+    return res.status(200).json({})
   } catch (e) {
-    const notFoundError = await services.handleNotFoundError(e, 'collection')
+    const notFoundError = await services.handleNotFoundError(e, 'Collection')
     return res.status(notFoundError.status).json({
       error: notFoundError.message,
     })
diff --git a/packages/component-invite/src/routes/collectionsInvitations/get.js b/packages/component-invite/src/routes/collectionsInvitations/get.js
index 18e1e0eb7a8a8c08a0fb5f3c34e220c91c1678c7..db8832dd49cd77e01908d23915ce26e6cc8bfd78 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/get.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/get.js
@@ -37,16 +37,18 @@ module.exports = models => async (req, res) => {
         error: 'Unauthorized.',
       })
 
-    const members = await teamHelper.getTeamMembersByCollection({
+    const members = await teamHelper.getTeamMembers({
       role,
+      objectType: 'collection',
     })
-    if (members === undefined) return res.status(200).json([])
+    if (!members) return res.status(200).json([])
 
     // TO DO: handle case for when the invitationID is provided
+    const invitationHelper = new Invitation({ role })
+
     const membersData = members.map(async member => {
       const user = await models.User.find(member)
-      const invitationHelper = new Invitation({ userId: user.id, role })
-
+      invitationHelper.userId = user.id
       const {
         invitedOn,
         respondedOn,
diff --git a/packages/component-invite/src/routes/collectionsInvitations/patch.js b/packages/component-invite/src/routes/collectionsInvitations/patch.js
index 69d6302f6184d69ead39341d5867748bec23477d..d28609053609980f7256195dda6e4c98bdc9f78e 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/patch.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/patch.js
@@ -1,144 +1,89 @@
-const logger = require('@pubsweet/logger')
 const mailService = require('pubsweet-component-mail-service')
-const last = require('lodash/last')
 
 const {
-  Email,
-  services,
-  Fragment,
-  Collection,
   Team,
   User,
+  services,
+  Collection,
+  Invitation,
 } = require('pubsweet-component-helper-service')
 
 module.exports = models => async (req, res) => {
   const { collectionId, invitationId } = req.params
   const { isAccepted, reason } = req.body
 
-  if (!services.checkForUndefinedParams(isAccepted)) {
-    res.status(400).json({ error: 'Missing parameters.' })
-    logger.error('some parameters are missing')
-    return
-  }
   const UserModel = models.User
   const user = await UserModel.find(req.user)
+
   try {
     const collection = await models.Collection.find(collectionId)
-    const invitation = await collection.invitations.find(
+    collection.invitations = collection.invitations || []
+    const invitation = collection.invitations.find(
       invitation => invitation.id === invitationId,
     )
-    if (invitation === undefined)
-      return res.status(404).json({
-        error: 'Invitation not found.',
-      })
-    if (invitation.hasAnswer)
-      return res
-        .status(400)
-        .json({ error: 'Invitation has already been answered.' })
-    if (invitation.userId !== user.id)
-      return res.status(403).json({
-        error: `User is not allowed to modify this invitation.`,
+
+    const invitationHelper = new Invitation({
+      userId: user.id,
+      role: 'handlingEditor',
+    })
+
+    const invitationValidation = invitationHelper.validateInvitation({
+      invitation,
+    })
+    if (invitationValidation.error)
+      return res.status(invitationValidation.status).json({
+        error: invitationValidation.error,
       })
 
     const collectionHelper = new Collection({ collection })
-    const fragment = await models.Fragment.find(last(collection.fragments))
-    const fragmentHelper = new Fragment({ fragment })
-    const parsedFragment = await fragmentHelper.getFragmentData({
-      handlingEditor: collection.handlingEditor,
-    })
     const baseUrl = services.getBaseUrl(req)
-    const {
-      authorsList: authors,
-      submittingAuthor,
-    } = await collectionHelper.getAuthorData({ UserModel })
-    const emailHelper = new Email({
-      UserModel,
-      collection,
-      parsedFragment,
-      baseUrl,
-      authors,
-    })
+
     const teamHelper = new Team({ TeamModel: models.Team, collectionId })
     const userHelper = new User({ UserModel })
 
-    if (invitation.role === 'handlingEditor')
-      await collectionHelper.updateHandlingEditor({ isAccepted })
+    await collectionHelper.updateHandlingEditor({ isAccepted })
     invitation.respondedOn = Date.now()
     invitation.hasAnswer = true
     const eic = await userHelper.getEditorInChief()
     const toEmail = eic.email
-    if (isAccepted === true) {
-      invitation.isAccepted = true
-      if (
-        invitation.role === 'reviewer' &&
-        collection.status === 'reviewersInvited'
-      )
-        await collectionHelper.updateStatus({ newStatus: 'underReview' })
 
-      await collection.save()
-      try {
-        if (invitation.role === 'handlingEditor')
-          await mailService.sendSimpleEmail({
-            toEmail,
-            user,
-            emailType: 'handling-editor-agreed',
-            dashboardUrl: baseUrl,
-            meta: {
-              collectionId: collection.customId,
-            },
-          })
-        if (invitation.role === 'reviewer')
-          emailHelper.setupReviewerDecisionEmail({
-            agree: true,
-            timestamp: invitation.respondedOn,
-            user,
-            authorName: `${submittingAuthor.firstName} ${
-              submittingAuthor.lastName
-            }`,
-          })
-        return res.status(200).json(invitation)
-      } catch (e) {
-        logger.error(e)
-        return res.status(500).json({ error: 'Email could not be sent.' })
-      }
-    } else {
-      invitation.isAccepted = false
-
-      if (invitation.role === 'handlingEditor')
-        await teamHelper.updateHETeam({
-          collection,
-          role: invitation.role,
-          user,
-        })
-      if (reason !== undefined) {
-        invitation.reason = reason
-      }
+    if (isAccepted) {
+      invitation.isAccepted = true
       await collection.save()
 
-      try {
-        if (invitation.role === 'handlingEditor') {
-          await mailService.sendSimpleEmail({
-            toEmail,
-            user,
-            emailType: 'handling-editor-declined',
-            meta: {
-              reason,
-              collectionId: collection.customId,
-            },
-          })
-        } else if (invitation.role === 'reviewer') {
-          collectionHelper.updateStatusByNumberOfReviewers()
-          emailHelper.setupReviewerDecisionEmail({
-            agree: false,
-            user,
-          })
-        }
-      } catch (e) {
-        logger.error(e)
-        return res.status(500).json({ error: 'Email could not be sent.' })
-      }
+      mailService.sendSimpleEmail({
+        toEmail,
+        user,
+        emailType: 'handling-editor-agreed',
+        dashboardUrl: baseUrl,
+        meta: {
+          collectionId: collection.customId,
+        },
+      })
+
+      return res.status(200).json(invitation)
     }
-    user.save()
+
+    await teamHelper.deleteHandlingEditor({
+      collection,
+      role: invitation.role,
+      user,
+    })
+
+    invitation.isAccepted = false
+    if (reason) invitation.reason = reason
+    await collection.save()
+
+    mailService.sendSimpleEmail({
+      toEmail,
+      user,
+      emailType: 'handling-editor-declined',
+      meta: {
+        reason,
+        collectionId: collection.customId,
+      },
+    })
+
     return res.status(200).json(invitation)
   } catch (e) {
     const notFoundError = await services.handleNotFoundError(e, 'collection')
diff --git a/packages/component-invite/src/routes/collectionsInvitations/post.js b/packages/component-invite/src/routes/collectionsInvitations/post.js
index 3bafaf954c6276637c2195b93ff0651ab53cdb05..3e20a3c89450bce8077a350249b150ac975dcab4 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/post.js
+++ b/packages/component-invite/src/routes/collectionsInvitations/post.js
@@ -1,49 +1,38 @@
-const config = require('config')
-const get = require('lodash/get')
-const last = require('lodash/last')
 const logger = require('@pubsweet/logger')
 const mailService = require('pubsweet-component-mail-service')
 const {
-  Email,
   services,
   authsome: authsomeHelper,
-  Fragment,
   Collection,
   Team,
   Invitation,
-  User,
 } = require('pubsweet-component-helper-service')
 
-const configRoles = config.get('roles')
-
 module.exports = models => async (req, res) => {
   const { email, role } = req.body
 
-  if (!services.checkForUndefinedParams(email, role)) {
-    res.status(400).json({ error: 'Email and role are required' })
-    logger.error('User ID and role are missing')
-    return
-  }
+  if (!services.checkForUndefinedParams(email, role))
+    return res.status(400).json({ error: 'Email and role are required' })
+
+  if (role !== 'handlingEditor')
+    return res.status(400).json({
+      error: `Role ${role} is invalid. Only handlingEditor is allowed.`,
+    })
 
-  if (!configRoles.collection.includes(role)) {
-    res.status(400).json({ error: `Role ${role} is invalid` })
-    logger.error(`invitation attempted on invalid role ${role}`)
-    return
-  }
   const UserModel = models.User
   const reqUser = await UserModel.find(req.user)
 
   if (email === reqUser.email && !reqUser.admin) {
     logger.error(`${reqUser.email} tried to invite his own email`)
-    return res.status(400).json({ error: 'Cannot invite yourself' })
+    return res.status(400).json({ error: 'Cannot invite yourself.' })
   }
 
-  const collectionId = get(req, 'params.collectionId')
+  const { collectionId } = req.params
   let collection
   try {
     collection = await models.Collection.find(collectionId)
   } catch (e) {
-    const notFoundError = await services.handleNotFoundError(e, 'collection')
+    const notFoundError = await services.handleNotFoundError(e, 'Collection')
     return res.status(notFoundError.status).json({
       error: notFoundError.message,
     })
@@ -61,115 +50,49 @@ module.exports = models => async (req, res) => {
     })
 
   const collectionHelper = new Collection({ collection })
-  const fragment = await models.Fragment.find(last(collection.fragments))
-  const fragmentHelper = new Fragment({ fragment })
-  const handlingEditor = collection.handlingEditor || {}
-  const parsedFragment = await fragmentHelper.getFragmentData({
-    handlingEditor,
-  })
   const baseUrl = services.getBaseUrl(req)
-  const {
-    authorsList: authors,
-    submittingAuthor,
-  } = await collectionHelper.getAuthorData({ UserModel })
-  const emailHelper = new Email({
-    UserModel,
-    collection,
-    parsedFragment,
-    baseUrl,
-    authors,
+
+  const teamHelper = new Team({
+    TeamModel: models.Team,
+    collectionId,
   })
-  const teamHelper = new Team({ TeamModel: models.Team, collectionId })
   const invitationHelper = new Invitation({ role })
 
   try {
     const user = await UserModel.findByEmail(email)
-    await teamHelper.setupManuscriptTeam({ user, role })
+    await teamHelper.setupTeam({ user, role, objectType: 'collection' })
     invitationHelper.userId = user.id
     let invitation = invitationHelper.getInvitation({
       invitations: collection.invitations,
     })
 
-    let resend = false
-    if (invitation !== undefined) {
+    if (invitation) {
       if (invitation.hasAnswer)
         return res
           .status(400)
           .json({ error: 'User has already replied to a previous invitation.' })
       invitation.invitedOn = Date.now()
       await collection.save()
-      resend = true
     } else {
-      invitation = await invitationHelper.setupInvitation({
-        collection,
+      invitation = await invitationHelper.createInvitation({
+        parentObject: collection,
       })
     }
 
-    try {
-      if (role === 'reviewer') {
-        if (collection.status === 'heAssigned')
-          await collectionHelper.updateStatus({ newStatus: 'reviewersInvited' })
+    invitation.invitedOn = Date.now()
+    await collection.save()
+    await collectionHelper.addHandlingEditor({ user, invitation })
 
-        await emailHelper.setupReviewerInvitationEmail({
-          user,
-          invitationId: invitation.id,
-          timestamp: invitation.invitedOn,
-          resend,
-          authorName: `${submittingAuthor.firstName} ${
-            submittingAuthor.lastName
-          }`,
-        })
-      }
+    mailService.sendSimpleEmail({
+      toEmail: user.email,
+      user,
+      emailType: 'assign-handling-editor',
+      dashboardUrl: baseUrl,
+    })
 
-      if (role === 'handlingEditor') {
-        invitation.invitedOn = Date.now()
-        await collection.save()
-        await collectionHelper.addHandlingEditor({ user, invitation })
-        mailService.sendSimpleEmail({
-          toEmail: user.email,
-          user,
-          emailType: 'assign-handling-editor',
-          dashboardUrl: baseUrl,
-        })
-      }
-      return res.status(200).json(invitation)
-    } catch (e) {
-      logger.error(e)
-      return res.status(500).json({ error: 'Email could not be sent.' })
-    }
+    return res.status(200).json(invitation)
   } catch (e) {
-    const userHelper = new User({ UserModel })
-    if (role === 'reviewer') {
-      const newUser = await userHelper.setupNewUser({
-        url: baseUrl,
-        role,
-        invitationType: 'invite',
-        body: req.body,
-      })
-      if (newUser.error !== undefined) {
-        return res.status(newUser.status).json({
-          error: newUser.message,
-        })
-      }
-      if (collection.status === 'heAssigned')
-        await collectionHelper.updateStatus({ newStatus: 'reviewersInvited' })
-      await teamHelper.setupManuscriptTeam({ user: newUser, role })
-      invitationHelper.userId = newUser.id
-      const invitation = await invitationHelper.setupInvitation({
-        collection,
-      })
-
-      await emailHelper.setupReviewerInvitationEmail({
-        user: newUser,
-        invitationId: invitation.id,
-        timestamp: invitation.invitedOn,
-        authorName: `${submittingAuthor.firstName} ${
-          submittingAuthor.lastName
-        }`,
-      })
-      return res.status(200).json(invitation)
-    }
-    const notFoundError = await services.handleNotFoundError(e, 'user')
+    const notFoundError = await services.handleNotFoundError(e, 'User')
     return res.status(notFoundError.status).json({
       error: notFoundError.message,
     })
diff --git a/packages/component-invite/src/routes/collectionsInvitations/decline.js b/packages/component-invite/src/routes/fragmentsInvitations/decline.js
similarity index 64%
rename from packages/component-invite/src/routes/collectionsInvitations/decline.js
rename to packages/component-invite/src/routes/fragmentsInvitations/decline.js
index b049a4a0aeb43f096b0ec673b338d91202440cc4..e867503771902281e8686ff0330e2508ca693dfa 100644
--- a/packages/component-invite/src/routes/collectionsInvitations/decline.js
+++ b/packages/component-invite/src/routes/fragmentsInvitations/decline.js
@@ -1,14 +1,12 @@
-const last = require('lodash/last')
-
 const {
   services,
   Email,
   Fragment,
-  Collection,
+  Invitation,
 } = require('pubsweet-component-helper-service')
 
 module.exports = models => async (req, res) => {
-  const { collectionId, invitationId } = req.params
+  const { collectionId, invitationId, fragmentId } = req.params
   const { invitationToken } = req.body
 
   if (!services.checkForUndefinedParams(invitationToken))
@@ -21,30 +19,34 @@ module.exports = models => async (req, res) => {
       invitationToken,
     )
     const collection = await models.Collection.find(collectionId)
-    const invitation = await collection.invitations.find(
+    if (!collection.fragments.includes(fragmentId))
+      return res.status(400).json({
+        error: `Fragment ${fragmentId} does not match collection ${collectionId}`,
+      })
+    const fragment = await models.Fragment.find(fragmentId)
+    fragment.invitations = fragment.invitations || []
+    const invitation = await fragment.invitations.find(
       invitation => invitation.id === invitationId,
     )
-    if (invitation === undefined)
-      return res.status(404).json({
-        error: `Invitation ${invitationId} not found`,
-      })
-    if (invitation.hasAnswer)
-      return res
-        .status(400)
-        .json({ error: `Invitation has already been answered.` })
-    if (invitation.userId !== user.id)
-      return res.status(403).json({
-        error: `User ${user.email} is not allowed to modify invitation ${
-          invitation.id
-        }`,
+
+    const invitationHelper = new Invitation({
+      userId: user.id,
+      role: 'reviewer',
+    })
+
+    const invitationValidation = invitationHelper.validateInvitation({
+      invitation,
+    })
+    if (invitationValidation.error)
+      return res.status(invitationValidation.status).json({
+        error: invitationValidation.error,
       })
 
     invitation.respondedOn = Date.now()
     invitation.hasAnswer = true
     invitation.isAccepted = false
-    await collection.save()
-    const collectionHelper = new Collection({ collection })
-    const fragment = await models.Fragment.find(last(collection.fragments))
+    await fragment.save()
+
     const fragmentHelper = new Fragment({ fragment })
     const parsedFragment = await fragmentHelper.getFragmentData({
       handlingEditor: collection.handlingEditor,
@@ -53,7 +55,7 @@ module.exports = models => async (req, res) => {
     const {
       authorsList: authors,
       submittingAuthor,
-    } = await collectionHelper.getAuthorData({ UserModel })
+    } = await fragmentHelper.getAuthorData({ UserModel })
     const emailHelper = new Email({
       UserModel,
       collection,
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/delete.js b/packages/component-invite/src/routes/fragmentsInvitations/delete.js
new file mode 100644
index 0000000000000000000000000000000000000000..b4da00bc268d61d124f9ef248f4ad3af04f91ecd
--- /dev/null
+++ b/packages/component-invite/src/routes/fragmentsInvitations/delete.js
@@ -0,0 +1,97 @@
+const {
+  services,
+  Team,
+  Email,
+  Fragment,
+  Collection,
+  authsome: authsomeHelper,
+} = require('pubsweet-component-helper-service')
+
+module.exports = models => async (req, res) => {
+  const { collectionId, invitationId, fragmentId } = req.params
+  const teamHelper = new Team({
+    TeamModel: models.Team,
+    collectionId,
+    fragmentId,
+  })
+
+  try {
+    const collection = await models.Collection.find(collectionId)
+    if (!collection.fragments.includes(fragmentId))
+      return res.status(400).json({
+        error: `Fragment ${fragmentId} does not match collection ${collectionId}.`,
+      })
+    const fragment = await models.Fragment.find(fragmentId)
+
+    const collectionHelper = new Collection({ collection })
+
+    const authsome = authsomeHelper.getAuthsome(models)
+    const target = {
+      collection,
+      path: req.route.path,
+    }
+    const canDelete = await authsome.can(req.user, 'DELETE', target)
+    if (!canDelete)
+      return res.status(403).json({
+        error: 'Unauthorized.',
+      })
+    fragment.invitations = fragment.invitations || []
+    const invitation = await fragment.invitations.find(
+      invitation => invitation.id === invitationId,
+    )
+    if (!invitation)
+      return res.status(404).json({
+        error: `Invitation ${invitationId} not found`,
+      })
+
+    const team = await teamHelper.getTeam({
+      role: invitation.role,
+      objectType: 'fragment',
+    })
+
+    fragment.invitations = fragment.invitations.filter(
+      inv => inv.id !== invitation.id,
+    )
+
+    await collectionHelper.updateStatusByNumberOfReviewers()
+
+    await teamHelper.removeTeamMember({
+      teamId: team.id,
+      userId: invitation.userId,
+    })
+
+    const UserModel = models.User
+    const user = await UserModel.find(invitation.userId)
+    user.teams = user.teams.filter(userTeamId => team.id !== userTeamId)
+    await user.save()
+
+    const fragmentHelper = new Fragment({ fragment })
+    const parsedFragment = await fragmentHelper.getFragmentData({
+      handlingEditor: collection.handlingEditor,
+    })
+    const baseUrl = services.getBaseUrl(req)
+    const {
+      authorsList: authors,
+      submittingAuthor,
+    } = await fragmentHelper.getAuthorData({ UserModel })
+    const emailHelper = new Email({
+      UserModel,
+      collection,
+      parsedFragment,
+      baseUrl,
+      authors,
+    })
+
+    emailHelper.setupReviewerUnassignEmail({
+      user,
+      authorName: `${submittingAuthor.firstName} ${submittingAuthor.lastName}`,
+    })
+
+    return res.status(200).json({})
+  } catch (e) {
+    const notFoundError = await services.handleNotFoundError(e, 'collection')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
+  }
+}
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/get.js b/packages/component-invite/src/routes/fragmentsInvitations/get.js
new file mode 100644
index 0000000000000000000000000000000000000000..2bb8c540e6576951ba8773108036dda02e8f2fee
--- /dev/null
+++ b/packages/component-invite/src/routes/fragmentsInvitations/get.js
@@ -0,0 +1,91 @@
+const config = require('config')
+const {
+  services,
+  Team,
+  Invitation,
+  authsome: authsomeHelper,
+} = require('pubsweet-component-helper-service')
+
+const configRoles = config.get('roles')
+
+module.exports = models => async (req, res) => {
+  const { role } = req.query
+  if (!services.checkForUndefinedParams(role)) {
+    res.status(400).json({ error: 'Role is required' })
+    return
+  }
+
+  if (!configRoles.collection.includes(role)) {
+    res.status(400).json({ error: `Role ${role} is invalid` })
+    return
+  }
+
+  const { collectionId, fragmentId } = req.params
+  const teamHelper = new Team({
+    TeamModel: models.Team,
+    collectionId,
+    fragmentId,
+  })
+
+  try {
+    const collection = await models.Collection.find(collectionId)
+    if (!collection.fragments.includes(fragmentId))
+      return res.status(400).json({
+        error: `Fragment ${fragmentId} does not match collection ${collectionId}.`,
+      })
+    const fragment = await models.Fragment.find(fragmentId)
+
+    const authsome = authsomeHelper.getAuthsome(models)
+    const target = {
+      fragment,
+      path: req.route.path,
+    }
+    const canGet = await authsome.can(req.user, 'GET', target)
+
+    if (!canGet)
+      return res.status(403).json({
+        error: 'Unauthorized.',
+      })
+
+    const members = await teamHelper.getTeamMembers({
+      role,
+      objectType: 'fragment',
+    })
+
+    if (!members) return res.status(200).json([])
+
+    // TO DO: handle case for when the invitationID is provided
+    const invitationHelper = new Invitation({ role })
+
+    const membersData = members.map(async member => {
+      const user = await models.User.find(member)
+      invitationHelper.userId = user.id
+      const {
+        invitedOn,
+        respondedOn,
+        status,
+        id,
+      } = invitationHelper.getInvitationsData({
+        invitations: fragment.invitations,
+      })
+
+      return {
+        name: `${user.firstName} ${user.lastName}`,
+        invitedOn,
+        respondedOn,
+        email: user.email,
+        status,
+        userId: user.id,
+        invitationId: id,
+      }
+    })
+
+    const resBody = await Promise.all(membersData)
+    res.status(200).json(resBody)
+  } catch (e) {
+    const notFoundError = await services.handleNotFoundError(e, 'Item')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
+  }
+}
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/patch.js b/packages/component-invite/src/routes/fragmentsInvitations/patch.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ecb11d6b286fd76b69d892e5222c71d424296c3
--- /dev/null
+++ b/packages/component-invite/src/routes/fragmentsInvitations/patch.js
@@ -0,0 +1,94 @@
+const {
+  Email,
+  services,
+  Fragment,
+  Collection,
+  Invitation,
+} = require('pubsweet-component-helper-service')
+
+module.exports = models => async (req, res) => {
+  const { collectionId, invitationId, fragmentId } = req.params
+  const { isAccepted, reason } = req.body
+
+  const UserModel = models.User
+  const user = await UserModel.find(req.user)
+  try {
+    const collection = await models.Collection.find(collectionId)
+    if (!collection.fragments.includes(fragmentId))
+      return res.status(400).json({
+        error: `Fragment ${fragmentId} does not match collection ${collectionId}`,
+      })
+    const fragment = await models.Fragment.find(fragmentId)
+    fragment.invitations = fragment.invitations || []
+    const invitation = await fragment.invitations.find(
+      invitation => invitation.id === invitationId,
+    )
+
+    const invitationHelper = new Invitation({
+      userId: user.id,
+      role: 'reviewer',
+    })
+    const invitationValidation = invitationHelper.validateInvitation({
+      invitation,
+    })
+    if (invitationValidation.error)
+      return res.status(invitationValidation.status).json({
+        error: invitationValidation.error,
+      })
+
+    const collectionHelper = new Collection({ collection })
+    const fragmentHelper = new Fragment({ fragment })
+    const parsedFragment = await fragmentHelper.getFragmentData({
+      handlingEditor: collection.handlingEditor,
+    })
+    const baseUrl = services.getBaseUrl(req)
+    const {
+      authorsList: authors,
+      submittingAuthor,
+    } = await fragmentHelper.getAuthorData({ UserModel })
+    const emailHelper = new Email({
+      UserModel,
+      collection,
+      parsedFragment,
+      baseUrl,
+      authors,
+    })
+
+    invitation.respondedOn = Date.now()
+    invitation.hasAnswer = true
+    if (isAccepted) {
+      invitation.isAccepted = true
+      if (collection.status === 'reviewersInvited')
+        await collectionHelper.updateStatus({ newStatus: 'underReview' })
+      await fragment.save()
+
+      emailHelper.setupReviewerDecisionEmail({
+        agree: true,
+        timestamp: invitation.respondedOn,
+        user,
+        authorName: `${submittingAuthor.firstName} ${
+          submittingAuthor.lastName
+        }`,
+      })
+
+      return res.status(200).json(invitation)
+    }
+
+    invitation.isAccepted = false
+    if (reason) invitation.reason = reason
+    await fragment.save()
+
+    collectionHelper.updateStatusByNumberOfReviewers()
+    emailHelper.setupReviewerDecisionEmail({
+      agree: false,
+      user,
+    })
+
+    return res.status(200).json(invitation)
+  } catch (e) {
+    const notFoundError = await services.handleNotFoundError(e, 'Item')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
+  }
+}
diff --git a/packages/component-invite/src/routes/fragmentsInvitations/post.js b/packages/component-invite/src/routes/fragmentsInvitations/post.js
new file mode 100644
index 0000000000000000000000000000000000000000..38991ec08d033de565d5685d7ab6077b978d8504
--- /dev/null
+++ b/packages/component-invite/src/routes/fragmentsInvitations/post.js
@@ -0,0 +1,159 @@
+const logger = require('@pubsweet/logger')
+const {
+  Email,
+  services,
+  authsome: authsomeHelper,
+  Fragment,
+  Collection,
+  Team,
+  Invitation,
+  User,
+} = require('pubsweet-component-helper-service')
+
+module.exports = models => async (req, res) => {
+  const { email, role } = req.body
+
+  if (!services.checkForUndefinedParams(email, role)) {
+    res.status(400).json({ error: 'Email and role are required.' })
+    logger.error('User ID and role are missing')
+    return
+  }
+
+  if (role !== 'reviewer')
+    return res
+      .status(400)
+      .json({ error: `Role ${role} is invalid. Only reviewer is accepted.` })
+
+  const UserModel = models.User
+  const reqUser = await UserModel.find(req.user)
+
+  if (email === reqUser.email && !reqUser.admin)
+    return res.status(400).json({ error: 'Cannot invite yourself.' })
+
+  const { collectionId, fragmentId } = req.params
+  let collection, fragment
+
+  try {
+    collection = await models.Collection.find(collectionId)
+    if (!collection.fragments.includes(fragmentId))
+      return res.status(400).json({
+        error: `Fragment ${fragmentId} does not match collection ${collectionId}.`,
+      })
+    fragment = await models.Fragment.find(fragmentId)
+  } catch (e) {
+    const notFoundError = await services.handleNotFoundError(e, 'item')
+    return res.status(notFoundError.status).json({
+      error: notFoundError.message,
+    })
+  }
+
+  const authsome = authsomeHelper.getAuthsome(models)
+  const target = {
+    collection,
+    path: req.route.path,
+  }
+  const canPost = await authsome.can(req.user, 'POST', target)
+  if (!canPost)
+    return res.status(403).json({
+      error: 'Unauthorized.',
+    })
+
+  const collectionHelper = new Collection({ collection })
+  const fragmentHelper = new Fragment({ fragment })
+  const handlingEditor = collection.handlingEditor || {}
+  const parsedFragment = await fragmentHelper.getFragmentData({
+    handlingEditor,
+  })
+  const baseUrl = services.getBaseUrl(req)
+  const {
+    authorsList: authors,
+    submittingAuthor,
+  } = await fragmentHelper.getAuthorData({ UserModel })
+  const emailHelper = new Email({
+    UserModel,
+    collection,
+    parsedFragment,
+    baseUrl,
+    authors,
+  })
+  const teamHelper = new Team({
+    TeamModel: models.Team,
+    collectionId,
+    fragmentId,
+  })
+  const invitationHelper = new Invitation({ role })
+
+  try {
+    const user = await UserModel.findByEmail(email)
+    await teamHelper.setupTeam({ user, role, objectType: 'fragment' })
+    invitationHelper.userId = user.id
+
+    let invitation = invitationHelper.getInvitation({
+      invitations: fragment.invitations,
+    })
+    let resend = false
+
+    if (invitation) {
+      if (invitation.hasAnswer)
+        return res
+          .status(400)
+          .json({ error: 'User has already replied to a previous invitation.' })
+
+      invitation.invitedOn = Date.now()
+      await fragment.save()
+      resend = true
+    } else {
+      invitation = await invitationHelper.createInvitation({
+        parentObject: fragment,
+      })
+    }
+
+    if (collection.status === 'heAssigned')
+      await collectionHelper.updateStatus({ newStatus: 'reviewersInvited' })
+
+    emailHelper.setupReviewerInvitationEmail({
+      user,
+      invitationId: invitation.id,
+      timestamp: invitation.invitedOn,
+      resend,
+      authorName: `${submittingAuthor.firstName} ${submittingAuthor.lastName}`,
+    })
+
+    return res.status(200).json(invitation)
+  } catch (e) {
+    const userHelper = new User({ UserModel })
+
+    const newUser = await userHelper.setupNewUser({
+      url: baseUrl,
+      role,
+      invitationType: 'invite',
+      body: req.body,
+    })
+
+    if (newUser.error !== undefined) {
+      return res.status(newUser.status).json({
+        error: newUser.message,
+      })
+    }
+
+    if (collection.status === 'heAssigned')
+      await collectionHelper.updateStatus({ newStatus: 'reviewersInvited' })
+
+    await teamHelper.setupTeam({ user: newUser, role, objectType: 'fragment' })
+
+    invitationHelper.userId = newUser.id
+
+    const invitation = await invitationHelper.createInvitation({
+      parentObject: fragment,
+    })
+
+    emailHelper.setupReviewerInvitationEmail({
+      user: newUser,
+      invitationId: invitation.id,
+      timestamp: invitation.invitedOn,
+      authorName: `${submittingAuthor.firstName} ${submittingAuthor.lastName}`,
+    })
+
+    return res.status(200).json(invitation)
+  }
+}
diff --git a/packages/component-invite/src/tests/collectionsInvitations/delete.test.js b/packages/component-invite/src/tests/collectionsInvitations/delete.test.js
index 4e6594490c6661e1db150e367b42f348997436bb..7fdef707ccadb721ce6099a69001da73ec29c552 100644
--- a/packages/component-invite/src/tests/collectionsInvitations/delete.test.js
+++ b/packages/component-invite/src/tests/collectionsInvitations/delete.test.js
@@ -2,15 +2,15 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
 process.env.SUPPRESS_NO_CONFIG_WARNING = true
 
 const cloneDeep = require('lodash/cloneDeep')
-const Model = require('./../helpers/Model')
-const fixtures = require('./../fixtures/fixtures')
-const requests = require('./../helpers/requests')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
 
+const { Model, fixtures } = fixturesService
 jest.mock('pubsweet-component-mail-service', () => ({
   sendSimpleEmail: jest.fn(),
 }))
 
-const path = '../../routes/collectionsInvitations/delete'
+const path = '../routes/collectionsInvitations/delete'
 const route = {
   path: '/api/collections/:collectionId/invitations/:invitationId',
 }
@@ -35,7 +35,7 @@ describe('Delete Collections Invitations route handler', () => {
     })
     expect(res.statusCode).toBe(404)
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('collection not found')
+    expect(data.error).toEqual('Collection not found')
   })
   it('should return an error when the invitation does not exist', async () => {
     const { editorInChief } = testFixtures.users
diff --git a/packages/component-invite/src/tests/collectionsInvitations/get.test.js b/packages/component-invite/src/tests/collectionsInvitations/get.test.js
index 7cf8a64115d3cfd58db7db627638ee37d296f43d..d630fa94cd909f282e74d0dd442dcd13b46f587d 100644
--- a/packages/component-invite/src/tests/collectionsInvitations/get.test.js
+++ b/packages/component-invite/src/tests/collectionsInvitations/get.test.js
@@ -1,17 +1,17 @@
 process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
 process.env.SUPPRESS_NO_CONFIG_WARNING = true
 
-const fixtures = require('./../fixtures/fixtures')
-const Model = require('./../helpers/Model')
 const cloneDeep = require('lodash/cloneDeep')
-const requests = require('./../helpers/requests')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
 
+const { Model, fixtures } = fixturesService
 jest.mock('pubsweet-component-mail-service', () => ({
   sendSimpleEmail: jest.fn(),
   sendNotificationEmail: jest.fn(),
   sendReviewerInvitationEmail: jest.fn(),
 }))
-const path = '../../routes/collectionsInvitations/get'
+const path = '../routes/collectionsInvitations/get'
 const route = {
   path: '/api/collections/:collectionId/invitations/:invitationId?',
 }
@@ -124,7 +124,7 @@ describe('Get collection invitations route handler', () => {
       models,
       path,
       query: {
-        role: 'reviewer',
+        role: 'handlingEditor',
       },
       params: {
         collectionId: collection.id,
diff --git a/packages/component-invite/src/tests/collectionsInvitations/patch.test.js b/packages/component-invite/src/tests/collectionsInvitations/patch.test.js
index 129b791c24ae9fdaf09f3f4bcc8573e1abff2985..7ed11f1546f5b45726c79c3ce683c0eeee0f776e 100644
--- a/packages/component-invite/src/tests/collectionsInvitations/patch.test.js
+++ b/packages/component-invite/src/tests/collectionsInvitations/patch.test.js
@@ -2,10 +2,10 @@ 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 cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
 
+const { Model, fixtures } = fixturesService
 jest.mock('pubsweet-component-mail-service', () => ({
   sendSimpleEmail: jest.fn(),
   sendNotificationEmail: jest.fn(),
@@ -41,22 +41,6 @@ describe('Patch collections invitations route handler', () => {
     await require(patchPath)(models)(req, res)
     expect(res.statusCode).toBe(200)
   })
-  it('should return success when the reviewer agrees work on a collection', async () => {
-    const { reviewer } = testFixtures.users
-    const { collection } = testFixtures.collections
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = reviewer.id
-    req.params.collectionId = collection.id
-    const reviewerInv = collection.invitations.find(
-      inv => inv.role === 'reviewer' && inv.hasAnswer === false,
-    )
-    req.params.invitationId = reviewerInv.id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-    expect(res.statusCode).toBe(200)
-  })
   it('should return success when the handling editor declines work on a collection', async () => {
     const { handlingEditor } = testFixtures.users
     const { collection } = testFixtures.collections
@@ -75,41 +59,6 @@ describe('Patch collections invitations route handler', () => {
 
     expect(res.statusCode).toBe(200)
   })
-  it('should return success when the reviewer declines work on a collection', async () => {
-    const { reviewer } = testFixtures.users
-    const { collection } = testFixtures.collections
-    body.isAccepted = false
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = reviewer.id
-    req.params.collectionId = collection.id
-    const inv = collection.invitations.find(
-      inv => inv.role === 'reviewer' && inv.hasAnswer === false,
-    )
-    req.params.invitationId = inv.id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-
-    expect(res.statusCode).toBe(200)
-  })
-  it('should return an error params are missing', async () => {
-    const { handlingEditor } = testFixtures.users
-    const { collection } = testFixtures.collections
-    delete body.isAccepted
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = handlingEditor.id
-    req.params.collectionId = collection.id
-    req.params.invitationId = collection.invitations[0].id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-
-    expect(res.statusCode).toBe(400)
-    const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('Missing parameters.')
-  })
   it('should return an error if the collection does not exists', async () => {
     const { handlingEditor } = testFixtures.users
     const req = httpMocks.createRequest({
diff --git a/packages/component-invite/src/tests/collectionsInvitations/post.test.js b/packages/component-invite/src/tests/collectionsInvitations/post.test.js
index 53eda4a07751aebb1120d5c00eadec0a60a87ae3..0608aced450fbf604c14fb2af509309262808ddc 100644
--- a/packages/component-invite/src/tests/collectionsInvitations/post.test.js
+++ b/packages/component-invite/src/tests/collectionsInvitations/post.test.js
@@ -1,15 +1,12 @@
 process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
 process.env.SUPPRESS_NO_CONFIG_WARNING = true
 
-const random = require('lodash/random')
-const fixtures = require('./../fixtures/fixtures')
 const Chance = require('chance')
-const Model = require('./../helpers/Model')
-const config = require('config')
 const cloneDeep = require('lodash/cloneDeep')
-const requests = require('./../helpers/requests')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
 
-const configRoles = config.get('roles')
+const { Model, fixtures } = fixturesService
 
 jest.mock('pubsweet-component-mail-service', () => ({
   sendSimpleEmail: jest.fn(),
@@ -17,10 +14,9 @@ jest.mock('pubsweet-component-mail-service', () => ({
   sendReviewerInvitationEmail: jest.fn(),
 }))
 const chance = new Chance()
-const roles = configRoles.collection
 const reqBody = {
   email: chance.email(),
-  role: roles[random(0, roles.length - 1)],
+  role: 'handlingEditor',
   firstName: chance.first(),
   lastName: chance.last(),
   title: 'Mr',
@@ -31,7 +27,7 @@ const route = {
   path: '/api/collections/:collectionId/invitations',
 }
 
-const path = '../../routes/collectionsInvitations/post'
+const path = '../routes/collectionsInvitations/post'
 describe('Post collections invitations route handler', () => {
   let testFixtures = {}
   let body = {}
@@ -79,31 +75,8 @@ describe('Post collections invitations route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.role).toEqual(body.role)
   })
-  it('should return success when the a reviewer is invited', async () => {
-    const { user, editorInChief } = testFixtures.users
-    const { collection } = testFixtures.collections
-    body = {
-      email: user.email,
-      role: 'reviewer',
-    }
-    const res = await requests.sendRequest({
-      body,
-      userId: editorInChief.id,
-      route,
-      models,
-      path,
-      params: {
-        collectionId: collection.id,
-      },
-    })
-
-    expect(res.statusCode).toBe(200)
-    const data = JSON.parse(res._getData())
-    expect(data.role).toEqual(body.role)
-  })
   it('should return an error when inviting his self', async () => {
     const { editorInChief } = testFixtures.users
-    body.role = roles[random(0, roles.length - 1)]
     body.email = editorInChief.email
     const res = await requests.sendRequest({
       body,
@@ -114,7 +87,7 @@ describe('Post collections invitations route handler', () => {
     })
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('Cannot invite yourself')
+    expect(data.error).toEqual('Cannot invite yourself.')
   })
   it('should return an error when the role is invalid', async () => {
     const { editorInChief } = testFixtures.users
@@ -127,7 +100,9 @@ describe('Post collections invitations route handler', () => {
       path,
     })
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual(`Role ${body.role} is invalid`)
+    expect(data.error).toEqual(
+      `Role ${body.role} is invalid. Only handlingEditor is allowed.`,
+    )
   })
   it('should return success when the EiC resends an invitation to a handlingEditor with a collection', async () => {
     const { handlingEditor, editorInChief } = testFixtures.users
@@ -152,15 +127,15 @@ describe('Post collections invitations route handler', () => {
     expect(data.role).toEqual(body.role)
   })
   it('should return an error when the invitation is already answered', async () => {
-    const { answerReviewer, handlingEditor } = testFixtures.users
+    const { answerHE, editorInChief } = testFixtures.users
     const { collection } = testFixtures.collections
     body = {
-      email: answerReviewer.email,
-      role: 'reviewer',
+      email: answerHE.email,
+      role: 'handlingEditor',
     }
     const res = await requests.sendRequest({
       body,
-      userId: handlingEditor.id,
+      userId: editorInChief.id,
       route,
       models,
       path,
diff --git a/packages/component-invite/src/tests/fixtures/collections.js b/packages/component-invite/src/tests/fixtures/collections.js
deleted file mode 100644
index bef6132199981a79b87ea86440146eaab5891883..0000000000000000000000000000000000000000
--- a/packages/component-invite/src/tests/fixtures/collections.js
+++ /dev/null
@@ -1,68 +0,0 @@
-const Chance = require('chance')
-const {
-  user,
-  handlingEditor,
-  author,
-  reviewer,
-  answerReviewer,
-} = require('./userData')
-const { fragment } = require('./fragments')
-
-const chance = new Chance()
-const collections = {
-  collection: {
-    id: chance.guid(),
-    title: chance.sentence(),
-    type: 'collection',
-    fragments: [fragment.id],
-    owners: [user.id],
-    save: jest.fn(),
-    authors: [
-      {
-        userId: author.id,
-        isSubmitting: true,
-        isCorresponding: false,
-      },
-    ],
-    invitations: [
-      {
-        id: chance.guid(),
-        role: 'handlingEditor',
-        hasAnswer: false,
-        isAccepted: false,
-        userId: handlingEditor.id,
-        invitedOn: chance.timestamp(),
-        respondedOn: null,
-      },
-      {
-        id: chance.guid(),
-        role: 'reviewer',
-        hasAnswer: false,
-        isAccepted: false,
-        userId: reviewer.id,
-        invitedOn: chance.timestamp(),
-        respondedOn: null,
-      },
-      {
-        id: chance.guid(),
-        role: 'reviewer',
-        hasAnswer: true,
-        isAccepted: false,
-        userId: answerReviewer.id,
-        invitedOn: chance.timestamp(),
-        respondedOn: chance.timestamp(),
-      },
-    ],
-    handlingEditor: {
-      id: handlingEditor.id,
-      hasAnswer: false,
-      isAccepted: false,
-      email: handlingEditor.email,
-      invitedOn: chance.timestamp(),
-      respondedOn: null,
-      name: `${handlingEditor.firstName} ${handlingEditor.lastName}`,
-    },
-  },
-}
-
-module.exports = collections
diff --git a/packages/component-invite/src/tests/fixtures/fragments.js b/packages/component-invite/src/tests/fixtures/fragments.js
deleted file mode 100644
index 08d0eedf3a531c317cbfb79e58a6d1b019413564..0000000000000000000000000000000000000000
--- a/packages/component-invite/src/tests/fixtures/fragments.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const Chance = require('chance')
-
-const chance = new Chance()
-const fragments = {
-  fragment: {
-    id: chance.guid(),
-    metadata: {
-      title: chance.sentence(),
-      abstract: chance.paragraph(),
-    },
-    recommendations: [],
-  },
-}
-
-module.exports = fragments
diff --git a/packages/component-invite/src/tests/fixtures/teamIDs.js b/packages/component-invite/src/tests/fixtures/teamIDs.js
deleted file mode 100644
index 607fd6661b1e7c9848bf13257a0d198f230fab00..0000000000000000000000000000000000000000
--- a/packages/component-invite/src/tests/fixtures/teamIDs.js
+++ /dev/null
@@ -1,10 +0,0 @@
-const Chance = require('chance')
-
-const chance = new Chance()
-const heID = chance.guid()
-const revId = chance.guid()
-
-module.exports = {
-  heTeamID: heID,
-  revTeamID: revId,
-}
diff --git a/packages/component-invite/src/tests/fixtures/userData.js b/packages/component-invite/src/tests/fixtures/userData.js
deleted file mode 100644
index 546e2d867998c6de1cab8e10a4df20fb74d5288d..0000000000000000000000000000000000000000
--- a/packages/component-invite/src/tests/fixtures/userData.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const Chance = require('chance')
-
-const chance = new Chance()
-const generateUserData = () => ({
-  id: chance.guid(),
-  email: chance.email(),
-  firstName: chance.first(),
-  lastName: chance.last(),
-})
-
-module.exports = {
-  handlingEditor: generateUserData(),
-  user: generateUserData(),
-  admin: generateUserData(),
-  author: generateUserData(),
-  reviewer: generateUserData(),
-  answerReviewer: generateUserData(),
-}
diff --git a/packages/component-invite/src/tests/collectionsInvitations/decline.test.js b/packages/component-invite/src/tests/fragmentsInvitations/decline.test.js
similarity index 73%
rename from packages/component-invite/src/tests/collectionsInvitations/decline.test.js
rename to packages/component-invite/src/tests/fragmentsInvitations/decline.test.js
index 562c3d92981611c95a57664e281fe482a0b04fc3..ece17117d96e22a7c4c7cf2288d537c18b4bf788 100644
--- a/packages/component-invite/src/tests/collectionsInvitations/decline.test.js
+++ b/packages/component-invite/src/tests/fragmentsInvitations/decline.test.js
@@ -2,8 +2,9 @@ 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 fixturesService = require('pubsweet-component-fixture-service')
+
+const { Model, fixtures } = fixturesService
 const cloneDeep = require('lodash/cloneDeep')
 
 jest.mock('pubsweet-component-mail-service', () => ({
@@ -13,8 +14,8 @@ jest.mock('pubsweet-component-mail-service', () => ({
 const reqBody = {
   invitationToken: 'inv-token-123',
 }
-const patchPath = '../../routes/collectionsInvitations/decline'
-describe('Patch collections invitations route handler', () => {
+const patchPath = '../../routes/fragmentsInvitations/decline'
+describe('Decline fragments invitations route handler', () => {
   let testFixtures = {}
   let body = {}
   let models
@@ -26,12 +27,16 @@ describe('Patch collections invitations route handler', () => {
   it('should return success when the reviewer declines work on a collection', async () => {
     const { reviewer } = testFixtures.users
     const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
     const req = httpMocks.createRequest({
       body,
     })
     req.user = reviewer.id
     req.params.collectionId = collection.id
-    const inv = collection.invitations.find(
+    req.params.fragmentId = fragment.id
+
+    const inv = fragment.invitations.find(
       inv => inv.role === 'reviewer' && inv.hasAnswer === false,
     )
     req.params.invitationId = inv.id
@@ -42,13 +47,14 @@ describe('Patch collections invitations route handler', () => {
   it('should return an error params are missing', async () => {
     const { reviewer } = testFixtures.users
     const { collection } = testFixtures.collections
+
     delete body.invitationToken
     const req = httpMocks.createRequest({
       body,
     })
     req.user = reviewer.id
     req.params.collectionId = collection.id
-    req.params.invitationId = collection.invitations[0].id
+
     const res = httpMocks.createResponse()
     await require(patchPath)(models)(req, res)
 
@@ -70,32 +76,59 @@ describe('Patch collections invitations route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('item not found')
   })
+  it('should return an error if the fragment does not exists', async () => {
+    const { reviewer } = testFixtures.users
+    const { collection } = testFixtures.collections
+
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = reviewer.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = 'invalid-id'
+
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `Fragment invalid-id does not match collection ${collection.id}`,
+    )
+  })
   it('should return an error when the invitation does not exist', async () => {
     const { user } = testFixtures.users
     const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
     const req = httpMocks.createRequest({
       body,
     })
     req.user = user.id
     req.params.collectionId = collection.id
+    req.params.fragmentId = fragment.id
+
     req.params.invitationId = 'invalid-id'
     const res = httpMocks.createResponse()
     await require(patchPath)(models)(req, res)
 
     expect(res.statusCode).toBe(404)
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('Invitation invalid-id not found')
+    expect(data.error).toEqual('Invitation not found.')
   })
   it('should return an error when the token is invalid', async () => {
     const { reviewer } = testFixtures.users
     const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
     body.invitationToken = 'invalid-token'
     const req = httpMocks.createRequest({
       body,
     })
     req.user = reviewer.id
     req.params.collectionId = collection.id
-    const inv = collection.invitations.find(
+    req.params.fragmentId = fragment.id
+
+    const inv = fragment.invitations.find(
       inv => inv.role === 'reviewer' && inv.hasAnswer === false,
     )
     req.params.invitationId = inv.id
@@ -108,12 +141,16 @@ describe('Patch collections invitations route handler', () => {
   it('should return an error when the invitation is already answered', async () => {
     const { reviewer } = testFixtures.users
     const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
     const req = httpMocks.createRequest({
       body,
     })
     req.user = reviewer.id
     req.params.collectionId = collection.id
-    const inv = collection.invitations.find(inv => inv.hasAnswer)
+    req.params.fragmentId = fragment.id
+
+    const inv = fragment.invitations.find(inv => inv.hasAnswer)
     req.params.invitationId = inv.id
     const res = httpMocks.createResponse()
     await require(patchPath)(models)(req, res)
diff --git a/packages/component-invite/src/tests/fragmentsInvitations/delete.test.js b/packages/component-invite/src/tests/fragmentsInvitations/delete.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..8396173f8262ce7698b2a1ddbed0c17b6fcb7b60
--- /dev/null
+++ b/packages/component-invite/src/tests/fragmentsInvitations/delete.test.js
@@ -0,0 +1,117 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
+
+const { Model, fixtures } = fixturesService
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+}))
+
+const path = '../routes/fragmentsInvitations/delete'
+const route = {
+  path:
+    '/api/collections/:collectionId/fragments/:fragmentId/invitations/:invitationId',
+}
+
+describe('Delete Fragments Invitations route handler', () => {
+  let testFixtures = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    models = Model.build(testFixtures)
+  })
+  it('should return an error when the collection does not exist', async () => {
+    const { editorInChief } = testFixtures.users
+    const res = await requests.sendRequest({
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: 'invalid-id',
+      },
+    })
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('collection not found')
+  })
+  it('should return an error when the fragment does not exist', async () => {
+    const { editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+
+    const res = await requests.sendRequest({
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: 'invalid-id',
+      },
+    })
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `Fragment invalid-id does not match collection ${collection.id}.`,
+    )
+  })
+  it('should return an error when the invitation does not exist', async () => {
+    const { editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    const res = await requests.sendRequest({
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+        invitationId: 'invalid-id',
+      },
+    })
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Invitation invalid-id not found')
+  })
+  it('should return success when the collection and invitation exist', async () => {
+    const { editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    const res = await requests.sendRequest({
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+        invitationId: fragment.invitations[0].id,
+      },
+    })
+    expect(res.statusCode).toBe(200)
+  })
+  it('should return an error when the user does not have invitation rights', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      userId: user.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+        invitationId: collection.invitations[0].id,
+      },
+    })
+    expect(res.statusCode).toBe(403)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Unauthorized.')
+  })
+})
diff --git a/packages/component-invite/src/tests/fragmentsInvitations/get.test.js b/packages/component-invite/src/tests/fragmentsInvitations/get.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..043e162fac230e383f769e197ec47ca0ba8debf9
--- /dev/null
+++ b/packages/component-invite/src/tests/fragmentsInvitations/get.test.js
@@ -0,0 +1,145 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
+
+const { Model, fixtures } = fixturesService
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+  sendNotificationEmail: jest.fn(),
+  sendReviewerInvitationEmail: jest.fn(),
+}))
+const path = '../routes/fragmentsInvitations/get'
+const route = {
+  path:
+    '/api/collections/:collectionId/fragments/:fragmentId/invitations/:invitationId?',
+}
+describe('Get fragment invitations route handler', () => {
+  let testFixtures = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    models = Model.build(testFixtures)
+  })
+  it('should return success when the request data is correct', async () => {
+    const { handlingEditor } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      userId: handlingEditor.id,
+      route,
+      models,
+      path,
+      query: {
+        role: 'reviewer',
+      },
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(200)
+    const data = JSON.parse(res._getData())
+    expect(data.length).toBeGreaterThan(0)
+  })
+  it('should return an error when parameters are missing', async () => {
+    const { handlingEditor } = testFixtures.users
+    const res = await requests.sendRequest({
+      userId: handlingEditor.id,
+      route,
+      models,
+      path,
+    })
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Role is required')
+  })
+  it('should return an error when the collection does not exist', async () => {
+    const { handlingEditor } = testFixtures.users
+    const res = await requests.sendRequest({
+      userId: handlingEditor.id,
+      route,
+      models,
+      path,
+      query: {
+        role: 'reviewer',
+      },
+      params: {
+        collectionId: 'invalid-id',
+      },
+    })
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Item not found')
+  })
+  it('should return an error when the fragment does not exist', async () => {
+    const { handlingEditor } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const res = await requests.sendRequest({
+      userId: handlingEditor.id,
+      route,
+      models,
+      path,
+      query: {
+        role: 'reviewer',
+      },
+      params: {
+        collectionId: collection.id,
+        fragmentId: 'invalid-id',
+      },
+    })
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `Fragment invalid-id does not match collection ${collection.id}.`,
+    )
+  })
+  it('should return an error when the role is invalid', async () => {
+    const { handlingEditor } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      userId: handlingEditor.id,
+      route,
+      models,
+      path,
+      query: {
+        role: 'invalidRole',
+      },
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(`Role invalidRole is invalid`)
+  })
+  it('should return an error when a user does not have invitation rights', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      userId: user.id,
+      route,
+      models,
+      path,
+      query: {
+        role: 'reviewer',
+      },
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+    expect(res.statusCode).toBe(403)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Unauthorized.')
+  })
+})
diff --git a/packages/component-invite/src/tests/fragmentsInvitations/patch.test.js b/packages/component-invite/src/tests/fragmentsInvitations/patch.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..93006adf157c73b8eff63846e335c3bdb8d46a23
--- /dev/null
+++ b/packages/component-invite/src/tests/fragmentsInvitations/patch.test.js
@@ -0,0 +1,160 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const httpMocks = require('node-mocks-http')
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+
+const { Model, fixtures } = fixturesService
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+  sendNotificationEmail: jest.fn(),
+  sendReviewerInvitationEmail: jest.fn(),
+}))
+
+const reqBody = {
+  isAccepted: true,
+}
+const patchPath = '../../routes/fragmentsInvitations/patch'
+describe('Patch fragments invitations route handler', () => {
+  let testFixtures = {}
+  let body = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    body = cloneDeep(reqBody)
+    models = Model.build(testFixtures)
+  })
+  it('should return success when the reviewer agrees work on a collection', async () => {
+    const { reviewer } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = reviewer.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = fragment.id
+    const reviewerInv = fragment.invitations.find(
+      inv => inv.role === 'reviewer' && inv.hasAnswer === false,
+    )
+    req.params.invitationId = reviewerInv.id
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+    expect(res.statusCode).toBe(200)
+  })
+  it('should return success when the reviewer declines work on a collection', async () => {
+    const { reviewer } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    body.isAccepted = false
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = reviewer.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = fragment.id
+
+    const inv = fragment.invitations.find(
+      inv => inv.role === 'reviewer' && inv.hasAnswer === false,
+    )
+    req.params.invitationId = inv.id
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(200)
+  })
+  it('should return an error if the collection does not exists', async () => {
+    const { handlingEditor } = testFixtures.users
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = handlingEditor.id
+    req.params.collectionId = 'invalid-id'
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Item not found')
+  })
+  it('should return an error when the invitation does not exist', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = user.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = fragment.id
+    req.params.invitationId = 'invalid-id'
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Invitation not found.')
+  })
+  it('should return an error when the fragment does not exist', async () => {
+    const { user } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = user.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = fragment.id
+    req.params.invitationId = 'invalid-id'
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(404)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Invitation not found.')
+  })
+  it("should return an error when a user tries to patch another user's invitation", async () => {
+    const { handlingEditor } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = handlingEditor.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = fragment.id
+    const inv = fragment.invitations.find(
+      inv => inv.role === 'reviewer' && inv.hasAnswer === false,
+    )
+    req.params.invitationId = inv.id
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+    expect(res.statusCode).toBe(403)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(`User is not allowed to modify this invitation.`)
+  })
+  it('should return an error when the invitation is already answered', async () => {
+    const { handlingEditor } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = handlingEditor.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = fragment.id
+
+    const inv = fragment.invitations.find(inv => inv.hasAnswer)
+    req.params.invitationId = inv.id
+    const res = httpMocks.createResponse()
+    await require(patchPath)(models)(req, res)
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(`Invitation has already been answered.`)
+  })
+})
diff --git a/packages/component-invite/src/tests/fragmentsInvitations/post.test.js b/packages/component-invite/src/tests/fragmentsInvitations/post.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..21da70379fe9ba9dc1e06df3802f6a331000a6fb
--- /dev/null
+++ b/packages/component-invite/src/tests/fragmentsInvitations/post.test.js
@@ -0,0 +1,155 @@
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
+process.env.SUPPRESS_NO_CONFIG_WARNING = true
+
+const Chance = require('chance')
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
+
+const { Model, fixtures } = fixturesService
+
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+  sendNotificationEmail: jest.fn(),
+  sendReviewerInvitationEmail: jest.fn(),
+}))
+const chance = new Chance()
+const reqBody = {
+  email: chance.email(),
+  role: 'reviewer',
+  firstName: chance.first(),
+  lastName: chance.last(),
+  title: 'Mr',
+  affiliation: chance.company(),
+  admin: false,
+}
+const route = {
+  path: '/api/collections/:collectionId/fragments/:fragmentId/invitations',
+}
+
+const path = '../routes/fragmentsInvitations/post'
+describe('Post fragments invitations route handler', () => {
+  let testFixtures = {}
+  let body = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    body = cloneDeep(reqBody)
+    models = Model.build(testFixtures)
+  })
+  it('should return an error params are missing', async () => {
+    const { admin } = testFixtures.users
+    delete body.email
+    const res = await requests.sendRequest({
+      body,
+      userId: admin.id,
+      route,
+      models,
+      path,
+    })
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Email and role are required.')
+  })
+  it('should return success when the a reviewer is invited', async () => {
+    const { user, editorInChief } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    body = {
+      email: user.email,
+      role: 'reviewer',
+    }
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(200)
+    const data = JSON.parse(res._getData())
+    expect(data.role).toEqual(body.role)
+  })
+  it('should return an error when inviting his self', async () => {
+    const { editorInChief } = testFixtures.users
+    body.email = editorInChief.email
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+    })
+    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 () => {
+    const { editorInChief } = testFixtures.users
+    body.role = 'someRandomRole'
+    const res = await requests.sendRequest({
+      body,
+      userId: editorInChief.id,
+      route,
+      models,
+      path,
+    })
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `Role ${body.role} is invalid. Only reviewer is accepted.`,
+    )
+  })
+  it('should return an error when the invitation is already answered', async () => {
+    const { answerReviewer, handlingEditor } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+    body = {
+      email: answerReviewer.email,
+      role: 'reviewer',
+    }
+    const res = await requests.sendRequest({
+      body,
+      userId: handlingEditor.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `User has already replied to a previous invitation.`,
+    )
+  })
+  it('should return an error when the user does not have invitation rights', async () => {
+    const { author } = testFixtures.users
+    const { collection } = testFixtures.collections
+    const { fragment } = testFixtures.fragments
+
+    const res = await requests.sendRequest({
+      body,
+      userId: author.id,
+      route,
+      models,
+      path,
+      params: {
+        collectionId: collection.id,
+        fragmentId: fragment.id,
+      },
+    })
+
+    expect(res.statusCode).toBe(403)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual('Unauthorized.')
+  })
+})
diff --git a/packages/component-invite/src/tests/helpers/requests.js b/packages/component-invite/src/tests/requests.js
similarity index 100%
rename from packages/component-invite/src/tests/helpers/requests.js
rename to packages/component-invite/src/tests/requests.js
diff --git a/packages/component-manuscript-manager/config/authsome-helpers.js b/packages/component-manuscript-manager/config/authsome-helpers.js
index 1add8d99b7fb8eca56a2abb7bf43088d599e5efc..164a9ce8ae550d9cd1709543ceb852169220cdc6 100644
--- a/packages/component-manuscript-manager/config/authsome-helpers.js
+++ b/packages/component-manuscript-manager/config/authsome-helpers.js
@@ -44,7 +44,10 @@ const filterObjectData = (
           rec => rec.userId === user.id,
         )
     }
-
+    parseAuthorsData(object, matchingCollPerm)
+    if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
+      return filterRefusedInvitations(object, user)
+    }
     return object
   }
   const matchingCollPerm = collectionsPermissions.find(
@@ -52,10 +55,6 @@ const filterObjectData = (
   )
   if (matchingCollPerm === undefined) return null
   setPublicStatuses(object, matchingCollPerm)
-  parseAuthorsData(object, matchingCollPerm)
-  if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
-    return filterRefusedInvitations(object, user)
-  }
 
   return object
 }
@@ -64,10 +63,10 @@ const getTeamsByPermissions = async (teamIds = [], permissions, TeamModel) => {
   const teams = await Promise.all(
     teamIds.map(async teamId => {
       const team = await TeamModel.find(teamId)
-      if (permissions.includes(team.teamType.permissions)) {
-        return team
+      if (!permissions.includes(team.teamType.permissions)) {
+        return null
       }
-      return null
+      return team
     }),
   )
 
diff --git a/packages/component-manuscript-manager/config/authsome-mode.js b/packages/component-manuscript-manager/config/authsome-mode.js
index 667879b274b86a59a28515ed3d595d97e6ad2808..8a92a1301df7ea1bed2dc105beb44834cdb9b16f 100644
--- a/packages/component-manuscript-manager/config/authsome-mode.js
+++ b/packages/component-manuscript-manager/config/authsome-mode.js
@@ -4,6 +4,7 @@ const omit = require('lodash/omit')
 const helpers = require('./authsome-helpers')
 
 async function teamPermissions(user, operation, object, context) {
+  const { models } = context
   const permissions = ['handlingEditor', 'author', 'reviewer']
   const teams = await helpers.getTeamsByPermissions(
     user.teams,
@@ -11,22 +12,38 @@ async function teamPermissions(user, operation, object, context) {
     context.models.Team,
   )
 
-  const collectionsPermissions = await Promise.all(
+  let collectionsPermissions = await Promise.all(
     teams.map(async team => {
-      const collection = await context.models.Collection.find(team.object.id)
+      let collection
+      if (team.object.type === 'collection') {
+        collection = await models.Collection.find(team.object.id)
+      } else if (team.object.type === 'fragment') {
+        const fragment = await models.Fragment.find(team.object.id)
+        collection = await models.Collection.find(fragment.collectionId)
+      }
+      if (
+        collection.status === 'rejected' &&
+        team.teamType.permissions === 'reviewer'
+      )
+        return null
       const collPerm = {
         id: collection.id,
         permission: team.teamType.permissions,
       }
       const objectType = get(object, 'type')
-      if (objectType === 'fragment' && collection.fragments.includes(object.id))
-        collPerm.fragmentId = object.id
+      if (objectType === 'fragment') {
+        if (collection.fragments.includes(object.id))
+          collPerm.fragmentId = object.id
+        else return null
+      }
 
+      if (objectType === 'collection')
+        if (object.id !== collection.id) return null
       return collPerm
     }),
   )
-
-  if (collectionsPermissions.length === 0) return {}
+  collectionsPermissions = collectionsPermissions.filter(cp => cp !== null)
+  if (collectionsPermissions.length === 0) return false
 
   return {
     filter: filterParam => {
@@ -109,11 +126,16 @@ async function authenticatedUser(user, operation, object, context) {
     return true
   }
 
-  // Allow the authenticated user to GET collections they own
-  if (operation === 'GET' && object === '/collections/') {
-    return {
-      filter: collection => collection.owners.includes(user.id),
+  // allow authenticate owners full pass for a collection
+  if (get(object, 'type') === 'collection') {
+    if (operation === 'PATCH') {
+      return {
+        filter: collection => omit(collection, 'filtered'),
+      }
     }
+    if (object.owners.includes(user.id)) return true
+    const owner = object.owners.find(own => own.id === user.id)
+    if (owner !== undefined) return true
   }
 
   // Allow owners of a collection to GET its teams, e.g.
@@ -173,29 +195,33 @@ async function authenticatedUser(user, operation, object, context) {
   // only allow a reviewer and an HE to submit and to modify a recommendation
   if (
     ['POST', 'PATCH'].includes(operation) &&
-    get(object.collection, 'type') === 'collection' &&
     object.path.includes('recommendations')
   ) {
-    const collection = await context.models.Collection.find(
-      get(object.collection, 'id'),
-    )
+    const authsomeObject = get(object, 'authsomeObject')
+
     const teams = await helpers.getTeamsByPermissions(
       user.teams,
       ['reviewer', 'handlingEditor'],
       context.models.Team,
     )
+
     if (teams.length === 0) return false
-    const matchingTeam = teams.find(team => team.object.id === collection.id)
+    const matchingTeam = teams.find(
+      team => team.object.id === authsomeObject.id,
+    )
+
     if (matchingTeam) return true
     return false
   }
 
-  if (user.teams.length !== 0 && operation === 'GET') {
+  if (user.teams.length !== 0 && ['GET'].includes(operation)) {
     const permissions = await teamPermissions(user, operation, object, context)
 
     if (permissions) {
       return permissions
     }
+
+    return false
   }
 
   if (get(object, 'type') === 'fragment') {
@@ -206,19 +232,6 @@ async function authenticatedUser(user, operation, object, context) {
     }
   }
 
-  if (get(object, 'type') === 'collection') {
-    if (['GET', 'DELETE'].includes(operation)) {
-      return true
-    }
-
-    // Only allow filtered updating (mirroring filtered creation) for non-admin users)
-    if (operation === 'PATCH') {
-      return {
-        filter: collection => omit(collection, 'filtered'),
-      }
-    }
-  }
-
   // A user can GET, DELETE and PATCH itself
   if (get(object, 'type') === 'user' && get(object, 'id') === user.id) {
     if (['GET', 'DELETE', 'PATCH'].includes(operation)) {
@@ -240,7 +253,7 @@ const authsomeMode = async (userId, operation, object, context) => {
   const user = await context.models.User.find(userId)
 
   // Admins and editor in chiefs can do anything
-  if (user && (user.admin === true || user.editorInChief === true)) return true
+  if (user && (user.admin || user.editorInChief)) return true
 
   if (user) {
     return authenticatedUser(user, operation, object, context)
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js
index fc771b67cbee2448a50bbad958be986c0c7b96d7..cda67212c7da2f4733b359596820d85974ef9e68 100644
--- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/patch.js
@@ -5,37 +5,16 @@ const {
   Fragment,
   Collection,
 } = require('pubsweet-component-helper-service')
-const logger = require('@pubsweet/logger')
 
 module.exports = models => async (req, res) => {
   const { collectionId, fragmentId, recommendationId } = req.params
   let collection, fragment
   try {
     collection = await models.Collection.find(collectionId)
-    if (!collection.fragments.includes(fragmentId)) {
-      logger.error(
-        `Collection ${collectionId} does not contain fragment ${fragmentId}`,
-      )
+    if (!collection.fragments.includes(fragmentId))
       return res.status(400).json({
         error: `Collection and fragment do not match.`,
       })
-    }
-    const authsome = authsomeHelper.getAuthsome(models)
-    const target = {
-      collection,
-      path: req.route.path,
-    }
-    const UserModel = models.User
-    const user = await UserModel.find(req.user)
-    const canPatch = await authsome.can(req.user, 'PATCH', target)
-    if (!canPatch) {
-      logger.error(
-        `User ${req.user} is not allowed to access Collection ${collectionId}`,
-      )
-      return res.status(403).json({
-        error: 'Unauthorized.',
-      })
-    }
 
     fragment = await models.Fragment.find(fragmentId)
     const recommendation = fragment.recommendations.find(
@@ -43,10 +22,30 @@ module.exports = models => async (req, res) => {
     )
     if (!recommendation)
       return res.status(404).json({ error: 'Recommendation not found.' })
+
     if (recommendation.userId !== req.user)
       return res.status(403).json({
         error: 'Unauthorized.',
       })
+
+    const authsome = authsomeHelper.getAuthsome(models)
+    const authsomeObject =
+      recommendation.recommendationType === 'editorRecommendation'
+        ? collection
+        : fragment
+    const target = {
+      authsomeObject,
+      path: req.route.path,
+    }
+    const canPatch = await authsome.can(req.user, 'PATCH', target)
+    if (!canPatch)
+      return res.status(403).json({
+        error: 'Unauthorized.',
+      })
+
+    const UserModel = models.User
+    const user = await UserModel.find(req.user)
+
     Object.assign(recommendation, req.body)
     recommendation.updatedOn = Date.now()
     if (req.body.submittedOn) {
@@ -56,7 +55,7 @@ module.exports = models => async (req, res) => {
       })
       const baseUrl = services.getBaseUrl(req)
       const collectionHelper = new Collection({ collection })
-      const authors = await collectionHelper.getAuthorData({
+      const authors = await fragmentHelper.getAuthorData({
         UserModel,
       })
       const email = new Email({
diff --git a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
index d5a4a500385cc43e771d01758a78137236320ad7..68a3e796fc609f546537e99901559e80fc465e3c 100644
--- a/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
+++ b/packages/component-manuscript-manager/src/routes/fragmentsRecommendations/post.js
@@ -32,8 +32,10 @@ module.exports = models => async (req, res) => {
     })
   }
   const authsome = authsomeHelper.getAuthsome(models)
+  const authsomeObject =
+    recommendationType === 'editorRecommendation' ? collection : fragment
   const target = {
-    collection,
+    authsomeObject,
     path: req.route.path,
   }
   const canPost = await authsome.can(req.user, 'POST', target)
@@ -59,7 +61,7 @@ module.exports = models => async (req, res) => {
     handlingEditor: collection.handlingEditor,
   })
   const baseUrl = services.getBaseUrl(req)
-  const authors = await collectionHelper.getAuthorData({ UserModel })
+  const authors = await fragmentHelper.getAuthorData({ UserModel })
   const email = new Email({
     UserModel,
     collection,
@@ -67,7 +69,7 @@ module.exports = models => async (req, res) => {
     baseUrl,
     authors,
   })
-
+  const FragmentModel = models.Fragment
   if (reqUser.editorInChief || reqUser.admin) {
     if (recommendation === 'return-to-handling-editor')
       collectionHelper.updateStatus({ newStatus: 'reviewCompleted' })
@@ -78,6 +80,7 @@ module.exports = models => async (req, res) => {
       email.setupAuthorsEmail({
         requestToRevision: false,
         publish: recommendation === 'publish',
+        FragmentModel,
       })
       email.setupHandlingEditorEmail({
         publish: recommendation === 'publish',
@@ -87,6 +90,7 @@ module.exports = models => async (req, res) => {
         recommendation,
         isSubmitted: true,
         agree: true,
+        FragmentModel,
       })
     }
   } else if (recommendationType === 'editorRecommendation') {
@@ -94,8 +98,9 @@ module.exports = models => async (req, res) => {
     email.setupReviewersEmail({
       recommendation,
       agree: true,
+      FragmentModel,
     })
-    email.setupReviewersEmail({ agree: false })
+    email.setupReviewersEmail({ agree: false, FragmentModel: models.Fragment })
     email.setupEiCEmail({
       recommendation,
       comments: newRecommendation.comments,
diff --git a/packages/component-manuscript-manager/src/tests/fixtures/fixtures.js b/packages/component-manuscript-manager/src/tests/fixtures/fixtures.js
deleted file mode 100644
index cbb5c85cbe056c7dc633db935bf5707764ac40bd..0000000000000000000000000000000000000000
--- a/packages/component-manuscript-manager/src/tests/fixtures/fixtures.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const users = require('./users')
-const collections = require('./collections')
-const fragments = require('./fragments')
-const teams = require('./teams')
-
-module.exports = {
-  users,
-  collections,
-  fragments,
-  teams,
-}
diff --git a/packages/component-manuscript-manager/src/tests/fixtures/fragments.js b/packages/component-manuscript-manager/src/tests/fixtures/fragments.js
deleted file mode 100644
index 0bc2a06b2cacf0a2a2dcb83ee5202cf9059f3618..0000000000000000000000000000000000000000
--- a/packages/component-manuscript-manager/src/tests/fixtures/fragments.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const Chance = require('chance')
-const { recReviewer } = require('./userData')
-
-const chance = new Chance()
-const fragments = {
-  fragment: {
-    id: chance.guid(),
-    metadata: {
-      title: chance.sentence(),
-      abstract: chance.paragraph(),
-    },
-    save: jest.fn(),
-    recommendations: [
-      {
-        recommendation: 'accept',
-        recommendationType: 'review',
-        comments: [
-          {
-            content: chance.paragraph(),
-            public: chance.bool(),
-            files: [
-              {
-                id: chance.guid(),
-                name: 'file.pdf',
-                size: chance.natural(),
-              },
-            ],
-          },
-        ],
-        id: chance.guid(),
-        userId: recReviewer.id,
-        createdOn: chance.timestamp(),
-        updatedOn: chance.timestamp(),
-      },
-    ],
-  },
-}
-
-module.exports = fragments
diff --git a/packages/component-manuscript-manager/src/tests/fixtures/teamIDs.js b/packages/component-manuscript-manager/src/tests/fixtures/teamIDs.js
deleted file mode 100644
index 607fd6661b1e7c9848bf13257a0d198f230fab00..0000000000000000000000000000000000000000
--- a/packages/component-manuscript-manager/src/tests/fixtures/teamIDs.js
+++ /dev/null
@@ -1,10 +0,0 @@
-const Chance = require('chance')
-
-const chance = new Chance()
-const heID = chance.guid()
-const revId = chance.guid()
-
-module.exports = {
-  heTeamID: heID,
-  revTeamID: revId,
-}
diff --git a/packages/component-manuscript-manager/src/tests/fixtures/teams.js b/packages/component-manuscript-manager/src/tests/fixtures/teams.js
deleted file mode 100644
index 1c87e804343747e1dfa7132259bc044c9f39081e..0000000000000000000000000000000000000000
--- a/packages/component-manuscript-manager/src/tests/fixtures/teams.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const users = require('./users')
-const collections = require('./collections')
-const { revTeamID, heTeamID } = require('./teamIDs')
-
-const { collection } = collections
-const { reviewer, handlingEditor } = users
-const teams = {
-  revTeam: {
-    teamType: {
-      name: 'reviewer',
-      permissions: 'reviewer',
-    },
-    group: 'reviewer',
-    name: 'reviewer',
-    object: {
-      type: 'collection',
-      id: collection.id,
-    },
-    members: [reviewer.id],
-    save: jest.fn(() => teams.revTeam),
-    updateProperties: jest.fn(() => teams.revTeam),
-    id: revTeamID,
-  },
-  heTeam: {
-    teamType: {
-      name: 'handlingEditor',
-      permissions: 'handlingEditor',
-    },
-    group: 'handlingEditor',
-    name: 'HandlingEditor',
-    object: {
-      type: 'collection',
-      id: collection.id,
-    },
-    members: [handlingEditor.id],
-    save: jest.fn(() => teams.heTeam),
-    updateProperties: jest.fn(() => teams.heTeam),
-    id: heTeamID,
-  },
-}
-module.exports = teams
diff --git a/packages/component-manuscript-manager/src/tests/fixtures/users.js b/packages/component-manuscript-manager/src/tests/fixtures/users.js
deleted file mode 100644
index 573c25543f255e06ffe659e1dcc5bd3feb2d38cb..0000000000000000000000000000000000000000
--- a/packages/component-manuscript-manager/src/tests/fixtures/users.js
+++ /dev/null
@@ -1,84 +0,0 @@
-const { reviewer, author, recReviewer, handlingEditor } = require('./userData')
-const { revTeamID, heTeamID } = require('./teamIDs')
-
-const Chance = require('chance')
-
-const chance = new Chance()
-const users = {
-  reviewer: {
-    type: 'user',
-    username: chance.word(),
-    email: reviewer.email,
-    password: 'password',
-    admin: false,
-    id: reviewer.id,
-    firstName: reviewer.firstName,
-    lastName: reviewer.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.reviewer),
-    isConfirmed: true,
-    teams: [revTeamID],
-  },
-  author: {
-    type: 'user',
-    username: chance.word(),
-    email: author.email,
-    password: 'password',
-    admin: false,
-    id: author.id,
-    firstName: author.firstName,
-    lastName: author.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.author),
-    isConfirmed: true,
-  },
-  recReviewer: {
-    type: 'user',
-    username: chance.word(),
-    email: recReviewer.email,
-    password: 'password',
-    admin: false,
-    id: recReviewer.id,
-    firstName: recReviewer.firstName,
-    lastName: recReviewer.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.recReviewer),
-    isConfirmed: true,
-    teams: [revTeamID],
-  },
-  handlingEditor: {
-    type: 'user',
-    username: chance.word(),
-    email: handlingEditor.email,
-    password: 'password',
-    admin: false,
-    id: handlingEditor.id,
-    firstName: handlingEditor.firstName,
-    lastName: handlingEditor.lastName,
-    teams: [heTeamID],
-    save: jest.fn(() => users.handlingEditor),
-    editorInChief: false,
-    handlingEditor: true,
-    title: 'Mr',
-  },
-  editorInChief: {
-    type: 'user',
-    username: chance.word(),
-    email: chance.email(),
-    password: 'password',
-    admin: false,
-    id: chance.guid(),
-    firstName: chance.first(),
-    lastName: chance.last(),
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.editorInChief),
-    isConfirmed: false,
-    editorInChief: true,
-  },
-}
-
-module.exports = users
diff --git a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/patch.test.js b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/patch.test.js
index 0e3c8013b981d741859bcf134f48db99e59f7e33..31755149c0d5f99a086d4360f32214953eac2765 100644
--- a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/patch.test.js
+++ b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/patch.test.js
@@ -1,12 +1,12 @@
 process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
 process.env.SUPPRESS_NO_CONFIG_WARNING = true
 
-const fixtures = require('./../fixtures/fixtures')
 const Chance = require('chance')
-const Model = require('./../helpers/Model')
 const cloneDeep = require('lodash/cloneDeep')
-const requests = require('./../helpers/requests')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
 
+const { Model, fixtures } = fixturesService
 jest.mock('pubsweet-component-mail-service', () => ({
   sendNotificationEmail: jest.fn(),
 }))
@@ -29,7 +29,7 @@ const reqBody = {
   recommendationType: 'review',
 }
 
-const path = '../../routes/fragmentsRecommendations/patch'
+const path = '../routes/fragmentsRecommendations/patch'
 const route = {
   path:
     '/api/collections/:collectionId/fragments/:fragmentId/recommendations/:recommendationId',
@@ -132,19 +132,20 @@ describe('Patch fragments recommendations route handler', () => {
     expect(data.error).toEqual('Recommendation not found.')
   })
   it('should return an error when the request user is not a reviewer', async () => {
-    const { author } = testFixtures.users
+    const { user } = testFixtures.users
     const { collection } = testFixtures.collections
     const { fragment } = testFixtures.fragments
 
     const res = await requests.sendRequest({
       body,
-      userId: author.id,
+      userId: user.id,
       models,
       route,
       path,
       params: {
         collectionId: collection.id,
         fragmentId: fragment.id,
+        recommendationId: fragment.recommendations[0].id,
       },
     })
 
diff --git a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js
index 364448eb2425e07b4f0df6ebcb9de904201d0489..30f7ee90ba001ce677c709bb86e2349cc0ba3e3c 100644
--- a/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js
+++ b/packages/component-manuscript-manager/src/tests/fragmentsRecommendations/post.test.js
@@ -1,12 +1,12 @@
 process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
 process.env.SUPPRESS_NO_CONFIG_WARNING = true
 
-const fixtures = require('./../fixtures/fixtures')
 const Chance = require('chance')
-const Model = require('./../helpers/Model')
 const cloneDeep = require('lodash/cloneDeep')
-const requests = require('./../helpers/requests')
+const fixturesService = require('pubsweet-component-fixture-service')
+const requests = require('../requests')
 
+const { Model, fixtures } = fixturesService
 const chance = new Chance()
 jest.mock('pubsweet-component-mail-service', () => ({
   sendNotificationEmail: jest.fn(),
@@ -29,7 +29,7 @@ const reqBody = {
   recommendationType: 'review',
 }
 
-const path = '../../routes/fragmentsRecommendations/post'
+const path = '../routes/fragmentsRecommendations/post'
 const route = {
   path: '/api/collections/:collectionId/fragments/:fragmentId/recommendations',
 }
diff --git a/packages/component-manuscript-manager/src/tests/helpers/Model.js b/packages/component-manuscript-manager/src/tests/helpers/Model.js
deleted file mode 100644
index 3e5a7364b9a7e907530d21ae9575644a69294dbc..0000000000000000000000000000000000000000
--- a/packages/component-manuscript-manager/src/tests/helpers/Model.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// const fixtures = require('../fixtures/fixtures')
-
-const UserMock = require('../mocks/User')
-
-const notFoundError = new Error()
-notFoundError.name = 'NotFoundError'
-notFoundError.status = 404
-
-const build = fixtures => {
-  const models = {
-    User: {},
-    Collection: {
-      find: jest.fn(id => findMock(id, 'collections', fixtures)),
-    },
-    Fragment: {
-      find: jest.fn(id => findMock(id, 'fragments', fixtures)),
-    },
-    Team: {
-      find: jest.fn(id => findMock(id, 'teams', fixtures)),
-    },
-  }
-  UserMock.find = jest.fn(id => findMock(id, 'users', fixtures))
-  UserMock.all = jest.fn(() => Object.values(fixtures.users))
-  models.User = UserMock
-  return models
-}
-
-const findMock = (id, type, fixtures) => {
-  const foundObj = Object.values(fixtures[type]).find(
-    fixtureObj => fixtureObj.id === id,
-  )
-  if (foundObj === undefined) return Promise.reject(notFoundError)
-  return Promise.resolve(foundObj)
-}
-module.exports = { build }
diff --git a/packages/component-manuscript-manager/src/tests/mocks/User.js b/packages/component-manuscript-manager/src/tests/mocks/User.js
deleted file mode 100644
index b337c5f31ce5d71eaadd61ccb95fb1f83eef7d83..0000000000000000000000000000000000000000
--- a/packages/component-manuscript-manager/src/tests/mocks/User.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/* eslint-disable func-names-any */
-const uuid = require('uuid')
-
-function User(properties) {
-  this.type = 'user'
-  this.email = properties.email
-  this.username = properties.username
-  this.password = properties.password
-  this.roles = properties.roles
-  this.title = properties.title
-  this.affiliation = properties.affiliation
-  this.firstName = properties.firstName
-  this.lastName = properties.lastName
-  this.admin = properties.admin
-}
-
-User.prototype.save = jest.fn(function saveUser() {
-  this.id = uuid.v4()
-  return Promise.resolve(this)
-})
-
-module.exports = User
diff --git a/packages/component-manuscript-manager/src/tests/helpers/requests.js b/packages/component-manuscript-manager/src/tests/requests.js
similarity index 100%
rename from packages/component-manuscript-manager/src/tests/helpers/requests.js
rename to packages/component-manuscript-manager/src/tests/requests.js
diff --git a/packages/component-user-manager/config/authsome-helpers.js b/packages/component-user-manager/config/authsome-helpers.js
index 1b7642bfca5445c4c1651d8cbfcca8aad45ef8c5..164a9ce8ae550d9cd1709543ceb852169220cdc6 100644
--- a/packages/component-user-manager/config/authsome-helpers.js
+++ b/packages/component-user-manager/config/authsome-helpers.js
@@ -44,7 +44,10 @@ const filterObjectData = (
           rec => rec.userId === user.id,
         )
     }
-
+    parseAuthorsData(object, matchingCollPerm)
+    if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
+      return filterRefusedInvitations(object, user)
+    }
     return object
   }
   const matchingCollPerm = collectionsPermissions.find(
@@ -52,10 +55,6 @@ const filterObjectData = (
   )
   if (matchingCollPerm === undefined) return null
   setPublicStatuses(object, matchingCollPerm)
-  parseAuthorsData(object, matchingCollPerm)
-  if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
-    return filterRefusedInvitations(object, user)
-  }
 
   return object
 }
diff --git a/packages/component-user-manager/config/authsome-mode.js b/packages/component-user-manager/config/authsome-mode.js
index 948dd93e25476745ef93e4db143ff99f79ff8cec..8a92a1301df7ea1bed2dc105beb44834cdb9b16f 100644
--- a/packages/component-user-manager/config/authsome-mode.js
+++ b/packages/component-user-manager/config/authsome-mode.js
@@ -4,6 +4,7 @@ const omit = require('lodash/omit')
 const helpers = require('./authsome-helpers')
 
 async function teamPermissions(user, operation, object, context) {
+  const { models } = context
   const permissions = ['handlingEditor', 'author', 'reviewer']
   const teams = await helpers.getTeamsByPermissions(
     user.teams,
@@ -13,7 +14,13 @@ async function teamPermissions(user, operation, object, context) {
 
   let collectionsPermissions = await Promise.all(
     teams.map(async team => {
-      const collection = await context.models.Collection.find(team.object.id)
+      let collection
+      if (team.object.type === 'collection') {
+        collection = await models.Collection.find(team.object.id)
+      } else if (team.object.type === 'fragment') {
+        const fragment = await models.Fragment.find(team.object.id)
+        collection = await models.Collection.find(fragment.collectionId)
+      }
       if (
         collection.status === 'rejected' &&
         team.teamType.permissions === 'reviewer'
@@ -188,19 +195,21 @@ async function authenticatedUser(user, operation, object, context) {
   // only allow a reviewer and an HE to submit and to modify a recommendation
   if (
     ['POST', 'PATCH'].includes(operation) &&
-    get(object.collection, 'type') === 'collection' &&
     object.path.includes('recommendations')
   ) {
-    const collection = await context.models.Collection.find(
-      get(object.collection, 'id'),
-    )
+    const authsomeObject = get(object, 'authsomeObject')
+
     const teams = await helpers.getTeamsByPermissions(
       user.teams,
       ['reviewer', 'handlingEditor'],
       context.models.Team,
     )
+
     if (teams.length === 0) return false
-    const matchingTeam = teams.find(team => team.object.id === collection.id)
+    const matchingTeam = teams.find(
+      team => team.object.id === authsomeObject.id,
+    )
+
     if (matchingTeam) return true
     return false
   }
diff --git a/packages/component-user-manager/src/tests/fixtures/collections.js b/packages/component-user-manager/src/tests/fixtures/collections.js
deleted file mode 100644
index 6c4e02668459f9d42c48b58151ccb87f190dcc77..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/fixtures/collections.js
+++ /dev/null
@@ -1,32 +0,0 @@
-const Chance = require('chance')
-const { submittingAuthor } = require('./userData')
-const { fragment } = require('./fragments')
-
-const chance = new Chance()
-const collections = {
-  standardCollection: {
-    id: chance.guid(),
-    title: chance.sentence(),
-    type: 'collection',
-    fragments: [fragment.id],
-    owners: [submittingAuthor.id],
-    authors: [
-      {
-        userId: submittingAuthor.id,
-        isSubmitting: true,
-        isCorresponding: false,
-      },
-    ],
-    save: jest.fn(() => collections.standardCollection),
-  },
-  authorsCollection: {
-    id: chance.guid(),
-    title: chance.sentence(),
-    type: 'collection',
-    fragments: [],
-    owners: [submittingAuthor.id],
-    save: jest.fn(),
-  },
-}
-
-module.exports = collections
diff --git a/packages/component-user-manager/src/tests/fixtures/fixtures.js b/packages/component-user-manager/src/tests/fixtures/fixtures.js
deleted file mode 100644
index 0ea29e85ac3a373a20a0e82377e0b6f3dfeef51e..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/fixtures/fixtures.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const users = require('./users')
-const collections = require('./collections')
-const teams = require('./teams')
-
-module.exports = {
-  users,
-  collections,
-  teams,
-}
diff --git a/packages/component-user-manager/src/tests/fixtures/fragments.js b/packages/component-user-manager/src/tests/fixtures/fragments.js
deleted file mode 100644
index 08d0eedf3a531c317cbfb79e58a6d1b019413564..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/fixtures/fragments.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const Chance = require('chance')
-
-const chance = new Chance()
-const fragments = {
-  fragment: {
-    id: chance.guid(),
-    metadata: {
-      title: chance.sentence(),
-      abstract: chance.paragraph(),
-    },
-    recommendations: [],
-  },
-}
-
-module.exports = fragments
diff --git a/packages/component-user-manager/src/tests/fixtures/teams.js b/packages/component-user-manager/src/tests/fixtures/teams.js
deleted file mode 100644
index 410b999e3f98f3b196eb28feb5512c744e5b61ea..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/fixtures/teams.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const users = require('./users')
-const collections = require('./collections')
-const { authorTeamID } = require('./teamIDs')
-
-const { standardCollection } = collections
-const { submittingAuthor } = users
-const teams = {
-  authorTeam: {
-    teamType: {
-      name: 'author',
-      permissions: 'author',
-    },
-    group: 'author',
-    name: 'author',
-    object: {
-      type: 'collection',
-      id: standardCollection.id,
-    },
-    members: [submittingAuthor.id],
-    save: jest.fn(() => teams.authorTeam),
-    updateProperties: jest.fn(() => teams.authorTeam),
-    id: authorTeamID,
-  },
-}
-module.exports = teams
diff --git a/packages/component-user-manager/src/tests/fixtures/userData.js b/packages/component-user-manager/src/tests/fixtures/userData.js
deleted file mode 100644
index 1a962c33dc4915763c259edae6deadfab9911f98..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/fixtures/userData.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const Chance = require('chance')
-
-const chance = new Chance()
-
-module.exports = {
-  author: {
-    id: chance.guid(),
-    email: chance.email(),
-    firstName: chance.first(),
-    lastName: chance.last(),
-  },
-  submittingAuthor: {
-    id: chance.guid(),
-    email: chance.email(),
-    firstName: chance.first(),
-    lastName: chance.last(),
-  },
-  admin: {
-    id: chance.guid(),
-    email: chance.email(),
-  },
-}
diff --git a/packages/component-user-manager/src/tests/fixtures/users.js b/packages/component-user-manager/src/tests/fixtures/users.js
deleted file mode 100644
index 654032921308157e12248205373970f595d0d497..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/fixtures/users.js
+++ /dev/null
@@ -1,49 +0,0 @@
-const { authorTeamID } = require('./teamIDs')
-const { author, submittingAuthor, admin } = require('./userData')
-const Chance = require('chance')
-
-const chance = new Chance()
-const users = {
-  admin: {
-    type: 'user',
-    username: 'admin',
-    email: admin.email,
-    password: 'password',
-    admin: true,
-    id: admin.id,
-  },
-  author: {
-    type: 'user',
-    username: 'author',
-    email: author.email,
-    password: 'password',
-    admin: false,
-    id: author.id,
-    passwordResetToken: chance.hash(),
-    firstName: author.firstName,
-    lastName: author.lastName,
-    affiliation: 'MIT',
-    title: 'Mr',
-    save: jest.fn(() => users.author),
-    isConfirmed: false,
-    teams: [authorTeamID],
-    updateProperties: jest.fn(() => users.author),
-  },
-  submittingAuthor: {
-    type: 'user',
-    username: 'sauthor',
-    email: submittingAuthor.email,
-    password: 'password',
-    admin: false,
-    id: submittingAuthor.id,
-    passwordResetToken: chance.hash(),
-    firstName: submittingAuthor.firstName,
-    lastName: submittingAuthor.lastName,
-    affiliation: chance.company(),
-    title: 'Mr',
-    save: jest.fn(() => users.submittingAuthor),
-    isConfirmed: false,
-  },
-}
-
-module.exports = users
diff --git a/packages/component-user-manager/src/tests/fragmentsUsers/delete.test.js b/packages/component-user-manager/src/tests/fragmentsUsers/delete.test.js
index 7242ac5c76c8b75ce0e265ca9ef98981490fe1a0..5bea3f85277e6cadf09fd9ff8b31ae5bbb15df7f 100644
--- a/packages/component-user-manager/src/tests/fragmentsUsers/delete.test.js
+++ b/packages/component-user-manager/src/tests/fragmentsUsers/delete.test.js
@@ -1,29 +1,37 @@
 process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
 process.env.SUPPRESS_NO_CONFIG_WARNING = true
+const cloneDeep = require('lodash/cloneDeep')
 
 const httpMocks = require('node-mocks-http')
-const fixtures = require('./../fixtures/fixtures')
-const Model = require('./../helpers/Model')
+const fixturesService = require('pubsweet-component-fixture-service')
 
-const models = Model.build()
+const { Model, fixtures } = fixturesService
 
 const { author, submittingAuthor } = fixtures.users
-const { standardCollection } = fixtures.collections
-const { authorTeam } = fixtures.teams
-const deletePath = '../../routes/collectionsUsers/delete'
-
-describe('Delete collections users route handler', () => {
+const { collection } = fixtures.collections
+const deletePath = '../../routes/fragmentsUsers/delete'
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+  sendNotificationEmail: jest.fn(),
+}))
+describe('Delete fragments users route handler', () => {
+  let testFixtures = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    models = Model.build(testFixtures)
+  })
   it('should return success when an author is deleted', async () => {
     const req = httpMocks.createRequest({})
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
+    req.params.fragmentId = fragmentId
     req.params.userId = author.id
     const res = httpMocks.createResponse()
     await require(deletePath)(models)(req, res)
 
     expect(res.statusCode).toBe(200)
-    expect(authorTeam.members).not.toContain(author.id)
-    expect(author.teams).not.toContain(authorTeam.id)
   })
   it('should return an error when the collection does not exist', async () => {
     const req = httpMocks.createRequest({})
@@ -40,7 +48,9 @@ describe('Delete collections users route handler', () => {
   it('should return an error when the user does not exist', async () => {
     const req = httpMocks.createRequest({})
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
+    req.params.fragmentId = fragmentId
     req.params.userId = 'invalid-id'
     const res = httpMocks.createResponse()
     await require(deletePath)(models)(req, res)
@@ -49,4 +59,19 @@ describe('Delete collections users route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('item not found')
   })
+  it('should return an error when the fragment does not exist', async () => {
+    const req = httpMocks.createRequest()
+    req.user = submittingAuthor.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = 'invalid-fragment-id'
+    req.params.userId = author.id
+    const res = httpMocks.createResponse()
+    await require(deletePath)(models)(req, res)
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `Fragment invalid-fragment-id does not match collection ${collection.id}`,
+    )
+  })
 })
diff --git a/packages/component-user-manager/src/tests/fragmentsUsers/get.test.js b/packages/component-user-manager/src/tests/fragmentsUsers/get.test.js
index b5975b4ed8d941cd760c22b4df9ec729c41e8ab7..66b2292a09acfe684c44feacc35933420ce21a65 100644
--- a/packages/component-user-manager/src/tests/fragmentsUsers/get.test.js
+++ b/packages/component-user-manager/src/tests/fragmentsUsers/get.test.js
@@ -1,21 +1,34 @@
 process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
 process.env.SUPPRESS_NO_CONFIG_WARNING = true
+const cloneDeep = require('lodash/cloneDeep')
 
 const httpMocks = require('node-mocks-http')
-const fixtures = require('./../fixtures/fixtures')
-const Model = require('./../helpers/Model')
+const fixturesService = require('pubsweet-component-fixture-service')
 
-const { standardCollection } = fixtures.collections
+const { Model, fixtures } = fixturesService
+
+const { collection } = fixtures.collections
 const { submittingAuthor } = fixtures.users
 
-const getPath = '../../routes/collectionsUsers/get'
-describe('Get collections users route handler', () => {
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+  sendNotificationEmail: jest.fn(),
+}))
+const getPath = '../../routes/fragmentsUsers/get'
+describe('Get fragments users route handler', () => {
+  let testFixtures = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    models = Model.build(testFixtures)
+  })
   it('should return success when the request data is correct', async () => {
     const req = httpMocks.createRequest()
-    req.params.collectionId = standardCollection.id
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
+    req.params.fragmentId = fragmentId
     req.user = submittingAuthor.id
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(getPath)(models)(req, res)
 
     expect(res.statusCode).toBe(200)
@@ -29,10 +42,23 @@ describe('Get collections users route handler', () => {
     req.params.collectionId = 'invalid-id'
     req.user = submittingAuthor.id
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(getPath)(models)(req, res)
     expect(res.statusCode).toBe(404)
     const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('collection not found')
+    expect(data.error).toEqual('item not found')
+  })
+  it('should return an error when the fragment does not exist', async () => {
+    const req = httpMocks.createRequest()
+    req.user = submittingAuthor.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = 'invalid-fragment-id'
+    const res = httpMocks.createResponse()
+    await require(getPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `Fragment invalid-fragment-id does not match collection ${collection.id}`,
+    )
   })
 })
diff --git a/packages/component-user-manager/src/tests/fragmentsUsers/patch.test.js b/packages/component-user-manager/src/tests/fragmentsUsers/patch.test.js
deleted file mode 100644
index 03c35f392d843bcf12c2b4859c656017a311c0a7..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/fragmentsUsers/patch.test.js
+++ /dev/null
@@ -1,119 +0,0 @@
-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 Chance = require('chance')
-
-const chance = new Chance()
-
-const models = Model.build()
-jest.mock('pubsweet-component-mail-service', () => ({
-  sendSimpleEmail: jest.fn(),
-  sendNotificationEmail: jest.fn(),
-}))
-
-const { author, submittingAuthor } = fixtures.users
-const { standardCollection, authorsCollection } = fixtures.collections
-const body = {
-  isSubmitting: false,
-  isCorresponding: true,
-  firstName: chance.first(),
-  lastName: chance.last(),
-  affiliation: chance.company(),
-}
-const patchPath = '../../routes/collectionsUsers/patch'
-describe('Patch collections users route handler', () => {
-  it('should return success when the request data is correct', async () => {
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    req.params.userId = submittingAuthor.id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-    const data = JSON.parse(res._getData())
-    expect(res.statusCode).toBe(200)
-    const matchingAuthor = data.authors.find(
-      author => author.userId === submittingAuthor.id,
-    )
-    expect(matchingAuthor.isSubmitting).toBe(body.isSubmitting)
-    expect(matchingAuthor.isCorresponding).toBe(body.isCorresponding)
-    expect(submittingAuthor.firstName).toBe(body.firstName)
-    expect(submittingAuthor.lastName).toBe(body.lastName)
-  })
-  it('should return an error when the params are missing', async () => {
-    delete body.isSubmitting
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    req.params.userId = submittingAuthor.id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-
-    expect(res.statusCode).toBe(400)
-    const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('Missing parameters')
-    body.isSubmitting = false
-  })
-  it('should return an error if the collection does not exists', async () => {
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = submittingAuthor.id
-    req.params.collectionId = 'invalid-id'
-    req.params.userId = submittingAuthor.id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-
-    expect(res.statusCode).toBe(404)
-    const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('item not found')
-  })
-  it('should return an error when the user does not exist', async () => {
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = author.id
-    req.params.collectionId = standardCollection.id
-    req.params.userId = 'invalid-id'
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-
-    expect(res.statusCode).toBe(404)
-    const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('item not found')
-  })
-  it('should return an error when the collection does not have authors', async () => {
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = submittingAuthor.id
-    req.params.collectionId = authorsCollection.id
-    req.params.userId = submittingAuthor.id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-
-    expect(res.statusCode).toBe(400)
-    const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('Collection does not have any authors')
-  })
-  it('should return an error when the collection and the user do not match', async () => {
-    const req = httpMocks.createRequest({
-      body,
-    })
-    req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    req.params.userId = author.id
-    const res = httpMocks.createResponse()
-    await require(patchPath)(models)(req, res)
-
-    expect(res.statusCode).toBe(400)
-    const data = JSON.parse(res._getData())
-    expect(data.error).toEqual('Collection and user do not match')
-  })
-})
diff --git a/packages/component-user-manager/src/tests/fragmentsUsers/post.test.js b/packages/component-user-manager/src/tests/fragmentsUsers/post.test.js
index b826f58d2a6fd26c78cac03392dc301e05ee8fca..42ceb286f20f187bde72a6e523527f8d42b85ae9 100644
--- a/packages/component-user-manager/src/tests/fragmentsUsers/post.test.js
+++ b/packages/component-user-manager/src/tests/fragmentsUsers/post.test.js
@@ -2,10 +2,11 @@ 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 Chance = require('chance')
-const Model = require('./../helpers/Model')
 const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
+
+const { Model, fixtures } = fixturesService
 
 jest.mock('pubsweet-component-mail-service', () => ({
   sendSimpleEmail: jest.fn(),
@@ -14,7 +15,7 @@ jest.mock('pubsweet-component-mail-service', () => ({
 const chance = new Chance()
 
 const { author, submittingAuthor } = fixtures.users
-const { standardCollection } = fixtures.collections
+const { collection } = fixtures.collections
 const postPath = '../../routes/fragmentsUsers/post'
 const reqBody = {
   email: chance.email(),
@@ -36,8 +37,8 @@ describe('Post fragments users route handler', () => {
       body,
     })
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    const [fragmentId] = standardCollection.fragments
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
     req.params.fragmentId = fragmentId
     const res = httpMocks.createResponse()
     await require(postPath)(models)(req, res)
@@ -53,8 +54,8 @@ describe('Post fragments users route handler', () => {
       body,
     })
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    const [fragmentId] = standardCollection.fragments
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
     req.params.fragmentId = fragmentId
     const res = httpMocks.createResponse()
     await require(postPath)(models)(req, res)
@@ -69,8 +70,8 @@ describe('Post fragments users route handler', () => {
       body,
     })
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    const [fragmentId] = standardCollection.fragments
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
     req.params.fragmentId = fragmentId
     const res = httpMocks.createResponse()
     await require(postPath)(models)(req, res)
@@ -87,8 +88,8 @@ describe('Post fragments users route handler', () => {
       body,
     })
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    const [fragmentId] = standardCollection.fragments
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
     req.params.fragmentId = fragmentId
     const res = httpMocks.createResponse()
     await require(postPath)(models)(req, res)
@@ -104,8 +105,8 @@ describe('Post fragments users route handler', () => {
       body,
     })
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    const [fragmentId] = standardCollection.fragments
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
     req.params.fragmentId = fragmentId
     const res = httpMocks.createResponse()
     await require(postPath)(models)(req, res)
@@ -121,8 +122,8 @@ describe('Post fragments users route handler', () => {
       body,
     })
     req.user = submittingAuthor.id
-    req.params.collectionId = standardCollection.id
-    const [fragmentId] = standardCollection.fragments
+    req.params.collectionId = collection.id
+    const [fragmentId] = collection.fragments
     req.params.fragmentId = fragmentId
     const res = httpMocks.createResponse()
     await require(postPath)(models)(req, res)
@@ -131,4 +132,21 @@ describe('Post fragments users route handler', () => {
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('invalid-role is not defined')
   })
+  it('should return an error when the fragment does not exist', async () => {
+    const req = httpMocks.createRequest({
+      body,
+    })
+    req.user = submittingAuthor.id
+    req.params.collectionId = collection.id
+    req.params.fragmentId = 'invalid-fragment-id'
+    req.params.userId = author.id
+    const res = httpMocks.createResponse()
+    await require(postPath)(models)(req, res)
+
+    expect(res.statusCode).toBe(400)
+    const data = JSON.parse(res._getData())
+    expect(data.error).toEqual(
+      `Fragment invalid-fragment-id does not match collection ${collection.id}`,
+    )
+  })
 })
diff --git a/packages/component-user-manager/src/tests/helpers/Model.js b/packages/component-user-manager/src/tests/helpers/Model.js
deleted file mode 100644
index e5f31fa11df30b229f84e129d41c33062f7b6d1d..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/helpers/Model.js
+++ /dev/null
@@ -1,61 +0,0 @@
-const fixtures = require('../fixtures/fixtures')
-
-const UserMock = require('../mocks/User')
-const TeamMock = require('../mocks/Team')
-
-const notFoundError = new Error()
-notFoundError.name = 'NotFoundError'
-notFoundError.status = 404
-
-const build = () => {
-  const models = {
-    User: {},
-    Collection: {
-      find: jest.fn(id => findMock(id, 'collections')),
-    },
-    Team: {},
-  }
-  UserMock.find = jest.fn(id => findMock(id, 'users'))
-  UserMock.findByEmail = jest.fn(email => findByEmailMock(email))
-  UserMock.all = jest.fn(() => Object.values(fixtures.users))
-  UserMock.updateProperties = jest.fn(user =>
-    updatePropertiesMock(user, 'users'),
-  )
-  TeamMock.find = jest.fn(id => findMock(id, 'teams'))
-  TeamMock.updateProperties = jest.fn(team =>
-    updatePropertiesMock(team, 'teams'),
-  )
-  TeamMock.all = jest.fn(() => Object.values(fixtures.teams))
-
-  models.User = UserMock
-  models.Team = TeamMock
-  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-user-manager/src/tests/mocks/Team.js b/packages/component-user-manager/src/tests/mocks/Team.js
deleted file mode 100644
index f84ef4dcf8ffa0e5056c8f0eb86573f624d74c06..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/mocks/Team.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/* eslint-disable func-names-any */
-
-function Team(properties) {
-  this.teamType = properties.teamType
-  this.group = properties.group
-  this.name = properties.name
-  this.object = properties.object
-  this.members = properties.members
-}
-
-Team.prototype.save = jest.fn(function saveTeam() {
-  this.id = '111222'
-  return Promise.resolve(this)
-})
-
-module.exports = Team
diff --git a/packages/component-user-manager/src/tests/mocks/User.js b/packages/component-user-manager/src/tests/mocks/User.js
deleted file mode 100644
index 9a7459fc5578d3aa1d5dcec8562fa8f17225b1eb..0000000000000000000000000000000000000000
--- a/packages/component-user-manager/src/tests/mocks/User.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/* eslint-disable func-names-any */
-
-function User(properties) {
-  this.type = 'user'
-  this.email = properties.email
-  this.username = properties.username
-  this.password = properties.password
-  this.roles = properties.roles
-  this.title = properties.title
-  this.affiliation = properties.affiliation
-  this.firstName = properties.firstName
-  this.lastName = properties.lastName
-  this.admin = properties.admin
-}
-
-User.prototype.save = jest.fn(function saveUser() {
-  this.id = '111222'
-  return Promise.resolve(this)
-})
-
-module.exports = User
diff --git a/packages/component-user-manager/src/tests/users/resetPassword.test.js b/packages/component-user-manager/src/tests/users/resetPassword.test.js
index f2a7522d2874cd428b57896a853943fe8dcb6d9b..12d6e07bc25a0e4a03737d08f09519c1a443d659 100644
--- a/packages/component-user-manager/src/tests/users/resetPassword.test.js
+++ b/packages/component-user-manager/src/tests/users/resetPassword.test.js
@@ -1,25 +1,27 @@
 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 Chance = require('chance')
-const Model = require('./../helpers/Model')
-const clone = require('lodash/cloneDeep')
+const httpMocks = require('node-mocks-http')
+const cloneDeep = require('lodash/cloneDeep')
+const fixturesService = require('pubsweet-component-fixture-service')
 
+const { Model, fixtures } = fixturesService
 const chance = new Chance()
 
-const { author } = fixtures.users
-const clonedAuthor = clone(author)
-
-const body = {
-  email: clonedAuthor.email,
-  firstName: clonedAuthor.firstName,
-  lastName: clonedAuthor.lastName,
-  title: clonedAuthor.title,
-  affiliation: clonedAuthor.affiliation,
+const { user, author } = fixtures.users
+jest.mock('pubsweet-component-mail-service', () => ({
+  sendSimpleEmail: jest.fn(),
+  sendNotificationEmail: jest.fn(),
+}))
+const reqBody = {
+  email: user.email,
+  firstName: user.firstName,
+  lastName: user.lastName,
+  title: user.title,
+  affiliation: user.affiliation,
   password: 'password',
-  token: clonedAuthor.passwordResetToken,
+  token: user.passwordResetToken,
   isConfirmed: false,
 }
 
@@ -28,69 +30,67 @@ notFoundError.name = 'NotFoundError'
 notFoundError.status = 404
 const resetPasswordPath = '../../routes/users/resetPassword'
 describe('Users password reset route handler', () => {
+  let testFixtures = {}
+  let body = {}
+  let models
+  beforeEach(() => {
+    testFixtures = cloneDeep(fixtures)
+    body = cloneDeep(reqBody)
+    models = Model.build(testFixtures)
+  })
   it('should return an error when some parameters are missing', async () => {
     delete body.email
     const req = httpMocks.createRequest({ body })
 
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(resetPasswordPath)(models)(req, res)
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('missing required params')
-    body.email = author.email
   })
   it('should return an error when the password is too small', async () => {
     body.password = 'small'
     const req = httpMocks.createRequest({ body })
 
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(resetPasswordPath)(models)(req, res)
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual(
       'password needs to be at least 7 characters long',
     )
-    body.password = 'password'
   })
   it('should return an error when user is not found', async () => {
     body.email = chance.email()
     const req = httpMocks.createRequest({ body })
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(resetPasswordPath)(models)(req, res)
     expect(res.statusCode).toBe(404)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('user not found')
-    body.email = author.email
   })
   it('should return an error when the tokens do not match', async () => {
     body.token = chance.hash()
     const req = httpMocks.createRequest({ body })
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(resetPasswordPath)(models)(req, res)
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('invalid request')
-    body.token = author.passwordResetToken
   })
   it('should return an error when the user is already confirmed', async () => {
-    author.isConfirmed = true
+    body.email = author.email
+    body.token = author.passwordResetToken
     const req = httpMocks.createRequest({ body })
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(resetPasswordPath)(models)(req, res)
     expect(res.statusCode).toBe(400)
     const data = JSON.parse(res._getData())
     expect(data.error).toEqual('User is already confirmed')
-    author.isConfirmed = false
   })
   it('should return success when the body is correct', async () => {
     const req = httpMocks.createRequest({ body })
     const res = httpMocks.createResponse()
-    const models = Model.build()
     await require(resetPasswordPath)(models)(req, res)
 
     expect(res.statusCode).toBe(200)
diff --git a/packages/xpub-faraday/config/authsome-helpers.js b/packages/xpub-faraday/config/authsome-helpers.js
index 1b7642bfca5445c4c1651d8cbfcca8aad45ef8c5..164a9ce8ae550d9cd1709543ceb852169220cdc6 100644
--- a/packages/xpub-faraday/config/authsome-helpers.js
+++ b/packages/xpub-faraday/config/authsome-helpers.js
@@ -44,7 +44,10 @@ const filterObjectData = (
           rec => rec.userId === user.id,
         )
     }
-
+    parseAuthorsData(object, matchingCollPerm)
+    if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
+      return filterRefusedInvitations(object, user)
+    }
     return object
   }
   const matchingCollPerm = collectionsPermissions.find(
@@ -52,10 +55,6 @@ const filterObjectData = (
   )
   if (matchingCollPerm === undefined) return null
   setPublicStatuses(object, matchingCollPerm)
-  parseAuthorsData(object, matchingCollPerm)
-  if (['reviewer', 'handlingEditor'].includes(matchingCollPerm.permission)) {
-    return filterRefusedInvitations(object, user)
-  }
 
   return object
 }
diff --git a/packages/xpub-faraday/config/authsome-mode.js b/packages/xpub-faraday/config/authsome-mode.js
index 948dd93e25476745ef93e4db143ff99f79ff8cec..8a92a1301df7ea1bed2dc105beb44834cdb9b16f 100644
--- a/packages/xpub-faraday/config/authsome-mode.js
+++ b/packages/xpub-faraday/config/authsome-mode.js
@@ -4,6 +4,7 @@ const omit = require('lodash/omit')
 const helpers = require('./authsome-helpers')
 
 async function teamPermissions(user, operation, object, context) {
+  const { models } = context
   const permissions = ['handlingEditor', 'author', 'reviewer']
   const teams = await helpers.getTeamsByPermissions(
     user.teams,
@@ -13,7 +14,13 @@ async function teamPermissions(user, operation, object, context) {
 
   let collectionsPermissions = await Promise.all(
     teams.map(async team => {
-      const collection = await context.models.Collection.find(team.object.id)
+      let collection
+      if (team.object.type === 'collection') {
+        collection = await models.Collection.find(team.object.id)
+      } else if (team.object.type === 'fragment') {
+        const fragment = await models.Fragment.find(team.object.id)
+        collection = await models.Collection.find(fragment.collectionId)
+      }
       if (
         collection.status === 'rejected' &&
         team.teamType.permissions === 'reviewer'
@@ -188,19 +195,21 @@ async function authenticatedUser(user, operation, object, context) {
   // only allow a reviewer and an HE to submit and to modify a recommendation
   if (
     ['POST', 'PATCH'].includes(operation) &&
-    get(object.collection, 'type') === 'collection' &&
     object.path.includes('recommendations')
   ) {
-    const collection = await context.models.Collection.find(
-      get(object.collection, 'id'),
-    )
+    const authsomeObject = get(object, 'authsomeObject')
+
     const teams = await helpers.getTeamsByPermissions(
       user.teams,
       ['reviewer', 'handlingEditor'],
       context.models.Team,
     )
+
     if (teams.length === 0) return false
-    const matchingTeam = teams.find(team => team.object.id === collection.id)
+    const matchingTeam = teams.find(
+      team => team.object.id === authsomeObject.id,
+    )
+
     if (matchingTeam) return true
     return false
   }
diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js
index 2b2c1062dbf806562db74079846592af8cd5e634..b8d8d607061cd81a82c828686002baac69a89312 100644
--- a/packages/xpub-faraday/config/validations.js
+++ b/packages/xpub-faraday/config/validations.js
@@ -7,7 +7,6 @@ module.exports = {
     created: Joi.date(),
     title: Joi.string(),
     status: Joi.string(),
-    reviewers: Joi.array(),
     customId: Joi.string(),
     invitations: Joi.array(),
     handlingEditor: Joi.object(),
@@ -74,6 +73,7 @@ module.exports = {
       lock: Joi.object(),
       decision: Joi.object(),
       authors: Joi.array(),
+      invitations: Joi.array(),
       recommendations: Joi.array().items(
         Joi.object({
           id: Joi.string().required(),