diff --git a/mock-identity-system/pom.xml b/mock-identity-system/pom.xml index d7d39db4..2f3a5e74 100644 --- a/mock-identity-system/pom.xml +++ b/mock-identity-system/pom.xml @@ -138,6 +138,41 @@ + + + com.networknt + json-schema-validator + 1.5.1 + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + + + com.fasterxml.jackson.core + jackson-databind + 2.12.1 + + + com.fasterxml.jackson.core + jackson-core + 2.12.1 + + + com.fasterxml.jackson.core + jackson-annotations + 2.12.1 + diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/controller/IdentityController.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/controller/IdentityController.java index a75e5cb2..12871f0c 100644 --- a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/controller/IdentityController.java +++ b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/controller/IdentityController.java @@ -5,18 +5,28 @@ */ package io.mosip.esignet.mock.identitysystem.controller; +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; import javax.validation.Valid; import io.mosip.esignet.mock.identitysystem.dto.*; +import io.mosip.esignet.mock.identitysystem.dto.Error; +import io.mosip.esignet.mock.identitysystem.validator.IdentitySchema; +import io.mosip.kernel.core.exception.ErrorResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import io.mosip.esignet.mock.identitysystem.exception.MockIdentityException; import io.mosip.esignet.mock.identitysystem.service.IdentityService; import io.mosip.esignet.mock.identitysystem.util.HelperUtil; +import java.util.ArrayList; import java.util.List; +import java.util.Set; @RestController @@ -29,7 +39,7 @@ public class IdentityController { @PostMapping(value = "identity", consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE }) public ResponseWrapper createIdentity - (@Valid @RequestBody RequestWrapper requestWrapper) throws MockIdentityException { + (@RequestBody @Valid RequestWrapper requestWrapper) throws MockIdentityException { ResponseWrapper response = new ResponseWrapper(); IdentityStatus identityStatus = new IdentityStatus(); @@ -43,7 +53,7 @@ public class IdentityController { @PutMapping(value = "identity", consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE }) public ResponseWrapper updateIdentity - (@Valid @RequestBody RequestWrapper requestWrapper) throws MockIdentityException { + (@RequestBody @Valid RequestWrapper requestWrapper) throws MockIdentityException { ResponseWrapper response = new ResponseWrapper(); IdentityStatus identityStatus = new IdentityStatus(); @@ -74,4 +84,25 @@ public ResponseWrapper createVerifiedClaim(@Valid @RequestB return response; } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleMethodArgumentNotValidException(ConstraintViolationException ex) { + List errors = new ArrayList<>(); + if(ex != null) { + Set> violations = ((ConstraintViolationException) ex).getConstraintViolations(); + for(ConstraintViolation cv : violations) { + errors.add(new Error(cv.getMessage(), cv.getPropertyPath().toString() + ": " + cv.getMessage())); + } + return new ResponseEntity(getResponseWrapper(errors), HttpStatus.OK); + } + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(errors); + } + + private ResponseWrapper getResponseWrapper(List errors) { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + responseWrapper.setResponseTime(HelperUtil.getCurrentUTCDateTime()); + responseWrapper.setErrors(errors); + return responseWrapper; + } } diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/CreateIdentity.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/CreateIdentity.java index bf646aeb..9c91b8db 100644 --- a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/CreateIdentity.java +++ b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/CreateIdentity.java @@ -5,8 +5,8 @@ */ package io.mosip.esignet.mock.identitysystem.dto; -import io.mosip.esignet.mock.identitysystem.validator.IdData; +import io.mosip.esignet.mock.identitysystem.validator.IdentitySchema; -@IdData(action = "CREATE") +@IdentitySchema(action = "CREATE") public class CreateIdentity extends IdentityData { } diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/IdentityData.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/IdentityData.java index 58c50a26..a4928f89 100644 --- a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/IdentityData.java +++ b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/IdentityData.java @@ -8,18 +8,13 @@ import java.util.List; -import javax.validation.constraints.NotBlank; - import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import io.mosip.esignet.mock.identitysystem.util.ErrorConstants; -import io.mosip.esignet.mock.identitysystem.validator.IdData; import lombok.Data; @Data @JsonIgnoreProperties(ignoreUnknown = true) public class IdentityData { - - @NotBlank(message = ErrorConstants.INVALID_INDIVIDUAL_ID) + String individualId; String pin; diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/UpdateIdentity.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/UpdateIdentity.java index d5325270..f891bc95 100644 --- a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/UpdateIdentity.java +++ b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/dto/UpdateIdentity.java @@ -5,8 +5,8 @@ */ package io.mosip.esignet.mock.identitysystem.dto; -import io.mosip.esignet.mock.identitysystem.validator.IdData; +import io.mosip.esignet.mock.identitysystem.validator.IdentitySchema; -@IdData(action = "UPDATE") +@IdentitySchema(action = "UPDATE") public class UpdateIdentity extends IdentityData { } diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/util/ErrorConstants.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/util/ErrorConstants.java index f903b905..910e0361 100644 --- a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/util/ErrorConstants.java +++ b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/util/ErrorConstants.java @@ -40,4 +40,5 @@ public class ErrorConstants { public static final String INVALID_TRUST_FRAMEWORK = "invalid_trust_framework"; public static final String INVALID_VERIFIED_DATE = "invalid_verified_date"; public static final String CLAIM_ALREADY_EXISTS="Claim already exists"; + public static final String INVALID_IDENTITY_DATA= "invalid_identity_data"; } diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdData.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdData.java deleted file mode 100644 index d4829155..00000000 --- a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdData.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.mosip.esignet.mock.identitysystem.validator; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static io.mosip.esignet.mock.identitysystem.util.ErrorConstants.INVALID_REQUEST; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import javax.validation.Constraint; -import javax.validation.Payload; - -@Target({TYPE}) -@Retention(RUNTIME) -@Constraint(validatedBy = IdentityDataValidator.class) -@Documented -public @interface IdData { - - String message() default INVALID_REQUEST; - - Class[] groups() default {}; - - Class[] payload() default {}; - - String action() default "CREATE"; - -} diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentityDataValidator.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentityDataValidator.java deleted file mode 100644 index 9ced35b3..00000000 --- a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentityDataValidator.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ -package io.mosip.esignet.mock.identitysystem.validator; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.mosip.esignet.mock.identitysystem.dto.IdentityData; - -@Component -public class IdentityDataValidator implements ConstraintValidator { - - @Value("#{T(java.util.Arrays).asList('${mosip.mock.identity.create.required.fields:}')}") - private List createRequiredFields; - - @Value("#{T(java.util.Arrays).asList('${mosip.mock.identity.update.required.fields:}')}") - private List updateRequiredFields; - - private String action; - - @Override - public void initialize(IdData constraintAnnotation) { - this.action = constraintAnnotation.action(); - } - - @Override - public boolean isValid(IdentityData value, ConstraintValidatorContext context) { - if (value == null) { - return false; - } - - @SuppressWarnings("unchecked") - Map fields = new ObjectMapper().convertValue(value, HashMap.class); - - fields.values().removeIf(Objects::isNull); - - return fields.keySet().containsAll(this.action.equals("UPDATE") ? updateRequiredFields : createRequiredFields); - } - -} diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentitySchema.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentitySchema.java new file mode 100644 index 00000000..27ef0caa --- /dev/null +++ b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentitySchema.java @@ -0,0 +1,23 @@ +package io.mosip.esignet.mock.identitysystem.validator; + + +import io.mosip.esignet.mock.identitysystem.util.ErrorConstants; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE_USE, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = IdentitySchemaValidator.class) +public @interface IdentitySchema { + + String message() default ErrorConstants.INVALID_IDENTITY_DATA; + Class[] groups() default {}; + Class[] payload() default {}; + + String action(); +} diff --git a/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentitySchemaValidator.java b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentitySchemaValidator.java new file mode 100644 index 00000000..301b0fb1 --- /dev/null +++ b/mock-identity-system/src/main/java/io/mosip/esignet/mock/identitysystem/validator/IdentitySchemaValidator.java @@ -0,0 +1,109 @@ +package io.mosip.esignet.mock.identitysystem.validator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.*; +import io.mosip.esignet.mock.identitysystem.dto.IdentityData; +import io.mosip.esignet.mock.identitysystem.dto.RequestWrapper; +import lombok.extern.slf4j.Slf4j; +import org.jose4j.lang.StringUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +@Slf4j +public class IdentitySchemaValidator implements ConstraintValidator { + + @Value("${mosip.mock.ida.identity.schema.url}") + private String identitySchemaUrl; + + @Value("#{${mosip.mock.ida.update-identity.non-mandatory.fields}}") + private Set nonMandatoryFieldsOnUpdate; + + private String action; + + private volatile JsonSchema schema; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + ResourceLoader resourceLoader; + + + @Override + public void initialize(IdentitySchema constraintAnnotation) { + this.action = constraintAnnotation.action(); + } + + @Override + public boolean isValid(Object object, ConstraintValidatorContext context) { + + if (!(object instanceof IdentityData)) { + return false; + } + IdentityData identityData=(IdentityData) object; + JsonNode identityJsonNode = objectMapper.valueToTree(identityData); + Set validationErrors = validateIdentityData(identityJsonNode); + + // Handle validation errors + if (!validationErrors.isEmpty()) { + addValidationErrorCode(validationErrors,context); + return false; + } + return true; + } + + private Set validateIdentityData(JsonNode identityJsonNode) { + Set errors = getSchema().validate(identityJsonNode); + // If not a create operation, filter out specific errors + if (action.equals("UPDATE")) { + // Ignore validation errors with code 1029 (null value) and for exempted fields when validating updateIdentity + errors = errors.stream() + .filter(error -> !error.getCode().equals("1029") || + !nonMandatoryFieldsOnUpdate.contains(error. + getInstanceLocation().getName(0))) + .collect(Collectors.toSet()); + } + return errors; + } + + private void addValidationErrorCode(Set errors, ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + errors.forEach(error->context. + buildConstraintViolationWithTemplate("invalid_"+error.getInstanceLocation().getName(0).toLowerCase()) + .addConstraintViolation()); + } + + private JsonSchema getSchema() { + if(schema !=null ) return schema; + synchronized (this) { + if (schema == null) { + InputStream schemaResponse = getResource(identitySchemaUrl); + JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012); + schema = jsonSchemaFactory.getSchema(schemaResponse); + } + } + return schema; + } + + private InputStream getResource(String url) { + try{ + Resource resource = resourceLoader.getResource(url); + return resource.getInputStream(); + }catch (IOException e){ + log.error("Failed to parse data: {}", url, e); + } + throw new RuntimeException("invalid_configuration"); + } +} diff --git a/mock-identity-system/src/main/resources/application-default.properties b/mock-identity-system/src/main/resources/application-default.properties index 0a123419..0254df88 100644 --- a/mock-identity-system/src/main/resources/application-default.properties +++ b/mock-identity-system/src/main/resources/application-default.properties @@ -103,12 +103,10 @@ spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true mosip.esignet.mock.authenticator.ida.otp-channels=email,phone -mosip.mock.identity.create.required.fields=individualId,fullName,givenName,familyName,gender,dateOfBirth,email,phone,streetAddress,locality,region,postalCode,country -mosip.mock.identity.update.required.fields=individualId mosip.esignet.mock.supported-fields=individualId,pin,givenName,familyName,gender,dateOfBirth,email,phone,streetAddress,locality,region,postalCode,country mosip.mock.ida.kba.default.field-language=eng -mosip.mock.ida.kyc.psut.field=psut + #Related to health check of hsm mosip.kernel.keymgr.hsm.health.check.enabled=false diff --git a/mock-identity-system/src/main/resources/bootstrap.properties b/mock-identity-system/src/main/resources/bootstrap.properties index 08225801..f5ea042c 100644 --- a/mock-identity-system/src/main/resources/bootstrap.properties +++ b/mock-identity-system/src/main/resources/bootstrap.properties @@ -1,7 +1,8 @@ spring.application.name=mock-identity-system #spring.cloud.config.uri=http://localhost:8888 spring.profiles.active=default,local - +#spring.cloud.config.label=master +spring.cloud.config.name=mock-identity-system spring.main.allow-bean-definition-overriding=true server.port=8082 diff --git a/mock-identity-system/src/main/resources/mock-identity-schema.json b/mock-identity-system/src/main/resources/mock-identity-schema.json new file mode 100644 index 00000000..41081997 --- /dev/null +++ b/mock-identity-system/src/main/resources/mock-identity-schema.json @@ -0,0 +1,248 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "$defs": { + "langField": { + "type": "array", + "items": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "language", + "value" + ], + "additionalProperties": false + } + } + }, + "properties": { + "individualId": { + "type": "string", + "pattern": "^[0-9]{5,19}$" + }, + "fullName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "givenName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "familyName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "middleName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "nickName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "preferredUsername": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "gender": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,20}|^[ء-ي\\s\\u0621-\\u064A\\u0660-\\u0669\\u0671-\\u06BF\\uFE70-\\uFEFF\\u0600-\\u06FFگچپژیلفقهمو ء-ي]{1,20}$)$" + } + } + } + } + ] + }, + "streetAddress": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ0-9\\s.,°№-]{1,200}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ0-9]{1,200})$" + + } + } + } + } + ] + }, + "locality": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ0-9\\s.,°№-]{1,200}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ0-9]{1,200})$" + } + } + } + } + ] + }, + "region": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ0-9\\s.,°№-]{1,200}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ0-9]{1,200})$" + } + } + } + } + ] + }, + "country": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,20}|^[ء-ي\\s\\u0621-\\u064A\\u0660-\\u0669\\u0671-\\u06BF\\uFE70-\\uFEFF\\u0600-\\u06FFگچپژیلفقهمو ء-ي]{1,20}$)$" + } + } + } + } + ] + }, + "pin": { + "type": "string", + "pattern": "\\S" + }, + "preferredLang": { + "type": "string" + }, + "dateOfBirth": { + "type": "string", + "pattern": "\\S" + }, + "postalCode": { + "type": "string", + "pattern": "\\S" + }, + "encodedPhoto": { + "type": "string" + }, + "email": { + "type": "string", + "pattern": "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$" + }, + "phone": { + "type": "string", + "pattern": "\\S" + }, + "zoneInfo": { + "type": "string" + }, + "locale": { + "type": "string", + "pattern": "\\S" + }, + "password": { + "type": "string", + "pattern": "\\S" + } + }, + "required": [ + "individualId", + "fullName", + "givenName", + "familyName", + "middleName", + "nickName", + "preferredUsername", + "gender", + "streetAddress", + "locality", + "region", + "country", + "pin", + "preferredLang", + "dateOfBirth", + "postalCode", + "encodedPhoto", + "email", + "phone", + "zoneInfo", + "locale", + "password" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/controller/IdentityControllerTest.java b/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/controller/IdentityControllerTest.java index aa78f333..9bb9f19c 100644 --- a/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/controller/IdentityControllerTest.java +++ b/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/controller/IdentityControllerTest.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.mosip.esignet.mock.identitysystem.dto.*; -import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,7 +30,6 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.mosip.esignet.mock.identitysystem.service.IdentityService; @@ -56,12 +54,40 @@ public class IdentityControllerTest { @Before public void init() { identityRequest = new IdentityData(); - identityRequest.setIndividualId("123456789"); + identityRequest.setIndividualId("826741183"); identityRequest.setEmail("test@gmail.com"); + + List preferredNameList =new ArrayList<>(); + LanguageValue engLangValue= new LanguageValue(); + engLangValue.setValue("Siddharth K Mansour"); + engLangValue.setLanguage("eng"); + LanguageValue arabicLangValue= new LanguageValue(); + arabicLangValue.setLanguage("ara"); + arabicLangValue.setValue("سيدارت ك منصور"); + preferredNameList.add(engLangValue); + preferredNameList.add(arabicLangValue); + identityRequest.setFullName(preferredNameList); + identityRequest.setPreferredUsername(preferredNameList); + identityRequest.setFamilyName(preferredNameList); + identityRequest.setGivenName(preferredNameList); + identityRequest.setPreferredUsername(preferredNameList); + identityRequest.setNickName(preferredNameList); + identityRequest.setPreferredUsername(preferredNameList); + identityRequest.setMiddleName(preferredNameList); + + LanguageValue mockLang = new LanguageValue(); + mockLang.setLanguage("eng"); + mockLang.setValue("mock"); + identityRequest.setGender(Arrays.asList(mockLang)); + identityRequest.setStreetAddress(Arrays.asList(mockLang)); + identityRequest.setLocality(Arrays.asList(mockLang)); + identityRequest.setRegion(Arrays.asList(mockLang)); + LanguageValue langValue = new LanguageValue(); langValue.setLanguage("eng"); langValue.setValue("ind"); identityRequest.setCountry(Arrays.asList(langValue)); + identityRequest.setDateOfBirth("20021990"); identityRequest.setEncodedPhoto("testencodedphoto"); identityRequest.setGender(Arrays.asList(langValue)); @@ -74,6 +100,10 @@ public void init() { identityRequest.setFamilyName(Arrays.asList(langValue)); identityRequest.setStreetAddress(Arrays.asList(langValue)); identityRequest.setPhone("9090909090"); + identityRequest.setPreferredLang("eng"); + identityRequest.setZoneInfo("local"); + identityRequest.setLocale("eng"); + identityRequest.setPassword("mock-password"); } @Test @@ -86,7 +116,7 @@ public void createIdentity_withValidIdentity_returnSuccessResponse() throws Exce Mockito.doNothing().when(identityService).addIdentity(identityRequest); mockMvc.perform(post("/identity").content(objectMapper.writeValueAsString(requestWrapper)) - .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) .andExpect(jsonPath("$.response.status").value("mock identity data created successfully")); } @@ -101,17 +131,59 @@ public void createIdentity_withInvalidIdentity_returnErrorResponse() throws Exce Mockito.doNothing().when(identityService).addIdentity(identityRequest); mockMvc.perform(post("/identity").content(objectMapper.writeValueAsString(requestWrapper)) - .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value("invalid_individualid")); + } + + @Test + public void createIdentity_withInvalidNameAndLocale_returnErrorResponse() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper(); + ZonedDateTime requestTime = ZonedDateTime.now(ZoneOffset.UTC); + requestWrapper.setRequestTime(requestTime.format(DateTimeFormatter.ofPattern(UTC_DATETIME_PATTERN))); + identityRequest.setFullName(null); + identityRequest.setLocale(null); + requestWrapper.setRequest(identityRequest); + + Mockito.doNothing().when(identityService).addIdentity(identityRequest); + mockMvc.perform(post("/identity").content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors.size()").value(2)); + } + + @Test + public void createIdentity_withInvalidFullName_returnErrorResponse() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper(); + ZonedDateTime requestTime = ZonedDateTime.now(ZoneOffset.UTC); + requestWrapper.setRequestTime(requestTime.format(DateTimeFormatter.ofPattern(UTC_DATETIME_PATTERN))); + + List nameList=new ArrayList<>(); + LanguageValue engLangValue= new LanguageValue(); + engLangValue.setValue("Siddharth K سيدارت"); + engLangValue.setLanguage("eng"); + LanguageValue arabicLangValue= new LanguageValue(); + arabicLangValue.setLanguage("ara"); + arabicLangValue.setValue("سيدارت ك منصور"); + nameList.add(engLangValue); + nameList.add(arabicLangValue); + identityRequest.setFullName(nameList); + requestWrapper.setRequest(identityRequest); + + Mockito.doNothing().when(identityService).addIdentity(identityRequest); + mockMvc.perform(post("/identity").content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) .andExpect(jsonPath("$.errors").isNotEmpty()) - .andExpect(jsonPath("$.errors[*].errorCode").value(Matchers.containsInAnyOrder(ErrorConstants.INVALID_REQUEST, ErrorConstants.INVALID_INDIVIDUAL_ID))); + .andExpect(jsonPath("$.errors[0].errorCode").value("invalid_fullname")); } - + @Test public void getIdentity_withValidId_returnSuccessResponse() throws Exception { + identityRequest.setIndividualId("123456789"); Mockito.when(identityService.getIdentity(Mockito.anyString())).thenReturn(identityRequest); mockMvc.perform(get("/identity/{individualId}", "123456789") - .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) .andExpect(jsonPath("$.response.individualId").value("123456789")); } @@ -178,4 +250,19 @@ public void updateIdentity_withValidIdentity_thenPass() throws Exception { .andExpect(jsonPath("$.response.status").value("mock Identity data updated successfully")); } + @Test + public void updateIdentity_withInValidIdentity_thenFail() throws Exception { + RequestWrapper requestWrapper = new RequestWrapper(); + ZonedDateTime requestTime = ZonedDateTime.now(ZoneOffset.UTC); + requestWrapper.setRequestTime(requestTime.format(DateTimeFormatter.ofPattern(UTC_DATETIME_PATTERN))); + identityRequest.setFullName(null); + requestWrapper.setRequest(identityRequest); + + Mockito.doNothing().when(identityService).updateIdentity(identityRequest); + mockMvc.perform(put("/identity").content(objectMapper.writeValueAsString(requestWrapper)) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.errors").isNotEmpty()) + .andExpect(jsonPath("$.errors[0].errorCode").value("invalid_fullname")); + } + } diff --git a/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/validator/ValidatorTest.java b/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/validator/ValidatorTest.java index c487e737..07dd4deb 100644 --- a/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/validator/ValidatorTest.java +++ b/mock-identity-system/src/test/java/io/mosip/esignet/mock/identitysystem/validator/ValidatorTest.java @@ -35,8 +35,6 @@ public class ValidatorTest { private static final String UTC_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - @InjectMocks - private IdentityDataValidator identityDataValidator; @Test public void requestTimeValidator_withNullValue_thenFail() { @@ -68,49 +66,4 @@ public void testIsValid_ValidDateJustOutsideNegativeVariation() { assertFalse(requestTimeValidator.isValid(validDate, context)); } - @Test - public void identityDataValidator_withNullValue_thenFail() { - assertFalse(identityDataValidator.isValid(null, context)); - } - - @Test - public void identityDataValidator_withValidInputForCreateAction_thenPass() { - List supportedFields = Arrays.asList("fullName", "email", "phone"); - ReflectionTestUtils.setField(identityDataValidator, "action", "CREATE"); - ReflectionTestUtils.setField(identityDataValidator, "createRequiredFields", supportedFields); - IdentityData identityData = new IdentityData(); - LanguageValue languageValue = new LanguageValue(); - languageValue.setLanguage("en"); - languageValue.setValue("John Doe"); - - identityData.setFullName(List.of(languageValue)); - identityData.setEmail("john.doe@example.com"); - identityData.setPhone("1234567890"); - identityData.setEncodedPhoto("encoded-photo"); - - assertTrue(identityDataValidator.isValid(identityData, context)); - } - - @Test - public void identityDataValidator_withValidInputForUpdateAction_thenPass() { - List supportedFields = Arrays.asList("individualId"); - ReflectionTestUtils.setField(identityDataValidator, "action", "UPDATE"); - ReflectionTestUtils.setField(identityDataValidator, "updateRequiredFields", supportedFields); - IdentityData identityData = new IdentityData(); - identityData.setIndividualId("individualId"); - - assertTrue(identityDataValidator.isValid(identityData, context)); - } - - @Test - public void identityDataValidator_withInvalidInputForUpdateAction_thenFail() { - List supportedFields = Arrays.asList("individualId", "email"); - ReflectionTestUtils.setField(identityDataValidator, "action", "UPDATE"); - ReflectionTestUtils.setField(identityDataValidator, "updateRequiredFields", supportedFields); - IdentityData identityData = new IdentityData(); - identityData.setIndividualId("individualId"); - - assertFalse(identityDataValidator.isValid(identityData, context)); - } - } diff --git a/mock-identity-system/src/test/resources/application-test.properties b/mock-identity-system/src/test/resources/application-test.properties new file mode 100644 index 00000000..5408cc3a --- /dev/null +++ b/mock-identity-system/src/test/resources/application-test.properties @@ -0,0 +1,3 @@ + +mosip.mock.ida.identity.schema.url=classpath:/mock-identity-test-schema.json +mosip.mock.ida.update-identity.non-mandatory.fields={"givenName","familyName","middleName","nickName","preferredUsername","gender","streetAddress","locality","region","country","pin","preferredLang","dateOfBirth","postalCode","encodedPhoto","email","phone","zoneInfo","locale","password"} diff --git a/mock-identity-system/src/test/resources/bootstrap.properties b/mock-identity-system/src/test/resources/bootstrap.properties new file mode 100644 index 00000000..b78f2069 --- /dev/null +++ b/mock-identity-system/src/test/resources/bootstrap.properties @@ -0,0 +1,2 @@ + +spring.profiles.active=test \ No newline at end of file diff --git a/mock-identity-system/src/test/resources/mock-identity-test-schema.json b/mock-identity-system/src/test/resources/mock-identity-test-schema.json new file mode 100644 index 00000000..41081997 --- /dev/null +++ b/mock-identity-system/src/test/resources/mock-identity-test-schema.json @@ -0,0 +1,248 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "$defs": { + "langField": { + "type": "array", + "items": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "language", + "value" + ], + "additionalProperties": false + } + } + }, + "properties": { + "individualId": { + "type": "string", + "pattern": "^[0-9]{5,19}$" + }, + "fullName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "givenName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "familyName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "middleName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "nickName": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "preferredUsername": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,40}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ]{1,40})$" + } + } + } + } + ] + }, + "gender": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,20}|^[ء-ي\\s\\u0621-\\u064A\\u0660-\\u0669\\u0671-\\u06BF\\uFE70-\\uFEFF\\u0600-\\u06FFگچپژیلفقهمو ء-ي]{1,20}$)$" + } + } + } + } + ] + }, + "streetAddress": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ0-9\\s.,°№-]{1,200}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ0-9]{1,200})$" + + } + } + } + } + ] + }, + "locality": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ0-9\\s.,°№-]{1,200}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ0-9]{1,200})$" + } + } + } + } + ] + }, + "region": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ0-9\\s.,°№-]{1,200}|[ء-ي\\s-٩ٱ-ڿﹰ-\uFEFF\u0600-ۿ0-9]{1,200})$" + } + } + } + } + ] + }, + "country": { + "allOf": [ + { "$ref": "#/$defs/langField" }, + { + "items": { + "properties": { + "value": { + "pattern": "^(?:[a-zA-ZÀ-ÿ\\s]{1,20}|^[ء-ي\\s\\u0621-\\u064A\\u0660-\\u0669\\u0671-\\u06BF\\uFE70-\\uFEFF\\u0600-\\u06FFگچپژیلفقهمو ء-ي]{1,20}$)$" + } + } + } + } + ] + }, + "pin": { + "type": "string", + "pattern": "\\S" + }, + "preferredLang": { + "type": "string" + }, + "dateOfBirth": { + "type": "string", + "pattern": "\\S" + }, + "postalCode": { + "type": "string", + "pattern": "\\S" + }, + "encodedPhoto": { + "type": "string" + }, + "email": { + "type": "string", + "pattern": "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$" + }, + "phone": { + "type": "string", + "pattern": "\\S" + }, + "zoneInfo": { + "type": "string" + }, + "locale": { + "type": "string", + "pattern": "\\S" + }, + "password": { + "type": "string", + "pattern": "\\S" + } + }, + "required": [ + "individualId", + "fullName", + "givenName", + "familyName", + "middleName", + "nickName", + "preferredUsername", + "gender", + "streetAddress", + "locality", + "region", + "country", + "pin", + "preferredLang", + "dateOfBirth", + "postalCode", + "encodedPhoto", + "email", + "phone", + "zoneInfo", + "locale", + "password" + ], + "additionalProperties": false +} \ No newline at end of file