diff --git a/build.gradle b/build.gradle index c88beb5..a9a11d2 100644 --- a/build.gradle +++ b/build.gradle @@ -29,8 +29,13 @@ dependencies { compile 'com.javaslang:javaslang:2.0.0-RC4' compile 'com.google.guava:guava:19.0' compile 'org.easymock:easymock:3.4' + compile group: 'cglib', name: 'cglib', version: '3.2.4' testCompile 'org.assertj:assertj-core:3.2.0' testCompile group: 'junit', name: 'junit', version: '4.11' + testCompile 'org.spockframework:spock-core:1.0-groovy-2.3' + testCompile( 'com.athaydes:spock-reports:1.2.12' ) { + transitive = false + } } buildscript { diff --git a/src/main/java/springAngularApp/Application.groovy b/src/main/java/springAngularApp/Application.groovy new file mode 100644 index 0000000..d2523d5 --- /dev/null +++ b/src/main/java/springAngularApp/Application.groovy @@ -0,0 +1,24 @@ +package springAngularApp + +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.EnableAspectJAutoProxy +import org.springframework.context.annotation.Import +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.transaction.annotation.EnableTransactionManagement +import springAngularApp.system.configuration.MvcConfiguration +import springAngularApp.system.configuration.SpringJpaConfig +import springAngularApp.system.configuration.WebSecurityConfig + +@SpringBootApplication +@EnableAspectJAutoProxy +@EnableTransactionManagement(proxyTargetClass = true) +@EnableJpaRepositories(transactionManagerRef = "txManager", entityManagerFactoryRef = "entityManagerFactory") +@Import([SpringJpaConfig.class, WebSecurityConfig.class, MvcConfiguration.class]) +class Application { + + static void main(String[] args) { + SpringApplication.run(Application.class, args) + } + +} diff --git a/src/main/java/springAngularApp/Application.java b/src/main/java/springAngularApp/Application.java deleted file mode 100644 index f3f1a26..0000000 --- a/src/main/java/springAngularApp/Application.java +++ /dev/null @@ -1,24 +0,0 @@ -package springAngularApp; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.EnableAspectJAutoProxy; -import org.springframework.context.annotation.Import; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.transaction.annotation.EnableTransactionManagement; -import springAngularApp.system.configuration.MvcConfiguration; -import springAngularApp.system.configuration.SpringJpaConfig; -import springAngularApp.system.configuration.WebSecurityConfig; - -@SpringBootApplication -@EnableAspectJAutoProxy -@EnableTransactionManagement(proxyTargetClass = true) -@EnableJpaRepositories(transactionManagerRef = "txManager", entityManagerFactoryRef = "entityManagerFactory") -@Import({SpringJpaConfig.class, WebSecurityConfig.class, MvcConfiguration.class}) -public class Application { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - -} \ No newline at end of file diff --git a/src/main/java/springAngularApp/users/ws/UserWebService.groovy b/src/main/java/springAngularApp/users/ws/UserWebService.groovy new file mode 100644 index 0000000..c3d4fc6 --- /dev/null +++ b/src/main/java/springAngularApp/users/ws/UserWebService.groovy @@ -0,0 +1,57 @@ +package springAngularApp.users.ws + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.WebDataBinder +import org.springframework.web.bind.annotation.* +import springAngularApp.system.service.AuthProvider +import springAngularApp.users.domain.model.UserCommand +import springAngularApp.users.service.UserGroupService +import springAngularApp.users.service.UserService +import springAngularApp.users.ws.schema.UserConfigurationModelResponse +import springAngularApp.users.ws.validation.UserValidator + +import javax.validation.Valid + +import static org.springframework.http.HttpStatus.OK +import static org.springframework.web.bind.annotation.RequestMethod.* +import static springAngularApp.users.domain.entities.UserAuthorities.ROLE_USER_DELETE +import static springAngularApp.users.domain.entities.UserAuthorities.ROLE_USER_EDIT + +@RestController +@RequestMapping(value = "ws/users") +class UserWebService { + + @Autowired UserGroupService userGroupService; + @Autowired UserValidator userValidator; + @Autowired AuthProvider authProvider; + @Autowired UserService userService; + + @InitBinder + initBinder(WebDataBinder binder) { + binder.setValidator userValidator; + } + + @RequestMapping(value = "/{userId}", method = DELETE) + def delete(@PathVariable String userId) { + userService.delete userId + new ResponseEntity(OK) + } + + @RequestMapping(method = POST) + def save(@Valid @RequestBody UserCommand userCommand) { + userService.save userCommand + new ResponseEntity(OK) + } + + @RequestMapping(method = GET, value = "/model") + def getModel() { + new UserConfigurationModelResponse( + hasUserDeleteAccess: authProvider.hasRole(ROLE_USER_DELETE), + hasUserEditAccess: authProvider.hasRole(ROLE_USER_EDIT), + userGroups: userGroupService.getUserGroupLinks(), + users: userService.getUsers(), + ) + } + +} diff --git a/src/main/java/springAngularApp/users/ws/UserWebService.java b/src/main/java/springAngularApp/users/ws/UserWebService.java deleted file mode 100644 index 612fae3..0000000 --- a/src/main/java/springAngularApp/users/ws/UserWebService.java +++ /dev/null @@ -1,56 +0,0 @@ -package springAngularApp.users.ws; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.annotation.*; -import springAngularApp.system.service.AuthProvider; -import springAngularApp.users.domain.model.UserCommand; -import springAngularApp.users.service.UserGroupService; -import springAngularApp.users.service.UserService; -import springAngularApp.users.ws.schema.UserConfigurationModelResponse; -import springAngularApp.users.ws.validation.UserValidator; - -import javax.validation.Valid; - -import static org.springframework.http.HttpStatus.OK; -import static org.springframework.web.bind.annotation.RequestMethod.*; -import static springAngularApp.users.domain.entities.UserAuthorities.ROLE_USER_DELETE; -import static springAngularApp.users.domain.entities.UserAuthorities.ROLE_USER_EDIT; - -@RestController -@RequestMapping(value = "ws/users") -public class UserWebService { - - @Autowired private UserGroupService userGroupService; - @Autowired private UserValidator userValidator; - @Autowired private AuthProvider authProvider; - @Autowired private UserService userService; - - @InitBinder - public void initBinder(WebDataBinder binder) { - binder.setValidator(userValidator); - } - - @RequestMapping(value = "/{userId}", method = DELETE) - public ResponseEntity delete(@PathVariable String userId) { - userService.delete(userId); - return new ResponseEntity(OK); - } - - @RequestMapping(method = POST) - public ResponseEntity save(@Valid @RequestBody UserCommand userCommand) { - userService.save(userCommand); - return new ResponseEntity(OK); - } - - @RequestMapping(method = GET, value = "/model") - public UserConfigurationModelResponse getModel() { - UserConfigurationModelResponse response = new UserConfigurationModelResponse(); - response.setHasUserDeleteAccess(authProvider.hasRole(ROLE_USER_DELETE)); - response.setHasUserEditAccess(authProvider.hasRole(ROLE_USER_EDIT)); - response.setUserGroups(userGroupService.getUserGroupLinks()); - response.setUsers(userService.getUsers()); - return response; - } -} diff --git a/src/test/java/springAngularApp/users/service/UserAuthorityServiceSpec.groovy b/src/test/java/springAngularApp/users/service/UserAuthorityServiceSpec.groovy new file mode 100644 index 0000000..b15b305 --- /dev/null +++ b/src/test/java/springAngularApp/users/service/UserAuthorityServiceSpec.groovy @@ -0,0 +1,47 @@ +package springAngularApp.users.service + +import spock.lang.Narrative +import spock.lang.Specification +import spock.lang.Title +import springAngularApp.system.domain.model.IdNameCommand +import springAngularApp.users.domain.repositories.UserAuthorityRepository + +import static springAngularApp.users.domain.entities.UserAuthorityFixture.createDefaultUserAuthority + +@Title("User authority service") +@Narrative(""" +As a used I want to be able to view and manage the user authorities. So that I need a layer between the domain and +the UI to provide this functionality. +""") +class UserAuthorityServiceSpec extends Specification { + + UserAuthorityRepository userAuthorityRepository = Mock(); + UserAuthorityService testee = new UserAuthorityService( + userAuthorityRepository: userAuthorityRepository + ); + + def "When service gets user authorities and nothing is found, empty list will be returned"() { + given: "no authorities exist" + userAuthorityRepository.findByOrderByNameAsc() >> []; + when: "get authorities links" + def actualUserAuthorities = testee.getUserAuthoritiesLinks(); + then: "authorities links must be empty" + actualUserAuthorities.isEmpty(); + } + + def "When service gets the user authority links and two links exist, two links will be mapped and returned"() { + given: "two existing links" + def firstUserAuthority = createDefaultUserAuthority(); + def secondUserAuthority = createDefaultUserAuthority(); + def firstIdNameCommand = new IdNameCommand(firstUserAuthority, { u -> u.getId() }, { u -> u.getName() }); + def secondIdNameCommand = new IdNameCommand(secondUserAuthority, { u -> u.getId() }, { u -> u.getName() }); + def givenAuthorities = [firstUserAuthority, secondUserAuthority]; + userAuthorityRepository.findByOrderByNameAsc() >> givenAuthorities; + when: "get authorities links" + def actualUserAuthorities = testee.getUserAuthoritiesLinks(); + then: "two links must be returned" + actualUserAuthorities.size() == 2 + actualUserAuthorities.containsAll([firstIdNameCommand, secondIdNameCommand]); + } + +} diff --git a/src/test/java/springAngularApp/users/service/UserGroupServiceSpec.groovy b/src/test/java/springAngularApp/users/service/UserGroupServiceSpec.groovy new file mode 100644 index 0000000..2f52d5a --- /dev/null +++ b/src/test/java/springAngularApp/users/service/UserGroupServiceSpec.groovy @@ -0,0 +1,110 @@ +package springAngularApp.users.service + +import spock.lang.Narrative +import spock.lang.Specification +import spock.lang.Title +import springAngularApp.system.domain.model.IdNameCommand +import springAngularApp.users.domain.entities.UserGroup +import springAngularApp.users.domain.entities.UserGroupFixture +import springAngularApp.users.domain.repositories.UserGroupRepository +import springAngularApp.users.service.mapper.UserGroupCommandMapper + +import static java.util.Collections.emptyList +import static springAngularApp.users.domain.entities.UserGroupFixture.createDefaultUserGroup +import static springAngularApp.users.domain.model.UserGroupCommandFixture.createDefaultUserGroupCommand + +@Title("User group service") +@Narrative(""" +As a used I want to be able to view and manage the user groups. So that I need a layer between the domain and +the UI to provide this functionality. +""") +public class UserGroupServiceSpec extends Specification { + + UserGroupRepository userGroupRepository = Mock(); + UserGroupCommandMapper userGroupCommandMapper = Mock(); + + UserGroupService testee = new UserGroupService( + userGroupRepository: userGroupRepository, + userGroupCommandMapper: userGroupCommandMapper + ); + + def "When service gets user group links and there is nothing found, the empty list will be returned"() { + given: "user groups don't exist" + userGroupRepository.findByOrderByNameAsc() >> emptyList(); + when: "get user group links" + def actualUserGroups = testee.getUserGroupLinks(); + then: "user group links list must be empty" + actualUserGroups.isEmpty() + } + + def "When service gets user group links and there are two user groups exist, both will be mapped and returned"() { + given: "two existing user groups" + def firstUserGroup = createDefaultUserGroup(); + def secondUserGroup = createDefaultUserGroup(); + def firstIdNameCommand = new IdNameCommand(firstUserGroup, { u -> u.getId() }, { u -> u.getName() }); + def secondIdNameCommand = new IdNameCommand(secondUserGroup, { u -> u.getId() }, { u -> u.getName() }); + userGroupRepository.findByOrderByNameAsc() >> [firstUserGroup, secondUserGroup]; + when: "get user group links" + def actualUserGroups = testee.getUserGroupLinks(); + then: "two user group links must be returned" + actualUserGroups.size() == 2 + actualUserGroups.containsAll([firstIdNameCommand, secondIdNameCommand]); + } + + def "When service gets user groups and there is nothing found, the empty list will be returned"() { + given: "user groups don't exist" + userGroupRepository.findByOrderByNameAsc() >> emptyList(); + when: "get user groups" + def actualUserGroups = testee.getUserGroups(); + then: "user groups list has to be empty" + actualUserGroups.isEmpty(); + } + + def "When service gets user groups and there are two user groups exist, both will be mapped and returned"() { + given: "two user group exist" + def firstUserGroup = createDefaultUserGroup(); + def secondUserGroup = createDefaultUserGroup(); + def firstUserGroupCommand = createDefaultUserGroupCommand(); + def secondUserGroupCommand = createDefaultUserGroupCommand(); + userGroupRepository.findByOrderByNameAsc() >> [firstUserGroup, secondUserGroup]; + userGroupCommandMapper.mapToCommand(firstUserGroup) >> firstUserGroupCommand; + userGroupCommandMapper.mapToCommand(secondUserGroup) >> secondUserGroupCommand; + when: "get user groups" + def actualUserGroups = testee.getUserGroups(); + then: "two user group should be returned" + actualUserGroups.size() == 2 + actualUserGroups.containsAll([firstUserGroupCommand, secondUserGroupCommand]); + } + + def "When service saves a valid group, the group is mapped and saved"() { + given: "valid group" + def userGroupCommand = createDefaultUserGroupCommand(); + def userGroup = createDefaultUserGroup(); + 1 * userGroupCommandMapper.mapFromCommand(userGroupCommand) >> userGroup; + when: "save" + testee.save(userGroupCommand); + then: "user group will be mapped and saved" + 1 * userGroupRepository.save(userGroup); + } + + def "When service deletes the general group, it delegates deletion to the repository"() { + given: "general group" + def userGroup = createDefaultUserGroup(); + userGroupRepository.findOne(userGroup.getId()) >> userGroup; + when: "delete" + testee.delete(userGroup.getId()); + then: "group will be deleted" + 1 * userGroupRepository.delete(userGroup.getId()); + } + + def "When service deletes the super user, IllegalArgumentException is thrown"() { + given: "super user group" + UserGroup superUserGroup = UserGroupFixture.builder().setSuperUserGroup(true).build(); + userGroupRepository.findOne(superUserGroup.getId()) >> superUserGroup; + when: "delete" + testee.delete(superUserGroup.getId()); + then: "exception will be thrown" + thrown(IllegalArgumentException) + } + +} \ No newline at end of file diff --git a/src/test/java/springAngularApp/users/service/UserServiceSpec.groovy b/src/test/java/springAngularApp/users/service/UserServiceSpec.groovy new file mode 100644 index 0000000..f4a5a5e --- /dev/null +++ b/src/test/java/springAngularApp/users/service/UserServiceSpec.groovy @@ -0,0 +1,84 @@ +package springAngularApp.users.service + +import spock.lang.Narrative +import spock.lang.Specification +import spock.lang.Title +import springAngularApp.users.domain.entities.User +import springAngularApp.users.domain.entities.UserFixture +import springAngularApp.users.domain.model.UserCommand +import springAngularApp.users.domain.repositories.UserRepository +import springAngularApp.users.service.mapper.UserCommandMapper + +import static springAngularApp.users.domain.entities.UserFixture.createDefaultUser + +@Title("User service") +@Narrative(""" +As a used I want to be able to view and manage users. So that I need a layer between the domain and +the UI to provide this functionality. +""") +class UserServiceSpec extends Specification { + + UserRepository userRepository = Mock(); + UserCommandMapper userCommandMapper = Mock(); + + UserService testee = new UserService( + userRepository: userRepository, + userCommandMapper: userCommandMapper + ); + + def "When service saves a valid user, it should be saved"() { + given: "valid user" + def userCommand = new UserCommand(); + def user = createDefaultUser(); + userCommandMapper.mapFromCommand(userCommand) >> user; + when: "save" + testee.save userCommand; + then: "user will be mapped and saved" + 1 * userRepository.save(user); + } + + def "When service deletes an existing user, it should delegate the call to the repository"() { + given: "regular user" + def user = createDefaultUser(); + userRepository.findOne(user.getId()) >> user; + when: "delete" + testee.delete user.getId(); + then: "repository should delete the selected user" + 1 * userRepository.delete(user.getId()); + } + + def "When service deletes an existing system user, exception is thrown"() { + given: "system user" + def user = UserFixture.builder().setSystemUser(true).build(); + userRepository.findOne(user.getId()) >> user; + when: "delete" + testee.delete user.getId(); + then: "exception should be thrown" + thrown IllegalArgumentException + } + + def "When service tries to get users and doesn't find them it has to return an empty list"() { + given: "users don't exist" + userRepository.findByOrderByFirstNameAsc() >> []; + when: "get users" + List actualUsers = testee.getUsers(); + then: "empty list will be returned" + actualUsers.isEmpty() + } + + def "When service tries to get users and there are two users, two users will be returned"(){ + given: "two user exist" + User firstUser = createDefaultUser(); + User secondUser = createDefaultUser(); + UserCommand firstUserCommand = new UserCommand(); + UserCommand secondUserCommand = new UserCommand(); + userRepository.findByOrderByFirstNameAsc() >> [firstUser, secondUser]; + userCommandMapper.mapToCommand(firstUser) >> firstUserCommand; + userCommandMapper.mapToCommand(secondUser) >> secondUserCommand; + when: "get users" + List actualUsers = testee.getUsers(); + then: "two users will be returned" + actualUsers.size() == 2 + actualUsers.containsAll([firstUserCommand, secondUserCommand]) + } +} diff --git a/src/test/java/springAngularApp/users/service/mapper/UserGroupCommandMapperSpec.groovy b/src/test/java/springAngularApp/users/service/mapper/UserGroupCommandMapperSpec.groovy new file mode 100644 index 0000000..0956527 --- /dev/null +++ b/src/test/java/springAngularApp/users/service/mapper/UserGroupCommandMapperSpec.groovy @@ -0,0 +1,94 @@ +package springAngularApp.users.service.mapper + +import spock.lang.Narrative +import spock.lang.Specification +import spock.lang.Title +import springAngularApp.system.domain.model.IdNameCommand +import springAngularApp.users.domain.entities.UserAuthority +import springAngularApp.users.domain.entities.UserGroup +import springAngularApp.users.domain.model.UserGroupCommand +import springAngularApp.users.domain.repositories.UserAuthorityRepository +import springAngularApp.users.domain.repositories.UserGroupRepository + +import static java.util.Arrays.asList +import static springAngularApp.users.domain.entities.UserAuthorityFixture.createDefaultUserAuthority +import static springAngularApp.users.domain.entities.UserGroupFixture.createDefaultUserGroup +import static springAngularApp.users.domain.model.UserGroupCommandFixture.createDefaultUserGroupCommand +import static springAngularApp.users.domain.model.UserGroupCommandFixture.createUserGroupCommandWithEmptyId +import static springAngularApp.users.service.UserAuthorityService.USER_AUTHORITY_TO_LINK_FUNCTION + +@Title("Mapper to map between UserGroup and UserGroupCommand") +@Narrative(""" + As a user I want to be able to save the user data from the UI. So that I need the clear way to transfer state + between domain objects and their UI representations. Thus, I need a converter to convert the object to/from command. +""") +class UserGroupCommandMapperSpec extends Specification { + + private UserAuthorityRepository userAuthorityRepository = Mock(); + private UserGroupRepository userGroupRepository = Mock(); + private UserGroupCommandMapper testee = new UserGroupCommandMapper( + userAuthorityRepository: userAuthorityRepository, + userGroupRepository: userGroupRepository + ) + + def "Mapper maps a valid entity to the command, entity will be mapped to the command"() { + given: "valid entity" + UserGroup userGroup = createDefaultUserGroup(); + userGroup.setAuthorities([createDefaultUserAuthority(), createDefaultUserAuthority()]); + when: "map to command" + UserGroupCommand command = testee.mapToCommand(userGroup); + then: "object has to be converted to the command" + assertMapToCommand(userGroup, command); + } + + private static void assertMapToCommand(UserGroup userGroup, UserGroupCommand command) { + assert command.getId() == userGroup.getId(); + assert command.getName() == userGroup.getName(); + assert command.isSuperUserGroup() == userGroup.isSuperUserGroup(); + command.getAuthorities().size().times { + IdNameCommand userAuthorityCommand = command.getAuthorities()[it]; + UserAuthority userAuthority = userGroup.getAuthorities()[it]; + assert userAuthorityCommand.getId() == userAuthority.getId(); + assert userAuthorityCommand.getName() == userAuthority.getName(); + } + } + + def "Mapper maps entity from command and entity exists, entity will be modified with values from the command"() { + given: "valid command for existing user" + UserGroupCommand command = createDefaultUserGroupCommand(); + userGroupRepository.findOne(command.getId()) >> createDefaultUserGroup(); + mockUserAuthoritiesForMapFromCommand(command); + when: "map from command" + UserGroup userGroup = testee.mapFromCommand(command); + then: "existing user entity must be modified with the new data" + assertMapFromCommand(command, userGroup); + } + + def "Mapper maps entity from command and entity does not exist, new entity will be created and mapped from command"() { + given: "valid command for not existing user" + UserGroupCommand command = createUserGroupCommandWithEmptyId(); + mockUserAuthoritiesForMapFromCommand(command); + when: "map from command" + UserGroup userGroup = testee.mapFromCommand(command); + then: "new user object must be created with the specified data" + assertMapFromCommand(command, userGroup); + } + + private void mockUserAuthoritiesForMapFromCommand(UserGroupCommand command) { + UserAuthority userAuthority = createDefaultUserAuthority(); + IdNameCommand userAuthorityLink = USER_AUTHORITY_TO_LINK_FUNCTION.apply(userAuthority); + command.getAuthorities() << userAuthorityLink; + userAuthorityRepository.findAll([userAuthority.getId()]) >> asList(userAuthority); + } + + private static void assertMapFromCommand(UserGroupCommand command, UserGroup userGroup) { + assert userGroup.isSuperUserGroup() == command.isSuperUserGroup(); + assert userGroup.getName() == command.getName(); + userGroup.getAuthorities().size().times { + def currentUserAuthorityCommand = command.getAuthorities()[it]; + def currentUserAuthority = userGroup.getAuthorities()[it]; + assert currentUserAuthorityCommand.getId() == currentUserAuthority.getId(); + } + } + +} diff --git a/src/test/java/springAngularApp/users/ws/validation/UserValidatorSpec.groovy b/src/test/java/springAngularApp/users/ws/validation/UserValidatorSpec.groovy new file mode 100644 index 0000000..88d1d6c --- /dev/null +++ b/src/test/java/springAngularApp/users/ws/validation/UserValidatorSpec.groovy @@ -0,0 +1,187 @@ +package springAngularApp.users.ws.validation + +import org.apache.commons.lang3.RandomStringUtils +import org.springframework.validation.Errors +import spock.lang.Narrative +import spock.lang.Specification +import spock.lang.Title +import spock.lang.Unroll +import springAngularApp.users.domain.model.UserCommand +import springAngularApp.users.domain.repositories.UserRepository + +import static springAngularApp.users.domain.entities.User.OLD_PASSWORD_MASK +import static springAngularApp.users.ws.validation.UserValidationConstants.* + +@Title("User validation specification") +@Narrative(""" +As a user I want to be able to create a new user in the system. The data is entered by hand and there could be mistakes +in that data. Before processing further the data should be validated to find those particular mistakes and show +them to the user and provide hints to fix them. +""") +class UserValidatorSpec extends Specification { + + UserRepository userRepository = Mock(); + Errors errors = Mock(); + + UserValidator testee = new UserValidator( + userRepository: userRepository, + ) + + def setup() { + userRepository.existsByName(_) >> false; + } + + def "No errors should be added when user name is not empty"() { + given: "valid user name" + UserCommand userCommand = new UserCommand(userName: "Test User Name"); + when: "validate" + testee.validate(userCommand, errors); + then: "no errors should be added" + 0 * errors.rejectValue(USER_NAME_FIELD, USER_NAME_IS_EMPTY_CODE); + } + + def "Errors must be added when user name is empty"() { + given: "empty user name" + UserCommand userCommand = new UserCommand(); + when: "validate" + testee.validate(userCommand, errors); + then: "errors should be added" + 1 * errors.rejectValue(USER_NAME_FIELD, USER_NAME_IS_EMPTY_CODE); + } + + def "No errors should be added when first name is not empty"() { + given: "valid first name" + UserCommand userCommand = new UserCommand(firstName: "Test First Name"); + when: "validate" + testee.validate(userCommand, errors); + then: "no errors should be added" + 0 * errors.rejectValue(USER_FIRST_NAME_FIELD, FIRST_NAME_IS_EMPTY_CODE); + } + + def "Errors must be added when first name is empty"() { + given: "empty first name" + UserCommand userCommand = new UserCommand(); + when: "validate" + testee.validate(userCommand, errors); + then: "errors should be added" + 1 * errors.rejectValue(USER_FIRST_NAME_FIELD, FIRST_NAME_IS_EMPTY_CODE); + } + + def "No errors should be added when last name is not empty"() { + given: "valid last name" + UserCommand userCommand = new UserCommand(lastName: "Test Last Name"); + when: "validate" + testee.validate(userCommand, errors); + then: "no errors should be added" + 0 * errors.rejectValue(USER_LAST_NAME_FIELD, LAST_NAME_IS_EMPTY_CODE); + } + + def "Errors must be added when last name is empty"() { + given: "empty last name" + UserCommand userCommand = new UserCommand(); + when: "validate" + testee.validate(userCommand, errors); + then: "errors should be added" + 1 * errors.rejectValue(USER_LAST_NAME_FIELD, LAST_NAME_IS_EMPTY_CODE); + } + + def "No errors should be added when user group is selected"() { + given: "user group is selected" + UserCommand userCommand = new UserCommand(userGroupId: "Test User Group Id"); + when: "validate" + testee.validate(userCommand, errors); + then: "no errors should be added" + 0 * errors.rejectValue(USER_GROUP_FIELD, USER_GROUP_IS_EMPTY_CODE); + } + + def "Errors must be added when user group is not selected"() { + given: "user group is not selected" + UserCommand userCommand = new UserCommand(); + when: "validate" + testee.validate(userCommand, errors); + then: "errors should be added" + 1 * errors.rejectValue(USER_GROUP_FIELD, USER_GROUP_IS_EMPTY_CODE); + } + + def "No errors should be added when password is not empty"() { + given: "valid password" + UserCommand userCommand = new UserCommand(password: "Test Password"); + when: "validate" + testee.validate(userCommand, errors); + then: "no errors should be added" + 0 * errors.rejectValue(PASSWORD_FIELD, PASSWORD_IS_EMPTY_CODE); + } + + def "Errors must be added when password is empty"() { + given: "empty password" + UserCommand userCommand = new UserCommand(); + when: "validate" + testee.validate(userCommand, errors); + then: "errors must be added" + 1 * errors.rejectValue(PASSWORD_FIELD, PASSWORD_IS_EMPTY_CODE); + } + + @Unroll + def "When the password length is #passwordLength then errors exist is #errorsShouldBeAdded"() { + given: "password with length #passwordLength" + String sevenCharactersLengthPassword = RandomStringUtils.random(passwordLength); + UserCommand userCommand = new UserCommand(password: sevenCharactersLengthPassword); + def expectedErrorsNumber = errorsShouldBeAdded ? 1 : 0; + when: "validate" + testee.validate(userCommand, errors); + then: "errors must be added is #errorsShouldBeAdded" + expectedErrorsNumber * errors.rejectValue(PASSWORD_FIELD, PASSWORD_IS_WEAK_CODE); + where: "passwordLength=#passwordLength; errorsShouldBeAdded=#errorsShouldBeAdded;" + passwordLength | errorsShouldBeAdded + 1 | true + 5 | true + 6 | false + 7 | false + 1000 | false + 10000 | false + } + + def "Errors should not be added if password equals to the old password even though it is too short"() { + given: "password that equals to old password" + UserCommand userCommand = new UserCommand(password: OLD_PASSWORD_MASK); + when: "validate" + testee.validate(userCommand, errors); + then: "errors mustn't be added" + 0 * errors.rejectValue(PASSWORD_FIELD, PASSWORD_IS_WEAK_CODE); + } + + def "Errors must be added when the user with the same name already exists"() { + given: "user with the name that equals to existing user name" + UserCommand userCommand = new UserCommand(userName: "Test name"); + when: "validate" + userRepository.existsByName(userCommand.getUserName()) >> true; + testee.validate(userCommand, errors); + then: "errors must be added" + 1 * errors.rejectValue(USER_NAME_FIELD, USER_NAME_EXISTS_CODE); + } + + def "Errors must no be added when the user with the specified name does not exist"() { + given: "new name in terms of the system" + UserCommand userCommand = new UserCommand(userName: "Test name"); + when: "validate" + userRepository.existsByName(userCommand.getUserName()) >> false; + testee.validate(userCommand, errors); + then: "errors mustn't be added" + 0 * errors.rejectValue(USER_NAME_FIELD, USER_NAME_EXISTS_CODE); + } + + @Unroll() + def "When user exists is #userExists there should be #errorsRejectedNumber errors added to the Binding result"() { + given: "existing user name is #userExists" + UserCommand userCommand = new UserCommand(userName: "Test name"); + when: "validate" + userRepository.existsByName(userCommand.getUserName()) >> userExists; + testee.validate(userCommand, errors); + then: "expect #errorsRejectedNumber errors" + errorsRejectedNumber * errors.rejectValue(USER_NAME_FIELD, USER_NAME_EXISTS_CODE); + where: "userExists = #userExists; errorsRejectedNumber = #errorsRejectedNumber;" + userExists | errorsRejectedNumber + true | 1 + false | 0 + } +}