diff --git a/docs/release-notes.md b/docs/release-notes.md index 81d20823..51062cc8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,14 @@ # Release Notes +## 1.4.6 + +This release has a few fixes that provide better user feedback during registration and while updating the user profile. + +Fix deletion of user in the administrative section. Its associated tokens were somehow set to `NULL` by Hibernate before +being deleted. + +Hide research focus in the user profile if nothing has been entered. + ## 1.4.5 This release resolves an issue with slow SMTP servers causing a proxy timeout by sending emails asynchronously. diff --git a/mkdocs.yml b/mkdocs.yml index ba99edd7..a1ce9ec2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,4 +12,4 @@ nav: plugins: - macros extra: - rdp_version: 1.4.3 + rdp_version: 1.4.6 diff --git a/pom.xml b/pom.xml index 3a256afb..20e57789 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ ubc.pavlab rdp - 1.4.5 + 1.4.6 @@ -175,7 +175,7 @@ 1.8 - 8.5.60 + 8.5.71 diff --git a/src/main/java/ubc/pavlab/rdp/controllers/LoginController.java b/src/main/java/ubc/pavlab/rdp/controllers/LoginController.java index e653da78..4c375e6f 100644 --- a/src/main/java/ubc/pavlab/rdp/controllers/LoginController.java +++ b/src/main/java/ubc/pavlab/rdp/controllers/LoginController.java @@ -67,7 +67,7 @@ public ModelAndView createNewUser( @Validated(User.ValidationUserAccount.class) RedirectAttributes redirectAttributes, Locale locale ) { ModelAndView modelAndView = new ModelAndView( "registration" ); - User userExists = userService.findUserByEmailNoAuth( user.getEmail() ); + User existingUser = userService.findUserByEmailNoAuth( user.getEmail() ); user.setEnabled( false ); @@ -78,9 +78,15 @@ public ModelAndView createNewUser( @Validated(User.ValidationUserAccount.class) userProfile.setHideGenelist( false ); userProfile.setContactEmailVerified( false ); - if ( userExists != null ) { - bindingResult.rejectValue( "email", "error.user", "There is already a user registered this email." ); - log.warn( "Trying to register an already registered email." ); + if ( existingUser != null ) { + if ( existingUser.isEnabled() ) { + bindingResult.rejectValue( "email", "error.user", "There is already a user registered this email." ); + } else { + // maybe the user is attempting to re-register, unaware that the confirmation hasn't been processed + userService.createVerificationTokenForUser( existingUser, locale ); + bindingResult.rejectValue( "email", "error.user", "You have already registered an account with this email. We just sent you a new confirmation email." ); + } + log.warn( "Trying to register an already registered email: " + user.getEmail() + "." ); } if ( bindingResult.hasErrors() ) { diff --git a/src/main/java/ubc/pavlab/rdp/controllers/UserController.java b/src/main/java/ubc/pavlab/rdp/controllers/UserController.java index 348d0205..af00b9ea 100644 --- a/src/main/java/ubc/pavlab/rdp/controllers/UserController.java +++ b/src/main/java/ubc/pavlab/rdp/controllers/UserController.java @@ -5,7 +5,7 @@ import lombok.extern.apachecommons.CommonsLog; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -13,6 +13,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; @@ -57,6 +58,9 @@ public class UserController { @Autowired private ApplicationSettings applicationSettings; + @Autowired + private MessageSource messageSource; + @GetMapping(value = { "/user/home" }) public ModelAndView userHome() { ModelAndView modelAndView = new ModelAndView( "user/home" ); @@ -243,16 +247,54 @@ public String verifyContactEmail( @RequestParam String token, RedirectAttributes @Data static class ProfileWithOrganUberonIds { @Valid - private Profile profile; - private Set organUberonIds; + private final Profile profile; + private final Set organUberonIds; + } + + @Data + static class ProfileSavedModel { + private final String message; + private final boolean contactEmailVerified; + } + + @Data + static class FieldErrorModel { + private final String field; + private final String message; + private final Object rejectedValue; + + public static FieldErrorModel fromFieldError( FieldError fieldError ) { + return new FieldErrorModel( fieldError.getField(), fieldError.getDefaultMessage(), fieldError.getRejectedValue() ); + } + } + + @Data + static class BindingResultModel { + private final List fieldErrors; + + public static BindingResultModel fromBindingResult( BindingResult bindingResult ) { + return new BindingResultModel( bindingResult.getFieldErrors().stream().map( FieldErrorModel::fromFieldError ).collect( Collectors.toList() ) ); + } } @ResponseBody - @PostMapping(value = "/user/profile", produces = MediaType.TEXT_PLAIN_VALUE) - public String saveProfile( @RequestBody ProfileWithOrganUberonIds profileWithOrganUberonIds, Locale locale ) { + @PostMapping(value = "/user/profile", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> saveProfile( @Valid @RequestBody ProfileWithOrganUberonIds profileWithOrganUberonIds, BindingResult bindingResult, Locale locale ) { User user = userService.findCurrentUser(); - userService.updateUserProfileAndPublicationsAndOrgans( user, profileWithOrganUberonIds.profile, profileWithOrganUberonIds.profile.getPublications(), profileWithOrganUberonIds.organUberonIds, locale ); - return "Saved."; + if ( bindingResult.hasErrors() ) { + return ResponseEntity.badRequest() + .body( BindingResultModel.fromBindingResult( bindingResult ) ); + } else { + String previousContactEmail = user.getProfile().getContactEmail(); + user = userService.updateUserProfileAndPublicationsAndOrgans( user, profileWithOrganUberonIds.profile, profileWithOrganUberonIds.profile.getPublications(), profileWithOrganUberonIds.organUberonIds, locale ); + String message = messageSource.getMessage( "UserController.profileSaved", new Object[]{ user.getProfile().getContactEmail() }, locale ); + if ( user.getProfile().getContactEmail() != null && + !user.getProfile().getContactEmail().equals( previousContactEmail ) && + !user.getProfile().isContactEmailVerified() ) { + message = messageSource.getMessage( "UserController.profileSavedAndContactEmailUpdated", new String[]{ user.getProfile().getContactEmail() }, locale ); + } + return ResponseEntity.ok( new ProfileSavedModel( message, user.getProfile().isContactEmailVerified() ) ); + } } @ResponseBody diff --git a/src/main/java/ubc/pavlab/rdp/model/AccessToken.java b/src/main/java/ubc/pavlab/rdp/model/AccessToken.java index 56a60ec1..7c9d7159 100644 --- a/src/main/java/ubc/pavlab/rdp/model/AccessToken.java +++ b/src/main/java/ubc/pavlab/rdp/model/AccessToken.java @@ -22,8 +22,8 @@ public class AccessToken extends Token implements UserContent { @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "user_id", nullable = false) + @ManyToOne(optional = false) + @JoinColumn(name = "user_id") private User user; @Override diff --git a/src/main/java/ubc/pavlab/rdp/model/PasswordResetToken.java b/src/main/java/ubc/pavlab/rdp/model/PasswordResetToken.java index 8fc60ba8..34e97639 100644 --- a/src/main/java/ubc/pavlab/rdp/model/PasswordResetToken.java +++ b/src/main/java/ubc/pavlab/rdp/model/PasswordResetToken.java @@ -26,8 +26,8 @@ public class PasswordResetToken extends Token implements UserContent { @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; - @OneToOne(fetch = FetchType.EAGER) - @JoinColumn(nullable = false, name = "user_id") + @ManyToOne(optional = false) + @JoinColumn(name = "user_id") private User user; @Override diff --git a/src/main/java/ubc/pavlab/rdp/model/User.java b/src/main/java/ubc/pavlab/rdp/model/User.java index 6e67d13d..ef57dec3 100644 --- a/src/main/java/ubc/pavlab/rdp/model/User.java +++ b/src/main/java/ubc/pavlab/rdp/model/User.java @@ -76,17 +76,17 @@ public interface ValidationServiceAccount { @JsonIgnore private final Set roles = new HashSet<>(); - @OneToMany(cascade = CascadeType.ALL) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "user_id") @JsonIgnore private final Set accessTokens = new HashSet<>(); - @OneToMany(cascade = CascadeType.ALL) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "user_id") @JsonIgnore private final Set verificationTokens = new HashSet<>(); - @OneToMany(cascade = CascadeType.ALL) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "user_id") @JsonIgnore private final Set passwordResetTokens = new HashSet<>(); diff --git a/src/main/java/ubc/pavlab/rdp/model/VerificationToken.java b/src/main/java/ubc/pavlab/rdp/model/VerificationToken.java index dbc1c422..42af71f2 100644 --- a/src/main/java/ubc/pavlab/rdp/model/VerificationToken.java +++ b/src/main/java/ubc/pavlab/rdp/model/VerificationToken.java @@ -27,8 +27,8 @@ public class VerificationToken extends Token implements UserContent { @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; - @OneToOne(fetch = FetchType.EAGER) - @JoinColumn(nullable = false, name = "user_id") + @ManyToOne(optional = false) + @JoinColumn(name = "user_id") private User user; @Email diff --git a/src/main/java/ubc/pavlab/rdp/services/RemoteResourceService.java b/src/main/java/ubc/pavlab/rdp/services/RemoteResourceService.java index e47c639d..240c20fc 100644 --- a/src/main/java/ubc/pavlab/rdp/services/RemoteResourceService.java +++ b/src/main/java/ubc/pavlab/rdp/services/RemoteResourceService.java @@ -1,5 +1,6 @@ package ubc.pavlab.rdp.services; +import ubc.pavlab.rdp.controllers.ApiController; import ubc.pavlab.rdp.exception.RemoteException; import ubc.pavlab.rdp.model.Taxon; import ubc.pavlab.rdp.model.User; @@ -10,6 +11,7 @@ import java.net.URI; import java.util.Collection; +import java.util.Locale; import java.util.Set; import java.util.UUID; @@ -19,15 +21,47 @@ */ public interface RemoteResourceService { + /** + * Get the version of the remote API by reading its OpenAPI specification. + * + * @param remoteHost from which only the authority is used with {@link URI#getAuthority()} + * @return the API version + * @throws RemoteException if any error occured while retrieving the API version + */ String getApiVersion( URI remoteHost ) throws RemoteException; + /** + * Find users by name among all partner registries. + * + * @see ApiController#searchUsersByName(String, Boolean, Set, Set, Set, String, String, Locale) + */ Collection findUsersByLikeName( String nameLike, Boolean prefix, Set researcherPositions, Collection researcherTypes, Collection organUberonIds ); + /** + * Find users by description among all partner registries. + * + * @see ApiController#searchUsersByDescription(String, Set, Set, Set, String, String, Locale) + */ Collection findUsersByDescription( String descriptionLike, Set researcherPositions, Collection researcherTypes, Collection organUberonIds ); + /** + * Find genes by symbol among all partner registries. + * + * @see ApiController#searchUsersByGeneSymbol(String, Integer, Set, Integer, Set, Set, Set, String, String, Locale) + */ Collection findGenesBySymbol( String symbol, Taxon taxon, Set tier, Integer orthologTaxonId, Set researcherPositions, Set researcherTypes, Set organUberonIds ); + /** + * Retrieve a user from a specific registry. + * + * @see ApiController#getUserById(Integer, String, String, Locale) + */ User getRemoteUser( Integer userId, URI remoteHost ) throws RemoteException; + /** + * Retrieve an anonymized user from a specific registry. + * + * @see ApiController#getUserByAnonymousId(UUID, String, String, Locale) + */ User getAnonymizedUser( UUID anonymousId, URI remoteHost ) throws RemoteException; } diff --git a/src/main/java/ubc/pavlab/rdp/services/RemoteResourceServiceImpl.java b/src/main/java/ubc/pavlab/rdp/services/RemoteResourceServiceImpl.java index b52f77f1..2b50f62c 100644 --- a/src/main/java/ubc/pavlab/rdp/services/RemoteResourceServiceImpl.java +++ b/src/main/java/ubc/pavlab/rdp/services/RemoteResourceServiceImpl.java @@ -11,10 +11,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.web.client.AsyncRestTemplate; -import org.springframework.web.client.RestClientException; import org.springframework.web.util.UriComponentsBuilder; import ubc.pavlab.rdp.exception.RemoteException; import ubc.pavlab.rdp.model.Taxon; @@ -69,9 +66,11 @@ public String getApiVersion( URI remoteHost ) throws RemoteException { .toUri(); try { OpenAPI openAPI = asyncRestTemplate.getForEntity( uri, OpenAPI.class ).get().getBody(); - // OpenAPI specification was introduced in 1.4, so we assume 1.0.0 for previous versions + // The OpenAPI specification was introduced in 1.4, so we assume 1.0.0 for previous versions if ( openAPI.getInfo() == null ) { return "1.0.0"; + } else if ( openAPI.getInfo().getVersion().equals( "v0" ) ) { + return "1.4.0"; // the version number was missing in early 1.4 } else { return openAPI.getInfo().getVersion(); } @@ -146,14 +145,7 @@ public User getRemoteUser( Integer userId, URI remoteHost ) throws RemoteExcepti .buildAndExpand( Collections.singletonMap( "userId", userId ) ) .toUri(); - try { - ResponseEntity responseEntity = asyncRestTemplate.getForEntity( uri, User.class ).get(); - User user = responseEntity.getBody(); - initUser( user ); - return user; - } catch ( InterruptedException | ExecutionException e ) { - throw new RemoteException( MessageFormat.format( "Unsuccessful response received for {0}.", uri ), e ); - } + return getUserByUri( uri ); } @Override @@ -174,6 +166,10 @@ public User getAnonymizedUser( UUID anonymousId, URI remoteHost ) throws RemoteE .buildAndExpand( Collections.singletonMap( "anonymousId", anonymousId ) ) .toUri(); + return getUserByUri( uri ); + } + + private User getUserByUri( URI uri ) throws RemoteException { try { ResponseEntity responseEntity = asyncRestTemplate.getForEntity( uri, User.class ).get(); User user = responseEntity.getBody(); diff --git a/src/main/java/ubc/pavlab/rdp/util/VersionUtils.java b/src/main/java/ubc/pavlab/rdp/util/VersionUtils.java index 60d506f5..2b3904ea 100644 --- a/src/main/java/ubc/pavlab/rdp/util/VersionUtils.java +++ b/src/main/java/ubc/pavlab/rdp/util/VersionUtils.java @@ -1,17 +1,20 @@ package ubc.pavlab.rdp.util; +import lombok.extern.apachecommons.CommonsLog; + +@CommonsLog public class VersionUtils { private static final int[] FACTORS = new int[]{ 99 * 99 * 99, 99 * 99, 99 }; - private static int parseVersion( String version ) { + private static int parseVersion( String version ) throws IllegalArgumentException { int i = 0; int v = 0; String[] components = version.split( "\\." ); for ( String c : components ) { int ci = Integer.parseInt( c ); if ( ci < 0 || ci > 99 ) { - throw new RuntimeException( "Version component must be within 0 and 99." ); + throw new IllegalArgumentException( "Version component must be within 0 and 99." ); } v += FACTORS[i++] * ci; } diff --git a/src/main/resources/db/migration/common/V1.4.6__make_token_user_id_nullable.sql b/src/main/resources/db/migration/common/V1.4.6__make_token_user_id_nullable.sql new file mode 100644 index 00000000..7139df1e --- /dev/null +++ b/src/main/resources/db/migration/common/V1.4.6__make_token_user_id_nullable.sql @@ -0,0 +1,4 @@ +-- I'm not exactly sure why this is necessary, but Hibernate set this column to NULL before removing it +alter table access_token modify column user_id integer null; +alter table password_reset_token modify column user_id integer null; +alter table verification_token modify column user_id integer null; \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 948d7085..1fef2ed2 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -36,7 +36,7 @@ TierType.TIER1=Tier 1 TierType.TIER2=Tier 2 TierType.TIER3=Tier 3 -AbstractUserDetailsAuthenticationProvider.badCredentials=Username/Password is incorrect. +AbstractUserDetailsAuthenticationProvider.badCredentials=Your username or password is incorrect. AbstractUserDetailsAuthenticationProvider.disabled=Your account is disabled, please confirm your email. Click here to resend confirmation. AbstractUserDetailsAuthenticationProvider.expired=User account has expired. AbstractUserDetailsAuthenticationProvider.locked=User account is locked. @@ -55,6 +55,11 @@ SearchController.errorNoOrthologs=No orthologs of {0} found in # {1} contains the taxon scientific name SearchController.errorNoGene=Unknown gene {0} in taxon {1}. +UserController.profileSaved=Your profile has been saved. +# {0} contains the contact email +UserController.profileSavedAndContactEmailUpdated=Your profile has been saved. Your contact email was updated and an \ +email with a verification link has been sent to {0}. + # {0} contains the site shortname ApiConfig.title={0} RESTful API # {0} contains the site shortname diff --git a/src/main/resources/static/js/profile.js b/src/main/resources/static/js/profile.js index 20d64211..eec6218b 100644 --- a/src/main/resources/static/js/profile.js +++ b/src/main/resources/static/js/profile.js @@ -162,40 +162,45 @@ var profile = collectProfile(); var spinner = $(this).find('.spinner'); - spinner.removeClass("d-none"); + spinner.toggleClass('d-none', false); + + // reset any invalid state + $('.is-invalid').toggleClass('is-invalid', false); + $('.invalid-feedback').remove(); // noinspection JSUnusedLocalSymbols $.ajax({ type: "POST", url: window.location.href, data: JSON.stringify(profile), - contentType: "application/json", - success: function (r) { - $('.success-row').show(); - $('.error-row').hide(); - spinner.addClass("d-none"); - initialProfile = collectProfile(); - $('.new-row').removeClass("new-row"); - $('#saved-button').focus(); - // hide the verification badge - $('.contact-email-verification-badge').toggleClass('d-none', true); - }, - error: function (r) { - var errorMessages = []; - try { - $.each(r.responseJSON.errors, function (idx, item) { - errorMessages.push(item.field + " - " + item.defaultMessage); - }); - } finally { - var message = "Profile Not Saved: " + errorMessages.join(", "); - - $('.success-row').hide(); - var $error = $('.error-row'); - $error.find('div.alert').text(message); - $error.show(); - spinner.addClass("d-none"); - } + contentType: "application/json" + }).done(function (r) { + // update the initial profile to the newly saved one + initialProfile = profile; + $('#profile-success-alert-message').text(r.message); + $('#profile-success-alert').show(); + $('#profile-error-alert').hide(); + // display the verified badge if the email is verified + // hide the not verified badge and resend link if the email is verified + // sorry for the inversed logic here, the d-none class hides the tag + $('#contact-email-verified-badge').toggleClass('d-none', !r.contactEmailVerified); + $('#contact-email-not-verified-badge').toggleClass('d-none', r.contactEmailVerified); + $('#contact-email-resend-verification-email-button').toggleClass('d-none', r.contactEmailVerified); + $('#saved-button').focus(); + }).fail(function (r) { + var message = "Your profile could not be saved."; + if ('fieldErrors' in r.responseJSON) { + r.responseJSON.fieldErrors.forEach(function (fieldError) { + $('[name="' + fieldError.field + '"]') + .toggleClass('is-invalid', true) + .after($('', {'class': 'invalid-feedback text-danger d-block'}).text(fieldError.message)); + }); } + $('#profile-error-alert-message').html(message); + $('#profile-error-alert').show(); + $('#profile-success-alert').hide(); + }).always(function () { + spinner.toggleClass('d-none', true); }); }); })(); diff --git a/src/main/resources/templates/user/profile.html b/src/main/resources/templates/user/profile.html index d04b557d..af0b5280 100644 --- a/src/main/resources/templates/user/profile.html +++ b/src/main/resources/templates/user/profile.html @@ -27,16 +27,18 @@ - Profile Saved! + × - - Error Saving Profile! + + × @@ -180,14 +182,17 @@ Contact Information Verified + id="contact-email-verified-badge" + class="badge badge-success">Verified - Not verified - + Resend contact email verification diff --git a/src/main/resources/templates/userView.html b/src/main/resources/templates/userView.html index cbcf87c7..824d316d 100644 --- a/src/main/resources/templates/userView.html +++ b/src/main/resources/templates/userView.html @@ -244,7 +244,8 @@ Publications - + diff --git a/src/test/java/ubc/pavlab/rdp/controllers/AdminControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/AdminControllerTest.java index c8bed853..591944e5 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/AdminControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/AdminControllerTest.java @@ -132,8 +132,8 @@ public void givenLoggedIn_whenCreateServiceAccount_thenRedirect3xx() throws Exce return createdUser; } ); mvc.perform( post( "/admin/create-service-account" ) - .param( "profile.name", "Service Account" ) - .param( "email", "service-account" ) ) + .param( "profile.name", "Service Account" ) + .param( "email", "service-account" ) ) .andExpect( status().is3xxRedirection() ) .andExpect( redirectedUrl( "/admin/users/1" ) ); ArgumentCaptor captor = ArgumentCaptor.forClass( User.class ); @@ -178,7 +178,7 @@ public void givenNotLoggedIn_whenDeleteUser_thenReturn3xx() when( userService.findUserById( eq( 1 ) ) ).thenReturn( user ); mvc.perform( get( "/admin/users/{userId}", user.getId() ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -192,7 +192,7 @@ public void givenLoggedInAsUser_whenDeleteUser_thenReturn403() when( userService.findUserById( eq( 1 ) ) ).thenReturn( me ); mvc.perform( get( "/admin/users/{userId}", me.getId() ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isForbidden() ); } @@ -206,7 +206,7 @@ public void givenLoggedInAsAdmin_whenDeleteUser_thenSucceed() when( userService.findUserById( eq( 1 ) ) ).thenReturn( me ); mvc.perform( delete( "/admin/users/{userId}", me.getId() ) - .param( "email", me.getEmail() ) ) + .param( "email", me.getEmail() ) ) .andExpect( status().is3xxRedirection() ) .andExpect( redirectedUrl( "/admin/users" ) ); @@ -222,7 +222,7 @@ public void givenLoggedInAsAdmin_whenDeleteUserWithWrongConfirmationEmail_thenRe when( userService.findUserById( eq( 1 ) ) ).thenReturn( me ); mvc.perform( delete( "/admin/users/{userId}", me.getId() ) - .param( "email", "123@example.com" ) ) + .param( "email", "123@example.com" ) ) .andExpect( status().isBadRequest() ) .andExpect( view().name( "admin/user" ) ); diff --git a/src/test/java/ubc/pavlab/rdp/controllers/ApiControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/ApiControllerTest.java index 5bd6ad76..3f1b4ec6 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/ApiControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/ApiControllerTest.java @@ -88,10 +88,10 @@ public void setUp() { public void searchGenes_withSearchDisabled_thenReturnServiceUnavailable() throws Exception { when( iSearchSettings.isEnabled() ).thenReturn( false ); mvc.perform( get( "/api/genes/search" ) - .header( "Authorization", "Bearer 1234" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER1" ) ) + .header( "Authorization", "Bearer 1234" ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER1" ) ) .andExpect( status().isServiceUnavailable() ); } @@ -112,10 +112,10 @@ public void searchGenes_withAuthToken_thenReturnSuccess() throws Exception { .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .header( "Authorization", "Bearer 1234" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER1" ) ) + .header( "Authorization", "Bearer 1234" ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER1" ) ) .andExpect( status().is2xxSuccessful() ); verify( userService ).getRemoteAdmin(); @@ -138,10 +138,10 @@ public void searchGenes_withAuthTokenInQuery_thenReturnSuccess() throws Exceptio .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER1" ) - .param( "auth", "1234" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER1" ) + .param( "auth", "1234" ) ) .andExpect( status().is2xxSuccessful() ); verify( userService ).getRemoteAdmin(); @@ -150,20 +150,20 @@ public void searchGenes_withAuthTokenInQuery_thenReturnSuccess() throws Exceptio @Test public void searchGenes_withInvalidAuthToken_thenReturnUnauthorized() throws Exception { mvc.perform( get( "/api/genes/search" ) - .header( "Authorization", "Bearer unknownToken" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER1" ) ) + .header( "Authorization", "Bearer unknownToken" ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER1" ) ) .andExpect( status().isUnauthorized() ); } @Test public void searchGenes_withInvalidAuthToken_thenReturnBadRequest() throws Exception { mvc.perform( get( "/api/genes/search" ) - .header( "Authorization", "Basic unknownToken" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER1" ) ) + .header( "Authorization", "Basic unknownToken" ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER1" ) ) .andExpect( status().isBadRequest() ); } @@ -171,10 +171,10 @@ public void searchGenes_withInvalidAuthToken_thenReturnBadRequest() throws Excep public void searchGenes_whenMisconfiguredRemoteAdmin_thenReturnServiceUnavailable() throws Exception { when( iSearchSettings.getAuthTokens() ).thenReturn( Collections.singletonList( "1234" ) ); mvc.perform( get( "/api/genes/search" ) - .header( "Authorization", "Bearer 1234" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER1" ) ) + .header( "Authorization", "Bearer 1234" ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER1" ) ) .andExpect( status().isServiceUnavailable() ); } @@ -191,9 +191,9 @@ public void searchGenes_withSingleTier_thenReturn200() throws Exception { .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER1" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER1" ) ) .andExpect( status().is2xxSuccessful() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ); @@ -213,10 +213,10 @@ public void searchGenes_withMultipleTiers_thenReturn200() throws Exception { .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tiers", "TIER1" ) - .param( "tiers", "TIER2" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tiers", "TIER1" ) + .param( "tiers", "TIER2" ) ) .andExpect( status().is2xxSuccessful() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ); @@ -236,11 +236,11 @@ public void searchGenes_withMultipleTiersIncludingTier3_thenReturn200() throws E .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tiers", "TIER1" ) - .param( "tiers", "TIER2" ) - .param( "tiers", "TIER3" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tiers", "TIER1" ) + .param( "tiers", "TIER2" ) + .param( "tiers", "TIER3" ) ) .andExpect( status().is2xxSuccessful() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ); @@ -265,8 +265,8 @@ public void searchGenes_withNoTiers_thenReturn200() throws Exception { .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) ) .andExpect( status().is2xxSuccessful() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ); @@ -286,9 +286,9 @@ public void searchGenes_withTiers12_thenReturn200() throws Exception { .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIERS1_2" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIERS1_2" ) ) .andExpect( status().is2xxSuccessful() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ); @@ -308,9 +308,9 @@ public void searchGenes_withTierAny_thenReturn200() throws Exception { .thenReturn( Sets.newSet( cdh1UserGene ) ); mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "ANY" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "ANY" ) ) .andExpect( status().is2xxSuccessful() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ); @@ -320,9 +320,9 @@ public void searchGenes_withTierAny_thenReturn200() throws Exception { @Test public void searchGenes_withInvalidTier_thenReturnBadRequest() throws Exception { mvc.perform( get( "/api/genes/search" ) - .param( "symbol", "CDH1" ) - .param( "taxonId", "9606" ) - .param( "tier", "TIER4" ) ) + .param( "symbol", "CDH1" ) + .param( "taxonId", "9606" ) + .param( "tier", "TIER4" ) ) .andExpect( status().isBadRequest() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ); } diff --git a/src/test/java/ubc/pavlab/rdp/controllers/GeneControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/GeneControllerTest.java index 47a059b6..7e7d6ddd 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/GeneControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/GeneControllerTest.java @@ -63,7 +63,7 @@ public void searchGenesByTaxonAndSymbols_thenReturnMatchingGenes() throws Except when( taxonService.findById( 1 ) ).thenReturn( taxon ); when( geneService.findBySymbolAndTaxon( "BRCA1", taxon ) ).thenReturn( gene ); mvc.perform( get( "/taxon/{taxonId}/gene/search", 1 ) - .param( "symbols", "BRCA1" ) ) + .param( "symbols", "BRCA1" ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$.BRCA1" ).value( gene ) ); verify( geneService ).findBySymbolAndTaxon( "BRCA1", taxon ); @@ -76,8 +76,8 @@ public void searchGenesByTaxonAndTerm_thenAutocompleteGenes() throws Exception { when( taxonService.findById( 1 ) ).thenReturn( taxon ); when( geneService.autocomplete( "BRCA1", taxon, 10 ) ).thenReturn( Collections.singleton( new SearchResult( GeneMatchType.EXACT_SYMBOL, gene ) ) ); mvc.perform( get( "/taxon/{taxonId}/gene/search", 1 ) - .param( "query", "BRCA1" ) - .param( "max", "10" ) ) + .param( "query", "BRCA1" ) + .param( "max", "10" ) ) .andExpect( status().isOk() ) .andExpect( content().contentTypeCompatibleWith( MediaType.APPLICATION_JSON ) ) .andExpect( jsonPath( "$[0].matchType" ).value( "Exact Symbol" ) ) diff --git a/src/test/java/ubc/pavlab/rdp/controllers/LoginControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/LoginControllerTest.java index 1ea1fa18..aedffb7c 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/LoginControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/LoginControllerTest.java @@ -1,11 +1,14 @@ package ubc.pavlab.rdp.controllers; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.core.convert.converter.Converter; +import org.springframework.format.support.FormattingConversionService; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -14,7 +17,9 @@ import org.springframework.test.web.servlet.MockMvc; import ubc.pavlab.rdp.WebSecurityConfig; import ubc.pavlab.rdp.exception.TokenException; +import ubc.pavlab.rdp.model.Profile; import ubc.pavlab.rdp.model.User; +import ubc.pavlab.rdp.model.enums.PrivacyLevelType; import ubc.pavlab.rdp.services.PrivacyService; import ubc.pavlab.rdp.services.UserService; import ubc.pavlab.rdp.settings.ApplicationSettings; @@ -62,6 +67,15 @@ public class LoginControllerTest { @MockBean private PermissionEvaluator permissionEvaluator; + @Autowired + private FormattingConversionService formattingConversionService; + + @Before + public void setUp() { + when( applicationSettings.getPrivacy() ).thenReturn( privacySettings ); + when( privacyService.getDefaultPrivacyLevel() ).thenReturn( PrivacyLevelType.PRIVATE ); + } + @Test public void login_thenReturnSuccess() throws Exception { mvc.perform( get( "/login" ) ) @@ -96,6 +110,37 @@ public void register_thenReturnSuccess() throws Exception { when( userService.create( any() ) ).thenAnswer( answer -> answer.getArgumentAt( 0, User.class ) ); } + @Test + public void register_whenEmailIsUsedButNotEnabled_thenResendConfirmation() throws Exception { + User user = User.builder() + .email( "foo@example.com" ) + .enabled( false ) + .profile( new Profile() ) + .build(); + when( userService.findUserByEmailNoAuth( "foo@example.com" ) ).thenReturn( user ); + + //noinspection Convert2Lambda + formattingConversionService.addConverter( new Converter() { + @Override + public User convert( Object o ) { + return User.builder() + .profile( Profile.builder().name( "Foo" ).build() ) + .email( "foo@example.com" ) + .build(); + } + } ); + + mvc.perform( post( "/registration" ) + .locale( Locale.getDefault() ) + .param( "user.profile.name", "Foo" ) + .param( "user.email", "foo@example.com" ) ) + .andExpect( status().isBadRequest() ) + .andExpect( view().name( "registration" ) ) + .andExpect( model().attribute( "user", new User() ) ); + + verify( userService ).createVerificationTokenForUser( user, Locale.getDefault() ); + } + @Test public void resendConfirmation_thenReturnSuccess() throws Exception { mvc.perform( get( "/resendConfirmation" ) @@ -122,7 +167,7 @@ public void registrationConfirm_thenReturnSuccess() throws Exception { User user = createUser( 1 ); when( userService.confirmVerificationToken( "1234" ) ).thenReturn( user ); mvc.perform( get( "/registrationConfirm" ) - .param( "token", "1234" ) ) + .param( "token", "1234" ) ) .andExpect( status().is3xxRedirection() ) .andExpect( redirectedUrl( "/login" ) ) .andExpect( flash().attributeExists( "message" ) ); @@ -132,7 +177,7 @@ public void registrationConfirm_thenReturnSuccess() throws Exception { public void registrationConfirm_whenTokenDoesNotExist_thenReturnError() throws Exception { when( userService.confirmVerificationToken( "1234" ) ).thenThrow( TokenException.class ); mvc.perform( get( "/registrationConfirm" ) - .param( "token", "1234" ) ) + .param( "token", "1234" ) ) .andExpect( status().isNotFound() ) .andExpect( view().name( "error/404" ) ); } diff --git a/src/test/java/ubc/pavlab/rdp/controllers/PasswordControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/PasswordControllerTest.java index 1fdfeaaa..fe1d5c1f 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/PasswordControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/PasswordControllerTest.java @@ -93,7 +93,7 @@ public void forgotPassword_thenReturnSuccess() throws Exception { public void forgotPassword_whenUserDoesNotExist_thenReturnNotFound() throws Exception { when( userService.findUserByEmailNoAuth( "foo@example.com" ) ).thenReturn( null ); mvc.perform( post( "/forgotPassword" ) - .param( "email", "foo@example.com" ) ) + .param( "email", "foo@example.com" ) ) .andExpect( status().isNotFound() ) .andExpect( view().name( "forgotPassword" ) ); } @@ -103,18 +103,18 @@ public void updatePassword_thenReturnSuccess() throws Exception { User user = createUser( 1 ); when( userService.findUserByIdNoAuth( 1 ) ).thenReturn( user ); mvc.perform( get( "/updatePassword" ) - .param( "id", "1" ) - .param( "token", "1234" ) ) + .param( "id", "1" ) + .param( "token", "1234" ) ) .andExpect( status().isOk() ) .andExpect( view().name( "updatePassword" ) ); verify( userService ).verifyPasswordResetToken( 1, "1234" ); mvc.perform( post( "/updatePassword" ) - .param( "id", "1" ) - .param( "token", "1234" ) - .param( "newPassword", "123456" ) - .param( "passwordConfirm", "123456" ) ) + .param( "id", "1" ) + .param( "token", "1234" ) + .param( "newPassword", "123456" ) + .param( "passwordConfirm", "123456" ) ) .andExpect( status().is3xxRedirection() ) .andExpect( redirectedUrl( "/user/home" ) ); @@ -124,10 +124,10 @@ public void updatePassword_thenReturnSuccess() throws Exception { @Test public void changePasswordByResetToken_whenNewPasswordIsTooShort_thenThrowValidationException() throws Exception { mvc.perform( post( "/updatePassword" ) - .param( "id", "1" ) - .param( "token", "1234" ) - .param( "newPassword", "12345" ) - .param( "passwordConfirm", "12345" ) ) + .param( "id", "1" ) + .param( "token", "1234" ) + .param( "newPassword", "12345" ) + .param( "passwordConfirm", "12345" ) ) .andExpect( status().isBadRequest() ) .andExpect( view().name( "updatePassword" ) ); } diff --git a/src/test/java/ubc/pavlab/rdp/controllers/SearchControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/SearchControllerTest.java index 249d5a20..878f73e1 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/SearchControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/SearchControllerTest.java @@ -136,9 +136,9 @@ public void getSearch_withoutPublicSearch_redirect3xx() throws Exception { public void getSearch_ByNameLike_return200() throws Exception { when( permissionEvaluator.hasPermission( any(), isNull(), eq( "search" ) ) ).thenReturn( true ); mvc.perform( get( "/search" ) - .param( "nameLike", "K" ) - .param( "prefix", "true" ) - .param( "iSearch", "false" ) ) + .param( "nameLike", "K" ) + .param( "prefix", "true" ) + .param( "iSearch", "false" ) ) .andExpect( status().isOk() ) .andExpect( view().name( "search" ) ) .andExpect( model().attributeExists( "users" ) ); @@ -148,8 +148,8 @@ public void getSearch_ByNameLike_return200() throws Exception { public void getSearch_ByDescriptionLike_return200() throws Exception { when( permissionEvaluator.hasPermission( any(), isNull(), eq( "search" ) ) ).thenReturn( true ); mvc.perform( get( "/search" ) - .param( "descriptionLike", "pancake" ) - .param( "iSearch", "false" ) ) + .param( "descriptionLike", "pancake" ) + .param( "iSearch", "false" ) ) .andExpect( status().isOk() ) .andExpect( view().name( "search" ) ) .andExpect( model().attributeExists( "users" ) ); @@ -163,9 +163,9 @@ public void getSearch_ByGeneSymbol_return200() throws Exception { when( taxonService.findById( 9606 ) ).thenReturn( humanTaxon ); when( geneService.findBySymbolAndTaxon( "BRCA1", humanTaxon ) ).thenReturn( gene ); mvc.perform( get( "/search" ) - .param( "symbol", "BRCA1" ) - .param( "taxonId", "9606" ) - .param( "iSearch", "false" ) ) + .param( "symbol", "BRCA1" ) + .param( "taxonId", "9606" ) + .param( "iSearch", "false" ) ) .andExpect( status().isOk() ) .andExpect( view().name( "search" ) ) .andExpect( model().attributeExists( "usergenes" ) ); @@ -197,7 +197,7 @@ public void viewUser_whenUserIsRemote_thenReturnSuccess() throws Exception { when( remoteResourceService.getRemoteUser( user.getId(), URI.create( "example.com" ) ) ).thenReturn( user ); when( privacyService.checkCurrentUserCanSearch( true ) ).thenReturn( true ); mvc.perform( get( "/userView/{userId}", user.getId() ) - .param( "remoteHost", "example.com" ) ) + .param( "remoteHost", "example.com" ) ) .andExpect( status().isOk() ) .andExpect( view().name( "userView" ) ); verify( remoteResourceService ).getRemoteUser( user.getId(), URI.create( "example.com" ) ); @@ -208,7 +208,7 @@ public void viewUser_whenRemoteUserIsNotFound_thenReturnNotFound() throws Except when( remoteResourceService.getRemoteUser( 1, URI.create( "example.com" ) ) ).thenReturn( null ); when( privacyService.checkCurrentUserCanSearch( true ) ).thenReturn( true ); mvc.perform( get( "/userView/{userId}", 1 ) - .param( "remoteHost", "example.com" ) ) + .param( "remoteHost", "example.com" ) ) .andExpect( status().isNotFound() ); verify( remoteResourceService ).getRemoteUser( 1, URI.create( "example.com" ) ); } @@ -218,7 +218,7 @@ public void viewUser_whenRemoteIsUnavailable_thenReturnNotFound() throws Excepti when( remoteResourceService.getRemoteUser( 1, URI.create( "example.com" ) ) ).thenThrow( RemoteException.class ); when( privacyService.checkCurrentUserCanSearch( true ) ).thenReturn( true ); mvc.perform( get( "/userView/{userId}", 1 ) - .param( "remoteHost", "example.com" ) ) + .param( "remoteHost", "example.com" ) ) .andExpect( status().isServiceUnavailable() ); verify( remoteResourceService ).getRemoteUser( 1, URI.create( "example.com" ) ); } @@ -230,8 +230,8 @@ public void searchItlUsersByNameView_thenReturnSuccess() throws Exception { when( remoteResourceService.findUsersByLikeName( "Mark", true, null, null, null ) ) .thenReturn( Collections.singleton( user ) ); mvc.perform( get( "/search/view/international" ) - .param( "nameLike", "Mark" ) - .param( "prefix", "true" ) ) + .param( "nameLike", "Mark" ) + .param( "prefix", "true" ) ) .andExpect( status().isOk() ) .andExpect( view().name( "fragments/user-table::user-table" ) ); } @@ -242,7 +242,7 @@ public void viewUser_whenRemoteUserCannotBeRetrieved_thenReturnNotFound() throws when( remoteResourceService.getRemoteUser( user.getId(), URI.create( "example.com" ) ) ).thenThrow( RemoteException.class ); when( privacyService.checkCurrentUserCanSearch( true ) ).thenReturn( true ); mvc.perform( get( "/userView/{userId}", user.getId() ) - .param( "remoteHost", "example.com" ) ) + .param( "remoteHost", "example.com" ) ) .andExpect( status().isServiceUnavailable() ); verify( remoteResourceService ).getRemoteUser( user.getId(), URI.create( "example.com" ) ); } @@ -273,7 +273,7 @@ public void previewUser_whenUserIsRemote_thenReturnSuccess() throws Exception { when( userService.findUserById( 1 ) ).thenReturn( user ); when( remoteResourceService.getRemoteUser( 1, URI.create( "http://localhost/" ) ) ).thenReturn( user ); mvc.perform( get( "/search/view/user-preview/{userId}", user.getId() ) - .param( "remoteHost", "http://localhost/" ) ) + .param( "remoteHost", "http://localhost/" ) ) .andExpect( status().isOk() ); verify( remoteResourceService ).getRemoteUser( 1, URI.create( "http://localhost/" ) ); } @@ -312,7 +312,7 @@ public void previewAnonymousUser_whenUserIsRemote_thenReturnSuccess() throws Exc when( remoteResourceService.getApiVersion( URI.create( "http://localhost/" ) ) ).thenReturn( "1.4.0" ); when( remoteResourceService.getAnonymizedUser( anonymousUser.getAnonymousId(), URI.create( "http://localhost/" ) ) ).thenReturn( anonymousUser ); mvc.perform( get( "/search/view/user-preview/by-anonymous-id/{anonymousId}", anonymousUser.getAnonymousId() ) - .param( "remoteHost", "http://localhost/" ) ) + .param( "remoteHost", "http://localhost/" ) ) .andExpect( status().isOk() ) .andExpect( view().name( "fragments/profile::user-preview" ) ) .andExpect( model().attribute( "user", anonymousUser ) ); @@ -325,7 +325,7 @@ public void previewAnonymousUser_whenUserIsRemoteAndApiVersionIsPre14_thenReturn anonymousUser.getProfile().getResearcherCategories().add( ResearcherCategory.IN_SILICO ); when( remoteResourceService.getApiVersion( URI.create( "http://localhost/" ) ) ).thenReturn( "1.0.0" ); mvc.perform( get( "/search/view/user-preview/by-anonymous-id/{anonymousId}", anonymousUser.getAnonymousId() ) - .param( "remoteHost", "http://localhost/" ) ) + .param( "remoteHost", "http://localhost/" ) ) .andExpect( status().isNotFound() ) .andExpect( view().name( "fragments/error::message" ) ); } diff --git a/src/test/java/ubc/pavlab/rdp/controllers/TermControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/TermControllerTest.java index 3ed100b2..31cc2f80 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/TermControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/TermControllerTest.java @@ -53,8 +53,8 @@ public void searchTermsByQueryAndTaxon_thenReturnMatchingTerms() throws Exceptio GeneOntologyTerm term = createTerm( "GO:0000001" ); when( goService.search( "GO:0000001", taxon, 10 ) ).thenReturn( Collections.singletonList( new SearchResult( TermMatchType.EXACT_ID, term ) ) ); mvc.perform( get( "/taxon/1/term/search" ) - .param( "query", "GO:0000001" ) - .param( "max", "10" ) ) + .param( "query", "GO:0000001" ) + .param( "max", "10" ) ) .andExpect( jsonPath( "$[0].matchType" ).value( "Exact Id" ) ) .andExpect( jsonPath( "$[0].match" ).value( term ) ); } diff --git a/src/test/java/ubc/pavlab/rdp/controllers/UserControllerTest.java b/src/test/java/ubc/pavlab/rdp/controllers/UserControllerTest.java index c234f10d..8cfa928c 100644 --- a/src/test/java/ubc/pavlab/rdp/controllers/UserControllerTest.java +++ b/src/test/java/ubc/pavlab/rdp/controllers/UserControllerTest.java @@ -127,6 +127,7 @@ public void setUp() { when( applicationSettings.getOrgans() ).thenReturn( organSettings ); when( applicationSettings.getIsearch() ).thenReturn( iSearchSettings ); when( taxonService.findById( any() ) ).then( i -> createTaxon( i.getArgumentAt( 0, Integer.class ) ) ); + when( userService.updateUserProfileAndPublicationsAndOrgans( any(), any(), any(), any(), any() ) ).thenAnswer( arg -> arg.getArgumentAt( 0, User.class ) ); } @Test @@ -207,9 +208,9 @@ public void contactSupport_thenReturnSuccess() throws Exception { .andExpect( view().name( "user/support" ) ); mvc.perform( post( "/user/support" ) - .param( "name", "John Doe" ) - .param( "message", "Is everything okay?" ) - .locale( Locale.ENGLISH ) ) + .param( "name", "John Doe" ) + .param( "message", "Is everything okay?" ) + .locale( Locale.ENGLISH ) ) .andExpect( status().isOk() ) .andExpect( view().name( "user/support" ) ); @@ -225,7 +226,7 @@ public void givenNotLoggedIn_whenGetUser_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -239,7 +240,7 @@ public void givenLoggedIn_whenGetUser_thenReturnJson() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$.email" ).value( user.getEmail() ) ) .andExpect( jsonPath( "$.password" ).doesNotExist() ); @@ -254,7 +255,7 @@ public void givenNotLoggedIn_whenGetUserTaxons_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -270,7 +271,7 @@ public void givenLoggedIn_whenGetUserTaxons_thenReturnJson() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$[0].id" ).value( taxon.getId() ) ) .andExpect( jsonPath( "$[0].commonName" ).value( taxon.getCommonName() ) ); @@ -285,7 +286,7 @@ public void givenNotLoggedIn_whenGetUserGenes_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/gene" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -304,7 +305,7 @@ public void givenLoggedIn_whenGetUserGenes_thenReturnJson() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/gene" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$[0].geneId" ).value( gene.getGeneId() ) ) .andExpect( jsonPath( "$[0].symbol" ).value( gene.getSymbol() ) ) @@ -321,7 +322,7 @@ public void givenNotLoggedIn_whenGetUserTerms_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/term" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -341,7 +342,7 @@ public void givenLoggedIn_whenGetUserTerms_thenReturnJson() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/term" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$[0].goId" ).value( term.getGoId() ) ) .andExpect( jsonPath( "$[0].name" ).value( term.getName() ) ) @@ -358,7 +359,7 @@ public void givenNotLoggedIn_whenGetUserGenesInTaxon_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon/9606/gene" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -382,7 +383,7 @@ public void givenLoggedIn_whenGetUserGenesInTaxon_thenReturnJson() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon/1/gene" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$[0].geneId" ).value( gene.getGeneId() ) ) .andExpect( jsonPath( "$[0].symbol" ).value( gene.getSymbol() ) ) @@ -390,7 +391,7 @@ public void givenLoggedIn_whenGetUserGenesInTaxon_thenReturnJson() .andExpect( jsonPath( "$[0].taxon.id" ).value( taxon.getId() ) ); mvc.perform( get( "/user/taxon/2/gene" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$[0].geneId" ).value( gene2.getGeneId() ) ) .andExpect( jsonPath( "$[0].symbol" ).value( gene2.getSymbol() ) ) @@ -407,7 +408,7 @@ public void givenNotLoggedIn_whenGetUserTermsInTaxon_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon/9606/term" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -433,7 +434,7 @@ public void givenLoggedIn_whenGetUserTermsInTaxon_thenReturnJson() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon/1/term" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$[0].goId" ).value( term.getGoId() ) ) .andExpect( jsonPath( "$[0].name" ).value( term.getName() ) ) @@ -442,7 +443,7 @@ public void givenLoggedIn_whenGetUserTermsInTaxon_thenReturnJson() .andExpect( jsonPath( "$[0].taxon.id" ).value( taxon.getId() ) ); mvc.perform( get( "/user/taxon/2/term" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$[0].goId" ).value( term2.getGoId() ) ) .andExpect( jsonPath( "$[0].name" ).value( term2.getName() ) ) @@ -460,7 +461,7 @@ public void givenNotLoggedIn_whenRecommendTerms_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon/1/term/recommend" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().is3xxRedirection() ); } @@ -485,14 +486,14 @@ public void givenLoggedIn_whenRecommendTerms_thenReturnJson() when( userService.recommendTerms( any(), eq( taxon2 ) ) ).thenReturn( Sets.newSet( t3, t4 ) ); mvc.perform( get( "/user/taxon/1/term/recommend" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$", hasSize( 2 ) ) ) .andExpect( jsonPath( "$[*].goId" ).value( containsInAnyOrder( t1.getGoId(), t2.getGoId() ) ) ) .andExpect( jsonPath( "$[*].taxon.id" ).value( contains( taxon.getId(), taxon.getId() ) ) ); mvc.perform( get( "/user/taxon/2/term/recommend" ) - .contentType( MediaType.APPLICATION_JSON ) ) + .contentType( MediaType.APPLICATION_JSON ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$" ).value( hasSize( 2 ) ) ) .andExpect( jsonPath( "$[*].goId" ).value( containsInAnyOrder( t3.getGoId(), t4.getGoId() ) ) ) @@ -510,8 +511,8 @@ public void givenNotLoggedIn_whenSaveProfile_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( post( "/user/profile" ) - .contentType( MediaType.APPLICATION_JSON ) - .content( "" ) ) + .contentType( MediaType.APPLICATION_JSON ) + .content( "" ) ) .andExpect( status().is3xxRedirection() ); } @@ -548,9 +549,9 @@ public void givenLoggedIn_whenSaveProfile_thenReturnSucceed() payload.setProfile( updatedProfile ); mvc.perform( post( "/user/profile" ) - .contentType( MediaType.APPLICATION_JSON ) - .content( objectMapper.writeValueAsString( payload ) ) - .locale( Locale.ENGLISH ) ) + .contentType( MediaType.APPLICATION_JSON ) + .content( objectMapper.writeValueAsString( payload ) ) + .locale( Locale.ENGLISH ) ) .andExpect( status().isOk() ); verify( userService ).updateUserProfileAndPublicationsAndOrgans( user, updatedProfile, updatedProfile.getPublications(), null, Locale.ENGLISH ); @@ -570,8 +571,8 @@ public void givenLoggedIn_whenSaveProfileAndInvalidWebsite_thenReturn400() payload.put( "profile", profileJson ); mvc.perform( post( "/user/profile" ) - .contentType( MediaType.APPLICATION_JSON ) - .content( payload.toString() ) ) + .contentType( MediaType.APPLICATION_JSON ) + .content( payload.toString() ) ) .andExpect( status().isBadRequest() ); } @@ -594,9 +595,9 @@ public void givenLoggedIn_whenSaveProfileWithNewPrivacyLevel_thenReturn200() payload.put( "profile", profileJson ); mvc.perform( post( "/user/profile" ) - .contentType( MediaType.APPLICATION_JSON ) - .content( payload.toString() ) - .locale( Locale.ENGLISH ) ) + .contentType( MediaType.APPLICATION_JSON ) + .content( payload.toString() ) + .locale( Locale.ENGLISH ) ) .andExpect( status().isOk() ); Profile profile = user.getProfile(); @@ -625,9 +626,9 @@ public void givenLoggedIn_whenSaveProfileWithUberonOrganIds_thenReturnSuccess() payload.put( "organUberonIds", new JSONArray( new String[]{ organ.getUberonId() } ) ); mvc.perform( post( "/user/profile" ) - .contentType( MediaType.APPLICATION_JSON ) - .content( payload.toString() ) - .locale( Locale.ENGLISH ) ) + .contentType( MediaType.APPLICATION_JSON ) + .content( payload.toString() ) + .locale( Locale.ENGLISH ) ) .andExpect( status().isOk() ); verify( userService ).updateUserProfileAndPublicationsAndOrgans( user, user.getProfile(), user.getProfile().getPublications(), Sets.newSet( organ.getUberonId() ), Locale.ENGLISH ); @@ -642,8 +643,8 @@ public void givenNotLoggedIn_whenSearchTermsInTaxon_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( post( "/user/taxon/1/term/search" ) - .contentType( MediaType.APPLICATION_JSON ) - .content( "" ) ) + .contentType( MediaType.APPLICATION_JSON ) + .content( "" ) ) .andExpect( status().is3xxRedirection() ); } @@ -662,9 +663,9 @@ public void givenLoggedIn_whenSearchTermsInTaxon_thenReturnJson() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( get( "/user/taxon/1/term/search" ) - .param( "goIds", toGOId( 1 ) ) - .param( "goIds", toGOId( 2 ) ) - .param( "goIds", toGOId( 3 ) ) ) + .param( "goIds", toGOId( 1 ) ) + .param( "goIds", toGOId( 2 ) ) + .param( "goIds", toGOId( 3 ) ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$['GO:0000001'].goId" ).value( toGOId( 1 ) ) ) .andExpect( jsonPath( "$['GO:0000002'].goId" ).value( toGOId( 2 ) ) ) @@ -674,9 +675,9 @@ public void givenLoggedIn_whenSearchTermsInTaxon_thenReturnJson() .andExpect( jsonPath( "$['GO:0000003'].taxon.id" ).value( taxon.getId() ) ); mvc.perform( get( "/user/taxon/2/term/search" ) - .param( "goIds", toGOId( 1 ) ) - .param( "goIds", toGOId( 2 ) ) - .param( "goIds", toGOId( 3 ) ) ) + .param( "goIds", toGOId( 1 ) ) + .param( "goIds", toGOId( 2 ) ) + .param( "goIds", toGOId( 3 ) ) ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$['GO:0000001']" ).isEmpty() ) .andExpect( jsonPath( "$['GO:0000002']" ).isEmpty() ) @@ -692,8 +693,8 @@ public void givenNotLoggedIn_whenSaveModel_thenReturn3xx() when( userService.findCurrentUser() ).thenReturn( user ); mvc.perform( post( "/user/model/1" ) - .contentType( MediaType.APPLICATION_JSON ) - .content( "" ) ) + .contentType( MediaType.APPLICATION_JSON ) + .content( "" ) ) .andExpect( status().is3xxRedirection() ); } @@ -719,7 +720,7 @@ public void givenLoggedIn_whenVerifyContactEmailWithToken_andTokenDoesNotExist_t throw new TokenException( "Verification token does not exist." ); } ); mvc.perform( get( "/user/verify-contact-email" ) - .param( "token", "1234" ) ) + .param( "token", "1234" ) ) .andExpect( status().is3xxRedirection() ) .andExpect( flash().attributeExists( "message" ) ) .andExpect( flash().attribute( "error", true ) ); @@ -740,4 +741,20 @@ public void givenLoggedIn_whenResendContactEmailVerification_thenRedirect3xx() t .andExpect( flash().attributeExists( "message" ) ); verify( userService ).createContactEmailVerificationTokenForUser( user, Locale.getDefault() ); } + + @Test + @WithMockUser + public void saveProfile_whenInvalidUrlIsProvided_thenReturnBadRequestWithMeaningfulValidationMessage() throws Exception { + User user = createUser( 1 ); + when( userService.findCurrentUser() ).thenReturn( user ); + Profile updatedProfile = new Profile(); + updatedProfile.setWebsite( "bad-url" ); + UserController.ProfileWithOrganUberonIds profileWithOrganUberonIds = new UserController.ProfileWithOrganUberonIds( updatedProfile, null ); + mvc.perform( post( "/user/profile" ) + .contentType( MediaType.APPLICATION_JSON ) + .content( objectMapper.writeValueAsString( profileWithOrganUberonIds ) ) ) + .andExpect( status().isBadRequest() ) + .andExpect( jsonPath( "$.fieldErrors[0].field" ).value( "profile.website" ) ) + .andExpect( jsonPath( "$.fieldErrors[0].rejectedValue" ).value( "bad-url" ) ); + } } diff --git a/src/test/java/ubc/pavlab/rdp/repositories/UserRepositoryTest.java b/src/test/java/ubc/pavlab/rdp/repositories/UserRepositoryTest.java index 076d42c2..3c8dea10 100644 --- a/src/test/java/ubc/pavlab/rdp/repositories/UserRepositoryTest.java +++ b/src/test/java/ubc/pavlab/rdp/repositories/UserRepositoryTest.java @@ -31,6 +31,9 @@ public class UserRepositoryTest { @Autowired private UserRepository userRepository; + @Autowired + private RoleRepository roleRepository; + @Test public void findByEmail_whenMatch_thenReturnUser() { // given @@ -480,18 +483,56 @@ public void save_whenUserHasMultipleResearcherCategories_thenSucceed() { @Test public void delete_whenVerificationToken_thenSucceed() { User user = createUnpersistedUser(); - user = entityManager.persist( user ); + user = entityManager.persistAndFlush( user ); - VerificationToken token = entityManager.persist( createVerificationToken( user, "token123" ) ); + VerificationToken token = entityManager.persistAndFlush( createVerificationToken( user, "token123" ) ); Taxon humanTaxon = entityManager.find( Taxon.class, 9606 ); Gene gene = createGene( 1, humanTaxon ); UserGene userGene = entityManager.persist( createUnpersistedUserGene( gene, user, TierType.TIER1, PrivacyLevelType.PRIVATE ) ); + entityManager.persistAndFlush( user ); + entityManager.refresh( user ); assertThat( user.getVerificationTokens() ).contains( token ); assertThat( user.getUserGenes().values() ).contains( userGene ); userRepository.delete( user ); + userRepository.flush(); + } + + @Autowired + private VerificationTokenRepository accessTokenRepository; + + @Test + public void delete_whenUserHasMultipleAssociations_thenSucceed() { + User user = createUnpersistedUser(); + user = entityManager.persist( user ); + + VerificationToken token = entityManager.persistAndFlush( createVerificationToken( user, "token123" ) ); + Taxon humanTaxon = entityManager.find( Taxon.class, 9606 ); + Gene gene = createGene( 1, humanTaxon ); + UserGene userGene = entityManager.persist( createUnpersistedUserGene( gene, user, TierType.TIER1, PrivacyLevelType.PRIVATE ) ); + OrganInfo organInfo = new OrganInfo(); + organInfo.setUberonId( "UBERON:00001" ); + UserOrgan userOrgan = entityManager.persistAndFlush( createUserOrgan( user, organInfo ) ); + + user.getVerificationTokens().add( token ); + user.getRoles().add( roleRepository.findByRole( "ROLE_USER" ) ); + user.getProfile().getResearcherCategories().add( ResearcherCategory.IN_SILICO ); + user.getUserOrgans().put( userOrgan.getUberonId(), userOrgan ); + user.getTaxonDescriptions().put( humanTaxon, "I'm a human researcher." ); + entityManager.persistAndFlush( user ); + + entityManager.refresh( user ); + assertThat( user.getVerificationTokens() ).contains( token ); + assertThat( user.getUserGenes().values() ).contains( userGene ); + assertThat( user.getUserOrgans().values() ).contains( userOrgan ); + + userRepository.delete( user ); + userRepository.flush(); + + // make sure that the token is not lingering + assertThat( accessTokenRepository.exists( token.getId() ) ).isFalse(); } } diff --git a/src/test/java/ubc/pavlab/rdp/services/GOServiceIntegrationTest.java b/src/test/java/ubc/pavlab/rdp/services/GOServiceIntegrationTest.java deleted file mode 100644 index c02805c4..00000000 --- a/src/test/java/ubc/pavlab/rdp/services/GOServiceIntegrationTest.java +++ /dev/null @@ -1,2 +0,0 @@ -package ubc.pavlab.rdp.services;public class GOServiceIntegrationTest { -} diff --git a/src/test/java/ubc/pavlab/rdp/services/RemoteResourceServiceTest.java b/src/test/java/ubc/pavlab/rdp/services/RemoteResourceServiceTest.java index 00347844..bcd90646 100644 --- a/src/test/java/ubc/pavlab/rdp/services/RemoteResourceServiceTest.java +++ b/src/test/java/ubc/pavlab/rdp/services/RemoteResourceServiceTest.java @@ -26,6 +26,7 @@ import ubc.pavlab.rdp.model.enums.TierType; import ubc.pavlab.rdp.repositories.RoleRepository; import ubc.pavlab.rdp.settings.ApplicationSettings; +import ubc.pavlab.rdp.util.VersionUtils; import java.net.URI; import java.net.URISyntaxException; @@ -109,6 +110,17 @@ public void getVersion_whenEndpointReturnPre14Response_thenAssume100() throws Re mockServer.verify(); } + @Test + public void getVersion_whenEndpointReturnEarly14Response_thenAssume140() throws RemoteException, JsonProcessingException { + MockRestServiceServer mockServer = MockRestServiceServer.createServer( asyncRestTemplate ); + mockServer.expect( requestTo( "http://example.com/api" ) ) + .andRespond( withStatus( HttpStatus.OK ) + .contentType( MediaType.APPLICATION_JSON ) + .body( objectMapper.writeValueAsString( new OpenAPI().info( new Info().version( "v0" ) ) ) ) ); + assertThat( remoteResourceService.getApiVersion( URI.create( "http://example.com/" ) ) ).isEqualTo( "1.4.0" ); + mockServer.verify(); + } + @Test public void findUserByLikeName_thenReturnSuccess() throws JsonProcessingException { MockRestServiceServer mockServer = MockRestServiceServer.createServer( asyncRestTemplate ); diff --git a/src/test/java/ubc/pavlab/rdp/util/VersionUtilsTest.java b/src/test/java/ubc/pavlab/rdp/util/VersionUtilsTest.java index 0fddf6cb..0e64732a 100644 --- a/src/test/java/ubc/pavlab/rdp/util/VersionUtilsTest.java +++ b/src/test/java/ubc/pavlab/rdp/util/VersionUtilsTest.java @@ -15,13 +15,18 @@ public void satisfiesVersion_thenMatchExpectations() { assertThat( VersionUtils.satisfiesVersion( "0.99", "0.9" ) ).isTrue(); } - @Test(expected = RuntimeException.class) + @Test(expected = IllegalArgumentException.class) public void satisfiesVersion_whenVersionIsOver99_thenRaiseRuntimeException() { VersionUtils.satisfiesVersion( "1.100", "1.0.0" ); } - @Test(expected = RuntimeException.class) + @Test(expected = IllegalArgumentException.class) public void satisfiesVersion_whenVersionIsUnder0_thenRaiseRuntimeException() { VersionUtils.satisfiesVersion( "1.-1", "1.0.0" ); } + + @Test(expected = IllegalArgumentException.class) + public void satisfiesVersion_whenVersionIsInvalid_thenRaiseIllegalArgumentException() { + VersionUtils.satisfiesVersion( "v0", "1.4.0" ); + } }