Просмотр исходного кода

Add an endpoint to access project members

Shane Kilkelly 10 лет назад
Родитель
Сommit
8f7603c324

+ 10 - 0
services/web/app/coffee/Features/Collaborators/CollaboratorsController.coffee

@@ -5,6 +5,7 @@ EditorRealTimeController = require "../Editor/EditorRealTimeController"
 LimitationsManager = require "../Subscription/LimitationsManager"
 UserGetter = require "../User/UserGetter"
 EmailHelper = require "../Helpers/EmailHelper"
+logger = require 'logger-sharelatex'
 
 
 module.exports = CollaboratorsController =
@@ -50,3 +51,12 @@ module.exports = CollaboratorsController =
 			return callback(error) if error?
 			EditorRealTimeController.emitToRoom(project_id, 'userRemovedFromProject', user_id)
 			callback()
+
+	getAllMembers: (req, res, next) ->
+		projectId = req.params.Project_id
+		logger.log {projectId}, "getting all active members for project"
+		CollaboratorsHandler.getAllMembers projectId, (err, members) ->
+			if err?
+				logger.err {projectId}, "error getting members for project"
+				return next(err)
+			res.json({members: members})

+ 10 - 0
services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee

@@ -8,6 +8,7 @@ async = require "async"
 PrivilegeLevels = require "../Authorization/PrivilegeLevels"
 Errors = require "../Errors/Errors"
 EmailHelper = require "../Helpers/EmailHelper"
+ProjectEditorHandler = require "../Project/ProjectEditorHandler"
 
 
 module.exports = CollaboratorsHandler =
@@ -144,3 +145,12 @@ module.exports = CollaboratorsHandler =
 					if error?
 						logger.error {err: error, project_id, user_id}, "error flushing to TPDS after adding collaborator"
 				callback()
+
+	getAllMembers: (projectId, callback=(err, members)->) ->
+		logger.log {projectId}, "fetching all members"
+		CollaboratorsHandler.getMembersWithPrivilegeLevels projectId, (error, rawMembers) ->
+			if error?
+				logger.err {projectId, error}, "error getting members for project"
+				return callback(error)
+			{owner, members} = ProjectEditorHandler.buildOwnerAndMembersViews(rawMembers)
+			callback(null, members)

+ 8 - 1
services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee

@@ -11,6 +11,13 @@ module.exports =
 		webRouter.post   '/project/:Project_id/users', AuthorizationMiddlewear.ensureUserCanAdminProject, CollaboratorsController.addUserToProject
 		webRouter.delete '/project/:Project_id/users/:user_id', AuthorizationMiddlewear.ensureUserCanAdminProject, CollaboratorsController.removeUserFromProject
 
+		webRouter.get(
+			'/project/:Project_id/members',
+			AuthenticationController.requireLogin(),
+			AuthorizationMiddlewear.ensureUserCanAdminProject,
+			CollaboratorsController.getAllMembers
+		)
+
 		# invites
 		webRouter.post(
 			'/project/:Project_id/invite',
@@ -26,7 +33,7 @@ module.exports =
 		)
 
 		webRouter.get(
-			'/project/:Project_id/invite',
+			'/project/:Project_id/invites',
 			AuthenticationController.requireLogin(),
 			AuthorizationMiddlewear.ensureUserCanAdminProject,
 			CollaboratorsInviteController.getAllInvites

+ 16 - 9
services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee

@@ -17,16 +17,11 @@ module.exports = ProjectEditorHandler =
 			members:     []
 			invites:     invites || []
 
-		owner = null
-		for member in members
-			if member.privilegeLevel == "owner"
-				owner = member.user
-			else
-				result.members.push @buildUserModelView member.user, member.privilegeLevel
-		if owner?
-			result.owner = @buildUserModelView owner, "owner"
+		{owner, ownerFeatures, members} = @buildOwnerAndMembersViews(members)
+		result.owner = owner
+		result.members = members
 
-		result.features = _.defaults(owner?.features or {}, {
+		result.features = _.defaults(ownerFeatures or {}, {
 			collaborators: -1 # Infinite
 			versioning: false
 			dropbox:false
@@ -38,6 +33,18 @@ module.exports = ProjectEditorHandler =
 
 		return result
 
+	buildOwnerAndMembersViews: (members) ->
+		owner = null
+		ownerFeatures = null
+		filteredMembers = []
+		for member in members
+			if member.privilegeLevel == "owner"
+				ownerFeatures = member.user.features
+				owner = @buildUserModelView member.user, "owner"
+			else
+				filteredMembers.push @buildUserModelView member.user, member.privilegeLevel
+		{owner: owner, ownerFeatures: ownerFeatures, members: filteredMembers}
+
 	buildUserModelView: (user, privileges) ->
 		_id        : user._id
 		first_name : user.first_name

+ 1 - 1
services/web/public/coffee/ide/share/services/projectInvites.coffee

@@ -25,7 +25,7 @@ define [
 				})
 
 			getInvites: () ->
-				$http.get("/project/#{ide.project_id}/invite", {
+				$http.get("/project/#{ide.project_id}/invites", {
 					json: true
 					headers:
 						"X-Csrf-Token": window.csrfToken

+ 8 - 0
services/web/public/coffee/ide/share/services/projectMembers.coffee

@@ -17,5 +17,13 @@ define [
 					privileges: privileges
 					_csrf: window.csrfToken
 				})
+
+			getMembers: () ->
+				$http.get("/project/#{ide.project_id}/members", {
+					json: true
+					headers:
+						"X-Csrf-Token": window.csrfToken
+				})
+
 		}
 	]

+ 44 - 9
services/web/test/UnitTests/coffee/Collaborators/CollaboratorsControllerTests.coffee

@@ -18,6 +18,7 @@ describe "CollaboratorsController", ->
 			'../Subscription/LimitationsManager' : @LimitationsManager = {}
 			'../Project/ProjectEditorHandler' : @ProjectEditorHandler = {}
 			'../User/UserGetter': @UserGetter = {}
+			'logger-sharelatex': @logger = {err: sinon.stub(), erro: sinon.stub(), log: sinon.stub()}
 		@res = new MockResponse()
 		@req = new MockRequest()
 
@@ -73,10 +74,10 @@ describe "CollaboratorsController", ->
 			beforeEach ->
 				@LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, false)
 				@CollaboratorsController.addUserToProject @req, @res, @next
-		
+
 			it "should not add the user to the project", ->
 				@CollaboratorsHandler.addEmailToProject.called.should.equal false
-		
+
 			it "should not emit a userAddedToProject event", ->
 				@EditorRealTimeController.emitToRoom.called.should.equal false
 
@@ -93,17 +94,17 @@ describe "CollaboratorsController", ->
 				@res.status = sinon.stub().returns @res
 				@res.send = sinon.stub()
 				@CollaboratorsController.addUserToProject @req, @res, @next
-		
+
 			it "should not add the user to the project", ->
 				@CollaboratorsHandler.addEmailToProject.called.should.equal false
-		
+
 			it "should not emit a userAddedToProject event", ->
 				@EditorRealTimeController.emitToRoom.called.should.equal false
 
 			it "should return a 400 response", ->
 				@res.status.calledWith(400).should.equal true
 				@res.send.calledWith("invalid email address").should.equal true
-			
+
 	describe "removeUserFromProject", ->
 		beforeEach ->
 			@req.params =
@@ -113,17 +114,17 @@ describe "CollaboratorsController", ->
 			@EditorRealTimeController.emitToRoom = sinon.stub()
 			@CollaboratorsHandler.removeUserFromProject = sinon.stub().callsArg(2)
 			@CollaboratorsController.removeUserFromProject @req, @res
-			
+
 		it "should from the user from the project", ->
 			@CollaboratorsHandler.removeUserFromProject
 				.calledWith(@project_id, @user_id)
 				.should.equal true
-		
+
 		it "should emit a userRemovedFromProject event to the proejct", ->
 			@EditorRealTimeController.emitToRoom
 				.calledWith(@project_id, 'userRemovedFromProject', @user_id)
 				.should.equal true
-				
+
 		it "should send the back a success response", ->
 			@res.sendStatus.calledWith(204).should.equal true
 
@@ -141,7 +142,7 @@ describe "CollaboratorsController", ->
 			@CollaboratorsHandler.removeUserFromProject
 				.calledWith(@project_id, @user_id)
 				.should.equal true
-	
+
 		it "should emit a userRemovedFromProject event to the proejct", ->
 			@EditorRealTimeController.emitToRoom
 				.calledWith(@project_id, 'userRemovedFromProject', @user_id)
@@ -150,3 +151,37 @@ describe "CollaboratorsController", ->
 		it "should return a success code", ->
 			@res.sendStatus.calledWith(204).should.equal true
 
+	describe 'getAllMembers', ->
+		beforeEach ->
+			@req.session =
+				user: _id: @user_id = "user-id-123"
+			@req.params = Project_id: @project_id
+			@res.json = sinon.stub()
+			@next = sinon.stub()
+			@members = [{a: 1}]
+			@CollaboratorsHandler.getAllMembers = sinon.stub().callsArgWith(1, null, @members)
+			@CollaboratorsController.getAllMembers(@req, @res, @next)
+
+		it 'should not produce an error', ->
+			@next.callCount.should.equal 0
+
+		it 'should produce a json response', ->
+			@res.json.callCount.should.equal 1
+			@res.json.calledWith({members: @members}).should.equal true
+
+		it 'should call CollaboratorsHandler.getAllMembers', ->
+			@CollaboratorsHandler.getAllMembers.callCount.should.equal 1
+
+		describe 'when CollaboratorsHandler.getAllMembers produces an error', ->
+			beforeEach ->
+				@res.json = sinon.stub()
+				@next = sinon.stub()
+				@CollaboratorsHandler.getAllMembers = sinon.stub().callsArgWith(1, new Error('woops'))
+				@CollaboratorsController.getAllMembers(@req, @res, @next)
+
+			it 'should produce an error', ->
+				@next.callCount.should.equal 1
+				@next.firstCall.args[0].should.be.instanceof Error
+
+			it 'should not produce a json response', ->
+				@res.json.callCount.should.equal 0

+ 88 - 29
services/web/test/UnitTests/coffee/Collaborators/CollaboratorsHandlerTests.coffee

@@ -18,13 +18,14 @@ describe "CollaboratorsHandler", ->
 			"../Project/ProjectEntityHandler": @ProjectEntityHandler = {}
 			"./CollaboratorsEmailHandler": @CollaboratorsEmailHandler = {}
 			"../Errors/Errors": Errors
+			"../Project/ProjectEditorHandler": @ProjectEditorHandler = {}
 
 		@project_id = "mock-project-id"
 		@user_id = "mock-user-id"
 		@adding_user_id = "adding-user-id"
 		@email = "joe@sharelatex.com"
 		@callback = sinon.stub()
-	
+
 	describe "getMemberIdsWithPrivilegeLevels", ->
 		describe "with project", ->
 			beforeEach ->
@@ -46,16 +47,16 @@ describe "CollaboratorsHandler", ->
 						{ id: "read-write-ref-2", privilegeLevel: "readAndWrite" }
 					])
 					.should.equal true
-		
+
 		describe "with a missing project", ->
 			beforeEach ->
 				@Project.findOne = sinon.stub().yields(null, null)
-			
+
 			it "should return a NotFoundError", (done) ->
 				@CollaboratorHandler.getMemberIdsWithPrivilegeLevels @project_id, (error) ->
 					error.should.be.instanceof Errors.NotFoundError
 					done()
-	
+
 	describe "getMemberIds", ->
 		beforeEach ->
 			@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
@@ -68,7 +69,7 @@ describe "CollaboratorsHandler", ->
 			@callback
 				.calledWith(null, ["member-id-1", "member-id-2"])
 				.should.equal true
-	
+
 	describe "getMembersWithPrivilegeLevels", ->
 		beforeEach ->
 			@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
@@ -86,7 +87,7 @@ describe "CollaboratorsHandler", ->
 			@UserGetter.getUser.withArgs("read-write-ref-2").yields(null, { _id: "read-write-ref-2" })
 			@UserGetter.getUser.withArgs("doesnt-exist").yields(null, null)
 			@CollaboratorHandler.getMembersWithPrivilegeLevels @project_id, @callback
-		
+
 		it "should return an array of members with their privilege levels", ->
 			@callback
 				.calledWith(null, [
@@ -96,7 +97,7 @@ describe "CollaboratorsHandler", ->
 					{ user: { _id: "read-write-ref-2" }, privilegeLevel: "readAndWrite" }
 				])
 				.should.equal true
-	
+
 	describe "getMemberIdPrivilegeLevel", ->
 		beforeEach ->
 			@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
@@ -116,7 +117,7 @@ describe "CollaboratorsHandler", ->
 			@CollaboratorHandler.getMemberIdPrivilegeLevel "member-id-3", @project_id, (error, level) ->
 				expect(level).to.equal false
 				done()
-	
+
 	describe "isUserMemberOfProject", ->
 		beforeEach ->
 			@CollaboratorHandler.getMemberIdsWithPrivilegeLevels = sinon.stub()
@@ -128,7 +129,7 @@ describe "CollaboratorsHandler", ->
 					{ id: @user_id, privilegeLevel: "readAndWrite" }
 				])
 				@CollaboratorHandler.isUserMemberOfProject @user_id, @project_id, @callback
-			
+
 			it "should return true and the privilegeLevel", ->
 				@callback
 					.calledWith(null, true, "readAndWrite")
@@ -140,12 +141,12 @@ describe "CollaboratorsHandler", ->
 					{ id: "not-the-user", privilegeLevel: "readOnly" }
 				])
 				@CollaboratorHandler.isUserMemberOfProject @user_id, @project_id, @callback
-			
+
 			it "should return false", ->
 				@callback
 					.calledWith(null, false, null)
 					.should.equal true
-	
+
 	describe "getProjectsUserIsCollaboratorOf", ->
 		beforeEach ->
 			@fields = "mock fields"
@@ -153,7 +154,7 @@ describe "CollaboratorsHandler", ->
 			@Project.find.withArgs({collaberator_refs:@user_id}, @fields).yields(null, ["mock-read-write-project-1", "mock-read-write-project-2"])
 			@Project.find.withArgs({readOnly_refs:@user_id}, @fields).yields(null, ["mock-read-only-project-1", "mock-read-only-project-2"])
 			@CollaboratorHandler.getProjectsUserIsCollaboratorOf @user_id, @fields, @callback
-		
+
 		it "should call the callback with the projects", ->
 			@callback
 				.calledWith(null, ["mock-read-write-project-1", "mock-read-write-project-2"], ["mock-read-only-project-1", "mock-read-only-project-2"])
@@ -172,7 +173,7 @@ describe "CollaboratorsHandler", ->
 					"$pull":{collaberator_refs:@user_id, readOnly_refs:@user_id}
 				})
 				.should.equal true
-	
+
 	describe "addUserToProject", ->
 		beforeEach ->
 			@Project.update = sinon.stub().callsArg(2)
@@ -182,7 +183,7 @@ describe "CollaboratorsHandler", ->
 			@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user = { _id: @user_id, email: @email })
 			@CollaboratorsEmailHandler.notifyUserOfProjectShare = sinon.stub()
 			@ContactManager.addContact = sinon.stub()
-			
+
 		describe "as readOnly", ->
 			beforeEach ->
 				@CollaboratorHandler.addUserIdToProject @project_id, @adding_user_id, @user_id, "readOnly", @callback
@@ -195,7 +196,7 @@ describe "CollaboratorsHandler", ->
 						"$addToSet":{ readOnly_refs: @user_id }
 					})
 					.should.equal true
-			
+
 			it "should flush the project to the TPDS", ->
 				@ProjectEntityHandler.flushProjectToThirdPartyDataStore
 					.calledWith(@project_id)
@@ -205,12 +206,12 @@ describe "CollaboratorsHandler", ->
 				@CollaboratorsEmailHandler.notifyUserOfProjectShare
 					.calledWith(@project_id, @email)
 					.should.equal true
-			
+
 			it "should add the user as a contact for the adding user", ->
 				@ContactManager.addContact
 					.calledWith(@adding_user_id, @user_id)
 					.should.equal true
-					
+
 		describe "as readAndWrite", ->
 			beforeEach ->
 				@CollaboratorHandler.addUserIdToProject @project_id, @adding_user_id, @user_id, "readAndWrite", @callback
@@ -223,19 +224,19 @@ describe "CollaboratorsHandler", ->
 						"$addToSet":{ collaberator_refs: @user_id }
 					})
 					.should.equal true
-			
+
 			it "should flush the project to the TPDS", ->
 				@ProjectEntityHandler.flushProjectToThirdPartyDataStore
 					.calledWith(@project_id)
 					.should.equal true
-		
+
 		describe "with invalid privilegeLevel", ->
 			beforeEach ->
 				@CollaboratorHandler.addUserIdToProject @project_id, @adding_user_id, @user_id, "notValid", @callback
 
 			it "should call the callback with an error", ->
 				@callback.calledWith(new Error()).should.equal true
-		
+
 		describe "when user already exists as a collaborator", ->
 			beforeEach ->
 				@project.collaberator_refs = [@user_id]
@@ -243,7 +244,7 @@ describe "CollaboratorsHandler", ->
 
 			it "should not add the user again", ->
 				@Project.update.called.should.equal false
-	
+
 	describe "addEmailToProject", ->
 		beforeEach ->
 			@UserCreator.getUserOrCreateHoldingAccount = sinon.stub().callsArgWith(1, null, @user = {_id: @user_id})
@@ -252,27 +253,27 @@ describe "CollaboratorsHandler", ->
 		describe "with a valid email", ->
 			beforeEach ->
 				@CollaboratorHandler.addEmailToProject @project_id, @adding_user_id, (@email = "Joe@example.com"), (@privilegeLevel = "readAndWrite"), @callback
-			
+
 			it "should get the user with the lowercased email", ->
 				@UserCreator.getUserOrCreateHoldingAccount
 					.calledWith(@email.toLowerCase())
 					.should.equal true
-		
+
 			it "should add the user to the project by id", ->
 				@CollaboratorHandler.addUserIdToProject
 					.calledWith(@project_id, @adding_user_id, @user_id, @privilegeLevel)
 					.should.equal true
-			
+
 			it "should return the callback with the user_id", ->
 				@callback.calledWith(null, @user_id).should.equal true
-		
+
 		describe "with an invalid email", ->
 			beforeEach ->
 				@CollaboratorHandler.addEmailToProject @project_id, @adding_user_id, "not-and-email", (@privilegeLevel = "readAndWrite"), @callback
-		
+
 			it "should call the callback with an error", ->
 				@callback.calledWith(new Error()).should.equal true
-			
+
 			it "should not add any users to the proejct", ->
 				@CollaboratorHandler.addUserIdToProject.called.should.equal false
 
@@ -286,9 +287,67 @@ describe "CollaboratorsHandler", ->
 			)
 			@CollaboratorHandler.removeUserFromProject = sinon.stub().yields()
 			@CollaboratorHandler.removeUserFromAllProjets @user_id, done
-		
+
 		it "should remove the user from each project", ->
 			for project_id in ["read-and-write-0", "read-and-write-1", "read-only-0", "read-only-1"]
 				@CollaboratorHandler.removeUserFromProject
 					.calledWith(project_id, @user_id)
-					.should.equal true
+					.should.equal true
+
+	describe 'getAllMembers', ->
+
+		beforeEach ->
+			@owning_user = {_id: 'owner-id', email: 'owner@example.com', features: {a: 1}}
+			@readwrite_user = {_id: 'readwrite-id', email: 'readwrite@example.com'}
+			@members = [
+				{user: @owning_user,    privilegeLevel: "owner"},
+				{user: @readwrite_user, privilegeLevel: "readAndWrite"}
+			]
+			@CollaboratorHandler.getMembersWithPrivilegeLevels = sinon.stub().callsArgWith(1, null, @members)
+			@ProjectEditorHandler.buildOwnerAndMembersViews = sinon.stub().returns(@views = {
+				owner: @owning_user,
+				ownerFeatures: @owning_user.features,
+				members: [ {_id: @readwrite_user._id, email: @readwrite_user.email} ]
+			})
+			@callback = sinon.stub()
+			@CollaboratorHandler.getAllMembers @project_id, @callback
+
+		it 'should not produce an error', ->
+			@callback.callCount.should.equal 1
+			expect(@callback.firstCall.args[0]).to.equal null
+
+		it 'should produce a list of members', ->
+			@callback.callCount.should.equal 1
+			expect(@callback.firstCall.args[1]).to.deep.equal @views.members
+
+		it 'should call getMembersWithPrivileges', ->
+			@CollaboratorHandler.getMembersWithPrivilegeLevels.callCount.should.equal 1
+			@CollaboratorHandler.getMembersWithPrivilegeLevels.firstCall.args[0].should.equal @project_id
+
+		it 'should call ProjectEditorHandler.buildOwnerAndMembersViews', ->
+			@ProjectEditorHandler.buildOwnerAndMembersViews.callCount.should.equal 1
+			@ProjectEditorHandler.buildOwnerAndMembersViews.firstCall.args[0].should.equal @members
+
+		describe 'when getMembersWithPrivileges produces an error', ->
+
+			beforeEach ->
+				@CollaboratorHandler.getMembersWithPrivilegeLevels = sinon.stub().callsArgWith(1, new Error('woops'))
+				@ProjectEditorHandler.buildOwnerAndMembersViews = sinon.stub().returns(@views = {
+					owner: @owning_user,
+					ownerFeatures: @owning_user.features,
+					members: [ {_id: @readwrite_user._id, email: @readwrite_user.email} ]
+				})
+				@callback = sinon.stub()
+				@CollaboratorHandler.getAllMembers @project_id, @callback
+
+			it 'should produce an error', ->
+				@callback.callCount.should.equal 1
+				expect(@callback.firstCall.args[0]).to.not.equal null
+				expect(@callback.firstCall.args[0]).to.be.instanceof Error
+
+			it 'should call getMembersWithPrivileges', ->
+				@CollaboratorHandler.getMembersWithPrivilegeLevels.callCount.should.equal 1
+				@CollaboratorHandler.getMembersWithPrivilegeLevels.firstCall.args[0].should.equal @project_id
+
+			it 'should not call ProjectEditorHandler.buildOwnerAndMembersViews', ->
+				@ProjectEditorHandler.buildOwnerAndMembersViews.callCount.should.equal 0

+ 39 - 2
services/web/test/UnitTests/coffee/Project/ProjectEditorHandlerTests.coffee

@@ -1,4 +1,5 @@
 chai = require('chai')
+expect = chai.expect
 should = chai.should()
 
 modulePath = "../../../../app/js/Features/Project/ProjectEditorHandler"
@@ -105,7 +106,7 @@ describe "ProjectEditorHandler", ->
 			it "should include the deletedDocs", ->
 				should.exist @result.deletedDocs
 				@result.deletedDocs.should.equal @project.deletedDocs
-				
+
 			it "should gather readOnly_refs and collaberators_refs into a list of members", ->
 				findMember = (id) =>
 					for member in @result.members
@@ -176,10 +177,46 @@ describe "ProjectEditorHandler", ->
 					compileGroup:"priority"
 					compileTimeout: 96
 				@result = @handler.buildProjectModelView @project, @members
-			
+
 			it "should copy the owner features to the project", ->
 				@result.features.versioning.should.equal @owner.features.versioning
 				@result.features.collaborators.should.equal @owner.features.collaborators
 				@result.features.compileGroup.should.equal @owner.features.compileGroup
 				@result.features.compileTimeout.should.equal @owner.features.compileTimeout
 
+	describe 'buildOwnerAndMembersViews', ->
+		beforeEach ->
+			@owner.features =
+				versioning: true
+				collaborators: 3
+				compileGroup:"priority"
+				compileTimeout: 22
+			@result = @handler.buildOwnerAndMembersViews @members
+
+		it 'should produce an object with owner, ownerFeatures and members keys', ->
+			expect(@result).to.have.all.keys ['owner', 'ownerFeatures', 'members']
+
+		it 'should separate the owner from the members', ->
+			@result.members.length.should.equal(@members.length-1)
+			expect(@result.owner._id).to.equal @owner._id
+			expect(@result.owner.email).to.equal @owner.email
+			expect(@result.members.filter((m) => m._id == @owner._id).length).to.equal 0
+
+		it 'should extract the ownerFeatures from the owner object', ->
+			expect(@result.ownerFeatures).to.deep.equal @owner.features
+
+		describe 'when there is no owner', ->
+			beforeEach ->
+				# remove the owner from members list
+				@membersWithoutOwner = @members.filter((m) => m.user._id != @owner._id)
+				@result = @handler.buildOwnerAndMembersViews @membersWithoutOwner
+
+			it 'should produce an object with owner, ownerFeatures and members keys', ->
+				expect(@result).to.have.all.keys ['owner', 'ownerFeatures', 'members']
+
+			it 'should not separate out an owner', ->
+				@result.members.length.should.equal @membersWithoutOwner.length
+				expect(@result.owner).to.equal null
+
+			it 'should not extract the ownerFeatures from the owner object', ->
+				expect(@result.ownerFeatures).to.equal null