Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.time.LocalDate;

import jakarta.validation.constraints.Future;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.BaseEntity;

Expand All @@ -37,16 +38,17 @@ public class Visit extends BaseEntity {

@Column(name = "visit_date")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Future(message = "Visit date must be in the future")
private LocalDate date;

@NotBlank
private String description;

/**
* Creates a new instance of Visit for the current date
* Creates a new instance of Visit for at least tommorow
*/
public Visit() {
this.date = LocalDate.now();
this.date = LocalDate.now().plusDays(1);
}

public LocalDate getDate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.samples.petclinic.owner;

import java.time.LocalDate;
import java.util.Map;
import java.util.Optional;

Expand Down Expand Up @@ -91,6 +92,11 @@ public String initNewVisitForm() {
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
public String processNewVisitForm(@ModelAttribute Owner owner, @PathVariable int petId, @Valid Visit visit,
BindingResult result, RedirectAttributes redirectAttributes) {

// Manual Backend Check (as requested by the specification)
if (visit.getDate() != null && !visit.getDate().isAfter(LocalDate.now())) {
result.rejectValue("date", "invalid", "Visit date must be in the future");
}
if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm";
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/templates/fragments/inputField.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="col-sm-10">
<div th:switch="${type}">
<input th:case="'text'" class="form-control" type="text" th:field="*{__${name}__}" />
<input th:case="'date'" class="form-control" type="date" th:field="*{__${name}__}" />
<input th:case="'date'" class="form-control" type="date" th:field="*{__${name}__}" th:min="${minVal}" />
</div>
<span th:if="${valid}" class="fa fa-ok form-control-feedback" aria-hidden="true"></span>
<th:block th:if="${!valid}">
Expand All @@ -24,4 +24,4 @@
</form>
</body>

</html>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ <h2>

<form th:object="${visit}" class="form-horizontal" method="post">
<div class="form-group has-feedback">
<input th:replace="~{fragments/inputField :: input ('Date', 'date', 'date')}" />
<input th:replace="~{fragments/inputField :: input (label='Date', name='date', type='date',minVal=${visit?.date})}" />
<input th:replace="~{fragments/inputField :: input ('Description', 'description', 'text')}" />
</div>

Expand All @@ -56,4 +56,4 @@ <h2>

</body>

</html>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@

import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDate;
import java.util.Locale;
import java.util.Set;

import org.junit.jupiter.api.Test;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.samples.petclinic.owner.Visit;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import jakarta.validation.ConstraintViolation;
Expand Down Expand Up @@ -57,4 +59,21 @@ void shouldNotValidateWhenFirstNameEmpty() {
assertThat(violation.getMessage()).isEqualTo("must not be blank");
}

@Test
void shouldNotValidateWhenVisitDateIsNotInFuture() {

LocaleContextHolder.setLocale(Locale.ENGLISH);
Visit visit = new Visit();
visit.setDate(LocalDate.now());
visit.setDescription("any description");

Validator validator = createValidator();
Set<ConstraintViolation<Visit>> constraintViolations = validator.validate(visit);

assertThat(constraintViolations).hasSize(1);
ConstraintViolation<Visit> violation = constraintViolations.iterator().next();
assertThat(violation.getPropertyPath()).hasToString("date");
assertThat(violation.getMessage()).isEqualTo("Visit date must be in the future");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.samples.petclinic.owner;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
Expand All @@ -31,7 +32,9 @@
import org.springframework.test.context.aot.DisabledInAotMode;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.validation.BindingResult;

import java.time.LocalDate;
import java.util.Optional;

/**
Expand Down Expand Up @@ -76,7 +79,8 @@ void processNewVisitFormSuccess() throws Exception {
mockMvc
.perform(post("/owners/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, TEST_PET_ID)
.param("name", "George")
.param("description", "Visit Description"))
.param("description", "Visit Description")
.param("date", String.valueOf(LocalDate.now().plusDays(1))))
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}"));
}
Expand All @@ -91,4 +95,24 @@ void processNewVisitFormHasErrors() throws Exception {
.andExpect(view().name("pets/createOrUpdateVisitForm"));
}

@Test
void processNewVisitFormHasErrorsWhenDateIsNotInFuture() throws Exception {
mockMvc
.perform(post("/owners/{ownerId}/pets/{petId}/visits/new", TEST_OWNER_ID, TEST_PET_ID)
.param("name", "George")
.param("description", "Visit Description") // Removed 'name' for clarity
.param("date", String.valueOf(LocalDate.now())))
.andExpect(status().isOk())
.andExpect(model().attributeHasFieldErrors("visit", "date"))
.andExpect(model().attributeHasFieldErrorCode("visit", "date", "Future"))
.andExpect(view().name("pets/createOrUpdateVisitForm"))
.andExpect(result -> {
BindingResult bindingResult = (BindingResult) result.getModelAndView()
.getModel()
.get("org.springframework.validation.BindingResult.visit");
String message = bindingResult.getFieldError("date").getDefaultMessage();
assertEquals("Visit date must be in the future", message);
});
}

}