package uk.ac.ox.zoo.seeg.abraid.mp.publicsite.web.user.registration; import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.ui.ModelMap; import org.springframework.web.bind.support.SessionStatus; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.Expert; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.ValidatorDiseaseGroup; import uk.ac.ox.zoo.seeg.abraid.mp.common.dto.json.AbraidJsonObjectMapper; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.DiseaseService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.EmailService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.ExpertService; import uk.ac.ox.zoo.seeg.abraid.mp.publicsite.domain.JsonExpertBasic; import uk.ac.ox.zoo.seeg.abraid.mp.publicsite.domain.JsonExpertDetails; import uk.ac.ox.zoo.seeg.abraid.mp.publicsite.security.CurrentUserService; import javax.servlet.ServletRequest; import java.util.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; /** * Tests for RegistrationController. * Copyright (c) 2014 University of Oxford */ public class RegistrationControllerTest { private RegistrationController target; private CurrentUserService currentUserService; private ExpertService expertService; private DiseaseService diseaseService; private EmailService emailService; private PasswordEncoder passwordEncoder; private RegistrationControllerValidator validator; private UserDetailsService userDetailService; private static JsonExpertBasic mockJsonExpertBasic() { JsonExpertBasic result = mock(JsonExpertBasic.class); when(result.getEmail()).thenReturn("a@b.com"); when(result.getPassword()).thenReturn("qwe123Q"); when(result.getPasswordConfirmation()).thenReturn("qwe123Q"); when(result.getCaptchaChallenge()).thenReturn("challenge"); when(result.getCaptchaResponse()).thenReturn("response"); return result; } @Before public void setup() { currentUserService = mock(CurrentUserService.class); when(currentUserService.getCurrentUserId()).thenReturn(null); expertService = mock(ExpertService.class); diseaseService = mock(DiseaseService.class); emailService = mock(EmailService.class); passwordEncoder = mock(PasswordEncoder.class); AbraidJsonObjectMapper json = new AbraidJsonObjectMapper(); validator = mock(RegistrationControllerValidator.class); userDetailService = mock(UserDetailsService.class); SecurityContextHolder.getContext().setAuthentication(null); target = new RegistrationController( currentUserService, expertService, diseaseService, emailService, passwordEncoder, json, validator, userDetailService); } @Test public void getAccountPageRedirectsLoggedInUsers() throws Exception { // Arrange when(currentUserService.getCurrentUserId()).thenReturn(1); SessionStatus sessionStatus = mock(SessionStatus.class); // Act String result = target.getAccountPage(mock(ModelMap.class), sessionStatus); // Assert verify(sessionStatus).setComplete(); assertThat(result).isEqualTo("redirect:/"); } @Test public void getAccountPageAddsANewExpertToTheModelIfNotPresent() throws Exception { // Arrange ModelMap modelMap = mock(ModelMap.class); // Act String result = target.getAccountPage(modelMap, mock(SessionStatus.class)); // Assert verify(modelMap).addAttribute(eq("expert"), any(Expert.class)); assertThat(result).isEqualTo("register/account"); } @Test public void getAccountPageValidatesExpertInModelIfPresentAndHasEmail() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setEmail("a@b.com"); modelMap.addAttribute("expert", expert); // Act String result = target.getAccountPage(modelMap, mock(SessionStatus.class)); // Assert verify(validator).validateBasicFields(expert); assertThat(result).isEqualTo("register/account"); } @Test public void getAccountPageValidatesExpertInModelIfPresentAndHasPassword() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setPassword("qwe123Q"); modelMap.addAttribute("expert", expert); // Act String result = target.getAccountPage(modelMap, mock(SessionStatus.class)); // Assert verify(validator).validateBasicFields(expert); assertThat(result).isEqualTo("register/account"); } @Test public void getAccountPageValidatesExpertInModelIfPresentAndHasEmailAndPassword() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setEmail("a@b.com"); expert.setPassword("qwe123Q"); modelMap.addAttribute("expert", expert); // Act String result = target.getAccountPage(modelMap, mock(SessionStatus.class)); // Assert verify(validator).validateBasicFields(expert); assertThat(result).isEqualTo("register/account"); } @Test public void getAccountPageValidatesExpertInModelIfPresentUnlessEmailAndPasswordAreStillNull() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); // Act String result = target.getAccountPage(modelMap, mock(SessionStatus.class)); // Assert verify(validator, never()).validateBasicFields(expert); assertThat(result).isEqualTo("register/account"); } @Test public void getAccountPageAddsTheNeededModelValuesAndReturnsTheCorrectTemplate() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setEmail("a@b.com"); modelMap.addAttribute("expert", expert); when(validator.createValidationCaptcha()).thenReturn("captcha"); when(validator.validateBasicFields(any(Expert.class))).thenReturn(Arrays.asList("m1", "m2")); // Act String result = target.getAccountPage(modelMap, mock(SessionStatus.class)); // Assert assertThat((String) (modelMap.get("alerts"))).isEqualTo("[\"m1\",\"m2\"]"); assertThat((String) (modelMap.get("captcha"))).isEqualTo("captcha"); assertThat((String) (modelMap.get("jsonExpert"))).isEqualTo("{\"email\":\"a@b.com\",\"captchaChallenge\":\"\",\"captchaResponse\":\"\"}"); assertThat(result).isEqualTo("register/account"); } @Test public void getDetailsPageRedirectsLoggedInUsers() throws Exception { // Arrange when(currentUserService.getCurrentUserId()).thenReturn(1); SessionStatus sessionStatus = mock(SessionStatus.class); // Act String result = target.getDetailsPage(mock(ModelMap.class), sessionStatus); // Assert verify(sessionStatus).setComplete(); assertThat(result).isEqualTo("redirect:/"); } @Test public void getDetailsPageRedirectToTheAccountPageIfExpertNotPresentInModel() throws Exception { // Arrange // Act String result = target.getDetailsPage(mock(ModelMap.class), mock(SessionStatus.class)); // Assert assertThat(result).isEqualTo("redirect:/register/account"); } @Test public void getDetailsPageValidatesExpertInModelAndRedirectToTheAccountPageIfNotValid() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(Arrays.asList("1")); // Act String result = target.getDetailsPage(modelMap, mock(SessionStatus.class)); // Assert verify(validator).validateBasicFields(expert); assertThat(result).isEqualTo("redirect:/register/account"); } @Test public void getDetailsPageAddsTheNeededModelValuesAndReturnsTheCorrectTemplate() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(diseaseService.getAllValidatorDiseaseGroups()).thenReturn(Arrays.asList( new ValidatorDiseaseGroup(1, "a"), new ValidatorDiseaseGroup(2, "b"), new ValidatorDiseaseGroup(3, "c") )); // Act String result = target.getDetailsPage(modelMap, mock(SessionStatus.class)); // Assert assertThat((String) (modelMap.get("diseases"))) .isEqualTo("[{\"id\":1,\"name\":\"a\"},{\"id\":2,\"name\":\"b\"},{\"id\":3,\"name\":\"c\"}]"); assertThat((String) (modelMap.get("jsonExpert"))).isEqualTo("{\"visibilityRequested\":false," + "\"diseaseInterests\":[]}"); assertThat(result).isEqualTo("register/details"); } @Test public void submitAccountPageRejectsLoggedInUsers() throws Exception { // Arrange when(currentUserService.getCurrentUserId()).thenReturn(1); SessionStatus sessionStatus = mock(SessionStatus.class); // Act ResponseEntity<List<String>> result = target.submitAccountPage( mock(ModelMap.class), sessionStatus, mock(JsonExpertBasic.class), mock(ServletRequest.class)); // Assert verify(sessionStatus).setComplete(); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); assertThat(result.getBody()).containsOnly("Logged in users cannot create new accounts"); } @Test public void submitAccountPageRejectsInvalidSessions() throws Exception { // Arrange SessionStatus sessionStatus = mock(SessionStatus.class); // Act ResponseEntity<List<String>> result = target.submitAccountPage( mock(ModelMap.class), sessionStatus, mock(JsonExpertBasic.class), mock(ServletRequest.class)); // Assert verify(sessionStatus).setComplete(); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); assertThat(result.getBody()).containsOnly("Invalid registration session"); } @Test public void submitAccountPageRejectsWithoutUpdatingExpertIfBasicFieldNotValid() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(Arrays.asList("1", "2")); when(validator.validateTransientFields(any(JsonExpertBasic.class), any(ServletRequest.class))) .thenReturn(new ArrayList<String>()); // Act ResponseEntity<List<String>> result = target.submitAccountPage( modelMap, mock(SessionStatus.class), mockJsonExpertBasic(), mock(ServletRequest.class)); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertThat(result.getBody()).containsOnly("1", "2"); assertThat(expert.getEmail()).isNull(); // Not updated assertThat(expert.getPassword()).isNull(); // Not updated } @Test public void submitAccountPageRejectsWithoutUpdatingExpertIfTransientFieldNotValid() throws Exception { // Arrange ServletRequest request = mock(ServletRequest.class); ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); JsonExpertBasic expertBasic = mockJsonExpertBasic(); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateTransientFields(expertBasic, request)) .thenReturn(Arrays.asList("1", "2")); // Act ResponseEntity<List<String>> result = target.submitAccountPage( modelMap, mock(SessionStatus.class), expertBasic, request); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertThat(result.getBody()).containsOnly("1", "2"); assertThat(expert.getEmail()).isNull(); // Not updated assertThat(expert.getPassword()).isNull(); // Not updated } @Test public void submitAccountPageReturnsNoContentStatusForSuccess() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateTransientFields(any(JsonExpertBasic.class), any(ServletRequest.class))) .thenReturn(new ArrayList<String>()); // Act ResponseEntity<List<String>> result = target.submitAccountPage( modelMap, mock(SessionStatus.class), mockJsonExpertBasic(), mock(ServletRequest.class)); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); assertThat(result.getBody()).isNull(); } @Test public void submitDetailsPageRejectsLoggedInUsers() throws Exception { // Arrange when(currentUserService.getCurrentUserId()).thenReturn(1); SessionStatus sessionStatus = mock(SessionStatus.class); // Act ResponseEntity<List<String>> result = target.submitDetailsPage( mock(ModelMap.class), sessionStatus, mock(JsonExpertDetails.class)); // Assert verify(sessionStatus).setComplete(); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); assertThat(result.getBody()).containsOnly("Logged in users cannot create new accounts"); } @Test public void submitDetailsPageRejectsInvalidSessions() throws Exception { // Arrange SessionStatus sessionStatus = mock(SessionStatus.class); // Act ResponseEntity<List<String>> result = target.submitDetailsPage( mock(ModelMap.class), sessionStatus, mock(JsonExpertDetails.class)); // Assert verify(sessionStatus).setComplete(); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); assertThat(result.getBody()).containsOnly("Invalid registration session"); } @Test public void submitDetailsPageRejectsAsConflictIfBasicFieldNotValid() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(Arrays.asList("1", "2")); // Act ResponseEntity<List<String>> result = target.submitDetailsPage( modelMap, mock(SessionStatus.class), mock(JsonExpertDetails.class)); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CONFLICT); assertThat(result.getBody()).containsOnly("1", "2"); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); } @Test public void submitDetailsPageRejectsIfDetailsFieldNotValid() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateDetailsFields(any(Expert.class))).thenReturn(Arrays.asList("1", "2")); // Act ResponseEntity<List<String>> result = target.submitDetailsPage( modelMap, mock(SessionStatus.class), mock(JsonExpertDetails.class)); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertThat(result.getBody()).containsOnly("1", "2"); } @Test public void submitDetailsPageExtractsUpdatesSavesExpertAndReturnsCreatedStatusForSuccess() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setEmail("a@b.com"); expert.setPassword("qwe123Q"); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateTransientFields(any(JsonExpertBasic.class), any(ServletRequest.class))) .thenReturn(new ArrayList<String>()); when(passwordEncoder.encode("qwe123Q")).thenReturn("hash"); when(userDetailService.loadUserByUsername("a@b.com")).thenReturn(mock(UserDetails.class)); JsonExpertDetails jsonExpert = new JsonExpertDetails(); jsonExpert.setJobTitle("job"); jsonExpert.setInstitution("institution"); jsonExpert.setVisibilityRequested(true); jsonExpert.setName("name"); // Act ResponseEntity<List<String>> result = target.submitDetailsPage( modelMap, mock(SessionStatus.class), jsonExpert); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); // Updates expert before save assertThat(expert.getJobTitle()).isEqualTo(jsonExpert.getJobTitle()); assertThat(expert.getInstitution()).isEqualTo(jsonExpert.getInstitution()); assertThat(expert.getVisibilityRequested()).isEqualTo(jsonExpert.getVisibilityRequested()); assertThat(expert.getName()).isEqualTo(jsonExpert.getName()); // Hashes password before save InOrder inOrder = inOrder(passwordEncoder, expertService); inOrder.verify(passwordEncoder).encode("qwe123Q"); inOrder.verify(expertService).saveExpert(expert); assertThat(expert.getPassword()).isEqualTo("hash"); } @Test public void submitDetailsPageHashesPasswordSavesExpertAndReturnsCreatedStatusForSuccess() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setPassword("qwe123Q"); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateTransientFields(any(JsonExpertBasic.class), any(ServletRequest.class))) .thenReturn(new ArrayList<String>()); when(passwordEncoder.encode("qwe123Q")).thenReturn("hash"); when(userDetailService.loadUserByUsername(anyString())).thenReturn(mock(UserDetails.class)); // Act ResponseEntity<List<String>> result = target.submitDetailsPage( modelMap, mock(SessionStatus.class), mock(JsonExpertDetails.class)); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); InOrder inOrder = inOrder(passwordEncoder, expertService); inOrder.verify(passwordEncoder).encode("qwe123Q"); inOrder.verify(expertService).saveExpert(expert); assertThat(expert.getPassword()).isEqualTo("hash"); } @Test public void submitDetailsPageLogsUserInForSuccess() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setPassword("qwe123Q"); modelMap.addAttribute("expert", expert); assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateTransientFields(any(JsonExpertBasic.class), any(ServletRequest.class))) .thenReturn(new ArrayList<String>()); when(passwordEncoder.encode("qwe123Q")).thenReturn("hash"); UserDetails expectedUser = mock(UserDetails.class); when(userDetailService.loadUserByUsername(anyString())).thenReturn(expectedUser); Collection<GrantedAuthority> expectedAuthorities = new ArrayList<>(); expectedAuthorities.add(new SimpleGrantedAuthority("a")); expectedAuthorities.add(new SimpleGrantedAuthority("b")); doReturn(expectedAuthorities).when(expectedUser).getAuthorities(); // Act ResponseEntity<List<String>> result = target.submitDetailsPage( modelMap, mock(SessionStatus.class), mock(JsonExpertDetails.class)); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(SecurityContextHolder.getContext().getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); assertThat(SecurityContextHolder.getContext().getAuthentication().getPrincipal()).isEqualTo(expectedUser); assertThat(SecurityContextHolder.getContext().getAuthentication().getAuthorities()).isEqualTo(expectedAuthorities); } @Test public void submitDetailsPageSendsEmailForNewUserRequiringVisibilityApproval() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setPassword("qwe123Q"); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateTransientFields(any(JsonExpertBasic.class), any(ServletRequest.class))) .thenReturn(new ArrayList<String>()); when(userDetailService.loadUserByUsername(anyString())).thenReturn(mock(UserDetails.class)); // Act JsonExpertDetails jsonExpertDetails = mock(JsonExpertDetails.class); when(jsonExpertDetails.getVisibilityRequested()).thenReturn(true); ResponseEntity<List<String>> result = target.submitDetailsPage( modelMap, mock(SessionStatus.class), jsonExpertDetails); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); Map<String, Object> data = new HashMap<>(); data.put("expert", expert); verify(emailService).sendEmailInBackground( "New user requiring visibility sign off", "registration/newUserEmail.ftl", data); } @Test public void submitDetailsPageSkipsEmailForNewUserNotRequiringVisibilityApproval() throws Exception { // Arrange ModelMap modelMap = new ModelMap(); Expert expert = new Expert(); expert.setPassword("qwe123Q"); modelMap.addAttribute("expert", expert); when(validator.validateBasicFields(any(Expert.class))).thenReturn(new ArrayList<String>()); when(validator.validateTransientFields(any(JsonExpertBasic.class), any(ServletRequest.class))) .thenReturn(new ArrayList<String>()); when(userDetailService.loadUserByUsername(anyString())).thenReturn(mock(UserDetails.class)); // Act JsonExpertDetails jsonExpertDetails = mock(JsonExpertDetails.class); when(jsonExpertDetails.getVisibilityRequested()).thenReturn(false); ResponseEntity<List<String>> result = target.submitDetailsPage( modelMap, mock(SessionStatus.class), jsonExpertDetails); // Assert assertThat(result.getStatusCode()).isEqualTo(HttpStatus.CREATED); verify(emailService, never()).sendEmailInBackground(anyString(), anyString(), anyMapOf(String.class, Object.class)); } }