register(@Valid @RequestBody UserTo userTo) {
+ log.info("register {}", userTo);
+ checkNew(userTo);
+ User created = prepareAndSave(UserUtil.createNewFromTo(userTo));
+ URI uriOfNewResource = ServletUriComponentsBuilder.fromCurrentContextPath()
+ .path(REST_URL).build().toUri();
+ return ResponseEntity.created(uriOfNewResource).body(created);
+ }
+
+ @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ @Transactional
+ public void update(@RequestBody @Valid UserTo userTo, @AuthenticationPrincipal AuthUser authUser) {
+ assureIdConsistent(userTo, authUser.id());
+ User user = authUser.getUser();
+ prepareAndSave(UserUtil.updateFromTo(user, userTo));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ru/javaops/bootjava/web/user/UniqueMailValidator.java b/src/main/java/ru/javaops/bootjava/web/user/UniqueMailValidator.java
new file mode 100644
index 0000000..2d8011e
--- /dev/null
+++ b/src/main/java/ru/javaops/bootjava/web/user/UniqueMailValidator.java
@@ -0,0 +1,49 @@
+package ru.javaops.bootjava.web.user;
+
+import lombok.AllArgsConstructor;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.Errors;
+import ru.javaops.bootjava.HasIdAndEmail;
+import ru.javaops.bootjava.repository.UserRepository;
+import ru.javaops.bootjava.web.SecurityUtil;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+@AllArgsConstructor
+public class UniqueMailValidator implements org.springframework.validation.Validator {
+ public static final String EXCEPTION_DUPLICATE_EMAIL = "User with this email already exists";
+
+ private final UserRepository repository;
+ private final HttpServletRequest request;
+
+ @Override
+ public boolean supports(@NonNull Class> clazz) {
+ return HasIdAndEmail.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public void validate(@NonNull Object target, @NonNull Errors errors) {
+ HasIdAndEmail user = ((HasIdAndEmail) target);
+ if (StringUtils.hasText(user.getEmail())) {
+ repository.findByEmailIgnoreCase(user.getEmail())
+ .ifPresent(dbUser -> {
+ if (request.getMethod().equals("PUT")) { // UPDATE
+ int dbId = dbUser.id();
+
+ // it is ok, if update ourself
+ if (user.getId() != null && dbId == user.id()) return;
+
+ // Workaround for update with user.id=null in request body
+ // ValidationUtil.assureIdConsistent called after this validation
+ String requestURI = request.getRequestURI();
+ if (requestURI.endsWith("/" + dbId) || (dbId == SecurityUtil.authId() && requestURI.contains("/profile")))
+ return;
+ }
+ errors.rejectValue("email", "", EXCEPTION_DUPLICATE_EMAIL);
+ });
+ }
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
new file mode 100644
index 0000000..9f55124
--- /dev/null
+++ b/src/main/resources/application.yaml
@@ -0,0 +1,54 @@
+# https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+spring:
+ jpa:
+ show-sql: true
+ open-in-view: false
+ # https://stackoverflow.com/a/67678945/548473
+ defer-datasource-initialization: true
+ hibernate:
+ ddl-auto: create
+ properties:
+ # http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#configurations
+ hibernate:
+ format_sql: true
+ default_batch_fetch_size: 20
+ # https://stackoverflow.com/questions/21257819/what-is-the-difference-between-hibernate-jdbc-fetch-size-and-hibernate-jdbc-batc
+ jdbc.batch_size: 20
+ datasource:
+ # ImMemory
+ url: jdbc:h2:mem:bootjava
+ # tcp: jdbc:h2:tcp://localhost:9092/mem:bootjava
+ # Absolute path
+ # url: jdbc:h2:C:/projects/bootjava/db/bootjava
+ # tcp: jdbc:h2:tcp://localhost:9092/C:/projects/bootjava/db/bootjava
+ # Relative path form current dir
+ # url: jdbc:h2:./db/bootjava
+ # Relative path from home
+ # url: jdbc:h2:~/bootjava
+ # tcp: jdbc:h2:tcp://localhost:9092/~/bootjava
+ username: sa
+ password:
+
+# Jackson Serialization Issue Resolver
+ jackson.visibility:
+ field: any
+ getter: none
+ setter: none
+ is-getter: none
+
+ # https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties-cache
+ cache:
+ cache-names: users
+ caffeine.spec: maximumSize=5000,expireAfterAccess=60s
+
+logging:
+ level:
+ root: WARN
+ ru.javaops.bootjava: DEBUG
+ org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: DEBUG
+
+server.servlet:
+ encoding:
+ charset: UTF-8 # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly
+ enabled: true # Enable http encoding support
+ force: true
\ No newline at end of file
diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql
new file mode 100644
index 0000000..d229d8d
--- /dev/null
+++ b/src/main/resources/data.sql
@@ -0,0 +1,8 @@
+INSERT INTO USERS (NAME, EMAIL, PASSWORD, ENABLED, REGISTERED)
+VALUES ('User', 'user@gmail.com', '{noop}password', 'true', '2022-04-04 11:33:30'),
+ ('Admin', 'admin@javaops.ru', '{noop}admin', 'true', '2022-04-04 11:33:30');
+
+INSERT INTO USER_ROLE (ROLE, USER_ID)
+VALUES ('USER', 1),
+ ('ADMIN', 2),
+ ('USER', 2);
\ No newline at end of file
diff --git a/src/test/java/ru/javaops/bootjava/RestaurantVotingApplicationTests.java b/src/test/java/ru/javaops/bootjava/RestaurantVotingApplicationTests.java
deleted file mode 100644
index 52bba6d..0000000
--- a/src/test/java/ru/javaops/bootjava/RestaurantVotingApplicationTests.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package ru.javaops.bootjava;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class RestaurantVotingApplicationTests {
-
- @Test
- void contextLoads() {
- }
-}
diff --git a/src/test/java/ru/javaops/bootjava/web/AbstractControllerTest.java b/src/test/java/ru/javaops/bootjava/web/AbstractControllerTest.java
new file mode 100644
index 0000000..b4d9cec
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/AbstractControllerTest.java
@@ -0,0 +1,26 @@
+package ru.javaops.bootjava.web;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.transaction.annotation.Transactional;
+
+//https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications
+@SpringBootTest
+@Transactional
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+//https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-mock-environment
+public abstract class AbstractControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ protected ResultActions perform(MockHttpServletRequestBuilder builder) throws Exception {
+ return mockMvc.perform(builder);
+ }
+}
diff --git a/src/test/java/ru/javaops/bootjava/web/MatcherFactory.java b/src/test/java/ru/javaops/bootjava/web/MatcherFactory.java
new file mode 100644
index 0000000..c510818
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/MatcherFactory.java
@@ -0,0 +1,83 @@
+package ru.javaops.bootjava.web;
+
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.ResultMatcher;
+import ru.javaops.bootjava.util.JsonUtil;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Factory for creating test matchers.
+ *
+ * Comparing actual and expected objects via AssertJ
+ * Support converting json MvcResult to objects for comparation.
+ */
+public class MatcherFactory {
+
+ public static Matcher usingAssertions(Class clazz, BiConsumer assertion, BiConsumer, Iterable> iterableAssertion) {
+ return new Matcher<>(clazz, assertion, iterableAssertion);
+ }
+
+ public static Matcher usingEqualsComparator(Class clazz) {
+ return usingAssertions(clazz,
+ (a, e) -> assertThat(a).isEqualTo(e),
+ (a, e) -> assertThat(a).isEqualTo(e));
+ }
+
+ public static Matcher usingIgnoringFieldsComparator(Class clazz, String... fieldsToIgnore) {
+ return usingAssertions(clazz,
+ (a, e) -> assertThat(a).usingRecursiveComparison().ignoringFields(fieldsToIgnore).isEqualTo(e),
+ (a, e) -> assertThat(a).usingRecursiveFieldByFieldElementComparatorIgnoringFields(fieldsToIgnore).isEqualTo(e));
+ }
+
+ public static class Matcher {
+ private final Class clazz;
+ private final BiConsumer assertion;
+ private final BiConsumer, Iterable> iterableAssertion;
+
+ private Matcher(Class clazz, BiConsumer assertion, BiConsumer, Iterable> iterableAssertion) {
+ this.clazz = clazz;
+ this.assertion = assertion;
+ this.iterableAssertion = iterableAssertion;
+ }
+
+ public void assertMatch(T actual, T expected) {
+ assertion.accept(actual, expected);
+ }
+
+ @SafeVarargs
+ public final void assertMatch(Iterable actual, T... expected) {
+ assertMatch(actual, List.of(expected));
+ }
+
+ public void assertMatch(Iterable actual, Iterable expected) {
+ iterableAssertion.accept(actual, expected);
+ }
+
+ public ResultMatcher contentJson(T expected) {
+ return result -> assertMatch(JsonUtil.readValue(getContent(result), clazz), expected);
+ }
+
+ @SafeVarargs
+ public final ResultMatcher contentJson(T... expected) {
+ return contentJson(List.of(expected));
+ }
+
+ public ResultMatcher contentJson(Iterable expected) {
+ return result -> assertMatch(JsonUtil.readValues(getContent(result), clazz), expected);
+ }
+
+ public T readFromJson(ResultActions action) throws UnsupportedEncodingException {
+ return JsonUtil.readValue(getContent(action.andReturn()), clazz);
+ }
+
+ private static String getContent(MvcResult result) throws UnsupportedEncodingException {
+ return result.getResponse().getContentAsString();
+ }
+ }
+}
diff --git a/src/test/java/ru/javaops/bootjava/web/user/AdminUserControllerTest.java b/src/test/java/ru/javaops/bootjava/web/user/AdminUserControllerTest.java
new file mode 100644
index 0000000..65fbf14
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/user/AdminUserControllerTest.java
@@ -0,0 +1,211 @@
+package ru.javaops.bootjava.web.user;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithUserDetails;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+import ru.javaops.bootjava.model.Role;
+import ru.javaops.bootjava.model.User;
+import ru.javaops.bootjava.repository.UserRepository;
+import ru.javaops.bootjava.web.AbstractControllerTest;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static ru.javaops.bootjava.web.user.UniqueMailValidator.EXCEPTION_DUPLICATE_EMAIL;
+import static ru.javaops.bootjava.web.user.UserTestData.*;
+
+class AdminUserControllerTest extends AbstractControllerTest {
+
+ private static final String REST_URL = AdminUserController.REST_URL + '/';
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void get() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL + ADMIN_ID))
+ .andExpect(status().isOk())
+ .andDo(print())
+ // https://jira.spring.io/browse/SPR-14472
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(USER_MATCHER.contentJson(admin));
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void getNotFound() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL + NOT_FOUND))
+ .andDo(print())
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void getByEmail() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL + "by-email?email=" + admin.getEmail()))
+ .andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(USER_MATCHER.contentJson(admin));
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void delete() throws Exception {
+ perform(MockMvcRequestBuilders.delete(REST_URL + USER_ID))
+ .andDo(print())
+ .andExpect(status().isNoContent());
+ assertFalse(userRepository.findById(USER_ID).isPresent());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void deleteNotFound() throws Exception {
+ perform(MockMvcRequestBuilders.delete(REST_URL + NOT_FOUND))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void enableNotFound() throws Exception {
+ perform(MockMvcRequestBuilders.patch(REST_URL + NOT_FOUND)
+ .param("enabled", "false")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity());
+ }
+
+ @Test
+ void getUnAuth() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL))
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void getForbidden() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL))
+ .andExpect(status().isForbidden());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void update() throws Exception {
+ User updated = getUpdated();
+ updated.setId(null);
+ perform(MockMvcRequestBuilders.put(REST_URL + USER_ID)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonWithPassword(updated, "newPass")))
+ .andDo(print())
+ .andExpect(status().isNoContent());
+
+ USER_MATCHER.assertMatch(userRepository.getExisted(USER_ID), getUpdated());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void createWithLocation() throws Exception {
+ User newUser = getNew();
+ ResultActions action = perform(MockMvcRequestBuilders.post(REST_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonWithPassword(newUser, "newPass")))
+ .andExpect(status().isCreated());
+
+ User created = USER_MATCHER.readFromJson(action);
+ int newId = created.id();
+ newUser.setId(newId);
+ USER_MATCHER.assertMatch(created, newUser);
+ USER_MATCHER.assertMatch(userRepository.getExisted(newId), newUser);
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void getAll() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL))
+ .andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(USER_MATCHER.contentJson(admin, user));
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void enable() throws Exception {
+ perform(MockMvcRequestBuilders.patch(REST_URL + USER_ID)
+ .param("enabled", "false")
+ .contentType(MediaType.APPLICATION_JSON))
+ .andDo(print())
+ .andExpect(status().isNoContent());
+
+ assertFalse(userRepository.getExisted(USER_ID).isEnabled());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void createInvalid() throws Exception {
+ User invalid = new User(null, null, "", "newPass", Role.USER, Role.ADMIN);
+ perform(MockMvcRequestBuilders.post(REST_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonWithPassword(invalid, "newPass")))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void updateInvalid() throws Exception {
+ User invalid = new User(user);
+ invalid.setName("");
+ perform(MockMvcRequestBuilders.put(REST_URL + USER_ID)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonWithPassword(invalid, "password")))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity());
+ }
+
+ @Test
+ @WithUserDetails(value = ADMIN_MAIL)
+ void updateHtmlUnsafe() throws Exception {
+ User updated = new User(user);
+ updated.setName("");
+ perform(MockMvcRequestBuilders.put(REST_URL + USER_ID)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonWithPassword(updated, "password")))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity());
+ }
+
+ @Test
+ @Transactional(propagation = Propagation.NEVER)
+ @WithUserDetails(value = ADMIN_MAIL)
+ void updateDuplicate() throws Exception {
+ User updated = new User(user);
+ updated.setEmail(ADMIN_MAIL);
+ perform(MockMvcRequestBuilders.put(REST_URL + USER_ID)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonWithPassword(updated, "password")))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity())
+ .andExpect(content().string(containsString(EXCEPTION_DUPLICATE_EMAIL)));
+ }
+
+ @Test
+ @Transactional(propagation = Propagation.NEVER)
+ @WithUserDetails(value = ADMIN_MAIL)
+ void createDuplicate() throws Exception {
+ User expected = new User(null, "New", USER_MAIL, "newPass", Role.USER, Role.ADMIN);
+ perform(MockMvcRequestBuilders.post(REST_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonWithPassword(expected, "newPass")))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity())
+ .andExpect(content().string(containsString(EXCEPTION_DUPLICATE_EMAIL)));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javaops/bootjava/web/user/ProfileControllerTest.java b/src/test/java/ru/javaops/bootjava/web/user/ProfileControllerTest.java
new file mode 100644
index 0000000..ba8e516
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/user/ProfileControllerTest.java
@@ -0,0 +1,112 @@
+package ru.javaops.bootjava.web.user;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithUserDetails;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import ru.javaops.bootjava.model.User;
+import ru.javaops.bootjava.repository.UserRepository;
+import ru.javaops.bootjava.to.UserTo;
+import ru.javaops.bootjava.util.JsonUtil;
+import ru.javaops.bootjava.util.UserUtil;
+import ru.javaops.bootjava.web.AbstractControllerTest;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static ru.javaops.bootjava.web.user.ProfileController.REST_URL;
+import static ru.javaops.bootjava.web.user.UniqueMailValidator.EXCEPTION_DUPLICATE_EMAIL;
+import static ru.javaops.bootjava.web.user.UserTestData.*;
+
+class ProfileControllerTest extends AbstractControllerTest {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void get() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL))
+ .andExpect(status().isOk())
+ .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
+ .andExpect(USER_MATCHER.contentJson(user));
+ }
+
+ @Test
+ void getUnAuth() throws Exception {
+ perform(MockMvcRequestBuilders.get(REST_URL))
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void delete() throws Exception {
+ perform(MockMvcRequestBuilders.delete(REST_URL))
+ .andExpect(status().isNoContent());
+ USER_MATCHER.assertMatch(userRepository.findAll(), admin);
+ }
+
+ @Test
+ void register() throws Exception {
+ UserTo newTo = new UserTo(null, "newName", "newemail@ya.ru", "newPassword");
+ User newUser = UserUtil.createNewFromTo(newTo);
+ ResultActions action = perform(MockMvcRequestBuilders.post(REST_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.writeValue(newTo)))
+ .andDo(print())
+ .andExpect(status().isCreated());
+
+ User created = USER_MATCHER.readFromJson(action);
+ int newId = created.id();
+ newUser.setId(newId);
+ USER_MATCHER.assertMatch(created, newUser);
+ USER_MATCHER.assertMatch(userRepository.getExisted(newId), newUser);
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void update() throws Exception {
+ UserTo updatedTo = new UserTo(null, "newName", USER_MAIL, "newPassword");
+ perform(MockMvcRequestBuilders.put(REST_URL).contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.writeValue(updatedTo)))
+ .andDo(print())
+ .andExpect(status().isNoContent());
+
+ USER_MATCHER.assertMatch(userRepository.getExisted(USER_ID), UserUtil.updateFromTo(new User(user), updatedTo));
+ }
+
+ @Test
+ void registerInvalid() throws Exception {
+ UserTo newTo = new UserTo(null, null, null, null);
+ perform(MockMvcRequestBuilders.post(REST_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.writeValue(newTo)))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity());
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void updateInvalid() throws Exception {
+ UserTo updatedTo = new UserTo(null, null, "password", null);
+ perform(MockMvcRequestBuilders.put(REST_URL)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.writeValue(updatedTo)))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity());
+ }
+
+ @Test
+ @WithUserDetails(value = USER_MAIL)
+ void updateDuplicate() throws Exception {
+ UserTo updatedTo = new UserTo(null, "newName", ADMIN_MAIL, "newPassword");
+ perform(MockMvcRequestBuilders.put(REST_URL).contentType(MediaType.APPLICATION_JSON)
+ .content(JsonUtil.writeValue(updatedTo)))
+ .andDo(print())
+ .andExpect(status().isUnprocessableEntity())
+ .andExpect(content().string(containsString(EXCEPTION_DUPLICATE_EMAIL)));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ru/javaops/bootjava/web/user/UserTestData.java b/src/test/java/ru/javaops/bootjava/web/user/UserTestData.java
new file mode 100644
index 0000000..f4841b6
--- /dev/null
+++ b/src/test/java/ru/javaops/bootjava/web/user/UserTestData.java
@@ -0,0 +1,34 @@
+package ru.javaops.bootjava.web.user;
+
+import ru.javaops.bootjava.model.Role;
+import ru.javaops.bootjava.model.User;
+import ru.javaops.bootjava.util.JsonUtil;
+import ru.javaops.bootjava.web.MatcherFactory;
+
+import java.util.Collections;
+import java.util.Date;
+
+public class UserTestData {
+ public static final MatcherFactory.Matcher USER_MATCHER = MatcherFactory.usingIgnoringFieldsComparator(User.class, "registered", "password");
+
+ public static final int USER_ID = 1;
+ public static final int ADMIN_ID = 2;
+ public static final int NOT_FOUND = 100;
+ public static final String USER_MAIL = "user@gmail.com";
+ public static final String ADMIN_MAIL = "admin@javaops.ru";
+
+ public static final User user = new User(USER_ID, "User", USER_MAIL, "password", Role.USER);
+ public static final User admin = new User(ADMIN_ID, "Admin", ADMIN_MAIL, "admin", Role.ADMIN, Role.USER);
+
+ public static User getNew() {
+ return new User(null, "New", "new@gmail.com", "newPass", false, new Date(), Collections.singleton(Role.USER));
+ }
+
+ public static User getUpdated() {
+ return new User(USER_ID, "UpdatedName", USER_MAIL, "newPass", false, new Date(), Collections.singleton(Role.ADMIN));
+ }
+
+ public static String jsonWithPassword(User user, String passw) {
+ return JsonUtil.writeAdditionProps(user, "password", passw);
+ }
+}
diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml
new file mode 100644
index 0000000..be16632
--- /dev/null
+++ b/src/test/resources/application-test.yaml
@@ -0,0 +1 @@
+spring.cache.type: none
\ No newline at end of file