Implemented Validation of AccountForm. #13
parent
5a68b1484a
commit
34e6e55ea4
4
pom.xml
4
pom.xml
|
@ -40,6 +40,10 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
|
|
@ -87,4 +87,19 @@ public class AccountCrudTest {
|
|||
.andExpect(jsonPath("$.receivers", is(List.of("vip@account.com"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAccountReturns400OnInvalidName() throws Exception {
|
||||
AccountForm form = new AccountForm("-- totally invalid --", List.of("vip@my-company.com"));
|
||||
mockMvc.perform(post("/accounts").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(form)))
|
||||
.andExpect(status().is4xxClientError())
|
||||
.andExpect(content().contentType(APPLICATION_JSON));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAccountReturns400OnInvalidReceiver() throws Exception {
|
||||
AccountForm form = new AccountForm("valid-name", List.of("valid@my-company.com", "in valid @my-company.com"));
|
||||
mockMvc.perform(post("/accounts").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(form)))
|
||||
.andExpect(status().is4xxClientError())
|
||||
.andExpect(content().contentType(APPLICATION_JSON));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package de.nclazz.service.mailrelay;
|
||||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
|
||||
import javax.mail.Session;
|
||||
|
@ -10,7 +10,7 @@ import javax.mail.internet.MimeMessage;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Configuration
|
||||
@TestConfiguration
|
||||
public class IntegrationTestConfiguration {
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -7,7 +7,8 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
|
@ -17,19 +18,28 @@ import java.time.LocalDateTime;
|
|||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("integration")
|
||||
@ActiveProfiles({ "integration", "non-async" })
|
||||
public class MailMessageForwarderTest {
|
||||
|
||||
static JavaMailSender javaMailSender = mock(JavaMailSender.class);
|
||||
static MimeMessage mimeMessage = new MimeMessage((Session) null);
|
||||
|
||||
@TestConfiguration
|
||||
static class MailTestConfiguration {
|
||||
@Bean
|
||||
public JavaMailSender javaMailSender() {
|
||||
when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage);
|
||||
return javaMailSender;
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private Relay relay;
|
||||
|
||||
@MockBean
|
||||
private JavaMailSender javaMailSender;
|
||||
|
||||
private MimeMessage mimeMessage = new MimeMessage((Session) null);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
|
|
|
@ -2,8 +2,10 @@ package de.nclazz.service.mailrelay;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("integration")
|
||||
class MailRelayApplicationTest {
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package de.nclazz.service.mailrelay;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
@Profile("!non-async")
|
||||
public class AsyncConfiguration {
|
||||
}
|
|
@ -3,11 +3,9 @@ package de.nclazz.service.mailrelay;
|
|||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
@EnableAsync
|
||||
@SpringBootApplication
|
||||
public class MailRelayApplication {
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
package de.nclazz.service.mailrelay.adapter.web;
|
||||
|
||||
import de.nclazz.service.mailrelay.domain.Account;
|
||||
import de.nclazz.service.mailrelay.domain.StringUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
|
@ -12,8 +16,13 @@ import java.util.List;
|
|||
@NoArgsConstructor
|
||||
public class AccountForm {
|
||||
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[^-_](?:[a-zA-Z_-]*[^-_])?$")
|
||||
private String name;
|
||||
private List<String> receivers;
|
||||
|
||||
@NotNull
|
||||
private List<@Pattern(regexp = StringUtils.EMAIL_RFC_5322_REGEX) String> receivers;
|
||||
|
||||
public Account toAccount() {
|
||||
return Account.of(this.name, this.receivers);
|
||||
|
|
|
@ -5,18 +5,23 @@ import de.nclazz.service.mailrelay.domain.Relay;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("accounts")
|
||||
|
@ -26,7 +31,7 @@ public class AccountRestController {
|
|||
private final Relay relay;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Account> addAccount(@RequestBody AccountForm form) {
|
||||
public ResponseEntity<Account> addAccount(@RequestBody @Valid AccountForm form) {
|
||||
Account account = form.toAccount();
|
||||
account = this.relay.saveAccount(account);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(account);
|
||||
|
@ -50,11 +55,20 @@ public class AccountRestController {
|
|||
}
|
||||
|
||||
@PutMapping("{guid}")
|
||||
public Account updateByGuid(@PathVariable("guid")UUID guid, @RequestBody AccountForm form) {
|
||||
public Account updateByGuid(@PathVariable("guid")UUID guid, @RequestBody @Valid AccountForm form) {
|
||||
Account account = this.relay.findAccount(guid)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
account.setName(form.getName());
|
||||
account.setReceivers(form.getReceivers());
|
||||
return this.relay.saveAccount(account);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public List<ValidationError> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
return ex.getBindingResult()
|
||||
.getAllErrors().stream()
|
||||
.map(ValidationError::fromObjectError)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package de.nclazz.service.mailrelay.adapter.web;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ValidationError {
|
||||
|
||||
private String target;
|
||||
private String message;
|
||||
private Object rejected;
|
||||
|
||||
public static ValidationError fromObjectError(ObjectError error) {
|
||||
return new ValidationError(
|
||||
((FieldError)error).getField(),
|
||||
error.getDefaultMessage(),
|
||||
((FieldError) error).getRejectedValue()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
@ -54,6 +55,7 @@ public class Message {
|
|||
@Column(name = "created_at")
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
@ToString.Exclude
|
||||
@JsonIgnore
|
||||
@OneToOne
|
||||
private Account account;
|
||||
|
|
|
@ -2,6 +2,8 @@ package de.nclazz.service.mailrelay.domain;
|
|||
|
||||
public abstract class StringUtils {
|
||||
|
||||
public static final String EMAIL_RFC_5322_REGEX = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";
|
||||
|
||||
private StringUtils() { /* no-op */ }
|
||||
|
||||
public static boolean isStringEmpty(String input) {
|
||||
|
@ -11,4 +13,6 @@ public abstract class StringUtils {
|
|||
return input.trim().isEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package de.nclazz.service.mailrelay.adapter.web;
|
||||
|
||||
import org.hibernate.validator.HibernateValidator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class AccountFormValidationTest {
|
||||
|
||||
private LocalValidatorFactoryBean localValidatorFactory;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
localValidatorFactory = new LocalValidatorFactoryBean();
|
||||
localValidatorFactory.setProviderClass(HibernateValidator.class);
|
||||
localValidatorFactory.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameAsEmptyStringShouldBeNotValid() {
|
||||
AccountForm form = new AccountForm("", List.of());
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameIsMissingShouldBeNotValid() {
|
||||
AccountForm form = new AccountForm(null, List.of());
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameWithSingleCharacterShouldBeValid() {
|
||||
AccountForm form = new AccountForm("a", List.of());
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameContainingNonAlphaNumericCharsShouldBeNotValid() {
|
||||
AccountForm form = new AccountForm("a_.+a", List.of());
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameStartingWithHyphenShouldBeNotValid() {
|
||||
AccountForm form = new AccountForm("-name", List.of());
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameContainingWithHyphenShouldBeValid() {
|
||||
AccountForm form = new AccountForm("prefix-name", List.of());
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void receiversWithEmailAddressesShouldBeValid() {
|
||||
AccountForm form = new AccountForm("legal-name", List.of("vip@company.com", "mail@example.com"));
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void receiversWithInvalidEmailAddressesShouldBeVNotalid() {
|
||||
AccountForm form = new AccountForm("legal-name", List.of("vip @ company.com", "--mail@example.com"));
|
||||
|
||||
assertThat(localValidatorFactory.validate(form))
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
}
|
|
@ -76,7 +76,9 @@ class ForwardMessageTest {
|
|||
List.of("info@company.com")
|
||||
);
|
||||
|
||||
Relay relay = TestRelayBuilder.builder().build();
|
||||
Relay relay = TestRelayBuilder.builder()
|
||||
.messageForwarder(forwarder)
|
||||
.build();
|
||||
relay.saveAccount(account);
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
|
Loading…
Reference in New Issue