From df7fcb0edb86be317ca4a25e2cc3a06881a3a576 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 9 Mar 2015 11:04:00 +0100 Subject: [PATCH] #65 - Added example project for Spring Data web support. --- README.md | 4 + pom.xml | 1 + web/README.md | 6 + web/pom.xml | 48 ++++++ web/src/main/java/example/Application.java | 80 +++++++++ web/src/main/java/example/users/Password.java | 76 +++++++++ web/src/main/java/example/users/User.java | 63 +++++++ .../java/example/users/UserManagement.java | 90 ++++++++++ .../java/example/users/UserRepository.java | 36 ++++ web/src/main/java/example/users/Username.java | 61 +++++++ .../example/users/web/UserController.java | 155 ++++++++++++++++++ web/src/main/resources/messages.properties | 2 + web/src/main/resources/static/css/style.css | 28 ++++ web/src/main/resources/templates/users.html | 53 ++++++ .../users/AbstractIntegrationTests.java | 31 ++++ .../users/UserManagementIntegrationTests.java | 43 +++++ .../users/UserRepositoryIntegrationTests.java | 38 +++++ 17 files changed, 815 insertions(+) create mode 100644 web/README.md create mode 100644 web/pom.xml create mode 100644 web/src/main/java/example/Application.java create mode 100644 web/src/main/java/example/users/Password.java create mode 100644 web/src/main/java/example/users/User.java create mode 100644 web/src/main/java/example/users/UserManagement.java create mode 100644 web/src/main/java/example/users/UserRepository.java create mode 100644 web/src/main/java/example/users/Username.java create mode 100644 web/src/main/java/example/users/web/UserController.java create mode 100644 web/src/main/resources/messages.properties create mode 100644 web/src/main/resources/static/css/style.css create mode 100644 web/src/main/resources/templates/users.html create mode 100644 web/src/test/java/example/users/AbstractIntegrationTests.java create mode 100644 web/src/test/java/example/users/UserManagementIntegrationTests.java create mode 100644 web/src/test/java/example/users/UserRepositoryIntegrationTests.java diff --git a/README.md b/README.md index 73d94d03..59f305fd 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ We have separate folders for the samples of individual modules: * `example` - Example how to use basic text search, geo-spatial search and facets. +## Spring Data web support + +* `web` - Example for Spring Data web integration (binding `Pageable` instances to Spring MVC controller methods, using interfaces to bind Spring MVCrequest payloads). + ## Miscellaneous * `multi-store` - Example project to use both Spring Data MongoDB and Spring Data JPA in one project. \ No newline at end of file diff --git a/pom.xml b/pom.xml index b694e03e..b7b13e3e 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,7 @@ solr cassandra elasticsearch + web diff --git a/web/README.md b/web/README.md new file mode 100644 index 00000000..3940d0ea --- /dev/null +++ b/web/README.md @@ -0,0 +1,6 @@ +# Spring Data - web support example + +This example shows some of the Spring Data integration features with Spring MVC. + +1. See how we plug into Spring MVC to create `Pageable` instances from request parameters in `UserController.users(…)`. +2. See how interfaces can be used to bind request payloads in `UserController.UserForm`. Spring Data creates a `Map`-backed proxy for you to easily create form-backing objects. \ No newline at end of file diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 00000000..5fa53fe7 --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,48 @@ + + 4.0.0 + + spring-data-web-example + + Spring Data - Web support example + + + org.springframework.data.examples + spring-data-examples + 1.0.0.BUILD-SNAPSHOT + + + + Fowler-BUILD-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-security + + + + org.hsqldb + hsqldb + + + + + \ No newline at end of file diff --git a/web/src/main/java/example/Application.java b/web/src/main/java/example/Application.java new file mode 100644 index 00000000..7d3443b4 --- /dev/null +++ b/web/src/main/java/example/Application.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example; + +import java.util.stream.IntStream; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.config.EnableSpringDataWebSupport; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import example.users.Password; +import example.users.UserManagement; +import example.users.Username; + +/** + * Central Spring Boot application class to bootstrap the application. Excludes Spring Security auto-configuration as we + * don't need it for the example but only want to use a {@link PasswordEncoder} (see {@link #passwordEncoder()}). + *

+ * Spring Data web support is transparently activated by Boot for you. In case you want to manually activate it, use + * {@link EnableSpringDataWebSupport}. The core aspects of the enabled functionality shown in this example are: + *

    + *
  1. Automatic population of a {@link Pageable} instances from request parameters (see + * {@link example.users.web.UserController#users(Pageable)})
  2. + *
  3. The ability to use proxy-backed interfaces to bind request payloads (see + * {@link example.users.web.UserController.UserForm})
  4. + *
+ * + * @author Oliver Gierke + */ +@SpringBootApplication(exclude = SecurityAutoConfiguration.class) +public class Application { + + public static void main(String... args) { + SpringApplication.run(Application.class, args); + } + + @Autowired UserManagement userManagement; + + /** + * Creates a few sample users. + */ + @PostConstruct + public void init() { + + IntStream.range(0, 41).forEach(index -> { + userManagement.register(new Username("user" + index), Password.raw("foobar")); + }); + } + + /** + * A Spring Security {@link PasswordEncoder} to encrypt passwords for newly created users, used in + * {@link UserManagement}. + * + * @return + */ + public @Bean PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/web/src/main/java/example/users/Password.java b/web/src/main/java/example/users/Password.java new file mode 100644 index 00000000..f2fb6ead --- /dev/null +++ b/web/src/main/java/example/users/Password.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import javax.persistence.Embeddable; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.experimental.Delegate; + +/** + * A value object to represent {@link Password}s in encrypted and unencrypted state. Note how the methods to create a + * {@link Password} in encrypted state are restricted to package scope so that only the user subsystem is actually able + * to encrypted passwords. + * + * @author Oliver Gierke + */ +@EqualsAndHashCode +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter(AccessLevel.PACKAGE) +@Embeddable +public class Password implements CharSequence { + + private @Delegate final String password; + private @Getter transient boolean encrypted; + + Password() { + this.password = null; + this.encrypted = true; + } + + /** + * Creates a new raw {@link Password} for the given source {@link String}. + * + * @param password must not be {@literal null} or empty. + * @return + */ + public static Password raw(String password) { + return new Password(password, false); + } + + /** + * Creates a new encrypted {@link Password} for the given {@link String}. Note how this method is package protected so + * that encrypted passwords can only created by components in this package and not accidentally by clients using the + * type from other packages. + * + * @param password must not be {@literal null} or empty. + * @return + */ + static Password encrypted(String password) { + return new Password(password, true); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return encrypted ? password : "********"; + } +} diff --git a/web/src/main/java/example/users/User.java b/web/src/main/java/example/users/User.java new file mode 100644 index 00000000..37a251a1 --- /dev/null +++ b/web/src/main/java/example/users/User.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * A {@link User} domain object. The primary entity of this example. Basically a combination of a {@link Username} and + * {@link Password}. + * + * @author Oliver Gierke + */ +@Entity +@Getter +@RequiredArgsConstructor +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@EqualsAndHashCode(of = "id") +public class User { + + private @GeneratedValue @Id Long id; + private final Username username; + private final Password password; + + User() { + this.username = null; + this.password = null; + } + + /** + * Makes sure only {@link User}s with encrypted {@link Password} can be persisted. + */ + @PrePersist + @PreUpdate + void assertEncrypted() { + + if (!password.isEncrypted()) { + throw new IllegalStateException("Tried to persist/load a user with a non-encrypted password!"); + } + } +} diff --git a/web/src/main/java/example/users/UserManagement.java b/web/src/main/java/example/users/UserManagement.java new file mode 100644 index 00000000..523ea986 --- /dev/null +++ b/web/src/main/java/example/users/UserManagement.java @@ -0,0 +1,90 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import java.util.Optional; + +import javax.transaction.Transactional; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +/** + * Domain service to register {@link User}s in the system. + * + * @author Oliver Gierke + */ +@Transactional +@Service +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class UserManagement { + + private final UserRepository repository; + private final PasswordEncoder encoder; + + /** + * Registers a {@link User} with the given {@link Username} and {@link Password}. + * + * @param username must not be {@literal null}. + * @param password must not be {@literal null}. + * @return + */ + public User register(Username username, Password password) { + + Assert.notNull(username, "Username must not be null!"); + Assert.notNull(password, "Password must not be null!"); + + repository.findByUsername(username).ifPresent(user -> { + throw new IllegalArgumentException("User with that name already exists!"); + }); + + Password encryptedPassword = Password.encrypted(encoder.encode(password)); + + return repository.save(new User(username, encryptedPassword)); + } + + /** + * Returns a {@link Page} of {@link User} for the given {@link Pageable}. + * + * @param pageable must not be {@literal null}. + * @return + */ + public Page findAll(Pageable pageable) { + + Assert.notNull(pageable, "Pageable must not be null!"); + + return repository.findAll(pageable); + } + + /** + * Returns the {@link User} with the given {@link Username}. + * + * @param username must not be {@literal null}. + * @return + */ + public Optional findByUsername(Username username) { + + Assert.notNull(username, "Username must not be null!"); + + return repository.findByUsername(username); + } +} diff --git a/web/src/main/java/example/users/UserRepository.java b/web/src/main/java/example/users/UserRepository.java new file mode 100644 index 00000000..00ca1194 --- /dev/null +++ b/web/src/main/java/example/users/UserRepository.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import java.util.Optional; + +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + * A Spring Data repository to persist {@link User}s. + * + * @author Oliver Gierke + */ +interface UserRepository extends PagingAndSortingRepository { + + /** + * Returns the user with the given {@link Username}. + * + * @param username can be {@literal null}. + * @return + */ + Optional findByUsername(Username username); +} diff --git a/web/src/main/java/example/users/Username.java b/web/src/main/java/example/users/Username.java new file mode 100644 index 00000000..bb90f9ef --- /dev/null +++ b/web/src/main/java/example/users/Username.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import javax.persistence.Embeddable; + +import lombok.EqualsAndHashCode; + +import org.springframework.util.StringUtils; + +/** + * value object to represent user names. + * + * @author Oliver Gierke + */ +@EqualsAndHashCode +@Embeddable +public class Username { + + private final String username; + + Username() { + this.username = null; + } + + /** + * Creates a new {@link Username}. + * + * @param username must not be {@literal null} or empty. + */ + public Username(String username) { + + if (!StringUtils.hasText(username)) { + throw new IllegalArgumentException("Invalid username!"); + } + + this.username = username; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return username; + } +} diff --git a/web/src/main/java/example/users/web/UserController.java b/web/src/main/java/example/users/web/UserController.java new file mode 100644 index 00000000..14164144 --- /dev/null +++ b/web/src/main/java/example/users/web/UserController.java @@ -0,0 +1,155 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users.web; + +import static org.springframework.validation.ValidationUtils.*; + +import java.util.Map; + +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.data.web.config.EnableSpringDataWebSupport; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.view.RedirectView; + +import example.users.Password; +import example.users.User; +import example.users.UserManagement; +import example.users.Username; + +/** + * A sample controller implementation to showcase Spring Data web support: + *
    + *
  1. Automatic population of a {@link Pageable} instance as controller method argument. This is achieved by the + * automatic activation of {@link EnableSpringDataWebSupport} and in turn its registration of a + * {@link PageableHandlerMethodArgumentResolver}.
  2. + *
  3. Usage of proxy-backed interfaces to bind request parameters.
  4. + *
+ * + * @author Oliver Gierke + */ +@Controller +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +@RequestMapping("/users") +class UserController { + + private final UserManagement userManagement; + + /** + * Equis the model with a {@link Page} of {@link User}s. Spring Data automatically populates the {@link Pageable} from + * request data according to the setup of {@link PageableHandlerMethodArgumentResolver}. Note how the defaults can be + * tweaked by using {@link PageableDefault}. + * + * @param pageable will never be {@literal null}. + * @return + */ + @ModelAttribute("users") + public Page users(@PageableDefault(size = 5) Pageable pageable) { + return userManagement.findAll(pageable); + } + + /** + * Registers a new {@link User} for the data provided by the given {@link UserForm}. Note, how an interface is used to + * bind request parameters. + * + * @param form the request data bound to the {@link UserForm} instance. + * @param binding the result of the binding operation. + * @param model the Spring MVC {@link Model}. + * @return + */ + @RequestMapping(method = RequestMethod.POST) + public Object register(UserForm userForm, BindingResult binding, Model model) { + + userForm.validate(binding, userManagement); + + if (binding.hasErrors()) { + return "users"; + } + + userManagement.register(new Username(userForm.getUsername()), Password.raw(userForm.getPassword())); + + RedirectView redirectView = new RedirectView("redirect:/users"); + redirectView.setPropagateQueryParams(true); + + return redirectView; + } + + /** + * Populates the {@link Model} with the {@link UserForm} automatically created by Spring Data web components. It will + * create a {@link Map}-backed proxy for the interface. + * + * @param model will never be {@literal null}. + * @param userForm will never be {@literal null}. + * @return + */ + @RequestMapping(method = RequestMethod.GET) + public String listUsers(Model model, UserForm userForm) { + + model.addAttribute("userForm", userForm); + + return "users"; + } + + /** + * An interface to represent the form to be used + * + * @author Oliver Gierke + */ + interface UserForm { + + String getUsername(); + + String getPassword(); + + String getRepeatedPassword(); + + /** + * Validates the {@link UserForm}. + * + * @param errors + * @param userManagement + */ + default void validate(BindingResult errors, UserManagement userManagement) { + + rejectIfEmptyOrWhitespace(errors, "username", "user.username.empty"); + rejectIfEmptyOrWhitespace(errors, "password", "user.password.empty"); + rejectIfEmptyOrWhitespace(errors, "repeatedPassword", "user.repeatedPassword.empty"); + + if (!getPassword().equals(getRepeatedPassword())) { + errors.rejectValue("repeatedPassword", "user.password.no-match"); + } + + try { + + userManagement.findByUsername(new Username(getUsername())).ifPresent( + user -> errors.rejectValue("username", "user.username.exists")); + + } catch (IllegalArgumentException o_O) { + errors.rejectValue("username", "user.username.invalidFormat"); + } + } + } +} diff --git a/web/src/main/resources/messages.properties b/web/src/main/resources/messages.properties new file mode 100644 index 00000000..adf14cde --- /dev/null +++ b/web/src/main/resources/messages.properties @@ -0,0 +1,2 @@ +user.username.exists = Username already exists! +user.password.no-match = The given passwords don't match! \ No newline at end of file diff --git a/web/src/main/resources/static/css/style.css b/web/src/main/resources/static/css/style.css new file mode 100644 index 00000000..63df6315 --- /dev/null +++ b/web/src/main/resources/static/css/style.css @@ -0,0 +1,28 @@ +body { + margin: auto; + width: 50%; +} + +form.user-form { + padding: 9px 14px; + border: 1px solid #e1e1e8; + border-radius: 4px; +} + +.fieldError { + border: 1px solid #a94442; +} + +.errors { + padding: 1em; + margin: 1em 0; + border: 1px solid #eee; + border-left-width: 5px; + border-left-color: #a94442; + border-radius: 5px; +} + +.errors li { + list-style-type: none; + margin: 0.5em 0.7em; +} diff --git a/web/src/main/resources/templates/users.html b/web/src/main/resources/templates/users.html new file mode 100644 index 00000000..10362245 --- /dev/null +++ b/web/src/main/resources/templates/users.html @@ -0,0 +1,53 @@ + + + + Users + + + + + +

Users

+ + + +
    +
  1. Username
  2. +
+ +
+ +
    +
  • Input is incorrect
  • +
+ +
+
+ +
+
+
+ +
+
+
+ +
+ +
+ + \ No newline at end of file diff --git a/web/src/test/java/example/users/AbstractIntegrationTests.java b/web/src/test/java/example/users/AbstractIntegrationTests.java new file mode 100644 index 00000000..5912f6b3 --- /dev/null +++ b/web/src/test/java/example/users/AbstractIntegrationTests.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import org.junit.runner.RunWith; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import example.Application; + +/** + * Integration tests to bootstrap the application. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +public abstract class AbstractIntegrationTests {} diff --git a/web/src/test/java/example/users/UserManagementIntegrationTests.java b/web/src/test/java/example/users/UserManagementIntegrationTests.java new file mode 100644 index 00000000..f28386ed --- /dev/null +++ b/web/src/test/java/example/users/UserManagementIntegrationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Integration tests for {@link UserManagement}. + * + * @author Oliver Gierke + */ +public class UserManagementIntegrationTests extends AbstractIntegrationTests { + + @Autowired UserManagement userManagement; + + /** + * @see #65 + */ + @Test + public void encryptsPasswordWhenCreatingAUser() { + + User user = userManagement.register(new Username("olivergierke"), Password.raw("foobar")); + + assertThat(user.getPassword().isEncrypted(), is(true)); + } +} diff --git a/web/src/test/java/example/users/UserRepositoryIntegrationTests.java b/web/src/test/java/example/users/UserRepositoryIntegrationTests.java new file mode 100644 index 00000000..0777e864 --- /dev/null +++ b/web/src/test/java/example/users/UserRepositoryIntegrationTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.users; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.InvalidDataAccessApiUsageException; + +/** + * Integration tests for {@link UserRepository}. + * + * @author Oliver Gierke + */ +public class UserRepositoryIntegrationTests extends AbstractIntegrationTests { + + @Autowired UserRepository users; + + /** + * @see #65 + */ + @Test(expected = InvalidDataAccessApiUsageException.class) + public void repositoryRejectsUnencryptedPassword() { + users.save(new User(new Username("olivergierke"), Password.raw("foobar"))); + } +}