diff --git a/pom.xml b/pom.xml index 622e927..5cbeab4 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-starter-data-jpa diff --git a/src/main/java/eu/ztsh/wymiana/validation/Adult.java b/src/main/java/eu/ztsh/wymiana/validation/Adult.java new file mode 100644 index 0000000..affba90 --- /dev/null +++ b/src/main/java/eu/ztsh/wymiana/validation/Adult.java @@ -0,0 +1,24 @@ +package eu.ztsh.wymiana.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ FIELD, PARAMETER }) +@Retention(RUNTIME) +@Constraint(validatedBy = AdultValidator.class) +@Documented +public @interface Adult { + String message() default "{jakarta.validation.constraints.Adult.message}"; + + Class[] groups() default { }; + + Class[] payload() default { }; +} diff --git a/src/main/java/eu/ztsh/wymiana/validation/AdultValidator.java b/src/main/java/eu/ztsh/wymiana/validation/AdultValidator.java new file mode 100644 index 0000000..47e2255 --- /dev/null +++ b/src/main/java/eu/ztsh/wymiana/validation/AdultValidator.java @@ -0,0 +1,42 @@ +package eu.ztsh.wymiana.validation; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Arrays; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class AdultValidator implements ConstraintValidator { + + private final Pattern pattern = Pattern.compile("\\d{11}"); + private final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd"); + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (!pattern.matcher(value).matches()) { + return false; + } + var datePart = Arrays.stream(value.substring(0, 6).split("")).map(Integer::parseInt).toArray(Integer[]::new); + final String prefix; + if (datePart[2] > 1) { + datePart[2] = (datePart[2] - 2); + prefix = "20"; + } else { + prefix = "19"; + } + var dateStamp = prefix.concat(Arrays.stream(datePart).map(Objects::toString).collect(Collectors.joining())); + try { + return LocalDate.parse(dateStamp, dtf) + .plusYears(18) + .isBefore(LocalDate.now(context.getClockProvider().getClock())); + } catch (DateTimeParseException exception) { + return false; + } + } + +} diff --git a/src/main/java/eu/ztsh/wymiana/web/model/UserCreateRequest.java b/src/main/java/eu/ztsh/wymiana/web/model/UserCreateRequest.java new file mode 100644 index 0000000..32d884e --- /dev/null +++ b/src/main/java/eu/ztsh/wymiana/web/model/UserCreateRequest.java @@ -0,0 +1,13 @@ +package eu.ztsh.wymiana.web.model; + +import eu.ztsh.wymiana.validation.Adult; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +public record UserCreateRequest( + @NotNull String name, + @NotNull String surname, + @Adult String pesel, + @Min(0) int pln) { + +} diff --git a/src/test/java/eu/ztsh/wymiana/validation/AdultValidatorTest.java b/src/test/java/eu/ztsh/wymiana/validation/AdultValidatorTest.java new file mode 100644 index 0000000..ca24718 --- /dev/null +++ b/src/test/java/eu/ztsh/wymiana/validation/AdultValidatorTest.java @@ -0,0 +1,58 @@ +package eu.ztsh.wymiana.validation; + +import jakarta.validation.ConstraintValidatorContext; +import org.hibernate.validator.internal.engine.DefaultClockProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; + +class AdultValidatorTest { + + private static AdultValidator validator; + private static ConstraintValidatorContext validatorContext; + + @BeforeAll + static void prepare() { + validator = new AdultValidator(); + validatorContext = Mockito.mock(ConstraintValidatorContext.class); + Mockito.when(validatorContext.getClockProvider()).thenReturn(DefaultClockProvider.INSTANCE); + } + + @Test + @DisplayName("No digits in PESEL") + void invalidPatternTest() { + assertThat(call("notAPesel")).isFalse(); + } + + @Test + @DisplayName("Not an adult") + void notAnAdultTest() { + assertThat(call("24242400000")).isFalse(); + } + + @Test + @DisplayName("Adult") + void adultTest() { + assertThat(call("88010100000")).isTrue(); + } + + @Test + @DisplayName("Elderly person") + void seniorTest() { + assertThat(call("00010100000")).isTrue(); + } + + @Test + @DisplayName("Invalid date") + void notAValidDateTest() { + assertThat(call("00919100000")).isFalse(); + } + + private boolean call(String value) { + return validator.isValid(value, validatorContext); + } + +}