From 2f98a082b437dd4ad2968a882b0320c78ad55e72 Mon Sep 17 00:00:00 2001 From: Youssef MAHTAT Date: Mon, 1 Dec 2025 07:23:47 +0100 Subject: [PATCH] MOSIP-42804 - increasing the code coverage Signed-off-by: Youssef MAHTAT --- .../LanguageCharacterValidatorTest.java | 79 ++ .../impl/CacheManagementServiceImplTest.java | 121 +++ .../CenterTypeCodeValidatorTest.java | 57 ++ .../CustomIntegerDeserializerTest.java | 58 ++ .../test/validator/FieldValidatorTest.java | 87 +- .../StatusCodeValidatorTest.java | 53 ++ .../registereddevice/StatusCodeValueTest.java | 57 ++ .../test/helper/ClientSettingsHelperTest.java | 502 ++++++++++ .../test/helper/IdentitySchemaHelperTest.java | 137 +++ .../test/helper/KeymanagerHelperTest.java | 169 ++++ .../impl/SyncAuthTokenServiceImplTest.java | 316 ++++++ .../impl/SyncMasterDataServiceImplTest.java | 896 ++++++++++++++++++ .../test/impl/SyncRolesServiceImplTest.java | 222 +++++ .../impl/SyncUserDetailsServiceImplTest.java | 389 ++++++++ .../service/SyncJobHelperServiceTest.java | 113 ++- .../SyncMasterDataServiceHelperTest.java | 604 +++++++++++- .../PartnerCACertEventSubscriberTest.java | 70 ++ 17 files changed, 3878 insertions(+), 52 deletions(-) create mode 100644 admin/admin-service/src/test/java/io/mosip/admin/validator/LanguageCharacterValidatorTest.java create mode 100644 admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/impl/CacheManagementServiceImplTest.java create mode 100644 admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CenterTypeCodeValidatorTest.java create mode 100644 admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CustomIntegerDeserializerTest.java create mode 100644 admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValidatorTest.java create mode 100644 admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValueTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/ClientSettingsHelperTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/IdentitySchemaHelperTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/KeymanagerHelperTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncAuthTokenServiceImplTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncMasterDataServiceImplTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncRolesServiceImplTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncUserDetailsServiceImplTest.java create mode 100644 admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/websub/PartnerCACertEventSubscriberTest.java diff --git a/admin/admin-service/src/test/java/io/mosip/admin/validator/LanguageCharacterValidatorTest.java b/admin/admin-service/src/test/java/io/mosip/admin/validator/LanguageCharacterValidatorTest.java new file mode 100644 index 0000000000..74eb510808 --- /dev/null +++ b/admin/admin-service/src/test/java/io/mosip/admin/validator/LanguageCharacterValidatorTest.java @@ -0,0 +1,79 @@ +package io.mosip.admin.validator; + +import jakarta.validation.ConstraintValidatorContext; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.*; + +public class LanguageCharacterValidatorTest { + + private LanguageCharacterValidator validator; + + @Before + public void setUp() { + validator = new LanguageCharacterValidator(); + } + + @Test + public void isValidShouldReturnTrueWhenAllowedCharactersRegexIsNull() { + // given + ReflectionTestUtils.setField(validator, "allowedCharactersRegex", null); + // when + boolean result = validator.isValid("AnyValue123", (ConstraintValidatorContext) null); + // then + assertTrue(result); + } + + @Test + public void isValidShouldReturnTrueWhenValueIsNull() { + // given + ReflectionTestUtils.setField(validator, "allowedCharactersRegex", "[^a-zA-Z]"); + // when + boolean result = validator.isValid(null, (ConstraintValidatorContext) null); + // then + assertTrue(result); + } + + @Test + public void isValidShouldReturnTrueWhenValueIsEmpty() { + // given + ReflectionTestUtils.setField(validator, "allowedCharactersRegex", "[^a-zA-Z]"); + // when + boolean result = validator.isValid("", (ConstraintValidatorContext) null); + // then + assertTrue(result); + } + + @Test + public void isValidShouldReturnTrueWhenValueContainsOnlyAllowedCharacters() { + // given + ReflectionTestUtils.setField(validator, "allowedCharactersRegex", "[^a-zA-Z]"); + // when + boolean result = validator.isValid("AlphaBeta", (ConstraintValidatorContext) null); + // then + assertTrue(result); + } + + @Test + public void isValidShouldReturnFalseWhenValueContainsNotAllowedCharacters() { + // given + ReflectionTestUtils.setField(validator, "allowedCharactersRegex", "[^a-zA-Z]"); + // when + boolean result = validator.isValid("Alpha1", (ConstraintValidatorContext) null); + // then + assertFalse(result); + } + + @Test + public void isValidShouldTrimValueBeforeValidation() { + // given + ReflectionTestUtils.setField(validator, "allowedCharactersRegex", "[^a-zA-Z]"); + // when + boolean result = validator.isValid(" Alpha ", (ConstraintValidatorContext) null); + // then + assertTrue(result); + } + +} \ No newline at end of file diff --git a/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/impl/CacheManagementServiceImplTest.java b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/impl/CacheManagementServiceImplTest.java new file mode 100644 index 0000000000..4d47931687 --- /dev/null +++ b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/impl/CacheManagementServiceImplTest.java @@ -0,0 +1,121 @@ +package io.mosip.kernel.masterdata.test.impl; + +import io.mosip.kernel.masterdata.service.impl.CacheManagementServiceImpl; +import io.mosip.kernel.masterdata.utils.CacheName; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.mockito.Mockito.*; + + +@RunWith(MockitoJUnitRunner.class) +public class CacheManagementServiceImplTest { + + @Mock + private CacheManager cacheManager; + + @Mock + private Cache cache1; + + @Mock + private Cache cache2; + + @InjectMocks + private CacheManagementServiceImpl cacheManagementService; + + + @Test + public void clearCacheByCacheNameShouldClearMatchingCache() { + // given + CacheName cacheName = CacheName.DOCUMENT_CATEGORY; + Set cacheNames = new HashSet<>(); + cacheNames.add(cacheName.getName()); + when(cacheManager.getCacheNames()).thenReturn(cacheNames); + when(cacheManager.getCache(cacheName.getName())).thenReturn(cache1); + // when + cacheManagementService.clearCacheByCacheName(cacheName); + // then + verify(cacheManager, times(1)).getCacheNames(); + verify(cacheManager, times(1)).getCache(cacheName.getName()); + verify(cache1, times(1)).clear(); + verifyNoMoreInteractions(cache1); + } + + @Test + public void clearCacheByCacheNameShouldNotFailWhenCacheIsNull() { + // given + CacheName cacheName = CacheName.BLOCK_LISTED_WORDS; + Set cacheNames = new HashSet<>(); + cacheNames.add(cacheName.getName()); + when(cacheManager.getCacheNames()).thenReturn(cacheNames); + when(cacheManager.getCache(cacheName.getName())).thenReturn(null); + // when + cacheManagementService.clearCacheByCacheName(cacheName); + // then + verify(cacheManager, times(1)).getCacheNames(); + verify(cacheManager, times(1)).getCache(cacheName.getName()); + verifyNoInteractions(cache1, cache2); + } + + @Test + public void clearCacheByCacheNameShouldDoNothingWhenNoNameMatches() { + // given + CacheName cacheName = CacheName.DYNAMIC_FIELD; + Set cacheNames = new HashSet<>(Arrays.asList("OTHER_CACHE_1", "OTHER_CACHE_2")); + when(cacheManager.getCacheNames()).thenReturn(cacheNames); + when(cacheManager.getCache("OTHER_CACHE_1")).thenReturn(cache1); + when(cacheManager.getCache("OTHER_CACHE_2")).thenReturn(cache2); + // when + cacheManagementService.clearCacheByCacheName(cacheName); + // then + verify(cacheManager, times(1)).getCacheNames(); + verify(cacheManager, times(1)).getCache("OTHER_CACHE_1"); + verify(cacheManager, times(1)).getCache("OTHER_CACHE_2"); + verify(cache1, never()).clear(); + verify(cache2, never()).clear(); + } + + @Test + public void clearCacheShouldClearAllNonNullCaches() { + // given + Set cacheNames = new HashSet<>(Arrays.asList("cache1", "cache2")); + when(cacheManager.getCacheNames()).thenReturn(cacheNames); + when(cacheManager.getCache("cache1")).thenReturn(cache1); + when(cacheManager.getCache("cache2")).thenReturn(cache2); + // when + cacheManagementService.clearCache(); + // then + verify(cacheManager, times(1)).getCacheNames(); + verify(cacheManager, times(1)).getCache("cache1"); + verify(cacheManager, times(1)).getCache("cache2"); + verify(cache1, times(1)).clear(); + verify(cache2, times(1)).clear(); + } + + @Test + public void clearCacheShouldIgnoreNullCaches() { + // given + Set cacheNames = new HashSet<>(Arrays.asList("cache1", "cache2")); + when(cacheManager.getCacheNames()).thenReturn(cacheNames); + when(cacheManager.getCache("cache1")).thenReturn(cache1); + when(cacheManager.getCache("cache2")).thenReturn(null); + // when + cacheManagementService.clearCache(); + // then + verify(cacheManager, times(1)).getCacheNames(); + verify(cacheManager, times(1)).getCache("cache1"); + verify(cacheManager, times(1)).getCache("cache2"); + verify(cache1, times(1)).clear(); // seul cache1 non null + verifyNoMoreInteractions(cache1); + } + +} \ No newline at end of file diff --git a/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CenterTypeCodeValidatorTest.java b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CenterTypeCodeValidatorTest.java new file mode 100644 index 0000000000..8c61c3acc4 --- /dev/null +++ b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CenterTypeCodeValidatorTest.java @@ -0,0 +1,57 @@ +package io.mosip.kernel.masterdata.test.validator; + +import io.mosip.kernel.masterdata.validator.CenterTypeCodeValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class CenterTypeCodeValidatorTest { + + private CenterTypeCodeValidator validator; + + @BeforeEach + void setup() { + validator = new CenterTypeCodeValidator(); + // Simulation de l’injection @Value + validator.setCenterTypeCodeValidate("^[A-Z]{3}[0-9]{2}$"); + // Exemple de regex : 3 lettres + 2 chiffres + } + + @Test + void testValidValue() { + boolean result = validator.isValid("ABC12", null); + assertTrue(result); + } + + @Test + void testInvalidValue() { + boolean result = validator.isValid("A12", null); + assertFalse(result); + } + + @Test + void testLowercaseInvalid() { + boolean result = validator.isValid("abc12", null); + assertFalse(result); + } + + @Test + void testNullValueThrowsException() { + assertThrows(NullPointerException.class, () -> validator.isValid(null, null)); + } + + @Test + void testEmptyValue() { + boolean result = validator.isValid("", null); + assertFalse(result); + } + + @Test + void testRegexChange() { + validator.setCenterTypeCodeValidate("^[0-9]{4}$"); + assertTrue(validator.isValid("1234", null)); + assertFalse(validator.isValid("ABC1", null)); + } + +} \ No newline at end of file diff --git a/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CustomIntegerDeserializerTest.java b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CustomIntegerDeserializerTest.java new file mode 100644 index 0000000000..17dd0ee772 --- /dev/null +++ b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/CustomIntegerDeserializerTest.java @@ -0,0 +1,58 @@ +package io.mosip.kernel.masterdata.test.validator; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import io.mosip.kernel.masterdata.validator.CustomIntegerDeserializer; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +public class CustomIntegerDeserializerTest { + + private ObjectMapper buildMapper() { + ObjectMapper mapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addDeserializer(Object.class, new CustomIntegerDeserializer()); + mapper.registerModule(module); + return mapper; + } + + @Test + void testValidInteger() throws Exception { + ObjectMapper mapper = buildMapper(); + String json = "123"; + Object result = mapper.readValue(json, Object.class); + assertTrue(result instanceof Integer); + assertEquals(123, result); + } + + @Test + void testInvalidFloat() { + ObjectMapper mapper = buildMapper(); + String json = "123.45"; + Exception ex = assertThrows(IOException.class, () -> { + mapper.readValue(json, Object.class); + }); + assertTrue(ex.getMessage().contains("Cannot coerce")); + } + + @Test + void testNullValue() throws Exception { + ObjectMapper mapper = buildMapper(); + String json = "null"; + Object result = mapper.readValue(json, Object.class); + assertNull(result); + } + + @Test + void testInvalidString() { + ObjectMapper mapper = buildMapper(); + String json = "\"abc\""; + assertThrows(IOException.class, () -> { + mapper.readValue(json, Object.class); + }); + } + +} \ No newline at end of file diff --git a/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/FieldValidatorTest.java b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/FieldValidatorTest.java index 54eedaadec..3a61ffa823 100644 --- a/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/FieldValidatorTest.java +++ b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/FieldValidatorTest.java @@ -12,6 +12,8 @@ import org.springframework.boot.test.context.SpringBootTest; import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @SpringBootTest @@ -32,7 +34,7 @@ public void setUp() { } @Test - public void testValidator_withValidInput_Success() { + public void testValidatorWithValidInputSuccess() { validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); validator.initialize(null); @@ -44,7 +46,7 @@ public void testValidator_withValidInput_Success() { } @Test - public void testValidator_withEmptyValueOrCode_Success() { + public void testValidatorWithEmptyValueOrCodeSuccess() { validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); validator.initialize(null); @@ -61,7 +63,7 @@ public void testValidator_withEmptyValueOrCode_Success() { } @Test - public void testValidator_withInvalidCharactersCode_Success() { + public void testValidatorWithInvalidCharactersCodeSuccess() { validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); validator.initialize(null); @@ -73,7 +75,7 @@ public void testValidator_withInvalidCharactersCode_Success() { } @Test - public void testValidator_withInvalidCharactersValue_Success() { + public void testValidatorWithInvalidCharactersValueSuccess() { validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); validator.initialize(null); @@ -84,4 +86,81 @@ public void testValidator_withInvalidCharactersValue_Success() { assertFalse(validator.isValid(jsonNode, null)); } + @Test + public void testValidatorWithNoAllowedCharactersReturnsTrue() { + validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); + validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); + validator.initialize(null); + when(jsonNode.get("value")).thenReturn(mapper.valueToTree("é退")); + when(jsonNode.get("code")).thenReturn(mapper.valueToTree("é退")); + assertTrue(validator.isValid(jsonNode, null)); + } + + @Test + public void testValidatorWithValueNullReturnsFalse() { + validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); + validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); + validator.initialize(null); + final JsonNode valueJsonNode = mock(JsonNode.class); + when(jsonNode.get("value")).thenReturn(valueJsonNode); + when(valueJsonNode.asText()).thenReturn(null); + when(jsonNode.get("code")).thenReturn(mapper.valueToTree("é退")); + assertFalse(validator.isValid(jsonNode, null)); + } + + @Test + public void testValidatorWithValueEmptyReturnsFalse() { + validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); + validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); + validator.initialize(null); + final JsonNode valueJsonNode = mock(JsonNode.class); + when(jsonNode.get("value")).thenReturn(valueJsonNode); + when(valueJsonNode.asText()).thenReturn(""); + when(jsonNode.get("code")).thenReturn(mapper.valueToTree("é退")); + assertFalse(validator.isValid(jsonNode, null)); + } + + @Test(expected = NullPointerException.class) + public void testValidatorMissingCodeNodeThrowsNPE() { + validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); + validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); + validator.initialize(null); + when(jsonNode.get("value")).thenReturn(mapper.valueToTree("some_value")); + when(jsonNode.get("code")).thenReturn(null); + validator.isValid(jsonNode, null); + } + + @Test + public void testValidatorWithCodeNullReturnsFalse() { + validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); + validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); + validator.initialize(null); + when(jsonNode.get("value")).thenReturn(mapper.valueToTree("some_value")); + final JsonNode codeJsonNode = mock(JsonNode.class); + when(jsonNode.get("code")).thenReturn(codeJsonNode); + when(codeJsonNode.asText()).thenReturn(null); + assertFalse(validator.isValid(jsonNode, null)); + } + + @Test + public void testValidatorWithCodeEmptyReturnsFalse() { + validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); + validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); + validator.initialize(null); + when(jsonNode.get("value")).thenReturn(mapper.valueToTree("some_value")); + final JsonNode codeJsonNode = mock(JsonNode.class); + when(jsonNode.get("code")).thenReturn(codeJsonNode); + when(codeJsonNode.asText()).thenReturn(""); + assertFalse(validator.isValid(jsonNode, null)); + } + + @Test(expected = NullPointerException.class) + public void testValidatorMissingValueNodeThrowsNPE() { + validator.setAllowedCodeCharactersRegex("[a-zA-Z0-9_]"); + validator.setAllowedValueCharactersRegex("[a-zA-Z0-9.,!@#$%^&*()]"); + validator.initialize(null); + when(jsonNode.get("value")).thenReturn(null); + validator.isValid(jsonNode, null); + } + } diff --git a/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValidatorTest.java b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValidatorTest.java new file mode 100644 index 0000000000..01e52ceeed --- /dev/null +++ b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValidatorTest.java @@ -0,0 +1,53 @@ +package io.mosip.kernel.masterdata.test.validator.registereddevice; + +import io.mosip.kernel.masterdata.validator.registereddevice.StatusCodeValidator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class StatusCodeValidatorTest { + + private final StatusCodeValidator validator = new StatusCodeValidator(); + + @Test + void testNullValue() { + assertFalse(validator.isValid(null, null)); + } + + @Test + void testEmptyValue() { + assertFalse(validator.isValid("", null)); + } + + @Test + void testBlankValue() { + assertFalse(validator.isValid(" ", null)); + } + + @Test + void testValidRegistered() { + assertTrue(validator.isValid("registered", null)); + } + + @Test + void testValidRegisteredDifferentCase() { + assertTrue(validator.isValid("REGISTERED", null)); + } + + @Test + void testValidRetired() { + assertTrue(validator.isValid("retired", null)); + } + + @Test + void testValidRevoked() { + assertTrue(validator.isValid("revoked", null)); + } + + @Test + void testInvalidStatusNotMatching() { + assertFalse(validator.isValid("UNKNOWN", null)); + } + +} \ No newline at end of file diff --git a/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValueTest.java b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValueTest.java new file mode 100644 index 0000000000..628e4b215b --- /dev/null +++ b/admin/kernel-masterdata-service/src/test/java/io/mosip/kernel/masterdata/test/validator/registereddevice/StatusCodeValueTest.java @@ -0,0 +1,57 @@ +package io.mosip.kernel.masterdata.test.validator.registereddevice; + +import io.mosip.kernel.masterdata.validator.registereddevice.StatusCodeValue; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class StatusCodeValueTest { + + @Test + void testToStringRegistered() { + assertEquals("registered", StatusCodeValue.REGISTERED.toString()); + } + + @Test + void testToStringRetired() { + assertEquals("retired", StatusCodeValue.RETIRED.toString()); + } + + @Test + void testToStringRevoked() { + assertEquals("revoked", StatusCodeValue.REVOKED.toString()); + } + + @Test + void testValueOfRegistered() { + StatusCodeValue value = StatusCodeValue.valueOf("REGISTERED"); + assertSame(StatusCodeValue.REGISTERED, value); + } + + @Test + void testValueOfRetired() { + StatusCodeValue value = StatusCodeValue.valueOf("RETIRED"); + assertSame(StatusCodeValue.RETIRED, value); + } + + @Test + void testValueOfRevoked() { + StatusCodeValue value = StatusCodeValue.valueOf("REVOKED"); + assertSame(StatusCodeValue.REVOKED, value); + } + + @Test + void testValuesContainsAll() { + StatusCodeValue[] values = StatusCodeValue.values(); + assertEquals(3, values.length); + assertArrayEquals( + new StatusCodeValue[] { + StatusCodeValue.REGISTERED, + StatusCodeValue.RETIRED, + StatusCodeValue.REVOKED + }, + values + ); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/ClientSettingsHelperTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/ClientSettingsHelperTest.java new file mode 100644 index 0000000000..e09da71d2c --- /dev/null +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/ClientSettingsHelperTest.java @@ -0,0 +1,502 @@ +package io.mosip.kernel.syncdata.test.helper; + + +import io.mosip.kernel.clientcrypto.constant.ClientType; +import io.mosip.kernel.clientcrypto.dto.TpmCryptoRequestDto; +import io.mosip.kernel.clientcrypto.dto.TpmCryptoResponseDto; +import io.mosip.kernel.clientcrypto.service.spi.ClientCryptoManagerService; +import io.mosip.kernel.syncdata.dto.DynamicFieldDto; +import io.mosip.kernel.syncdata.dto.RegistrationCenterMachineDto; +import io.mosip.kernel.syncdata.dto.response.SyncDataBaseDto; +import io.mosip.kernel.syncdata.entity.AppAuthenticationMethod; +import io.mosip.kernel.syncdata.entity.Holiday; +import io.mosip.kernel.syncdata.service.helper.ClientSettingsHelper; +import io.mosip.kernel.syncdata.utils.MapperUtils; +import io.mosip.kernel.syncdata.utils.SyncMasterDataServiceHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.core.env.Environment; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + + +@RunWith(MockitoJUnitRunner.class) +public class ClientSettingsHelperTest { + + @InjectMocks + private ClientSettingsHelper helper; + + @Mock + private SyncMasterDataServiceHelper serviceHelper; + + @Mock + private Environment environment; + + @Mock + private MapperUtils mapper; + + @Mock + private ClientCryptoManagerService clientCryptoManagerService; + + + // --------------------------------------------------------------------- + // hasURLDetails (private) + // --------------------------------------------------------------------- + + @Test + public void testHasURLDetailsNoPropertyReturnsFalse() { + when(environment.containsProperty(anyString())).thenReturn(false); + boolean result = ReflectionTestUtils.invokeMethod( + helper, + "hasURLDetails", + AppAuthenticationMethod.class, + true, // isV2API + false // deltaSync + ); + assertFalse(result); + } + + @Test + public void testHasURLDetailsOnlyOnFullSyncTrueAndDeltaSyncTrueReturnsFalse() { + when(environment.containsProperty("mosip.sync.entity.url.APPAUTHENTICATIONMETHOD")).thenReturn(true); + when(environment.getProperty( + eq("mosip.sync.entity.only-on-fullsync.APPAUTHENTICATIONMETHOD"), + eq(Boolean.class), + eq(true) + )).thenReturn(true); // onlyOnFullSync = true + boolean result = ReflectionTestUtils.invokeMethod( + helper, + "hasURLDetails", + AppAuthenticationMethod.class, + true, // isV2API + true // deltaSync + ); + assertFalse(result); + } + + @Test + public void testHasURLDetailsOnlyOnFullSyncFalseAndDeltaSyncTrueReturnsTrue() { + when(environment.containsProperty("mosip.sync.entity.url.APPAUTHENTICATIONMETHOD")).thenReturn(true); + when(environment.getProperty( + eq("mosip.sync.entity.only-on-fullsync.APPAUTHENTICATIONMETHOD"), + eq(Boolean.class), + eq(true) + )).thenReturn(false); // onlyOnFullSync = false + boolean result = ReflectionTestUtils.invokeMethod( + helper, + "hasURLDetails", + AppAuthenticationMethod.class, + true, // isV2API + true // deltaSync + ); + assertTrue(result); + } + + // --------------------------------------------------------------------- + // getLastUpdatedTimeFromEntity (private) + // --------------------------------------------------------------------- + + @Test + public void testGetLastUpdatedTimeFromEntityFullSyncEntityReturnsNull() { + LocalDateTime lastUpdated = LocalDateTime.now().minusDays(1); + List fullSync = Collections.singletonList("Machine"); + LocalDateTime result = ReflectionTestUtils.invokeMethod( + helper, + "getLastUpdatedTimeFromEntity", + io.mosip.kernel.syncdata.entity.Machine.class, + lastUpdated, + fullSync + ); + assertNull(result); + } + + @Test + public void testGetLastUpdatedTimeFromEntityNotFullSyncEntityReturnsOriginal() { + LocalDateTime lastUpdated = LocalDateTime.now().minusDays(1); + List fullSync = Collections.singletonList("Holiday"); + LocalDateTime result = ReflectionTestUtils.invokeMethod( + helper, + "getLastUpdatedTimeFromEntity", + io.mosip.kernel.syncdata.entity.Machine.class, + lastUpdated, + fullSync + ); + assertEquals(lastUpdated, result); + } + + // --------------------------------------------------------------------- + // getInitiateDataFetch + // --------------------------------------------------------------------- + + @Test + public void testGetInitiateDataFetchFullSyncMachinePassesNullLastUpdatedForMachine() { + String machineId = "M1"; + String regCenterId = "RC1"; + LocalDateTime lastUpdated = LocalDateTime.now().minusDays(1); + LocalDateTime current = LocalDateTime.now(); + // No URL details -> fall back to serviceHelper for all entities + when(serviceHelper.getAppAuthenticationMethodDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getAppRolePriorityDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getRegistrationCenter(any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getMachines(any(), any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getTemplates(any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getDocumentTypes(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getApplicantValidDocument(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getLocationHierarchy(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getReasonCategory(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getReasonList(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getHolidays(any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getBlackListedWords(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getScreenAuthorizationDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getScreenDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getProcessList(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getSyncJobDefDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getPermittedConfig(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getLanguageList(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getTemplateFileFormats(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getTemplateTypes(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getRegistrationCenterMachines(any(), any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getRegistrationCenterUsers(any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getValidDocuments(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getLocationHierarchyList(any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getAllDynamicFields(any())).thenReturn(mock(CompletableFuture.class)); + Map, CompletableFuture> result = helper.getInitiateDataFetch( + machineId, + regCenterId, + lastUpdated, + current, + false, // isV2API + false, // deltaSync + "Machine" // full sync only for Machine + ); + assertTrue(result.containsKey(io.mosip.kernel.syncdata.entity.Machine.class)); + // Verify getMachines called with lastUpdated = null for full-sync + verify(serviceHelper).getMachines(eq(regCenterId), isNull(), eq(current), eq(machineId)); + } + + @Test + public void testGetInitiateDataFetchUsesUrlDetailsWhenConfigured() { + String machineId = "M1"; + String regCenterId = "RC1"; + LocalDateTime lastUpdated = LocalDateTime.now().minusDays(1); + LocalDateTime current = LocalDateTime.now(); + when(environment.containsProperty("mosip.sync.entity.url.APPAUTHENTICATIONMETHOD")).thenReturn(true); + when(environment.getProperty( + eq("mosip.sync.entity.only-on-fullsync.APPAUTHENTICATIONMETHOD"), + eq(Boolean.class), + eq(true) + )).thenReturn(false); + when(environment.getProperty( + "mosip.sync.entity.url.APPAUTHENTICATIONMETHOD" + )).thenReturn("http://example.com/app-auth"); + when(environment.getProperty( + "mosip.sync.entity.auth-required.APPAUTHENTICATIONMETHOD" + )).thenReturn("true"); + when(environment.getProperty( + "mosip.sync.entity.auth-token.APPAUTHENTICATIONMETHOD" + )).thenReturn("token"); + when(environment.getProperty( + "mosip.sync.entity.encrypted.APPAUTHENTICATIONMETHOD" + )).thenReturn("false"); + when(environment.getProperty( + "mosip.sync.entity.headers.APPAUTHENTICATIONMETHOD" + )).thenReturn("header1:value1"); + when(serviceHelper.getAppRolePriorityDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getRegistrationCenter(any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getMachines(any(), any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getTemplates(any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getDocumentTypes(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getApplicantValidDocument(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getLocationHierarchy(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getReasonCategory(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getReasonList(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getHolidays(any(), any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getBlackListedWords(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getScreenAuthorizationDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getScreenDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getProcessList(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getSyncJobDefDetails(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getPermittedConfig(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getLanguageList(any(), any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getLocationHierarchyList(any())).thenReturn(mock(CompletableFuture.class)); + when(serviceHelper.getAllDynamicFields(any())).thenReturn(mock(CompletableFuture.class)); + Map, CompletableFuture> result = helper.getInitiateDataFetch( + machineId, + regCenterId, + lastUpdated, + current, + true, // isV2API + false, // deltaSync + null + ); + CompletableFuture future = (CompletableFuture) result.get(AppAuthenticationMethod.class); + assertNotNull(future); + @SuppressWarnings("unchecked") + Map urlDetails = (Map) future.join(); + assertEquals("http://example.com/app-auth", urlDetails.get("url")); + assertEquals("true", urlDetails.get("auth-required")); + assertEquals("token", urlDetails.get("auth-token")); + assertEquals("false", urlDetails.get("encrypted")); + assertEquals("header1:value1", urlDetails.get("headers")); + // Service helper should NOT be called for AppAuthenticationMethod when URL details are present + verify(serviceHelper, never()).getAppAuthenticationMethodDetails(any(), any()); + } + + // --------------------------------------------------------------------- + // retrieveData + processEntry + getEncryptedSyncDataBaseDto + // --------------------------------------------------------------------- + + @Test + public void testRetrieveDataStructuredUrlSuccess() throws Exception { + Map, CompletableFuture> futures = new HashMap<>(); + Map urlMap = new HashMap<>(); + urlMap.put("url", "http://example.com"); + futures.put(AppAuthenticationMethod.class, CompletableFuture.completedFuture(urlMap)); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + regCenterMachineDto.setPublicKey("PUB_KEY"); + regCenterMachineDto.setClientType(ClientType.LOCAL); + when(mapper.getObjectAsJsonString(urlMap)).thenReturn("{\"url\":\"http://example.com\"}"); + TpmCryptoResponseDto enc = new TpmCryptoResponseDto(); + enc.setValue("ENC_VALUE"); + when(clientCryptoManagerService.csEncrypt(any(TpmCryptoRequestDto.class))).thenReturn(enc); + List list = helper.retrieveData(futures, regCenterMachineDto, true); + assertEquals(1, list.size()); + SyncDataBaseDto dto = list.get(0); + assertNotNull(dto); + } + + @Test + public void testRetrieveDataStructuredUrlEncryptionErrorReturnsEmpty() throws Exception { + Map, CompletableFuture> futures = new HashMap<>(); + Map urlMap = new HashMap<>(); + urlMap.put("url", "http://example.com"); + futures.put(AppAuthenticationMethod.class, CompletableFuture.completedFuture(urlMap)); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + regCenterMachineDto.setPublicKey("PUB_KEY"); + regCenterMachineDto.setClientType(ClientType.LOCAL); + when(mapper.getObjectAsJsonString(urlMap)).thenReturn("{\"url\":\"http://example.com\"}"); + when(clientCryptoManagerService.csEncrypt( + any(TpmCryptoRequestDto.class) + )).thenThrow(new RuntimeException("enc error")); + List list = helper.retrieveData(futures, regCenterMachineDto, true); + // error is caught and entry skipped + assertTrue(list.isEmpty()); + } + + @Test + public void testRetrieveDataDynamicV2UsesServiceHelperV2() { + Map, CompletableFuture> futures = new HashMap<>(); + DynamicFieldDto d1 = new DynamicFieldDto(); + d1.setName("fieldName"); + DynamicFieldDto d2 = new DynamicFieldDto(); + d2.setName("fieldName"); + List dynamicList = Arrays.asList(d1, d2); + futures.put(DynamicFieldDto.class, CompletableFuture.completedFuture(dynamicList)); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + // When V2 : getSyncDataBaseDtoV2 should be used once, with grouped list + doAnswer(invocation -> { + String key = invocation.getArgument(0); + String entityType = invocation.getArgument(1); + @SuppressWarnings("unchecked") + List data = (List) invocation.getArgument(2); + @SuppressWarnings("unchecked") + List out = (List) invocation.getArgument(4); + assertEquals("fieldName", key); + assertEquals("dynamic", entityType); + assertEquals(2, data.size()); + out.add(new SyncDataBaseDto(key, entityType, "payload")); + return null; + }).when(serviceHelper).getSyncDataBaseDtoV2( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + List list = helper.retrieveData(futures, regCenterMachineDto, true); + assertEquals(1, list.size()); + verify(serviceHelper, times(1)).getSyncDataBaseDtoV2( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + } + + @Test + public void testRetrieveDataDynamicNonV2UsesServiceHelperLegacy() { + Map, CompletableFuture> futures = new HashMap<>(); + DynamicFieldDto d1 = new DynamicFieldDto(); + d1.setName("fieldName"); + DynamicFieldDto d2 = new DynamicFieldDto(); + d2.setName("fieldName"); + List dynamicList = Arrays.asList(d1, d2); + futures.put(DynamicFieldDto.class, CompletableFuture.completedFuture(dynamicList)); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + doAnswer(invocation -> { + String key = invocation.getArgument(0); + String entityType = invocation.getArgument(1); + @SuppressWarnings("unchecked") + List data = (List) invocation.getArgument(2); + @SuppressWarnings("unchecked") + List out = (List) invocation.getArgument(4); + assertEquals("fieldName", key); + assertEquals("dynamic", entityType); + assertEquals(2, data.size()); + out.add(new SyncDataBaseDto(key, entityType, "payload")); + return null; + }).when(serviceHelper).getSyncDataBaseDto( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + List list = helper.retrieveData(futures, regCenterMachineDto, false); + assertEquals(1, list.size()); + verify(serviceHelper, times(1)).getSyncDataBaseDto( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + } + + @Test + public void testRetrieveDataStructuredV2UsesSyncDataBaseDtoV2() { + Map, CompletableFuture> futures = new HashMap<>(); + List data = Collections.singletonList("val"); + futures.put(Holiday.class, CompletableFuture.completedFuture(data)); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + doAnswer(invocation -> { + String key = invocation.getArgument(0); + String entityType = invocation.getArgument(1); + @SuppressWarnings("unchecked") + List list = (List) invocation.getArgument(2); + @SuppressWarnings("unchecked") + List out = (List) invocation.getArgument(4); + assertEquals("Holiday", key); + assertEquals("structured", entityType); + assertEquals(1, list.size()); + out.add(new SyncDataBaseDto(key, entityType, "payload")); + return null; + }).when(serviceHelper).getSyncDataBaseDtoV2( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + List list = helper.retrieveData(futures, regCenterMachineDto, true); + assertEquals(1, list.size()); + verify(serviceHelper, times(1)).getSyncDataBaseDtoV2( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + } + + @Test + public void testRetrieveDataStructuredNonV2UsesSyncDataBaseDto() { + Map, CompletableFuture> futures = new HashMap<>(); + List data = Collections.singletonList("val"); + futures.put(Holiday.class, CompletableFuture.completedFuture(data)); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + doAnswer(invocation -> { + String key = invocation.getArgument(0); + String entityType = invocation.getArgument(1); + @SuppressWarnings("unchecked") + List list = (List) invocation.getArgument(2); + @SuppressWarnings("unchecked") + List out = (List) invocation.getArgument(4); + assertEquals("Holiday", key); + assertEquals("structured", entityType); + assertEquals(1, list.size()); + out.add(new SyncDataBaseDto(key, entityType, "payload")); + return null; + }).when(serviceHelper).getSyncDataBaseDto( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + List list = helper.retrieveData(futures, regCenterMachineDto, false); + assertEquals(1, list.size()); + verify(serviceHelper, times(1)).getSyncDataBaseDto( + anyString(), + anyString(), + anyList(), + eq(regCenterMachineDto), + anyList() + ); + } + + @Test(expected = RuntimeException.class) + public void testRetrieveDataFutureThrowsRuntimeExceptionWrappedInRuntimeException() { + Map, CompletableFuture> futures = new HashMap<>(); + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(new RuntimeException("boom")); + futures.put(AppAuthenticationMethod.class, failed); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + helper.retrieveData(futures, regCenterMachineDto, true); + } + + // --------------------------------------------------------------------- + // getConfiguredScriptUrlDetail + // --------------------------------------------------------------------- + + @Test + public void testGetConfiguredScriptUrlDetailSuccess() throws Exception { + // Prepare scriptNames and init() (PostConstruct) + Set scriptNames = new HashSet<>(); + scriptNames.add("applicanttype.mvel"); + ReflectionTestUtils.setField(helper, "scriptNames", scriptNames); + // Init will call buildUrlDetailMap() for each script + when(environment.getProperty(anyString())).thenReturn(null); + ReflectionTestUtils.invokeMethod(helper, "init"); + // Script detail -> JSON -> base64 -> encrypt + when(mapper.getObjectAsJsonString(anyMap())).thenReturn("{\"key\":\"value\"}"); + TpmCryptoResponseDto enc = new TpmCryptoResponseDto(); + enc.setValue("ENC_SCRIPT"); + when(clientCryptoManagerService.csEncrypt(any(TpmCryptoRequestDto.class))).thenReturn(enc); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + regCenterMachineDto.setPublicKey("PUB_KEY"); + regCenterMachineDto.setClientType(ClientType.LOCAL); + List list = helper.getConfiguredScriptUrlDetail(regCenterMachineDto); + assertEquals(1, list.size()); + SyncDataBaseDto dto = list.get(0); + assertNotNull(dto); + } + + @Test + public void testGetConfiguredScriptUrlDetailMapperThrowsExceptionIsSwallowed() throws Exception { + Set scriptNames = new HashSet<>(); + scriptNames.add("applicanttype.mvel"); + ReflectionTestUtils.setField(helper, "scriptNames", scriptNames); + when(environment.getProperty(anyString())).thenReturn(null); + ReflectionTestUtils.invokeMethod(helper, "init"); + when(mapper.getObjectAsJsonString(anyMap())).thenThrow(new RuntimeException("json error")); + RegistrationCenterMachineDto regCenterMachineDto = new RegistrationCenterMachineDto(); + regCenterMachineDto.setPublicKey("PUB_KEY"); + regCenterMachineDto.setClientType(ClientType.LOCAL); + List list = helper.getConfiguredScriptUrlDetail(regCenterMachineDto); + // Error is logged, script is skipped + assertTrue(list.isEmpty()); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/IdentitySchemaHelperTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/IdentitySchemaHelperTest.java new file mode 100644 index 0000000000..bf64f82dbd --- /dev/null +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/IdentitySchemaHelperTest.java @@ -0,0 +1,137 @@ +package io.mosip.kernel.syncdata.test.helper; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.mosip.kernel.core.exception.ServiceError; +import io.mosip.kernel.core.http.ResponseWrapper; +import io.mosip.kernel.syncdata.exception.SyncDataServiceException; +import io.mosip.kernel.syncdata.service.helper.IdentitySchemaHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDateTime; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + + +@RunWith(MockitoJUnitRunner.class) +public class IdentitySchemaHelperTest { + + @InjectMocks + private IdentitySchemaHelper identitySchemaHelper; + + @Mock + private RestTemplate restTemplate; + + @Mock + private ObjectMapper objectMapper; + + @Before + public void setUp() { + ReflectionTestUtils.setField( + identitySchemaHelper, + "idSchemaUrl", + "http://dummy-url/idschema" + ); + } + + @Test + public void testGetLatestIdentitySchemaNormalNoErrors() throws Exception { + // given + ObjectNode node = new ObjectMapper().createObjectNode(); + node.put("field", "value"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(node); + wrapper.setErrors(null); + when(restTemplate.getForEntity(any(), eq(String.class))) + .thenReturn(ResponseEntity.ok("{}")); + when(objectMapper.readValue(anyString(), any(TypeReference.class))) + .thenReturn(wrapper); + // when + JsonNode result = identitySchemaHelper.getLatestIdentitySchema( + LocalDateTime.now(), + 1.2, + "some-domain", + "some-type" + ); + // then + assertNotNull(result); + assertEquals("value", result.get("field").asText()); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetLatestIdentitySchemaWithErrorsThrowsSyncDataServiceException() throws Exception { + // given + ObjectNode node = new ObjectMapper().createObjectNode(); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(node); + final ServiceError serviceError = new ServiceError("TEST", "TEST"); + wrapper.setErrors(Collections.singletonList(serviceError)); + when(restTemplate.getForEntity(any(), eq(String.class))) + .thenReturn(ResponseEntity.ok("{}")); + when(objectMapper.readValue(anyString(), any(TypeReference.class))) + .thenReturn(wrapper); + // when / then + identitySchemaHelper.getLatestIdentitySchema( + LocalDateTime.now(), + 1.2, + "some-domain", + "schema" + ); + } + + @Test + public void testGetLatestIdentitySchemaRegistrationClientSchemaRetainsSpecificFields() throws Exception { + // given + ObjectNode node = new ObjectMapper().createObjectNode(); + node.put("schema", "schemaVal"); + node.put("schemaJson", "{}"); + node.put("id", "idVal"); + node.put("idVersion", "1.0"); + node.put("effectiveFrom", "2024-01-01T00:00:00"); + node.put("extra", "shouldBeRemoved"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(node); + wrapper.setErrors(null); + when(restTemplate.getForEntity(any(), eq(String.class))) + .thenReturn(ResponseEntity.ok("{}")); + when(objectMapper.readValue(anyString(), any(TypeReference.class))) + .thenReturn(wrapper); + // when + JsonNode result = identitySchemaHelper.getLatestIdentitySchema( + LocalDateTime.now(), 1.2, "registration-client", "schema"); + // then + assertTrue(result.has("schema")); + assertTrue(result.has("schemaJson")); + assertTrue(result.has("id")); + assertTrue(result.has("idVersion")); + assertTrue(result.has("effectiveFrom")); + assertFalse(result.has("extra")); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetLatestIdentitySchemaOnExceptionThrowsSyncDataServiceException() throws Exception { + // given + when(restTemplate.getForEntity(any(), eq(String.class))).thenThrow(new RuntimeException("boom")); + // when / then + identitySchemaHelper.getLatestIdentitySchema( + LocalDateTime.now(), + 1.2, + "some-domain", + "some-type" + ); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/KeymanagerHelperTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/KeymanagerHelperTest.java new file mode 100644 index 0000000000..34276c4857 --- /dev/null +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/helper/KeymanagerHelperTest.java @@ -0,0 +1,169 @@ +package io.mosip.kernel.syncdata.test.helper; + + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.kernel.core.exception.ServiceError; +import io.mosip.kernel.core.http.ResponseWrapper; +import io.mosip.kernel.signature.dto.JWTSignatureResponseDto; +import io.mosip.kernel.syncdata.dto.response.KeyPairGenerateResponseDto; +import io.mosip.kernel.syncdata.exception.SyncDataServiceException; +import io.mosip.kernel.syncdata.exception.SyncInvalidArgumentException; +import io.mosip.kernel.syncdata.service.helper.KeymanagerHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.HttpEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.Collections; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class KeymanagerHelperTest { + + @InjectMocks + private KeymanagerHelper keymanagerHelper; + + @Mock + private RestTemplate restTemplate; + + @Mock + private ObjectMapper objectMapper; + + @Before + public void setUp() { + ReflectionTestUtils.setField( + keymanagerHelper, + "certificateUrl", + "http://localhost/keymanager/cert" + ); + ReflectionTestUtils.setField( + keymanagerHelper, + "signUrl", + "http://localhost/keymanager/sign" + ); + ReflectionTestUtils.setField( + keymanagerHelper, + "signApplicationid", + "KERNEL" + ); + ReflectionTestUtils.setField( + keymanagerHelper, + "signRefid", + "SIGN" + ); + } + + /* ====================== getCertificate ====================== */ + + @Test + public void testGetCertificateSuccessNoErrorsWithReferenceId() throws Exception { + KeyPairGenerateResponseDto expected = new KeyPairGenerateResponseDto(); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(expected); + wrapper.setErrors(null); // errors == null + when(restTemplate.getForEntity(any(), eq(String.class))).thenReturn(ResponseEntity.ok("{}")); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + KeyPairGenerateResponseDto result = keymanagerHelper.getCertificate( + "APP_ID", + Optional.of("REF_ID") + ); + assertSame(expected, result); + } + + @Test + public void testGetCertificateSuccessEmptyErrorsNoReferenceId() throws Exception { + KeyPairGenerateResponseDto expected = new KeyPairGenerateResponseDto(); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(expected); + wrapper.setErrors(Collections.emptyList()); // errors non null mais vide + when(restTemplate.getForEntity(any(), eq(String.class))).thenReturn(ResponseEntity.ok("{}")); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + KeyPairGenerateResponseDto result = keymanagerHelper.getCertificate( + "APP_ID", + Optional.empty() + ); + assertSame(expected, result); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetCertificateWithErrorsThrowsSyncDataServiceException() throws Exception { + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(new KeyPairGenerateResponseDto()); + final ServiceError serviceError = new ServiceError("TEST", "TEST"); + wrapper.setErrors(Collections.singletonList(serviceError)); + when(restTemplate.getForEntity(any(), eq(String.class))).thenReturn(ResponseEntity.ok("{}")); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + keymanagerHelper.getCertificate("APP_ID", Optional.of("REF_ID")); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetCertificateRestTemplateThrowsThrowsSyncDataServiceException() throws Exception { + when(restTemplate.getForEntity(any(), eq(String.class))).thenThrow(new RuntimeException("REST error")); + keymanagerHelper.getCertificate("APP_ID", Optional.empty()); + } + + /* ====================== getSignature ====================== */ + + @Test + public void testGetSignatureSuccessReturnsJwtSignedData() throws Exception { + JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); + responseDto.setJwtSignedData("SIGNED_DATA"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(responseDto); + wrapper.setErrors(null); + when(restTemplate.postForEntity(any(), any(HttpEntity.class), eq(String.class))).thenReturn( + ResponseEntity.ok("{}") + ); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + String result = keymanagerHelper.getSignature("{\"data\":\"test\"}"); + assertEquals("SIGNED_DATA", result); + } + + @Test(expected = SyncInvalidArgumentException.class) + public void testGetSignatureWithErrorsThrowsSyncInvalidArgumentException() throws Exception { + JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(responseDto); + final ServiceError serviceError = new ServiceError("TEST", "TEST"); + wrapper.setErrors(Collections.singletonList(serviceError)); // errors non vide + when(restTemplate.postForEntity(any(), any(HttpEntity.class), eq(String.class))).thenReturn( + ResponseEntity.ok("{}") + ); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + keymanagerHelper.getSignature("{\"data\":\"test\"}"); + } + + @Test(expected = RuntimeException.class) + public void testGetSignatureRestTemplateThrowsPropagatesException() throws Exception { + when(restTemplate.postForEntity(any(), any(HttpEntity.class), eq(String.class))).thenThrow( + new RuntimeException("REST error") + ); + keymanagerHelper.getSignature("{\"data\":\"test\"}"); + } + + /* ====================== getFileSignature ====================== */ + + @Test + public void testGetFileSignatureDelegatesToGetSignature() throws Exception { + // on utilise un spy pour vérifier l'argument passé à getSignature + KeymanagerHelper spyHelper = Mockito.spy(keymanagerHelper); + doReturn("SIGNED_HASH").when(spyHelper).getSignature("{\"hash\":\"abc123\"}"); + String result = spyHelper.getFileSignature("abc123"); + assertEquals("SIGNED_HASH", result); + verify(spyHelper, times(1)).getSignature("{\"hash\":\"abc123\"}"); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncAuthTokenServiceImplTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncAuthTokenServiceImplTest.java new file mode 100644 index 0000000000..9e131ef184 --- /dev/null +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncAuthTokenServiceImplTest.java @@ -0,0 +1,316 @@ +package io.mosip.kernel.syncdata.test.impl; + + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.mosip.kernel.clientcrypto.service.impl.ClientCryptoFacade; +import io.mosip.kernel.core.authmanager.model.AuthNResponse; +import io.mosip.kernel.core.exception.ServiceError; +import io.mosip.kernel.core.http.ResponseWrapper; +import io.mosip.kernel.core.util.CryptoUtil; +import io.mosip.kernel.cryptomanager.util.CryptomanagerUtils; +import io.mosip.kernel.syncdata.dto.response.TokenResponseDto; +import io.mosip.kernel.syncdata.entity.Machine; +import io.mosip.kernel.syncdata.exception.RequestException; +import io.mosip.kernel.syncdata.repository.MachineRepository; +import io.mosip.kernel.syncdata.service.SyncUserDetailsService; +import io.mosip.kernel.syncdata.service.impl.SyncAuthTokenServiceImpl; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.HttpEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + + +@RunWith(MockitoJUnitRunner.class) +public class SyncAuthTokenServiceImplTest { + + @InjectMocks + private SyncAuthTokenServiceImpl service; + + @Mock + private ClientCryptoFacade clientCryptoFacade; + + @Mock + private MachineRepository machineRepository; + + @Mock + private SyncUserDetailsService syncUserDetailsService; + + @Mock + private RestTemplate restTemplate; + + @Mock + private CryptomanagerUtils cryptomanagerUtils; + + private ObjectMapper objectMapper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + ReflectionTestUtils.setField(service, "objectMapper", objectMapper); + ReflectionTestUtils.setField(service, "newAuthTokenInternalUrl", "http://auth/new"); + ReflectionTestUtils.setField(service, "otpAuthTokenInternalUrl", "http://auth/otp"); + ReflectionTestUtils.setField(service, "refreshAuthTokenInternalUrl", "http://auth/refresh"); + ReflectionTestUtils.setField(service, "authTokenInternalAppId", "REG_CLIENT"); + ReflectionTestUtils.setField(service, "clientId", "client-id"); + ReflectionTestUtils.setField(service, "secretKey", "secret"); + ReflectionTestUtils.setField(service, "sendOTPUrl", "http://auth/send-otp"); + ReflectionTestUtils.setField(service, "maxMinutes", -5); + ReflectionTestUtils.setField(service, "minMinutes", 5); + service.init(); + } + + // ---------- Helpers ---------- + + private String buildJwt(String kid, Map payloadMap) throws Exception { + String headerJson; + if (kid == null) { + headerJson = "{\"alg\":\"RS256\"}"; + } else { + headerJson = "{\"alg\":\"RS256\",\"kid\":\"" + kid + "\"}"; + } + String payloadJson = objectMapper.writeValueAsString(payloadMap); + String header = CryptoUtil.encodeToURLSafeBase64(headerJson.getBytes()); + String payload = CryptoUtil.encodeToURLSafeBase64(payloadJson.getBytes()); + String signature = CryptoUtil.encodeToURLSafeBase64("dummy-signature".getBytes()); + return header + "." + payload + "." + signature; + } + + private Machine buildMachine(String name) { + Machine m = new Machine(); + m.setId("M1"); + m.setIsActive(true); + m.setName(name); + m.setSignPublicKey("SIGN_PUB_BASE64"); + m.setPublicKey("PUB_BASE64"); + return m; + } + + private ResponseEntity buildTokenResponseEntity() throws Exception { + TokenResponseDto tokenResponseDto = new TokenResponseDto(); + tokenResponseDto.setToken("ACCESS_TOKEN"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(tokenResponseDto); + String body = objectMapper.writeValueAsString(wrapper); + return ResponseEntity.ok(body); + } + + // ---------- Tests ---------- + + @Test + public void initShouldConfigureObjectMapper() throws Exception { + String json = "{\"timestamp\":\"2024-01-01T10:00:00\"}"; + Map map = objectMapper.readValue(json, new TypeReference>() { + }); + assertEquals("2024-01-01T10:00:00", map.get("timestamp")); + } + + @Test + public void getAuthTokenWithAuthTypeNewShouldReturnEncryptedToken() throws Exception { + Machine machine = buildMachine("MACHINE-1"); + when(machineRepository.findBySignKeyIndex("key1")).thenReturn(Collections.singletonList(machine)); + when(cryptomanagerUtils.decodeBase64Data(anyString())).thenReturn("decoded".getBytes()); + when(clientCryptoFacade.validateSignature(any(), any(), any(), any())).thenReturn(true); + when(clientCryptoFacade.encrypt(any(), any(), any())).thenReturn("cipher-bytes".getBytes()); + when(restTemplate.postForEntity(any(URI.class), any(HttpEntity.class), eq(String.class))).thenReturn( + buildTokenResponseEntity() + ); + Map payload = new HashMap<>(); + payload.put("userId", "admin"); + payload.put("password", "pwd"); + payload.put("authType", "NEW"); + payload.put("machineName", "MACHINE-1"); + DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String formattedTimestamp = LocalDateTime.now(ZoneOffset.UTC).format(timestampFormatter); + payload.put("timestamp", formattedTimestamp); + String jwt = buildJwt("key1", payload); + String result = service.getAuthToken(jwt); + assertNotNull(result); + byte[] decoded = CryptoUtil.decodeURLSafeBase64(result); + assertEquals("cipher-bytes", new String(decoded)); + verify(clientCryptoFacade, times(1)).encrypt(any(), any(), any()); + } + + @Test + public void getAuthTokenWithAuthTypeOtpShouldCallOtpUrl() throws Exception { + Machine machine = buildMachine("MACHINE-OTP"); + when(machineRepository.findBySignKeyIndex("key2")).thenReturn(Collections.singletonList(machine)); + when(cryptomanagerUtils.decodeBase64Data(anyString())).thenReturn("decoded".getBytes()); + when(clientCryptoFacade.validateSignature(any(), any(), any(), any())).thenReturn(true); + when(clientCryptoFacade.encrypt(any(), any(), any())).thenReturn("cipher-otp".getBytes()); + when(restTemplate.postForEntity(any(URI.class), any(HttpEntity.class), eq(String.class))).thenReturn( + buildTokenResponseEntity() + ); + Map payload = new HashMap<>(); + payload.put("userId", "user-otp"); + payload.put("otp", "123456"); + payload.put("authType", "OTP"); + payload.put("machineName", "MACHINE-OTP"); + DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String formattedTimestamp = LocalDateTime.now(ZoneOffset.UTC).format(timestampFormatter); + payload.put("timestamp", formattedTimestamp); + String jwt = buildJwt("key2", payload); + String result = service.getAuthToken(jwt); + assertNotNull(result); + verify(restTemplate, atLeastOnce()).postForEntity(any(URI.class), any(HttpEntity.class), eq(String.class)); + } + + @Test + public void getAuthTokenWithAuthTypeRefreshShouldCallRefreshUrl() throws Exception { + Machine machine = buildMachine("MACHINE-REFRESH"); + when(machineRepository.findBySignKeyIndex("key3")).thenReturn(Collections.singletonList(machine)); + when(cryptomanagerUtils.decodeBase64Data(anyString())).thenReturn("decoded".getBytes()); + when(clientCryptoFacade.validateSignature(any(), any(), any(), any())).thenReturn(true); + when(clientCryptoFacade.encrypt(any(), any(), any())).thenReturn("cipher-refresh".getBytes()); + when(restTemplate.postForEntity(any(URI.class), any(HttpEntity.class), eq(String.class))).thenReturn( + buildTokenResponseEntity() + ); + Map payload = new HashMap<>(); + payload.put("userId", "user-refresh"); + payload.put("refreshToken", "REFRESH-TOKEN"); + payload.put("authType", "REFRESH"); + payload.put("machineName", "MACHINE-REFRESH"); + DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String formattedTimestamp = LocalDateTime.now(ZoneOffset.UTC).format(timestampFormatter); + payload.put("timestamp", formattedTimestamp); + String jwt = buildJwt("key3", payload); + String result = service.getAuthToken(jwt); + assertNotNull(result); + verify(restTemplate, atLeastOnce()).postForEntity(any(URI.class), any(HttpEntity.class), eq(String.class)); + } + + @Test(expected = RequestException.class) + public void getAuthTokenInvalidPartsShouldThrowRequestException() { + String invalid = "no-dot-here"; + service.getAuthToken(invalid); + } + + @Test(expected = RequestException.class) + public void getAuthTokenHeaderWithoutKidShouldThrowRequestException() throws Exception { + Map payload = new HashMap<>(); + payload.put("authType", "NEW"); + DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String formattedTimestamp = LocalDateTime.now(ZoneOffset.UTC).format(timestampFormatter); + payload.put("timestamp", formattedTimestamp); + String jwt = buildJwt(null, payload); + service.getAuthToken(jwt); + } + + @Test(expected = RequestException.class) + public void getAuthTokenWithOldTimestampShouldThrowRequestException() throws Exception { + Machine machine = buildMachine("MACHINE-TIME"); + when(machineRepository.findBySignKeyIndex("key-time")).thenReturn(Collections.singletonList(machine)); + when(cryptomanagerUtils.decodeBase64Data(anyString())).thenReturn("decoded".getBytes()); + when(clientCryptoFacade.validateSignature(any(), any(), any(), any())).thenReturn(true); + Map payload = new HashMap<>(); + payload.put("authType", "NEW"); + payload.put("machineName", "MACHINE-TIME"); + DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String formattedTimestamp = LocalDateTime.now(ZoneOffset.UTC).minusMinutes(30).format(timestampFormatter); + payload.put("timestamp", formattedTimestamp); + String jwt = buildJwt("key-time", payload); + service.getAuthToken(jwt); + } + + @Test(expected = RequestException.class) + public void getAuthTokenUnknownAuthTypeShouldThrowErrorGettingToken() throws Exception { + Machine machine = buildMachine("MACHINE-UNKNOWN"); + when(machineRepository.findBySignKeyIndex("key-unknown")).thenReturn(Collections.singletonList(machine)); + when(cryptomanagerUtils.decodeBase64Data(anyString())).thenReturn("decoded".getBytes()); + when(clientCryptoFacade.validateSignature(any(), any(), any(), any())).thenReturn(true); + Map payload = new HashMap<>(); + payload.put("authType", "somethingElse"); + payload.put("machineName", "MACHINE-UNKNOWN"); + DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String formattedTimestamp = LocalDateTime.now(ZoneOffset.UTC).format(timestampFormatter); + payload.put("timestamp", formattedTimestamp); + String jwt = buildJwt("key-unknown", payload); + service.getAuthToken(jwt); + } + + @Test + public void sendOtpValidRequestShouldReturnResponse() throws Exception { + Machine machine = buildMachine("MACH-OTP"); + when(machineRepository.findBySignKeyIndex("key-otp")).thenReturn(Collections.singletonList(machine)); + when(cryptomanagerUtils.decodeBase64Data(anyString())).thenReturn("decoded".getBytes()); + when(clientCryptoFacade.validateSignature(any(), any(), any(), any())).thenReturn(true); + when(syncUserDetailsService.getUserDetailsFromAuthServer(anyList())).thenReturn(null); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(new AuthNResponse()); + String body = objectMapper.writeValueAsString(wrapper); + when(restTemplate.postForEntity(any(URI.class), any(HttpEntity.class), eq(String.class))).thenReturn( + ResponseEntity.ok(body) + ); + Map payload = new HashMap<>(); + payload.put("userId", "user1"); + payload.put("otpChannel", Arrays.asList("email")); + payload.put("appId", "REG_CLIENT"); + payload.put("useridtype", "USER"); + payload.put("context", "CTX"); + DateTimeFormatter timestampFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String formattedTimestamp = LocalDateTime.now(ZoneOffset.UTC).format(timestampFormatter); + payload.put("timestamp", formattedTimestamp); + String jwt = buildJwt("key-otp", payload); + ResponseWrapper resp = service.sendOTP(jwt); + assertNotNull(resp); + assertNotNull(resp.getResponse()); + verify(restTemplate, times(1)).postForEntity( + any(URI.class), + any(HttpEntity.class), + eq(String.class) + ); + } + + @Test(expected = RequestException.class) + public void sendOtpInvalidPartsShouldThrowRequestException() { + service.sendOTP("invalid"); + } + + @Test(expected = RequestException.class) + public void getAuthTokenWhenAuthServerReturnsErrorShouldThrowRequestException() throws Exception { + Machine machine = buildMachine("MACH-ERROR"); + when(machineRepository.findBySignKeyIndex("key-err")).thenReturn(Collections.singletonList(machine)); + when(cryptomanagerUtils.decodeBase64Data(anyString())).thenReturn("decoded".getBytes()); + when(clientCryptoFacade.validateSignature(any(), any(), any(), any())).thenReturn(true); + TokenResponseDto tokenResponseDto = new TokenResponseDto(); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(tokenResponseDto); + List errors = new ArrayList<>(); + ServiceError error = new ServiceError(); + error.setErrorCode("ERR_CODE"); + error.setMessage("ERR_MESSAGE"); + errors.add(error); + wrapper.setErrors(errors); + String body = objectMapper.writeValueAsString(wrapper); + Map payload = new HashMap<>(); + payload.put("userId", "admin"); + payload.put("password", "pwd"); + payload.put("authType", "NEW"); + payload.put("machineName", "MACH-ERROR"); + payload.put("timestamp", LocalDateTime.now(ZoneOffset.UTC)); + String jwt = buildJwt("key-err", payload); + service.getAuthToken(jwt); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncMasterDataServiceImplTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncMasterDataServiceImplTest.java new file mode 100644 index 0000000000..af8af9865d --- /dev/null +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncMasterDataServiceImplTest.java @@ -0,0 +1,896 @@ +package io.mosip.kernel.syncdata.test.impl; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.kernel.clientcrypto.dto.TpmCryptoRequestDto; +import io.mosip.kernel.clientcrypto.dto.TpmCryptoResponseDto; +import io.mosip.kernel.clientcrypto.service.spi.ClientCryptoManagerService; +import io.mosip.kernel.core.exception.FileNotFoundException; +import io.mosip.kernel.core.http.ResponseWrapper; +import io.mosip.kernel.cryptomanager.util.CryptomanagerUtils; +import io.mosip.kernel.keymanagerservice.entity.CACertificateStore; +import io.mosip.kernel.keymanagerservice.repository.CACertificateStoreRepository; +import io.mosip.kernel.syncdata.dto.*; +import io.mosip.kernel.syncdata.dto.response.*; +import io.mosip.kernel.syncdata.entity.Machine; +import io.mosip.kernel.syncdata.exception.RequestException; +import io.mosip.kernel.syncdata.exception.SyncDataServiceException; +import io.mosip.kernel.syncdata.repository.MachineRepository; +import io.mosip.kernel.syncdata.repository.ModuleDetailRepository; +import io.mosip.kernel.syncdata.service.helper.ClientSettingsHelper; +import io.mosip.kernel.syncdata.service.helper.IdentitySchemaHelper; +import io.mosip.kernel.syncdata.service.helper.KeymanagerHelper; +import io.mosip.kernel.syncdata.service.impl.SyncMasterDataServiceImpl; +import io.mosip.kernel.syncdata.utils.MapperUtils; +import io.mosip.kernel.syncdata.utils.SyncMasterDataServiceHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +@RunWith(MockitoJUnitRunner.class) +public class SyncMasterDataServiceImplTest { + + @InjectMocks + private SyncMasterDataServiceImpl service; + + @Mock + private SyncMasterDataServiceHelper serviceHelper; + + @Mock + private MachineRepository machineRepo; + + @Mock + private MapperUtils mapper; + + @Mock + private IdentitySchemaHelper identitySchemaHelper; + + @Mock + private KeymanagerHelper keymanagerHelper; + + @Mock + private RestTemplate restTemplate; + + @Mock + private ObjectMapper objectMapper; + + @Mock + private CACertificateStoreRepository caCertificateStoreRepository; + + @Mock + private ModuleDetailRepository moduleDetailRepository; + + @Mock + private ClientSettingsHelper clientSettingsHelper; + + @Mock + private Environment environment; + + @Mock + private ClientCryptoManagerService clientCryptoManagerService; + + @Mock + private CryptomanagerUtils cryptomanagerUtils; + + private Path tempDir; + + @Before + public void setUp() throws Exception { + tempDir = Files.createTempDirectory("syncdata-test"); + ReflectionTestUtils.setField(service, "machineUrl", "http://localhost/machine/%s"); + ReflectionTestUtils.setField(service, "clientSettingsDir", tempDir.toString()); + } + + @After + public void cleanup() throws Exception { + if (tempDir != null) { + Files.walk(tempDir) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(java.io.File::delete); + } + } + + // --------------------------------------------------------------------- + // syncClientSettings + // --------------------------------------------------------------------- + @Test + public void testSyncClientSettingsSuccess() { + String regCenterId = "RC1"; + String keyIndex = "KEY1"; + LocalDateTime from = LocalDateTime.now().minusDays(1); + LocalDateTime to = LocalDateTime.now(); + RegistrationCenterMachineDto machineDto = new RegistrationCenterMachineDto(); + machineDto.setMachineId("M1"); + machineDto.setRegCenterId("RC1"); + when(serviceHelper.getRegistrationCenterMachine(regCenterId, keyIndex)).thenReturn(machineDto); + Map, CompletableFuture> futureMap = new HashMap<>(); + when(clientSettingsHelper.getInitiateDataFetch( + anyString(), + anyString(), + any(), + any(), + eq(false), + anyBoolean(), + isNull() + )).thenReturn(futureMap); + when(clientSettingsHelper.retrieveData( + eq(futureMap), + eq(machineDto), + eq(false) + )).thenReturn(Collections.emptyList()); + SyncDataResponseDto resp = service.syncClientSettings(regCenterId, keyIndex, from, to); + assertNotNull(resp); + assertNotNull(resp.getDataToSync()); + assertTrue(resp.getDataToSync().isEmpty()); + } + + // --------------------------------------------------------------------- + // syncClientSettingsV2 + // --------------------------------------------------------------------- + + @Test + public void testSyncClientSettingsV2Success() { + String regCenterId = "RC1"; + String keyIndex = "KEY1"; + RegistrationCenterMachineDto machineDto = new RegistrationCenterMachineDto(); + machineDto.setMachineId("M1"); + machineDto.setRegCenterId("RC1"); + when(serviceHelper.getRegistrationCenterMachine(regCenterId, keyIndex)).thenReturn(machineDto); + Map, CompletableFuture> futureMap = new HashMap<>(); + when(clientSettingsHelper.getInitiateDataFetch( + anyString(), + anyString(), + any(), + any(), + eq(true), + anyBoolean(), + any() + )).thenReturn(futureMap); + SyncDataBaseDto data1 = mock(SyncDataBaseDto.class); + when(clientSettingsHelper.retrieveData( + eq(futureMap), + eq(machineDto), + eq(true) + )).thenReturn(Collections.singletonList(data1)); + SyncDataBaseDto script1 = mock(SyncDataBaseDto.class); + when(clientSettingsHelper.getConfiguredScriptUrlDetail( + eq(machineDto) + )).thenReturn(Collections.singletonList(script1)); + SyncDataResponseDto resp = service.syncClientSettingsV2( + regCenterId, + keyIndex, + null, + LocalDateTime.now(), + "1.0.0", + null + ); + assertNotNull(resp); + assertNotNull(resp.getDataToSync()); + assertEquals(2, resp.getDataToSync().size()); + } + + @Test(expected = SyncDataServiceException.class) + public void testSyncClientSettingsV2FutureThrowsSyncDataServiceException() { + String regCenterId = "RC1"; + String keyIndex = "KEY1"; + RegistrationCenterMachineDto machineDto = new RegistrationCenterMachineDto(); + machineDto.setMachineId("M1"); + machineDto.setRegCenterId("RC1"); + when(serviceHelper.getRegistrationCenterMachine(regCenterId, keyIndex)).thenReturn(machineDto); + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(new SyncDataServiceException("ERR", "msg")); + Map, CompletableFuture> futureMap = new HashMap<>(); + futureMap.put(String.class, failed); + when(clientSettingsHelper.getInitiateDataFetch( + anyString(), + anyString(), + any(), + any(), + eq(true), + anyBoolean(), + any() + )).thenReturn(futureMap); + service.syncClientSettingsV2( + regCenterId, + keyIndex, + null, + LocalDateTime.now(), + "1.0.0", + null + ); + } + + @Test(expected = RuntimeException.class) + public void testSyncClientSettingsV2FutureThrowsOtherRuntimeException() { + String regCenterId = "RC1"; + String keyIndex = "KEY1"; + RegistrationCenterMachineDto machineDto = new RegistrationCenterMachineDto(); + machineDto.setMachineId("M1"); + machineDto.setRegCenterId("RC1"); + when(serviceHelper.getRegistrationCenterMachine(regCenterId, keyIndex)).thenReturn(machineDto); + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(new RuntimeException("boom")); + Map, CompletableFuture> futureMap = new HashMap<>(); + futureMap.put(String.class, failed); + when(clientSettingsHelper.getInitiateDataFetch( + anyString(), + anyString(), + any(), + any(), + eq(true), + anyBoolean(), + isNull() + )).thenReturn(futureMap); + service.syncClientSettingsV2( + regCenterId, + keyIndex, + null, + LocalDateTime.now(), + "1.0.0", + null + ); + } + + // --------------------------------------------------------------------- + // getLatestPublishedIdSchema + // --------------------------------------------------------------------- + + @Test + public void testGetLatestPublishedIdSchemaDelegatesToHelper() { + JsonNode node = mock(JsonNode.class); + when(identitySchemaHelper.getLatestIdentitySchema( + any(), anyDouble(), anyString(), anyString() + )).thenReturn(node); + JsonNode result = service.getLatestPublishedIdSchema( + LocalDateTime.now(), + 1.0, + "dom", + "schema" + ); + assertSame(node, result); + } + + // --------------------------------------------------------------------- + // getCertificate + // --------------------------------------------------------------------- + + @Test + public void testGetCertificateDelegatesToKeymanager() { + KeyPairGenerateResponseDto dto = new KeyPairGenerateResponseDto(); + when(keymanagerHelper.getCertificate(eq("APP"), eq(Optional.of("REF")))).thenReturn(dto); + KeyPairGenerateResponseDto result = service.getCertificate("APP", Optional.of("REF")); + assertSame(dto, result); + } + + // --------------------------------------------------------------------- + // getMachineById (private) — success, errors, exception + // --------------------------------------------------------------------- + + @Test + public void testGetMachineByIdSuccess() throws Exception { + String machineId = "M1"; + String url = "http://localhost/machine/M1"; + ResponseEntity en = new ResponseEntity<>("BODY", HttpStatus.OK); + when(restTemplate.getForEntity(any(), eq(String.class))).thenReturn(en); + MachineResponseDto machineResp = new MachineResponseDto(); + machineResp.setMachines(Collections.singletonList(new MachineDto())); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(machineResp); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + MachineResponseDto result = (MachineResponseDto) ReflectionTestUtils.invokeMethod( + service, + "getMachineById", + machineId + ); + assertSame(machineResp, result); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetMachineByIdWithErrorsThrowsSyncDataServiceException() throws Exception { + String machineId = "M1"; + ResponseEntity en = new ResponseEntity<>("BODY", HttpStatus.OK); + when(restTemplate.getForEntity(any(), eq(String.class))).thenReturn(en); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setErrors(Collections.singletonList(new io.mosip.kernel.core.exception.ServiceError())); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + ReflectionTestUtils.invokeMethod(service, "getMachineById", machineId); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetMachineByIdExceptionThrowsSyncDataServiceException() throws Exception { + String machineId = "M1"; + when(restTemplate.getForEntity(any(), eq(String.class))).thenThrow(new RuntimeException("boom")); + ReflectionTestUtils.invokeMethod(service, "getMachineById", machineId); + } + + // --------------------------------------------------------------------- + // getClientPublicKey + // --------------------------------------------------------------------- + + @Test + public void testGetClientPublicKeySuccess() throws Exception { + String machineId = "M1"; + ResponseEntity en = new ResponseEntity<>("BODY", HttpStatus.OK); + when(restTemplate.getForEntity(any(), eq(String.class))).thenReturn(en); + MachineDto m = new MachineDto(); + m.setSignPublicKey("SIGN_KEY"); + m.setPublicKey("PUB_KEY"); + MachineResponseDto machineResp = new MachineResponseDto(); + machineResp.setMachines(Collections.singletonList(m)); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(machineResp); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + ClientPublicKeyResponseDto resp = service.getClientPublicKey(machineId); + assertEquals("SIGN_KEY", resp.getSigningPublicKey()); + assertEquals("PUB_KEY", resp.getEncryptionPublicKey()); + } + + @Test(expected = RequestException.class) + public void testGetClientPublicKeyNoMachinesThrowsRequestException() throws Exception { + String machineId = "M1"; + ResponseEntity en = new ResponseEntity<>("BODY", HttpStatus.OK); + when(restTemplate.getForEntity(any(), eq(String.class))).thenReturn(en); + MachineResponseDto machineResp = new MachineResponseDto(); + machineResp.setMachines(Collections.emptyList()); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(machineResp); + when(objectMapper.readValue(anyString(), any(TypeReference.class))).thenReturn(wrapper); + service.getClientPublicKey(machineId); + } + + // --------------------------------------------------------------------- + // getPartnerCACertificates + // --------------------------------------------------------------------- + + @Test + public void testGetPartnerCACertificatesNullListReturnsEmpty() { + when(caCertificateStoreRepository.findAllLatestCreatedUpdateDeleted(any(), any())).thenReturn(null); + CACertificates certs = service.getPartnerCACertificates(LocalDateTime.now().minusDays(1), LocalDateTime.now()); + assertNotNull(certs); + assertNotNull(certs.getCertificateDTOList()); + assertTrue(certs.getCertificateDTOList().isEmpty()); + } + + @Test + public void testGetPartnerCACertificatesWithDataMapsFields() { + CACertificateStore store = mock(CACertificateStore.class); + when(store.getCertId()).thenReturn("ID1"); + when(store.getCertIssuer()).thenReturn("ISSUER"); + when(caCertificateStoreRepository.findAllLatestCreatedUpdateDeleted( + any(), + any() + )).thenReturn(Collections.singletonList(store)); + CACertificates certs = service.getPartnerCACertificates(LocalDateTime.now().minusDays(1), LocalDateTime.now()); + assertNotNull(certs); + assertEquals(1, certs.getCertificateDTOList().size()); + CACertificateDTO dto = certs.getCertificateDTOList().get(0); + assertEquals("ID1", dto.getCertId()); + assertEquals("ISSUER", dto.getCertIssuer()); + } + + // --------------------------------------------------------------------- + // getClientSettingsJsonFile + getEntityResource + getEncryptedData + // --------------------------------------------------------------------- + + @Test + public void testGetClientSettingsJsonFileNotEncryptedSuccess() throws Exception { + String entity = "settings.json"; + String keyIndex = "KEY1"; + String content = "{\"a\":1}"; + Machine machine = new Machine(); + machine.setPublicKey("PUB"); + when(machineRepo.findByMachineKeyIndex(keyIndex)).thenReturn(Collections.singletonList(machine)); + when(environment.getProperty( + eq("mosip.sync.entity.encrypted.SETTINGS.JSON"), + eq(Boolean.class), + eq(false) + )).thenReturn(false); + Path file = tempDir.resolve(entity); + Files.write(file, content.getBytes(StandardCharsets.UTF_8)); + when(keymanagerHelper.getFileSignature(anyString())).thenReturn("SIG"); + ResponseEntity resp = service.getClientSettingsJsonFile(entity, keyIndex); + assertEquals(HttpStatus.OK, resp.getStatusCode()); + assertEquals(MediaType.APPLICATION_OCTET_STREAM, resp.getHeaders().getContentType()); + assertEquals("SIG", resp.getHeaders().getFirst("file-signature")); + assertTrue(resp.getBody() instanceof String); + assertEquals(content, resp.getBody()); + } + + @Test + public void testGetClientSettingsJsonFileEncryptedSuccess() throws Exception { + String entity = "settings.json"; + String keyIndex = "KEY1"; + String content = "{\"a\":1}"; + Machine machine = new Machine(); + machine.setPublicKey("PUB_KEY"); + when(machineRepo.findByMachineKeyIndex(keyIndex)).thenReturn(Collections.singletonList(machine)); + when(environment.getProperty( + eq("mosip.sync.entity.encrypted.SETTINGS.JSON"), + eq(Boolean.class), + eq(false) + )).thenReturn(true); + Path file = tempDir.resolve(entity); + Files.write(file, content.getBytes(StandardCharsets.UTF_8)); + when(keymanagerHelper.getFileSignature(anyString())).thenReturn("SIG"); + TpmCryptoResponseDto enc = new TpmCryptoResponseDto(); + enc.setValue("ENC_VALUE"); + when(clientCryptoManagerService.csEncrypt(any(TpmCryptoRequestDto.class))).thenReturn(enc); + ResponseEntity resp = service.getClientSettingsJsonFile(entity, keyIndex); + assertEquals(HttpStatus.OK, resp.getStatusCode()); + assertEquals("SIG", resp.getHeaders().getFirst("file-signature")); + assertTrue(resp.getBody() instanceof String); + assertEquals("ENC_VALUE", resp.getBody()); + } + + @Test(expected = FileNotFoundException.class) + public void testGetClientSettingsJsonFileFileNotFoundThrowsFileNotFound() throws Exception { + String entity = "missing.json"; + String keyIndex = "KEY1"; + Machine machine = new Machine(); + machine.setPublicKey("PUB"); + when(machineRepo.findByMachineKeyIndex(keyIndex)).thenReturn(Collections.singletonList(machine)); + when(environment.getProperty( + anyString(), + eq(Boolean.class), + eq(false) + )).thenReturn(false); + service.getClientSettingsJsonFile(entity, keyIndex); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetClientSettingsJsonFile_EncryptionError_ThrowsSyncDataServiceException() throws Exception { + String entity = "settings.json"; + String keyIndex = "KEY1"; + String content = "{\"a\":1}"; + Machine machine = new Machine(); + machine.setPublicKey("PUB_KEY"); + when(machineRepo.findByMachineKeyIndex(keyIndex)).thenReturn(Collections.singletonList(machine)); + when(environment.getProperty( + eq("mosip.sync.entity.encrypted.SETTINGS.JSON"), + eq(Boolean.class), + eq(false) + )).thenReturn(true); + Path file = tempDir.resolve(entity); + Files.write(file, content.getBytes(StandardCharsets.UTF_8)); + when(keymanagerHelper.getFileSignature(anyString())).thenReturn("SIG"); + when(clientCryptoManagerService.csEncrypt(any(TpmCryptoRequestDto.class))).thenThrow(new RuntimeException("enc error")); + service.getClientSettingsJsonFile(entity, keyIndex); + } + + @Test(expected = RequestException.class) + public void testGetClientSettingsJsonFileInvalidKeyIndexThrowsRequestException() throws Exception { + String entity = "settings.json"; + String keyIndex = " "; + service.getClientSettingsJsonFile(entity, keyIndex); + } + + // --------------------------------------------------------------------- + // validateKeyMachineMapping (public) + // --------------------------------------------------------------------- + + @Test + public void testValidateKeyMachineMappingSuccess() { + UploadPublicKeyRequestDto req = new UploadPublicKeyRequestDto(); + req.setMachineName("MACHINE"); + req.setPublicKey("BASE64KEY"); + Machine m = new Machine(); + m.setPublicKey("BASE64KEY"); + m.setKeyIndex("KEY-123"); + when(machineRepo.findByMachineName("MACHINE")).thenReturn(Collections.singletonList(m)); + byte[] decoded = new byte[]{1, 2, 3}; + when(cryptomanagerUtils.decodeBase64Data("BASE64KEY")).thenReturn(decoded); + UploadPublicKeyResponseDto resp = service.validateKeyMachineMapping(req); + assertEquals("KEY-123", resp.getKeyIndex()); + } + + @Test(expected = RequestException.class) + public void testValidateKeyMachineMappingKeyMismatchThrowsRequestException() { + UploadPublicKeyRequestDto req = new UploadPublicKeyRequestDto(); + req.setMachineName("MACHINE"); + req.setPublicKey("KEY1"); + Machine m = new Machine(); + m.setPublicKey("KEY2"); + m.setKeyIndex("K"); + when(machineRepo.findByMachineName("MACHINE")).thenReturn(Collections.singletonList(m)); + when(cryptomanagerUtils.decodeBase64Data("KEY1")).thenReturn(new byte[]{1}); + when(cryptomanagerUtils.decodeBase64Data("KEY2")).thenReturn(new byte[]{2}); + service.validateKeyMachineMapping(req); + } + + // --------------------------------------------------------------------- + // validateSyncClientSettingsInputs() + // --------------------------------------------------------------------- + + @Test(expected = RequestException.class) + public void testValidateSyncClientSettingsInputsRegCenterIdNull() { + ReflectionTestUtils.invokeMethod( + service, + "validateSyncClientSettingsInputs", + null, // regCenterId + "KEY1", // keyIndex + LocalDateTime.now().minusDays(1), + LocalDateTime.now() + ); + } + + @Test(expected = RequestException.class) + public void testValidateSyncClientSettingsInputsRegCenterIdEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateSyncClientSettingsInputs", + " ", // empty + "KEY1", + LocalDateTime.now().minusDays(1), + LocalDateTime.now() + ); + } + + @Test(expected = RequestException.class) + public void testValidateSyncClientSettingsInputsKeyIndexNull() { + ReflectionTestUtils.invokeMethod( + service, + "validateSyncClientSettingsInputs", + "RC1", + null, // keyIndex + LocalDateTime.now().minusDays(1), + LocalDateTime.now() + ); + } + + @Test(expected = RequestException.class) + public void testValidateSyncClientSettingsInputsKeyIndexEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateSyncClientSettingsInputs", + "RC1", + " ", // empty keyIndex + LocalDateTime.now().minusDays(1), + LocalDateTime.now() + ); + } + + @Test(expected = RequestException.class) + public void testValidateSyncClientSettingsInputsCurrentTimestampNull() { + ReflectionTestUtils.invokeMethod( + service, + "validateSyncClientSettingsInputs", + "RC1", + "KEY1", + LocalDateTime.now().minusDays(1), + null // currentTimestamp null + ); + } + + @Test(expected = RequestException.class) + public void testValidateSyncClientSettingsInputsLastUpdatedAfterCurrentTimestamp() { + LocalDateTime now = LocalDateTime.now(); + ReflectionTestUtils.invokeMethod( + service, + "validateSyncClientSettingsInputs", + "RC1", + "KEY1", + now.plusSeconds(1), // lastUpdated > currentTimestamp + now + ); + } + + @Test + public void testValidateSyncClientSettingsInputsValidInputsNoException() { + LocalDateTime now = LocalDateTime.now(); + ReflectionTestUtils.invokeMethod( + service, + "validateSyncClientSettingsInputs", + "RC1", + "KEY1", + now.minusDays(1), // valid + now + ); + } + + // --------------------------------------------------------------------- + // validateKeyMachineMappingInputs() + // --------------------------------------------------------------------- + + @Test(expected = RequestException.class) + public void testValidateKeyMachineMappingInputsNullDto() { + ReflectionTestUtils.invokeMethod(service, + "validateKeyMachineMappingInputs", + (UploadPublicKeyRequestDto) null); + } + + @Test(expected = RequestException.class) + public void testValidateKeyMachineMappingInputsMachineNameNull() { + UploadPublicKeyRequestDto dto = new UploadPublicKeyRequestDto(); + dto.setMachineName(null); + dto.setPublicKey("PUBKEY"); + + ReflectionTestUtils.invokeMethod(service, + "validateKeyMachineMappingInputs", + dto); + } + + @Test(expected = RequestException.class) + public void testValidateKeyMachineMappingInputsMachineNameEmpty() { + UploadPublicKeyRequestDto dto = new UploadPublicKeyRequestDto(); + dto.setMachineName(" "); // empty + dto.setPublicKey("PUBKEY"); + + ReflectionTestUtils.invokeMethod(service, + "validateKeyMachineMappingInputs", + dto); + } + + @Test(expected = RequestException.class) + public void testValidateKeyMachineMappingInputsPublicKeyNull() { + UploadPublicKeyRequestDto dto = new UploadPublicKeyRequestDto(); + dto.setMachineName("MACHINE1"); + dto.setPublicKey(null); + + ReflectionTestUtils.invokeMethod(service, + "validateKeyMachineMappingInputs", + dto); + } + + @Test(expected = RequestException.class) + public void testValidateKeyMachineMappingInputsPublicKeyEmpty() { + UploadPublicKeyRequestDto dto = new UploadPublicKeyRequestDto(); + dto.setMachineName("MACHINE1"); + dto.setPublicKey(" "); // empty + + ReflectionTestUtils.invokeMethod(service, + "validateKeyMachineMappingInputs", + dto); + } + + @Test + public void testValidateKeyMachineMappingInputsValidInputNoException() { + UploadPublicKeyRequestDto dto = new UploadPublicKeyRequestDto(); + dto.setMachineName("MACHINE1"); + dto.setPublicKey("PUBKEY123"); + + // Should NOT throw + ReflectionTestUtils.invokeMethod(service, + "validateKeyMachineMappingInputs", + dto); + } + + // --------------------------------------------------------------------- + // validateIdSchemaInputs() + // --------------------------------------------------------------------- + + @Test(expected = RequestException.class) + public void testValidateIdSchemaInputsDomainEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateIdSchemaInputs", + " ", // domain empty + "PERSON" // valid type + ); + } + + @Test(expected = RequestException.class) + public void testValidateIdSchemaInputsTypeEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateIdSchemaInputs", + "identity", // valid domain + " " // empty type + ); + } + + @Test + public void testValidateIdSchemaInputsNullValuesNoException() { + // Both NULL -> allowed + ReflectionTestUtils.invokeMethod( + service, + "validateIdSchemaInputs", + null, + null + ); + } + + @Test + public void testValidateIdSchemaInputsValidInputsNoException() { + ReflectionTestUtils.invokeMethod( + service, + "validateIdSchemaInputs", + "identity", + "PERSON" + ); + } + + // --------------------------------------------------------------------- + // validateCertificateInputs() + // --------------------------------------------------------------------- + + @Test(expected = RequestException.class) + public void testValidateCertificateInputsApplicationIdNull() { + ReflectionTestUtils.invokeMethod( + service, + "validateCertificateInputs", + null, // null applicationId + Optional.of("REF123") + ); + } + + @Test(expected = RequestException.class) + public void testValidateCertificateInputsApplicationIdEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateCertificateInputs", + " ", // empty applicationId + Optional.of("REF123") + ); + } + + @Test(expected = RequestException.class) + public void testValidateCertificateInputsReferenceIdEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateCertificateInputs", + "APP123", + Optional.of(" ") // empty referenceId + ); + } + + @Test + public void testValidateCertificateInputsReferenceIdNotPresentNoException() { + ReflectionTestUtils.invokeMethod( + service, + "validateCertificateInputs", + "APP123", + Optional.empty() // no referenceId → valid + ); + } + + @Test + public void testValidateCertificateInputsValidInputsNoException() { + ReflectionTestUtils.invokeMethod( + service, + "validateCertificateInputs", + "APP123", + Optional.of("REF456") // valid referenceId + ); + } + + // --------------------------------------------------------------------- + // validateClientPublicKeyInputs() + // --------------------------------------------------------------------- + + @Test(expected = RequestException.class) + public void testValidateClientPublicKeyInputsMachineIdNull() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientPublicKeyInputs", + (String) null // machineId = null + ); + } + + @Test(expected = RequestException.class) + public void testValidateClientPublicKeyInputsMachineIdEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientPublicKeyInputs", + " " // machineId empty + ); + } + + @Test + public void testValidateClientPublicKeyInputsValidInputNoException() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientPublicKeyInputs", + "MACHINE123" // valid + ); + } + + // --------------------------------------------------------------------- + // validateClientSettingsJsonFileInputs() + // --------------------------------------------------------------------- + + @Test(expected = RequestException.class) + public void testValidateClientSettingsJsonFileInputsEntityIdentifierNull() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + null, // entityIdentifier + "KEY1" + ); + } + + @Test(expected = RequestException.class) + public void testValidateClientSettingsJsonFileInputsEntityIdentifierEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + " ", // empty + "KEY1" + ); + } + + @Test(expected = RequestException.class) + public void testValidateClientSettingsJsonFileInputsKeyIndexNull() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + "ENTITY123", + null // keyIndex null + ); + } + + @Test(expected = RequestException.class) + public void testValidateClientSettingsJsonFileInputsKeyIndexEmpty() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + "ENTITY123", + " " // empty + ); + } + + @Test(expected = RequestException.class) + public void testValidateClientSettingsJsonFileInputsPathTraversalDoubleDot() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + "folder..name", // contains ".." + "KEY1" + ); + } + + @Test(expected = RequestException.class) + public void testValidateClientSettingsJsonFileInputsPathTraversalSlash() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + "folder/name", // contains "/" + "KEY1" + ); + } + + @Test(expected = RequestException.class) + public void testValidateClientSettingsJsonFileInputsPathTraversalBackslash() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + "folder\\name", // contains "\" + "KEY1" + ); + } + + @Test + public void testValidateClientSettingsJsonFileInputsValidNoException() { + ReflectionTestUtils.invokeMethod( + service, + "validateClientSettingsJsonFileInputs", + "ENTITY123", + "KEY1" + ); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncRolesServiceImplTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncRolesServiceImplTest.java new file mode 100644 index 0000000000..843665bccf --- /dev/null +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncRolesServiceImplTest.java @@ -0,0 +1,222 @@ +package io.mosip.kernel.syncdata.test.impl; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.mosip.kernel.core.authmanager.exception.AuthNException; +import io.mosip.kernel.core.authmanager.exception.AuthZException; +import io.mosip.kernel.core.exception.ServiceError; +import io.mosip.kernel.core.http.ResponseWrapper; +import io.mosip.kernel.syncdata.dto.response.RolesResponseDto; +import io.mosip.kernel.syncdata.exception.ParseResponseException; +import io.mosip.kernel.syncdata.exception.SyncDataServiceException; +import io.mosip.kernel.syncdata.exception.SyncServiceException; +import io.mosip.kernel.syncdata.service.impl.SyncRolesServiceImpl; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.*; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; + + +@RunWith(MockitoJUnitRunner.class) +public class SyncRolesServiceImplTest { + + private SyncRolesServiceImpl service; + + @Mock + private RestTemplate restTemplate; + + private ObjectMapper objectMapper; + + @Before + public void setUp() { + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + service = new SyncRolesServiceImpl(); + ReflectionTestUtils.setField(service, "restTemplate", restTemplate); + ReflectionTestUtils.setField(service, "objectMapper", objectMapper); + ReflectionTestUtils.setField(service, "authBaseUrl", "http://auth"); + ReflectionTestUtils.setField(service, "authServiceName", "/roles"); + ReflectionTestUtils.setField(service, "syncDataRequestId", "SYNCDATA.REQUEST"); + ReflectionTestUtils.setField(service, "syncDataVersionId", "v1.0"); + } + + private String buildSuccessBody() throws Exception { + ResponseWrapper wrapper = new ResponseWrapper<>(); + RolesResponseDto dto = new RolesResponseDto(); + wrapper.setResponse(dto); + return objectMapper.writeValueAsString(wrapper); + } + + private String buildBodyWithErrors() throws Exception { + ResponseWrapper wrapper = new ResponseWrapper<>(); + ServiceError error = new ServiceError("TEST", "TEST MESSAGE"); + wrapper.setErrors(Collections.singletonList(error)); + return objectMapper.writeValueAsString(wrapper); + } + + /* ======================= SUCCESS PATH ======================= */ + + @Test + public void testGetAllRolesSuccess() throws Exception { + String body = buildSuccessBody(); + ResponseEntity httpResponse = ResponseEntity.ok(body); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenReturn(httpResponse); + RolesResponseDto result = service.getAllRoles(); + assertNotNull(result); + } + + /* ======================= HTTP ERROR 401 ======================= */ + + @Test(expected = AuthNException.class) + public void testGetAllRolesHttp401WithErrorsThrowsAuthNException() throws Exception { + String errorBody = buildBodyWithErrors(); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.UNAUTHORIZED, + "401", + HttpHeaders.EMPTY, + errorBody.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenThrow(ex); + service.getAllRoles(); + } + + @Test(expected = BadCredentialsException.class) + public void testGetAllRolesHttp401NoErrorsThrowsBadCredentialsException() throws Exception { + String errorBody = buildSuccessBody(); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.UNAUTHORIZED, + "401", + HttpHeaders.EMPTY, + errorBody.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenThrow(ex); + service.getAllRoles(); + } + + /* ======================= HTTP ERROR 403 ======================= */ + + @Test(expected = AuthZException.class) + public void testGetAllRolesHttp403WithErrorsThrowsAuthZException() throws Exception { + String errorBody = buildBodyWithErrors(); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.FORBIDDEN, + "403", + HttpHeaders.EMPTY, + errorBody.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenThrow(ex); + service.getAllRoles(); + } + + @Test(expected = AccessDeniedException.class) + public void testGetAllRolesHttp403NoErrorsThrowsAccessDeniedException() throws Exception { + String errorBody = buildSuccessBody(); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.FORBIDDEN, + "403", + HttpHeaders.EMPTY, + errorBody.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenThrow(ex); + service.getAllRoles(); + } + + /* ======================= AUTRE HTTP ERROR (500) ======================= */ + + @Test(expected = SyncDataServiceException.class) + public void testGetAllRolesHttp500ThrowsSyncDataServiceException() throws Exception { + String errorBody = buildSuccessBody(); + HttpServerErrorException ex = HttpServerErrorException.create( + HttpStatus.INTERNAL_SERVER_ERROR, + "500", + HttpHeaders.EMPTY, + errorBody.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenThrow(ex); + service.getAllRoles(); + } + + /* ======================= RESPONSE AVEC ERREURS ======================= */ + + @Test(expected = SyncServiceException.class) + public void testGetAllRolesResponseHasValidationErrorsThrowsSyncServiceException() throws Exception { + String body = buildBodyWithErrors(); + ResponseEntity httpResponse = ResponseEntity.ok(body); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenReturn(httpResponse); + service.getAllRoles(); + } + + /* ======================= PARSE ERROR ======================= */ + + @Test(expected = ParseResponseException.class) + public void testGetAllRolesParseResponseIOExceptionThrowsParseResponseException() { + String body = "NOT_JSON"; + ResponseEntity httpResponse = ResponseEntity.ok(body); + when(restTemplate.exchange( + anyString(), + eq(HttpMethod.GET), + any(HttpEntity.class), + eq(String.class)) + ).thenReturn(httpResponse); + service.getAllRoles(); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncUserDetailsServiceImplTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncUserDetailsServiceImplTest.java new file mode 100644 index 0000000000..1c4ffbd559 --- /dev/null +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/impl/SyncUserDetailsServiceImplTest.java @@ -0,0 +1,389 @@ +package io.mosip.kernel.syncdata.test.impl; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.mosip.kernel.clientcrypto.constant.ClientType; +import io.mosip.kernel.clientcrypto.dto.TpmCryptoRequestDto; +import io.mosip.kernel.clientcrypto.dto.TpmCryptoResponseDto; +import io.mosip.kernel.clientcrypto.service.spi.ClientCryptoManagerService; +import io.mosip.kernel.core.authmanager.exception.AuthNException; +import io.mosip.kernel.core.authmanager.exception.AuthZException; +import io.mosip.kernel.core.exception.ServiceError; +import io.mosip.kernel.core.http.ResponseWrapper; +import io.mosip.kernel.syncdata.dto.RegistrationCenterMachineDto; +import io.mosip.kernel.syncdata.dto.RegistrationCenterUserDto; +import io.mosip.kernel.syncdata.dto.SyncUserDto; +import io.mosip.kernel.syncdata.dto.response.RegistrationCenterUserResponseDto; +import io.mosip.kernel.syncdata.dto.response.UserDetailResponseDto; +import io.mosip.kernel.syncdata.entity.UserDetails; +import io.mosip.kernel.syncdata.exception.DataNotFoundException; +import io.mosip.kernel.syncdata.exception.ParseResponseException; +import io.mosip.kernel.syncdata.exception.SyncDataServiceException; +import io.mosip.kernel.syncdata.exception.SyncServiceException; +import io.mosip.kernel.syncdata.repository.UserDetailsRepository; +import io.mosip.kernel.syncdata.service.impl.SyncUserDetailsServiceImpl; +import io.mosip.kernel.syncdata.utils.MapperUtils; +import io.mosip.kernel.syncdata.utils.SyncMasterDataServiceHelper; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.dao.DataAccessException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + + +public class SyncUserDetailsServiceImplTest { + + private SyncUserDetailsServiceImpl service; + + @Mock + private UserDetailsRepository userDetailsRepository; + + @Mock + private SyncMasterDataServiceHelper serviceHelper; + + @Mock + private ClientCryptoManagerService clientCryptoManagerService; + + @Mock + private MapperUtils mapper; + + @Mock + private RestTemplate restTemplate; + + private ObjectMapper objectMapper; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + service = new SyncUserDetailsServiceImpl(); + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + ReflectionTestUtils.setField(service, "userDetailsRepository", userDetailsRepository); + ReflectionTestUtils.setField(service, "serviceHelper", serviceHelper); + ReflectionTestUtils.setField(service, "clientCryptoManagerService", clientCryptoManagerService); + ReflectionTestUtils.setField(service, "mapper", mapper); + ReflectionTestUtils.setField(service, "restTemplate", restTemplate); + ReflectionTestUtils.setField(service, "objectMapper", objectMapper); + ReflectionTestUtils.setField(service, "authUserDetailsBaseUri", "http://auth"); + ReflectionTestUtils.setField(service, "authUserDetailsUri", "/userdetails"); + ReflectionTestUtils.setField(service, "syncDataRequestId", "SYNCDATA.REQUEST"); + ReflectionTestUtils.setField(service, "syncDataVersionId", "v1.0"); + ReflectionTestUtils.setField(service, "pageSize", 2); + } + + // ------------------------------------------------------------------------- + // getUserDetailsFromAuthServer – invalid params + // ------------------------------------------------------------------------- + + @Test(expected = NullPointerException.class) + public void testGetUserDetailsFromAuthServerNullIds() { + service.getUserDetailsFromAuthServer(null); + } + + @Test(expected = NullPointerException.class) + public void testGetUserDetailsFromAuthServerEmptyIds() { + service.getUserDetailsFromAuthServer(Arrays.asList()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetUserDetailsFromAuthServerNullResponse() throws Exception { + List ids = Arrays.asList("u1", "u2"); + ResponseEntity entity = new ResponseEntity<>(null, HttpStatus.OK); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenReturn(entity); + service.getUserDetailsFromAuthServer(ids); + } + + @Test(expected = ParseResponseException.class) + public void testGetUserDetailsFromAuthServerEmptyResponse() throws Exception { + List ids = Arrays.asList("u1", "u2"); + ResponseEntity entity = new ResponseEntity<>("", HttpStatus.OK); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenReturn(entity); + service.getUserDetailsFromAuthServer(ids); + } + + // ------------------------------------------------------------------------- + // getUserDetailsFromAuthServer – success + // ------------------------------------------------------------------------- + + @Test + public void testGetUserDetailsFromAuthServerSuccess() throws Exception { + List ids = Arrays.asList("u1", "u2"); + UserDetailResponseDto dto = new UserDetailResponseDto(); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(dto); + String body = objectMapper.writeValueAsString(wrapper); + ResponseEntity entity = new ResponseEntity<>(body, HttpStatus.OK); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenReturn(entity); + UserDetailResponseDto result = service.getUserDetailsFromAuthServer(ids); + assertEquals(dto, result); + } + + // ------------------------------------------------------------------------- + // getUserDetailsFromAuthServer – 401/403/500 branches + // ------------------------------------------------------------------------- + + @Test(expected = AuthNException.class) + public void testGetUserDetailsFromAuthServer401WithErrorsThrowsAuthN() throws Exception { + List ids = Collections.singletonList("u1"); + ServiceError err = new ServiceError("KER-TEST-001", "auth error"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setErrors(Collections.singletonList(err)); + String body = objectMapper.writeValueAsString(wrapper); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.UNAUTHORIZED, + "401", + HttpHeaders.EMPTY, + body.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenThrow(ex); + service.getUserDetailsFromAuthServer(ids); + } + + @Test(expected = BadCredentialsException.class) + public void testGetUserDetailsFromAuthServer401NoErrorsThrowsBadCredentials() throws Exception { + List ids = Collections.singletonList("u1"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + String body = objectMapper.writeValueAsString(wrapper); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.UNAUTHORIZED, + "401", + HttpHeaders.EMPTY, + body.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenThrow(ex); + service.getUserDetailsFromAuthServer(ids); + } + + @Test(expected = AuthZException.class) + public void testGetUserDetailsFromAuthServer403WithErrorsThrowsAuthZ() throws Exception { + List ids = Collections.singletonList("u1"); + ServiceError err = new ServiceError("KER-TEST-002", "authz error"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setErrors(Collections.singletonList(err)); + String body = objectMapper.writeValueAsString(wrapper); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.FORBIDDEN, + "403", + HttpHeaders.EMPTY, + body.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenThrow(ex); + service.getUserDetailsFromAuthServer(ids); + } + + @Test(expected = AccessDeniedException.class) + public void testGetUserDetailsFromAuthServer403NoErrorsThrowsAccessDenied() throws Exception { + List ids = Collections.singletonList("u1"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + String body = objectMapper.writeValueAsString(wrapper); + HttpClientErrorException ex = HttpClientErrorException.create( + HttpStatus.FORBIDDEN, + "403", + HttpHeaders.EMPTY, + body.getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenThrow(ex); + service.getUserDetailsFromAuthServer(ids); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetUserDetailsFromAuthServer500ThrowsSyncDataServiceException() throws Exception { + List ids = Collections.singletonList("u1"); + HttpServerErrorException ex = HttpServerErrorException.create( + HttpStatus.INTERNAL_SERVER_ERROR, + "500", + HttpHeaders.EMPTY, + "error".getBytes(StandardCharsets.UTF_8), + StandardCharsets.UTF_8 + ); + when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), eq(String.class))).thenThrow(ex); + service.getUserDetailsFromAuthServer(ids); + } + + // ------------------------------------------------------------------------- + // getUserDetailFromResponse (private) via ReflectionTestUtils + // ------------------------------------------------------------------------- + + @Test(expected = SyncServiceException.class) + public void testGetUserDetailFromResponseWithErrorsThrowsSyncServiceException() throws Exception { + ServiceError err = new ServiceError("KER-TEST-003", "validation"); + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setErrors(Collections.singletonList(err)); + String json = objectMapper.writeValueAsString(wrapper); + ReflectionTestUtils.invokeMethod(service, "getUserDetailFromResponse", json); + } + + @Test(expected = ParseResponseException.class) + public void testGetUserDetailFromResponseNullResponseThrowsParseResponseException() throws Exception { + ResponseWrapper wrapper = new ResponseWrapper<>(); + wrapper.setResponse(null); + String json = objectMapper.writeValueAsString(wrapper); + ReflectionTestUtils.invokeMethod(service, "getUserDetailFromResponse", json); + } + + @Test(expected = ParseResponseException.class) + public void testGetUserDetailFromResponse_InvalidJson_ThrowsParseResponseException() { + ReflectionTestUtils.invokeMethod(service, "getUserDetailFromResponse", "not-json"); + } + + // ------------------------------------------------------------------------- + // getUsersBasedOnRegistrationCenterId + // ------------------------------------------------------------------------- + + @Test + public void testGetUsersBasedOnRegistrationCenterIdSuccess() { + String regCenterId = "RC1"; + UserDetails u1 = mock(UserDetails.class); + when(u1.getIsActive()).thenReturn(true); + when(u1.getIsDeleted()).thenReturn(false); + when(u1.getLangCode()).thenReturn("en"); + when(u1.getRegCenterId()).thenReturn(regCenterId); + when(u1.getId()).thenReturn("user1"); + when(userDetailsRepository.findByUsersByRegCenterId(regCenterId)).thenReturn(Collections.singletonList(u1)); + RegistrationCenterUserResponseDto resp = service.getUsersBasedOnRegistrationCenterId(regCenterId); + assertNotNull(resp); + assertEquals(1, resp.getRegistrationCenterUsers().size()); + assertEquals("user1", resp.getRegistrationCenterUsers().get(0).getUserId()); + } + + @Test(expected = SyncDataServiceException.class) + public void testGetUsersBasedOnRegistrationCenterIdDataAccessException() { + when(userDetailsRepository.findByUsersByRegCenterId("RC1")).thenThrow( + new DataAccessException("db") { + } + ); + service.getUsersBasedOnRegistrationCenterId("RC1"); + } + + @Test(expected = DataNotFoundException.class) + public void testGetUsersBasedOnRegistrationCenterIdNoUsers() { + when(userDetailsRepository.findByUsersByRegCenterId("RC1")).thenReturn(Collections.emptyList()); + service.getUsersBasedOnRegistrationCenterId("RC1"); + } + + // ------------------------------------------------------------------------- + // fetchUsersPaged – plusieurs pages + // ------------------------------------------------------------------------- + + @Test + public void testFetchUsersPagedReturnsAllPages() { + String regCenterId = "RC1"; + UserDetails u1 = mock(UserDetails.class); + when(u1.getIsActive()).thenReturn(true); + when(u1.getIsDeleted()).thenReturn(false); + when(u1.getLangCode()).thenReturn("en"); + when(u1.getRegCenterId()).thenReturn(regCenterId); + when(u1.getId()).thenReturn("user1"); + UserDetails u2 = mock(UserDetails.class); + when(u2.getIsActive()).thenReturn(true); + when(u2.getIsDeleted()).thenReturn(false); + when(u2.getLangCode()).thenReturn("en"); + when(u2.getRegCenterId()).thenReturn(regCenterId); + when(u2.getId()).thenReturn("user2"); + UserDetails u3 = mock(UserDetails.class); + when(u3.getIsActive()).thenReturn(true); + when(u3.getIsDeleted()).thenReturn(false); + when(u3.getLangCode()).thenReturn("en"); + when(u3.getRegCenterId()).thenReturn(regCenterId); + when(u3.getId()).thenReturn("user3"); + Page page1 = new PageImpl<>(Arrays.asList(u1, u2), PageRequest.of(0, 2), 3); + Page page2 = new PageImpl<>(Collections.singletonList(u3), PageRequest.of(1, 2), 3); + when(userDetailsRepository.findPageByRegCenterId( + eq(regCenterId), + any(PageRequest.class) + )).thenReturn(page1, page2); + @SuppressWarnings("unchecked") + List result = (List) ReflectionTestUtils.invokeMethod( + service, + "fetchUsersPaged", + regCenterId + ); + assertEquals(3, result.size()); + assertEquals("user1", result.get(0).getUserId()); + assertEquals("user3", result.get(2).getUserId()); + } + + // ------------------------------------------------------------------------- + // getAllUserDetailsBasedOnKeyIndexV2 + // ------------------------------------------------------------------------- + + @Test + public void testGetAllUserDetailsBasedOnKeyIndexV2SuccessEncrypts() throws Exception { + String keyIndex = "KEY1"; + String regCenterId = "RC1"; + RegistrationCenterMachineDto machineDto = new RegistrationCenterMachineDto(); + machineDto.setRegCenterId(regCenterId); + machineDto.setPublicKey("PUB_KEY"); + machineDto.setClientType(ClientType.LOCAL); + when(serviceHelper.getRegistrationCenterMachine(null, keyIndex)).thenReturn(machineDto); + UserDetails u1 = mock(UserDetails.class); + when(u1.getIsActive()).thenReturn(true); + when(u1.getIsDeleted()).thenReturn(false); + when(u1.getLangCode()).thenReturn("en"); + when(u1.getRegCenterId()).thenReturn(regCenterId); + when(u1.getId()).thenReturn("user1"); + Page page = new PageImpl<>( + Collections.singletonList(u1), + PageRequest.of(0, 2), + 1 + ); + when(userDetailsRepository.findPageByRegCenterId(eq(regCenterId), any(PageRequest.class))).thenReturn(page); + when(mapper.getObjectAsJsonString(any())).thenReturn("[{\"userId\":\"user1\"}]"); + TpmCryptoResponseDto enc = new TpmCryptoResponseDto(); + enc.setValue("encrypted-value"); + when(clientCryptoManagerService.csEncrypt(any(TpmCryptoRequestDto.class))).thenReturn(enc); + SyncUserDto dto = service.getAllUserDetailsBasedOnKeyIndexV2(keyIndex); + assertNotNull(dto); + assertEquals("encrypted-value", dto.getUserDetails()); + verify(clientCryptoManagerService, times(1)).csEncrypt(any(TpmCryptoRequestDto.class)); + } + + @Test + public void testGetAllUserDetailsBasedOnKeyIndexV2NoUsersNoEncryption() { + String keyIndex = "KEY1"; + String regCenterId = "RC1"; + RegistrationCenterMachineDto machineDto = new RegistrationCenterMachineDto(); + machineDto.setRegCenterId(regCenterId); + machineDto.setPublicKey("PUB_KEY"); + machineDto.setClientType(ClientType.LOCAL); + when(serviceHelper.getRegistrationCenterMachine(null, keyIndex)).thenReturn(machineDto); + Page emptyPage = new PageImpl<>( + Collections.emptyList(), + PageRequest.of(0, 2), + 0 + ); + when(userDetailsRepository.findPageByRegCenterId(eq(regCenterId), any(PageRequest.class))).thenReturn(emptyPage); + SyncUserDto dto = service.getAllUserDetailsBasedOnKeyIndexV2(keyIndex); + assertNotNull(dto); + assertNull(dto.getUserDetails()); + verify(clientCryptoManagerService, never()).csEncrypt(any(TpmCryptoRequestDto.class)); + } + +} \ No newline at end of file diff --git a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/service/SyncJobHelperServiceTest.java b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/service/SyncJobHelperServiceTest.java index ac84fdb877..a9e7216d84 100644 --- a/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/service/SyncJobHelperServiceTest.java +++ b/admin/kernel-syncdata-service/src/test/java/io/mosip/kernel/syncdata/test/service/SyncJobHelperServiceTest.java @@ -3,6 +3,7 @@ import io.mosip.kernel.syncdata.dto.BlacklistedWordsDto; import io.mosip.kernel.syncdata.dto.DynamicFieldDto; import io.mosip.kernel.syncdata.entity.*; +import io.mosip.kernel.syncdata.exception.SyncDataServiceException; import io.mosip.kernel.syncdata.service.helper.SyncJobHelperService; import io.mosip.kernel.syncdata.utils.MapperUtils; import io.mosip.kernel.syncdata.utils.SyncMasterDataServiceHelper; @@ -15,6 +16,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RestTemplate; import java.time.LocalDateTime; @@ -44,7 +46,7 @@ public class SyncJobHelperServiceTest { private SyncJobHelperService syncJobHelperService; @Test - public void testEvictDeltaCaches_Success() { + public void testEvictDeltaCachesSuccess() { Cache cache = mock(Cache.class); lenient().when(cacheManager.getCache("delta-sync")).thenReturn(cache); @@ -56,7 +58,7 @@ public void testEvictDeltaCaches_Success() { } @Test - public void testGetFullSyncCurrentTimestamp_Success() { + public void testGetFullSyncCurrentTimestampSuccess() { LocalDateTime localDateTime = LocalDateTime.now(ZoneOffset.UTC); LocalDateTime expectedTimestamp = localDateTime.withHour(0).withMinute(0).withSecond(0).withNano(0); @@ -66,12 +68,12 @@ public void testGetFullSyncCurrentTimestamp_Success() { } @Test - public void testGetDeltaSyncCurrentTimestamp_Success() { + public void testGetDeltaSyncCurrentTimestampSuccess() { lenient().when(serviceHelper.getAppAuthenticationMethodDetails(any(), any(LocalDateTime.class))).thenReturn(CompletableFuture.completedFuture(new ArrayList<>())); } @Test - public void testClearCacheAndRecreateSnapshot_Success() throws Exception { + public void testClearCacheAndRecreateSnapshotSuccess() throws Exception { List appAuthMethods = new ArrayList<>(); List appRolePriorities = new ArrayList<>(); List