From 313cc380c0cff6f9ec9379899436bc36a3b9d3aa Mon Sep 17 00:00:00 2001 From: Amir Mohammad Hl Date: Tue, 1 Oct 2024 01:00:16 +0330 Subject: [PATCH] Add NationalIdService and tests --- .../java/com/persiantools4j/Validator.java | 11 ++ .../exception/ValidationException.java | 9 ++ .../nationalid/service/NationalIdService.java | 7 ++ .../service/impl/NationalIdServiceImpl.java | 67 +++++++++++ .../impl/NationalIdServiceImplTest.java | 112 ++++++++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 src/main/java/com/persiantools4j/Validator.java create mode 100644 src/main/java/com/persiantools4j/exception/ValidationException.java create mode 100644 src/main/java/com/persiantools4j/nationalid/service/NationalIdService.java create mode 100644 src/main/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImpl.java create mode 100644 src/test/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImplTest.java diff --git a/src/main/java/com/persiantools4j/Validator.java b/src/main/java/com/persiantools4j/Validator.java new file mode 100644 index 0000000..7c59078 --- /dev/null +++ b/src/main/java/com/persiantools4j/Validator.java @@ -0,0 +1,11 @@ +package com.persiantools4j; + +import com.persiantools4j.exception.ValidationException; + +public interface Validator { + + boolean isValid(T t); + + void validate(T t) throws ValidationException; + +} diff --git a/src/main/java/com/persiantools4j/exception/ValidationException.java b/src/main/java/com/persiantools4j/exception/ValidationException.java new file mode 100644 index 0000000..335f70a --- /dev/null +++ b/src/main/java/com/persiantools4j/exception/ValidationException.java @@ -0,0 +1,9 @@ +package com.persiantools4j.exception; + +public class ValidationException extends IllegalArgumentException { + + public ValidationException(String s) { + super(s); + } + +} diff --git a/src/main/java/com/persiantools4j/nationalid/service/NationalIdService.java b/src/main/java/com/persiantools4j/nationalid/service/NationalIdService.java new file mode 100644 index 0000000..f99a05a --- /dev/null +++ b/src/main/java/com/persiantools4j/nationalid/service/NationalIdService.java @@ -0,0 +1,7 @@ +package com.persiantools4j.nationalid.service; + +import com.persiantools4j.Validator; + +public interface NationalIdService extends Validator { + +} diff --git a/src/main/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImpl.java b/src/main/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImpl.java new file mode 100644 index 0000000..6ccd02d --- /dev/null +++ b/src/main/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImpl.java @@ -0,0 +1,67 @@ +package com.persiantools4j.nationalid.service.impl; + +import com.persiantools4j.exception.ValidationException; +import com.persiantools4j.nationalid.service.NationalIdService; +import com.persiantools4j.utils.NumberUtils; + +import java.util.function.BiFunction; +import java.util.stream.IntStream; + +public class NationalIdServiceImpl implements NationalIdService { + + public static volatile NationalIdServiceImpl instance; + + private NationalIdServiceImpl() { + + } + + public static NationalIdServiceImpl getInstance() { + if (instance == null) { + synchronized (NationalIdServiceImpl.class) { + if (instance == null) { + instance = new NationalIdServiceImpl(); + } + } + } + return instance; + } + + @Override + public boolean isValid(String nationalId) { + try { + validate(nationalId); + } catch (ValidationException e) { + return false; + } + return true; + } + + @Override + public void validate(String nationalId) { + validateNationalIdFormat(nationalId); + int controlDigit = NumberUtils.getNumericValue(nationalId, nationalId.length() - 1); + BiFunction digitCalculator = (partialResult, index) -> { + int digit = NumberUtils.getNumericValue(nationalId, index); + return partialResult + digit * (10 - index); + }; + int sum = IntStream.range(0, 9) + .boxed() + .reduce(0, digitCalculator, Integer::sum); + int remainder = sum % 11; + boolean remainderLessThanTwo = (remainder < 2) && (controlDigit == remainder); + boolean remainderEqualAndMoreThanTwo = (remainder >= 2) && (remainder + controlDigit == 11); + if (!remainderLessThanTwo && !remainderEqualAndMoreThanTwo) { + throw new ValidationException("Invalid NationalId: " + nationalId); + } + } + + private void validateNationalIdFormat(String nationalId) { + if (nationalId == null) { + throw new ValidationException("National ID is null"); + } + if (!nationalId.matches("^\\d{10}$") || nationalId.matches("^(\\d)\\1{9}$")) { + throw new ValidationException("Invalid National ID format: " + nationalId); + } + } + +} diff --git a/src/test/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImplTest.java b/src/test/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImplTest.java new file mode 100644 index 0000000..13d56fe --- /dev/null +++ b/src/test/java/com/persiantools4j/nationalid/service/impl/NationalIdServiceImplTest.java @@ -0,0 +1,112 @@ +package com.persiantools4j.nationalid.service.impl; + +import com.persiantools4j.exception.ValidationException; +import com.persiantools4j.nationalid.service.NationalIdService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DisplayName("National ID service") +class NationalIdServiceImplTest { + + private NationalIdService nationalIdService; + + private static Stream validNationalIdCases() { + return Stream.of( + Arguments.of("6104038931"), + Arguments.of("3111001008"), + Arguments.of("3413857606"), + Arguments.of("9731001018"), + Arguments.of("3520082780") + ); + } + + private static Stream invalidNationalIdCases() { + return Stream.of( + Arguments.of(""), + Arguments.of((Object) null), + Arguments.of("6104038932"), + Arguments.of("3111001003"), + Arguments.of("3413857604"), + Arguments.of("9731001011"), + Arguments.of("3520082782"), + Arguments.of("0000000000"), + Arguments.of("1111111111"), + Arguments.of("2222222222"), + Arguments.of("3333333333"), + Arguments.of("4444444444"), + Arguments.of("5555555555"), + Arguments.of("6666666666"), + Arguments.of("7777777777"), + Arguments.of("8888888888"), + Arguments.of("9999999999") + ); + } + + @BeforeEach + void setUp() { + nationalIdService = NationalIdServiceImpl.getInstance(); + } + + @Test + @DisplayName("Get instance test") + void testGetInstance() { + NationalIdService firstNationalIdService = NationalIdServiceImpl.getInstance(); + assertThat(firstNationalIdService).isNotNull(); + NationalIdService secondNationalIdService = NationalIdServiceImpl.getInstance(); + assertThat(secondNationalIdService).isNotNull(); + assertThat(firstNationalIdService).isSameAs(secondNationalIdService); + } + + @Test + @DisplayName("Get instance thread-safe test") + void testGetInstanceThreadSafe() throws InterruptedException { + final NationalIdService[] nationalIdServices = new NationalIdService[2]; + Thread firstThread = new Thread(() -> nationalIdServices[0] = NationalIdServiceImpl.getInstance()); + Thread secondThread = new Thread(() -> nationalIdServices[1] = NationalIdServiceImpl.getInstance()); + firstThread.start(); + secondThread.start(); + firstThread.join(); + secondThread.join(); + assertThat(nationalIdServices[0]).isNotNull(); + assertThat(nationalIdServices[1]).isNotNull(); + assertThat(nationalIdServices[0]).isSameAs(nationalIdServices[1]); + } + + @ParameterizedTest + @MethodSource("validNationalIdCases") + @DisplayName("Valid National ID test") + void testValidNationalId(String nationalId) { + assertThat(nationalIdService.isValid(nationalId)).isTrue(); + } + + @ParameterizedTest + @MethodSource("invalidNationalIdCases") + @DisplayName("Invalid National ID test") + void testInvalidNationalId(String nationalId) { + assertThat(nationalIdService.isValid(nationalId)).isFalse(); + } + + @ParameterizedTest + @MethodSource("validNationalIdCases") + @DisplayName("National ID validation test") + void testValidateNationalId(String nationalId) { + assertThatCode(() -> nationalIdService.validate(nationalId)).doesNotThrowAnyException(); + } + + @ParameterizedTest + @MethodSource("invalidNationalIdCases") + @DisplayName("Exceptional validation National ID test") + void testExceptionValidateNationalId(String nationalId) { + assertThatThrownBy(() -> nationalIdService.validate(nationalId)).isInstanceOf(ValidationException.class); + } +}