Skip to content

Commit

Permalink
Add NationalIdService and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Imorate committed Sep 30, 2024
1 parent 86692e0 commit 313cc38
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 0 deletions.
11 changes: 11 additions & 0 deletions src/main/java/com/persiantools4j/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.persiantools4j;

import com.persiantools4j.exception.ValidationException;

public interface Validator<T> {

boolean isValid(T t);

void validate(T t) throws ValidationException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.persiantools4j.exception;

public class ValidationException extends IllegalArgumentException {

public ValidationException(String s) {
super(s);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.persiantools4j.nationalid.service;

import com.persiantools4j.Validator;

public interface NationalIdService extends Validator<String> {

}
Original file line number Diff line number Diff line change
@@ -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<Integer, Integer, Integer> 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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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<Arguments> validNationalIdCases() {
return Stream.of(
Arguments.of("6104038931"),
Arguments.of("3111001008"),
Arguments.of("3413857606"),
Arguments.of("9731001018"),
Arguments.of("3520082780")
);
}

private static Stream<Arguments> 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);
}
}

0 comments on commit 313cc38

Please sign in to comment.