diff --git a/README.md b/README.md index edc5f6c77..a8f70d45b 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,58 @@ The project requires JDK 21, postgres and google client credentials For detailed setup instructions (including running Redis with Docker CLI and updating configuration), see the [Cache Providers Setup Guide](#cache-providers-setup-guide) section. -3. Refer to the [How to create Google Client Credentials](docker-compose/README.md#how-to-create-google-client-credentials) section to create + +3. **Configuring Postgres Database:** + 1. **Ensure Postgres Service is Available and Connected** + - **Using Local Postgres Installation:** If you have Postgres installed locally, run the database initialization scripts: + ```bash + cd db_scripts/inji_mimoto + ./deploy.sh deploy.properties + ``` + * If needed, update the database connection properties in [application-local.properties](src/main/resources/application-local.properties) with your local Postgres credentials. + + - **Using Docker Compose:** If you don't have postgres setup in your local you can run Postgres along with Mimoto by using the `postgres` service in [docker-compose.yml](docker-compose/docker-compose.yml). + + - **Or, run Postgres using Docker while starting Mimoto through your IDE:** + * Use the following Docker command to start the Postgres service and expose it on the default port 5432. Make sure this port is accessible from your local machine. + ```bash + docker pull postgres:latest # Pull the Postgres image if not already available + docker run -d --name postgres \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_DB=inji_mimoto \ + -p 5432:5432 \ + postgres:latest + ``` + * Start Mimoto normally, following the instructions mentioned in [Build & run (for developers) section](#build--run-for-developers). + 2. **Update the following properties** in + - [application-local.properties](src/main/resources/application-local.properties) *(when running through IDE)*, or + - [mimoto-default.properties](docker-compose/config/mimoto-default.properties) *(when running through Docker)*: + + **Look for properties starting with:** + - `spring.datasource.*` + - `spring.datasource.*` + - `mosip.mimoto.database.*` + 3. **Check data in postgres by using the following commands** + ```bash + docker exec -it postgres psql -U postgres -d inji_mimoto # connect to container + + \dt mimoto.* # to see all tables in mimoto schema + + SELECT * FROM mimoto.; # to see data in a table + ``` + +4. Refer to the [How to create Google Client Credentials](docker-compose/README.md#how-to-create-google-client-credentials) section to create Google client credentials and update below properties in `application-local.properties`. ``` spring.security.oauth2.client.registration.google.client-id= spring.security.oauth2.client.registration.google.client-secret= ``` -4. Add identity providers as issuers in the `mimoto-issuers-config.json` file of [resources folder](src/main/resources/mimoto-issuers-config.json). For each provider, create a corresponding object with its issuer-specific configuration. Refer to the [Issuers Configuration](docker-compose/README.md#mimoto-issuers-configuration) section for details on how to structure this file and understand each field's purpose and what values need to be updated. +5. Add identity providers as issuers in the `mimoto-issuers-config.json` file of [resources folder](src/main/resources/mimoto-issuers-config.json). For each provider, create a corresponding object with its issuer-specific configuration. Refer to the [Issuers Configuration](docker-compose/README.md#mimoto-issuers-configuration) section for details on how to structure this file and understand each field's purpose and what values need to be updated. -5. Add or update the verifiers clientId, redirect and response Uris in `mimoto-trusted-verifiers.json` file of [resources folder](src/main/resources/mimoto-trusted-verifiers.json) for Verifiable credential Online Sharing. +6. Add or update the verifiers clientId, redirect and response Uris in `mimoto-trusted-verifiers.json` file of [resources folder](src/main/resources/mimoto-trusted-verifiers.json) for Verifiable credential Online Sharing. -6. Keystore(oidckeystore.p12) Configuration: +7. Keystore(oidckeystore.p12) Configuration: In the root directory, create a certs folder and generate an OIDC client. Add the onboard client’s key to the oidckeystore.p12 file and place this file inside the certs folder. Refer to the [official documentation](https://docs.inji.io/inji-wallet/inji-mobile/technical-overview/customization-overview/credential_providers) for guidance on how to create the **oidckeystore.p12** file and add the OIDC client key to it. * The **oidckeystore.p12** file stores keys and certificates, each identified by an alias (e.g., mpartner-default-mimoto-insurance-oidc). Mimoto uses this alias to find the correct entry and access the corresponding private key during the authentication flow. @@ -52,19 +93,19 @@ The project requires JDK 21, postgres and google client credentials ``` * Mimoto also uses this same keystore file (oidckeystore.p12) to store keys generated at service startup, which are essential for performing encryption and decryption operations through the KeyManager service. -7. To configure any Mobile Wallet specific configurations refer to the [Inji Mobile Wallet Configuration](docker-compose/README.md#inji-mobile-wallet-configuration) section. +8. To configure any Mobile Wallet specific configurations refer to the [Inji Mobile Wallet Configuration](docker-compose/README.md#inji-mobile-wallet-configuration) section. -8. Run the SQLs using /deploy.sh script. from [db_scripts folder](db_scripts/inji_mimoto) +9. Run the SQLs using /deploy.sh script. from [db_scripts folder](db_scripts/inji_mimoto) ``` ./deploy.sh deploy.properties ``` -9. Build the jar +10. Build the jar ``` mvn clean install -Dgpg.skip=true -Dmaven.javadoc.skip=true -DskipTests=true ``` -10. Run following command +11. Run following command ``` mvn spring-boot:run -Dspring.profiles.active=local ``` diff --git a/db_upgrade_script/inji_mimoto/sql/0.13.1_to_0.14.0_rollback.sql b/db_upgrade_script/inji_mimoto/sql/0.18.0_to_0.18.1_rollback.sql similarity index 100% rename from db_upgrade_script/inji_mimoto/sql/0.13.1_to_0.14.0_rollback.sql rename to db_upgrade_script/inji_mimoto/sql/0.18.0_to_0.18.1_rollback.sql diff --git a/db_upgrade_script/inji_mimoto/sql/0.13.1_to_0.14.0_upgrade.sql b/db_upgrade_script/inji_mimoto/sql/0.18.0_to_0.18.1_upgrade.sql similarity index 100% rename from db_upgrade_script/inji_mimoto/sql/0.13.1_to_0.14.0_upgrade.sql rename to db_upgrade_script/inji_mimoto/sql/0.18.0_to_0.18.1_upgrade.sql diff --git a/db_upgrade_script/inji_mimoto/sql/0.18.1_to_0.19.0_rollback.sql b/db_upgrade_script/inji_mimoto/sql/0.18.1_to_0.19.0_rollback.sql new file mode 100644 index 000000000..e69de29bb diff --git a/db_upgrade_script/inji_mimoto/sql/0.18.1_to_0.19.0_upgrade.sql b/db_upgrade_script/inji_mimoto/sql/0.18.1_to_0.19.0_upgrade.sql new file mode 100644 index 000000000..e69de29bb diff --git a/docker-compose/README.md b/docker-compose/README.md index 48f0aa7e7..5cf1819ce 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -40,11 +40,12 @@ This is the docker-compose setup to run mimoto which act as BFF for Inji mobile ``` - **Running Mimoto in IDE and other services like `datashare service` via Docker Compose:** 1. In `docker-compose.yml`, update the `DATASHARE_DOMAIN` environment variable for the `Datashare service` to `localhost:8097`. - 2. Then, start your dependent services by running the following command + 2. Ensure that the `mimoto-service` service (or) any other service is commented out in the `docker-compose.yml` file which you will be running locally. + 3. Then, start your dependent services by running the following command ```bash - docker-compose up # Use this to comment inji web service and start all the other services defined in docker-compose.yml + docker-compose up # Use this to start all the services defined in docker-compose.yml # OR - docker-compose up datashare other_service_name # To start specific services (replace with actual names) + docker-compose up datashare postgres other_service_name # To start specific services (replace with actual names from docker-compose.yml) ``` **Note:** Use the **-d** flag with docker-compose up to run the services in detached (background) mode. diff --git a/docs/VCFormatExtension.md b/docs/VCFormatExtension.md index 83a48e404..49e10dd87 100644 --- a/docs/VCFormatExtension.md +++ b/docs/VCFormatExtension.md @@ -74,10 +74,10 @@ Before starting the implementation, check existing handlers implementation ### 2. Implement New Handler -Create a new handler class implementing `CredentialFormatHandler`: +Create a new handler class implementing `CredentialFormatHandler` (with a @Component qualifier name required for injection):: ```java -@Component +@Component("vc+sd-jwt") @Slf4j public class SDJWTCredentialFormatHandler implements CredentialFormatHandler { @@ -93,7 +93,7 @@ public class SDJWTCredentialFormatHandler implements CredentialFormatHandler { ### 3. Handler Registration -New handler will be auto-discovered by Spring. The factory automatically injects all handlers: +New handler will be auto-discovered by Spring. The factory automatically injects all handlers based on credential format: **Reference**: [`CredentialFormatHandlerFactory.java`](https://github.com/mosip/mimoto/blob/release-0.19.x/src/main/java/io/mosip/mimoto/service/CredentialFormatHandlerFactory.java) diff --git a/docs/WalletUnlockProcess.md b/docs/WalletUnlockProcess.md index a585ad168..a4d230780 100644 --- a/docs/WalletUnlockProcess.md +++ b/docs/WalletUnlockProcess.md @@ -135,7 +135,7 @@ participant WalletRepository ## Configuration Configurable properties that govern the entire Passcode flow within the Wallet unlock process are defined in the -`application-local.properties` file for the local setup, and in the `mimoto-default.properties` file for the environment setup. +`application-default.properties` file for the local setup, and in the `mimoto-default.properties` file for the environment setup. #### Passcode Control Properties - `wallet.passcode.retryBlockedUntil`: Duration for which the Wallet remains in temporary lock state (in milliseconds) diff --git a/pom.xml b/pom.xml index 7c233abaa..6f6a28cfc 100644 --- a/pom.xml +++ b/pom.xml @@ -144,7 +144,7 @@ io.mosip vcverifier-jar - 1.4.0-SNAPSHOT + 1.4.0 io.micrometer @@ -449,7 +449,7 @@ io.mosip pixelpass-jar - 0.7.0-SNAPSHOT + 0.7.0 @@ -489,7 +489,7 @@ io.mosip.kernel kernel-keymanager-service - 1.3.0-beta.2 + 1.3.0-beta.4 lib diff --git a/src/main/java/io/mosip/mimoto/controller/CredentialShareController.java b/src/main/java/io/mosip/mimoto/controller/CredentialShareController.java index fc2e93093..9308546e7 100644 --- a/src/main/java/io/mosip/mimoto/controller/CredentialShareController.java +++ b/src/main/java/io/mosip/mimoto/controller/CredentialShareController.java @@ -87,7 +87,6 @@ public class CredentialShareController { public ResponseEntity handleSubscribeEvent(@RequestBody EventModel eventModel) throws Exception { log.info("Received websub event:: transaction id = " + eventModel.getEvent().getTransactionId()); - log.debug("Received websub event:: " + JsonUtils.javaObjectToJsonString(eventModel)); GenericResponseDTO responseDTO = new GenericResponseDTO(); Path vcRequestIdPath = Path.of( utilities.getDataPath(), diff --git a/src/main/java/io/mosip/mimoto/controller/IdpController.java b/src/main/java/io/mosip/mimoto/controller/IdpController.java index 43ea97ec4..df676c50d 100644 --- a/src/main/java/io/mosip/mimoto/controller/IdpController.java +++ b/src/main/java/io/mosip/mimoto/controller/IdpController.java @@ -54,7 +54,7 @@ public class IdpController { @PostMapping(value = "/binding-otp", produces = MediaType.APPLICATION_JSON_VALUE) @SuppressWarnings("unchecked") public ResponseEntity> otpRequest(@Valid @RequestBody BindingOtpRequestDto requestDTO, BindingResult result) throws Exception { - log.debug("Received binding-otp request : " + JsonUtils.javaObjectToJsonString(requestDTO)); + log.debug("Received binding-otp request"); requestValidator.validateInputRequest(result); requestValidator.validateNotificationChannel(requestDTO.getRequest().getOtpChannels()); ResponseWrapper responseWrapper = new ResponseWrapper<>(); @@ -79,7 +79,7 @@ public ResponseEntity> otpRequest(@Valid public ResponseEntity> request(@RequestBody WalletBindingRequestDTO requestDTO) throws Exception { - log.debug("Received wallet-binding request : " + JsonUtils.javaObjectToJsonString(requestDTO)); + log.debug("Received wallet-binding request "); ResponseWrapper responseWrapper = new ResponseWrapper<>(); try { WalletBindingInnerRequestDto innerRequestDto = new WalletBindingInnerRequestDto(); @@ -101,7 +101,7 @@ public ResponseEntity> request(@Reques responseWrapper = joseUtil.addThumbprintAndKeyId(internalResponse); return ResponseEntity.status(HttpStatus.OK).body(responseWrapper); } catch (Exception e) { - log.error("Wallet binding error occurred for transaction id " + requestDTO.getRequest().getIndividualId(), e); + log.error("Wallet binding error occurred ", e); String[] errorObj = Utilities.handleExceptionWithErrorCode(e, PlatformErrorMessages.MIMOTO_WALLET_BINDING_EXCEPTION.getCode()); List errors = Utilities.getErrors(errorObj[0], errorObj[1]); responseWrapper.setResponse(null); diff --git a/src/main/java/io/mosip/mimoto/service/impl/CredentialServiceImpl.java b/src/main/java/io/mosip/mimoto/service/impl/CredentialServiceImpl.java index 506d0ca42..ea13215f4 100644 --- a/src/main/java/io/mosip/mimoto/service/impl/CredentialServiceImpl.java +++ b/src/main/java/io/mosip/mimoto/service/impl/CredentialServiceImpl.java @@ -95,7 +95,7 @@ public VCCredentialResponse downloadCredential(String credentialEndpoint, VCCred vcCredentialRequest, VerifiableCredentialResponse.class, accessToken); if (response == null) throw new InvalidCredentialResourceException("VC Credential Issue API not accessible"); - log.debug("VC Credential Response is {} " , response); + log.debug("VC Credential Response received"); return new VCCredentialResponse(vcCredentialRequest.getFormat(), response.getCredential()); } diff --git a/src/main/java/io/mosip/mimoto/service/impl/DataShareServiceImpl.java b/src/main/java/io/mosip/mimoto/service/impl/DataShareServiceImpl.java index 96c560c11..222e6e3b6 100644 --- a/src/main/java/io/mosip/mimoto/service/impl/DataShareServiceImpl.java +++ b/src/main/java/io/mosip/mimoto/service/impl/DataShareServiceImpl.java @@ -65,7 +65,7 @@ public String getFilename() { HttpEntity> requestEntity = new HttpEntity<>(map, headers); DataShareResponseWrapperDTO dataShareResponseWrapperDTO = pushCredentialIntoDataShare(requestEntity, credentialValidity); - log.info("Data pushed into DataShare -> " + dataShareResponseWrapperDTO); + log.info("Data pushed into DataShare -> "); return dataShareResponseWrapperDTO.getDataShare().getUrl(); } @@ -111,7 +111,7 @@ public VCCredentialResponse downloadCredentialFromDataShare(PresentationRequest } VCCredentialResponse vcCredentialResponse = objectMapper.readValue(vcCredentialResponseString, VCCredentialResponse.class); - log.info("Completed Mapping the Credential to Object => " + vcCredentialResponse ); + log.info("Completed Mapping the Credential to Object => "); if(vcCredentialResponse.getCredential() == null){ DataShareResponseDto dataShareResponse = objectMapper.readValue(vcCredentialResponseString, DataShareResponseDto.class); String errorCode = dataShareResponse.getErrors().get(0).getErrorCode(); diff --git a/src/main/java/io/mosip/mimoto/service/impl/VcSdJwtCredentialFormatHandler.java b/src/main/java/io/mosip/mimoto/service/impl/VcSdJwtCredentialFormatHandler.java index 4a63d48fd..f3a522dce 100644 --- a/src/main/java/io/mosip/mimoto/service/impl/VcSdJwtCredentialFormatHandler.java +++ b/src/main/java/io/mosip/mimoto/service/impl/VcSdJwtCredentialFormatHandler.java @@ -43,7 +43,7 @@ public Map extractCredentialClaims(VCCredentialResponse vcCreden if (credential instanceof String) { return extractClaimsFromSdJwt((String) credential); } - log.warn("Unexpected credential format in response for SD-JWT VC: {}", credential); + log.warn("Unexpected credential format in response for SD-JWT VC:"); return Collections.emptyMap(); } diff --git a/src/main/java/io/mosip/mimoto/service/impl/VerifierServiceImpl.java b/src/main/java/io/mosip/mimoto/service/impl/VerifierServiceImpl.java index 127244e51..6c522c553 100644 --- a/src/main/java/io/mosip/mimoto/service/impl/VerifierServiceImpl.java +++ b/src/main/java/io/mosip/mimoto/service/impl/VerifierServiceImpl.java @@ -11,8 +11,6 @@ import io.mosip.mimoto.util.Utilities; import lombok.extern.slf4j.Slf4j; import org.apache.commons.validator.routines.UrlValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.AntPathMatcher; @@ -41,8 +39,6 @@ public class VerifierServiceImpl implements VerifierService { urlValidator = new UrlValidator(ALLOW_ALL_SCHEMES+ALLOW_LOCAL_URLS); } - private final Logger logger = LoggerFactory.getLogger(VerifierServiceImpl.class); - public VerifiersDTO getTrustedVerifiers() throws ApiNotAccessibleException, JsonProcessingException { String trustedVerifiersJsonValue = utilities.getTrustedVerifiersJsonValue(); if (trustedVerifiersJsonValue == null) { diff --git a/src/main/java/io/mosip/mimoto/util/JwtUtils.java b/src/main/java/io/mosip/mimoto/util/JwtUtils.java index 3944fa3fa..ae7fd5650 100644 --- a/src/main/java/io/mosip/mimoto/util/JwtUtils.java +++ b/src/main/java/io/mosip/mimoto/util/JwtUtils.java @@ -28,7 +28,7 @@ public static Map parseJwtPayload(String jwt) { try { String[] parts = jwt.split("\\."); if (parts.length < 3) { - log.error("Invalid JWT format for payload parsing: {}", jwt); + log.error("Invalid JWT format for payload parsing: "); return Collections.emptyMap(); } String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); @@ -46,7 +46,7 @@ public static Map parseJwtHeader(String jwt) { try { String[] parts = jwt.split("\\."); if (parts.length < 3) { - log.warn("Invalid JWT format for header parsing: {}", jwt); + log.warn("Invalid JWT format for header parsing: "); return Collections.emptyMap(); } String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); diff --git a/src/main/java/io/mosip/mimoto/util/WalletValidator.java b/src/main/java/io/mosip/mimoto/util/WalletValidator.java index e8ffa503d..d7f9c59a5 100644 --- a/src/main/java/io/mosip/mimoto/util/WalletValidator.java +++ b/src/main/java/io/mosip/mimoto/util/WalletValidator.java @@ -60,9 +60,9 @@ public void validateWalletName(String name) throws InvalidRequestException { * @throws InvalidRequestException If the Wallet PIN is invalid. */ public void validateWalletPin(String pin) throws InvalidRequestException { - log.debug("Validating Wallet PIN: {}", pin); + log.debug("Validating Wallet PIN"); if (pin == null || !pin.matches(pinRegex)) { - log.warn("Invalid PIN: {}", pin); + log.warn("Invalid PIN"); throw new InvalidRequestException(ErrorConstants.INVALID_REQUEST.getErrorCode(), "PIN must be numeric with 6 digits"); } } diff --git a/src/main/java/io/mosip/mimoto/util/WebSubSubscriptionHelper.java b/src/main/java/io/mosip/mimoto/util/WebSubSubscriptionHelper.java index 4396d16ca..2c5941bca 100644 --- a/src/main/java/io/mosip/mimoto/util/WebSubSubscriptionHelper.java +++ b/src/main/java/io/mosip/mimoto/util/WebSubSubscriptionHelper.java @@ -55,7 +55,7 @@ public SubscriptionChangeResponse unSubscribeEvent(String topic, String callBack unsubscriptionRequest.setCallbackURL(callBackUrl); unsubscriptionRequest.setHubURL(webSubHubSubUrl); unsubscriptionRequest.setTopic(topic); - log.info("Unsubscription request : {}", unsubscriptionRequest); + log.info("Unsubscription request : "); return sb.unSubscribe(unsubscriptionRequest); } catch (WebSubClientException e) { log.info("Websub unsubscription error: {} ", e.getMessage()); @@ -70,7 +70,7 @@ public SubscriptionChangeResponse subscribeEvent(String topic, String callBackUr subscriptionRequest.setHubURL(webSubHubSubUrl); subscriptionRequest.setSecret(secret); subscriptionRequest.setTopic(topic); - log.info("Subscription request : {}", subscriptionRequest); + log.info("Subscription request : "); return sb.subscribe(subscriptionRequest); } catch (WebSubClientException e) { log.info("Websub subscription error: {}", e.getMessage()); diff --git a/src/test/java/io/mosip/mimoto/service/CredentialPDFGeneratorServiceTest.java b/src/test/java/io/mosip/mimoto/service/CredentialPDFGeneratorServiceTest.java index a1961e757..86cf7cf56 100644 --- a/src/test/java/io/mosip/mimoto/service/CredentialPDFGeneratorServiceTest.java +++ b/src/test/java/io/mosip/mimoto/service/CredentialPDFGeneratorServiceTest.java @@ -750,4 +750,138 @@ private CredentialIssuerDisplayResponse createDisplayResponse(String name, Strin response.setLocale(locale); return response; } + + @Test + void testFormatValueWithMap() throws Exception { + when(credentialFormatHandlerFactory.getHandler("ldp_vc")).thenReturn(credentialFormatHandler); + + // Setup credential with map containing "value" + Map subject = Map.of("education", Map.of("value", "Bachelor's Degree")); + when(credentialFormatHandler.extractCredentialClaims(vcCredentialResponse)).thenReturn(subject); + + LinkedHashMap> displayProps = new LinkedHashMap<>(); + displayProps.put("education", Map.of(createDisplayResponse("Education", "en"), Map.of("value", "Bachelor's Degree"))); + when(credentialFormatHandler.loadDisplayPropertiesFromWellknown(any(), any(), anyString())).thenReturn(displayProps); + + credentialsSupportedResponse.getCredentialDefinition().getCredentialSubject().put("education", createDisplay("Education")); + credentialsSupportedResponse.setOrder(List.of("education")); + + when(utilities.getCredentialSupportedTemplateString(anyString(), anyString())) + .thenReturn("Education: $rowProperties.education"); + when(presentationService.constructPresentationDefinition(any())).thenReturn(new PresentationDefinitionDTO()); + when(objectMapper.writeValueAsString(any())).thenReturn("{}"); + + ByteArrayInputStream result = credentialPDFGeneratorService.generatePdfForVerifiableCredential( + "TestCredential", vcCredentialResponse, issuerDTO, credentialsSupportedResponse, "", "", "en"); + + assertNotNull(result); // Verifies formatValue extracted "Bachelor's Degree" + } + + @Test + void testFormatValueWithStringList() throws Exception { + when(credentialFormatHandlerFactory.getHandler("ldp_vc")).thenReturn(credentialFormatHandler); + + // Setup credential with list of strings + Map subject = Map.of("skills", List.of("Java", "Spring", "Boot")); + when(credentialFormatHandler.extractCredentialClaims(vcCredentialResponse)).thenReturn(subject); + + LinkedHashMap> displayProps = new LinkedHashMap<>(); + displayProps.put("skills", Map.of(createDisplayResponse("Skills", "en"), List.of("Java", "Spring", "Boot"))); + when(credentialFormatHandler.loadDisplayPropertiesFromWellknown(any(), any(), anyString())).thenReturn(displayProps); + + credentialsSupportedResponse.getCredentialDefinition().getCredentialSubject().put("skills", createDisplay("Skills")); + credentialsSupportedResponse.setOrder(List.of("skills")); + + when(utilities.getCredentialSupportedTemplateString(anyString(), anyString())) + .thenReturn("Skills: $rowProperties.skills"); + when(presentationService.constructPresentationDefinition(any())).thenReturn(new PresentationDefinitionDTO()); + when(objectMapper.writeValueAsString(any())).thenReturn("{}"); + + ByteArrayInputStream result = credentialPDFGeneratorService.generatePdfForVerifiableCredential( + "TestCredential", vcCredentialResponse, issuerDTO, credentialsSupportedResponse, "", "", "en"); + + assertNotNull(result); // Verifies formatValue joined to "Java, Spring, Boot" + } + + @Test + void testFormatValueWithLocaleMapList() throws Exception { + when(credentialFormatHandlerFactory.getHandler("ldp_vc")).thenReturn(credentialFormatHandler); + + // Setup credential with locale-specific list + List> localeData = List.of( + Map.of("language", "en", "value", "English Name"), + Map.of("language", "fr", "value", "French Name") + ); + Map subject = Map.of("name", localeData); + when(credentialFormatHandler.extractCredentialClaims(vcCredentialResponse)).thenReturn(subject); + + LinkedHashMap> displayProps = new LinkedHashMap<>(); + displayProps.put("name", Map.of(createDisplayResponse("Name", "en"), localeData)); + when(credentialFormatHandler.loadDisplayPropertiesFromWellknown(any(), any(), anyString())).thenReturn(displayProps); + + credentialsSupportedResponse.getCredentialDefinition().getCredentialSubject().put("name", createDisplay("Name")); + credentialsSupportedResponse.setOrder(List.of("name")); + + when(utilities.getCredentialSupportedTemplateString(anyString(), anyString())) + .thenReturn("Name: $rowProperties.name"); + when(presentationService.constructPresentationDefinition(any())).thenReturn(new PresentationDefinitionDTO()); + when(objectMapper.writeValueAsString(any())).thenReturn("{}"); + + ByteArrayInputStream result = credentialPDFGeneratorService.generatePdfForVerifiableCredential( + "TestCredential", vcCredentialResponse, issuerDTO, credentialsSupportedResponse, "", "", "en"); + + assertNotNull(result); // Verifies formatValue selected "English Name" for locale "en" + } + + @Test + void testFormatValueWithEmptyList() throws Exception { + when(credentialFormatHandlerFactory.getHandler("ldp_vc")).thenReturn(credentialFormatHandler); + + // Setup credential with empty list + Map subject = Map.of("tags", List.of()); + when(credentialFormatHandler.extractCredentialClaims(vcCredentialResponse)).thenReturn(subject); + + LinkedHashMap> displayProps = new LinkedHashMap<>(); + displayProps.put("tags", Map.of(createDisplayResponse("Tags", "en"), List.of())); + when(credentialFormatHandler.loadDisplayPropertiesFromWellknown(any(), any(), anyString())).thenReturn(displayProps); + + credentialsSupportedResponse.getCredentialDefinition().getCredentialSubject().put("tags", createDisplay("Tags")); + credentialsSupportedResponse.setOrder(List.of("tags")); + + when(utilities.getCredentialSupportedTemplateString(anyString(), anyString())) + .thenReturn("Tags: $rowProperties.tags"); + when(presentationService.constructPresentationDefinition(any())).thenReturn(new PresentationDefinitionDTO()); + when(objectMapper.writeValueAsString(any())).thenReturn("{}"); + + ByteArrayInputStream result = credentialPDFGeneratorService.generatePdfForVerifiableCredential( + "TestCredential", vcCredentialResponse, issuerDTO, credentialsSupportedResponse, "", "", "en"); + + assertNotNull(result); // Verifies formatValue returned "" for empty list + } + + @Test + void testFormatValueWithPrimitive() throws Exception { + when(credentialFormatHandlerFactory.getHandler("ldp_vc")).thenReturn(credentialFormatHandler); + + // Setup credential with number + Map subject = Map.of("age", 25); + when(credentialFormatHandler.extractCredentialClaims(vcCredentialResponse)).thenReturn(subject); + + LinkedHashMap> displayProps = new LinkedHashMap<>(); + displayProps.put("age", Map.of(createDisplayResponse("Age", "en"), 25)); + when(credentialFormatHandler.loadDisplayPropertiesFromWellknown(any(), any(), anyString())).thenReturn(displayProps); + + credentialsSupportedResponse.getCredentialDefinition().getCredentialSubject().put("age", createDisplay("Age")); + credentialsSupportedResponse.setOrder(List.of("age")); + + when(utilities.getCredentialSupportedTemplateString(anyString(), anyString())) + .thenReturn("Age: $rowProperties.age"); + when(presentationService.constructPresentationDefinition(any())).thenReturn(new PresentationDefinitionDTO()); + when(objectMapper.writeValueAsString(any())).thenReturn("{}"); + + ByteArrayInputStream result = credentialPDFGeneratorService.generatePdfForVerifiableCredential( + "TestCredential", vcCredentialResponse, issuerDTO, credentialsSupportedResponse, "", "", "en"); + + assertNotNull(result); // Verifies formatValue converted 25 to "25" + } }