Skip to content
This repository was archived by the owner on Apr 27, 2023. It is now read-only.

Commit e69df4c

Browse files
committed
Added DeleteUserUseCaseSpec and test cases. Enhanced error message on ui
1 parent 272d73c commit e69df4c

File tree

12 files changed

+237
-70
lines changed

12 files changed

+237
-70
lines changed

codebrag-dao/src/main/scala/com/softwaremill/codebrag/dao/user/SQLUserDAO.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,5 @@ class SQLUserDAO(val database: SQLDatabase) extends UserDAO with SQLUserSchema {
141141
users.filter(_.id === userId).delete
142142
}
143143
}
144-
144+
145145
}

codebrag-dao/src/test/scala/com/softwaremill/codebrag/dao/user/UserDAOSpec.scala

+32
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,38 @@ class SQLUserDAOSpec extends FlatSpecWithSQL with ClearSQLDataAfterTest with Bef
370370
foundUser.get.notifications.commits.get.getMillis should equal(commitDate.getMillis)
371371
foundUser.get.notifications.followups.get.getMillis should equal(followupDate.getMillis)
372372
}
373+
it should "deleta a user " taggedAs RequiresDb in {
374+
// given
375+
val user = UserAssembler.randomUser.withBasicAuth("user", "pass").withAdmin(set = false).withActive(set = false).get
376+
userDAO.add(user)
377+
val newAuth = Authentication.basic(user.authentication.username, "newpass")
373378

379+
// when
380+
val modifiedUser = user.copy(authentication = newAuth, admin = true, active = true)
381+
userDAO.delete(modifiedUser.id)
382+
val deletedUser = userDAO.findById(user.id)
383+
384+
// then
385+
assert(deletedUser === None , "Deletion was attempted but found " + deletedUser)
386+
}
387+
it should "deleta a user and should not show in the list " taggedAs RequiresDb in {
388+
// given
389+
val user = UserAssembler.randomUser.withBasicAuth("user", "pass").withAdmin(set = false).withActive(set = false).get
390+
userDAO.add(user)
391+
val newAuth = Authentication.basic(user.authentication.username, "newpass")
392+
393+
// when
394+
val tobeDeletedUser = user.copy(authentication = newAuth, admin = true, active = true)
395+
val userCountBeforeDelete = userDAO.findAll().length
396+
userDAO.delete(tobeDeletedUser.id)
397+
val deletedUser = userDAO.findById(user.id)
398+
val userCountAfterDelete = userDAO.findAll().length
399+
// then
400+
assert(deletedUser === None , "Deletion was attempted but found " + deletedUser)
401+
userCountAfterDelete should be(userCountBeforeDelete -1)
402+
403+
}
404+
374405
"rememberNotifications" should "store dates properly" taggedAs RequiresDb in {
375406
// given
376407
val user = UserAssembler.randomUser.get
@@ -479,4 +510,5 @@ class SQLUserDAOSpec extends FlatSpecWithSQL with ClearSQLDataAfterTest with Bef
479510
partial should have size (2)
480511
partial.map(_.name).toSet should be (users.map(_.name).toSet)
481512
}
513+
482514
}

codebrag-rest/src/main/scala/ScalatraBootstrap.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class ScalatraBootstrap extends LifeCycle with Logging {
6969
RepositoryUpdateScheduler.scheduleUpdates(actorSystem, repositories, commitImportService)
7070
context.mount(new RegistrationServlet(registerService, registerNewUserUseCase, listReposAfterRegistration, listRepoBranchesAfterRegistration, watchBranchAfterRegistration, unwatchBranchAfterRegistration), Prefix + RegistrationServlet.MappingPath)
7171
context.mount(new SessionServlet(authenticator, loginUserUseCase, userFinder), Prefix + SessionServlet.MappingPath)
72-
context.mount(new UsersServlet(authenticator, userFinder, modifyUserDetailsUseCase, config), Prefix + UsersServlet.MappingPath)
72+
context.mount(new UsersServlet(authenticator, userFinder, modifyUserDetailsUseCase, deleteUserUseCase, config), Prefix + UsersServlet.MappingPath)
7373
context.mount(new UserAliasesEndpoint(authenticator, addUserAliasUseCase, deleteUserAliasUseCase), Prefix + UserAliasesEndpoint.MappingPath)
7474
context.mount(new UsersSettingsServlet(authenticator, userDao, changeUserSettingsUseCase), Prefix + "users/settings")
7575
context.mount(new CommitsServlet(authenticator, toReviewCommitsFinder, allCommitsFinder, reactionFinder, addCommentUseCase,

codebrag-rest/src/main/scala/com/softwaremill/codebrag/Beans.scala

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ trait Beans extends ActorSystemSupport with CommitsModule with Daos {
7979
lazy val generateInvitationCodeUseCase = new GenerateInvitationCodeUseCase(invitationsService, userDao)
8080
lazy val sendInvitationEmailUseCase = new SendInvitationEmailUseCase(invitationsService, userDao)
8181
lazy val modifyUserDetailsUseCase = new ModifyUserDetailsUseCase(userDao)
82+
lazy val deleteUserUseCase = new DeleteUserUseCase(userDao)
8283
lazy val updateUserBrowsingContextUseCase = new UpdateUserBrowsingContextUseCase(userRepoDetailsDao)
8384
lazy val addUserAliasUseCase = new AddUserAliasUseCase(userAliasDao, userDao)
8485
lazy val deleteUserAliasUseCase = new DeleteUserAliasUseCase(userAliasDao)

codebrag-rest/src/main/scala/com/softwaremill/codebrag/rest/UsersServlet.scala

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import org.bson.types.ObjectId
66
import org.scalatra
77
import com.softwaremill.codebrag.finders.user.UserFinder
88
import com.softwaremill.codebrag.finders.user.ManagedUsersListView
9-
import com.softwaremill.codebrag.usecases.user.{RegisterNewUserUseCase, ModifyUserDetailsUseCase, ModifyUserDetailsForm}
9+
import com.softwaremill.codebrag.usecases.user.{RegisterNewUserUseCase, ModifyUserDetailsUseCase, ModifyUserDetailsForm,DeleteUserUseCase,DeleteUserForm}
1010

1111
class UsersServlet(
1212
val authenticator: Authenticator,
1313
userFinder: UserFinder,
1414
modifyUserUseCase: ModifyUserDetailsUseCase,
15+
deleteUserUseCase: DeleteUserUseCase,
1516
config: CodebragConfig) extends JsonServletWithAuthentication {
1617

1718
get("/") {
@@ -34,9 +35,13 @@ class UsersServlet(
3435
case _ => scalatra.Ok()
3536
}
3637
}
37-
delete("/:id") {
38-
haltIfNotAuthenticated()
39-
modifyUserUseCase.delete(new ObjectId(params("id")))
38+
delete("/:userId") {
39+
haltIfNotAuthenticated()
40+
val targetUserId = new ObjectId(params("userId"))
41+
deleteUserUseCase.execute(user.id, DeleteUserForm(targetUserId)) match {
42+
case Left(errors) => scalatra.BadRequest(errors)
43+
case _ => scalatra.Ok()
44+
}
4045
}
4146
}
4247

codebrag-rest/src/test/scala/com/softwaremill/codebrag/rest/UsersServletSpec.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import com.softwaremill.codebrag.finders.user.ManagedUserView
1313
import com.softwaremill.codebrag.finders.user.ManagedUsersListView
1414
import com.softwaremill.codebrag.domain.builder.UserAssembler
1515
import com.softwaremill.codebrag.domain.User
16-
import com.softwaremill.codebrag.usecases.user.ModifyUserDetailsUseCase
16+
import com.softwaremill.codebrag.usecases.user.{ModifyUserDetailsUseCase,DeleteUserUseCase}
1717

1818
class UsersServletSpec extends AuthenticatableServletSpec {
1919

2020
val modifyUserUseCase = mock[ModifyUserDetailsUseCase]
21+
val deleteUserUseCase = mock[DeleteUserUseCase]
2122
var userFinder: UserFinder = _
2223
var config: CodebragConfig = _
2324

@@ -60,7 +61,7 @@ class UsersServletSpec extends AuthenticatableServletSpec {
6061
}
6162

6263
class TestableUsersServlet(fakeAuthenticator: Authenticator, fakeScentry: Scentry[User])
63-
extends UsersServlet(fakeAuthenticator, userFinder, modifyUserUseCase, config) {
64+
extends UsersServlet(fakeAuthenticator, userFinder, modifyUserUseCase, deleteUserUseCase ,config) {
6465
override def scentry(implicit request: javax.servlet.http.HttpServletRequest) = fakeScentry
6566
}
6667

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.softwaremill.codebrag.usecases.user
2+
3+
import com.softwaremill.codebrag.dao.user.UserDAO
4+
import com.softwaremill.codebrag.usecases.assertions.UserAssertions
5+
import org.bson.types.ObjectId
6+
import com.softwaremill.codebrag.domain.{Authentication, User}
7+
import com.typesafe.scalalogging.slf4j.Logging
8+
import com.softwaremill.scalaval.Validation._
9+
10+
case class DeleteUserForm(userId: ObjectId) {
11+
12+
}
13+
14+
class DeleteUserUseCase(protected val userDao: UserDAO) extends Logging {
15+
16+
import UserAssertions._
17+
18+
def execute(executorId: ObjectId, form: DeleteUserForm): Either[Errors, Unit] = {
19+
assertUserWithId(executorId, mustBeActive, mustBeAdmin)(userDao)
20+
val targetUser = loadUser(form.userId)
21+
validateUserDetails(executorId, targetUser, form).whenOk[Unit] {
22+
logger.debug(s"Validation passed, attempting to delete user $targetUser")
23+
userDao.delete(form.userId)
24+
}
25+
}
26+
27+
private def loadUser(userId: ObjectId) = userDao.findById(userId).getOrElse(throw new IllegalStateException(s"User $userId not found"))
28+
29+
private def validateUserDetails(executorId: ObjectId, user: User, form: DeleteUserForm) = {
30+
val changeOwnFlagsCheck = rule("userId") {
31+
val isDeleteFlags = user.admin || user.active
32+
(!isDeleteFlags || (isDeleteFlags && executorId != user.id), "Cannot delete own user")
33+
}
34+
validate(changeOwnFlagsCheck)
35+
}
36+
37+
}

codebrag-service/src/main/scala/com/softwaremill/codebrag/usecases/user/ModifyUserDetailsUseCase.scala

-4
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,4 @@ class ModifyUserDetailsUseCase(protected val userDao: UserDAO) extends Logging {
5151
validate(checkUserActive, changeOwnFlagsCheck)
5252
}
5353

54-
def delete(userId: ObjectId) = {
55-
userDao.delete(userId)
56-
}
57-
5854
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.softwaremill.codebrag.usecases.user
2+
3+
import org.scalatest.{BeforeAndAfter, FlatSpec}
4+
import org.scalatest.matchers.ShouldMatchers
5+
import org.scalatest.mock.MockitoSugar
6+
import com.softwaremill.codebrag.dao.user.UserDAO
7+
import org.mockito.Matchers._
8+
import org.mockito.Mockito._
9+
import com.softwaremill.codebrag.domain.builder.UserAssembler
10+
import com.softwaremill.codebrag.domain.{Authentication, User}
11+
import com.softwaremill.codebrag.usecases.assertions.{ActiveUserStatusRequiredException, AdminRoleRequiredException}
12+
import org.bson.types.ObjectId
13+
14+
class DeleteUserUseCaseSpec extends FlatSpec with ShouldMatchers with BeforeAndAfter with MockitoSugar {
15+
16+
val userDao = mock[UserDAO]
17+
val useCase = new DeleteUserUseCase(userDao)
18+
19+
val InactiveUser = UserAssembler.randomUser.withActive(set = false).get
20+
val ActiveUser = UserAssembler.randomUser.withActive().get
21+
22+
val ValidExecutor = UserAssembler.randomUser.withAdmin().withActive().get
23+
val NonAdminExecutor = UserAssembler.randomUser.withActive().withAdmin(set = false).get
24+
val InactiveExecutor = UserAssembler.randomUser.withActive(set = false).withAdmin().get
25+
26+
after {
27+
reset(userDao)
28+
}
29+
30+
it should "not delete user when executing user is neither admin nor active" in {
31+
// given
32+
setupReturningUserFromDB(NonAdminExecutor, InactiveExecutor)
33+
val form = DeleteUserForm(ActiveUser.id)
34+
35+
// when
36+
intercept[AdminRoleRequiredException] {
37+
useCase.execute(NonAdminExecutor.id, form)
38+
}
39+
intercept[ActiveUserStatusRequiredException] {
40+
useCase.execute(InactiveExecutor.id, form)
41+
}
42+
43+
// then
44+
verify(userDao, never()).delete(any[ObjectId])
45+
}
46+
47+
it should "not allow to delete yourself" in {
48+
// given
49+
setupReturningUserFromDB(ValidExecutor)
50+
51+
// when
52+
val ownChangeForm = DeleteUserForm(ValidExecutor.id)
53+
val Left(result) = useCase.execute(ValidExecutor.id, ownChangeForm)
54+
55+
// then
56+
result should be(Map("userId" -> List("Cannot delete own user")))
57+
verify(userDao, never()).delete(any[ObjectId])
58+
}
59+
60+
61+
it should "delete user when validation passes" in {
62+
// given
63+
stubCurrentlyActiveUsersCountTo(0)
64+
setupReturningUserFromDB(ValidExecutor, ActiveUser)
65+
66+
// when
67+
val newAuth = Authentication.basic(ActiveUser.authentication.username, "secret")
68+
val form = new DeleteUserForm(ActiveUser.id)
69+
val result = useCase.execute(ValidExecutor.id, form)
70+
71+
// then
72+
result should be('right)
73+
val expectedUser = form
74+
verify(userDao).delete(expectedUser.userId)
75+
}
76+
77+
private def stubCurrentlyActiveUsersCountTo(activeUsersCount: Int) {
78+
when(userDao.countAllActive()).thenReturn(activeUsersCount)
79+
}
80+
81+
private def setupReturningUserFromDB(users: User*) {
82+
users.foreach { user =>
83+
when(userDao.findById(user.id)).thenReturn(Some(user))
84+
}
85+
}
86+
87+
}
88+

codebrag-ui/app/scripts/users/manageUsersPopupCtrl.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,10 @@ angular.module('codebrag.userMgmt')
2626
});
2727
};
2828

29-
$scope.deleteUser = function(userId) {
29+
$scope.deleteUser = function(user) {
3030
$scope.flash.clear();
31-
var userData = { userId: userId };
32-
userMgmtService.deleteUser(userId).then(deleteSuccess, deleteFailed('active', userId))
33-
userMgmtService.loadUsers().then(function(users) {
34-
$scope.users = users;
35-
});
31+
var userData = { userId: user.userId };
32+
userMgmtService.deleteUser(userData).then(deleteSuccess(user), deleteFailed('active', user.userId))
3633
};
3734

3835
$scope.askForNewPassword = function(user) {
@@ -42,16 +39,19 @@ angular.module('codebrag.userMgmt')
4239
$scope.flash.add('info', 'User password changed');
4340
});
4441
};
45-
function deleteSuccess() {
46-
$scope.flash.add('error', 'User is removed');
42+
function deleteSuccess(user) {
43+
$scope.flash.add('error', 'User ' + user.email + ' is deleted');
44+
userMgmtService.loadUsers().then(function(users) {
45+
$scope.users = users;
46+
});
4747
}
4848

4949
function modifySuccess() {
5050
$scope.flash.add('info', 'User details changed');
5151
}
5252
function deleteFailed(flag, userId) {
5353
return function(errorsMap) {
54-
$scope.flash.add('error', 'Could not change user details');
54+
$scope.flash.add('error', ' Could not delete user ');
5555
flattenErrorsMap(errorsMap).forEach(function(error) {
5656
$scope.flash.add('error', error);
5757
});

codebrag-ui/app/scripts/users/userMgmtService.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ angular.module('codebrag.userMgmt')
1919
return $http.put(modifyUserUrl, userData).then(null, modifyUserFailed);
2020
};
2121

22-
this.deleteUser = function(userId) {
23-
var modifyUserUrl = [usersApiUrl, '/', userId].join('');
24-
return $http.delete(modifyUserUrl).then(null, modifyUserFailed);
22+
this.deleteUser = function(userData) {
23+
var deleteUserUrl = [usersApiUrl, '/', userData.userId].join('');
24+
return $http.delete(deleteUserUrl).then(null, modifyUserFailed);
2525
};
2626

2727
function modifyUserFailed(response) {

0 commit comments

Comments
 (0)