Skip to content

Commit

Permalink
11_04_HW10_duplicate_datetime
Browse files Browse the repository at this point in the history
  • Loading branch information
GlyzinAI committed Sep 8, 2019
1 parent a02f607 commit 5fe680c
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 31 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ branches:
services:
- postgresql

#https://docs.travis-ci.com/user/environment-variables#Default-Environment-Variables
env:
- TOPJAVA_ROOT=$TRAVIS_BUILD_DIR

# https://docs.travis-ci.com/user/notifications#Configuring-email-notifications
#notifications:
# email: false
3 changes: 2 additions & 1 deletion config/messages/app.properties
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ common.confirm=Are you sure?
common.save=Save
common.cancel=Cancel

exception.user.duplicateEmail=User with this email already exists
exception.user.duplicateEmail=User with this email already exists
exception.meal.duplicateDateTime=You already have meal with this date/time
3 changes: 2 additions & 1 deletion config/messages/app_ru.properties
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ common.confirm=Вы уверены?
common.save=Сохранить
common.cancel=Отменить

exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении
exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении
exception.meal.duplicateDateTime=У вас уже есть еда с такой датой/временем
23 changes: 23 additions & 0 deletions src/main/java/ru/javawebinar/topjava/web/ExceptionInfoHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
Expand All @@ -22,7 +23,9 @@
import ru.javawebinar.topjava.util.exception.NotFoundException;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static ru.javawebinar.topjava.util.exception.ErrorType.*;

Expand All @@ -31,6 +34,16 @@
public class ExceptionInfoHandler {
private static Logger log = LoggerFactory.getLogger(ExceptionInfoHandler.class);

public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail";
public static final String EXCEPTION_DUPLICATE_DATETIME = "exception.meal.duplicateDateTime";

private static final Map<String, String> CONSTRAINS_I18N_MAP = Map.of(
"users_unique_email_idx", EXCEPTION_DUPLICATE_EMAIL,
"meals_unique_user_datetime_idx", EXCEPTION_DUPLICATE_DATETIME);

@Autowired
private MessageUtil messageUtil;

// http://stackoverflow.com/a/22358422/548473
@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
@ExceptionHandler(NotFoundException.class)
Expand All @@ -41,6 +54,16 @@ public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) {
@ResponseStatus(value = HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) {
String rootMsg = ValidationUtil.getRootCause(e).getMessage();
if (rootMsg != null) {
String lowerCaseMsg = rootMsg.toLowerCase();
Optional<Map.Entry<String, String>> entry = CONSTRAINS_I18N_MAP.entrySet().stream()
.filter(it -> lowerCaseMsg.contains(it.getKey()))
.findAny();
if (entry.isPresent()) {
return logAndGetErrorInfo(req, e, false, VALIDATION_ERROR, messageUtil.getMessage(entry.get().getValue()));
}
}
return logAndGetErrorInfo(req, e, true, DATA_ERROR);
}

Expand Down
27 changes: 27 additions & 0 deletions src/main/java/ru/javawebinar/topjava/web/MessageUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ru.javawebinar.topjava.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

import java.util.Locale;

@Component
public class MessageUtil {

private final MessageSource messageSource;

@Autowired
public MessageUtil(MessageSource messageSource) {
this.messageSource = messageSource;
}

public String getMessage(String code, Locale locale) {
return messageSource.getMessage(code, null, locale);
}

public String getMessage(String code) {
return getMessage(code, LocaleContextHolder.getLocale());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
public abstract class AbstractUserController {
protected final Logger log = LoggerFactory.getLogger(getClass());

public static final String EXCEPTION_DUPLICATE_EMAIL = "exception.user.duplicateEmail";

@Autowired
protected UserService service;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package ru.javawebinar.topjava.web.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import ru.javawebinar.topjava.model.User;
import ru.javawebinar.topjava.to.UserTo;
import ru.javawebinar.topjava.util.exception.IllegalRequestDataException;

import javax.validation.Valid;
import java.util.List;
Expand All @@ -18,9 +13,6 @@
@RequestMapping("/ajax/admin/users")
public class AdminUIController extends AbstractUserController {

@Autowired
private MessageSource messageSource;

@Override
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List<User> getAll() {
Expand All @@ -42,14 +34,10 @@ public void delete(@PathVariable int id) {

@PostMapping
public void createOrUpdate(@Valid UserTo userTo) {
try {
if (userTo.isNew()) {
super.create(userTo);
} else {
super.update(userTo, userTo.id());
}
} catch (DataIntegrityViolationException e) {
throw new IllegalRequestDataException(messageSource.getMessage(EXCEPTION_DUPLICATE_EMAIL, null, LocaleContextHolder.getLocale()));
if (userTo.isNew()) {
super.create(userTo);
} else {
super.update(userTo, userTo.id());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

import javax.validation.Valid;

import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL;

@Controller
@RequestMapping("/profile")
public class ProfileUIController extends AbstractUserController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import ru.javawebinar.topjava.AllActiveProfileResolver;
import ru.javawebinar.topjava.repository.JpaUtil;
import ru.javawebinar.topjava.service.UserService;
import ru.javawebinar.topjava.util.exception.ErrorType;

import javax.annotation.PostConstruct;
import java.util.Locale;

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@SpringJUnitWebConfig(locations = {
"classpath:spring/spring-app.xml",
Expand Down Expand Up @@ -50,6 +54,9 @@ abstract public class AbstractControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;

@Autowired
protected MessageUtil messageUtil;

@PostConstruct
private void postConstruct() {
mockMvc = MockMvcBuilders
Expand All @@ -66,4 +73,16 @@ void setUp() {
jpaUtil.clear2ndLevelHibernateCache();
}
}

private String getMessage(String code) {
return messageUtil.getMessage(code, Locale.ENGLISH);
}

public ResultMatcher errorType(ErrorType type) {
return jsonPath("$.type").value(type.name());
}

public ResultMatcher detailMessage(String code) {
return jsonPath("$.details").value(getMessage(code));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
import org.springframework.http.MediaType;
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.javawebinar.topjava.model.Meal;
import ru.javawebinar.topjava.service.MealService;
import ru.javawebinar.topjava.util.exception.ErrorType;
import ru.javawebinar.topjava.web.AbstractControllerTest;
import ru.javawebinar.topjava.web.json.JsonUtil;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static ru.javawebinar.topjava.MealTestData.assertMatch;
import static ru.javawebinar.topjava.MealTestData.contentJson;
import static ru.javawebinar.topjava.MealTestData.*;
Expand All @@ -22,6 +24,8 @@
import static ru.javawebinar.topjava.model.AbstractBaseEntity.START_SEQ;
import static ru.javawebinar.topjava.util.MealsUtil.createWithExcess;
import static ru.javawebinar.topjava.util.MealsUtil.getWithExcess;
import static ru.javawebinar.topjava.util.exception.ErrorType.VALIDATION_ERROR;
import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_DATETIME;

class MealRestControllerTest extends AbstractControllerTest {

Expand Down Expand Up @@ -135,7 +139,7 @@ void createInvalid() throws Exception {
.with(userHttpBasic(ADMIN)))
.andDo(print())
.andExpect(status().isUnprocessableEntity())
.andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name()))
.andExpect(errorType(VALIDATION_ERROR))
.andDo(print());
}

Expand All @@ -148,7 +152,36 @@ void updateInvalid() throws Exception {
.with(userHttpBasic(USER)))
.andDo(print())
.andExpect(status().isUnprocessableEntity())
.andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name()))
.andExpect(errorType(VALIDATION_ERROR))
.andDo(print());
}

@Test
@Transactional(propagation = Propagation.NEVER)
void updateDuplicate() throws Exception {
Meal invalid = new Meal(MEAL1_ID, MEAL2.getDateTime(), "Dummy", 200);

mockMvc.perform(MockMvcRequestBuilders.put(REST_URL + MEAL1_ID)
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.writeValue(invalid))
.with(userHttpBasic(USER)))
.andDo(print())
.andExpect(status().isConflict())
.andExpect(errorType(VALIDATION_ERROR))
.andExpect(detailMessage(EXCEPTION_DUPLICATE_DATETIME));
}

@Test
@Transactional(propagation = Propagation.NEVER)
void createDuplicate() throws Exception {
Meal invalid = new Meal(null, ADMIN_MEAL1.getDateTime(), "Dummy", 200);
mockMvc.perform(MockMvcRequestBuilders.post(REST_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(JsonUtil.writeValue(invalid))
.with(userHttpBasic(ADMIN)))
.andDo(print())
.andExpect(status().isConflict())
.andExpect(errorType(VALIDATION_ERROR))
.andExpect(detailMessage(EXCEPTION_DUPLICATE_DATETIME));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@
import org.springframework.http.MediaType;
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.javawebinar.topjava.model.Role;
import ru.javawebinar.topjava.model.User;
import ru.javawebinar.topjava.util.exception.ErrorType;
import ru.javawebinar.topjava.web.AbstractControllerTest;
import ru.javawebinar.topjava.web.json.JsonUtil;

import java.util.Collections;

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.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static ru.javawebinar.topjava.TestUtil.readFromJson;
import static ru.javawebinar.topjava.TestUtil.userHttpBasic;
import static ru.javawebinar.topjava.UserTestData.*;
import static ru.javawebinar.topjava.util.exception.ErrorType.VALIDATION_ERROR;
import static ru.javawebinar.topjava.web.ExceptionInfoHandler.EXCEPTION_DUPLICATE_EMAIL;

class AdminRestControllerTest extends AbstractControllerTest {

Expand Down Expand Up @@ -139,7 +143,7 @@ void createInvalid() throws Exception {
.with(userHttpBasic(ADMIN))
.content(JsonUtil.writeValue(expected)))
.andExpect(status().isUnprocessableEntity())
.andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name()))
.andExpect(errorType(VALIDATION_ERROR))
.andDo(print());
}

Expand All @@ -153,7 +157,35 @@ void updateInvalid() throws Exception {
.content(JsonUtil.writeValue(updated)))
.andExpect(status().isUnprocessableEntity())
.andDo(print())
.andExpect(jsonPath("$.type").value(ErrorType.VALIDATION_ERROR.name()))
.andExpect(errorType(VALIDATION_ERROR))
.andDo(print());
}
@Test
@Transactional(propagation = Propagation.NEVER)
void updateDuplicate() throws Exception {
User updated = new User(USER);
updated.setEmail("[email protected]");
mockMvc.perform(MockMvcRequestBuilders.put(REST_URL + USER_ID)
.contentType(MediaType.APPLICATION_JSON)
.with(userHttpBasic(ADMIN))
.content(jsonWithPassword(updated, "password")))
.andExpect(status().isConflict())
.andExpect(errorType(VALIDATION_ERROR))
.andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL))
.andDo(print());
}

@Test
@Transactional(propagation = Propagation.NEVER)
void createDuplicate() throws Exception {
User expected = new User(null, "New", "[email protected]", "newPass", 2300, Role.ROLE_USER, Role.ROLE_ADMIN);
mockMvc.perform(MockMvcRequestBuilders.post(REST_URL)
.contentType(MediaType.APPLICATION_JSON)
.with(userHttpBasic(ADMIN))
.content(jsonWithPassword(expected, "newPass")))
.andExpect(status().isConflict())
.andExpect(errorType(VALIDATION_ERROR))
.andExpect(detailMessage(EXCEPTION_DUPLICATE_EMAIL));

}
}
Loading

0 comments on commit 5fe680c

Please sign in to comment.