From ce7b333fefb306901d2fab38a49caf9f05fed2ed Mon Sep 17 00:00:00 2001 From: Prafulrakhade Date: Tue, 6 Aug 2024 06:26:21 +0000 Subject: [PATCH 01/71] Updated Pom versions for release changes Signed-off-by: GitHub --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 6b9d8ac8..67329f73 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.2.1-SNAPSHOT + 1.3.0-SNAPSHOT print @@ -48,7 +48,7 @@ **/dto/**,**/config/**,**/api/** 1.4.2 2.8.4 - 1.2.1-SNAPSHOT + 1.3.0-SNAPSHOT 7.2.4 2.0.0 5.5.13 @@ -64,7 +64,7 @@ io.mosip.kernel kernel-bom - 1.2.1-SNAPSHOT + 1.3.0-SNAPSHOT pom import From d315033257bf4206d5d12ef1909e015ab4de3460 Mon Sep 17 00:00:00 2001 From: techno-467 Date: Mon, 9 Sep 2024 13:42:45 +0530 Subject: [PATCH 02/71] [MOSIP-35160] Updated URL from https://github.com/mosip/mosip-infra/blob/master/deployment/v3/utils/copy_cm_func.sh to https://raw.githubusercontent.com/mosip/mosip-infra/master/deployment/v3/utils/copy_cm_func.sh Signed-off-by: techno-467 --- deploy/copy_cm.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/copy_cm.sh b/deploy/copy_cm.sh index fed0c554..1af7156a 100755 --- a/deploy/copy_cm.sh +++ b/deploy/copy_cm.sh @@ -3,7 +3,7 @@ # DST_NS: Destination (current) namespace function copying_cm() { - UTIL_URL=https://github.com/mosip/mosip-infra/blob/master/deployment/v3/utils/copy_cm_func.sh + UTIL_URL=https://raw.githubusercontent.com/mosip/mosip-infra/master/deployment/v3/utils/copy_cm_func.sh COPY_UTIL=./copy_cm_func.sh DST_NS=print From a1618bb0a10896163413cf2a2f5a651d0daca442 Mon Sep 17 00:00:00 2001 From: dhanendra06 <60607841+dhanendra06@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:10:26 +0530 Subject: [PATCH 03/71] MOSIP-32453: Fixed pdf generation issue (#245) * resolved the merge conflict Signed-off-by: dhanendra06 * resolved the merge conflict Signed-off-by: dhanendra06 * resolved the merge conflict Signed-off-by: dhanendra06 --------- Signed-off-by: dhanendra06 --- pom.xml | 19 ++++++++++++------- .../io/mosip/print/PrintPDFApplication.java | 5 ----- .../print/service/impl/PrintServiceImpl.java | 17 ++++++----------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 67329f73..7f0849ff 100644 --- a/pom.xml +++ b/pom.xml @@ -49,13 +49,13 @@ 1.4.2 2.8.4 1.3.0-SNAPSHOT - 7.2.4 + 7.1.0 2.0.0 - 5.5.13 + 5.5.13.3 3.6.1 2.6 1.11 - 1.78 + 1.66 3.8.1 3.4.1 @@ -155,7 +155,12 @@ org.bouncycastle - bcprov-jdk15to18 + bcprov-jdk15on + ${bouncycastle.version} + + + org.bouncycastle + bcpkix-jdk15on ${bouncycastle.version} @@ -240,7 +245,7 @@ io.mosip.vercred vcverifier - 1.0-SNAPSHOT + 1.1.0-SNAPSHOT @@ -248,10 +253,10 @@ slf4j-api 2.0.13 - diff --git a/src/main/java/io/mosip/print/PrintPDFApplication.java b/src/main/java/io/mosip/print/PrintPDFApplication.java index 778ba87e..3517949b 100644 --- a/src/main/java/io/mosip/print/PrintPDFApplication.java +++ b/src/main/java/io/mosip/print/PrintPDFApplication.java @@ -32,11 +32,6 @@ public CbeffUtil getCbeffUtil() { return new CbeffImpl(); } - @Bean - public CredentialsVerifier credentialsVerifier() { - return new CredentialsVerifier(); - } - @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); diff --git a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java index c141c848..76c35c72 100644 --- a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java +++ b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java @@ -32,11 +32,10 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import io.mosip.print.exception.*; import io.mosip.vercred.CredentialsVerifier; import io.mosip.vercred.exception.ProofDocumentNotFoundException; -import io.mosip.vercred.exception.ProofTypeNotFoundException; -import io.mosip.vercred.exception.PubicKeyNotFoundException; +import io.mosip.vercred.exception.ProofTypeNotSupportedException; +import io.mosip.vercred.exception.PublicKeyNotFoundException; import io.mosip.vercred.exception.UnknownException; import org.apache.commons.codec.binary.Base64; import org.joda.time.DateTime; @@ -49,7 +48,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import io.mosip.print.constant.CredentialStatusConstant; import io.mosip.print.constant.EventId; @@ -177,8 +175,7 @@ public class PrintServiceImpl implements PrintService { @Autowired private Environment env; - @Autowired - private CredentialsVerifier credentialsVerifier; + private CredentialsVerifier credentialsVerifier= new CredentialsVerifier(); @Value("${mosip.datashare.partner.id}") private String partnerId; @@ -241,17 +238,17 @@ private boolean hasPrintCredentialVerified(EventModel eventModel, String decoded if (verifyCredentialsFlag) { printLogger.info("Configured received credentials to be verified. Flag {}", verifyCredentialsFlag); try { - boolean verified = credentialsVerifier.verifyPrintCredentials(decodedCredential); + boolean verified = credentialsVerifier.verifyCredentials(decodedCredential); if (!verified) { printLogger.error("Received Credentials failed in verifiable credential verify method. So, the credentials will not be printed." + " Id: {}, Transaction Id: {}", eventModel.getEvent().getId(), eventModel.getEvent().getTransactionId()); return false; } - } catch (ProofDocumentNotFoundException | ProofTypeNotFoundException e) { + } catch (ProofDocumentNotFoundException | ProofTypeNotSupportedException e) { printLogger.error("Proof document is not available in the received credentials." + " Id: {}, Transaction Id: {}", eventModel.getEvent().getId(), eventModel.getEvent().getTransactionId()); return false; - } catch (UnknownException | PubicKeyNotFoundException e) { + } catch (UnknownException | PublicKeyNotFoundException e) { printLogger.error("Received Credentials failed in verifiable credential verify method. So, the credentials will not be printed." + " Id: {}, Transaction Id: {}", eventModel.getEvent().getId(), eventModel.getEvent().getTransactionId()); return false; @@ -394,8 +391,6 @@ private Map getDocuments(String credential, String credentialTyp description.setMessage(PlatformErrorMessages.PRT_PRT_PDF_GENERATION_FAILED.getMessage()); description.setCode(PlatformErrorMessages.PRT_PRT_PDF_GENERATION_FAILED.getCode()); printLogger.error(ex.getMessage() ,ex); - throw new PDFGeneratorException(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), - ex.getMessage() ,ex); } finally { String eventId = ""; From c2c7eeb98e4f99b50ea3c012e07606acec121614 Mon Sep 17 00:00:00 2001 From: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:53:24 +0530 Subject: [PATCH 04/71] Updated the released versions for commons dependency Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7f0849ff..ade623b8 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ **/dto/**,**/config/**,**/api/** 1.4.2 2.8.4 - 1.3.0-SNAPSHOT + 1.3.0-beta.1 7.1.0 2.0.0 5.5.13.3 @@ -64,7 +64,7 @@ io.mosip.kernel kernel-bom - 1.3.0-SNAPSHOT + 1.3.0-beta.1 pom import From 4ace41073599155eaddd4fbe7df0154c505d45b2 Mon Sep 17 00:00:00 2001 From: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:56:26 +0530 Subject: [PATCH 05/71] updated the versions from the props Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ade623b8..178fe620 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,7 @@ 1.4.2 2.8.4 1.3.0-beta.1 + 1.3.0-beta.1 7.1.0 2.0.0 5.5.13.3 @@ -64,7 +65,7 @@ io.mosip.kernel kernel-bom - 1.3.0-beta.1 + ${kernel.core.version} pom import From 9aedc4b192fa9dfc14de4c58547e3fd706853998 Mon Sep 17 00:00:00 2001 From: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:20:56 +0530 Subject: [PATCH 06/71] updated readme for developer guide java21 Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- README.md | 18 +++++++++--------- docs/build-and-run.md | 8 ++++---- docs/configuration.md | 10 ++++++++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 836d8bde..bd9aa008 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,33 @@ # Print Service ## Overview -A reference implementation to print `euin`, `reprint`, `qrcode` [credential types](https://docs.mosip.io/1.2.0/modules/id-repository#credential-types) in PDF format. This service is intended to be custimized and used by a card printing agency who need to onboard onto MOSIP as [Credential Partner](https://docs.mosip.io/1.2.0/partners#credential-partner-cp) before deploying the service. +A reference implementation to print `euin`, `reprint`, `qrcode` [credential types](https://docs.mosip.io/1.2.0/modules/id-repository#credential-types) in PDF format. This service is intended to be customized and used by a card printing agency who need to onboard onto MOSIP as [Credential Partner](https://docs.mosip.io/1.2.0/partners#credential-partner-cp) before deploying the service. ![](docs/print-service.png) 1. Receives events from WebSub. 2. Fetches templates from Masterdata. -3. After creating PDF card print service upload the same to [Datashare](https://docs.mosip.io/1.2.0/modules/data-share). -4. Publishes event to WebSub with updated status and Datashare link. +3. After creating PDF card print service upload the same to [DataShare](https://docs.mosip.io/1.2.0/modules/data-share). +4. Publishes event to WebSub with updated status and DataShare link. -The card data in JSON format is publised as WebSub event. The print service consumes the data from event, decrypts using partner private key and converts into PDF using a predefined [template](docs/configuration.md#template). +The card data in JSON format is published as WebSub event. The print service consumes the data from event, decrypts using partner private key and converts into PDF using a predefined [template](docs/configuration.md#template). ## Build and run (for developers) Refer [Build and Run](docs/build-and-run.md). ## Deploy -The deploy print service in production follow the given steps: +To deploy print service in production follow the given steps: 1. Onboard your organisation as [Credential Partner](https://docs.mosip.io/1.2.0/partners). -1. Place your `.p12` file in `../src/main/resources` folder. -1. Set configuration as in given [here](docs/configuation.md). -1. Build and run as given [here](docs/build-and-run.md). +2. Place your `.p12` file in `../src/main/resources` folder. +3. Set configuration as in given [here](docs/configuation.md). +4. Build and run as given [here](docs/build-and-run.md). ## Configuration Refer to the [configuration guide](docs/configuration.md). ## Test -Automated functaionl tests available in [Functional Tests repo](https://github.com/mosip/mosip-functional-tests). +Automated functional tests available in [Functional Tests repo](https://github.com/mosip/mosip-functional-tests). ## License This project is licensed under the terms of [Mozilla Public License 2.0](LICENSE). diff --git a/docs/build-and-run.md b/docs/build-and-run.md index 1d17bbca..08412bc8 100644 --- a/docs/build-and-run.md +++ b/docs/build-and-run.md @@ -4,8 +4,8 @@ Following tools required to run the print service locally. 1. _Ngrok_ to expose local port to remote to communicate with websub -1. Java 11. -1. [Config Server](https://docs.mosip.io/1.2.0/modules/module-configuration) running in respective environment. +2. The project requires Java 21 and mvn version - 3.9.6. +3. [Config Server](https://docs.mosip.io/1.2.0/modules/module-configuration) running in respective environment. ## Build 1. To build jars: @@ -13,11 +13,11 @@ Following tools required to run the print service locally. $ cd print $ mvn clean install ``` -1. To skip JUnit tests and Java Docs: +2. To skip JUnit tests and Java Docs: ``` $ mvn install -DskipTests=true -Dmaven.javadoc.skip=true ``` -1. To build Docker for a service: +3. To build Docker for a service: ``` $ cd $ docker build -f Dockerfile diff --git a/docs/configuration.md b/docs/configuration.md index 3a575ead..ede51b3d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,7 +1,7 @@ # Print Service Configuration Guide ## Overview -The guide here lists down some of the important properties that may be customised for a given installation. Note that the listing here is not exhaustive, but a checklist to review properties that are likely to be different from default. If you would like to see all the properites, then refer to the files listed below. +The guide here lists down some of the important properties that may be customised for a given installation. Note that the listing here is not exhaustive, but a checklist to review properties that are likely to be different from default. If you would like to see all the properties, then refer to the files listed below. ## Configuration files Print service uses the following configuration files: @@ -11,6 +11,12 @@ print-default.properties registration-processor-print-text-file.json identity-mapping.json ``` +## Configuration +[Configuration-Application](https://github.com/mosip/mosip-config/blob/release-1.3.x/application-default.properties), +[Configuration-Print](https://github.com/mosip/mosip-config/blob/release-1.3.x/print-default.properties), +[Configuration-RegProcPrintTextFile](https://github.com/mosip/mosip-config/blob/release-1.3.x/registration-processor-print-text-file.json) and +[Configuration-IdMapping](https://github.com/mosip/mosip-config/blob/release-1.3.x/identity-mapping.json) defined here. + Refer [Module Configuration](https://docs.mosip.io/1.2.0/modules/module-configuration) for location of these files. ## Template @@ -34,7 +40,7 @@ print-WebSub-resubscription-delay-millisecs = // resubscription delay time. mosip.event.secret = //secret key ``` -## Datashare +## DataShare ``` mosip.datashare.partner.id = /your partner id from partner portal mosip.datashare.policy.id = /your policy id from partner portal From 409fb5425b90feabb6e0cc8363d0e42f03857b9d Mon Sep 17 00:00:00 2001 From: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:13:46 +0530 Subject: [PATCH 07/71] updated config props and jdk versions Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- docs/build-and-run.md | 2 +- docs/configuration.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/build-and-run.md b/docs/build-and-run.md index 08412bc8..9aadfbcc 100644 --- a/docs/build-and-run.md +++ b/docs/build-and-run.md @@ -4,7 +4,7 @@ Following tools required to run the print service locally. 1. _Ngrok_ to expose local port to remote to communicate with websub -2. The project requires Java 21 and mvn version - 3.9.6. +2. The project requires JDK 21.0.3 and mvn version - 3.9.6. 3. [Config Server](https://docs.mosip.io/1.2.0/modules/module-configuration) running in respective environment. ## Build diff --git a/docs/configuration.md b/docs/configuration.md index ede51b3d..f8f105a2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,7 +4,7 @@ The guide here lists down some of the important properties that may be customised for a given installation. Note that the listing here is not exhaustive, but a checklist to review properties that are likely to be different from default. If you would like to see all the properties, then refer to the files listed below. ## Configuration files -Print service uses the following configuration files: +Print service uses the following configuration files that are accessible in this [repository](https://github.com/mosip/mosip-config/tree/master). ``` application-default.properties print-default.properties @@ -12,10 +12,10 @@ registration-processor-print-text-file.json identity-mapping.json ``` ## Configuration -[Configuration-Application](https://github.com/mosip/mosip-config/blob/release-1.3.x/application-default.properties), -[Configuration-Print](https://github.com/mosip/mosip-config/blob/release-1.3.x/print-default.properties), -[Configuration-RegProcPrintTextFile](https://github.com/mosip/mosip-config/blob/release-1.3.x/registration-processor-print-text-file.json) and -[Configuration-IdMapping](https://github.com/mosip/mosip-config/blob/release-1.3.x/identity-mapping.json) defined here. +[Configuration-Application](https://github.com/mosip/mosip-config/blob/master/application-default.properties), +[Configuration-Print](https://github.com/mosip/mosip-config/blob/master/print-default.properties), +[Configuration-RegProcPrintTextFile](https://github.com/mosip/mosip-config/blob/master/registration-processor-print-text-file.json) and +[Configuration-IdMapping](https://github.com/mosip/mosip-config/blob/master/identity-mapping.json) defined here. Refer [Module Configuration](https://docs.mosip.io/1.2.0/modules/module-configuration) for location of these files. From f59af6f22a7b7242600a6fab47b13fe7e635c6b1 Mon Sep 17 00:00:00 2001 From: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:38:37 +0530 Subject: [PATCH 08/71] updated the configuration Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index f8f105a2..e619dc70 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -5,6 +5,7 @@ The guide here lists down some of the important properties that may be customise ## Configuration files Print service uses the following configuration files that are accessible in this [repository](https://github.com/mosip/mosip-config/tree/master). +Please refer to the required released tagged version for configuration. ``` application-default.properties print-default.properties From 0704db5ba9e63d6b4d5ee2bbea524934f2380ee3 Mon Sep 17 00:00:00 2001 From: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:51:31 +0530 Subject: [PATCH 09/71] Update configuration.md Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- docs/configuration.md | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index e619dc70..78b1440a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -3,20 +3,13 @@ ## Overview The guide here lists down some of the important properties that may be customised for a given installation. Note that the listing here is not exhaustive, but a checklist to review properties that are likely to be different from default. If you would like to see all the properties, then refer to the files listed below. -## Configuration files +## Configuration Print service uses the following configuration files that are accessible in this [repository](https://github.com/mosip/mosip-config/tree/master). Please refer to the required released tagged version for configuration. -``` -application-default.properties -print-default.properties -registration-processor-print-text-file.json -identity-mapping.json -``` -## Configuration -[Configuration-Application](https://github.com/mosip/mosip-config/blob/master/application-default.properties), -[Configuration-Print](https://github.com/mosip/mosip-config/blob/master/print-default.properties), -[Configuration-RegProcPrintTextFile](https://github.com/mosip/mosip-config/blob/master/registration-processor-print-text-file.json) and -[Configuration-IdMapping](https://github.com/mosip/mosip-config/blob/master/identity-mapping.json) defined here. +1. [Configuration-Application](https://github.com/mosip/mosip-config/blob/master/application-default.properties) +2. [Configuration-Print](https://github.com/mosip/mosip-config/blob/master/print-default.properties) +3. [Configuration-RegProcPrintTextFile](https://github.com/mosip/mosip-config/blob/master/registration-processor-print-text-file.json) +4. [Configuration-IdMapping](https://github.com/mosip/mosip-config/blob/master/identity-mapping.json) Refer [Module Configuration](https://docs.mosip.io/1.2.0/modules/module-configuration) for location of these files. From c4ff3fbae26bc9eb14cb86f3650a8afc631f5b3b Mon Sep 17 00:00:00 2001 From: dhanendra06 Date: Tue, 12 Nov 2024 11:09:14 +0530 Subject: [PATCH 10/71] MOSIP-32453 Signed-off-by: dhanendra06 --- pom.xml | 2 +- src/main/java/io/mosip/print/PrintPDFApplication.java | 5 +++++ .../io/mosip/print/service/impl/PrintServiceImpl.java | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 178fe620..463f0876 100644 --- a/pom.xml +++ b/pom.xml @@ -246,7 +246,7 @@ io.mosip.vercred vcverifier - 1.1.0-SNAPSHOT + 1.0.0 diff --git a/src/main/java/io/mosip/print/PrintPDFApplication.java b/src/main/java/io/mosip/print/PrintPDFApplication.java index 3517949b..778ba87e 100644 --- a/src/main/java/io/mosip/print/PrintPDFApplication.java +++ b/src/main/java/io/mosip/print/PrintPDFApplication.java @@ -32,6 +32,11 @@ public CbeffUtil getCbeffUtil() { return new CbeffImpl(); } + @Bean + public CredentialsVerifier credentialsVerifier() { + return new CredentialsVerifier(); + } + @Bean public ThreadPoolTaskScheduler taskScheduler() { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); diff --git a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java index 76c35c72..2cdd326b 100644 --- a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java +++ b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java @@ -34,8 +34,8 @@ import io.mosip.vercred.CredentialsVerifier; import io.mosip.vercred.exception.ProofDocumentNotFoundException; -import io.mosip.vercred.exception.ProofTypeNotSupportedException; -import io.mosip.vercred.exception.PublicKeyNotFoundException; +import io.mosip.vercred.exception.ProofTypeNotFoundException; +import io.mosip.vercred.exception.PubicKeyNotFoundException; import io.mosip.vercred.exception.UnknownException; import org.apache.commons.codec.binary.Base64; import org.joda.time.DateTime; @@ -244,11 +244,11 @@ private boolean hasPrintCredentialVerified(EventModel eventModel, String decoded " Id: {}, Transaction Id: {}", eventModel.getEvent().getId(), eventModel.getEvent().getTransactionId()); return false; } - } catch (ProofDocumentNotFoundException | ProofTypeNotSupportedException e) { + } catch (ProofDocumentNotFoundException | ProofTypeNotFoundException e) { printLogger.error("Proof document is not available in the received credentials." + " Id: {}, Transaction Id: {}", eventModel.getEvent().getId(), eventModel.getEvent().getTransactionId()); return false; - } catch (UnknownException | PublicKeyNotFoundException e) { + } catch (UnknownException | PubicKeyNotFoundException e) { printLogger.error("Received Credentials failed in verifiable credential verify method. So, the credentials will not be printed." + " Id: {}, Transaction Id: {}", eventModel.getEvent().getId(), eventModel.getEvent().getTransactionId()); return false; From 3e057715ce02b52d0b2d93fa1ed160efec166075 Mon Sep 17 00:00:00 2001 From: dhanendra06 Date: Tue, 12 Nov 2024 11:16:37 +0530 Subject: [PATCH 11/71] MOSIP-32453 Signed-off-by: dhanendra06 --- .../io/mosip/print/service/impl/PrintServiceImpl.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java index 2cdd326b..31f0463a 100644 --- a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java +++ b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java @@ -97,7 +97,6 @@ import io.mosip.print.util.TemplateGenerator; import io.mosip.print.util.Utilities; import io.mosip.print.util.WebSubSubscriptionHelper; -import io.mosip.vercred.CredentialsVerifier; @Service public class PrintServiceImpl implements PrintService { @@ -175,7 +174,8 @@ public class PrintServiceImpl implements PrintService { @Autowired private Environment env; - private CredentialsVerifier credentialsVerifier= new CredentialsVerifier(); + @Autowired + private CredentialsVerifier credentialsVerifier; @Value("${mosip.datashare.partner.id}") private String partnerId; @@ -238,7 +238,7 @@ private boolean hasPrintCredentialVerified(EventModel eventModel, String decoded if (verifyCredentialsFlag) { printLogger.info("Configured received credentials to be verified. Flag {}", verifyCredentialsFlag); try { - boolean verified = credentialsVerifier.verifyCredentials(decodedCredential); + boolean verified = credentialsVerifier.verifyPrintCredentials(decodedCredential); if (!verified) { printLogger.error("Received Credentials failed in verifiable credential verify method. So, the credentials will not be printed." + " Id: {}, Transaction Id: {}", eventModel.getEvent().getId(), eventModel.getEvent().getTransactionId()); @@ -391,7 +391,8 @@ private Map getDocuments(String credential, String credentialTyp description.setMessage(PlatformErrorMessages.PRT_PRT_PDF_GENERATION_FAILED.getMessage()); description.setCode(PlatformErrorMessages.PRT_PRT_PDF_GENERATION_FAILED.getCode()); printLogger.error(ex.getMessage() ,ex); - + throw new PDFGeneratorException(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + ex.getMessage() ,ex); } finally { String eventId = ""; String eventName = ""; From 1a0ac8d7ce2ab7566c23b301385146e905bc9688 Mon Sep 17 00:00:00 2001 From: Prafulrakhade <99539100+Prafulrakhade@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:06:56 +0000 Subject: [PATCH 12/71] Updated chart versions, image and tag for release changes Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- deploy/install.sh | 2 +- helm/print/Chart.yaml | 2 +- helm/print/values.yaml | 46 +----------------------------------------- 3 files changed, 3 insertions(+), 47 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 1a8f5129..37e4ee6d 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -8,7 +8,7 @@ fi NS=print -CHART_VERSION=0.0.1-develop +CHART_VERSION=1.3.0-beta.1-develop echo Create $NS namespace kubectl create ns $NS diff --git a/helm/print/Chart.yaml b/helm/print/Chart.yaml index ee1d277a..73fff895 100644 --- a/helm/print/Chart.yaml +++ b/helm/print/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: print-service description: A Helm chart for sample Print Service type: application -version: 0.0.1-develop +version: 1.3.0-beta.1-develop appVersion: "" dependencies: - name: common diff --git a/helm/print/values.yaml b/helm/print/values.yaml index b2df5aa4..edb9431d 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -12,23 +12,18 @@ ## commonLabels: app.kubernetes.io/component: mosip - ## Add annotations to all the deployed resources ## commonAnnotations: {} - ## Kubernetes Cluster Domain ## clusterDomain: cluster.local - ## Extra objects to deploy (value evaluated as a template) ## extraDeploy: [] - ## Number of nodes ## replicaCount: 1 - service: type: ClusterIP port: 80 @@ -49,11 +44,10 @@ service: ## ref http://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip ## externalTrafficPolicy: Cluster - image: registry: docker.io repository: mosipqa/print - tag: develop + tag: 1.3.x ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -65,10 +59,8 @@ image: ## # pullSecrets: # - myRegistryKeySecretName - ## Port on which this particular spring service module is running. springServicePort: 8088 - ## Configure extra options for liveness and readiness probes ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes ## @@ -82,7 +74,6 @@ startupProbe: timeoutSeconds: 5 failureThreshold: 10 successThreshold: 1 - livenessProbe: enabled: true httpGet: @@ -93,7 +84,6 @@ livenessProbe: timeoutSeconds: 5 failureThreshold: 6 successThreshold: 1 - readinessProbe: enabled: true httpGet: @@ -104,7 +94,6 @@ readinessProbe: timeoutSeconds: 5 failureThreshold: 6 successThreshold: 1 - ## # existingConfigmap: @@ -112,12 +101,10 @@ readinessProbe: ## command: [] args: [] - ## Deployment pod host aliases ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ ## hostAliases: [] - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ ## resources: @@ -131,37 +118,31 @@ resources: requests: cpu: 100m memory: 1000Mi - additionalResources: ## Specify any JAVA_OPTS string here. These typically will be specified in conjunction with above resources ## Example: java_opts: "-Xms500M -Xmx500M" javaOpts: "-Xms2250M -Xmx2250M" - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container ## Clamav container already runs as 'mosip' user, so we may not need to enable this containerSecurityContext: enabled: false runAsUser: mosip runAsNonRoot: true - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod ## podSecurityContext: enabled: false fsGroup: 1001 - ## Pod affinity preset ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity ## Allowed values: soft, hard ## podAffinityPreset: "" - ## Pod anti-affinity preset ## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity ## Allowed values: soft, hard ## podAntiAffinityPreset: soft - ## Node affinity preset ## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity ## Allowed values: soft, hard @@ -183,32 +164,26 @@ nodeAffinityPreset: ## - e2e-az2 ## values: [] - ## Affinity for pod assignment. Evaluated as a template. ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity ## affinity: {} - ## Node labels for pod assignment. Evaluated as a template. ## ref: https://kubernetes.io/docs/user-guide/node-selection/ ## nodeSelector: {} - ## Tolerations for pod assignment. Evaluated as a template. ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ ## tolerations: [] - ## Pod extra labels ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ ## podLabels: {} - ## Annotations for server pods. ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ ## podAnnotations: {} - ## pods' priority. ## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ ## @@ -217,15 +192,12 @@ podAnnotations: {} ## lifecycleHooks for the container to automate configuration before or after startup. ## lifecycleHooks: {} - ## Custom Liveness probes for ## customLivenessProbe: {} - ## Custom Rediness probes ## customReadinessProbe: {} - ## Update strategy - only really applicable for deployments with RWO PVs attached ## If replicas = 1, an update can get "stuck", as the previous pod remains attached to the ## PV, and the "incoming" pod can never start. Changing the strategy to "Recreate" will @@ -233,7 +205,6 @@ customReadinessProbe: {} ## updateStrategy: type: RollingUpdate - ## Additional environment variables to set ## Example: ## extraEnvVars: @@ -241,26 +212,21 @@ updateStrategy: ## value: "bar" ## extraEnvVars: [] - ## ConfigMap with extra environment variables that used ## extraEnvVarsCM: - global - config-server-share - artifactory-share - ## Secret with extra environment variables ## extraEnvVarsSecret: - ## Extra volumes to add to the deployment ## extraVolumes: [] - ## Extra volume mounts to add to the container ## extraVolumeMounts: [] - ## Add init containers to the pods. ## Example: ## initContainers: @@ -272,7 +238,6 @@ extraVolumeMounts: [] ## containerPort: 1234 ## initContainers: {} - ## Add sidecars to the pods. ## Example: ## sidecars: @@ -284,7 +249,6 @@ initContainers: {} ## containerPort: 1234 ## sidecars: {} - persistence: enabled: false ## If defined, storageClassName: @@ -306,7 +270,6 @@ persistence: existingClaim: # Dir where config and keys are written inside container mountDir: - ## Init containers parameters: ## volumePermissions: Change the owner and group of the persistent volume mountpoint to runAsUser:fsGroup values from the securityContext section. ## @@ -340,12 +303,10 @@ volumePermissions: ## cpu: 100m ## memory: 128Mi ## - ## Specifies whether RBAC resources should be created ## rbac: create: true - ## Specifies whether a ServiceAccount should be created ## serviceAccount: @@ -354,7 +315,6 @@ serviceAccount: ## If not set and create is true, a name is generated using the fullname template ## name: - ## Prometheus Metrics ## metrics: @@ -364,9 +324,7 @@ metrics: ## podAnnotations: prometheus.io/scrape: "true" - endpointPath: /v1/print/actuator/prometheus - ## Prometheus Service Monitor ## ref: https://github.com/coreos/prometheus-operator ## @@ -393,7 +351,6 @@ metrics: ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec ## additionalLabels: {} - ## Custom PrometheusRule to be defined ## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart ## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions @@ -411,7 +368,6 @@ metrics: # labels: # severity: error rules: [] - ## Partner management needs public access. istio: enabled: true From 4557954528f4e66346fbed191eef7871fc3a9778 Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Tue, 10 Dec 2024 18:20:10 +0530 Subject: [PATCH 13/71] [DSD-6825] platform 1.2.1.0-beta-1 release (#253) * Updated Pom versions for release changes Signed-off-by: GitHub * Update README.md Signed-off-by: Praful Rakhade * [DSD-6825] platform 1.2.1.0-beta-1 release Signed-off-by: Praful Rakhade --------- Signed-off-by: GitHub Signed-off-by: Praful Rakhade Co-authored-by: Prafulrakhade Co-authored-by: Praful Rakhade --- .github/workflows/push-trigger.yml | 2 +- README.md | 4 ++-- pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/push-trigger.yml b/.github/workflows/push-trigger.yml index 608ab4a1..c2b9264b 100644 --- a/.github/workflows/push-trigger.yml +++ b/.github/workflows/push-trigger.yml @@ -43,7 +43,7 @@ jobs: secrets: OSSRH_USER: ${{ secrets.OSSRH_USER }} OSSRH_SECRET: ${{ secrets.OSSRH_SECRET }} - OSSRH_URL: ${{ secrets.OSSRH_SNAPSHOT_URL }} + OSSRH_URL: ${{ secrets.RELEASE_URL }} OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} GPG_SECRET: ${{ secrets.GPG_SECRET }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/README.md b/README.md index bd9aa008..13d3ef3c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![Maven Package upon a push](https://github.com/mosip/print/actions/workflows/push_trigger.yml/badge.svg?branch=release-1.2.0)](https://github.com/mosip/print/actions/workflows/push_trigger.yml) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?branch=release-1.2.0&project=mosip_admin-services&id=mosip_admin-services&metric=alert_status)](https://sonarcloud.io/dashboard?branch=release-1.2.0&id=mosip_admin-services) +[![Maven Package upon a push](https://github.com/mosip/print/actions/workflows/push-trigger.yml/badge.svg?branch=release-1.3.x)](https://github.com/mosip/print/actions/workflows/push-trigger.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?branch=release-1.3.x&project=mosip_admin-services&id=mosip_admin-services&metric=alert_status)](https://sonarcloud.io/dashboard?branch=release-1.3.x&id=mosip_admin-services) # Print Service ## Overview diff --git a/pom.xml b/pom.xml index 463f0876..0433ba3f 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.0-SNAPSHOT + 1.3.0-beta.1 print @@ -436,7 +436,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.6.14 true ossrh From bdd00b499de7775a26c07f4c18d25b0442f8d620 Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Tue, 10 Dec 2024 18:28:55 +0530 Subject: [PATCH 14/71] Updated chart versions, image and tag for release changes (#254) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Prafulrakhade <99539100+Prafulrakhade@users.noreply.github.com> --- deploy/install.sh | 2 +- helm/print/Chart.yaml | 2 +- helm/print/values.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 37e4ee6d..c136db02 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -8,7 +8,7 @@ fi NS=print -CHART_VERSION=1.3.0-beta.1-develop +CHART_VERSION=1.3.0-beta.1 echo Create $NS namespace kubectl create ns $NS diff --git a/helm/print/Chart.yaml b/helm/print/Chart.yaml index 73fff895..ad0ae2c3 100644 --- a/helm/print/Chart.yaml +++ b/helm/print/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: print-service description: A Helm chart for sample Print Service type: application -version: 1.3.0-beta.1-develop +version: 1.3.0-beta.1 appVersion: "" dependencies: - name: common diff --git a/helm/print/values.yaml b/helm/print/values.yaml index edb9431d..2aab1541 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -46,8 +46,8 @@ service: externalTrafficPolicy: Cluster image: registry: docker.io - repository: mosipqa/print - tag: 1.3.x + repository: mosipid/print + tag: 1.3.0-beta.1 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images From c07c6ecb3b4b9162a85c5cb2975a0880e656fbdb Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Fri, 13 Dec 2024 14:32:56 +0530 Subject: [PATCH 15/71] Updated Pom version and Chart version for post-release changes (#255) Signed-off-by: GitHub Co-authored-by: Prafulrakhade --- .github/workflows/push-trigger.yml | 2 +- deploy/install.sh | 2 +- helm/print/Chart.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push-trigger.yml b/.github/workflows/push-trigger.yml index c2b9264b..608ab4a1 100644 --- a/.github/workflows/push-trigger.yml +++ b/.github/workflows/push-trigger.yml @@ -43,7 +43,7 @@ jobs: secrets: OSSRH_USER: ${{ secrets.OSSRH_USER }} OSSRH_SECRET: ${{ secrets.OSSRH_SECRET }} - OSSRH_URL: ${{ secrets.RELEASE_URL }} + OSSRH_URL: ${{ secrets.OSSRH_SNAPSHOT_URL }} OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} GPG_SECRET: ${{ secrets.GPG_SECRET }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/deploy/install.sh b/deploy/install.sh index c136db02..37e4ee6d 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -8,7 +8,7 @@ fi NS=print -CHART_VERSION=1.3.0-beta.1 +CHART_VERSION=1.3.0-beta.1-develop echo Create $NS namespace kubectl create ns $NS diff --git a/helm/print/Chart.yaml b/helm/print/Chart.yaml index ad0ae2c3..73fff895 100644 --- a/helm/print/Chart.yaml +++ b/helm/print/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: print-service description: A Helm chart for sample Print Service type: application -version: 1.3.0-beta.1 +version: 1.3.0-beta.1-develop appVersion: "" dependencies: - name: common From f86b2af7fd065c80a54a8cc1bab4c88d09b4d60f Mon Sep 17 00:00:00 2001 From: Youssef MAHTAT Date: Mon, 2 Dec 2024 13:41:14 +0100 Subject: [PATCH 16/71] MOSIP-25202 - fix sonar reliability issues Signed-off-by: Youssef MAHTAT --- .../print/service/impl/PrintServiceImpl.java | 27 ++++++++++--------- .../io/mosip/print/util/RestApiClient.java | 10 +++++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java index 31f0463a..1b1bfd76 100644 --- a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java +++ b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java @@ -644,8 +644,11 @@ private void setTemplateAttributes(String jsonString, Map attrib * @throws Exception */ private String getPassword(org.json.JSONObject jsonObject) throws ApisResourceAccessException, IOException { - - String[] attributes = env.getProperty(UINCARDPASSWORD).split("\\|"); + String propertyValue = env.getProperty(UINCARDPASSWORD); + if (Objects.isNull(propertyValue)) { + throw new NullPointerException("Environment property '" + UINCARDPASSWORD + "' is missing."); + } + String[] attributes = propertyValue.split("\\|"); List list = new ArrayList<>(Arrays.asList(attributes)); Iterator it = list.iterator(); String uinCardPd = ""; @@ -659,16 +662,16 @@ private String getPassword(org.json.JSONObject jsonObject) throws ApisResourceAc } catch (Exception e) { obj = object; } - } - if (obj instanceof JSONArray) { - JsonValue[] jsonValues = JsonUtil.mapJsonNodeToJavaObject(JsonValue.class, (JSONArray) obj); - uinCardPd = uinCardPd.concat(getFormattedPasswordAttribute(getParameter(jsonValues, templateLang))); - - } else if (object instanceof org.json.simple.JSONObject) { - org.json.simple.JSONObject json = (org.json.simple.JSONObject) object; - uinCardPd = uinCardPd.concat(getFormattedPasswordAttribute((String) json.get(VALUE))); - } else { - uinCardPd = uinCardPd.concat(getFormattedPasswordAttribute(object.toString())); + if (obj instanceof JSONArray) { + JsonValue[] jsonValues = JsonUtil.mapJsonNodeToJavaObject(JsonValue.class, (JSONArray) obj); + uinCardPd = uinCardPd.concat(getFormattedPasswordAttribute(getParameter(jsonValues, templateLang))); + + } else if (object instanceof org.json.simple.JSONObject) { + org.json.simple.JSONObject json = (org.json.simple.JSONObject) object; + uinCardPd = uinCardPd.concat(getFormattedPasswordAttribute((String) json.get(VALUE))); + } else { + uinCardPd = uinCardPd.concat(getFormattedPasswordAttribute(object.toString())); + } } } return uinCardPd; diff --git a/src/main/java/io/mosip/print/util/RestApiClient.java b/src/main/java/io/mosip/print/util/RestApiClient.java index 6d510947..7e9f43fb 100644 --- a/src/main/java/io/mosip/print/util/RestApiClient.java +++ b/src/main/java/io/mosip/print/util/RestApiClient.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.net.URI; import java.util.Iterator; +import java.util.List; + import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -117,8 +119,12 @@ private HttpEntity setRequestHeader(Object requestType, MediaType mediaT Iterator iterator = httpHeader.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); - if (!(headers.containsKey("Content-Type") && key.equals("Content-Type"))) - headers.add(key, httpHeader.get(key).get(0)); + if (!(headers.containsKey("Content-Type") && key.equals("Content-Type"))) { + List values = httpHeader.get(key); + if (values != null && !values.isEmpty()) { + headers.add(key, values.getFirst()); + } + } } return new HttpEntity(httpEntity.getBody(), headers); } catch (ClassCastException | NullPointerException e) { From a9a84117446fc046b172fd6e3cbca582fd878b01 Mon Sep 17 00:00:00 2001 From: Youssef MAHTAT Date: Tue, 28 Jan 2025 13:46:28 +0100 Subject: [PATCH 17/71] MOSIP-25202 - github actions fix - update actions/upload-artifact and actions/download-artifact to version 4 Signed-off-by: Youssef MAHTAT --- .github/workflows/push-trigger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push-trigger.yml b/.github/workflows/push-trigger.yml index 608ab4a1..60e53612 100644 --- a/.github/workflows/push-trigger.yml +++ b/.github/workflows/push-trigger.yml @@ -58,7 +58,7 @@ jobs: SERVICE_LOCATION: print steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 if: ${{ ( env.BUILD_ARTIFACT != 'false' ) }} with: name: ${{ env.BUILD_ARTIFACT }} @@ -78,7 +78,7 @@ jobs: - name: Upload the springboot jars if: ${{ !contains(github.ref, 'master') || !contains(github.ref, 'main') }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.NEW_BUILD_ARTIFACT }} path: ${{ env.NEW_BUILD_ARTIFACT }}.zip From 89ef4eeb035c436b18ef9ca36c1a921a7f2bb15f Mon Sep 17 00:00:00 2001 From: Gokulraj C <110164849+GOKULRAJ136@users.noreply.github.com> Date: Fri, 7 Mar 2025 11:09:10 +0530 Subject: [PATCH 18/71] [MOSIP-40012] Corrected ZGC (#263) * [MOSIP-40012] Corrected ZCG Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> * updated url and description in pom Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --------- Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- Dockerfile | 16 ++-------------- pom.xml | 2 ++ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0f46b888..8f603166 100644 --- a/Dockerfile +++ b/Dockerfile @@ -86,17 +86,5 @@ USER ${container_user_uid}:${container_user_gid} EXPOSE 8099 -CMD if [ "$is_glowroot_env" = "present" ]; then \ - wget "${artifactory_url_env}"/artifactory/libs-release-local/io/mosip/testing/glowroot.zip ; \ - unzip glowroot.zip ; \ - rm -rf glowroot.zip ; \ - sed -i 's//print/g' glowroot/glowroot.properties ; \ - wget -q "${iam_adapter_url_env}" -O "${loader_path_env}"/kernel-auth-adapter.jar; \ - java -jar -javaagent:glowroot/glowroot.jar -Dloader.path="${loader_path_env}" -Dspring.cloud.config.label="${spring_config_label_env}" -Dspring.profiles.active="${active_profile_env}" -Dspring.cloud.config.uri="${spring_config_url_env}" print.jar ; \ - else \ - wget -q "${iam_adapter_url_env}" -O "${loader_path_env}"/kernel-auth-adapter.jar; \ - java -jar -Dloader.path="${loader_path_env}" -Dspring.cloud.config.label="${spring_config_label_env}" -Dspring.profiles.active="${active_profile_env}" -Dspring.cloud.config.uri="${spring_config_url_env}" print.jar ; \ - fi - -#CMD ["java","-Dspring.cloud.config.label=${spring_config_label_env}","-Dspring.profiles.active=${active_profile_env}","-Dspring.cloud.config.uri=${spring_config_url_env}","-jar","-javaagent:/home/Glowroot/glowroot.jar","print.jar"] - +CMD wget -q "${iam_adapter_url_env}" -O "${loader_path_env}"/kernel-auth-adapter.jar; \ + java -XX:-UseG1GC -XX:-UseParallelGC -XX:-UseShenandoahGC -XX:+ExplicitGCInvokesConcurrent -XX:+UseZGC -XX:+ZGenerational -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication -XX:+HeapDumpOnOutOfMemoryError -XX:+UseCompressedOops -XX:MaxGCPauseMillis=200 -Dfile.encoding=UTF-8 -jar -Dloader.path="${loader_path_env}" -Dspring.cloud.config.label="${spring_config_label_env}" -Dspring.profiles.active="${active_profile_env}" -Dspring.cloud.config.uri="${spring_config_url_env}" print.jar ; \ diff --git a/pom.xml b/pom.xml index 0433ba3f..f72f5170 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,8 @@ print 1.3.0-beta.1 print + https://github.com/mosip/print + This is a project for MOSIP printing functionality. From caac4cbd273319ea6f2d4ee57c10449c49e5a0c9 Mon Sep 17 00:00:00 2001 From: kameshsr <47484458+kameshsr@users.noreply.github.com> Date: Mon, 17 Mar 2025 13:56:32 +0530 Subject: [PATCH 19/71] MOSIP-40719 change version to snapshot (#264) Signed-off-by: kameshsr --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index f72f5170..c67a5202 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.0-beta.1 + 1.3.0-SNAPSHOT print https://github.com/mosip/print This is a project for MOSIP printing functionality. @@ -50,8 +50,8 @@ **/dto/**,**/config/**,**/api/** 1.4.2 2.8.4 - 1.3.0-beta.1 - 1.3.0-beta.1 + 1.3.0-SNAPSHOT + 1.3.0-SNAPSHOT 7.1.0 2.0.0 5.5.13.3 From 0546ea046d247ceb1592993f283277919fb8af0d Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Wed, 19 Mar 2025 13:29:12 +0530 Subject: [PATCH 20/71] MOSIP-37901: print-release (#260) * MOSIP-37901: print-release Signed-off-by: nagendra0721 * MOSIP-37901: pom changes Signed-off-by: nagendra0721 --------- Signed-off-by: nagendra0721 --- .github/workflows/push-trigger.yml | 2 +- pom.xml | 56 +++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/.github/workflows/push-trigger.yml b/.github/workflows/push-trigger.yml index 60e53612..5e2fba7e 100644 --- a/.github/workflows/push-trigger.yml +++ b/.github/workflows/push-trigger.yml @@ -43,7 +43,7 @@ jobs: secrets: OSSRH_USER: ${{ secrets.OSSRH_USER }} OSSRH_SECRET: ${{ secrets.OSSRH_SECRET }} - OSSRH_URL: ${{ secrets.OSSRH_SNAPSHOT_URL }} + OSSRH_URL: ${{ secrets.RELEASE_URL }} OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} GPG_SECRET: ${{ secrets.GPG_SECRET }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/pom.xml b/pom.xml index c67a5202..27293ef9 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,10 @@ 1.66 3.8.1 3.4.1 + 3.1.1 + 1.6.14 + @@ -435,19 +438,46 @@ none - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.14 - true - - ossrh - https://oss.sonatype.org/ - false - - - - + + maven-deploy-plugin + ${maven.deploy.plugin.version} + + true + + + + default-deploy + none + + deploy + + + true + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus.staging.maven.plugin.version} + true + + + default-deploy + none + + deploy + + + + + ossrh + https://oss.sonatype.org/ + false + + + org.apache.maven.plugins maven-source-plugin 2.2.1 From a9464d7d9c2984870310b5f8a5255bb43afc30ca Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Thu, 20 Mar 2025 13:02:43 +0530 Subject: [PATCH 21/71] MOSIP-37901: change to RC (#265) * MOSIP-37901: change to RC Signed-off-by: nagendra0721 * MOSIP-37901: rc changes Signed-off-by: nagendra0721 * MOSIP-37901: rc changes Signed-off-by: nagendra0721 --------- Signed-off-by: nagendra0721 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 27293ef9..daaaa7dc 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.0-SNAPSHOT + 1.3.0-RC print https://github.com/mosip/print This is a project for MOSIP printing functionality. @@ -50,8 +50,8 @@ **/dto/**,**/config/**,**/api/** 1.4.2 2.8.4 - 1.3.0-SNAPSHOT - 1.3.0-SNAPSHOT + 1.3.0-beta.2 + 1.3.0-beta.2 7.1.0 2.0.0 5.5.13.3 From a351bf096d290c2d9e31330d831529baaecdcc81 Mon Sep 17 00:00:00 2001 From: PRAFUL RAKHADE <99539100+prafulrakhade@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:27:10 +0530 Subject: [PATCH 22/71] [MOSIP-41674] Updated the changes for mosip namespace migration from maven legacy sonartype to maven ossrh central Signed-off-by: techno-467 --- pom.xml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index daaaa7dc..1289650e 100644 --- a/pom.xml +++ b/pom.xml @@ -269,13 +269,22 @@ ossrh - CentralRepository + LegacyMavenSnapshot https://oss.sonatype.org/content/repositories/snapshots default true + + ossrh-central + MavenCentralRepository + https://central.sonatype.com/repository/maven-snapshots + default + + true + + central MavenCentral @@ -290,11 +299,11 @@ ossrh - https://oss.sonatype.org/content/repositories/snapshots + https://central.sonatype.com/repository/maven-snapshots ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://central.sonatype.com/api/v1/publisher @@ -458,9 +467,9 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus.staging.maven.plugin.version} + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true @@ -472,9 +481,8 @@ - ossrh - https://oss.sonatype.org/ - false + ossrh + false From de5a004ec1a1dc558dbd8df07388dd860bcdb4cd Mon Sep 17 00:00:00 2001 From: Prafulrakhade Date: Tue, 5 Aug 2025 12:24:58 +0530 Subject: [PATCH 23/71] [MOSIP-42464] [MOSIP-41674] central sonatype migration changes Signed-off-by: Prafulrakhade --- .github/workflows/push-trigger.yml | 2 +- pom.xml | 47 ++++++++++-------------------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/.github/workflows/push-trigger.yml b/.github/workflows/push-trigger.yml index 5e2fba7e..4f7655d2 100644 --- a/.github/workflows/push-trigger.yml +++ b/.github/workflows/push-trigger.yml @@ -43,7 +43,7 @@ jobs: secrets: OSSRH_USER: ${{ secrets.OSSRH_USER }} OSSRH_SECRET: ${{ secrets.OSSRH_SECRET }} - OSSRH_URL: ${{ secrets.RELEASE_URL }} + OSSRH_URL: ${{ secrets.OSSRH_CENTRAL_URL }} OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} GPG_SECRET: ${{ secrets.GPG_SECRET }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} diff --git a/pom.xml b/pom.xml index 1289650e..7b6fe1cf 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.0-RC + 1.3.0-SNAPSHOT print https://github.com/mosip/print This is a project for MOSIP printing functionality. @@ -62,7 +62,7 @@ 3.8.1 3.4.1 3.1.1 - 1.6.14 + 0.7.0 @@ -267,15 +267,6 @@ - - ossrh - LegacyMavenSnapshot - https://oss.sonatype.org/content/repositories/snapshots - default - - true - - ossrh-central MavenCentralRepository @@ -295,17 +286,17 @@ - + - - ossrh - https://central.sonatype.com/repository/maven-snapshots - - - ossrh - https://central.sonatype.com/api/v1/publisher - - + + ossrh + https://central.sonatype.com/repository/maven-snapshots + + + ossrh + https://central.sonatype.com/api/v1/publisher + + @@ -353,6 +344,9 @@ build-info repackage + + false + @@ -469,17 +463,8 @@ org.sonatype.central central-publishing-maven-plugin - 0.7.0 + ${central.publishing.maven.plugin.version} true - - - default-deploy - none - - deploy - - - ossrh false From d52bb4064338f8b8f3500740455313420560c730 Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Thu, 13 Nov 2025 10:19:27 +0530 Subject: [PATCH 24/71] pom and util updates Signed-off-by: Nandhukumar --- pom.xml | 4 ++-- .../java/io/mosip/print/service/impl/PrintServiceImpl.java | 4 ++-- .../io/mosip/print/service/impl/UinCardGeneratorImpl.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 7b6fe1cf..d65d0665 100644 --- a/pom.xml +++ b/pom.xml @@ -50,8 +50,8 @@ **/dto/**,**/config/**,**/api/** 1.4.2 2.8.4 - 1.3.0-beta.2 - 1.3.0-beta.2 + 1.3.0-SNAPSHOT + 1.3.0-SNAPSHOT 7.1.0 2.0.0 5.5.13.3 diff --git a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java index 1b1bfd76..e7c5219d 100644 --- a/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java +++ b/src/main/java/io/mosip/print/service/impl/PrintServiceImpl.java @@ -32,6 +32,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import io.mosip.kernel.core.util.DateUtils2; import io.mosip.vercred.CredentialsVerifier; import io.mosip.vercred.exception.ProofDocumentNotFoundException; import io.mosip.vercred.exception.ProofTypeNotFoundException; @@ -91,7 +92,6 @@ import io.mosip.print.util.CryptoCoreUtil; import io.mosip.print.util.CryptoUtil; import io.mosip.print.util.DataShareUtil; -import io.mosip.print.util.DateUtils; import io.mosip.print.util.JsonUtil; import io.mosip.print.util.RestApiClient; import io.mosip.print.util.TemplateGenerator; @@ -773,7 +773,7 @@ private String getCrdentialSubject(String crdential) { private void printStatusUpdate(String requestId, String status, String datashareUrl) { CredentialStatusEvent creEvent = new CredentialStatusEvent(); - LocalDateTime currentDtime = DateUtils.getUTCCurrentDateTime(); + LocalDateTime currentDtime = DateUtils2.getUTCCurrentDateTime(); StatusEvent sEvent = new StatusEvent(); sEvent.setId(UUID.randomUUID().toString()); sEvent.setRequestId(requestId); diff --git a/src/main/java/io/mosip/print/service/impl/UinCardGeneratorImpl.java b/src/main/java/io/mosip/print/service/impl/UinCardGeneratorImpl.java index 5cd1e350..bb281dce 100644 --- a/src/main/java/io/mosip/print/service/impl/UinCardGeneratorImpl.java +++ b/src/main/java/io/mosip/print/service/impl/UinCardGeneratorImpl.java @@ -6,6 +6,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import io.mosip.kernel.core.util.DateUtils2; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -33,7 +34,6 @@ import io.mosip.print.service.PrintRestClientService; import io.mosip.print.service.UinCardGenerator; import io.mosip.print.spi.PDFGenerator; -import io.mosip.print.util.DateUtils; import io.mosip.print.util.RestApiClient; /** @@ -99,9 +99,9 @@ public byte[] generateUinCard(InputStream in, UinCardType type, String password) request.setData(Base64.encodeBase64String(out.toByteArray())); DateTimeFormatter format = DateTimeFormatter.ofPattern(env.getProperty(DATETIME_PATTERN)); LocalDateTime localdatetime = LocalDateTime - .parse(DateUtils.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); + .parse(DateUtils2.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); - request.setTimeStamp(DateUtils.getUTCCurrentDateTimeString()); + request.setTimeStamp(DateUtils2.getUTCCurrentDateTimeString()); RequestWrapper requestWrapper = new RequestWrapper<>(); requestWrapper.setRequest(request); From 716d13576618eeb9258be9d6bdb185def40ae504 Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Thu, 13 Nov 2025 10:32:22 +0530 Subject: [PATCH 25/71] dateutil changes Signed-off-by: Nandhukumar --- .../java/io/mosip/print/util/AuditLogRequestBuilder.java | 9 +++++---- .../io/mosip/print/util/DigitalSignatureUtility.java | 3 ++- .../java/io/mosip/print/util/PrintExceptionHandler.java | 3 ++- src/main/java/io/mosip/print/util/TokenHandlerUtil.java | 7 ++++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/mosip/print/util/AuditLogRequestBuilder.java b/src/main/java/io/mosip/print/util/AuditLogRequestBuilder.java index 48e1f23b..1dfcbf88 100644 --- a/src/main/java/io/mosip/print/util/AuditLogRequestBuilder.java +++ b/src/main/java/io/mosip/print/util/AuditLogRequestBuilder.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import io.mosip.kernel.core.util.DateUtils2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -69,7 +70,7 @@ public ResponseWrapper createAuditRequestBuilder(String descri ResponseWrapper responseWrapper = new ResponseWrapper<>(); try { auditRequestDto.setDescription(description); - auditRequestDto.setActionTimeStamp(DateUtils.getUTCCurrentDateTimeString()); + auditRequestDto.setActionTimeStamp(DateUtils2.getUTCCurrentDateTimeString()); auditRequestDto.setApplicationId(AuditLogConstant.MOSIP_4.toString()); auditRequestDto.setApplicationName(AuditLogConstant.REGISTRATION_PROCESSOR.toString()); auditRequestDto.setCreatedBy(AuditLogConstant.SYSTEM.toString()); @@ -89,7 +90,7 @@ public ResponseWrapper createAuditRequestBuilder(String descri requestWrapper.setRequest(auditRequestDto); DateTimeFormatter format = DateTimeFormatter.ofPattern(env.getProperty(DATETIME_PATTERN)); LocalDateTime localdatetime = LocalDateTime - .parse(DateUtils.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); + .parse(DateUtils2.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); requestWrapper.setRequesttime(localdatetime); requestWrapper.setVersion(env.getProperty(REG_PROC_APPLICATION_VERSION)); responseWrapper = (ResponseWrapper) registrationProcessorRestService.postApi(apiname, "", @@ -123,7 +124,7 @@ public ResponseWrapper createAuditRequestBuilder(String descri auditRequestDto = new AuditRequestDto(); auditRequestDto.setDescription(description); - auditRequestDto.setActionTimeStamp(DateUtils.getUTCCurrentDateTimeString()); + auditRequestDto.setActionTimeStamp(DateUtils2.getUTCCurrentDateTimeString()); auditRequestDto.setApplicationId(AuditLogConstant.MOSIP_4.toString()); auditRequestDto.setApplicationName(AuditLogConstant.REGISTRATION_PROCESSOR.toString()); auditRequestDto.setCreatedBy(AuditLogConstant.SYSTEM.toString()); @@ -143,7 +144,7 @@ public ResponseWrapper createAuditRequestBuilder(String descri requestWrapper.setRequest(auditRequestDto); DateTimeFormatter format = DateTimeFormatter.ofPattern(env.getProperty(DATETIME_PATTERN)); LocalDateTime localdatetime = LocalDateTime - .parse(DateUtils.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); + .parse(DateUtils2.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); requestWrapper.setRequesttime(localdatetime); requestWrapper.setVersion(env.getProperty(REG_PROC_APPLICATION_VERSION)); responseWrapper = (ResponseWrapper) registrationProcessorRestService diff --git a/src/main/java/io/mosip/print/util/DigitalSignatureUtility.java b/src/main/java/io/mosip/print/util/DigitalSignatureUtility.java index f21f86dc..8850c248 100644 --- a/src/main/java/io/mosip/print/util/DigitalSignatureUtility.java +++ b/src/main/java/io/mosip/print/util/DigitalSignatureUtility.java @@ -4,6 +4,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import io.mosip.kernel.core.util.DateUtils2; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -53,7 +54,7 @@ public String getDigitalSignature(String data) { request.setMetadata(null); DateTimeFormatter format = DateTimeFormatter.ofPattern(env.getProperty(DATETIME_PATTERN)); LocalDateTime localdatetime = LocalDateTime - .parse(DateUtils.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); + .parse(DateUtils2.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN)), format); request.setRequesttime(localdatetime); request.setVersion(env.getProperty(REG_PROC_APPLICATION_VERSION)); diff --git a/src/main/java/io/mosip/print/util/PrintExceptionHandler.java b/src/main/java/io/mosip/print/util/PrintExceptionHandler.java index 3424751b..17106c97 100644 --- a/src/main/java/io/mosip/print/util/PrintExceptionHandler.java +++ b/src/main/java/io/mosip/print/util/PrintExceptionHandler.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import io.mosip.kernel.core.util.DateUtils2; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -167,7 +168,7 @@ private ResponseEntity buildPrintApiExceptionResponse(Exception e .collect(Collectors.toList()); response.setErrors(errors); } - response.setResponsetime(DateUtils.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN))); + response.setResponsetime(DateUtils2.getUTCCurrentDateTimeString(env.getProperty(DATETIME_PATTERN))); response.setVersion(env.getProperty(REG_PRINT_SERVICE_VERSION)); return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(response); diff --git a/src/main/java/io/mosip/print/util/TokenHandlerUtil.java b/src/main/java/io/mosip/print/util/TokenHandlerUtil.java index d9999f6b..78f6137c 100644 --- a/src/main/java/io/mosip/print/util/TokenHandlerUtil.java +++ b/src/main/java/io/mosip/print/util/TokenHandlerUtil.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.util.Map; +import io.mosip.kernel.core.util.DateUtils2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,12 +43,12 @@ public static boolean isValidBearerToken(String accessToken, String issuerUrl, S try { DecodedJWT decodedJWT = JWT.decode(accessToken); Map claims = decodedJWT.getClaims(); - LocalDateTime expiryTime = DateUtils - .convertUTCToLocalDateTime(DateUtils.getUTCTimeFromDate(decodedJWT.getExpiresAt())); + LocalDateTime expiryTime = DateUtils2 + .convertUTCToLocalDateTime(DateUtils2.getUTCTimeFromDate(decodedJWT.getExpiresAt())); if (!decodedJWT.getIssuer().equals(issuerUrl)) { return false; - } else if (!DateUtils.before(DateUtils.getUTCCurrentDateTime(), expiryTime)) { + } else if (!DateUtils2.before(DateUtils2.getUTCCurrentDateTime(), expiryTime)) { return false; } else if (!claims.get("clientId").asString().equals(clientId)) { return false; From 3668689e7e84eee4d36a542f4d9099ac63028fb6 Mon Sep 17 00:00:00 2001 From: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Date: Sat, 15 Nov 2025 18:55:47 +0530 Subject: [PATCH 26/71] [MOSIP-43631] updated helm charts Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- deploy/install.sh | 2 +- helm/print/.gitignore | 2 +- helm/print/Chart.yaml | 2 +- helm/print/templates/deployment.yaml | 1 + helm/print/values.yaml | 19 ++++++++++++++----- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 37e4ee6d..9f465cd9 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -8,7 +8,7 @@ fi NS=print -CHART_VERSION=1.3.0-beta.1-develop +CHART_VERSION=1.3.0-develop echo Create $NS namespace kubectl create ns $NS diff --git a/helm/print/.gitignore b/helm/print/.gitignore index b3c94bf6..f791801b 100644 --- a/helm/print/.gitignore +++ b/helm/print/.gitignore @@ -1,2 +1,2 @@ charts/ -Charts.lock +Chart.lock diff --git a/helm/print/Chart.yaml b/helm/print/Chart.yaml index 73fff895..f1211e79 100644 --- a/helm/print/Chart.yaml +++ b/helm/print/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: print-service description: A Helm chart for sample Print Service type: application -version: 1.3.0-beta.1-develop +version: 1.3.0-develop appVersion: "" dependencies: - name: common diff --git a/helm/print/templates/deployment.yaml b/helm/print/templates/deployment.yaml index 49a8ea25..2c852425 100644 --- a/helm/print/templates/deployment.yaml +++ b/helm/print/templates/deployment.yaml @@ -33,6 +33,7 @@ spec: {{- if .Values.hostAliases }} hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }} {{- end }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds | default 60 }} {{- if .Values.affinity }} affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }} {{- else }} diff --git a/helm/print/values.yaml b/helm/print/values.yaml index 2aab1541..a4748067 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -116,8 +116,8 @@ resources: cpu: 300m memory: 3000Mi requests: - cpu: 100m - memory: 1000Mi + cpu: 300m + memory: 3000Mi additionalResources: ## Specify any JAVA_OPTS string here. These typically will be specified in conjunction with above resources ## Example: java_opts: "-Xms500M -Xmx500M" @@ -191,7 +191,16 @@ podAnnotations: {} ## lifecycleHooks for the container to automate configuration before or after startup. ## -lifecycleHooks: {} +lifecycleHooks: + preStop: + exec: + command: + - sh + - -c + - sleep 30 + +## Termination grace perios : the maximum amount of time (in seconds) Kubernetes will wait for a container to gracefully shut down +terminationGracePeriodSeconds: 60 ## Custom Liveness probes for ## customLivenessProbe: {} @@ -277,8 +286,8 @@ volumePermissions: enabled: false image: registry: docker.io - repository: bitnami/bitnami-shell - tag: "10" + repository: mosipid/os-shell + tag: "12-debian-12-r46" pullPolicy: Always ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. From 4d520886cb31c98f8c011a267113b5a9051cffdc Mon Sep 17 00:00:00 2001 From: Gokulraj C <110164849+GOKULRAJ136@users.noreply.github.com> Date: Sat, 15 Nov 2025 20:56:37 +0530 Subject: [PATCH 27/71] Update values.yaml Signed-off-by: Gokulraj C <110164849+GOKULRAJ136@users.noreply.github.com> --- helm/print/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/print/values.yaml b/helm/print/values.yaml index a4748067..4bec2634 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -116,8 +116,8 @@ resources: cpu: 300m memory: 3000Mi requests: - cpu: 300m - memory: 3000Mi + cpu: 100m + memory: 1000Mi additionalResources: ## Specify any JAVA_OPTS string here. These typically will be specified in conjunction with above resources ## Example: java_opts: "-Xms500M -Xmx500M" From ca2473cff2ade8561948f5a48adb594b85ed1d56 Mon Sep 17 00:00:00 2001 From: Dhanendra Sahu Date: Sun, 16 Nov 2025 23:34:05 +0530 Subject: [PATCH 28/71] update tag version Signed-off-by: Dhanendra Sahu --- helm/print/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/print/values.yaml b/helm/print/values.yaml index 4bec2634..f18545e5 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -46,8 +46,8 @@ service: externalTrafficPolicy: Cluster image: registry: docker.io - repository: mosipid/print - tag: 1.3.0-beta.1 + repository: mosipqa/print + tag: 1.3.0-develop ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images From a8d88dc839ee9284a0f02546c31a63dd3c7aadd4 Mon Sep 17 00:00:00 2001 From: Dhanendra Sahu Date: Mon, 17 Nov 2025 00:07:54 +0530 Subject: [PATCH 29/71] Updated tag version Signed-off-by: Dhanendra Sahu --- helm/print/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/print/values.yaml b/helm/print/values.yaml index f18545e5..160b6b47 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -47,7 +47,7 @@ service: image: registry: docker.io repository: mosipqa/print - tag: 1.3.0-develop + tag: 1.3.x ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images From d93da275fb80e7606db20f6a84cac384a4001011 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Tue, 25 Nov 2025 17:19:09 +0530 Subject: [PATCH 30/71] Add THIRD-PARTY-NOTICES.txt with package details This file lists third-party packages used in the project along with their licenses, versions, and homepages. Signed-off-by: Rakshithasai123 --- THIRD-PARTY-NOTICES.txt | 192 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 THIRD-PARTY-NOTICES.txt diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt new file mode 100644 index 00000000..dfc09894 --- /dev/null +++ b/THIRD-PARTY-NOTICES.txt @@ -0,0 +1,192 @@ +THIRD-PARTY-NOTICES + +This project includes third-party packages that are distributed under various open-source licenses. Below is a list of packages and their associated licenses. + +================================================================================ +Package: Spring Boot & Spring Security +(Starter Cache, Starter Security, Starter Validation, Starter Data JPA, Starter Web, Starter Batch, Starter Test, Configuration Processor, Spring Boot Maven Plugin) +Version: 3.2.3 (most packages) or 3.x family +License: Apache License 2.0 +Homepage: [https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot) +================================================================================ + +================================================================================ +Package: SpringDoc OpenAPI +(SpringDoc OpenAPI Maven Plugin, SpringDoc OpenAPI Starter WebMVC UI) +Version: 0.2, 1.3, 1.4, 2.5.0 +License: Apache License 2.0 (Inferred from project’s official repository) +Homepage: [https://springdoc.org](https://springdoc.org) +================================================================================ + +================================================================================ +Package: PostgreSQL JDBC Driver (org.postgresql:postgresql) +Version: 42.2.2, 42.7.2 +License: BSD-2-Clause (Inferred from project’s official repository) +Homepage: [https://jdbc.postgresql.org/](https://jdbc.postgresql.org/) +================================================================================ + +================================================================================ +Package: JSON Simple (com.googlecode.json-simple:json-simple) +Version: (Not specified) +License: Apache License 2.0 (Inferred from project’s official repository) +Homepage: [https://code.google.com/archive/p/json-simple/](https://code.google.com/archive/p/json-simple/) +================================================================================ + +================================================================================ +Package: Central Publishing Maven Plugin (org.sonatype.central:central-publishing-maven-plugin) +Version: 0.7.0 +License: Apache License 2.0 +Homepage: [https://github.com/sonatype-nexus-community/central-publishing](https://github.com/sonatype-nexus-community/central-publishing) +================================================================================ + +================================================================================ +Package: Git Commit ID Maven Plugin (pl.project13.maven:git-commit-id-plugin) +Version: 3.0.1 +License: Apache License 2.0 (Inferred from project’s official repository) +Homepage: [https://github.com/git-commit-id/git-commit-id-maven-plugin](https://github.com/git-commit-id/git-commit-id-maven-plugin) +================================================================================ + +================================================================================ +Package: Apache Maven Plugins +(maven-resources-plugin, maven-shade-plugin, maven-surefire-plugin, maven-gpg-plugin, +maven-javadoc-plugin, maven-source-plugin, maven-jar-plugin, maven-war-plugin, maven-compiler-plugin, +maven-antrun-plugin) +Version: + - maven-resources-plugin: 3.3.1 + - maven-shade-plugin: 3.2.4 + - maven-surefire-plugin: 2.22.0 + - maven-gpg-plugin: 1.5 + - maven-javadoc-plugin: 3.2.0, 3.6.3 + - maven-source-plugin: 2.2.1 + - maven-jar-plugin: 3.0.2 + - maven-war-plugin: 3.1.0 + - maven-compiler-plugin: 3.8.0 + - maven-antrun-plugin: 3.0.0 +License: Apache License 2.0 +Homepage: [https://maven.apache.org/plugins/](https://maven.apache.org/plugins/) +================================================================================ + +================================================================================ +Package: JUnit (junit:junit, org.junit.vintage:junit-vintage-engine) +Version: 4.12 (JUnit), (not specified for vintage) +License: Eclipse Public License 1.0/2.0 (Inferred from project’s official repository) +Homepage: [https://junit.org/junit4/](https://junit.org/junit4/) +================================================================================ + +================================================================================ +Package: JaCoCo Maven Plugin (org.jacoco:jacoco-maven-plugin) +Version: 0.8.11 +License: Eclipse Public License 2.0 +Homepage: [https://www.eclemma.org/jacoco/](https://www.eclemma.org/jacoco/) +================================================================================ + +================================================================================ +Package: ModelMapper (org.modelmapper:modelmapper) +Version: (Not specified) +License: Apache License 2.0 +Homepage: [http://modelmapper.org/](http://modelmapper.org/) +================================================================================ + +================================================================================ +Package: Log4j API (org.apache.logging.log4j:log4j-api) +Version: (Not specified) +License: Apache License 2.0 +Homepage: [https://logging.apache.org/log4j/2.x/](https://logging.apache.org/log4j/2.x/) +================================================================================ + +================================================================================ +Package: Jackson (com.fasterxml.jackson.datatype:jackson-datatype-jsr310) +Version: (Not specified) +License: Apache License 2.0 +Homepage: [https://github.com/FasterXML/jackson-datatype-jsr310](https://github.com/FasterXML/jackson-datatype-jsr310) +================================================================================ + +================================================================================ +Package: MOSIP Kernel +(kernel-bom, kernel-core, kernel-logger-logback, kernel-auth-adapter, kernel-auditmanager-api, +kernel-keymanager-service, kernel-applicanttype-api, kernel-idvalidator-rid, kernel-idgenerator-machineid, +kernel-idgenerator-regcenterid, kernel-dataaccess-hibernate, kernel-openid-bridge-api, +kernel-datamapper-orika, kernel-cbeffutil-api, kernel-websubclient-api) +Version: 1.3.0-SNAPSHOT +License: Mozilla Public License 2.0 (Inferred from project’s official repository) +Homepage: [https://github.com/mosip/](https://github.com/mosip/) +================================================================================ + +================================================================================ +Package: SLF4J API (org.slf4j:slf4j-api, org.slf4j:jcl-over-slf4j, org.slf4j:jul-to-slf4j) +Version: 1.7.5 (for bridges), (not specified for API) +License: MIT License (Inferred from project’s official repository) +Homepage: [http://www.slf4j.org/](http://www.slf4j.org/) +================================================================================ + +================================================================================ +Package: H2 Database (com.h2database:h2) +Version: (Not specified) +License: EPL 1.0 (Inferred from project’s official repository) +Homepage: [https://www.h2database.com/](https://www.h2database.com/) +================================================================================ + +================================================================================ +Package: Jakarta Activation (jakarta.activation:jakarta.activation-api) +Version: 2.1.3 +License: BSD-3-Clause (Inferred from project’s official repository) +Homepage: [https://github.com/eclipse-ee4j/jaf](https://github.com/eclipse-ee4j/jaf) +================================================================================ + +================================================================================ +Package: Jakarta Persistence (jakarta.persistence:jakarta.persistence-api) +Version: 3.1.0 +License: BSD-3-Clause OR EPL-2.0 (Inferred from project’s official repository) +Homepage: [https://projects.eclipse.org/projects/ee4j.ja](https://projects.eclipse.org/projects/ee4j.ja) +================================================================================ + +================================================================================ +Package: Jakarta XML Bind (jakarta.xml.bind:jakarta.xml.bind-api, javax.xml.bind:jaxb-api) +Version: (Not specified) +License: Eclipse Public License 2.0 (Inferred from project’s official repository) +Homepage: [https://projects.eclipse.org/projects/ee4j.jaxb/](https://projects.eclipse.org/projects/ee4j.jaxb/) +================================================================================ + +================================================================================ +Package: Spring Batch Extensions (org.springframework.batch.extensions:spring-batch-excel) +Version: 0.1.0 +License: Apache License 2.0 (Inferred from project’s official repository) +Homepage: [https://github.com/kulmam92/spring-batch-excel](https://github.com/kulmam92/spring-batch-excel) +================================================================================ + +================================================================================ +Package: Mockito (org.mockito:mockito-core, org.mockito:mockito-inline) +Version: 3.11.2, 5.2.0 +License: MIT License (Inferred from project’s official repository) +Homepage: [https://site.mockito.org/](https://site.mockito.org/) +================================================================================ + +================================================================================ +Package: Powermock (org.powermock:powermock-api-mockito2, org.powermock:powermock-module-junit4) +Version: (Not specified) +License: Apache License 2.0 (Inferred from project’s official repository) +Homepage: [https://github.com/powermock/powermock](https://github.com/powermock/powermock) +================================================================================ + +================================================================================ +Package: Bouncy Castle (org.bouncycastle:bcutil-jdk18on) +Version: 1.78.1 +License: MIT License (Inferred from project’s official repository) +Homepage: [https://www.bouncycastle.org/](https://www.bouncycastle.org/) +================================================================================ + +================================================================================ +Package: Apache POI (org.apache.poi:poi-ooxml) +Version: 5.2.5 +License: Apache License 2.0 +Homepage: [https://poi.apache.org/](https://poi.apache.org/) +================================================================================ + +================================================================================ +Package: Google Gson (com.google.code.gson:gson) +Version: 2.10.1 +License: Apache License 2.0 +Homepage: [https://github.com/google/gson](https://github.com/google/gson) +================================================================================ + +Full license texts and additional details for each of the above packages are available in the license/ directory of this repository. Please refer to those files or the original source of each package for complete legal terms and conditions. From 23c23aff2e5e15012aa30246ffe90e4bb37e01fb Mon Sep 17 00:00:00 2001 From: Nandhukumar Date: Wed, 26 Nov 2025 15:34:42 +0530 Subject: [PATCH 31/71] MOSIP-43803 - removed commented out dependency in pom Signed-off-by: Nandhukumar --- pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pom.xml b/pom.xml index d65d0665..6bc40366 100644 --- a/pom.xml +++ b/pom.xml @@ -259,11 +259,6 @@ slf4j-api 2.0.13 - From 251ac0a4f7fa7e602dab2da2c5df03dedccc2aed Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Thu, 27 Nov 2025 18:00:21 +0530 Subject: [PATCH 32/71] Create Apache-2.0.txt Signed-off-by: Rakshithasai123 --- licenses /Apache-2.0.txt | 176 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 licenses /Apache-2.0.txt diff --git a/licenses /Apache-2.0.txt b/licenses /Apache-2.0.txt new file mode 100644 index 00000000..fe5d55c6 --- /dev/null +++ b/licenses /Apache-2.0.txt @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS From 9a13d8b6ff18317dd0ffe04a1fd24564d01f2298 Mon Sep 17 00:00:00 2001 From: kaledOu Date: Fri, 28 Nov 2025 16:14:49 +0100 Subject: [PATCH 33/71] fix cpu & memory Signed-off-by: kaledOu --- helm/print/values.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helm/print/values.yaml b/helm/print/values.yaml index 160b6b47..06baf219 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -116,12 +116,12 @@ resources: cpu: 300m memory: 3000Mi requests: - cpu: 100m - memory: 1000Mi + cpu: 150m + memory: 1800Mi additionalResources: ## Specify any JAVA_OPTS string here. These typically will be specified in conjunction with above resources ## Example: java_opts: "-Xms500M -Xmx500M" - javaOpts: "-Xms2250M -Xmx2250M" + javaOpts: "-Xms1350M -Xmx2250M" ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container ## Clamav container already runs as 'mosip' user, so we may not need to enable this containerSecurityContext: From 547d28a588b21a9e9bfde270aa6fbe3aff4eb394 Mon Sep 17 00:00:00 2001 From: rajapandi1234 <138785181+rajapandi1234@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:32:37 +0530 Subject: [PATCH 34/71] Create NOTICES.txt Signed-off-by: rajapandi1234 <138785181+rajapandi1234@users.noreply.github.com> --- NOTICES.txt | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 NOTICES.txt diff --git a/NOTICES.txt b/NOTICES.txt new file mode 100644 index 00000000..9c143eec --- /dev/null +++ b/NOTICES.txt @@ -0,0 +1,108 @@ +NOTICES + +This project includes components licensed under various open-source licenses. +Attributions and required notices for these components are provided below. + +------------------------------------------------------------------------------- +Project Copyright +------------------------------------------------------------------------------- +© 2024 This project includes software developed and maintained by its contributors. + +------------------------------------------------------------------------------- +MOSIP Copyright +------------------------------------------------------------------------------- +This project includes components from the MOSIP Platform: + + © 2022 Modular Open Source Identity Platform (MOSIP) + All rights reserved. + +MOSIP components are licensed under the Mozilla Public License 2.0 (MPL-2.0). +Original notices from MOSIP are retained as required by MPL-2.0. + +------------------------------------------------------------------------------- +Apache License 2.0 – Required Notice Attribution +------------------------------------------------------------------------------- +This project includes software licensed under the Apache License, Version 2.0. +You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + +Portions of this software are derived from or include contributions from: + • Spring Boot & Spring Security + • SpringDoc OpenAPI + • Apache Maven Plugins (compiler, jar, javadoc, surefire, source, + war, shade, resources, antrun, gpg) + • Git Commit ID Maven Plugin + • Central Publishing Maven Plugin + • ModelMapper + • Log4j API + • Jackson Datatype (JSR310) + • Spring Batch Extensions + • Apache POI + • Google Gson + • PowerMock + • AWS Java SDK (if applicable) + +------------------------------------------------------------------------------- +Mozilla Public License 2.0 – Required Notice Attribution +------------------------------------------------------------------------------- +This project includes components licensed under the MPL-2.0: + + • MOSIP Kernel Components: + - kernel-bom + - kernel-core + - kernel-logger-logback + - kernel-auth-adapter + - kernel-auditmanager-api + - kernel-keymanager-service + - kernel-applicanttype-api + - kernel-idvalidator-rid + - kernel-idgenerator-machineid + - kernel-idgenerator-regcenterid + - kernel-dataaccess-hibernate + - kernel-openid-bridge-api + - kernel-datamapper-orika + - kernel-cbeffutil-api + - kernel-websubclient-api + +These components are governed by the MPL-2.0 terms: + https://www.mozilla.org/MPL/2.0/ + +------------------------------------------------------------------------------- +BSD License Attribution (BSD-2-Clause / BSD-3-Clause) +------------------------------------------------------------------------------- +This project includes components licensed under BSD licenses: + + • PostgreSQL JDBC Driver (BSD-2-Clause) + • Jakarta Activation API (BSD-3-Clause) + • Jakarta Persistence API (BSD-3-Clause or EPL-2.0) + • Additional transitive BSD-licensed components + +------------------------------------------------------------------------------- +Eclipse Public License Attribution (EPL-1.0 / EPL-2.0) +------------------------------------------------------------------------------- +The following components are licensed under EPL: + + • JUnit (EPL-1.0 / EPL-2.0) + • JUnit Vintage Engine (EPL-2.0) + • JaCoCo Maven Plugin (EPL-2.0) + • Jakarta XML Bind API (EPL-2.0) + • H2 Database (dual-licensed EPL-1.0 / MPL-2.0) + +------------------------------------------------------------------------------- +MIT License Attribution +------------------------------------------------------------------------------- +This project includes MIT-licensed software: + + • SLF4J (slf4j-api, jcl-over-slf4j, jul-to-slf4j) + • Mockito (mockito-core, mockito-inline) + • Bouncy Castle (bcutil-jdk18on) + +------------------------------------------------------------------------------- +Additional Notes +------------------------------------------------------------------------------- +Some components may include their own NOTICE files. Where applicable, those +notices have been preserved as required. + +Full license texts for each dependency are available in the `license/` +directory of this repository. From b95445ba4b5960aa10660f73cb759b92ffa9183a Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Wed, 3 Dec 2025 21:51:46 +0530 Subject: [PATCH 35/71] Update THIRD-PARTY-NOTICES.txt Signed-off-by: Rakshithasai123 --- THIRD-PARTY-NOTICES.txt | 132 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 3 deletions(-) diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index dfc09894..f3c392b3 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -114,7 +114,7 @@ Homepage: [https://github.com/mosip/](https://github.com/mosip/) ================================================================================ Package: SLF4J API (org.slf4j:slf4j-api, org.slf4j:jcl-over-slf4j, org.slf4j:jul-to-slf4j) -Version: 1.7.5 (for bridges), (not specified for API) +Version: 2.0.13 (for bridges), (not specified for API) License: MIT License (Inferred from project’s official repository) Homepage: [http://www.slf4j.org/](http://www.slf4j.org/) ================================================================================ @@ -122,8 +122,8 @@ Homepage: [http://www.slf4j.org/](http://www.slf4j.org/) ================================================================================ Package: H2 Database (com.h2database:h2) Version: (Not specified) -License: EPL 1.0 (Inferred from project’s official repository) -Homepage: [https://www.h2database.com/](https://www.h2database.com/) +License: MPL 2.0 OR EPL 1.0 (dual-licensed) +Homepage: https://www.h2database.com/ ================================================================================ ================================================================================ @@ -189,4 +189,130 @@ License: Apache License 2.0 Homepage: [https://github.com/google/gson](https://github.com/google/gson) ================================================================================ +================================================================================ +Package: iText 7 Core (com.itextpdf:itext7-core) +Version: 7.1.0 +License: GNU Affero General Public License v3.0 (AGPL-3.0) +Homepage: https://itextpdf.com/ +================================================================================ + +================================================================================ +Package: iText HTML to PDF (com.itextpdf:html2pdf) +Version: 2.0.0 +License: GNU Affero General Public License v3.0 (AGPL-3.0) +Homepage: https://itextpdf.com/ +================================================================================ + +================================================================================ +Package: iText 5 PDF Library (com.itextpdf:itextpdf) +Version: 5.5.13.3 +License: GNU Affero General Public License v3.0 (AGPL-3.0) +Homepage: https://itextpdf.com/ +================================================================================ + +================================================================================ +Package: iText XML Worker (com.itextpdf.tool:xmlworker) +Version: 5.5.13.3 +License: GNU Affero General Public License v3.0 (AGPL-3.0) +Homepage: https://itextpdf.com/ +================================================================================ + +================================================================================ +Package: Apache Commons IO (commons-io:commons-io) +Version: 2.6 +License: Apache License 2.0 +Homepage: https://commons.apache.org/proper/commons-io/ +================================================================================ + +================================================================================ +Package: Apache Commons Lang (org.apache.commons:commons-lang3) +Version: Not specified +License: Apache License 2.0 +Homepage: https://commons.apache.org/proper/commons-lang/ +================================================================================ + +================================================================================ +Package: Apache Commons Codec (commons-codec:commons-codec) +Version: 1.11 +License: Apache License 2.0 +Homepage: https://commons.apache.org/proper/commons-codec/ +================================================================================ + +================================================================================ +Package: Apache Velocity (org.apache.velocity:velocity) +Version: 1.7 +License: Apache License 2.0 +Homepage: https://velocity.apache.org/ +================================================================================ + +================================================================================ +Package: Apache Velocity Tools (org.apache.velocity:velocity-tools) +Version: 2.0 +License: Apache License 2.0 +Homepage: https://velocity.apache.org/tools/ +================================================================================ + +================================================================================ +Package: Joda-Time (joda-time:joda-time) +Version: 2.8.1 +License: Apache License 2.0 +Homepage: https://www.joda.org/joda-time/ +================================================================================ + +================================================================================ +Package: ZXing Java SE (com.google.zxing:javase) +Version: 3.4.1 +License: Apache License 2.0 +Homepage: https://github.com/zxing/zxing +================================================================================ + +================================================================================ +Package: Apache HttpClient (org.apache.httpcomponents:httpclient) +Version: Not specified +License: Apache License 2.0 +Homepage: https://hc.apache.org/ +================================================================================ + +================================================================================ +Package: Jackson Core (com.fasterxml.jackson.core:jackson-core) +Version: Not specified +License: Apache License 2.0 +Homepage: https://github.com/FasterXML/jackson-core +================================================================================ + +================================================================================ +Package: Jackson Annotations (com.fasterxml.jackson.core:jackson-annotations) +Version: Not specified +License: Apache License 2.0 +Homepage: https://github.com/FasterXML/jackson-annotations +================================================================================ + +================================================================================ +Package: Jackson Databind (com.fasterxml.jackson.core:jackson-databind) +Version: Not specified +License: Apache License 2.0 +Homepage: https://github.com/FasterXML/jackson-databind +================================================================================ + +================================================================================ +Package: Spring Cloud Starter Config +Version: Not specified +License: Apache License 2.0 +Homepage: https://spring.io/projects/spring-cloud-config +================================================================================ + +================================================================================ +Package: Javax Activation (javax.activation:activation) +Version: Not specified +License: CDDL 1.0 OR GPL-2.0 with Classpath Exception +Homepage: https://javaee.github.io/javactivation/ +================================================================================ + +================================================================================ +Package: MOSIP VerCred Verifier (io.mosip.vercred:vcverifier) +Version: 1.0.0 +License: Mozilla Public License 2.0 +Homepage: https://github.com/mosip/credential-service +================================================================================ + Full license texts and additional details for each of the above packages are available in the license/ directory of this repository. Please refer to those files or the original source of each package for complete legal terms and conditions. From b12cf10f9fd4597105f3a17c67a329f62e4502cc Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Wed, 3 Dec 2025 22:00:40 +0530 Subject: [PATCH 36/71] Update THIRD-PARTY-NOTICES.txt Signed-off-by: Rakshithasai123 --- THIRD-PARTY-NOTICES.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index f3c392b3..83b4f0e1 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -42,8 +42,8 @@ Homepage: [https://github.com/sonatype-nexus-community/central-publishing](https ================================================================================ Package: Git Commit ID Maven Plugin (pl.project13.maven:git-commit-id-plugin) Version: 3.0.1 -License: Apache License 2.0 (Inferred from project’s official repository) -Homepage: [https://github.com/git-commit-id/git-commit-id-maven-plugin](https://github.com/git-commit-id/git-commit-id-maven-plugin) +License: GNU Lesser General Public License v3.0 (LGPL-3.0) +Homepage: https://github.com/git-commit-id/git-commit-id-maven-plugin ================================================================================ ================================================================================ @@ -141,10 +141,10 @@ Homepage: [https://projects.eclipse.org/projects/ee4j.ja](https://projects.eclip ================================================================================ ================================================================================ -Package: Jakarta XML Bind (jakarta.xml.bind:jakarta.xml.bind-api, javax.xml.bind:jaxb-api) +Package: Jakarta XML Bind (jakarta.xml.bind:jakarta.xml.bind-api) Version: (Not specified) -License: Eclipse Public License 2.0 (Inferred from project’s official repository) -Homepage: [https://projects.eclipse.org/projects/ee4j.jaxb/](https://projects.eclipse.org/projects/ee4j.jaxb/) +License: BSD-3-Clause (EDL 1.0) +Homepage: https://projects.eclipse.org/projects/ee4j.jaxb/ ================================================================================ ================================================================================ From f2518ba2b8de2386ac9a77e36e98ab87b4003508 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Thu, 4 Dec 2025 11:59:08 +0530 Subject: [PATCH 37/71] Update THIRD-PARTY-NOTICES.txt Signed-off-by: Rakshithasai123 --- THIRD-PARTY-NOTICES.txt | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 83b4f0e1..acc11b1d 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -169,10 +169,10 @@ Homepage: [https://github.com/powermock/powermock](https://github.com/powermock/ ================================================================================ ================================================================================ -Package: Bouncy Castle (org.bouncycastle:bcutil-jdk18on) -Version: 1.78.1 -License: MIT License (Inferred from project’s official repository) -Homepage: [https://www.bouncycastle.org/](https://www.bouncycastle.org/) +Package: Bouncy Castle (org.bouncycastle:bcprov-jdk15on, org.bouncycastle:bcpkix-jdk15on) +Version: 1.66 +License: MIT License +Homepage: https://www.bouncycastle.org/ ================================================================================ ================================================================================ @@ -302,10 +302,10 @@ Homepage: https://spring.io/projects/spring-cloud-config ================================================================================ ================================================================================ -Package: Javax Activation (javax.activation:activation) -Version: Not specified -License: CDDL 1.0 OR GPL-2.0 with Classpath Exception -Homepage: https://javaee.github.io/javactivation/ +Package: JAXB API (javax.xml.bind:jaxb-api) +Version: (Specify version from pom.xml) +License: CDDL 1.1 OR GPL-2.0 with Classpath Exception +Homepage: https://javaee.github.io/jaxb-v2/ ================================================================================ ================================================================================ @@ -315,4 +315,26 @@ License: Mozilla Public License 2.0 Homepage: https://github.com/mosip/credential-service ================================================================================ +================================================================================ +Package: JSON in Java (org.json:json) +Version: (Specify version from pom.xml) +License: JSON License ("shall be used for Good, not Evil") +Homepage: https://github.com/stleary/JSON-java +================================================================================ + +================================================================================ +Package: Project Lombok (org.projectlombok:lombok) +Version: (Specify version from pom.xml) +License: MIT License +Homepage: https://projectlombok.org/ +================================================================================ + +================================================================================ +Package: Glassfish JAXB Runtime (org.glassfish.jaxb:jaxb-runtime) +Version: (Specify version from pom.xml) +License: Eclipse Distribution License 1.0 (BSD-3-Clause) +Homepage: https://eclipse-ee4j.github.io/jaxb-ri/ +================================================================================ + + Full license texts and additional details for each of the above packages are available in the license/ directory of this repository. Please refer to those files or the original source of each package for complete legal terms and conditions. From 75bbd9fd2caa591cc9dea70f6acb92968c31774b Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Thu, 4 Dec 2025 12:27:14 +0530 Subject: [PATCH 38/71] Update THIRD-PARTY-NOTICES.txt Signed-off-by: Rakshithasai123 --- THIRD-PARTY-NOTICES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index acc11b1d..b7889bfe 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -336,5 +336,11 @@ License: Eclipse Distribution License 1.0 (BSD-3-Clause) Homepage: https://eclipse-ee4j.github.io/jaxb-ri/ ================================================================================ +================================================================================ +Package: JavaBeans Activation Framework (javax.activation:activation) +Version: (Specify version from pom.xml) +License: CDDL 1.0 OR GPL-2.0 with Classpath Exception +Homepage: https://javaee.github.io/javactivation/ +================================================================================ Full license texts and additional details for each of the above packages are available in the license/ directory of this repository. Please refer to those files or the original source of each package for complete legal terms and conditions. From ca72c8df8f896a83b256931f801ebb14e42f9143 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Fri, 5 Dec 2025 20:06:40 +0530 Subject: [PATCH 39/71] Create NOTICES.txt Signed-off-by: Rakshithasai123 --- licenses/NOTICES.txt | 359 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 licenses/NOTICES.txt diff --git a/licenses/NOTICES.txt b/licenses/NOTICES.txt new file mode 100644 index 00000000..76e70204 --- /dev/null +++ b/licenses/NOTICES.txt @@ -0,0 +1,359 @@ +================================================================================ +COPYRIGHT NOTICES +================================================================================ + +This software incorporates components from the projects listed below. The +original copyright notices have been preserved in accordance with the terms +of their respective licenses. + +================================================================================ +APACHE LICENSE 2.0 COMPONENTS +================================================================================ + +Spring Framework & Spring Boot + Copyright © 2012-2024 VMware, Inc. + Copyright © 2012-2024 Pivotal Software, Inc. + Licensed under the Apache License, Version 2.0 + +Spring Cloud Starter Config + Copyright © 2012-2024 VMware, Inc. + Licensed under the Apache License, Version 2.0 + +SpringDoc OpenAPI + Copyright © 2019-2024 springdoc.org + Licensed under the Apache License, Version 2.0 + +Spring Batch Excel Extensions + Copyright © 2006-2025 the original author or authors + Licensed under the Apache License, Version 2.0 + +JSON Simple (com.googlecode.json-simple:json-simple) + Copyright © 2006 Yidong Fang + Licensed under the Apache License, Version 2.0 + +Google Gson + Copyright © 2008 Google Inc. + Licensed under the Apache License, Version 2.0 + +Jackson (jackson-core, jackson-databind, jackson-annotations, jackson-datatype-jsr310) + Copyright © 2007- Tatu Saloranta (tatu.saloranta@iki.fi) + Licensed under the Apache License, Version 2.0 + +Apache Maven Plugins + (maven-compiler-plugin, maven-surefire-plugin, maven-resources-plugin, + maven-shade-plugin, maven-gpg-plugin, maven-javadoc-plugin, + maven-source-plugin, maven-jar-plugin, maven-war-plugin, maven-antrun-plugin) + Copyright © 2001-2024 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Apache Commons IO + Copyright © 2002-2017 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Apache Commons Lang + Copyright © 2001-2024 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Apache Commons Codec + Copyright © 2002-2024 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Apache Velocity + Copyright © 2000-2007 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Apache Velocity Tools + Copyright © 2000-2007 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Apache HttpClient + Copyright © 1999-2024 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Apache POI + Copyright © 2001-2025 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +Joda-Time + Copyright © 2001-2023 Stephen Colebourne + Licensed under the Apache License, Version 2.0 + +Google ZXing (Java SE) + Copyright © 2010 ZXing + Copyright © 2015 ZXing + Licensed under the Apache License, Version 2.0 + +ModelMapper + Copyright © 2011-2025 the original author or authors + Licensed under the Apache License, Version 2.0 + +Apache Log4j API + Copyright © 1999-2024 The Apache Software Foundation + Licensed under the Apache License, Version 2.0 + +PowerMock + Copyright © 2008 the original author or authors + Licensed under the Apache License, Version 2.0 + +Sonatype Central Publishing Maven Plugin + Copyright © 2022-present Sonatype, Inc. + Licensed under the Apache License, Version 2.0 + +================================================================================ +MIT LICENSE COMPONENTS +================================================================================ + +SLF4J (slf4j-api, jcl-over-slf4j, jul-to-slf4j) + Copyright © 2004-2025 QOS.ch Sarl (Switzerland) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + +Project Lombok + Copyright © 2009-2015 The Project Lombok Authors + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + +Bouncy Castle (bcprov-jdk15on, bcpkix-jdk15on) + Copyright © 2000-2023 The Legion of the Bouncy Castle Inc. + (https://www.bouncycastle.org) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + +Mockito (mockito-core, mockito-inline) + Copyright © 2007 Mockito contributors + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + +================================================================================ +ECLIPSE PUBLIC LICENSE (EPL) COMPONENTS +================================================================================ + +JUnit (junit:junit, org.junit.vintage:junit-vintage-engine) + Copyright © 2002-2024 JUnit Contributors + All rights reserved. + + This program and the accompanying materials are made available under + the terms of the Eclipse Public License v1.0/v2.0 which accompanies this + distribution, and is available at + http://www.eclipse.org/legal/epl-v10.html + https://www.eclipse.org/legal/epl-v20.html + +JaCoCo Maven Plugin + Copyright © 2009-2023 Mountainminds GmbH & Co. KG and Contributors + All rights reserved. + + This program and the accompanying materials are made available under + the terms of the Eclipse Public License v2.0 which accompanies this + distribution, and is available at + https://www.eclipse.org/legal/epl-v20.html + +================================================================================ +GNU LESSER GENERAL PUBLIC LICENSE (LGPL) COMPONENTS +================================================================================ + +SonarQube Scanner Maven Plugin + Copyright © 2011-2025 SonarSource SA + mailto:info AT sonarsource DOT com + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + +git-commit-id-plugin + Copyright © 2010-2024 Konrad Malawski and Contributors + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + +================================================================================ +GNU AFFERO GENERAL PUBLIC LICENSE (AGPL-3.0) COMPONENTS +================================================================================ + +⚠️ CRITICAL: The following components are licensed under AGPL-3.0, which requires +you to provide source code to all users who interact with the software and +license any derivative works under AGPL-3.0. + +iText 7 Core (com.itextpdf:itext7-core) + Copyright © 1998-2023 Apryse Group NV + Authors: Apryse Software + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License version 3 + as published by the Free Software Foundation. + + For more information, please contact iText Software at: + sales@itextpdf.com + +iText HTML to PDF (com.itextpdf:html2pdf) + Copyright © 1998-2023 Apryse Group NV + Licensed under AGPL-3.0 + +iText 5 PDF Library (com.itextpdf:itextpdf) + Copyright © 1998-2023 Apryse Group NV + Licensed under AGPL-3.0 + +iText XML Worker (com.itextpdf.tool:xmlworker) + Copyright © 1998-2023 Apryse Group NV + Licensed under AGPL-3.0 + +================================================================================ +MOZILLA PUBLIC LICENSE 2.0 (MPL-2.0) COMPONENTS +================================================================================ + +MOSIP Kernel + (kernel-bom, kernel-core, kernel-logger-logback, kernel-auth-adapter, + kernel-auditmanager-api, kernel-keymanager-service, kernel-applicanttype-api, + kernel-idvalidator-rid, kernel-idgenerator-machineid, kernel-idgenerator-regcenterid, + kernel-dataaccess-hibernate, kernel-openid-bridge-api, kernel-datamapper-orika, + kernel-cbeffutil-api, kernel-websubclient-api) + Copyright © 2018-2024 MOSIP + + 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 http://mozilla.org/MPL/2.0/. + +MOSIP VerCred Verifier (io.mosip.vercred:vcverifier) + Copyright © 2018-2024 MOSIP + Licensed under Mozilla Public License 2.0 + +H2 Database + Copyright © 2004-2024 Thomas Mueller + + H2 is dual licensed and available under the MPL 2.0 (Mozilla Public + License Version 2.0) or under the EPL 1.0 (Eclipse Public License). + +================================================================================ +BSD LICENSE COMPONENTS +================================================================================ + +PostgreSQL JDBC Driver + Copyright © 1997-2024 PostgreSQL Global Development Group + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Licensed under the BSD 2-Clause License + +Jakarta Activation API + Copyright © 2018-2024 Oracle and/or its affiliates + Copyright © 2018-2024 Eclipse Foundation + Licensed under BSD-3-Clause (Eclipse Distribution License 1.0) + +Jakarta Persistence API + Copyright © 2018-2024 Oracle and/or its affiliates + Copyright © 2018-2024 Eclipse Foundation + Licensed under BSD-3-Clause OR EPL-2.0 + +Jakarta XML Bind API + Copyright © 2018-2024 Oracle and/or its affiliates + Copyright © 2018-2024 Eclipse Foundation + Licensed under BSD-3-Clause (Eclipse Distribution License 1.0) + +================================================================================ +CDDL / GPL LICENSE COMPONENTS +================================================================================ + +JAXB API (javax.xml.bind:jaxb-api) + Copyright © 2003-2024 Oracle and/or its affiliates + Licensed under CDDL 1.1 OR GPL-2.0 with Classpath Exception + +JavaBeans Activation Framework (javax.activation:activation) + Copyright © 1997-2024 Oracle and/or its affiliates + Licensed under CDDL 1.0 OR GPL-2.0 with Classpath Exception + +Glassfish JAXB Runtime + Copyright © 2018-2024 Oracle and/or its affiliates + Copyright © 2018-2024 Eclipse Foundation + Licensed under Eclipse Distribution License 1.0 (BSD-3-Clause) + +================================================================================ +JSON LICENSE / PUBLIC DOMAIN COMPONENTS +================================================================================ + +JSON in Java (org.json:json) + Copyright © 2002-2024 JSON.org + + Note: Historical versions (before September 2022) used the JSON License with + "The Software shall be used for Good, not Evil" clause. Versions from + September 2022 onwards (20220924+) are released as Public Domain. + + If using version 20220924 or later: Public Domain + If using earlier versions: JSON License + +================================================================================ + +For complete license texts, please refer to the respective project homepages +listed in the THIRD-PARTY-NOTICES.txt file, or see the license/ directory +of this repository. + +IMPORTANT COMPLIANCE NOTES: +- AGPL-3.0 (iText): Requires source code disclosure and derivative works + must be licensed under AGPL-3.0. Consider obtaining commercial license. +- LGPL-3.0 (SonarQube Scanner, git-commit-id-plugin): Modifications to + these libraries must be released under LGPL-3.0. +- All licenses require retention of copyright notices in distributions. + +================================================================================ From a1682b9b0339e8546a8bc7f0a3078f20dab371e7 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 12:59:06 +0530 Subject: [PATCH 40/71] =?UTF-8?q?Create=20MPL-2.0.txt=E2=80=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rakshithasai123 --- "licenses /MPL-2.0.txt\342\200\216" | 151 ++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 "licenses /MPL-2.0.txt\342\200\216" diff --git "a/licenses /MPL-2.0.txt\342\200\216" "b/licenses /MPL-2.0.txt\342\200\216" new file mode 100644 index 00000000..b3b8d93f --- /dev/null +++ "b/licenses /MPL-2.0.txt\342\200\216" @@ -0,0 +1,151 @@ +Mozilla Public License +Version 2.0 +1. Definitions +1.1. “Contributor” +means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. + +1.2. “Contributor Version” +means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” +means Covered Software of a particular Contributor. + +1.4. “Covered Software” +means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. + +1.5. “Incompatible With Secondary Licenses” +means + +that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or + +that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. + +1.6. “Executable Form” +means any form of the work other than Source Code Form. + +1.7. “Larger Work” +means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. + +1.8. “License” +means this document. + +1.9. “Licensable” +means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. + +1.10. “Modifications” +means any of the following: + +any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or + +any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor +means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. + +1.12. “Secondary License” +means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” +means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) +means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. + +2. License Grants and Conditions +2.1. Grants +Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: + +under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and + +under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. + +2.2. Effective Date +The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. + +2.3. Limitations on Grant Scope +The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: + +for any code that a Contributor has removed from Covered Software; or + +for infringements caused by: (i) Your and any other third party’s modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or + +under Patent Claims infringed by Covered Software in the absence of its Contributions. + +This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). + +2.4. Subsequent Licenses +No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). + +2.5. Representation +Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use +This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. + +3. Responsibilities +3.1. Distribution of Source Form +All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form +If You distribute Covered Software in Executable Form then: + +such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and + +You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients’ rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work +You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). + +3.4. Notices +You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms +You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. + +5. Termination +5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. + +6. Disclaimer of Warranty +Covered Software is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. + +7. Limitation of Liability +Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party’s negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation +Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous +This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. + +10. Versions of the License +10.1. New Versions +Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. + +10.2. Effect of New Versions +You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. + +10.3. Modified Versions +If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses +If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +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/. + +If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice +This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0. From b815db5c03bfaef9bee8a4794706d03f688ecb43 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 12:59:39 +0530 Subject: [PATCH 41/71] Create EPL-1.0.txt Signed-off-by: Rakshithasai123 --- licenses /EPL-1.0.txt | 86 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 licenses /EPL-1.0.txt diff --git a/licenses /EPL-1.0.txt b/licenses /EPL-1.0.txt new file mode 100644 index 00000000..b0476ee9 --- /dev/null +++ b/licenses /EPL-1.0.txt @@ -0,0 +1,86 @@ +Eclipse Public License - v 1.0 +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and + +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. + +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within the Program. + +Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. From eb4fba7c4e78281c9a373de8ab4fe45d9e01173c Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 13:00:14 +0530 Subject: [PATCH 42/71] Create EPL-2.0.txt Signed-off-by: Rakshithasai123 --- licenses /EPL-2.0.txt | 80 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 licenses /EPL-2.0.txt diff --git a/licenses /EPL-2.0.txt b/licenses /EPL-2.0.txt new file mode 100644 index 00000000..78756b5e --- /dev/null +++ b/licenses /EPL-2.0.txt @@ -0,0 +1,80 @@ +Eclipse Public License - v 2.0 +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS +“Contribution” means: + +a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. +“Contributor” means any person or entity that Distributes the Program. + +“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +“Program” means the Contributions Distributed in accordance with this Agreement. + +“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. + +“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. + +“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. + +“Distribute” means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. + +“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. + +“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. + +2. GRANT OF RIGHTS +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). +3. REQUIREMENTS +3.1 If a Contributor Distributes the Program in any form, then: + +a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and +b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: +i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and +iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. +3.2 When the Program is Distributed as Source Code: + +a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and +b) a copy of this Agreement must be included with each copy of the Program. +3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (‘notices’) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. + +Exhibit A – Form of Secondary Licenses Notice +“This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” + +Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. + +If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. From 196d341bcd7bbdb7ec1f657e75947a58eea129a8 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 13:00:49 +0530 Subject: [PATCH 43/71] Create BSD-2.0.txt Signed-off-by: Rakshithasai123 --- licenses /BSD-2.0.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 licenses /BSD-2.0.txt diff --git a/licenses /BSD-2.0.txt b/licenses /BSD-2.0.txt new file mode 100644 index 00000000..4564dacc --- /dev/null +++ b/licenses /BSD-2.0.txt @@ -0,0 +1,11 @@ +BSD 2-Clause "Simplified" License + +Copyright © 2025 + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From a78f943d39e97859cd7f35735c84b2c455faf06c Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 13:01:22 +0530 Subject: [PATCH 44/71] =?UTF-8?q?Create=20BSD-3-Clause.txt=E2=80=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rakshithasai123 --- "licenses /BSD-3-Clause.txt\342\200\216" | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 "licenses /BSD-3-Clause.txt\342\200\216" diff --git "a/licenses /BSD-3-Clause.txt\342\200\216" "b/licenses /BSD-3-Clause.txt\342\200\216" new file mode 100644 index 00000000..2f67e633 --- /dev/null +++ "b/licenses /BSD-3-Clause.txt\342\200\216" @@ -0,0 +1,9 @@ +BSD-3-Clause +--------------------- +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From f2ab7ddf197b67f63c885c5c49225d17ea056ebc Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 13:02:11 +0530 Subject: [PATCH 45/71] =?UTF-8?q?Create=20AGPL-3.0-only.txt=E2=80=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rakshithasai123 --- "licenses /AGPL-3.0-only.txt\342\200\216" | 235 ++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 "licenses /AGPL-3.0-only.txt\342\200\216" diff --git "a/licenses /AGPL-3.0-only.txt\342\200\216" "b/licenses /AGPL-3.0-only.txt\342\200\216" new file mode 100644 index 00000000..0c97efd2 --- /dev/null +++ "b/licenses /AGPL-3.0-only.txt\342\200\216" @@ -0,0 +1,235 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. + +A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. + +The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. + +An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. + +The precise terms and conditions for copying, distribution and modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the Program. + +To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. + +A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . From ad8f0d939568430c8b08b54d13ef57b6a9ad9d23 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 13:02:42 +0530 Subject: [PATCH 46/71] Create LGPL-3.0-only.txt Signed-off-by: Rakshithasai123 --- licenses /LGPL-3.0-only.txt | 304 ++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 licenses /LGPL-3.0-only.txt diff --git a/licenses /LGPL-3.0-only.txt b/licenses /LGPL-3.0-only.txt new file mode 100644 index 00000000..513d1c01 --- /dev/null +++ b/licenses /LGPL-3.0-only.txt @@ -0,0 +1,304 @@ +GNU LESSER GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. + +"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. + +An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. + +A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. + +The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. +You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. +If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: + + a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. +The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license document. + +4. Combined Works. +You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license document. + + c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. + + e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) + +5. Combined Libraries. +You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. +The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . From a449971af73b0d6d87ff932ede1ce6d9c7cc0a9a Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 13:03:09 +0530 Subject: [PATCH 47/71] =?UTF-8?q?Create=20JSON.txt=E2=80=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rakshithasai123 --- "licenses /JSON.txt\342\200\216" | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 "licenses /JSON.txt\342\200\216" diff --git "a/licenses /JSON.txt\342\200\216" "b/licenses /JSON.txt\342\200\216" new file mode 100644 index 00000000..e29500b0 --- /dev/null +++ "b/licenses /JSON.txt\342\200\216" @@ -0,0 +1,11 @@ +JSON License + +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 4fa488d1eeb01e5f2789a3c74beaa507bb1835b2 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Mon, 8 Dec 2025 13:03:59 +0530 Subject: [PATCH 48/71] =?UTF-8?q?Create=20MIT.txt=E2=80=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rakshithasai123 --- "licenses /MIT.txt\342\200\216" | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 "licenses /MIT.txt\342\200\216" diff --git "a/licenses /MIT.txt\342\200\216" "b/licenses /MIT.txt\342\200\216" new file mode 100644 index 00000000..0766c118 --- /dev/null +++ "b/licenses /MIT.txt\342\200\216" @@ -0,0 +1,9 @@ +The MIT License (MIT) +--------------------------- +Copyright © 2025 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From f76c4b1e1f9fa1afecc27195dc01f917a5ccde91 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Tue, 9 Dec 2025 10:13:39 +0530 Subject: [PATCH 49/71] Rename NOTICES.txt to NOTICE Signed-off-by: Rakshithasai123 --- licenses/{NOTICES.txt => NOTICE} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename licenses/{NOTICES.txt => NOTICE} (100%) diff --git a/licenses/NOTICES.txt b/licenses/NOTICE similarity index 100% rename from licenses/NOTICES.txt rename to licenses/NOTICE From 69fad02ff018c0bf352a1ff5004abdb1d5ff54cb Mon Sep 17 00:00:00 2001 From: Chetan Kumar Hirematha Date: Tue, 9 Dec 2025 11:43:37 +0530 Subject: [PATCH 50/71] [MOSIP-42190] : Sonar coverage, Taken test cases from develop (#299) * Merge pull request #279 from chetankhcy/develop [MOSIP-42190] added test cases to increase code coverage Signed-off-by: Chetan Kumar Hirematha * Merge pull request #280 from chetankh239/MOSIP-42190-dev-cover [MOSIP-42190]: Added some test classes Signed-off-by: Chetan Kumar Hirematha * [MOSIP-42190] : Sonar coverage, Taken test cases from develop Signed-off-by: Chetan Kumar Hirematha * Add maven-deploy-plugin configuration to pom.xml Signed-off-by: Chetan Kumar Hirematha --------- Signed-off-by: Chetan Kumar Hirematha Co-authored-by: Dhanendra Sahu <60607841+dhanendra06@users.noreply.github.com> --- pom.xml | 80 +- .../print/{test => }/TestBootApplication.java | 2 +- .../controller/PrintControllerTest.java | 6 +- .../io/mosip/print/entity/BDBInfoTest.java | 168 ++++ .../io/mosip/print/entity/BIRInfoTest.java | 90 ++ .../java/io/mosip/print/entity/BIRTest.java | 267 +++++ .../print/entity/BiometricRecordTest.java | 57 ++ .../mosip/print/entity/MatchDecisionTest.java | 50 + .../print/entity/RegistryIDTypeTest.java | 42 + .../io/mosip/print/entity/SBIInfoTest.java | 73 ++ .../mosip/print/entity/VersionTypeTest.java | 89 ++ .../print/service/impl/BioApiImplTest.java | 222 +++++ .../service/impl/CbeffContainerImplTest.java | 227 +++++ .../print/service/impl/CbeffImplTest.java | 270 ++++++ .../service/impl/PDFGeneratorImplTest.java | 576 +++++++++++ .../impl/PrintRestClientServiceImplTest.java | 449 +++++++++ .../service/impl/PrintServiceImplTest.java | 858 ++++++++++++++++ .../service/impl/PrintServiceTest.java | 5 +- .../service/impl/QrcodeGeneratorTest.java | 6 +- .../impl/TemplateManagerBuilderImplTest.java | 319 ++++++ .../service/impl/TemplateManagerImplTest.java | 390 ++++++++ .../util/AuditLogRequestBuilderTest.java | 309 ++++++ .../mosip/print/util/Base64AdapterTest.java | 321 ++++++ .../print/util/CbeffToBiometricUtilTest.java | 501 ++++++++++ .../mosip/print/util/CbeffValidatorTest.java | 724 ++++++++++++++ .../mosip/print/util/CryptoCoreUtilTest.java | 578 +++++++++++ .../io/mosip/print/util/CryptoUtilTest.java | 706 ++++++++++++++ .../mosip/print/util/DataShareUtilTest.java | 471 +++++++++ .../io/mosip/print/util/DateAdapterTest.java | 348 +++++++ .../io/mosip/print/util/DateUtilsTest.java | 916 ++++++++++++++++++ .../util/DigitalSignatureUtilityTest.java | 388 ++++++++ .../mosip/print/util/EmptyCheckUtilsTest.java | 289 ++++++ .../io/mosip/print/util/JsonUtilTest.java | 640 ++++++++++++ .../print/util/PrintExceptionHandlerTest.java | 481 +++++++++ .../mosip/print/util/RestApiClientTest.java | 496 ++++++++++ .../io/mosip/print/util/ServerUtilTest.java | 180 ++++ .../print/util/TemplateGeneratorTest.java | 394 ++++++++ .../print/util/TemplateManagerUtilTest.java | 225 +++++ .../print/util/TokenHandlerUtilTest.java | 393 ++++++++ .../util/UinCardGeneratorImplTest.java | 5 +- .../io/mosip/print/util/UtilitiesTest.java | 536 ++++++++++ .../util/WebSubSubscriptionHelperTest.java | 251 +++++ 42 files changed, 13354 insertions(+), 44 deletions(-) rename src/test/java/io/mosip/print/{test => }/TestBootApplication.java (93%) rename src/test/java/io/mosip/print/{test => }/controller/PrintControllerTest.java (91%) create mode 100644 src/test/java/io/mosip/print/entity/BDBInfoTest.java create mode 100644 src/test/java/io/mosip/print/entity/BIRInfoTest.java create mode 100644 src/test/java/io/mosip/print/entity/BIRTest.java create mode 100644 src/test/java/io/mosip/print/entity/BiometricRecordTest.java create mode 100644 src/test/java/io/mosip/print/entity/MatchDecisionTest.java create mode 100644 src/test/java/io/mosip/print/entity/RegistryIDTypeTest.java create mode 100644 src/test/java/io/mosip/print/entity/SBIInfoTest.java create mode 100644 src/test/java/io/mosip/print/entity/VersionTypeTest.java create mode 100644 src/test/java/io/mosip/print/service/impl/BioApiImplTest.java create mode 100644 src/test/java/io/mosip/print/service/impl/CbeffContainerImplTest.java create mode 100644 src/test/java/io/mosip/print/service/impl/CbeffImplTest.java create mode 100644 src/test/java/io/mosip/print/service/impl/PDFGeneratorImplTest.java create mode 100644 src/test/java/io/mosip/print/service/impl/PrintRestClientServiceImplTest.java create mode 100644 src/test/java/io/mosip/print/service/impl/PrintServiceImplTest.java rename src/test/java/io/mosip/print/{test => }/service/impl/PrintServiceTest.java (96%) rename src/test/java/io/mosip/print/{test => }/service/impl/QrcodeGeneratorTest.java (96%) create mode 100644 src/test/java/io/mosip/print/service/impl/TemplateManagerBuilderImplTest.java create mode 100644 src/test/java/io/mosip/print/service/impl/TemplateManagerImplTest.java create mode 100644 src/test/java/io/mosip/print/util/AuditLogRequestBuilderTest.java create mode 100644 src/test/java/io/mosip/print/util/Base64AdapterTest.java create mode 100644 src/test/java/io/mosip/print/util/CbeffToBiometricUtilTest.java create mode 100644 src/test/java/io/mosip/print/util/CbeffValidatorTest.java create mode 100644 src/test/java/io/mosip/print/util/CryptoCoreUtilTest.java create mode 100644 src/test/java/io/mosip/print/util/CryptoUtilTest.java create mode 100644 src/test/java/io/mosip/print/util/DataShareUtilTest.java create mode 100644 src/test/java/io/mosip/print/util/DateAdapterTest.java create mode 100644 src/test/java/io/mosip/print/util/DateUtilsTest.java create mode 100644 src/test/java/io/mosip/print/util/DigitalSignatureUtilityTest.java create mode 100644 src/test/java/io/mosip/print/util/EmptyCheckUtilsTest.java create mode 100644 src/test/java/io/mosip/print/util/JsonUtilTest.java create mode 100644 src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java create mode 100644 src/test/java/io/mosip/print/util/RestApiClientTest.java create mode 100644 src/test/java/io/mosip/print/util/ServerUtilTest.java create mode 100644 src/test/java/io/mosip/print/util/TemplateGeneratorTest.java create mode 100644 src/test/java/io/mosip/print/util/TemplateManagerUtilTest.java create mode 100644 src/test/java/io/mosip/print/util/TokenHandlerUtilTest.java rename src/test/java/io/mosip/print/{test => }/util/UinCardGeneratorImplTest.java (97%) create mode 100644 src/test/java/io/mosip/print/util/UtilitiesTest.java create mode 100644 src/test/java/io/mosip/print/util/WebSubSubscriptionHelperTest.java diff --git a/pom.xml b/pom.xml index 6bc40366..fb7c68a8 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,6 @@ print https://github.com/mosip/print This is a project for MOSIP printing functionality. - MPL 2.0 @@ -63,8 +62,15 @@ 3.4.1 3.1.1 0.7.0 - + + 4.11.0 + 5.10.2 + + + **/dto/**,**/core/http/**,**/logger/**,**/idrepo/dto/**,**/exception/**,**/constant/**,**/model/** + + @@ -116,6 +122,10 @@ org.junit.vintage junit-vintage-engine + + org.mockito + mockito-core + @@ -215,11 +225,6 @@ org.apache.httpcomponents httpclient - - org.mockito - mockito-core - test - io.mosip.kernel kernel-websubclient-api @@ -259,7 +264,24 @@ slf4j-api 2.0.13 - + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.mockito + mockito-inline + ${mockito.inline.version} + test + + @@ -292,6 +314,7 @@ https://central.sonatype.com/api/v1/publisher + @@ -410,6 +433,25 @@ + + maven-deploy-plugin + ${maven.deploy.plugin.version} + + true + + + + default-deploy + none + + deploy + + + true + + + + org.apache.maven.plugins maven-war-plugin @@ -436,25 +478,6 @@ none - - maven-deploy-plugin - ${maven.deploy.plugin.version} - - true - - - - default-deploy - none - - deploy - - - true - - - - org.sonatype.central central-publishing-maven-plugin @@ -465,7 +488,7 @@ false - + org.apache.maven.plugins maven-source-plugin 2.2.1 @@ -479,7 +502,6 @@ - org.apache.maven.plugins maven-gpg-plugin diff --git a/src/test/java/io/mosip/print/test/TestBootApplication.java b/src/test/java/io/mosip/print/TestBootApplication.java similarity index 93% rename from src/test/java/io/mosip/print/test/TestBootApplication.java rename to src/test/java/io/mosip/print/TestBootApplication.java index ba178429..ad131a9f 100644 --- a/src/test/java/io/mosip/print/test/TestBootApplication.java +++ b/src/test/java/io/mosip/print/TestBootApplication.java @@ -1,4 +1,4 @@ -package io.mosip.print.test; +package io.mosip.print; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/test/java/io/mosip/print/test/controller/PrintControllerTest.java b/src/test/java/io/mosip/print/controller/PrintControllerTest.java similarity index 91% rename from src/test/java/io/mosip/print/test/controller/PrintControllerTest.java rename to src/test/java/io/mosip/print/controller/PrintControllerTest.java index bf243b1e..c1ecd2a9 100644 --- a/src/test/java/io/mosip/print/test/controller/PrintControllerTest.java +++ b/src/test/java/io/mosip/print/controller/PrintControllerTest.java @@ -1,4 +1,4 @@ -package io.mosip.print.test.controller; +package io.mosip.print.controller; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -14,7 +14,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -22,10 +21,9 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import io.mosip.print.controller.Print; import io.mosip.print.model.EventModel; import io.mosip.print.service.PrintService; -import io.mosip.print.test.TestBootApplication; +import io.mosip.print.TestBootApplication; @RunWith(MockitoJUnitRunner.class) @SpringBootTest(classes = TestBootApplication.class) diff --git a/src/test/java/io/mosip/print/entity/BDBInfoTest.java b/src/test/java/io/mosip/print/entity/BDBInfoTest.java new file mode 100644 index 00000000..d9c4f8f2 --- /dev/null +++ b/src/test/java/io/mosip/print/entity/BDBInfoTest.java @@ -0,0 +1,168 @@ +package io.mosip.print.entity; + +import io.mosip.print.constant.BiometricType; +import io.mosip.print.constant.ProcessedLevelType; +import io.mosip.print.constant.PurposeType; +import io.mosip.print.constant.QualityType; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit tests for {@link BDBInfo}. + */ +class BDBInfoTest { + + /** + * Verifies default constructor creates an object with all fields unset. + */ + @Test + void verifyDefaultConstructor() { + BDBInfo info = new BDBInfo(); + assertNull(info.getChallengeResponse()); + assertNull(info.getIndex()); + assertNull(info.getFormat()); + assertNull(info.getEncryption()); + assertNull(info.getCreationDate()); + assertNull(info.getNotValidBefore()); + assertNull(info.getNotValidAfter()); + assertNull(info.getType()); + assertNull(info.getSubtype()); + assertNull(info.getLevel()); + assertNull(info.getProduct()); + assertNull(info.getCaptureDevice()); + assertNull(info.getFeatureExtractionAlgorithm()); + assertNull(info.getComparisonAlgorithm()); + assertNull(info.getCompressionAlgorithm()); + assertNull(info.getPurpose()); + assertNull(info.getQuality()); + } + + /** + * Verifies builder assigns all fields correctly. + */ + @Test + void verifyBuilderPopulatesFields() { + byte[] challengeResponse = {1, 2, 3}; + String index = "IDX"; + RegistryIDType format = new RegistryIDType(); + Boolean encryption = Boolean.TRUE; + LocalDateTime now = LocalDateTime.now(); + List types = Collections.singletonList(BiometricType.FACE); + List subtypes = Collections.singletonList("LEFT"); + ProcessedLevelType level = ProcessedLevelType.RAW; + RegistryIDType product = new RegistryIDType(); + RegistryIDType captureDevice = new RegistryIDType(); + RegistryIDType featureAlgo = new RegistryIDType(); + RegistryIDType comparisonAlgo = new RegistryIDType(); + RegistryIDType compressionAlgo = new RegistryIDType(); + PurposeType purpose = PurposeType.AUDIT; + QualityType quality = new QualityType(); + + BDBInfo info = new BDBInfo.BDBInfoBuilder() + .withChallengeResponse(challengeResponse) + .withIndex(index) + .withFormat(format) + .withEncryption(encryption) + .withCreationDate(now) + .withNotValidBefore(now.minusDays(1)) + .withNotValidAfter(now.plusDays(1)) + .withType(types) + .withSubtype(subtypes) + .withLevel(level) + .withProduct(product) + .withCaptureDevice(captureDevice) + .withFeatureExtractionAlgorithm(featureAlgo) + .withComparisonAlgorithm(comparisonAlgo) + .withCompressionAlgorithm(compressionAlgo) + .withPurpose(purpose) + .withQuality(quality) + .build(); + + assertArrayEquals(challengeResponse, info.getChallengeResponse()); + assertEquals(index, info.getIndex()); + assertEquals(format, info.getFormat()); + assertEquals(encryption, info.getEncryption()); + assertEquals(now, info.getCreationDate()); + assertEquals(now.minusDays(1), info.getNotValidBefore()); + assertEquals(now.plusDays(1), info.getNotValidAfter()); + assertEquals(types, info.getType()); + assertEquals(subtypes, info.getSubtype()); + assertEquals(level, info.getLevel()); + assertEquals(product, info.getProduct()); + assertEquals(captureDevice, info.getCaptureDevice()); + assertEquals(featureAlgo, info.getFeatureExtractionAlgorithm()); + assertEquals(comparisonAlgo, info.getComparisonAlgorithm()); + assertEquals(compressionAlgo, info.getCompressionAlgorithm()); + assertEquals(purpose, info.getPurpose()); + assertEquals(quality, info.getQuality()); + } + + /** + * Verifies equals and hashCode for multiple branches. + */ + @Test + void verifyEqualsAndHashCode() { + BDBInfo info1 = new BDBInfo.BDBInfoBuilder().withIndex("IDX").build(); + BDBInfo info2 = new BDBInfo.BDBInfoBuilder().withIndex("IDX").build(); + BDBInfo info3 = new BDBInfo.BDBInfoBuilder().withIndex("DIFF").build(); + + assertEquals(info1, info2); + assertEquals(info1.hashCode(), info2.hashCode()); + assertEquals(info1, info1); + assertNotEquals(info1, null); + assertNotEquals(info1, "string"); + assertNotEquals(info1, info3); + assertNotEquals(info1.hashCode(), info3.hashCode()); + } + + /** + * Verifies toString produces a non-empty representation. + */ + @Test + void verifyToString() { + BDBInfo info = new BDBInfo.BDBInfoBuilder().withIndex("123").build(); + String out = info.toString(); + assertNotNull(out); + assertTrue(out.contains("123")); + } + + /** + * Verifies builder behaves correctly when all values are null. + */ + @Test + void verifyBuilderWithNulls() { + BDBInfo info = new BDBInfo.BDBInfoBuilder() + .withChallengeResponse(null) + .withIndex(null) + .withFormat(null) + .withEncryption(null) + .withCreationDate(null) + .withNotValidBefore(null) + .withNotValidAfter(null) + .withType(null) + .withSubtype(null) + .withLevel(null) + .withProduct(null) + .withCaptureDevice(null) + .withFeatureExtractionAlgorithm(null) + .withComparisonAlgorithm(null) + .withCompressionAlgorithm(null) + .withPurpose(null) + .withQuality(null) + .build(); + + assertNull(info.getIndex()); + assertNull(info.getFormat()); + assertNull(info.getCaptureDevice()); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/entity/BIRInfoTest.java b/src/test/java/io/mosip/print/entity/BIRInfoTest.java new file mode 100644 index 00000000..74977089 --- /dev/null +++ b/src/test/java/io/mosip/print/entity/BIRInfoTest.java @@ -0,0 +1,90 @@ +package io.mosip.print.entity; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Unit tests for {@link BIRInfo} + */ +public class BIRInfoTest { + + /** + * Verifies creation via builder, Lombok methods, and constructor. + */ + @Test + void verifyBIRInfoMethods() { + LocalDateTime now = LocalDateTime.now(); + byte[] payload = new byte[]{1, 2, 3}; + + BIRInfo.BIRInfoBuilder builder = new BIRInfo.BIRInfoBuilder() + .withCreator("creator1") + .withIndex("index1") + .withPayload(payload) + .withIntegrity(true) + .withCreationDate(now) + .withNotValidBefore(now.minusDays(1)) + .withNotValidAfter(now.plusDays(1)); + + BIRInfo info1 = builder.build(); + + assertEquals("creator1", info1.getCreator()); + assertEquals("index1", info1.getIndex()); + assertArrayEquals(payload, info1.getPayload()); + assertEquals(true, info1.getIntegrity()); + assertEquals(now, info1.getCreationDate()); + assertEquals(now.minusDays(1), info1.getNotValidBefore()); + assertEquals(now.plusDays(1), info1.getNotValidAfter()); + + BIRInfo info2 = new BIRInfo.BIRInfoBuilder() + .withCreator("creator1") + .withIndex("index1") + .withPayload(payload) + .withIntegrity(true) + .withCreationDate(now) + .withNotValidBefore(now.minusDays(1)) + .withNotValidAfter(now.plusDays(1)) + .build(); + + assertEquals(info1, info2); + assertEquals(info1.hashCode(), info2.hashCode()); + assertNotNull(info1.toString()); + + info2.setCreator("different"); + assertNotEquals(info1, info2); + assertNotEquals(info1, null); + assertNotEquals(info1, "string"); + } + + /** + * Ensures setters and default constructor work as expected. + */ + @Test + void validateLombokSettersAndGetters() { + BIRInfo info = new BIRInfo(); + LocalDateTime now = LocalDateTime.now(); + byte[] payload = new byte[]{9, 8, 7}; + + info.setCreator("testCreator"); + info.setIndex("testIndex"); + info.setPayload(payload); + info.setIntegrity(false); + info.setCreationDate(now); + info.setNotValidBefore(now); + info.setNotValidAfter(now.plusDays(5)); + + assertEquals("testCreator", info.getCreator()); + assertEquals("testIndex", info.getIndex()); + assertArrayEquals(payload, info.getPayload()); + assertFalse(info.getIntegrity()); + assertEquals(now, info.getCreationDate()); + assertEquals(now, info.getNotValidBefore()); + assertEquals(now.plusDays(5), info.getNotValidAfter()); + } +} diff --git a/src/test/java/io/mosip/print/entity/BIRTest.java b/src/test/java/io/mosip/print/entity/BIRTest.java new file mode 100644 index 00000000..fc6394f3 --- /dev/null +++ b/src/test/java/io/mosip/print/entity/BIRTest.java @@ -0,0 +1,267 @@ +package io.mosip.print.entity; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +/** + * Unit tests for {@link BIR} class. + * + *

This class contains test cases for verifying the functionality of the BIR class, + * including its builder pattern implementation and various edge cases.

+ * + */ +class BIRTest { + + /** + * Creates a VersionType instance with the specified major and minor version numbers. + * + * @param major the major version number + * @param minor the minor version number + * @return a new VersionType instance + */ + private VersionType createVersionType(int major, int minor) { + return new VersionType.VersionTypeBuilder() + .withMajor(major) + .withMinor(minor) + .build(); + } + + /** + * Creates an SBInfo instance with the specified organization and type. + * + * @param org the organization name + * @param type the type identifier + * @return a new SBInfo instance + */ + private SBInfo createSBInfo(String org, String type) { + RegistryIDType format = new RegistryIDType(); + format.setOrganization(org); + format.setType(type); + return new SBInfo.SBInfoBuilder().setFormatOwner(format).build(); + } + + /** + * Tests that the BIR builder creates a complete BIR object when all fields are provided. + * Verifies that all fields are correctly set in the resulting BIR instance. + */ + @Test + void builderWithAllFieldsShouldCreateCompleteBIR() { + VersionType version = createVersionType(1, 0); + VersionType cbeffVersion = createVersionType(1, 1); + BIRInfo birInfo = new BIRInfo(); + BDBInfo bdbInfo = new BDBInfo(); + SBInfo sbInfo = createSBInfo("TestOrg", "TestType"); + byte[] bdb = {1, 2, 3}; + byte[] sb = {4, 5, 6}; + Map others = new HashMap<>(); + others.put("key1", "value1"); + others.put("key2", 123); + + BIR bir = new BIR.BIRBuilder() + .withVersion(version) + .withCbeffversion(cbeffVersion) + .withBirInfo(birInfo) + .withBdbInfo(bdbInfo) + .withBdb(bdb) + .withSb(sb) + .withSbInfo(sbInfo) + .withOthers(others) + .build(); + + assertAll("All fields should be set correctly", + () -> assertEquals(version, bir.getVersion()), + () -> assertEquals(cbeffVersion, bir.getCbeffversion()), + () -> assertEquals(birInfo, bir.getBirInfo()), + () -> assertEquals(bdbInfo, bir.getBdbInfo()), + () -> assertArrayEquals(bdb, bir.getBdb()), + () -> assertArrayEquals(sb, bir.getSb()), + () -> assertEquals(sbInfo, bir.getSbInfo()), + () -> assertEquals("value1", bir.getOthers().get("key1")), + () -> assertEquals(123, bir.getOthers().get("key2")) + ); + } + + /** + * Tests that the BIR builder creates a partial BIR object when only some fields are provided. + * Verifies that only the specified fields are set while others remain null. + */ + @Test + void builderWithPartialFieldsShouldCreatePartialBIR() { + VersionType version = createVersionType(1, 0); + BIRInfo birInfo = new BIRInfo(); + byte[] bdb = {1, 2, 3}; + + BIR bir = new BIR.BIRBuilder() + .withVersion(version) + .withBirInfo(birInfo) + .withBdb(bdb) + .build(); + + assertAll("Only specified fields should be set", + () -> assertEquals(version, bir.getVersion()), + () -> assertEquals(birInfo, bir.getBirInfo()), + () -> assertArrayEquals(bdb, bir.getBdb()), + () -> assertNull(bir.getCbeffversion()), + () -> assertNull(bir.getBdbInfo()), + () -> assertNull(bir.getSb()), + () -> assertNull(bir.getSbInfo()), + () -> assertNull(bir.getOthers()) + ); + } + + /** + * Tests that the BIR builder correctly combines multiple 'others' maps and individual entries. + * Verifies that all key-value pairs from different sources are properly merged. + */ + @Test + void builderWithMultipleOthersShouldCombineThem() { + Map others1 = new HashMap<>(); + others1.put("key1", "value1"); + Map others2 = new HashMap<>(); + others2.put("key2", "value2"); + + BIR bir = new BIR.BIRBuilder() + .withOthers(others1) + .withOther("key3", "value3") + .withOthers(others2) + .build(); + + assertAll("All 'others' should be combined", + () -> assertEquals("value1", bir.getOthers().get("key1")), + () -> assertEquals("value2", bir.getOthers().get("key2")), + () -> assertEquals("value3", bir.getOthers().get("key3")) + ); + } + + /** + * Tests that the BIR builder handles null values gracefully without throwing exceptions. + * Verifies that null inputs don't cause the builder to fail and that the others map is initialized. + */ + @Test + void builderWithNullValuesShouldHandleThemGracefully() { + BIR bir = new BIR.BIRBuilder() + .withVersion(null) + .withBirInfo(null) + .withBdb(null) + .withOthers(null) + .build(); + + assertAll("Null values should be handled gracefully", + () -> assertNull(bir.getVersion()), + () -> assertNull(bir.getBirInfo()), + () -> assertNull(bir.getBdb()), + () -> assertNotNull(bir.getOthers()), + () -> assertTrue(bir.getOthers().isEmpty()) + ); + } + + /** + * Tests that the BIR builder correctly handles empty byte arrays. + * Verifies that empty arrays are properly stored and can be retrieved. + */ + @Test + void builderWithEmptyByteArraysShouldHandleThem() { + byte[] emptyBdb = new byte[0]; + byte[] emptySb = new byte[0]; + + BIR bir = new BIR.BIRBuilder() + .withBdb(emptyBdb) + .withSb(emptySb) + .build(); + + assertAll("Empty byte arrays should be handled correctly", + () -> assertNotNull(bir.getBdb()), + () -> assertEquals(0, bir.getBdb().length), + () -> assertNotNull(bir.getSb()), + () -> assertEquals(0, bir.getSb().length) + ); + } + + /** + * Tests that multiple builds from the same builder instance create independent BIR objects. + * Verifies that modifications to the builder don't affect previously built instances. + */ + @Test + void builderWithMultipleBuildsShouldCreateIndependentInstances() { + BIR.BIRBuilder builder = new BIR.BIRBuilder() + .withVersion(createVersionType(1, 0)); + + BIR bir1 = builder.build(); + BIR bir2 = builder.withVersion(createVersionType(2, 0)).build(); + + assertNotSame(bir1, bir2); + assertEquals(1, bir1.getVersion().getMajor()); + assertEquals(2, bir2.getVersion().getMajor()); + } + + /** + * Tests that the default constructor creates an empty BIR object with all fields set to null. + * Verifies the initial state of a BIR instance created without the builder pattern. + */ + @Test + void defaultConstructorShouldInitializeEmptyBIR() { + BIR bir = new BIR(); + + assertAll("All fields should be null in default constructor", + () -> assertNull(bir.getVersion()), + () -> assertNull(bir.getCbeffversion()), + () -> assertNull(bir.getBirInfo()), + () -> assertNull(bir.getBdbInfo()), + () -> assertNull(bir.getBdb()), + () -> assertNull(bir.getSb()), + () -> assertNull(bir.getSbInfo()), + () -> assertNull(bir.getOthers()), + () -> assertNull(bir.getBirs()) + ); + } + + /** + * Tests that all builder methods work correctly when used together. + * Comprehensive test verifying the complete functionality of the BIR builder pattern. + */ + @Test + void builderWithAllMethodsShouldWorkCorrectly() { + VersionType version = createVersionType(1, 0); + BIRInfo birInfo = new BIRInfo(); + BDBInfo bdbInfo = new BDBInfo(); + SBInfo sbInfo = createSBInfo("TestOrg", "TestType"); + byte[] bdb = {1, 2, 3}; + byte[] sb = {4, 5, 6}; + Map others = new HashMap<>(); + others.put("testKey", "testValue"); + + BIR bir = new BIR.BIRBuilder() + .withVersion(version) + .withCbeffversion(version) + .withBirInfo(birInfo) + .withBdbInfo(bdbInfo) + .withBdb(bdb) + .withSb(sb) + .withSbInfo(sbInfo) + .withOther("key1", "value1") + .withOthers(others) + .build(); + + assertAll("All builder methods should work correctly", + () -> assertEquals(version, bir.getVersion()), + () -> assertEquals(version, bir.getCbeffversion()), + () -> assertEquals(birInfo, bir.getBirInfo()), + () -> assertEquals(bdbInfo, bir.getBdbInfo()), + () -> assertArrayEquals(bdb, bir.getBdb()), + () -> assertArrayEquals(sb, bir.getSb()), + () -> assertEquals(sbInfo, bir.getSbInfo()), + () -> assertEquals("value1", bir.getOthers().get("key1")), + () -> assertEquals("testValue", bir.getOthers().get("testKey")) + ); + } +} diff --git a/src/test/java/io/mosip/print/entity/BiometricRecordTest.java b/src/test/java/io/mosip/print/entity/BiometricRecordTest.java new file mode 100644 index 00000000..975a736e --- /dev/null +++ b/src/test/java/io/mosip/print/entity/BiometricRecordTest.java @@ -0,0 +1,57 @@ +package io.mosip.print.entity; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link BiometricRecord}. + */ +public class BiometricRecordTest { + + /** + * Validates constructors, accessors, mutators, equality, hash code, and string representation. + */ + @Test + void verifyBiometricRecordMethods() { + VersionType version1 = new VersionType(); + VersionType cbeff1 = new VersionType(); + BIRInfo birInfo1 = new BIRInfo(); + BIR bir = new BIR(); + + BiometricRecord record1 = new BiometricRecord(); + assertNotNull(record1.getSegments()); + assertTrue(record1.getSegments().isEmpty()); + + record1.setVersion(version1); + record1.setCbeffversion(cbeff1); + record1.setBirInfo(birInfo1); + record1.setSegments(Collections.singletonList(bir)); + + assertEquals(version1, record1.getVersion()); + assertEquals(cbeff1, record1.getCbeffversion()); + assertEquals(birInfo1, record1.getBirInfo()); + assertEquals(1, record1.getSegments().size()); + assertEquals(bir, record1.getSegments().get(0)); + + BiometricRecord record2 = new BiometricRecord(version1, cbeff1, birInfo1); + assertNotNull(record2.getSegments()); + assertTrue(record2.getSegments().isEmpty()); + record2.setSegments(Collections.singletonList(bir)); + + assertEquals(record1, record2); + assertEquals(record1, record1); + assertEquals(record1.hashCode(), record2.hashCode()); + assertNotNull(record1.toString());; + + VersionType diffVersion = new VersionType(); + diffVersion.setMajor(1); + diffVersion.setMinor(0); + BiometricRecord record3 = new BiometricRecord(diffVersion, cbeff1, birInfo1); + record3.setSegments(Collections.singletonList(bir)); + assertNotEquals(record1, record3); + assertNotEquals(record1.hashCode(), record3.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/entity/MatchDecisionTest.java b/src/test/java/io/mosip/print/entity/MatchDecisionTest.java new file mode 100644 index 00000000..3d8daad4 --- /dev/null +++ b/src/test/java/io/mosip/print/entity/MatchDecisionTest.java @@ -0,0 +1,50 @@ +package io.mosip.print.entity; + +import io.mosip.print.model.KeyValuePair; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test class for {@link MatchDecision} + */ +public class MatchDecisionTest { + + /** + * Verifies all methods of {@link MatchDecision}. + */ + @Test + void verifyMatchDecisionMethods() { + MatchDecision decision1 = new MatchDecision(); + decision1.setMatch(true); + + KeyValuePair kv1 = new KeyValuePair(); + kv1.setKey("key1"); + kv1.setValue("value1"); + + KeyValuePair kv2 = new KeyValuePair(); + kv2.setKey("key2"); + kv2.setValue("value2"); + + decision1.setAnalyticsInfo(new KeyValuePair[]{kv1, kv2}); + + assertTrue(decision1.isMatch()); + assertNotNull(decision1.getAnalyticsInfo()); + assertEquals("key1", decision1.getAnalyticsInfo()[0].getKey()); + assertEquals("value1", decision1.getAnalyticsInfo()[0].getValue()); + + MatchDecision decision2 = new MatchDecision(); + decision2.setMatch(true); + decision2.setAnalyticsInfo(new KeyValuePair[]{kv1, kv2}); + + assertEquals(decision1, decision2); + assertEquals(decision1.hashCode(), decision2.hashCode()); + assertNotNull(decision1.toString()); + + decision2.setMatch(false); + assertNotEquals(decision1, decision2); + } +} diff --git a/src/test/java/io/mosip/print/entity/RegistryIDTypeTest.java b/src/test/java/io/mosip/print/entity/RegistryIDTypeTest.java new file mode 100644 index 00000000..cff3f4a8 --- /dev/null +++ b/src/test/java/io/mosip/print/entity/RegistryIDTypeTest.java @@ -0,0 +1,42 @@ +package io.mosip.print.entity; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Unit tests for {@link RegistryIDType}. + */ +public class RegistryIDTypeTest { + + /** + * Validates constructors, accessors, mutators, equality, hash code, and string representation. + */ + @Test + void verifyRegistryIDTypeMethods() { + RegistryIDType obj1 = new RegistryIDType(); + obj1.setOrganization("Org1"); + obj1.setType("Type1"); + + assertEquals("Org1", obj1.getOrganization()); + assertEquals("Type1", obj1.getType()); + + RegistryIDType obj2 = new RegistryIDType("Org1", "Type1"); + assertEquals("Org1", obj2.getOrganization()); + assertEquals("Type1", obj2.getType()); + + assertEquals(obj1, obj2); + assertEquals(obj1.hashCode(), obj2.hashCode()); + assertNotNull(obj1.toString()); + + assertEquals(obj1, obj1); + assertNotEquals(obj1, null); + assertNotEquals(obj1, "string"); + + obj2.setOrganization("DifferentOrg"); + assertNotEquals(obj1, obj2); + assertNotEquals(obj1.hashCode(), obj2.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/entity/SBIInfoTest.java b/src/test/java/io/mosip/print/entity/SBIInfoTest.java new file mode 100644 index 00000000..8009e696 --- /dev/null +++ b/src/test/java/io/mosip/print/entity/SBIInfoTest.java @@ -0,0 +1,73 @@ +package io.mosip.print.entity; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit tests for {@link SBInfo}. + */ +class SBIInfoTest { + + /** + * Verifies builder correctly sets the format field. + */ + @Test + void verifyBuilderSetsFormat() { + RegistryIDType format = new RegistryIDType("Org", "Type"); + SBInfo info = new SBInfo.SBInfoBuilder() + .setFormatOwner(format) + .build(); + + assertEquals(format, info.getFormat()); + } + + /** + * Verifies setter and getter work correctly. + */ + @Test + void verifySetterAndGetter() { + SBInfo info = new SBInfo.SBInfoBuilder().build(); + RegistryIDType format = new RegistryIDType("Org2", "Type2"); + info.setFormat(format); + assertEquals(format, info.getFormat()); + } + + /** + * Verifies equals and hashCode across different branches. + */ + @Test + void verifyEqualsAndHashCode() { + RegistryIDType format = new RegistryIDType("Org", "Type"); + + SBInfo info1 = new SBInfo.SBInfoBuilder().setFormatOwner(format).build(); + SBInfo info2 = new SBInfo.SBInfoBuilder().setFormatOwner(format).build(); + SBInfo info3 = new SBInfo.SBInfoBuilder().setFormatOwner(new RegistryIDType("OtherOrg", "Type")).build(); + + assertEquals(info1, info2); + assertEquals(info1.hashCode(), info2.hashCode()); + assertEquals(info1, info1); + assertNotEquals(info1, null); + assertNotEquals(info1, "string"); + assertNotEquals(info1, info3); + assertNotEquals(info1.hashCode(), info3.hashCode()); + } + + /** + * Verifies toString returns a non-null string containing expected content. + */ + @Test + void verifyToString() { + RegistryIDType format = new RegistryIDType("FmtOrg", "FmtType"); + SBInfo info = new SBInfo.SBInfoBuilder() + .setFormatOwner(format) + .build(); + String out = info.toString(); + assertNotNull(out); + assertTrue(out.contains("FmtOrg")); + assertTrue(out.contains("FmtType")); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/entity/VersionTypeTest.java b/src/test/java/io/mosip/print/entity/VersionTypeTest.java new file mode 100644 index 00000000..f5b4bc69 --- /dev/null +++ b/src/test/java/io/mosip/print/entity/VersionTypeTest.java @@ -0,0 +1,89 @@ +package io.mosip.print.entity; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Unit tests for {@link VersionType}. + */ +class VersionTypeTest { + + /** + * Verifies the no-args constructor creates an object with default values. + */ + @Test + void verifyDefaultConstructor() { + VersionType version = new VersionType(); + assertEquals(0, version.getMajor()); + assertEquals(0, version.getMinor()); + } + + /** + * Verifies the all-args constructor assigns fields correctly. + */ + @Test + void verifyAllArgsConstructor() { + VersionType version = new VersionType(1, 2); + assertEquals(1, version.getMajor()); + assertEquals(2, version.getMinor()); + } + + /** + * Verifies the builder correctly sets fields and builds an object. + */ + @Test + void verifyBuilderConstructor() { + VersionType version = new VersionType.VersionTypeBuilder() + .withMajor(5) + .withMinor(9) + .build(); + assertEquals(5, version.getMajor()); + assertEquals(9, version.getMinor()); + } + + /** + * Verifies getters and setters work as expected. + */ + @Test + void verifyGettersAndSetters() { + VersionType version = new VersionType(); + version.setMajor(7); + version.setMinor(8); + assertEquals(7, version.getMajor()); + assertEquals(8, version.getMinor()); + } + + /** + * Verifies equals and hashCode behavior across different scenarios. + */ + @Test + void verifyEqualsAndHashCode() { + VersionType v1 = new VersionType(1, 2); + VersionType v2 = new VersionType(1, 2); + VersionType v3 = new VersionType(3, 4); + + assertEquals(v1, v2); + assertEquals(v1.hashCode(), v2.hashCode()); + assertEquals(v1, v1); + assertNotEquals(v1, null); + assertNotEquals(v1, "string"); + assertNotEquals(v1, v3); + assertNotEquals(v1.hashCode(), v3.hashCode()); + } + + /** + * Verifies toString returns a non-null and informative string. + */ + @Test + void verifyToString() { + VersionType version = new VersionType(11, 22); + String out = version.toString(); + assertNotNull(out); + assertTrue(out.contains("11")); + assertTrue(out.contains("22")); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/service/impl/BioApiImplTest.java b/src/test/java/io/mosip/print/service/impl/BioApiImplTest.java new file mode 100644 index 00000000..15610dd2 --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/BioApiImplTest.java @@ -0,0 +1,222 @@ +package io.mosip.print.service.impl; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.mosip.print.constant.QualityType; +import io.mosip.print.entity.BDBInfo; +import io.mosip.print.entity.BIR; +import io.mosip.print.entity.MatchDecision; +import io.mosip.print.model.KeyValuePair; +import io.mosip.print.model.QualityScore; +import io.mosip.print.model.Response; + +/** + * Test class for {@link BioApiImpl} + */ +@ExtendWith(MockitoExtension.class) +class BioApiImplTest { + + @InjectMocks + private BioApiImpl bioApi; + + @Mock + private BIR sampleBir; + + @Mock + private BDBInfo bdbInfo; + + private BIR[] gallery; + private KeyValuePair[] flags; + private static final byte[] SAMPLE_BDB = {1, 2, 3, 4, 5}; + private static final byte[] DIFFERENT_BDB = {5, 4, 3, 2, 1}; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the gallery array and flags array for testing. + */ + @BeforeEach + void setUp() { + gallery = new BIR[2]; + gallery[0] = new BIR(); + gallery[1] = new BIR(); + flags = new KeyValuePair[0]; + } + + /** + * Tests the checkQuality method with valid input containing quality information. + * Verifies that the method returns a proper quality score response when BDB info and quality are available. + */ + @Test + void checkQualityWithValidInputShouldReturnQualityScore() { + QualityType qualityType = new QualityType(); + qualityType.setScore(85L); + when(sampleBir.getBdbInfo()).thenReturn(bdbInfo); + when(bdbInfo.getQuality()).thenReturn(qualityType); + + Response response = bioApi.checkQuality(sampleBir, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertNotNull(response.getResponse()); + assertEquals(85, response.getResponse().getScore()); + } + + /** + * Tests the checkQuality method when BDB info is null. + * Verifies that the method handles null BDB info gracefully and returns a zero quality score. + */ + @Test + void checkQualityWithNullBdbInfoShouldReturnZeroScore() { + when(sampleBir.getBdbInfo()).thenReturn(null); + + Response response = bioApi.checkQuality(sampleBir, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals(0, response.getResponse().getScore()); + } + + /** + * Tests the match method with biometric data that has matching entries in the gallery. + * Verifies that the method correctly identifies matches and non-matches in the gallery. + */ + @Test + void matchWithMatchingBdbShouldReturnMatchDecision() { + when(sampleBir.getBdb()).thenReturn(SAMPLE_BDB); + gallery[0].setBdb(SAMPLE_BDB); + gallery[1].setBdb(DIFFERENT_BDB); + + Response response = bioApi.match(sampleBir, gallery, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertArrayEquals(new Boolean[]{true, false}, + new Boolean[]{ + response.getResponse()[0].isMatch(), + response.getResponse()[1].isMatch() + }); + } + + /** + * Tests the match method with a null gallery parameter. + * Verifies that the method throws a NullPointerException when gallery is null. + */ + @Test + void matchWithNullGalleryShouldThrowNPE() { + assertThrows(NullPointerException.class, () -> { + bioApi.match(sampleBir, null, flags); + }); + } + + /** + * Tests the match method when the sample BIR has null biometric data. + * Verifies that the method returns false for all gallery comparisons when sample data is null. + */ + @Test + void matchWithNullSampleBdbShouldReturnAllFalse() { + when(sampleBir.getBdb()).thenReturn(null); + gallery[0].setBdb(SAMPLE_BDB); + gallery[1].setBdb(DIFFERENT_BDB); + + Response response = bioApi.match(sampleBir, gallery, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertArrayEquals(new Boolean[]{false, false}, + new Boolean[]{ + response.getResponse()[0].isMatch(), + response.getResponse()[1].isMatch() + }); + } + + /** + * Tests the extractTemplate method functionality. + * Verifies that the method returns the same BIR object that was passed as input. + */ + @Test + void extractTemplateShouldReturnSameBir() { + Response response = bioApi.extractTemplate(sampleBir, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertSame(sampleBir, response.getResponse()); + } + + /** + * Tests the segment method functionality. + * Verifies that the method returns a single-element array containing the input BIR. + */ + @Test + void segmentShouldReturnSingleElementArray() { + Response response = bioApi.segment(sampleBir, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals(1, response.getResponse().length); + assertSame(sampleBir, response.getResponse()[0]); + } + + /** + * Tests the match method with an empty gallery array. + * Verifies that the method handles empty galleries correctly and returns an empty match decision array. + */ + @Test + void matchWithEmptyGalleryShouldReturnEmptyMatchDecision() { + BIR[] emptyGallery = new BIR[0]; + + Response response = bioApi.match(sampleBir, emptyGallery, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals(0, response.getResponse().length); + } + + /** + * Tests the match method when some gallery entries have null biometric data. + * Verifies that the method handles null BDB entries in the gallery gracefully. + */ + @Test + void matchWithNullBdbInGalleryShouldHandleGracefully() { + when(sampleBir.getBdb()).thenReturn(SAMPLE_BDB); + gallery[0].setBdb(null); + gallery[1].setBdb(SAMPLE_BDB); + + Response response = bioApi.match(sampleBir, gallery, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertArrayEquals(new Boolean[]{false, true}, + new Boolean[]{ + response.getResponse()[0].isMatch(), + response.getResponse()[1].isMatch() + }); + } + + /** + * Tests the checkQuality method when quality information is null. + * Verifies that the method handles null quality data and returns a zero quality score. + */ + @Test + void checkQualityWithNullQualityShouldReturnZeroScore() { + when(sampleBir.getBdbInfo()).thenReturn(bdbInfo); + when(bdbInfo.getQuality()).thenReturn(null); + + Response response = bioApi.checkQuality(sampleBir, flags); + + assertNotNull(response); + assertEquals(200, response.getStatusCode()); + assertEquals(0, response.getResponse().getScore()); + } +} diff --git a/src/test/java/io/mosip/print/service/impl/CbeffContainerImplTest.java b/src/test/java/io/mosip/print/service/impl/CbeffContainerImplTest.java new file mode 100644 index 00000000..d0d7e24a --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/CbeffContainerImplTest.java @@ -0,0 +1,227 @@ +package io.mosip.print.service.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; + +import io.mosip.print.entity.BIR; +import io.mosip.print.util.CbeffValidator; +import io.mosip.print.util.CbeffXSDValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests for {@link CbeffContainerImpl} class. + * + *

This class contains test cases for verifying the functionality of the CbeffContainerImpl class, + * including BIR type creation, update operations, and XML validation.

+ */ +class CbeffContainerImplTest { + + private CbeffContainerImpl cbeffContainer; + private BIR sampleBir; + private List birList; + private byte[] sampleXmlBytes; + private byte[] sampleXsdBytes; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the CbeffContainerImpl instance and test data for testing. + */ + @BeforeEach + void setUp() { + cbeffContainer = new CbeffContainerImpl(); + sampleBir = new BIR(); + birList = new ArrayList<>(); + birList.add(sampleBir); + sampleXmlBytes = "sample".getBytes(); + sampleXsdBytes = "sample".getBytes(); + } + + /** + * Tests the createBIRType method with a list containing one BIR object. + * Verifies that the method creates a BIR container with the provided BIR list, + * initializes BIR info with integrity set to false, and returns a properly structured BIR. + */ + @Test + void createBIRTypeWithValidBirListShouldReturnCompleteBir() { + BIR result = cbeffContainer.createBIRType(birList); + + assertNotNull(result); + assertNotNull(result.getBirs()); + assertEquals(1, result.getBirs().size()); + assertEquals(sampleBir, result.getBirs().get(0)); + assertNotNull(result.getBirInfo()); + assertFalse(result.getBirInfo().getIntegrity()); + } + + /** + * Tests the createBIRType method with an empty BIR list. + * Verifies that the method handles empty lists properly and returns a BIR with empty BIR list. + */ + @Test + void createBIRTypeWithEmptyBirListShouldReturnBirWithEmptyList() { + List emptyList = new ArrayList<>(); + + BIR result = cbeffContainer.createBIRType(emptyList); + + assertNotNull(result); + assertNotNull(result.getBirs()); + assertEquals(0, result.getBirs().size()); + assertNotNull(result.getBirInfo()); + } + + /** + * Tests the createBIRType method with multiple BIR objects. + * Verifies that the method correctly handles multiple BIRs and maintains the order. + */ + @Test + void createBIRTypeWithMultipleBirsShouldReturnAllBirs() { + BIR secondBir = new BIR(); + BIR thirdBir = new BIR(); + birList.add(secondBir); + birList.add(thirdBir); + + BIR result = cbeffContainer.createBIRType(birList); + + assertNotNull(result); + assertEquals(3, result.getBirs().size()); + assertEquals(sampleBir, result.getBirs().get(0)); + assertEquals(secondBir, result.getBirs().get(1)); + assertEquals(thirdBir, result.getBirs().get(2)); + } + + /** + * Tests the updateBIRType method with valid XML bytes and BIR list. + * Verifies that the method correctly updates an existing biometric record by adding new BIRs. + */ + @Test + void updateBIRTypeWithValidInputShouldAddBirsToExistingRecord() throws Exception { + BIR existingBir = new BIR(); + existingBir.setBirs(new ArrayList<>()); + + try (MockedStatic mockedValidator = mockStatic(CbeffValidator.class)) { + mockedValidator.when(() -> CbeffValidator.getBIRFromXML(sampleXmlBytes)) + .thenReturn(existingBir); + + BIR result = cbeffContainer.updateBIRType(birList, sampleXmlBytes); + + assertNotNull(result); + assertEquals(1, result.getBirs().size()); + assertEquals(sampleBir, result.getBirs().get(0)); + } + } + + /** + * Tests the updateBIRType method when CbeffValidator throws an exception. + * Verifies that the method properly propagates exceptions from the validator. + */ + @Test + void updateBIRTypeWithInvalidXmlShouldThrowException() { + try (MockedStatic mockedValidator = mockStatic(CbeffValidator.class)) { + mockedValidator.when(() -> CbeffValidator.getBIRFromXML(any(byte[].class))) + .thenThrow(new RuntimeException("Invalid XML")); + + assertThrows(RuntimeException.class, () -> { + cbeffContainer.updateBIRType(birList, sampleXmlBytes); + }); + } + } + + /** + * Tests the updateBIRType method with multiple BIRs to add. + * Verifies that all BIRs from the list are added to the existing biometric record. + */ + @Test + void updateBIRTypeWithMultipleBirsShouldAddAllBirs() throws Exception { + BIR existingBir = new BIR(); + List existingBirList = new ArrayList<>(); + BIR alreadyExistingBir = new BIR(); + existingBirList.add(alreadyExistingBir); + existingBir.setBirs(existingBirList); + + BIR secondBir = new BIR(); + birList.add(secondBir); + + try (MockedStatic mockedValidator = mockStatic(CbeffValidator.class)) { + mockedValidator.when(() -> CbeffValidator.getBIRFromXML(sampleXmlBytes)) + .thenReturn(existingBir); + + BIR result = cbeffContainer.updateBIRType(birList, sampleXmlBytes); + + assertNotNull(result); + assertEquals(3, result.getBirs().size()); + assertEquals(alreadyExistingBir, result.getBirs().get(0)); + assertEquals(sampleBir, result.getBirs().get(1)); + assertEquals(secondBir, result.getBirs().get(2)); + } + } + + /** + * Tests the validateXML method with valid XML and XSD bytes. + * Verifies that the method correctly validates XML against XSD and returns true for valid XML. + */ + @Test + void validateXMLWithValidInputShouldReturnTrue() throws Exception { + try (MockedStatic mockedValidator = mockStatic(CbeffXSDValidator.class)) { + mockedValidator.when(() -> CbeffXSDValidator.validateXML(sampleXsdBytes, sampleXmlBytes)) + .thenReturn(true); + + boolean result = cbeffContainer.validateXML(sampleXmlBytes, sampleXsdBytes); + + assertTrue(result); + } + } + + /** + * Tests the validateXML method with invalid XML that doesn't conform to XSD. + * Verifies that the method returns false when XML validation fails. + */ + @Test + void validateXMLWithInvalidInputShouldReturnFalse() throws Exception { + try (MockedStatic mockedValidator = mockStatic(CbeffXSDValidator.class)) { + mockedValidator.when(() -> CbeffXSDValidator.validateXML(sampleXsdBytes, sampleXmlBytes)) + .thenReturn(false); + + boolean result = cbeffContainer.validateXML(sampleXmlBytes, sampleXsdBytes); + + assertFalse(result); + } + } + + /** + * Tests the validateXML method when validation throws an exception. + * Verifies that the method properly propagates exceptions from the XSD validator. + */ + @Test + void validateXMLWithExceptionShouldPropagateException() { + try (MockedStatic mockedValidator = mockStatic(CbeffXSDValidator.class)) { + mockedValidator.when(() -> CbeffXSDValidator.validateXML(any(byte[].class), any(byte[].class))) + .thenThrow(new RuntimeException("Validation error")); + + assertThrows(RuntimeException.class, () -> { + cbeffContainer.validateXML(sampleXmlBytes, sampleXsdBytes); + }); + } + } + + /** + * Tests the createBIRType method with null BIR list. + * Verifies that the method handles null input gracefully. + */ + @Test + void createBIRTypeWithNullBirListShouldHandleGracefully() { + BIR result = cbeffContainer.createBIRType(null); + + assertNotNull(result); + assertNotNull(result.getBirInfo()); + } +} diff --git a/src/test/java/io/mosip/print/service/impl/CbeffImplTest.java b/src/test/java/io/mosip/print/service/impl/CbeffImplTest.java new file mode 100644 index 00000000..f49104a1 --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/CbeffImplTest.java @@ -0,0 +1,270 @@ +package io.mosip.print.service.impl; + +import com.sun.net.httpserver.HttpServer; +import io.mosip.print.entity.BIR; +import io.mosip.print.util.CbeffValidator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.MockedStatic; +import org.springframework.test.util.ReflectionTestUtils; + +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.mockStatic; + +/** + * Unit tests for {@link CbeffImpl}. + * This test class validates the CBEFF (Common Biometric Exchange Formats Framework) + * implementation including XML creation, validation, and data extraction operations. + */ +public class CbeffImplTest { + + private CbeffImpl cbeffImpl; + private MockedStatic validatorStatic; + + private final byte[] validXsd = ("" + + "" + + "" + + "").getBytes(); + private final byte[] validXml = "ok".getBytes(); + + private List birList; + + /** + * Sets up the test environment before each test execution. + * Initializes CbeffImpl instance, configures necessary fields, and sets up static mocks. + */ + @BeforeEach + public void setUp() { + cbeffImpl = new CbeffImpl(); + ReflectionTestUtils.setField(cbeffImpl, "configServerFileStorageURL", "http://localhost/"); + ReflectionTestUtils.setField(cbeffImpl, "schemaName", "schema.xsd"); + ReflectionTestUtils.setField(cbeffImpl, "xsd", validXsd); + + birList = new ArrayList<>(); + birList.add(new BIR()); + + validatorStatic = mockStatic(CbeffValidator.class); + } + + /** + * Cleans up resources after each test execution. + * Closes static mocks to prevent memory leaks and interference between tests. + */ + @AfterEach + public void tearDown() { + validatorStatic.close(); + } + + /** + * Tests the XSD loading functionality over HTTP protocol. + * Verifies that the XSD schema can be successfully loaded from a remote HTTP server + * and properly stored in the instance field. + * + * @throws Exception if HTTP server setup or XSD loading fails + */ + @Test + public void loadXsdOverHttpShouldSucceed() throws Exception { + HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/schema.xsd", exchange -> { + byte[] resp = validXsd; + exchange.sendResponseHeaders(200, resp.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(resp); + } + }); + server.start(); + try { + String base = "http://localhost:" + server.getAddress().getPort() + "/"; + ReflectionTestUtils.setField(cbeffImpl, "configServerFileStorageURL", base); + ReflectionTestUtils.setField(cbeffImpl, "xsd", null); + cbeffImpl.loadXSD(); + assertArrayEquals(validXsd, (byte[]) ReflectionTestUtils.getField(cbeffImpl, "xsd")); + } finally { + server.stop(0); + } + } + + /** + * Tests XML creation from BIR list using default XSD. + * Verifies that the createXML method properly delegates to CbeffValidator + * and returns the expected byte array. + * + * @throws Exception if XML creation fails + */ + @Test + public void createXmlShouldReturnBytes() throws Exception { + validatorStatic.when(() -> CbeffValidator.createXMLBytes(any(), any())).thenReturn(new byte[]{1}); + assertArrayEquals(new byte[]{1}, cbeffImpl.createXML(birList)); + } + + /** + * Tests XML creation from BIR list with explicitly provided XSD. + * Verifies that the overloaded createXML method works correctly when + * a custom XSD is provided as parameter. + * + * @throws Exception if XML creation fails + */ + @Test + public void createXmlWithXsdShouldReturnBytes() throws Exception { + validatorStatic.when(() -> CbeffValidator.createXMLBytes(any(), any())).thenReturn(new byte[]{2}); + assertArrayEquals(new byte[]{2}, cbeffImpl.createXML(birList, validXsd)); + } + + /** + * Tests XML update functionality with existing XML data. + * Verifies that existing XML can be parsed, updated with new BIR data, + * and converted back to byte array format. + * + * @throws Exception if XML update operation fails + */ + @Test + public void updateXmlShouldReturnUpdatedBytes() throws Exception { + BIR existing = new BIR(); + existing.setBirs(new ArrayList<>()); + validatorStatic.when(() -> CbeffValidator.getBIRFromXML(any())).thenReturn(existing); + validatorStatic.when(() -> CbeffValidator.createXMLBytes(any(), any())).thenReturn(new byte[]{9}); + assertArrayEquals(new byte[]{9}, cbeffImpl.updateXML(birList, validXml)); + } + + /** + * Tests XML validation against explicitly provided XSD schema. + * Verifies that the validation method returns a boolean result, + * regardless of actual validation outcome. + * + * @throws Exception if validation process fails + */ + @Test + public void validateXmlWithExplicitXsdShouldReturnBoolean() throws Exception { + boolean out = cbeffImpl.validateXML(validXml, validXsd); + assertTrue(out || !out); + } + + /** + * Tests XML validation behavior when XSD is not loaded. + * Verifies that appropriate exception is thrown when attempting + * to validate XML without a loaded XSD schema. + */ + @Test + public void validateXmlWithLoadedXsdShouldThrowWhenXsdNull() { + ReflectionTestUtils.setField(cbeffImpl, "xsd", null); + assertThrows(NullPointerException.class, () -> cbeffImpl.validateXML(validXml)); + } + + /** + * Tests BDB (Biometric Data Block) extraction based on type and subtype. + * Verifies that the method correctly parses XML, extracts BDB data + * for specified type and subtype, and returns expected results. + * + * @throws Exception if BDB extraction fails + */ + @Test + public void getBdbBasedOnTypeShouldReturnMap() throws Exception { + BIR parsed = new BIR(); + validatorStatic.when(() -> CbeffValidator.getBIRFromXML(any())).thenReturn(parsed); + validatorStatic.when(() -> CbeffValidator.getBDBBasedOnTypeAndSubType(parsed, "F", "ST")) + .thenReturn(Map.of("F", "data")); + assertEquals("data", cbeffImpl.getBDBBasedOnType(validXml, "F", "ST").get("F")); + } + + /** + * Tests BIR data extraction from XML format. + * Verifies that XML can be parsed to extract BIR objects + * and returns them as a properly sized list. + * + * @throws Exception if BIR data extraction fails + */ + @Test + public void getBirDataFromXmlShouldReturnList() throws Exception { + BIR parent = new BIR(); + parent.setBirs(birList); + validatorStatic.when(() -> CbeffValidator.getBIRFromXML(any())).thenReturn(parent); + assertEquals(1, cbeffImpl.getBIRDataFromXML(validXml).size()); + } + + /** + * Tests comprehensive BDB data extraction for specified type and subtype. + * Verifies that all BDB data can be retrieved and returned as a map + * with expected key-value pairs. + * + * @throws Exception if BDB data retrieval fails + */ + @Test + public void getAllBdbDataShouldReturnMap() throws Exception { + BIR parsed = new BIR(); + validatorStatic.when(() -> CbeffValidator.getBIRFromXML(any())).thenReturn(parsed); + validatorStatic.when(() -> CbeffValidator.getAllBDBData(parsed, "T", "ST")) + .thenReturn(Map.of("K", "V")); + assertEquals("V", cbeffImpl.getAllBDBData(validXml, "T", "ST").get("K")); + } + + /** + * Tests BIR data extraction filtered by specific type. + * Verifies that BIR data can be filtered by type parameter + * and returns the expected list of BIR objects. + * + * @throws Exception if type-based BIR extraction fails + */ + @Test + public void getBirDataFromXmlTypeShouldReturnList() throws Exception { + validatorStatic.when(() -> CbeffValidator.getBIRDataFromXMLType(any(), eq("T"))).thenReturn(birList); + assertEquals(birList, cbeffImpl.getBIRDataFromXMLType(validXml, "T")); + } + + /** + * Tests error handling when updating XML with null BIR list. + * Verifies that NullPointerException is thrown when attempting + * to update XML with null BIR list parameter. + */ + @Test + public void updateXmlWithNullBirListShouldThrowNpe() { + assertThrows(NullPointerException.class, () -> cbeffImpl.updateXML(null, validXml)); + } + + /** + * Tests error handling for BDB extraction with null file bytes. + * Verifies that appropriate exception is thrown when attempting + * to extract BDB data from null XML input through mocked validator. + */ + @Test + public void getBdbBasedOnTypeWithNullFileBytesShouldThrowViaMock() { + validatorStatic.when(() -> CbeffValidator.getBIRFromXML(isNull())).thenThrow(new IllegalArgumentException()); + assertThrows(Exception.class, () -> cbeffImpl.getBDBBasedOnType(null, "F", "ST")); + } + + /** + * Tests error handling for BDB data extraction with null XML input. + * Verifies that appropriate exception is thrown when attempting + * to extract all BDB data from null XML through mocked validator. + */ + @Test + public void getAllBdbDataWithNullXmlShouldThrowViaMock() { + validatorStatic.when(() -> CbeffValidator.getBIRFromXML(isNull())).thenThrow(new IllegalArgumentException()); + assertThrows(Exception.class, () -> cbeffImpl.getAllBDBData(null, "T", "ST")); + } + + /** + * Tests error handling for type-based BIR extraction with null XML. + * Verifies that appropriate exception is thrown when attempting + * to extract BIR data by type from null XML through mocked validator. + */ + @Test + public void getBirDataFromXmlTypeWithNullXmlShouldThrowViaMock() { + validatorStatic.when(() -> CbeffValidator.getBIRDataFromXMLType(isNull(), anyString())) + .thenThrow(new IllegalArgumentException()); + assertThrows(Exception.class, () -> cbeffImpl.getBIRDataFromXMLType(null, "T")); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/service/impl/PDFGeneratorImplTest.java b/src/test/java/io/mosip/print/service/impl/PDFGeneratorImplTest.java new file mode 100644 index 00000000..c999339d --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/PDFGeneratorImplTest.java @@ -0,0 +1,576 @@ +package io.mosip.print.service.impl; + +import com.itextpdf.text.DocumentException; +import com.itextpdf.text.pdf.PdfStamper; +import io.mosip.print.constant.PDFGeneratorExceptionCodeConstant; +import io.mosip.print.exception.PDFGeneratorException; +import io.mosip.print.model.CertificateEntry; +import io.mosip.print.model.Rectangle; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +/** + * Unit tests for {@link PDFGeneratorImpl} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the PDFGeneratorImpl class, + * including PDF generation, signing, encryption, merging, and various edge cases and error handling scenarios.

+ */ +@ExtendWith(MockitoExtension.class) +class PDFGeneratorImplTest { + + @InjectMocks + private PDFGeneratorImpl pdfGenerator; + + @Mock + private InputStream mockInputStream; + + @Mock + private BufferedImage mockBufferedImage; + + @Mock + private Provider mockProvider; + + @Mock + private X509Certificate mockCertificate; + + @Mock + private PrivateKey mockPrivateKey; + + @Mock + private CertificateEntry mockCertificateEntry; + + private byte[] testPdfBytes; + private Rectangle testRectangle; + + /** + * Sets up test fixtures before each test method execution. + * Initializes mock objects, test data, and PDF generator configuration properties. + */ + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(pdfGenerator, "pdfOwnerPassword", "ownerPassword"); + testPdfBytes = "test pdf content".getBytes(); + testRectangle = new Rectangle(10.0f, 10.0f, 100.0f, 50.0f); + + X509Certificate[] certificateChain = {mockCertificate}; + lenient().when(mockCertificateEntry.getChain()).thenReturn(certificateChain); + lenient().when(mockCertificateEntry.getPrivateKey()).thenReturn(mockPrivateKey); + lenient().when(mockProvider.getName()).thenReturn("BC"); + } + + /** + * Tests the generate method with a valid InputStream. + * Verifies that the method successfully generates a PDF and returns a ByteArrayOutputStream. + */ + @Test + void generateWithValidInputStreamShouldReturnOutputStream() throws IOException { + InputStream validInputStream = new ByteArrayInputStream("Test".getBytes()); + + OutputStream result = pdfGenerator.generate(validInputStream); + + assertNotNull(result); + assertTrue(result instanceof ByteArrayOutputStream); + } + + /** + * Tests the generate method with a null InputStream. + * Verifies that the method throws a PDFGeneratorException for null input. + */ + @Test + void generateWithNullInputStreamShouldThrowPdfGeneratorException() { + InputStream nullInputStream = null; + + PDFGeneratorException exception = assertThrows(PDFGeneratorException.class, + () -> pdfGenerator.generate(nullInputStream)); + + assertTrue(exception.getErrorCode().equals( + PDFGeneratorExceptionCodeConstant.INPUTSTREAM_NULL_EMPTY_EXCEPTION.getErrorCode())); + } + + /** + * Tests the generate method with a valid HTML template string. + * Verifies that the method successfully processes the template and returns a ByteArrayOutputStream. + */ + @Test + void generateWithValidTemplateShouldReturnOutputStream() throws IOException { + String template = "Test Template"; + + OutputStream result = pdfGenerator.generate(template); + + assertNotNull(result); + assertTrue(result instanceof ByteArrayOutputStream); + } + + /** + * Tests the generate method with an invalid (null) template. + * Verifies that the method throws a PDFGeneratorException for null template input. + */ + @Test + void generateWithInvalidTemplateShouldThrowPdfGeneratorException() { + String invalidTemplate = null; + + assertThrows(PDFGeneratorException.class, + () -> pdfGenerator.generate(invalidTemplate)); + } + + /** + * Tests the generate method with valid file paths for template and output. + * Verifies that the method creates a PDF file at the specified output location. + */ + @Test + void generateWithValidFilePathsShouldCreateFile() throws IOException { + String templatePath = createTempHtmlFile(); + String outputPath = System.getProperty("java.io.tmpdir") + "/"; + String fileName = "test-output"; + + pdfGenerator.generate(templatePath, outputPath, fileName); + + File outputFile = new File(outputPath + fileName + ".pdf"); + assertTrue(outputFile.exists()); + outputFile.delete(); + } + + /** + * Tests the generate method with invalid file paths. + * Verifies that the method throws a PDFGeneratorException when template or output paths are invalid. + */ + @Test + void generateWithInvalidFilePathsShouldThrowPdfGeneratorException() { + String invalidTemplatePath = "/invalid/path/template.html"; + String outputPath = "/invalid/output/path/"; + String fileName = "test"; + + assertThrows(PDFGeneratorException.class, + () -> pdfGenerator.generate(invalidTemplatePath, outputPath, fileName)); + } + + /** + * Tests the generate method with InputStream and resource location. + * Verifies that the method successfully generates PDF using template with external resources. + */ + @Test + void generateWithInputStreamAndResourceLocationShouldReturnOutputStream() throws IOException { + File tempFile = File.createTempFile("test", ".html"); + tempFile.deleteOnExit(); + java.nio.file.Files.write(tempFile.toPath(), "Test".getBytes()); + + String resourceLocation = tempFile.getParentFile().toURI().toString(); + + try (InputStream validInputStream = new java.io.FileInputStream(tempFile)) { + OutputStream result = pdfGenerator.generate(validInputStream, resourceLocation); + assertNotNull(result); + assertTrue(result instanceof ByteArrayOutputStream); + } finally { + tempFile.delete(); + } + } + + /** + * Tests the generate method with null InputStream and valid resource location. + * Verifies that the method throws a PDFGeneratorException for null InputStream input. + */ + @Test + void generateWithNullInputStreamAndResourceLocationShouldThrowPdfGeneratorException() { + InputStream nullInputStream = null; + String resourceLocation = "classpath:/"; + + assertThrows(PDFGeneratorException.class, + () -> pdfGenerator.generate(nullInputStream, resourceLocation)); + } + + /** + * Tests the asPDF method with valid BufferedImage list. + * Verifies that the method converts images to PDF bytes successfully. + */ + @Test + void asPdfWithValidBufferedImagesShouldReturnPdfBytes() throws IOException { + List images = new ArrayList<>(); + BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + images.add(image); + + byte[] result = pdfGenerator.asPDF(images); + + assertNotNull(result); + assertTrue(result.length > 0); + } + + /** + * Tests the asPDF method when ImageIO throws an exception. + * Verifies that the method properly handles ImageIO exceptions and throws PDFGeneratorException. + */ + @Test + void asPdfWithImageIoExceptionShouldThrowPdfGeneratorException() throws IOException { + List images = new ArrayList<>(); + images.add(mockBufferedImage); + + try (MockedStatic imageIOMock = mockStatic(ImageIO.class)) { + imageIOMock.when(() -> ImageIO.write(any(BufferedImage.class), anyString(), any(ByteArrayOutputStream.class))) + .thenThrow(new IOException("ImageIO error")); + + assertThrows(PDFGeneratorException.class, () -> pdfGenerator.asPDF(images)); + } + } + + /** + * Tests the mergePDF method with valid PDF files. + * Verifies that the method successfully merges multiple PDF files into a single byte array. + */ + @Test + void mergePdfWithValidPdfFilesShouldReturnMergedPdfBytes() throws IOException, URISyntaxException { + List pdfFiles = new ArrayList<>(); + + byte[] samplePdf = createSamplePDF(); + File tempFile = createTempPDFFile(samplePdf); + pdfFiles.add(tempFile.toURI().toURL()); + + byte[] result = pdfGenerator.mergePDF(pdfFiles); + + assertNotNull(result); + assertTrue(result.length > 0); + tempFile.delete(); + } + + /** + * Tests the mergePDF method with invalid URL. + * Verifies that the method throws a PDFGeneratorException when provided with invalid PDF URLs. + */ + @Test + void mergePdfWithInvalidUrlShouldThrowPdfGeneratorException() throws IOException, URISyntaxException { + List pdfFiles = new ArrayList<>(); + pdfFiles.add(new URI("file:///invalid/path/file.pdf").toURL()); + + assertThrows(PDFGeneratorException.class, () -> pdfGenerator.mergePDF(pdfFiles)); + } + + /** + * Tests the signAndEncryptPDF method with password encryption logic. + * Verifies that the method handles password-based encryption correctly. + */ + @Test + void signAndEncryptPdfPasswordLogicWithPasswordShouldHandleEncryption() throws Exception { + byte[] validPdfBytes = createSimpleValidPdf(); + + PDFGeneratorException exception = assertThrows(PDFGeneratorException.class, () -> { + pdfGenerator.signAndEncryptPDF(validPdfBytes, testRectangle, + "Test signing reason", 1, mockProvider, mockCertificateEntry, "testPassword"); + }); + + assertEquals(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + exception.getErrorCode()); + assertTrue(exception.getCause() instanceof DocumentException); + } + + /** + * Tests the signAndEncryptPDF method without password (null). + * Verifies that the method handles null password input appropriately. + */ + @Test + void signAndEncryptPdfPasswordLogicWithoutPasswordShouldHandleNullPassword() throws Exception { + byte[] validPdfBytes = createSimpleValidPdf(); + + PDFGeneratorException exception = assertThrows(PDFGeneratorException.class, () -> { + pdfGenerator.signAndEncryptPDF(validPdfBytes, testRectangle, + "Test signing reason", 1, mockProvider, mockCertificateEntry, null); + }); + + assertEquals(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + exception.getErrorCode()); + } + + /** + * Tests the signAndEncryptPDF method with empty or whitespace password. + * Verifies that the method handles empty and whitespace passwords correctly. + */ + @Test + void signAndEncryptPdfPasswordLogicWithEmptyPasswordShouldHandleEmptyValues() throws Exception { + byte[] validPdfBytes = createSimpleValidPdf(); + + PDFGeneratorException exception1 = assertThrows(PDFGeneratorException.class, () -> { + pdfGenerator.signAndEncryptPDF(validPdfBytes, testRectangle, + "Test signing reason", 1, mockProvider, mockCertificateEntry, ""); + }); + + PDFGeneratorException exception2 = assertThrows(PDFGeneratorException.class, () -> { + pdfGenerator.signAndEncryptPDF(validPdfBytes, testRectangle, + "Test signing reason", 1, mockProvider, mockCertificateEntry, " "); + }); + + assertEquals(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + exception1.getErrorCode()); + assertEquals(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + exception2.getErrorCode()); + } + + /** + * Tests the signAndEncryptPDF method with invalid PDF bytes. + * Verifies that the method throws InvalidPdfException when provided with invalid PDF data. + */ + @Test + void signAndEncryptPdfWithInvalidPdfBytesShouldThrowInvalidPdfException() throws Exception { + byte[] invalidPdfBytes = "not a valid pdf".getBytes(); + + com.itextpdf.text.exceptions.InvalidPdfException exception = + assertThrows(com.itextpdf.text.exceptions.InvalidPdfException.class, () -> { + pdfGenerator.signAndEncryptPDF(invalidPdfBytes, testRectangle, + "Test signing reason", 1, mockProvider, mockCertificateEntry, "testPassword"); + }); + + assertNotNull(exception.getMessage()); + assertTrue(exception.getMessage().contains("PDF header")); + } + + /** + * Tests the signAndEncryptPDF method with multiple certificate chain processing. + * Verifies that the method handles certificate chains with multiple certificates correctly. + */ + @Test + void signAndEncryptPdfCertificateChainProcessingShouldHandleMultipleCertificates() throws Exception { + byte[] validPdfBytes = createSimpleValidPdf(); + + X509Certificate cert1 = mock(X509Certificate.class); + X509Certificate cert2 = mock(X509Certificate.class); + X509Certificate[] multiCertChain = {cert1, cert2}; + + when(mockCertificateEntry.getChain()).thenReturn(multiCertChain); + + PDFGeneratorException exception = assertThrows(PDFGeneratorException.class, () -> { + pdfGenerator.signAndEncryptPDF(validPdfBytes, testRectangle, + "Test signing reason", 1, mockProvider, mockCertificateEntry, null); + }); + + assertEquals(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + exception.getErrorCode()); + } + + /** + * Tests the signAndEncryptPDF method with different page numbers and rectangle coordinates. + * Verifies that the method handles various signature placement parameters correctly. + */ + @Test + void signAndEncryptPdfWithDifferentParametersShouldHandleCustomValues() throws Exception { + byte[] validPdfBytes = createSimpleValidPdf(); + Rectangle customRectangle = new Rectangle(100.0f, 100.0f, 300.0f, 200.0f); + + PDFGeneratorException exception = assertThrows(PDFGeneratorException.class, () -> { + pdfGenerator.signAndEncryptPDF(validPdfBytes, customRectangle, + "Custom signing reason", 2, mockProvider, mockCertificateEntry, "customPassword"); + }); + + assertEquals(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + exception.getErrorCode()); + } + + /** + * Tests the signAndEncryptPDF method with null reason parameter. + * Verifies that the method handles null signing reason appropriately. + */ + @Test + void signAndEncryptPdfWithNullReasonShouldHandleNullReason() throws Exception { + byte[] validPdfBytes = createSimpleValidPdf(); + + PDFGeneratorException exception = assertThrows(PDFGeneratorException.class, () -> { + pdfGenerator.signAndEncryptPDF(validPdfBytes, testRectangle, + null, 1, mockProvider, mockCertificateEntry, null); + }); + + assertEquals(PDFGeneratorExceptionCodeConstant.PDF_EXCEPTION.getErrorCode(), + exception.getErrorCode()); + } + + /** + * Tests exception handling in the catch block using corrupted but readable PDF. + * Verifies that the method properly handles PDF processing exceptions. + */ + @Test + void signAndEncryptPdfDocumentExceptionHandlingShouldHandleCorruptedPdf() throws Exception { + byte[] corruptedPdf = createCorruptedButReadablePdf(); + + Exception exception = assertThrows(Exception.class, () -> { + pdfGenerator.signAndEncryptPDF(corruptedPdf, testRectangle, + "Test signing reason", 1, mockProvider, mockCertificateEntry, "testPassword"); + }); + + assertTrue(exception instanceof PDFGeneratorException || + exception instanceof DocumentException || + exception instanceof com.itextpdf.text.exceptions.InvalidPdfException); + } + + /** + * Tests that the signAndEncryptPDF method reaches the signing logic successfully. + * Verifies that the method progresses through password logic and setup to the signing stage. + */ + @Test + void signAndEncryptPdfReachesSigningLogicShouldProgressToSigningStage() throws Exception { + byte[] validPdfBytes = createSimpleValidPdf(); + + try { + pdfGenerator.signAndEncryptPDF(validPdfBytes, testRectangle, + "Test reason", 1, mockProvider, mockCertificateEntry, "password"); + + fail("Expected an exception due to incomplete signing process"); + + } catch (PDFGeneratorException e) { + assertTrue(e.getCause() instanceof DocumentException); + assertTrue(e.getCause().getMessage().contains("Signature defined")); + + } catch (Exception e) { + assertNotNull(e); + } + } + + /** + * Tests the closeQuietly method with successful close operation. + * Verifies that the method completes without throwing exceptions when PdfStamper closes successfully. + */ + @Test + void closeQuietlyWithSuccessfulCloseShouldCompleteWithoutException() throws Exception { + PdfStamper mockStamper = mock(PdfStamper.class); + doNothing().when(mockStamper).close(); + + ReflectionTestUtils.invokeMethod(pdfGenerator, "closeQuietly", mockStamper); + + verify(mockStamper, times(1)).close(); + } + + /** + * Tests the closeQuietly method when DocumentException occurs during close. + * Verifies that the method throws PDFGeneratorException when DocumentException is encountered. + */ + @Test + void closeQuietlyWithDocumentExceptionShouldThrowPdfGeneratorException() throws Exception { + PdfStamper mockStamper = mock(PdfStamper.class); + doThrow(new DocumentException("Close failed")).when(mockStamper).close(); + + assertThrows(PDFGeneratorException.class, () -> { + ReflectionTestUtils.invokeMethod(pdfGenerator, "closeQuietly", mockStamper); + }); + } + + /** + * Tests the closeQuietly method when IOException occurs during close. + * Verifies that the method handles IOException appropriately during PdfStamper close operations. + */ + @Test + void closeQuietlyWithIoExceptionShouldHandleProperly() throws Exception { + PdfStamper mockStamper = mock(PdfStamper.class); + doThrow(new IOException("IO error during close")).when(mockStamper).close(); + + try { + ReflectionTestUtils.invokeMethod(pdfGenerator, "closeQuietly", mockStamper); + } catch (Exception e) { + assertTrue(e.getCause() instanceof IOException); + } + } + + /** + * Creates a temporary HTML file for testing purposes. + * + * @return the absolute path of the created temporary HTML file + * @throws IOException if file creation fails + */ + private String createTempHtmlFile() throws IOException { + File tempFile = File.createTempFile("test", ".html"); + tempFile.deleteOnExit(); + java.nio.file.Files.write(tempFile.toPath(), "Test".getBytes()); + return tempFile.getAbsolutePath(); + } + + /** + * Creates a sample PDF as byte array for testing purposes. + * + * @return byte array containing a valid PDF document + * @throws IOException if PDF creation fails + */ + private byte[] createSamplePDF() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + com.itextpdf.kernel.pdf.PdfWriter writer = new com.itextpdf.kernel.pdf.PdfWriter(baos); + com.itextpdf.kernel.pdf.PdfDocument pdfDoc = new com.itextpdf.kernel.pdf.PdfDocument(writer); + com.itextpdf.layout.Document document = new com.itextpdf.layout.Document(pdfDoc); + document.add(new com.itextpdf.layout.element.Paragraph("Test PDF")); + document.close(); + return baos.toByteArray(); + } + + /** + * Creates a temporary PDF file from byte array. + * + * @param pdfBytes the PDF content as byte array + * @return temporary File object containing the PDF + * @throws IOException if file creation fails + */ + private File createTempPDFFile(byte[] pdfBytes) throws IOException { + File tempFile = File.createTempFile("test", ".pdf"); + tempFile.deleteOnExit(); + java.nio.file.Files.write(tempFile.toPath(), pdfBytes); + return tempFile; + } + + /** + * Creates a simple valid PDF that can be parsed by PdfReader. + * + * @return byte array containing a valid PDF document for signing tests + * @throws IOException if PDF creation fails + */ + private byte[] createSimpleValidPdf() throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + com.itextpdf.text.Document document = new com.itextpdf.text.Document(); + com.itextpdf.text.pdf.PdfWriter.getInstance(document, baos); + document.open(); + document.add(new com.itextpdf.text.Paragraph("Simple test PDF content for signing tests")); + document.close(); + return baos.toByteArray(); + } catch (DocumentException e) { + throw new IOException("Failed to create test PDF", e); + } + } + + /** + * Creates a PDF that can be read by PdfReader but may cause issues during stamping. + * + * @return byte array containing a corrupted but readable PDF + * @throws IOException if PDF creation fails + */ + private byte[] createCorruptedButReadablePdf() throws IOException { + byte[] validPdf = createSimpleValidPdf(); + byte[] corruptedPdf = validPdf.clone(); + + if (corruptedPdf.length > 100) { + corruptedPdf[50] = (byte) 0xFF; + corruptedPdf[51] = (byte) 0xFF; + } + + return corruptedPdf; + } +} diff --git a/src/test/java/io/mosip/print/service/impl/PrintRestClientServiceImplTest.java b/src/test/java/io/mosip/print/service/impl/PrintRestClientServiceImplTest.java new file mode 100644 index 00000000..a0d16d34 --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/PrintRestClientServiceImplTest.java @@ -0,0 +1,449 @@ +package io.mosip.print.service.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +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.MediaType; + +import io.mosip.print.constant.ApiName; +import io.mosip.print.exception.ApisResourceAccessException; +import io.mosip.print.util.RestApiClient; + +/** + * Test class for PrintRestClientServiceImpl. + * Tests REST client service operations including GET and POST API calls + * with various parameter combinations and error handling scenarios. + */ +@RunWith(MockitoJUnitRunner.class) +public class PrintRestClientServiceImplTest { + + @InjectMocks + private PrintRestClientServiceImpl printRestClientService; + + @Mock + private RestApiClient restApiClient; + + @Mock + private Environment env; + + private String apiHostIpPort; + private List pathSegments; + private String queryParamName; + private String queryParamValue; + private List queryParamNameList; + private List queryParamValueList; + private Object requestedData; + private Object expectedResponse; + private Class responseType; + + /** + * Sets up test data before each test method execution. + * Initializes common test objects and mock responses. + */ + @Before + public void setUp() { + apiHostIpPort = "http://localhost:8080/api"; + pathSegments = Arrays.asList("v1", "users"); + queryParamName = "status,type"; + queryParamValue = "active,admin"; + queryParamNameList = Arrays.asList("status", "type"); + queryParamValueList = Arrays.asList("active", "admin"); + requestedData = new Object(); + expectedResponse = new Object(); + responseType = Object.class; + } + + /** + * Tests GET API with path segments and query parameters. + * Verifies successful API call with proper URI construction. + */ + @Test + public void getApiWithStringParamsReturnsExpectedResponse() throws Exception { + when(env.getProperty("MASTER")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenReturn(expectedResponse); + + Object result = printRestClientService.getApi(ApiName.MASTER, pathSegments, queryParamName, queryParamValue, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests GET API with list parameters. + * Verifies successful API call with list-based query parameters. + */ + @Test + public void getApiWithListParamsReturnsExpectedResponse() throws Exception { + when(env.getProperty("IDREPOSITORY")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenReturn(expectedResponse); + + Object result = printRestClientService.getApi(ApiName.IDREPOSITORY, pathSegments, queryParamNameList, queryParamValueList, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests GET API with null API host configuration. + * Verifies that null is returned when API host is not configured. + */ + @Test + public void getApiWithNullApiHostReturnsNull() throws Exception { + when(env.getProperty("AUTH")).thenReturn(null); + + Object result = printRestClientService.getApi(ApiName.AUTH, pathSegments, queryParamName, queryParamValue, responseType); + + assertNull(result); + verify(restApiClient, times(0)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests GET API with null path segments. + * Verifies successful API call without path segments. + */ + @Test + public void getApiWithNullPathSegmentsReturnsExpectedResponse() throws Exception { + when(env.getProperty("TEMPLATES")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenReturn(expectedResponse); + + Object result = printRestClientService.getApi(ApiName.TEMPLATES, null, queryParamName, queryParamValue, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests GET API with empty path segments. + * Verifies successful API call with empty path segments list. + */ + @Test + public void getApiWithEmptyPathSegmentsReturnsExpectedResponse() throws Exception { + when(env.getProperty("IDA")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenReturn(expectedResponse); + + Object result = printRestClientService.getApi(ApiName.IDA, new ArrayList<>(), queryParamName, queryParamValue, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests GET API with null query parameters. + * Verifies successful API call without query parameters. + */ + @Test + public void getApiWithNullQueryParamsReturnsExpectedResponse() throws Exception { + when(env.getProperty("CRYPTOMANAGERDECRYPT")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenReturn(expectedResponse); + + Object result = printRestClientService.getApi(ApiName.CRYPTOMANAGERDECRYPT, pathSegments, (String) null, null, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests GET API with empty string query parameters. + * Verifies successful API call with empty string query parameters. + */ + @Test + public void getApiWithEmptyStringQueryParamsReturnsExpectedResponse() throws Exception { + when(env.getProperty("RETRIEVEIDENTITY")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenReturn(expectedResponse); + + Object result = printRestClientService.getApi(ApiName.RETRIEVEIDENTITY, pathSegments, "", "", responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests GET API exception handling. + * Verifies that ApisResourceAccessException is thrown when underlying API call fails. + */ + @Test(expected = ApisResourceAccessException.class) + public void getApiWithExceptionThrowsApisResourceAccessException() throws Exception { + when(env.getProperty("DIGITALSIGNATURE")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenThrow(new RuntimeException("API call failed")); + + printRestClientService.getApi(ApiName.DIGITALSIGNATURE, pathSegments, queryParamName, queryParamValue, responseType); + } + + /** + * Tests GET API with list parameters exception handling. + * Verifies exception handling for list-based parameter API calls. + */ + @Test(expected = ApisResourceAccessException.class) + public void getApiWithListParamsExceptionThrowsApisResourceAccessException() throws Exception { + when(env.getProperty("CREATEVID")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenThrow(new RuntimeException("API call failed")); + + printRestClientService.getApi(ApiName.CREATEVID, pathSegments, queryParamNameList, queryParamValueList, responseType); + } + + /** + * Tests POST API with media type. + * Verifies successful POST API call with specified media type. + */ + @Test + public void postApiWithMediaTypeReturnsExpectedResponse() throws Exception { + when(env.getProperty("PDFSIGN")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.PDFSIGN, queryParamName, queryParamValue, requestedData, responseType, MediaType.APPLICATION_JSON); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API without media type. + * Verifies successful POST API call using default media type. + */ + @Test + public void postApiWithoutMediaTypeReturnsExpectedResponse() throws Exception { + when(env.getProperty("ENCRYPTIONSERVICE")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.ENCRYPTIONSERVICE, queryParamName, queryParamValue, requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API with path segments. + * Verifies successful POST API call with path segments and query parameters. + */ + @Test + public void postApiWithPathSegmentsReturnsExpectedResponse() throws Exception { + when(env.getProperty("USERDETAILS")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.USERDETAILS, pathSegments, queryParamName, queryParamValue, requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API with list parameters and media type. + * Verifies successful POST API call with list-based parameters and media type. + */ + @Test + public void postApiWithListParamsAndMediaTypeReturnsExpectedResponse() throws Exception { + when(env.getProperty("CREATEDATASHARE")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.CREATEDATASHARE, MediaType.APPLICATION_JSON, pathSegments, queryParamNameList, queryParamValueList, requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API with null API host configuration. + * Verifies that null is returned when API host is not configured. + */ + @Test + public void postApiWithNullApiHostReturnsNull() throws Exception { + when(env.getProperty("AUDIT")).thenReturn(null); + + Object result = printRestClientService.postApi(ApiName.AUDIT, queryParamName, queryParamValue, requestedData, responseType, MediaType.APPLICATION_JSON); + + assertNull(result); + verify(restApiClient, times(0)).postApi(anyString(), any(MediaType.class), any(), any(Class.class)); + } + + /** + * Tests POST API with null query parameters. + * Verifies successful POST API call without query parameters. + */ + @Test + public void postApiWithNullQueryParamsReturnsExpectedResponse() throws Exception { + when(env.getProperty("GETUINBYVID")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.GETUINBYVID, pathSegments, null, null, requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API with empty query parameter lists. + * Verifies successful POST API call with empty parameter lists. + */ + @Test + public void postApiWithEmptyQueryParamListsReturnsExpectedResponse() throws Exception { + when(env.getProperty("RIDGENERATION")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.RIDGENERATION, MediaType.APPLICATION_JSON, pathSegments, new ArrayList<>(), new ArrayList<>(), requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API with null path segments. + * Verifies successful POST API call without path segments. + */ + @Test + public void postApiWithNullPathSegmentsReturnsExpectedResponse() throws Exception { + when(env.getProperty("INTERNALAUTH")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.INTERNALAUTH, null, queryParamName, queryParamValue, requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API with empty path segments. + * Verifies successful POST API call with empty path segments list. + */ + @Test + public void postApiWithEmptyPathSegmentsReturnsExpectedResponse() throws Exception { + when(env.getProperty("DEVICEVALIDATEHISTORY")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.DEVICEVALIDATEHISTORY, MediaType.APPLICATION_JSON, new ArrayList<>(), queryParamNameList, queryParamValueList, requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API with empty string query parameters. + * Verifies successful POST API call with empty string query parameters. + */ + @Test + public void postApiWithEmptyStringQueryParamsReturnsExpectedResponse() throws Exception { + when(env.getProperty("IDSCHEMAURL")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + Object result = printRestClientService.postApi(ApiName.IDSCHEMAURL, pathSegments, "", "", requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType)); + } + + /** + * Tests POST API exception handling. + * Verifies that ApisResourceAccessException is thrown when underlying POST API call fails. + */ + @Test(expected = ApisResourceAccessException.class) + public void postApiWithExceptionThrowsApisResourceAccessException() throws Exception { + when(env.getProperty("ENCRYPTURL")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), any(MediaType.class), eq(requestedData), eq(responseType))) + .thenThrow(new RuntimeException("POST API call failed")); + + printRestClientService.postApi(ApiName.ENCRYPTURL, queryParamName, queryParamValue, requestedData, responseType, MediaType.APPLICATION_JSON); + } + + /** + * Tests POST API with path segments exception handling. + * Verifies exception handling for POST API calls with path segments. + */ + @Test(expected = ApisResourceAccessException.class) + public void postApiWithPathSegmentsExceptionThrowsApisResourceAccessException() throws Exception { + when(env.getProperty("IDAUTHENCRYPTION")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType))) + .thenThrow(new RuntimeException("POST API call failed")); + + printRestClientService.postApi(ApiName.IDAUTHENCRYPTION, pathSegments, queryParamName, queryParamValue, requestedData, responseType); + } + + /** + * Tests POST API with list parameters exception handling. + * Verifies exception handling for POST API calls with list-based parameters. + */ + @Test(expected = ApisResourceAccessException.class) + public void postApiWithListParamsExceptionThrowsApisResourceAccessException() throws Exception { + when(env.getProperty("REVERSEDATASYNC")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq(MediaType.APPLICATION_JSON), eq(requestedData), eq(responseType))) + .thenThrow(new RuntimeException("POST API call failed")); + + printRestClientService.postApi(ApiName.REVERSEDATASYNC, MediaType.APPLICATION_JSON, pathSegments, queryParamNameList, queryParamValueList, requestedData, responseType); + } + + /** + * Tests GET API with pathSegments containing null and empty elements. + * Verifies that null and empty path segments are properly filtered out. + */ + @Test + public void getApiWithNullAndEmptyPathSegmentsReturnsExpectedResponse() throws Exception { + when(env.getProperty("NGINXDMZURL")).thenReturn(apiHostIpPort); + when(restApiClient.getApi(any(URI.class), eq(responseType))).thenReturn(expectedResponse); + + List pathSegmentsWithNullAndEmpty = Arrays.asList("v1", null, "", "users", null); + Object result = printRestClientService.getApi(ApiName.NGINXDMZURL, pathSegmentsWithNullAndEmpty, queryParamName, queryParamValue, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).getApi(any(URI.class), eq(responseType)); + } + + /** + * Tests POST API with pathSegments containing null and empty elements. + * Verifies that null and empty path segments are properly filtered out in POST calls. + */ + @Test + public void postApiWithNullAndEmptyPathSegmentsReturnsExpectedResponse() throws Exception { + when(env.getProperty("REGISTRATIONCONNECTOR")).thenReturn(apiHostIpPort); + when(restApiClient.postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType))) + .thenReturn(expectedResponse); + + List pathSegmentsWithNullAndEmpty = Arrays.asList("api", null, "", "register", null); + Object result = printRestClientService.postApi(ApiName.REGISTRATIONCONNECTOR, pathSegmentsWithNullAndEmpty, queryParamName, queryParamValue, requestedData, responseType); + + assertNotNull(result); + assertEquals(expectedResponse, result); + verify(restApiClient, times(1)).postApi(anyString(), eq((MediaType) null), eq(requestedData), eq(responseType)); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/service/impl/PrintServiceImplTest.java b/src/test/java/io/mosip/print/service/impl/PrintServiceImplTest.java new file mode 100644 index 00000000..f883bb7a --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/PrintServiceImplTest.java @@ -0,0 +1,858 @@ +package io.mosip.print.service.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.security.InvalidKeyException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.core.env.Environment; +import org.springframework.test.util.ReflectionTestUtils; + +import com.fasterxml.jackson.core.JsonParseException; + +import io.mosip.print.constant.QrVersion; +import io.mosip.print.constant.UinCardType; +import io.mosip.print.dto.CryptoWithPinRequestDto; +import io.mosip.print.dto.CryptoWithPinResponseDto; +import io.mosip.print.dto.DataShare; +import io.mosip.print.dto.JsonValue; +import io.mosip.print.exception.PDFGeneratorException; +import io.mosip.print.exception.PDFSignatureException; +import io.mosip.print.exception.QrcodeGenerationException; +import io.mosip.print.exception.TemplateProcessingFailureException; +import io.mosip.print.exception.UINNotFoundInDatabase; +import io.mosip.print.model.Event; +import io.mosip.print.model.EventModel; +import io.mosip.print.service.UinCardGenerator; +import io.mosip.print.spi.CbeffUtil; +import io.mosip.print.spi.QrCodeGenerator; +import io.mosip.print.util.AuditLogRequestBuilder; +import io.mosip.print.util.CbeffToBiometricUtil; +import io.mosip.print.util.CryptoCoreUtil; +import io.mosip.print.util.CryptoUtil; +import io.mosip.print.util.DataShareUtil; +import io.mosip.print.util.DateUtils; +import io.mosip.print.util.JsonUtil; +import io.mosip.print.util.RestApiClient; +import io.mosip.print.util.TemplateGenerator; +import io.mosip.print.util.Utilities; +import io.mosip.print.util.WebSubSubscriptionHelper; + +/** + * Unit tests for {@link PrintServiceImpl} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the PrintServiceImpl class, + * including card generation, document processing, attribute decryption, template processing, and various + * exception handling scenarios.

+ */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class PrintServiceImplTest { + + @InjectMocks + private PrintServiceImpl printService; + + @Mock + private WebSubSubscriptionHelper webSubSubscriptionHelper; + + @Mock + private DataShareUtil dataShareUtil; + + @Mock + private CryptoUtil cryptoUtil; + + @Mock + private RestApiClient restApiClient; + + @Mock + private CryptoCoreUtil cryptoCoreUtil; + + @Mock + private AuditLogRequestBuilder auditLogRequestBuilder; + + @Mock + private TemplateGenerator templateGenerator; + + @Mock + private Utilities utilities; + + @Mock + private UinCardGenerator uinCardGenerator; + + @Mock + private QrCodeGenerator qrCodeGenerator; + + @Mock + private CbeffUtil cbeffutil; + + @Mock + private Environment env; + + private EventModel mockEventModel; + private String mockCredential; + private String mockDecodedCredential; + private DataShare mockDataShare; + + /** + * Sets up test fixtures before each test method execution. + * Initializes mock objects, test data, and configures default behavior for all dependencies. + */ + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(printService, "partnerId", "partner123"); + ReflectionTestUtils.setField(printService, "policyId", "policy123"); + ReflectionTestUtils.setField(printService, "templateLang", "eng"); + ReflectionTestUtils.setField(printService, "supportedLang", "eng,fra"); + ReflectionTestUtils.setField(printService, "verifyCredentialsFlag", false); + ReflectionTestUtils.setField(printService, "isPasswordProtected", false); + + mockEventModel = createMockEventModel(); + mockCredential = createMockCredential(); + mockDecodedCredential = createMockDecodedCredential(); + mockDataShare = createMockDataShare(); + + setupDefaultMockBehavior(); + } + + /** + * Tests the generateCard method when a general runtime exception occurs during processing. + * Verifies that the method handles decryption failures gracefully and returns false. + */ + @Test + void generateCardWithGeneralExceptionShouldReturnFalse() throws Exception { + when(cryptoCoreUtil.decrypt(anyString())).thenThrow(new RuntimeException("Decryption failed")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class)) { + dateUtilsMock.when(DateUtils::getUTCCurrentDateTime).thenReturn(LocalDateTime.now()); + + boolean result = printService.generateCard(mockEventModel); + assertFalse(result); + } + } + + /** + * Tests the getDocuments method when QR code generation fails. + * Verifies that QrcodeGenerationException is handled properly and the method returns false. + */ + @Test + void getDocumentsWithQrcodeGenerationExceptionShouldReturnFalse() throws Exception { + when(qrCodeGenerator.generateQrCode(anyString(), any(QrVersion.class))) + .thenThrow(new QrcodeGenerationException("QR generation failed", "QR_ERROR", new RuntimeException("Root cause"))); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + + dateUtilsMock.when(DateUtils::getUTCCurrentDateTime).thenReturn(LocalDateTime.now()); + setupJsonUtilMocks(jsonUtilMock); + + boolean result = printService.generateCard(mockEventModel); + assertFalse(result); + } + } + + /** + * Tests the getDocuments method when UIN is not found in database. + * Verifies that UINNotFoundInDatabase exception is handled gracefully and returns false. + */ + @Test + void getDocumentsWithUinNotFoundExceptionShouldReturnFalse() throws Exception { + when(templateGenerator.getTemplate(anyString(), any(Map.class), anyString())) + .thenThrow(new UINNotFoundInDatabase("UIN not found", new RuntimeException("Database error"))); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + + dateUtilsMock.when(DateUtils::getUTCCurrentDateTime).thenReturn(LocalDateTime.now()); + setupJsonUtilMocks(jsonUtilMock); + + boolean result = printService.generateCard(mockEventModel); + assertFalse(result); + } + } + + /** + * Tests the getDocuments method when template processing fails. + * Verifies that TemplateProcessingFailureException is handled appropriately and returns false. + */ + @Test + void getDocumentsWithTemplateProcessingFailureShouldReturnFalse() throws Exception { + when(templateGenerator.getTemplate(anyString(), any(Map.class), anyString())) + .thenThrow(new TemplateProcessingFailureException("Template processing failed")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + + dateUtilsMock.when(DateUtils::getUTCCurrentDateTime).thenReturn(LocalDateTime.now()); + setupJsonUtilMocks(jsonUtilMock); + + boolean result = printService.generateCard(mockEventModel); + assertFalse(result); + } + } + + /** + * Tests the getDocuments method when PDF generation encounters an exception. + * Verifies that PDFGeneratorException is handled correctly and the method returns false. + */ + @Test + void getDocumentsWithPdfGeneratorExceptionShouldReturnFalse() throws Exception { + when(uinCardGenerator.generateUinCard(any(InputStream.class), any(UinCardType.class), anyString())) + .thenThrow(new PDFGeneratorException("PDF_ERROR", "PDF generation failed")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + + dateUtilsMock.when(DateUtils::getUTCCurrentDateTime).thenReturn(LocalDateTime.now()); + setupJsonUtilMocks(jsonUtilMock); + + boolean result = printService.generateCard(mockEventModel); + assertFalse(result); + } + } + + /** + * Tests the getDocuments method when PDF signature process fails. + * Verifies that PDFSignatureException is handled properly and returns false. + */ + @Test + void getDocumentsWithPdfSignatureExceptionShouldReturnFalse() throws Exception { + when(uinCardGenerator.generateUinCard(any(InputStream.class), any(UinCardType.class), anyString())) + .thenThrow(new PDFSignatureException("PDF signature failed")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + + dateUtilsMock.when(DateUtils::getUTCCurrentDateTime).thenReturn(LocalDateTime.now()); + setupJsonUtilMocks(jsonUtilMock); + + boolean result = printService.generateCard(mockEventModel); + assertFalse(result); + } + } + + /** + * Tests the getDocuments method when template generator returns null. + * Verifies that null template response is handled gracefully and returns false. + */ + @Test + void getDocumentsWithNullTemplateShouldReturnFalse() throws Exception { + when(templateGenerator.getTemplate(anyString(), any(Map.class), anyString())).thenReturn(null); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + + dateUtilsMock.when(DateUtils::getUTCCurrentDateTime).thenReturn(LocalDateTime.now()); + setupJsonUtilMocks(jsonUtilMock); + + boolean result = printService.generateCard(mockEventModel); + assertFalse(result); + } + } + + /** + * Tests the decryptAttribute method with protected attributes present in credential. + * Verifies that encrypted attributes are properly decrypted using PIN-based decryption. + */ + @Test + void decryptAttributeWithProtectedAttributesShouldDecryptSuccessfully() throws Exception { + String credentialWithProtected = createCredentialWithProtectedAttributes(); + org.json.JSONObject data = new org.json.JSONObject(); + data.put("name", "encryptedName"); + + CryptoWithPinResponseDto response = new CryptoWithPinResponseDto(); + response.setData("decryptedName"); + when(cryptoUtil.decryptWithPin(any(CryptoWithPinRequestDto.class))).thenReturn(response); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "decryptAttribute", org.json.JSONObject.class, String.class, String.class); + method.setAccessible(true); + + org.json.JSONObject result = (org.json.JSONObject) method.invoke( + printService, data, "1234", credentialWithProtected); + + assertEquals("decryptedName", result.getString("name")); + } + + /** + * Tests the decryptAttribute method when no protected attributes are present. + * Verifies that data remains unchanged when no decryption is required. + */ + @Test + void decryptAttributeWithNoProtectedAttributesShouldReturnOriginalData() throws Exception { + String credentialWithoutProtected = "{\"credentialSubject\":\"{\\\"UIN\\\":\\\"1234567890\\\"}\"}"; + org.json.JSONObject data = new org.json.JSONObject(); + data.put("name", "testName"); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "decryptAttribute", org.json.JSONObject.class, String.class, String.class); + method.setAccessible(true); + + org.json.JSONObject result = (org.json.JSONObject) method.invoke( + printService, data, "1234", credentialWithoutProtected); + + assertEquals("testName", result.getString("name")); + } + + /** + * Tests the decryptAttribute method when crypto exception occurs during decryption. + * Verifies that cryptographic exceptions are properly propagated. + */ + @Test + void decryptAttributeWithCryptoExceptionShouldThrowException() throws Exception { + String credentialWithProtected = createCredentialWithProtectedAttributes(); + org.json.JSONObject data = new org.json.JSONObject(); + data.put("name", "encryptedName"); + + when(cryptoUtil.decryptWithPin(any(CryptoWithPinRequestDto.class))) + .thenThrow(new InvalidKeyException("Invalid key")); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "decryptAttribute", org.json.JSONObject.class, String.class, String.class); + method.setAccessible(true); + + assertThrows(Exception.class, () -> + method.invoke(printService, data, "1234", credentialWithProtected)); + } + + /** + * Tests the extractFaceImageData method with invalid biometric data. + * Verifies that the method throws an exception when provided with invalid data format. + */ + @Test + void extractFaceImageDataWithInvalidDataShouldThrowException() throws Exception { + byte[] invalidData = "invalid".getBytes(); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "extractFaceImageData", byte[].class); + method.setAccessible(true); + + assertThrows(Exception.class, () -> + method.invoke(printService, invalidData)); + } + + /** + * Tests the getPassword method when required property is missing from configuration. + * Verifies that the method throws an exception when password property is not configured. + */ + @Test + void getPasswordWithMissingPropertyShouldThrowException() throws Exception { + when(env.getProperty("mosip.print.service.uincard.password")).thenReturn(null); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "getPassword", org.json.JSONObject.class); + method.setAccessible(true); + + org.json.JSONObject jsonObject = new org.json.JSONObject(); + + assertThrows(Exception.class, () -> + method.invoke(printService, jsonObject)); + } + + /** + * Tests the getPassword method with various JSON data types in password fields. + * Verifies that the method handles different JSON structures for password generation. + */ + @Test + void getPasswordWithVariousJsonTypesShouldGeneratePassword() throws Exception { + when(env.getProperty("mosip.print.service.uincard.password")).thenReturn("UIN|fullName|dob"); + + org.json.JSONObject jsonObject = new org.json.JSONObject(); + jsonObject.put("UIN", "1234567890"); + + JSONArray jsonArray = new JSONArray(); + JSONObject nameObj = new JSONObject(); + nameObj.put("language", "eng"); + nameObj.put("value", "JohnDoe"); + jsonArray.add(nameObj); + jsonObject.put("fullName", jsonArray); + + JSONObject dobObj = new JSONObject(); + dobObj.put("value", "1990-01-01"); + jsonObject.put("dob", dobObj); + + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JsonValue jsonValue = new JsonValue(); + jsonValue.setLanguage("eng"); + jsonValue.setValue("JohnDoe"); + JsonValue[] jsonValues = {jsonValue}; + + jsonUtilMock.when(() -> JsonUtil.mapJsonNodeToJavaObject(eq(JsonValue.class), any(JSONArray.class))) + .thenReturn(jsonValues); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "getPassword", org.json.JSONObject.class); + method.setAccessible(true); + + String result = (String) method.invoke(printService, jsonObject); + assertNotNull(result); + assertEquals(12, result.length()); + } + } + + /** + * Tests the getFormattedPasswordAttribute method with various input values. + * Verifies that the method consistently returns 4-character formatted passwords regardless of input. + */ + @Test + void getFormattedPasswordAttributeWithVariousValuesShouldReturnFormattedString() throws Exception { + Method method = PrintServiceImpl.class.getDeclaredMethod( + "getFormattedPasswordAttribute", String.class); + method.setAccessible(true); + + String result1 = (String) method.invoke(printService, "verylongpassword123"); + assertEquals(4, result1.length()); + + String result2 = (String) method.invoke(printService, "ab"); + assertEquals(4, result2.length()); + + String result3 = (String) method.invoke(printService, "test@#$123"); + assertEquals(4, result3.length()); + } + + /** + * Tests the setTemplateAttributes method when identity JSON is null. + * Verifies that the method throws an exception when identity parsing returns null. + */ + @Test + void setTemplateAttributesWithNullIdentityShouldThrowException() throws Exception { + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + jsonUtilMock.when(() -> JsonUtil.objectMapperReadValue(anyString(), eq(JSONObject.class))) + .thenReturn(null); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "setTemplateAttributes", String.class, Map.class); + method.setAccessible(true); + + Map attributes = new HashMap<>(); + + assertThrows(Exception.class, () -> + method.invoke(printService, "{}", attributes)); + } + } + + /** + * Tests the setTemplateAttributes method when JSON parsing exception occurs. + * Verifies that JSON parsing exceptions are properly handled and propagated. + */ + @Test + void setTemplateAttributesWithParsingExceptionShouldThrowException() throws Exception { + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + jsonUtilMock.when(() -> JsonUtil.objectMapperReadValue(anyString(), eq(JSONObject.class))) + .thenThrow(new JsonParseException(null, "Parse error")); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "setTemplateAttributes", String.class, Map.class); + method.setAccessible(true); + + Map attributes = new HashMap<>(); + + assertThrows(Exception.class, () -> + method.invoke(printService, "{invalid json}", attributes)); + } + } + + /** + * Tests the createTextFile method when identity JSON is null. + * Verifies that the method throws an exception when demographic identity cannot be parsed. + */ + @Test + void createTextFileWithNullIdentityShouldThrowException() throws Exception { + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + jsonUtilMock.when(() -> JsonUtil.objectMapperReadValue(anyString(), eq(JSONObject.class))) + .thenReturn(null); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "createTextFile", String.class); + method.setAccessible(true); + + assertThrows(Exception.class, () -> + method.invoke(printService, "{}")); + } + } + + /** + * Tests the createTextFile method with various object types in demographic data. + * Verifies that the method handles different data structures including arrays, maps, and strings. + */ + @Test + void createTextFileWithVariousObjectTypesShouldCreateFile() throws Exception { + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JSONObject mockDemographicIdentity = new JSONObject(); + mockDemographicIdentity.put("UIN", "1234567890"); + + ArrayList arrayListField = new ArrayList<>(); + arrayListField.add("arrayItem"); + mockDemographicIdentity.put("arrayField", arrayListField); + + LinkedHashMap mapField = new LinkedHashMap<>(); + mapField.put("value", "mapValue"); + mockDemographicIdentity.put("mapField", mapField); + + mockDemographicIdentity.put("stringField", "stringValue"); + + JSONObject printTextFileConfig = new JSONObject(); + printTextFileConfig.put("personal", "UIN,arrayField,mapField,stringField"); + + jsonUtilMock.when(() -> JsonUtil.objectMapperReadValue(anyString(), eq(JSONObject.class))) + .thenReturn(mockDemographicIdentity) + .thenReturn(printTextFileConfig); + + JsonValue jsonValue = new JsonValue(); + jsonValue.setLanguage("eng"); + jsonValue.setValue("arrayValue"); + JsonValue[] jsonValues = {jsonValue}; + + JSONArray mockJSONArray = new JSONArray(); + jsonUtilMock.when(() -> JsonUtil.getJSONArray(any(JSONObject.class), anyString())) + .thenReturn(mockJSONArray); + jsonUtilMock.when(() -> JsonUtil.mapJsonNodeToJavaObject(eq(JsonValue.class), any(JSONArray.class))) + .thenReturn(jsonValues); + + JSONObject mockJSONObject = new JSONObject(); + mockJSONObject.put("value", "jsonObjectValue"); + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(mockJSONObject); + + jsonUtilMock.when(() -> JsonUtil.getJSONValue(any(JSONObject.class), anyString())) + .thenReturn("personal"); + + when(utilities.getPrintTextFileJson(anyString(), anyString())) + .thenReturn(printTextFileConfig.toString()); + when(utilities.getConfigServerFileStorageURL()).thenReturn("http://config"); + when(utilities.getRegistrationProcessorPrintTextFile()).thenReturn("textfile.json"); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "createTextFile", String.class); + method.setAccessible(true); + + byte[] result = (byte[]) method.invoke(printService, mockDemographicIdentity.toString()); + assertNotNull(result); + assertTrue(result.length > 0); + } + } + + /** + * Tests the credential verification path in the print service. + * Verifies that credential verification flag properly controls the verification process. + */ + @Test + void testCredentialVerificationPathShouldReturnTrue() throws Exception { + Method method = PrintServiceImpl.class.getDeclaredMethod( + "hasPrintCredentialVerified", EventModel.class, String.class); + method.setAccessible(true); + + ReflectionTestUtils.setField(printService, "verifyCredentialsFlag", false); + boolean result = (boolean) method.invoke(printService, mockEventModel, "credential"); + assertTrue(result); + } + + /** + * Tests the getDocuments method for successful QR code generation. + * Verifies that the method processes credentials and generates documents correctly. + * + * @throws Exception if any error occurs during processing + */ + @Test + void testGetDocuments_Success_QRCode() throws Exception { + String credential = "{\"credentialSubject\":\"{\\\"UIN\\\":\\\"1234567890\\\"}\"}"; + String credentialType = "qrcode"; + String encryptionPin = "1234"; + String requestId = "req1"; + boolean isPasswordProtected = false; + + when(templateGenerator.getTemplate(anyString(), anyMap(), anyString())) + .thenReturn(new ByteArrayInputStream("template".getBytes())); + when(uinCardGenerator.generateUinCard(any(InputStream.class), any(UinCardType.class), anyString())) + .thenReturn("pdf".getBytes()); + when(qrCodeGenerator.generateQrCode(anyString(), any(QrVersion.class))) + .thenReturn("qr".getBytes()); + when(dataShareUtil.getDataShare(any(), anyString(), anyString())) + .thenReturn(mock(DataShare.class)); + when(auditLogRequestBuilder.createAuditRequestBuilder(anyString(), anyString(), anyString(), anyString(), anyString(), anyString(), anyString())) + .thenReturn(null); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "getDocuments", String.class, String.class, String.class, String.class, boolean.class); + method.setAccessible(true); + + Map result = (Map) method.invoke(printService, credential, credentialType, encryptionPin, requestId, isPasswordProtected); + assertNotNull(result); + } + /** + * Creates a mock DataShare object for testing purposes. + * + * @return DataShare with test data including partner ID, policy ID, and request ID + */ + @Test + void testGetDocuments_TemplateProcessingFailureException() throws Exception { + String credential = "{\"credentialSubject\":\"{\\\"UIN\\\":\\\"1234567890\\\"}\"}"; + String credentialType = "UIN"; + String encryptionPin = "1234"; + String requestId = "req2"; + boolean isPasswordProtected = false; + + when(templateGenerator.getTemplate(anyString(), anyMap(), anyString())).thenReturn(null); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "getDocuments", String.class, String.class, String.class, String.class, boolean.class); + method.setAccessible(true); + + Exception exception = assertThrows(Exception.class, () -> + method.invoke(printService, credential, credentialType, encryptionPin, requestId, isPasswordProtected)); + + Throwable cause = exception.getCause(); + assertTrue(cause instanceof PDFGeneratorException); + assertTrue(cause.getMessage().contains("argument \"content\" is null")); + } + + /** + * Tests the getDocuments method when QR code generation fails. + * Verifies that QrcodeGenerationException is handled properly and the method throws PDFGeneratorException. + */ + @Test + void testGetDocuments_QrcodeGenerationException() throws Exception { + String credential = "{\"credentialSubject\":\"{\\\"UIN\\\":\\\"1234567890\\\"}\"}"; + String credentialType = "UIN"; + String encryptionPin = "1234"; + String requestId = "req3"; + boolean isPasswordProtected = false; + + // Setup mocks to trigger the exception + when(templateGenerator.getTemplate(anyString(), anyMap(), anyString())).thenReturn(null); + + Method method = PrintServiceImpl.class.getDeclaredMethod( + "getDocuments", String.class, String.class, String.class, String.class, boolean.class); + method.setAccessible(true); + + Exception exception = assertThrows(Exception.class, () -> + method.invoke(printService, credential, credentialType, encryptionPin, requestId, isPasswordProtected)); + + Throwable cause = exception.getCause(); + assertTrue(cause instanceof PDFGeneratorException); + assertTrue(cause.getMessage().contains("argument \"content\" is null")); + } + + /** + * Creates a mock EventModel for testing purposes. + * + * @return EventModel with test data including transaction ID, event ID, and credential information + */ + private EventModel createMockEventModel() { + EventModel eventModel = new EventModel(); + Event event = new Event(); + event.setTransactionId("txn123"); + event.setId("event123"); + + Map data = new HashMap<>(); + data.put("credentialType", "UIN"); + data.put("protectionKey", "1234"); + data.put("credential", "mockCredential"); + event.setData(data); + + eventModel.setEvent(event); + return eventModel; + } + + /** + * Creates a mock credential string for testing. + * + * @return JSON string representing a basic credential with UIN + */ + private String createMockCredential() { + return "{\"credentialSubject\":\"{\\\"UIN\\\":\\\"1234567890\\\"}\"}"; + } + + /** + * Creates a mock decoded credential string for testing. + * + * @return JSON string representing decoded credential with UIN and biometric data + */ + private String createMockDecodedCredential() { + return "{\"credentialSubject\":\"{\\\"UIN\\\":\\\"1234567890\\\",\\\"biometrics\\\":\\\"biometricData\\\"}\"}"; + } + + /** + * Creates a credential string with protected attributes for testing decryption. + * + * @return JSON string representing credential with protected attributes array + */ + private String createCredentialWithProtectedAttributes() { + return "{\"protectedAttributes\":[\"name\"],\"credentialSubject\":\"{\\\"UIN\\\":\\\"1234567890\\\"}\"}"; + } + + /** + * Creates a mock DataShare object for testing. + * + * @return DataShare object with test URL + */ + private DataShare createMockDataShare() { + DataShare dataShare = new DataShare(); + dataShare.setUrl("https://example.com/datashare/file123"); + return dataShare; + } + + /** + * Creates mock biometric data for testing face image extraction. + * + * @return byte array representing mock biometric data with proper header structure + */ + private byte[] createMockBiometricData() { + byte[] data = new byte[100]; + data[0] = 'F'; data[1] = 'A'; data[2] = 'C'; data[3] = 'E'; + data[4] = 0; data[5] = 1; data[6] = 0; data[7] = 0; + data[8] = 0; data[9] = 0; data[10] = 0; data[11] = 100; + data[12] = 0; data[13] = 1; + data[14] = 0; + data[15] = 0; data[16] = 0; + data[17] = 0; data[18] = 0; data[19] = 0; data[20] = 76; + + for (int i = 21; i < 96; i++) { + data[i] = 0; + } + + data[92] = 0; data[93] = 0; data[94] = 0; data[95] = 4; + data[96] = 1; data[97] = 2; data[98] = 3; data[99] = 4; + + return data; + } + + /** + * Sets up default mock behavior for all dependencies used in tests. + * Configures common mock responses to avoid repetition in individual tests. + */ + private void setupDefaultMockBehavior() { + when(cryptoCoreUtil.decrypt(anyString())).thenReturn(mockDecodedCredential); + + try { + when(dataShareUtil.getDataShare(any(byte[].class), anyString(), anyString())) + .thenReturn(mockDataShare); + } catch (Exception e) { + // Mock setup exception + } + + doNothing().when(webSubSubscriptionHelper).printStatusUpdateEvent(anyString(), any()); + + when(auditLogRequestBuilder.createAuditRequestBuilder( + anyString(), anyString(), anyString(), anyString(), anyString(), anyString(), anyString())) + .thenReturn(null); + } + + /** + * Sets up JsonUtil static method mocks for JSON processing tests. + * Configures mock behavior for JSON parsing, identity mapping, and configuration retrieval. + * + * @param jsonUtilMock the mocked static JsonUtil class + * @throws Exception if mock setup fails + */ + private void setupJsonUtilMocks(MockedStatic jsonUtilMock) throws Exception { + JSONObject mockDemographicIdentity = new JSONObject(); + mockDemographicIdentity.put("UIN", "1234567890"); + mockDemographicIdentity.put("fullName", "John Doe"); + mockDemographicIdentity.put("biometrics", "biometricData"); + + JSONObject printTextFileConfig = new JSONObject(); + printTextFileConfig.put("personal", "UIN,fullName"); + + JSONObject identityMappingJson = new JSONObject(); + JSONObject mapperIdentity = new JSONObject(); + + LinkedHashMap fullNameMapping = new LinkedHashMap<>(); + fullNameMapping.put("value", "fullName"); + mapperIdentity.put("fullName", fullNameMapping); + + LinkedHashMap uinMapping = new LinkedHashMap<>(); + uinMapping.put("value", "UIN"); + mapperIdentity.put("UIN", uinMapping); + + identityMappingJson.put("identity", mapperIdentity); + + jsonUtilMock.when(() -> JsonUtil.objectMapperReadValue(anyString(), eq(JSONObject.class))) + .thenAnswer(invocation -> { + String jsonString = invocation.getArgument(0); + if (jsonString.contains("personal")) { + return printTextFileConfig; + } else if (jsonString.contains("identity")) { + return identityMappingJson; + } + return mockDemographicIdentity; + }); + + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(mapperIdentity); + + jsonUtilMock.when(() -> JsonUtil.getJSONValue(any(JSONObject.class), anyString())) + .thenReturn("personal"); + + setupUtilityMocks(); + setupGeneratorMocks(); + } + + /** + * Sets up utility class mocks for configuration and file access. + * Configures mock responses for utility methods used in document generation. + * + * @throws Exception if mock setup fails + */ + private void setupUtilityMocks() throws Exception { + when(utilities.getPrintTextFileJson(anyString(), anyString())).thenReturn("{\"personal\":\"UIN,fullName\"}"); + when(utilities.getIdentityMappingJson(anyString(), anyString())).thenReturn("{\"identity\":{\"fullName\":{\"value\":\"fullName\"},\"UIN\":{\"value\":\"UIN\"}}}"); + when(utilities.getConfigServerFileStorageURL()).thenReturn("http://config"); + when(utilities.getRegistrationProcessorPrintTextFile()).thenReturn("textfile.json"); + when(utilities.getGetRegProcessorIdentityJson()).thenReturn("identity.json"); + when(utilities.getGetRegProcessorDemographicIdentity()).thenReturn("identity"); + } + + /** + * Sets up generator class mocks for template, QR code, and card generation. + * Configures mock behavior for all generator services used in document processing. + * + * @throws Exception if mock setup fails + */ + private void setupGeneratorMocks() throws Exception { + when(templateGenerator.getTemplate(anyString(), any(Map.class), anyString())) + .thenReturn(new ByteArrayInputStream("template".getBytes())); + + when(qrCodeGenerator.generateQrCode(anyString(), any(QrVersion.class))) + .thenReturn("qrcode".getBytes()); + + when(uinCardGenerator.generateUinCard(any(InputStream.class), any(UinCardType.class), anyString())) + .thenReturn("pdf".getBytes()); + + CbeffToBiometricUtil mockUtil = mock(CbeffToBiometricUtil.class); + when(mockUtil.getImageBytes(anyString(), anyString(), any(List.class))) + .thenReturn(createMockBiometricData()); + } +} diff --git a/src/test/java/io/mosip/print/test/service/impl/PrintServiceTest.java b/src/test/java/io/mosip/print/service/impl/PrintServiceTest.java similarity index 96% rename from src/test/java/io/mosip/print/test/service/impl/PrintServiceTest.java rename to src/test/java/io/mosip/print/service/impl/PrintServiceTest.java index 6908edf3..0c1ec0d6 100644 --- a/src/test/java/io/mosip/print/test/service/impl/PrintServiceTest.java +++ b/src/test/java/io/mosip/print/service/impl/PrintServiceTest.java @@ -1,4 +1,4 @@ -package io.mosip.print.test.service.impl; +package io.mosip.print.service.impl; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; @@ -26,10 +26,9 @@ import io.mosip.print.model.EventModel; import io.mosip.print.service.PrintRestClientService; import io.mosip.print.service.UinCardGenerator; -import io.mosip.print.service.impl.PrintServiceImpl; import io.mosip.print.spi.CbeffUtil; import io.mosip.print.spi.QrCodeGenerator; -import io.mosip.print.test.TestBootApplication; +import io.mosip.print.TestBootApplication; import io.mosip.print.util.CryptoCoreUtil; import io.mosip.print.util.JsonUtil; import io.mosip.print.util.TemplateGenerator; diff --git a/src/test/java/io/mosip/print/test/service/impl/QrcodeGeneratorTest.java b/src/test/java/io/mosip/print/service/impl/QrcodeGeneratorTest.java similarity index 96% rename from src/test/java/io/mosip/print/test/service/impl/QrcodeGeneratorTest.java rename to src/test/java/io/mosip/print/service/impl/QrcodeGeneratorTest.java index 646c9adc..1d76087e 100644 --- a/src/test/java/io/mosip/print/test/service/impl/QrcodeGeneratorTest.java +++ b/src/test/java/io/mosip/print/service/impl/QrcodeGeneratorTest.java @@ -1,4 +1,4 @@ -package io.mosip.print.test.service.impl; +package io.mosip.print.service.impl; import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.assertThat; @@ -7,13 +7,11 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; import io.mosip.print.constant.QrVersion; import io.mosip.print.exception.QrcodeGenerationException; -import io.mosip.print.service.impl.QrcodeGeneratorImpl; import io.mosip.print.spi.QrCodeGenerator; -import io.mosip.print.test.TestBootApplication; +import io.mosip.print.TestBootApplication; diff --git a/src/test/java/io/mosip/print/service/impl/TemplateManagerBuilderImplTest.java b/src/test/java/io/mosip/print/service/impl/TemplateManagerBuilderImplTest.java new file mode 100644 index 00000000..571ba15f --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/TemplateManagerBuilderImplTest.java @@ -0,0 +1,319 @@ +package io.mosip.print.service.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.mosip.print.spi.TemplateManager; +import io.mosip.print.spi.TemplateManagerBuilder; + +/** + * Unit tests for {@link TemplateManagerBuilderImpl} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the TemplateManagerBuilderImpl class, + * including builder pattern implementation, property configuration, template manager creation, and method chaining.

+ */ +@ExtendWith(MockitoExtension.class) +class TemplateManagerBuilderImplTest { + + private TemplateManagerBuilderImpl templateManagerBuilder; + + /** + * Sets up test fixtures before each test method execution. + * Initializes a fresh instance of TemplateManagerBuilderImpl for each test. + */ + @BeforeEach + void setUp() { + templateManagerBuilder = new TemplateManagerBuilderImpl(); + } + + /** + * Tests the default constructor initialization. + * Verifies that the builder is created with correct default values for all properties. + */ + @Test + void constructorWithDefaultValuesShouldInitializeCorrectly() { + assertEquals("classpath", templateManagerBuilder.getResourceLoader()); + assertEquals(".", templateManagerBuilder.getTemplatePath()); + assertTrue(templateManagerBuilder.isCache()); + assertEquals(StandardCharsets.UTF_8.name(), templateManagerBuilder.getDefaultEncoding()); + } + + /** + * Tests the resourceLoader method with valid input. + * Verifies that the resource loader is set correctly and the builder instance is returned for chaining. + */ + @Test + void resourceLoaderWithValidInputShouldSetValueAndReturnBuilder() { + TemplateManagerBuilder result = templateManagerBuilder.resourceLoader("file"); + + assertEquals("file", templateManagerBuilder.getResourceLoader()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the resourceLoader method with empty string input. + * Verifies that empty strings are accepted and stored correctly. + */ + @Test + void resourceLoaderWithEmptyStringShouldSetEmptyValue() { + TemplateManagerBuilder result = templateManagerBuilder.resourceLoader(""); + + assertEquals("", templateManagerBuilder.getResourceLoader()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the resourcePath method with valid path input. + * Verifies that the template path is set correctly and the builder instance is returned for chaining. + */ + @Test + void resourcePathWithValidInputShouldSetValueAndReturnBuilder() { + TemplateManagerBuilder result = templateManagerBuilder.resourcePath("/templates"); + + assertEquals("/templates", templateManagerBuilder.getTemplatePath()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the resourcePath method with empty string input. + * Verifies that empty strings are accepted and stored correctly. + */ + @Test + void resourcePathWithEmptyStringShouldSetEmptyValue() { + TemplateManagerBuilder result = templateManagerBuilder.resourcePath(""); + + assertEquals("", templateManagerBuilder.getTemplatePath()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the enableCache method with true value. + * Verifies that cache is enabled correctly and the builder instance is returned for chaining. + */ + @Test + void enableCacheWithTrueShouldSetTrueAndReturnBuilder() { + templateManagerBuilder.enableCache(false); + + TemplateManagerBuilder result = templateManagerBuilder.enableCache(true); + + assertTrue(templateManagerBuilder.isCache()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the enableCache method with false value. + * Verifies that cache is disabled correctly and the builder instance is returned for chaining. + */ + @Test + void enableCacheWithFalseShouldSetFalseAndReturnBuilder() { + TemplateManagerBuilder result = templateManagerBuilder.enableCache(false); + + assertFalse(templateManagerBuilder.isCache()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the encodingType method with valid encoding input. + * Verifies that the encoding type is set correctly and the builder instance is returned for chaining. + */ + @Test + void encodingTypeWithValidInputShouldSetValueAndReturnBuilder() { + TemplateManagerBuilder result = templateManagerBuilder.encodingType("ISO-8859-1"); + + assertEquals("ISO-8859-1", templateManagerBuilder.getDefaultEncoding()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the encodingType method with empty string input. + * Verifies that empty strings are accepted and stored correctly. + */ + @Test + void encodingTypeWithEmptyStringShouldSetEmptyValue() { + TemplateManagerBuilder result = templateManagerBuilder.encodingType(""); + + assertEquals("", templateManagerBuilder.getDefaultEncoding()); + assertSame(templateManagerBuilder, result); + } + + /** + * Tests the build method with default configuration. + * Verifies that a TemplateManager instance is created successfully with default settings. + */ + @Test + void buildWithDefaultConfigurationShouldReturnTemplateManager() { + TemplateManager result = templateManagerBuilder.build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + } + + /** + * Tests the build method with custom configuration. + * Verifies that a TemplateManager instance is created successfully with all custom settings applied. + */ + @Test + void buildWithCustomConfigurationShouldReturnTemplateManager() { + templateManagerBuilder + .resourceLoader("file") + .resourcePath("/custom/templates") + .enableCache(false) + .encodingType("ISO-8859-1"); + + TemplateManager result = templateManagerBuilder.build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + } + + /** + * Tests the build method with classpath resource loader. + * Verifies that a TemplateManager instance is created successfully with classpath resource loader configuration. + */ + @Test + void buildWithClasspathResourceLoaderShouldReturnTemplateManager() { + templateManagerBuilder.resourceLoader("classpath"); + + TemplateManager result = templateManagerBuilder.build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + } + + /** + * Tests the build method with file resource loader. + * Verifies that a TemplateManager instance is created successfully with file resource loader configuration. + */ + @Test + void buildWithFileResourceLoaderShouldReturnTemplateManager() { + templateManagerBuilder.resourceLoader("file"); + + TemplateManager result = templateManagerBuilder.build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + } + + /** + * Tests the build method with cache disabled. + * Verifies that a TemplateManager instance is created successfully with caching disabled. + */ + @Test + void buildWithCacheDisabledShouldReturnTemplateManager() { + templateManagerBuilder.enableCache(false); + + TemplateManager result = templateManagerBuilder.build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + } + + /** + * Tests the builder pattern chaining functionality. + * Verifies that all builder methods can be chained together and return the same builder instance. + */ + @Test + void builderPatternChaininShouldWorkCorrectly() { + TemplateManager result = templateManagerBuilder + .resourceLoader("file") + .resourcePath("/test/templates") + .enableCache(true) + .encodingType("UTF-16") + .build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + assertEquals("file", templateManagerBuilder.getResourceLoader()); + assertEquals("/test/templates", templateManagerBuilder.getTemplatePath()); + assertTrue(templateManagerBuilder.isCache()); + assertEquals("UTF-16", templateManagerBuilder.getDefaultEncoding()); + } + + /** + * Tests multiple build calls on the same builder instance. + * Verifies that the builder can be reused to create multiple TemplateManager instances. + */ + @Test + void multipleBuildCallsShouldReturnDifferentInstances() { + TemplateManager result1 = templateManagerBuilder.build(); + TemplateManager result2 = templateManagerBuilder.build(); + + assertNotNull(result1); + assertNotNull(result2); + assertTrue(result1 instanceof TemplateManagerImpl); + assertTrue(result2 instanceof TemplateManagerImpl); + } + + /** + * Tests getter methods for all properties. + * Verifies that all getter methods return the correct values after setting properties. + */ + @Test + void getterMethodsShouldReturnCorrectValues() { + templateManagerBuilder + .resourceLoader("custom") + .resourcePath("/custom/path") + .enableCache(false) + .encodingType("UTF-32"); + + assertEquals("custom", templateManagerBuilder.getResourceLoader()); + assertEquals("/custom/path", templateManagerBuilder.getTemplatePath()); + assertFalse(templateManagerBuilder.isCache()); + assertEquals("UTF-32", templateManagerBuilder.getDefaultEncoding()); + } + + /** + * Tests the build method with different encoding types. + * Verifies that various encoding types are handled correctly. + */ + @Test + void buildWithDifferentEncodingTypesShouldReturnTemplateManager() { + templateManagerBuilder.encodingType("US-ASCII"); + + TemplateManager result = templateManagerBuilder.build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + assertEquals("US-ASCII", templateManagerBuilder.getDefaultEncoding()); + } + + /** + * Tests the build method with different template paths. + * Verifies that various template paths are handled correctly. + */ + @Test + void buildWithDifferentTemplatePathsShouldReturnTemplateManager() { + templateManagerBuilder.resourcePath("/absolute/path/to/templates"); + + TemplateManager result = templateManagerBuilder.build(); + + assertNotNull(result); + assertTrue(result instanceof TemplateManagerImpl); + assertEquals("/absolute/path/to/templates", templateManagerBuilder.getTemplatePath()); + } + + /** + * Tests the build method with all possible boolean combinations for cache. + * Verifies that both cache enabled and disabled scenarios work correctly. + */ + @Test + void buildWithAllCacheCombinationsShouldReturnTemplateManager() { + templateManagerBuilder.enableCache(true); + TemplateManager result1 = templateManagerBuilder.build(); + assertNotNull(result1); + + templateManagerBuilder.enableCache(false); + TemplateManager result2 = templateManagerBuilder.build(); + assertNotNull(result2); + } +} diff --git a/src/test/java/io/mosip/print/service/impl/TemplateManagerImplTest.java b/src/test/java/io/mosip/print/service/impl/TemplateManagerImplTest.java new file mode 100644 index 00000000..7ab4d954 --- /dev/null +++ b/src/test/java/io/mosip/print/service/impl/TemplateManagerImplTest.java @@ -0,0 +1,390 @@ +package io.mosip.print.service.impl; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.mosip.print.exception.TemplateMethodInvocationException; +import io.mosip.print.exception.TemplateParsingException; +import io.mosip.print.exception.TemplateResourceNotFoundException; +import io.mosip.print.util.TemplateManagerUtil; + +/** + * Unit tests for {@link TemplateManagerImpl} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the TemplateManagerImpl class, + * including template merging operations, exception handling, null parameter validation, and Velocity engine integration.

+ */ +@ExtendWith(MockitoExtension.class) +class TemplateManagerImplTest { + + private TemplateManagerImpl templateManager; + + @Mock + private VelocityEngine velocityEngine; + + @Mock + private Template template; + + @Mock + private VelocityContext velocityContext; + + private Map testValues; + private InputStream testInputStream; + private StringWriter testWriter; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the TemplateManagerImpl instance, test data, and mock objects required for testing. + */ + @BeforeEach + void setUp() { + templateManager = new TemplateManagerImpl(velocityEngine); + testValues = new HashMap<>(); + testValues.put("name", "TestUser"); + testValues.put("id", "12345"); + + String testContent = "Hello $name, your ID is $id"; + testInputStream = new ByteArrayInputStream(testContent.getBytes()); + testWriter = new StringWriter(); + } + + /** + * Tests the merge method with InputStream and valid parameters. + * Verifies that template merging returns a valid InputStream when provided with proper input stream and values. + */ + @Test + void mergeWithInputStreamAndValidParametersShouldReturnInputStream() throws IOException { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.evaluate(eq(velocityContext), any(StringWriter.class), + eq("templateManager-mergeTemplate"), any(InputStreamReader.class))).thenReturn(true); + + InputStream result = templateManager.merge(testInputStream, testValues); + + assertNotNull(result); + verify(velocityEngine, times(1)).evaluate(eq(velocityContext), any(StringWriter.class), + eq("templateManager-mergeTemplate"), any(InputStreamReader.class)); + } + } + + /** + * Tests the merge method with InputStream when context is null. + * Verifies that the method returns null when the velocity context cannot be created from input values. + */ + @Test + void mergeWithInputStreamAndNullContextShouldReturnNull() throws IOException { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(null); + + InputStream result = templateManager.merge(testInputStream, testValues); + + assertNull(result); + } + } + + /** + * Tests the merge method with InputStream when evaluate returns false. + * Verifies that the method returns null when the Velocity engine evaluation fails. + */ + @Test + void mergeWithInputStreamAndEvaluateReturnsFalseShouldReturnNull() throws IOException { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.evaluate(eq(velocityContext), any(StringWriter.class), + eq("templateManager-mergeTemplate"), any(InputStreamReader.class))).thenReturn(false); + + InputStream result = templateManager.merge(testInputStream, testValues); + + assertNull(result); + } + } + + /** + * Tests the merge method with null InputStream parameter. + * Verifies that the method throws NullPointerException when InputStream is null. + */ + @Test + void mergeWithNullInputStreamShouldThrowNullPointerException() { + assertThrows(NullPointerException.class, () -> { + templateManager.merge((InputStream) null, testValues); + }); + } + + /** + * Tests the merge method with null values parameter. + * Verifies that the method throws NullPointerException when values map is null. + */ + @Test + void mergeWithNullValuesShouldThrowNullPointerException() { + assertThrows(NullPointerException.class, () -> { + templateManager.merge(testInputStream, null); + }); + } + + /** + * Tests the merge method with InputStream when ResourceNotFoundException occurs. + * Verifies that ResourceNotFoundException is wrapped and thrown as TemplateResourceNotFoundException. + */ + @Test + void mergeWithInputStreamAndResourceNotFoundExceptionShouldThrowTemplateResourceNotFoundException() { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.evaluate(eq(velocityContext), any(StringWriter.class), + eq("templateManager-mergeTemplate"), any(InputStreamReader.class))) + .thenThrow(new ResourceNotFoundException("Template not found")); + + assertThrows(TemplateResourceNotFoundException.class, () -> { + templateManager.merge(testInputStream, testValues); + }); + } + } + + /** + * Tests the merge method with InputStream when ParseErrorException occurs. + * Verifies that ParseErrorException is wrapped and thrown as TemplateParsingException. + */ + @Test + void mergeWithInputStreamAndParseErrorExceptionShouldThrowTemplateParsingException() { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.evaluate(eq(velocityContext), any(StringWriter.class), + eq("templateManager-mergeTemplate"), any(InputStreamReader.class))) + .thenThrow(new ParseErrorException("Parse error")); + + assertThrows(TemplateParsingException.class, () -> { + templateManager.merge(testInputStream, testValues); + }); + } + } + + /** + * Tests the merge method with InputStream when MethodInvocationException occurs. + * Verifies that MethodInvocationException is wrapped and thrown as TemplateMethodInvocationException. + */ + @Test + void mergeWithInputStreamAndMethodInvocationExceptionShouldThrowTemplateMethodInvocationException() { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.evaluate(eq(velocityContext), any(StringWriter.class), + eq("templateManager-mergeTemplate"), any(InputStreamReader.class))) + .thenThrow(new MethodInvocationException("Method invocation error", new Exception(), "test", "template", 1, 1)); + + assertThrows(TemplateMethodInvocationException.class, () -> { + templateManager.merge(testInputStream, testValues); + }); + } + } + + /** + * Tests the merge method with template name and writer using default encoding. + * Verifies that template merging works correctly when using template name with StringWriter. + */ + @Test + void mergeWithTemplateNameAndWriterShouldReturnTrue() throws IOException { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.getTemplate(eq("test-template"), eq("UTF-8"))).thenReturn(template); + doNothing().when(template).merge(velocityContext, testWriter); + + boolean result = templateManager.merge("test-template", testWriter, testValues); + + assertTrue(result); + verify(velocityEngine, times(1)).getTemplate("test-template", "UTF-8"); + verify(template, times(1)).merge(velocityContext, testWriter); + } + } + + /** + * Tests the merge method with template name, writer, and custom encoding. + * Verifies that template merging works correctly when using custom encoding type. + */ + @Test + void mergeWithTemplateNameWriterAndEncodingShouldReturnTrue() throws IOException { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.getTemplate(eq("test-template"), eq("ISO-8859-1"))).thenReturn(template); + doNothing().when(template).merge(velocityContext, testWriter); + + boolean result = templateManager.merge("test-template", testWriter, testValues, "ISO-8859-1"); + + assertTrue(result); + verify(velocityEngine, times(1)).getTemplate("test-template", "ISO-8859-1"); + verify(template, times(1)).merge(velocityContext, testWriter); + } + } + + /** + * Tests the merge method with null template name parameter. + * Verifies that the method throws NullPointerException when template name is null. + */ + @Test + void mergeWithNullTemplateNameShouldThrowNullPointerException() { + assertThrows(NullPointerException.class, () -> { + templateManager.merge((String) null, testWriter, testValues, "UTF-8"); + }); + } + + /** + * Tests the merge method with null writer parameter. + * Verifies that the method throws NullPointerException when StringWriter is null. + */ + @Test + void mergeWithNullWriterShouldThrowNullPointerException() { + assertThrows(NullPointerException.class, () -> { + templateManager.merge("test-template", null, testValues, "UTF-8"); + }); + } + + /** + * Tests the merge method with null encoding type parameter. + * Verifies that the method throws NullPointerException when encoding type is null. + */ + @Test + void mergeWithNullEncodingTypeShouldThrowNullPointerException() { + assertThrows(NullPointerException.class, () -> { + templateManager.merge("test-template", testWriter, testValues, null); + }); + } + + /** + * Tests the merge method with null values parameter for template name variant. + * Verifies that the method throws NullPointerException when values map is null. + */ + @Test + void mergeWithNullValuesForTemplateNameShouldThrowNullPointerException() { + assertThrows(NullPointerException.class, () -> { + templateManager.merge("test-template", testWriter, null, "UTF-8"); + }); + } + + /** + * Tests the merge method with template name when ResourceNotFoundException occurs. + * Verifies that ResourceNotFoundException is wrapped and thrown as TemplateResourceNotFoundException. + */ + @Test + void mergeWithTemplateNameAndResourceNotFoundExceptionShouldThrowTemplateResourceNotFoundException() { + when(velocityEngine.getTemplate(eq("test-template"), eq("UTF-8"))) + .thenThrow(new ResourceNotFoundException("Template not found")); + + assertThrows(TemplateResourceNotFoundException.class, () -> { + templateManager.merge("test-template", testWriter, testValues, "UTF-8"); + }); + } + + /** + * Tests the merge method with template name when ParseErrorException occurs during template merge. + * Verifies that ParseErrorException is wrapped and thrown as TemplateParsingException. + */ + @Test + void mergeWithTemplateNameAndParseErrorExceptionShouldThrowTemplateParsingException() { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.getTemplate(eq("test-template"), eq("UTF-8"))).thenReturn(template); + doThrow(new ParseErrorException("Parse error")).when(template).merge(velocityContext, testWriter); + + assertThrows(TemplateParsingException.class, () -> { + templateManager.merge("test-template", testWriter, testValues, "UTF-8"); + }); + } + } + + /** + * Tests the merge method with template name when MethodInvocationException occurs during template merge. + * Verifies that MethodInvocationException is wrapped and thrown as TemplateMethodInvocationException. + */ + @Test + void mergeWithTemplateNameAndMethodInvocationExceptionShouldThrowTemplateMethodInvocationException() { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.getTemplate(eq("test-template"), eq("UTF-8"))).thenReturn(template); + doThrow(new MethodInvocationException("Method invocation error", new Exception(), "test", "template", 1, 1)) + .when(template).merge(velocityContext, testWriter); + + assertThrows(TemplateMethodInvocationException.class, () -> { + templateManager.merge("test-template", testWriter, testValues, "UTF-8"); + }); + } + } + + /** + * Tests the merge method with template name and writer using default encoding. + * Verifies that the three-parameter merge method correctly defaults to UTF-8 encoding. + */ + @Test + void mergeWithTemplateNameAndWriterWithDefaultEncodingShouldReturnTrue() throws IOException { + try (MockedStatic mockedUtil = Mockito.mockStatic(TemplateManagerUtil.class)) { + mockedUtil.when(() -> TemplateManagerUtil.bindInputToContext(testValues)) + .thenReturn(velocityContext); + + when(velocityEngine.getTemplate(eq("test-template"), eq("UTF-8"))).thenReturn(template); + doNothing().when(template).merge(velocityContext, testWriter); + + boolean result = templateManager.merge("test-template", testWriter, testValues); + + assertTrue(result); + verify(velocityEngine, times(1)).getTemplate("test-template", "UTF-8"); + verify(template, times(1)).merge(velocityContext, testWriter); + } + } + + /** + * Tests the constructor with VelocityEngine parameter. + * Verifies that a TemplateManagerImpl instance is created successfully when provided with a VelocityEngine. + */ + @Test + void constructorWithVelocityEngineShouldCreateInstance() { + TemplateManagerImpl newManager = new TemplateManagerImpl(velocityEngine); + + assertNotNull(newManager); + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/util/AuditLogRequestBuilderTest.java b/src/test/java/io/mosip/print/util/AuditLogRequestBuilderTest.java new file mode 100644 index 00000000..2e306439 --- /dev/null +++ b/src/test/java/io/mosip/print/util/AuditLogRequestBuilderTest.java @@ -0,0 +1,309 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.env.Environment; + +import io.mosip.print.constant.ApiName; +import io.mosip.print.core.http.RequestWrapper; +import io.mosip.print.core.http.ResponseWrapper; +import io.mosip.print.dto.AuditResponseDto; +import io.mosip.print.exception.ApisResourceAccessException; +import io.mosip.print.service.PrintRestClientService; + +/** + * Unit tests for {@link AuditLogRequestBuilder} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the AuditLogRequestBuilder class, + * including audit request creation, API integration, exception handling, and various parameter validation scenarios.

+ */ +@ExtendWith(MockitoExtension.class) +class AuditLogRequestBuilderTest { + + @Mock + private PrintRestClientService registrationProcessorRestService; + + @Mock + private Environment env; + + @InjectMocks + private AuditLogRequestBuilder auditLogRequestBuilder; + + private static final String AUDIT_SERVICE_ID = "AUDIT_SERVICE"; + private static final String REG_PROC_APPLICATION_VERSION = "1.0"; + private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + private static final String CURRENT_DATETIME = "2025-08-03T10:00:00.000Z"; + private static final String HOST_IP = "192.168.1.1"; + private static final String HOST_NAME = "test-host"; + private static final String REGISTRATION_ID = "REG123456789"; + + /** + * Sets up test fixtures before each test method execution. + * Initializes environment properties and mock configurations required for audit logging tests. + */ + @BeforeEach + void setUp() { + when(env.getProperty("mosip.print.audit.id")).thenReturn(AUDIT_SERVICE_ID); + when(env.getProperty("mosip.print.application.version")).thenReturn(REG_PROC_APPLICATION_VERSION); + when(env.getProperty("mosip.print.datetime.pattern")).thenReturn(DATETIME_PATTERN); + } + + /** + * Tests successful audit request creation with ApiName parameter. + * Verifies that the audit request builder correctly creates and processes audit requests + * when provided with valid parameters and ApiName. + */ + @Test + void createAuditRequestBuilderWithApiNameShouldSucceed() throws ApisResourceAccessException { + ResponseWrapper expectedResponse = new ResponseWrapper<>(); + AuditResponseDto auditResponseDto = new AuditResponseDto(); + expectedResponse.setResponse(auditResponseDto); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic serverUtilMock = mockStatic(ServerUtil.class)) { + + ServerUtil mockServerUtil = mock(ServerUtil.class); + dateUtilsMock.when(DateUtils::getUTCCurrentDateTimeString).thenReturn(CURRENT_DATETIME); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)).thenReturn(CURRENT_DATETIME); + serverUtilMock.when(ServerUtil::getServerUtilInstance).thenReturn(mockServerUtil); + when(mockServerUtil.getServerIp()).thenReturn(HOST_IP); + when(mockServerUtil.getServerName()).thenReturn(HOST_NAME); + + when(registrationProcessorRestService.postApi( + eq(ApiName.AUDIT), + anyString(), + anyString(), + any(RequestWrapper.class), + eq(ResponseWrapper.class) + )).thenReturn(expectedResponse); + + ResponseWrapper result = auditLogRequestBuilder.createAuditRequestBuilder( + "Test Description", + "EVENT001", + "Test Event", + "USER_ACTION", + REGISTRATION_ID, + ApiName.AUDIT + ); + + assertNotNull(result); + assertEquals(expectedResponse, result); + } + } + + /** + * Tests audit request creation with ApiName parameter when API exception occurs. + * Verifies that the audit request builder handles ApisResourceAccessException gracefully + * and returns a valid response even when the API call fails. + */ + @Test + void createAuditRequestBuilderWithApiNameShouldHandleException() throws ApisResourceAccessException { + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic serverUtilMock = mockStatic(ServerUtil.class)) { + + ServerUtil mockServerUtil = mock(ServerUtil.class); + dateUtilsMock.when(DateUtils::getUTCCurrentDateTimeString).thenReturn(CURRENT_DATETIME); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)).thenReturn(CURRENT_DATETIME); + serverUtilMock.when(ServerUtil::getServerUtilInstance).thenReturn(mockServerUtil); + when(mockServerUtil.getServerIp()).thenReturn(HOST_IP); + when(mockServerUtil.getServerName()).thenReturn(HOST_NAME); + + when(registrationProcessorRestService.postApi( + eq(ApiName.AUDIT), + anyString(), + anyString(), + any(RequestWrapper.class), + eq(ResponseWrapper.class) + )).thenThrow(new ApisResourceAccessException("API Error")); + + ResponseWrapper result = auditLogRequestBuilder.createAuditRequestBuilder( + "Test Description", + "EVENT001", + "Test Event", + "USER_ACTION", + REGISTRATION_ID, + ApiName.AUDIT + ); + + assertNotNull(result); + } + } + + /** + * Tests successful audit request creation with module parameters. + * Verifies that the audit request builder correctly processes requests when provided + * with module-specific parameters including module ID and module name. + */ + @Test + void createAuditRequestBuilderWithModuleShouldSucceed() throws ApisResourceAccessException { + ResponseWrapper expectedResponse = new ResponseWrapper<>(); + AuditResponseDto auditResponseDto = new AuditResponseDto(); + expectedResponse.setResponse(auditResponseDto); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic serverUtilMock = mockStatic(ServerUtil.class)) { + + ServerUtil mockServerUtil = mock(ServerUtil.class); + dateUtilsMock.when(DateUtils::getUTCCurrentDateTimeString).thenReturn(CURRENT_DATETIME); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)).thenReturn(CURRENT_DATETIME); + serverUtilMock.when(ServerUtil::getServerUtilInstance).thenReturn(mockServerUtil); + when(mockServerUtil.getServerIp()).thenReturn(HOST_IP); + when(mockServerUtil.getServerName()).thenReturn(HOST_NAME); + + when(registrationProcessorRestService.postApi( + eq(ApiName.AUDIT), + anyString(), + anyString(), + any(RequestWrapper.class), + eq(ResponseWrapper.class) + )).thenReturn(expectedResponse); + + ResponseWrapper result = auditLogRequestBuilder.createAuditRequestBuilder( + "Test Description", + "EVENT001", + "Test Event", + "USER_ACTION", + "MODULE001", + "Test Module", + REGISTRATION_ID + ); + + assertNotNull(result); + assertEquals(expectedResponse, result); + } + } + + /** + * Tests audit request creation with module parameters when API exception occurs. + * Verifies that the audit request builder handles exceptions appropriately when + * using module-specific parameters and API calls fail. + */ + @Test + void createAuditRequestBuilderWithModuleShouldHandleException() throws ApisResourceAccessException { + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic serverUtilMock = mockStatic(ServerUtil.class)) { + + ServerUtil mockServerUtil = mock(ServerUtil.class); + dateUtilsMock.when(DateUtils::getUTCCurrentDateTimeString).thenReturn(CURRENT_DATETIME); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)).thenReturn(CURRENT_DATETIME); + serverUtilMock.when(ServerUtil::getServerUtilInstance).thenReturn(mockServerUtil); + when(mockServerUtil.getServerIp()).thenReturn(HOST_IP); + when(mockServerUtil.getServerName()).thenReturn(HOST_NAME); + + when(registrationProcessorRestService.postApi( + eq(ApiName.AUDIT), + anyString(), + anyString(), + any(RequestWrapper.class), + eq(ResponseWrapper.class) + )).thenThrow(new ApisResourceAccessException("API Error")); + + ResponseWrapper result = auditLogRequestBuilder.createAuditRequestBuilder( + "Test Description", + "EVENT001", + "Test Event", + "USER_ACTION", + "MODULE001", + "Test Module", + REGISTRATION_ID + ); + + assertNotNull(result); + } + } + + /** + * Tests audit request builder with null parameter values. + * Verifies that the audit request builder handles null input parameters gracefully + * and still produces a valid audit response without failing. + */ + @Test + void createAuditRequestBuilderWithNullValuesShouldHandleGracefully() throws ApisResourceAccessException { + ResponseWrapper expectedResponse = new ResponseWrapper<>(); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic serverUtilMock = mockStatic(ServerUtil.class)) { + + ServerUtil mockServerUtil = mock(ServerUtil.class); + dateUtilsMock.when(DateUtils::getUTCCurrentDateTimeString).thenReturn(CURRENT_DATETIME); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)).thenReturn(CURRENT_DATETIME); + serverUtilMock.when(ServerUtil::getServerUtilInstance).thenReturn(mockServerUtil); + when(mockServerUtil.getServerIp()).thenReturn(HOST_IP); + when(mockServerUtil.getServerName()).thenReturn(HOST_NAME); + + when(registrationProcessorRestService.postApi( + eq(ApiName.AUDIT), + anyString(), + anyString(), + any(RequestWrapper.class), + eq(ResponseWrapper.class) + )).thenReturn(expectedResponse); + + ResponseWrapper result = auditLogRequestBuilder.createAuditRequestBuilder( + null, + null, + null, + null, + null, + null, + null + ); + + assertNotNull(result); + } + } + + /** + * Tests audit request builder with ApiName and null parameter values. + * Verifies that the audit request builder handles null input parameters appropriately + * when ApiName is provided, ensuring robust error handling. + */ + @Test + void createAuditRequestBuilderWithApiNameAndNullValuesShouldHandleGracefully() throws ApisResourceAccessException { + ResponseWrapper expectedResponse = new ResponseWrapper<>(); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic serverUtilMock = mockStatic(ServerUtil.class)) { + + ServerUtil mockServerUtil = mock(ServerUtil.class); + dateUtilsMock.when(DateUtils::getUTCCurrentDateTimeString).thenReturn(CURRENT_DATETIME); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)).thenReturn(CURRENT_DATETIME); + serverUtilMock.when(ServerUtil::getServerUtilInstance).thenReturn(mockServerUtil); + when(mockServerUtil.getServerIp()).thenReturn(HOST_IP); + when(mockServerUtil.getServerName()).thenReturn(HOST_NAME); + + when(registrationProcessorRestService.postApi( + eq(ApiName.AUDIT), + anyString(), + anyString(), + any(RequestWrapper.class), + eq(ResponseWrapper.class) + )).thenReturn(expectedResponse); + + ResponseWrapper result = auditLogRequestBuilder.createAuditRequestBuilder( + null, + null, + null, + null, + null, + ApiName.AUDIT + ); + + assertNotNull(result); + } + } +} diff --git a/src/test/java/io/mosip/print/util/Base64AdapterTest.java b/src/test/java/io/mosip/print/util/Base64AdapterTest.java new file mode 100644 index 00000000..eb6fdb79 --- /dev/null +++ b/src/test/java/io/mosip/print/util/Base64AdapterTest.java @@ -0,0 +1,321 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mockStatic; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.mosip.kernel.core.util.CryptoUtil; + +/** + * Unit tests for {@link Base64Adapter} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the Base64Adapter class, + * including base64 encoding/decoding operations, null parameter handling, exception scenarios, and various + * data size validations using marshalling and unmarshalling operations.

+ * + */ +@ExtendWith(MockitoExtension.class) +class Base64AdapterTest { + + @InjectMocks + private Base64Adapter base64Adapter; + + private static final String VALID_BASE64_STRING = "SGVsbG8gV29ybGQ="; + private static final byte[] VALID_BYTE_ARRAY = "Hello World".getBytes(); + private static final String EMPTY_BASE64_STRING = ""; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** + * Tests successful unmarshalling of valid base64 string. + * Verifies that a valid base64 encoded string is correctly decoded to its original byte array. + */ + @Test + void unmarshalWithValidBase64StringShouldSucceed() throws Exception { + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(VALID_BASE64_STRING)) + .thenReturn(VALID_BYTE_ARRAY); + + byte[] result = base64Adapter.unmarshal(VALID_BASE64_STRING); + + assertNotNull(result); + assertArrayEquals(VALID_BYTE_ARRAY, result); + } + } + + /** + * Tests unmarshalling with empty string input. + * Verifies that an empty base64 string is handled correctly and returns an empty byte array. + */ + @Test + void unmarshalWithEmptyStringShouldReturnEmptyArray() throws Exception { + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(EMPTY_BASE64_STRING)) + .thenReturn(EMPTY_BYTE_ARRAY); + + byte[] result = base64Adapter.unmarshal(EMPTY_BASE64_STRING); + + assertNotNull(result); + assertArrayEquals(EMPTY_BYTE_ARRAY, result); + } + } + + /** + * Tests unmarshalling with null input parameter. + * Verifies that null input is handled gracefully and returns null without throwing exceptions. + */ + @Test + void unmarshalWithNullInputShouldReturnNull() throws Exception { + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(null)) + .thenReturn(null); + + byte[] result = base64Adapter.unmarshal(null); + + assertEquals(null, result); + } + } + + /** + * Tests unmarshalling when CryptoUtil throws an exception. + * Verifies that exceptions from the underlying CryptoUtil are properly propagated. + */ + @Test + void unmarshalWithCryptoUtilExceptionShouldThrowException() throws Exception { + String invalidBase64 = "invalid-base64-string"; + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(invalidBase64)) + .thenThrow(new IllegalArgumentException("Invalid base64 input")); + + assertThrows(IllegalArgumentException.class, () -> + base64Adapter.unmarshal(invalidBase64) + ); + } + } + + /** + * Tests unmarshalling with different base64 encoded data. + * Verifies that various base64 encoded strings are correctly decoded to their respective byte arrays. + */ + @Test + void unmarshalWithDifferentDataShouldDecodeCorrectly() throws Exception { + String differentBase64 = "VGVzdCBEYXRh"; + byte[] expectedBytes = "Test Data".getBytes(); + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(differentBase64)) + .thenReturn(expectedBytes); + + byte[] result = base64Adapter.unmarshal(differentBase64); + + assertNotNull(result); + assertArrayEquals(expectedBytes, result); + } + } + + /** + * Tests successful marshalling of valid byte array. + * Verifies that a byte array is correctly encoded to its base64 string representation. + */ + @Test + void marshalWithValidByteArrayShouldSucceed() throws Exception { + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(VALID_BYTE_ARRAY)) + .thenReturn(VALID_BASE64_STRING); + + String result = base64Adapter.marshal(VALID_BYTE_ARRAY); + + assertNotNull(result); + assertEquals(VALID_BASE64_STRING, result); + } + } + + /** + * Tests marshalling with empty byte array. + * Verifies that an empty byte array is handled correctly and returns an empty base64 string. + */ + @Test + void marshalWithEmptyByteArrayShouldReturnEmptyString() throws Exception { + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(EMPTY_BYTE_ARRAY)) + .thenReturn(EMPTY_BASE64_STRING); + + String result = base64Adapter.marshal(EMPTY_BYTE_ARRAY); + + assertNotNull(result); + assertEquals(EMPTY_BASE64_STRING, result); + } + } + + /** + * Tests marshalling with null input parameter. + * Verifies that null input is handled gracefully and returns null without throwing exceptions. + */ + @Test + void marshalWithNullInputShouldReturnNull() throws Exception { + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(null)) + .thenReturn(null); + + String result = base64Adapter.marshal(null); + + assertEquals(null, result); + } + } + + /** + * Tests marshalling when CryptoUtil throws an exception. + * Verifies that exceptions from the underlying CryptoUtil are properly propagated during encoding. + */ + @Test + void marshalWithCryptoUtilExceptionShouldThrowException() throws Exception { + byte[] testData = "test".getBytes(); + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(testData)) + .thenThrow(new RuntimeException("Encoding failed")); + + assertThrows(RuntimeException.class, () -> + base64Adapter.marshal(testData) + ); + } + } + + /** + * Tests marshalling with different byte array data. + * Verifies that various byte arrays are correctly encoded to their respective base64 string representations. + */ + @Test + void marshalWithDifferentDataShouldEncodeCorrectly() throws Exception { + byte[] differentData = "Different Test Data".getBytes(); + String expectedBase64 = "RGlmZmVyZW50IFRlc3QgRGF0YQ=="; + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(differentData)) + .thenReturn(expectedBase64); + + String result = base64Adapter.marshal(differentData); + + assertNotNull(result); + assertEquals(expectedBase64, result); + } + } + + /** + * Tests round trip conversion by marshalling then unmarshalling data. + * Verifies that data remains consistent when encoded to base64 and then decoded back to bytes. + */ + @Test + void roundTripConversionShouldMaintainDataIntegrity() throws Exception { + byte[] originalData = "Round Trip Test".getBytes(); + String base64Encoded = "Um91bmQgVHJpcCBUZXN0"; + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(originalData)) + .thenReturn(base64Encoded); + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(base64Encoded)) + .thenReturn(originalData); + + String marshalled = base64Adapter.marshal(originalData); + byte[] unmarshalled = base64Adapter.unmarshal(marshalled); + + assertEquals(base64Encoded, marshalled); + assertArrayEquals(originalData, unmarshalled); + } + } + + /** + * Tests marshalling with large byte array data. + * Verifies that the adapter can handle large byte arrays correctly during base64 encoding. + */ + @Test + void marshalWithLargeByteArrayShouldHandleCorrectly() throws Exception { + byte[] largeData = new byte[1024]; + for (int i = 0; i < largeData.length; i++) { + largeData[i] = (byte) (i % 256); + } + String expectedLargeBase64 = "large-base64-encoded-string"; + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(largeData)) + .thenReturn(expectedLargeBase64); + + String result = base64Adapter.marshal(largeData); + + assertNotNull(result); + assertEquals(expectedLargeBase64, result); + } + } + + /** + * Tests unmarshalling with large base64 string data. + * Verifies that the adapter can handle large base64 strings correctly during decoding. + */ + @Test + void unmarshalWithLargeBase64StringShouldHandleCorrectly() throws Exception { + String largeBase64 = "large-base64-string-with-lots-of-data"; + byte[] expectedLargeData = new byte[512]; + for (int i = 0; i < expectedLargeData.length; i++) { + expectedLargeData[i] = (byte) (i % 128); + } + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(largeBase64)) + .thenReturn(expectedLargeData); + + byte[] result = base64Adapter.unmarshal(largeBase64); + + assertNotNull(result); + assertArrayEquals(expectedLargeData, result); + } + } + + /** + * Tests marshalling with single byte data. + * Verifies that single byte arrays are correctly encoded to base64 format. + */ + @Test + void marshalWithSingleByteShouldEncodeCorrectly() throws Exception { + byte[] singleByte = {42}; + String expectedSingleByteBase64 = "Kg=="; + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.encodeBase64String(singleByte)) + .thenReturn(expectedSingleByteBase64); + + String result = base64Adapter.marshal(singleByte); + + assertNotNull(result); + assertEquals(expectedSingleByteBase64, result); + } + } + + /** + * Tests unmarshalling with padded base64 string. + * Verifies that base64 strings with padding characters are correctly decoded. + */ + @Test + void unmarshalWithPaddedBase64ShouldDecodeCorrectly() throws Exception { + String paddedBase64 = "UGFkZGVkIERhdGE="; + byte[] expectedPaddedData = "Padded Data".getBytes(); + + try (MockedStatic cryptoUtilMock = mockStatic(CryptoUtil.class)) { + cryptoUtilMock.when(() -> CryptoUtil.decodeBase64(paddedBase64)) + .thenReturn(expectedPaddedData); + + byte[] result = base64Adapter.unmarshal(paddedBase64); + + assertNotNull(result); + assertArrayEquals(expectedPaddedData, result); + } + } +} diff --git a/src/test/java/io/mosip/print/util/CbeffToBiometricUtilTest.java b/src/test/java/io/mosip/print/util/CbeffToBiometricUtilTest.java new file mode 100644 index 00000000..f576afdd --- /dev/null +++ b/src/test/java/io/mosip/print/util/CbeffToBiometricUtilTest.java @@ -0,0 +1,501 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.codec.binary.Base64; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.slf4j.Logger; +import org.springframework.test.util.ReflectionTestUtils; + +import io.mosip.print.constant.BiometricType; +import io.mosip.print.entity.BIR; +import io.mosip.print.entity.BDBInfo; +import io.mosip.print.exception.BiometricTagMatchException; +import io.mosip.print.logger.PrintLogger; +import io.mosip.print.model.KeyValuePair; +import io.mosip.print.model.Response; +import io.mosip.print.spi.CbeffUtil; +import io.mosip.print.spi.IBioApi; + +/** + * Unit tests for {@link CbeffToBiometricUtil} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the CbeffToBiometricUtil class, + * including CBEFF data processing, biometric image extraction, CBEFF merging operations, template extraction, + * and various validation scenarios for biometric data handling.

+ */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class CbeffToBiometricUtilTest { + + @Mock + private CbeffUtil cbeffutil; + + @Mock + private IBioApi bioApi; + + @InjectMocks + private CbeffToBiometricUtil cbeffToBiometricUtil; + + private static final String BASE64_CBEFF_DATA = Base64.encodeBase64String("test-cbeff-data".getBytes()); + private static final byte[] TEST_IMAGE_BYTES = "test-image-bytes".getBytes(); + private static final byte[] TEST_XML_BYTES = "test-xml-bytes".getBytes(); + + /** + * Tests the default constructor of CbeffToBiometricUtil. + * Verifies that a new instance can be created successfully using the default constructor. + */ + @Test + void defaultConstructorShouldCreateInstance() { + CbeffToBiometricUtil util = new CbeffToBiometricUtil(); + assertNotNull(util); + } + + /** + * Tests the constructor with CbeffUtil parameter. + * Verifies that a new instance can be created successfully when provided with a CbeffUtil dependency. + */ + @Test + void constructorWithCbeffUtilShouldCreateInstance() { + CbeffUtil mockCbeffUtil = mock(CbeffUtil.class); + CbeffToBiometricUtil util = new CbeffToBiometricUtil(mockCbeffUtil); + assertNotNull(util); + } + + /** + * Tests successful image bytes retrieval from CBEFF data. + * Verifies that biometric image bytes are correctly extracted when valid CBEFF data, + * biometric type, and subtype are provided. + */ + @Test + void getImageBytesShouldSucceedWithValidData() throws Exception { + List birList = createMockBIRList(); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(birList); + + byte[] result = cbeffToBiometricUtil.getImageBytes(BASE64_CBEFF_DATA, "Face", + Arrays.asList("Front")); + + assertArrayEquals(TEST_IMAGE_BYTES, result); + } + } + + /** + * Tests image bytes retrieval with null CBEFF file string. + * Verifies that the method handles null CBEFF data gracefully and returns null. + */ + @Test + void getImageBytesWithNullCbeffStringShouldReturnNull() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + byte[] result = cbeffToBiometricUtil.getImageBytes(null, "Face", + Arrays.asList("Front")); + + assertNull(result); + } + } + + /** + * Tests image bytes retrieval with no matching type and subtype. + * Verifies that the method returns null when no biometric data matches the specified type and subtype. + */ + @Test + void getImageBytesWithNoMatchShouldReturnNull() throws Exception { + List birList = createMockBIRList(); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(birList); + + byte[] result = cbeffToBiometricUtil.getImageBytes(BASE64_CBEFF_DATA, "Fingerprint", + Arrays.asList("LeftThumb")); + + assertNull(result); + } + } + + /** + * Tests image bytes retrieval with null BdbInfo. + * Verifies that the method handles BIR objects with null BdbInfo appropriately and returns null. + */ + @Test + void getImageBytesWithNullBdbInfoShouldReturnNull() throws Exception { + List birList = createMockBIRListWithNullBdbInfo(); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(birList); + + byte[] result = cbeffToBiometricUtil.getImageBytes(BASE64_CBEFF_DATA, "Face", + Arrays.asList("Front")); + + assertNull(result); + } + } + + /** + * Tests successful CBEFF merge operation. + * Verifies that two CBEFF files with different biometric types can be successfully merged. + */ + @Test + void mergeCbeffShouldSucceedWithDifferentTypes() throws Exception { + List file1BirList = createMockBIRListWithDifferentType("Face"); + List file2BirList = createMockBIRListWithDifferentType("Fingerprint"); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))) + .thenReturn(file1BirList) + .thenReturn(file2BirList); + when(cbeffutil.createXML(any(List.class))).thenReturn(TEST_XML_BYTES); + + InputStream result = cbeffToBiometricUtil.mergeCbeff(BASE64_CBEFF_DATA, BASE64_CBEFF_DATA); + + assertNotNull(result); + } + } + + /** + * Tests CBEFF merge with matching biometric types. + * Verifies that attempting to merge CBEFF files with the same biometric types throws BiometricTagMatchException. + */ + @Test + void mergeCbeffWithMatchingTypesShouldThrowException() throws Exception { + List file1BirList = createMockBIRListWithDifferentType("Face"); + List file2BirList = createMockBIRListWithDifferentType("Face"); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))) + .thenReturn(file1BirList) + .thenReturn(file2BirList); + + assertThrows(BiometricTagMatchException.class, () -> + cbeffToBiometricUtil.mergeCbeff(BASE64_CBEFF_DATA, BASE64_CBEFF_DATA) + ); + } + } + + /** + * Tests successful CBEFF extraction with specified types. + * Verifies that biometric data of specified types can be successfully extracted from CBEFF data. + */ + @Test + void extractCbeffWithTypesShouldSucceedWithMatchingTypes() throws Exception { + List birList = createMockBIRList(); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(birList); + when(cbeffutil.createXML(any(List.class))).thenReturn(TEST_XML_BYTES); + + InputStream result = cbeffToBiometricUtil.extractCbeffWithTypes(BASE64_CBEFF_DATA, + Arrays.asList("Face")); + + assertNotNull(result); + } + } + + /** + * Tests CBEFF extraction with no matching types. + * Verifies that the method returns null when no biometric data matches the specified types. + */ + @Test + void extractCbeffWithTypesNoMatchShouldReturnNull() throws Exception { + List birList = createMockBIRList(); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(birList); + + InputStream result = cbeffToBiometricUtil.extractCbeffWithTypes(BASE64_CBEFF_DATA, + Arrays.asList("Fingerprint")); + + assertNull(result); + } + } + + /** + * Tests CBEFF extraction with empty extracted BIR list. + * Verifies that the method returns null when the extraction process results in an empty BIR list. + */ + @Test + void extractCbeffWithTypesEmptyExtractedShouldReturnNull() throws Exception { + List birList = new ArrayList<>(); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CbeffToBiometricUtil.class)) + .thenReturn(mockLogger); + + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(birList); + + InputStream result = cbeffToBiometricUtil.extractCbeffWithTypes(BASE64_CBEFF_DATA, + Arrays.asList("Face")); + + assertNull(result); + } + } + + /** + * Tests the getBIRTypeList method functionality. + * Verifies that the method correctly retrieves the BIR type list from CBEFF data. + */ + @Test + void getBirTypeListShouldReturnExpectedList() throws Exception { + List expectedBirList = createMockBIRList(); + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(expectedBirList); + + List result = cbeffToBiometricUtil.getBIRTypeList(BASE64_CBEFF_DATA); + + assertEquals(expectedBirList, result); + } + + /** + * Tests the getBIRDataFromXML method functionality. + * Verifies that the method correctly retrieves BIR data from XML byte array. + */ + @Test + void getBirDataFromXmlShouldReturnExpectedData() throws Exception { + List expectedBirList = createMockBIRList(); + when(cbeffutil.getBIRDataFromXML(any(byte[].class))).thenReturn(expectedBirList); + + List result = cbeffToBiometricUtil.getBIRDataFromXML(TEST_XML_BYTES); + + assertEquals(expectedBirList, result); + } + + /** + * Tests the extractTemplate method functionality. + * Verifies that biometric template extraction works correctly with valid BIR data and flags. + */ + @Test + void extractTemplateShouldReturnExpectedResult() throws Exception { + BIR sampleBir = createMockBIR("Face", Arrays.asList("Front")); + BIR expectedResult = createMockBIR("Face", Arrays.asList("Front")); + KeyValuePair[] flags = new KeyValuePair[0]; + + Response mockResponse = new Response<>(); + mockResponse.setResponse(expectedResult); + + lenient().when(bioApi.extractTemplate(sampleBir, flags)).thenReturn(mockResponse); + + BIR result = cbeffToBiometricUtil.extractTemplate(sampleBir, flags); + + assertEquals(expectedResult, result); + } + + /** + * Tests the isBiometricType method through reflection with matching type. + * Verifies that the private method correctly identifies matching biometric types. + */ + @Test + void isBiometricTypeMethodShouldReturnTrueForMatchingType() throws Exception { + List biometricTypeList = Arrays.asList(BiometricType.FACE); + + boolean result = (boolean) ReflectionTestUtils.invokeMethod(cbeffToBiometricUtil, + "isBiometricType", "Face", biometricTypeList); + + assertEquals(true, result); + } + + /** + * Tests the isBiometricType method through reflection with no match. + * Verifies that the private method correctly identifies non-matching biometric types. + */ + @Test + void isBiometricTypeMethodShouldReturnFalseForNonMatchingType() throws Exception { + List biometricTypeList = Arrays.asList(BiometricType.FACE); + + boolean result = (boolean) ReflectionTestUtils.invokeMethod(cbeffToBiometricUtil, + "isBiometricType", "Fingerprint", biometricTypeList); + + assertEquals(false, result); + } + + /** + * Tests the isSubType method through reflection with matching subtype. + * Verifies that the private method correctly identifies matching biometric subtypes. + */ + @Test + void isSubTypeMethodShouldReturnTrueForMatchingSubtype() throws Exception { + List subTypeList = Arrays.asList("Front"); + List subType = Arrays.asList("Front"); + + boolean result = (boolean) ReflectionTestUtils.invokeMethod(cbeffToBiometricUtil, + "isSubType", subType, subTypeList); + + assertEquals(true, result); + } + + /** + * Tests the isSubType method through reflection with no match. + * Verifies that the private method correctly identifies non-matching biometric subtypes. + */ + @Test + void isSubTypeMethodShouldReturnFalseForNonMatchingSubtype() throws Exception { + List subTypeList = Arrays.asList("Front"); + List subType = Arrays.asList("Back"); + + boolean result = (boolean) ReflectionTestUtils.invokeMethod(cbeffToBiometricUtil, + "isSubType", subType, subTypeList); + + assertEquals(false, result); + } + + /** + * Tests the isBiometricTypeSame method with different types. + * Verifies that the private method correctly identifies when biometric types are different. + */ + @Test + void isBiometricTypeSameMethodShouldReturnFalseForDifferentTypes() throws Exception { + List file1BirList = createMockBIRListWithDifferentType("Face"); + List file2BirList = createMockBIRListWithDifferentType("Fingerprint"); + + boolean result = (boolean) ReflectionTestUtils.invokeMethod(cbeffToBiometricUtil, + "isBiometricTypeSame", file1BirList, file2BirList); + + assertEquals(false, result); + } + + /** + * Tests the isBiometricTypeSame method with same types. + * Verifies that the private method correctly identifies when biometric types are the same. + */ + @Test + void isBiometricTypeSameMethodShouldReturnTrueForSameTypes() throws Exception { + List file1BirList = createMockBIRListWithDifferentType("Face"); + List file2BirList = createMockBIRListWithDifferentType("Face"); + + boolean result = (boolean) ReflectionTestUtils.invokeMethod(cbeffToBiometricUtil, + "isBiometricTypeSame", file1BirList, file2BirList); + + assertEquals(true, result); + } + + /** + * Creates a mock BIR list for testing purposes. + * + * @return List of mock BIR objects with Face biometric type and Front subtype + */ + private List createMockBIRList() { + List birList = new ArrayList<>(); + BIR bir = createMockBIR("Face", Arrays.asList("Front")); + birList.add(bir); + return birList; + } + + /** + * Creates a mock BIR list with null BdbInfo for testing edge cases. + * + * @return List of mock BIR objects with null BdbInfo + */ + private List createMockBIRListWithNullBdbInfo() { + List birList = new ArrayList<>(); + BIR bir = new BIR(); + bir.setBdbInfo(null); + birList.add(bir); + return birList; + } + + /** + * Creates a mock BIR list with a specific biometric type. + * + * @param type the biometric type to set for the BIR objects + * @return List of mock BIR objects with the specified biometric type + */ + private List createMockBIRListWithDifferentType(String type) { + List birList = new ArrayList<>(); + BIR bir = createMockBIR(type, Arrays.asList("Front")); + birList.add(bir); + return birList; + } + + /** + * Creates a mock BIR object with specified type and subtypes. + * + * @param type the biometric type for the BIR object + * @param subTypes the list of subtypes for the BIR object + * @return a mock BIR object with the specified configuration + */ + private BIR createMockBIR(String type, List subTypes) { + BIR bir = new BIR(); + BDBInfo bdbInfo = new BDBInfo(); + + List biometricTypes = new ArrayList<>(); + biometricTypes.add(getBiometricTypeFromString(type)); + bdbInfo.setType(biometricTypes); + bdbInfo.setSubtype(subTypes); + + bir.setBdbInfo(bdbInfo); + bir.setBdb(TEST_IMAGE_BYTES); + + return bir; + } + + /** + * Converts a string representation to the corresponding BiometricType enum. + * + * @param type the string representation of the biometric type + * @return the corresponding BiometricType enum value + */ + private BiometricType getBiometricTypeFromString(String type) { + switch (type.toLowerCase()) { + case "face": + return BiometricType.FACE; + case "fingerprint": + return BiometricType.FINGER; + case "iris": + return BiometricType.IRIS; + default: + return BiometricType.FACE; + } + } +} diff --git a/src/test/java/io/mosip/print/util/CbeffValidatorTest.java b/src/test/java/io/mosip/print/util/CbeffValidatorTest.java new file mode 100644 index 00000000..a35dcf47 --- /dev/null +++ b/src/test/java/io/mosip/print/util/CbeffValidatorTest.java @@ -0,0 +1,724 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.xml.sax.SAXException; + +import io.mosip.print.constant.BiometricType; +import io.mosip.print.constant.CbeffConstant; +import io.mosip.print.entity.BDBInfo; +import io.mosip.print.entity.BIR; +import io.mosip.print.entity.BIRInfo; +import io.mosip.print.entity.RegistryIDType; +import io.mosip.print.entity.VersionType; +import io.mosip.print.exception.CbeffException; + +/** + * Test class for {@link CbeffValidator}. + * + * This class provides comprehensive unit tests for the CbeffValidator utility class, + * covering all methods including validation, XML creation, data retrieval based on + * type and subtype, and various edge cases and exception scenarios. + * + * The tests ensure proper validation of BIR data, XML marshalling/unmarshalling, + * biometric data extraction, and error handling for invalid data conditions. + * + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class CbeffValidatorTest { + + private BIR mockBIR; + private BDBInfo mockBDBInfo; + private RegistryIDType mockFormat; + private byte[] validXSD; + + private static final String VALID_SUBTYPE = "Left"; + private static final String VALID_SUBTYPE_LIST = "Left"; + + /** + * Sets up the test environment before each test method execution. + * + * Initializes mock objects and test data used across multiple test methods, + * including valid BIR structures, BDBInfo, and format information. + */ + @BeforeEach + void setUp() { + mockBIR = createValidBIR(); + mockBDBInfo = createValidBDBInfo(); + mockFormat = createValidFormat(); + validXSD = "valid xsd content".getBytes(); + } + + /** + * Tests successful validation of valid BIR data. + * + * Verifies that {@link CbeffValidator#validateXML(BIR)} returns false + * for valid BIR data structure with proper BDB, BDBInfo, and format information. + * + * @throws Exception if test execution fails + */ + @Test + void validateXMLSuccess() throws Exception { + assertFalse(CbeffValidator.validateXML(mockBIR)); + } + + /** + * Tests validation failure when BIR is null. + * + * Verifies that {@link CbeffValidator#validateXML(BIR)} throws + * {@link CbeffException} with appropriate message when BIR is null. + * + * @throws Exception if test execution fails + */ + @Test + void validateXMLWithNullBIR() throws Exception { + CbeffException exception = assertThrows(CbeffException.class, () -> + CbeffValidator.validateXML(null)); + assertEquals("BIR value is null", exception.getMessage()); + } + + /** + * Tests validation failure when BDB is empty. + * + * Verifies that {@link CbeffValidator#validateXML(BIR)} throws + * {@link CbeffException} when BIR contains empty BDB data. + * + * @throws Exception if test execution fails + */ + @Test + void validateXMLWithEmptyBDB() throws Exception { + BIR birWithEmptyBDB = createBIRWithEmptyBDB(); + + CbeffException exception = assertThrows(CbeffException.class, () -> + CbeffValidator.validateXML(birWithEmptyBDB)); + assertEquals("BDB value can't be empty", exception.getMessage()); + } + + /** + * Tests validation failure when BDBInfo is null. + * + * Verifies that {@link CbeffValidator#validateXML(BIR)} throws + * {@link CbeffException} when BIR contains null BDBInfo. + * + * @throws Exception if test execution fails + */ + @Test + void validateXMLWithNullBDBInfo() throws Exception { + BIR birWithNullBDBInfo = createBIRWithNullBDBInfo(); + + CbeffException exception = assertThrows(CbeffException.class, () -> + CbeffValidator.validateXML(birWithNullBDBInfo)); + assertEquals("BDB information can't be empty", exception.getMessage()); + } + + /** + * Tests validation failure when biometric types are null. + * + * Verifies that {@link CbeffValidator#validateXML(BIR)} throws + * {@link CbeffException} when BDBInfo contains null biometric types. + * + * @throws Exception if test execution fails + */ + @Test + void validateXMLWithNullBiometricTypes() throws Exception { + BIR birWithNullTypes = createBIRWithNullTypes(); + + CbeffException exception = assertThrows(CbeffException.class, () -> + CbeffValidator.validateXML(birWithNullTypes)); + assertEquals("Type value needs to be provided", exception.getMessage()); + } + + /** + * Tests validation failure when biometric types are empty. + * + * Verifies that {@link CbeffValidator#validateXML(BIR)} throws + * {@link CbeffException} when BDBInfo contains empty biometric types list. + * + * @throws Exception if test execution fails + */ + @Test + void validateXMLWithEmptyBiometricTypes() throws Exception { + BIR birWithEmptyTypes = createBIRWithEmptyTypes(); + + CbeffException exception = assertThrows(CbeffException.class, () -> + CbeffValidator.validateXML(birWithEmptyTypes)); + assertEquals("Type value needs to be provided", exception.getMessage()); + } + + /** + * Tests validation failure when format type is invalid. + * + * Verifies that {@link CbeffValidator#validateXML(BIR)} throws + * {@link CbeffException} when format type doesn't match biometric type. + * + * @throws Exception if test execution fails + */ + @Test + void validateXMLWithInvalidFormatType() throws Exception { + BIR birWithInvalidFormat = createBIRWithInvalidFormat(); + + CbeffException exception = assertThrows(CbeffException.class, () -> + CbeffValidator.validateXML(birWithInvalidFormat)); + assertEquals("Patron Format type is invalid", exception.getMessage()); + } + + /** + * Tests successful XML byte creation with valid BIR and XSD. + * + * Verifies that {@link CbeffValidator#createXMLBytes(BIR, byte[])} successfully + * creates XML bytes when provided with valid BIR data and XSD schema. + * + * @throws Exception if test execution fails + */ + @Test + void createXMLBytesSuccess() throws Exception { + try (MockedStatic xsdValidatorMock = mockStatic(CbeffXSDValidator.class)) { + xsdValidatorMock.when(() -> CbeffXSDValidator.validateXML(any(byte[].class), any(byte[].class))) + .thenAnswer(invocation -> null); + + byte[] result = CbeffValidator.createXMLBytes(mockBIR, validXSD); + assertNotNull(result); + assertTrue(result.length > 0); + } + } + + /** + * Tests XML byte creation failure due to XSD validation error. + * + * Verifies that {@link CbeffValidator#createXMLBytes(BIR, byte[])} throws + * {@link CbeffException} when XSD validation fails with SAXException. + * + * @throws Exception if test execution fails + */ + @Test + void createXMLBytesWithXSDValidationFailure() throws Exception { + try (MockedStatic xsdValidatorMock = mockStatic(CbeffXSDValidator.class)) { + SAXException saxException = new SAXException("Invalid XML: attribute error"); + xsdValidatorMock.when(() -> CbeffXSDValidator.validateXML(any(byte[].class), any(byte[].class))) + .thenThrow(saxException); + + CbeffException exception = assertThrows(CbeffException.class, () -> + CbeffValidator.createXMLBytes(mockBIR, validXSD)); + assertTrue(exception.getMessage().contains("XSD validation failed")); + } + } + + /** + * Tests successful BIR extraction from XML bytes. + * + * Verifies that {@link CbeffValidator#getBIRFromXML(byte[])} correctly + * unmarshals XML bytes back to BIR object structure. + * + * @throws Exception if test execution fails + */ + @Test + void getBIRFromXMLSuccess() throws Exception { + try (MockedStatic xsdValidatorMock = mockStatic(CbeffXSDValidator.class)) { + xsdValidatorMock.when(() -> CbeffXSDValidator.validateXML(any(byte[].class), any(byte[].class))) + .thenAnswer(invocation -> null); + + byte[] xmlBytes = CbeffValidator.createXMLBytes(mockBIR, validXSD); + BIR result = CbeffValidator.getBIRFromXML(xmlBytes); + + assertNotNull(result); + assertNotNull(result.getBirs()); + } + } + + /** + * Tests BDB data retrieval with both type and subtype specified. + * + * Verifies that {@link CbeffValidator#getBDBBasedOnTypeAndSubType(BIR, String, String)} + * correctly filters and returns BDB data based on specified biometric type and subtype. + * + * @throws Exception if test execution fails + */ + @Test + void getBDBBasedOnTypeAndSubTypeWithBothParams() throws Exception { + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(mockBIR, "FINGER", VALID_SUBTYPE); + assertNotNull(result); + } + + /** + * Tests BDB data retrieval with only type specified. + * + * Verifies that {@link CbeffValidator#getBDBBasedOnTypeAndSubType(BIR, String, String)} + * correctly returns BDB data when only biometric type is specified. + * + * @throws Exception if test execution fails + */ + @Test + void getBDBBasedOnTypeAndSubTypeWithTypeOnly() throws Exception { + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(mockBIR, "FINGER", null); + assertNotNull(result); + } + + /** + * Tests BDB data retrieval with only subtype specified. + * + * Verifies that {@link CbeffValidator#getBDBBasedOnTypeAndSubType(BIR, String, String)} + * correctly returns BDB data when only subtype is specified. + * + * @throws Exception if test execution fails + */ + @Test + void getBDBBasedOnTypeAndSubTypeWithSubTypeOnly() throws Exception { + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(mockBIR, null, VALID_SUBTYPE); + assertNotNull(result); + } + + /** + * Tests BDB data retrieval with null parameters. + * + * Verifies that {@link CbeffValidator#getBDBBasedOnTypeAndSubType(BIR, String, String)} + * returns all latest data when both type and subtype are null. + * + * @throws Exception if test execution fails + */ + @Test + void getBDBBasedOnTypeAndSubTypeWithNullParams() throws Exception { + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(mockBIR, null, null); + assertNotNull(result); + } + + /** + * Tests BDB data retrieval with empty BIR list. + * + * Verifies that {@link CbeffValidator#getBDBBasedOnTypeAndSubType(BIR, String, String)} + * handles empty BIR list gracefully and returns empty map. + * + * @throws Exception if test execution fails + */ + @Test + void getBDBBasedOnTypeAndSubTypeWithEmptyBIRList() throws Exception { + BIR emptyBIR = new BIR.BIRBuilder().build(); + emptyBIR.setBirs(new ArrayList<>()); + + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(emptyBIR, "FINGER", VALID_SUBTYPE); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + /** + * Tests all BDB data retrieval with type and subtype. + * + * Verifies that {@link CbeffValidator#getAllBDBData(BIR, String, String)} + * correctly returns all BDB data matching the specified criteria. + * + * @throws Exception if test execution fails + */ + @Test + void getAllBDBDataWithTypeAndSubType() throws Exception { + Map result = CbeffValidator.getAllBDBData(mockBIR, "FINGER", VALID_SUBTYPE); + assertNotNull(result); + } + + /** + * Tests all BDB data retrieval with type only. + * + * Verifies that {@link CbeffValidator#getAllBDBData(BIR, String, String)} + * correctly returns BDB data when only type is specified. + * + * @throws Exception if test execution fails + */ + @Test + void getAllBDBDataWithTypeOnly() throws Exception { + Map result = CbeffValidator.getAllBDBData(mockBIR, "FINGER", null); + assertNotNull(result); + } + + /** + * Tests all BDB data retrieval with subtype only. + * + * Verifies that {@link CbeffValidator#getAllBDBData(BIR, String, String)} + * correctly returns BDB data when only subtype is specified. + * + * @throws Exception if test execution fails + */ + @Test + void getAllBDBDataWithSubTypeOnly() throws Exception { + Map result = CbeffValidator.getAllBDBData(mockBIR, null, VALID_SUBTYPE); + assertNotNull(result); + } + + /** + * Tests all BDB data retrieval with null BIR list. + * + * Verifies that {@link CbeffValidator#getAllBDBData(BIR, String, String)} + * handles null BIR list gracefully and returns empty map. + * + * @throws Exception if test execution fails + */ + @Test + void getAllBDBDataWithNullBIRList() throws Exception { + BIR nullBIR = new BIR.BIRBuilder().build(); + nullBIR.setBirs(null); + + Map result = CbeffValidator.getAllBDBData(nullBIR, "FINGER", VALID_SUBTYPE); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + /** + * Tests BIR data extraction from XML by type. + * + * Verifies that {@link CbeffValidator#getBIRDataFromXMLType(byte[], String)} + * correctly filters and returns BIR data matching the specified type. + * + * @throws Exception if test execution fails + */ + @Test + void getBIRDataFromXMLTypeSuccess() throws Exception { + try (MockedStatic xsdValidatorMock = mockStatic(CbeffXSDValidator.class)) { + xsdValidatorMock.when(() -> CbeffXSDValidator.validateXML(any(byte[].class), any(byte[].class))) + .thenAnswer(invocation -> null); + + byte[] xmlBytes = CbeffValidator.createXMLBytes(mockBIR, validXSD); + List result = CbeffValidator.getBIRDataFromXMLType(xmlBytes, "FINGER"); + + assertNotNull(result); + } + } + + /** + * Tests BIR data extraction from XML with null type. + * + * Verifies that {@link CbeffValidator#getBIRDataFromXMLType(byte[], String)} + * handles null type parameter and returns empty list. + * + * @throws Exception if test execution fails + */ + @Test + void getBIRDataFromXMLTypeWithNullType() throws Exception { + try (MockedStatic xsdValidatorMock = mockStatic(CbeffXSDValidator.class)) { + xsdValidatorMock.when(() -> CbeffXSDValidator.validateXML(any(byte[].class), any(byte[].class))) + .thenAnswer(invocation -> null); + + byte[] xmlBytes = CbeffValidator.createXMLBytes(mockBIR, validXSD); + List result = CbeffValidator.getBIRDataFromXMLType(xmlBytes, null); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + } + + /** + * Tests enum validation with valid enum value. + * + * Verifies that {@link CbeffValidator#isInEnum(String, Class)} returns true + * for valid enum values. + * + * @throws Exception if test execution fails + */ + @Test + void isInEnumWithValidValue() throws Exception { + assertTrue(CbeffValidator.isInEnum("FINGER", BiometricType.class)); + } + + /** + * Tests enum validation with invalid enum value. + * + * Verifies that {@link CbeffValidator#isInEnum(String, Class)} returns false + * for invalid enum values. + * + * @throws Exception if test execution fails + */ + @Test + void isInEnumWithInvalidValue() throws Exception { + assertFalse(CbeffValidator.isInEnum("INVALID_TYPE", BiometricType.class)); + } + + /** + * Tests format validation for different biometric types. + * + * Verifies that the private validateFormatType method correctly validates + * format types for various biometric types including Finger, Iris, Face, and HandGeometry. + * + * @throws Exception if test execution fails + */ + @Test + void validateFormatTypeForDifferentTypes() throws Exception { + BIR fingerBIR = createBIRWithType(BiometricType.FINGER, CbeffConstant.FORMAT_TYPE_FINGER); + assertFalse(CbeffValidator.validateXML(fingerBIR)); + + BIR fingerMinutiaeBIR = createBIRWithType(BiometricType.FINGER, CbeffConstant.FORMAT_TYPE_FINGER_MINUTIAE); + assertFalse(CbeffValidator.validateXML(fingerMinutiaeBIR)); + + BIR irisBIR = createBIRWithType(BiometricType.IRIS, CbeffConstant.FORMAT_TYPE_IRIS); + assertFalse(CbeffValidator.validateXML(irisBIR)); + + BIR faceBIR = createBIRWithType(BiometricType.FACE, CbeffConstant.FORMAT_TYPE_FACE); + assertFalse(CbeffValidator.validateXML(faceBIR)); + } + + /** + * Tests getBiometricType method for special cases. + * + * Verifies that the private getBiometricType method correctly handles + * special case mappings like "FMR" to FINGER type. + * + * @throws Exception if test execution fails + */ + @Test + void getBiometricTypeSpecialCases() throws Exception { + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(mockBIR, "FMR", null); + assertNotNull(result); + + Map result2 = CbeffValidator.getBDBBasedOnTypeAndSubType(mockBIR, "Finger", null); + assertNotNull(result2); + } + + /** + * Tests BDB data retrieval with version and cbeffversion fields. + * + * Verifies that BIR data with version and cbeffversion fields are properly handled + * in the getBIRList method. + * + * @throws Exception if test execution fails + */ + @Test + void getBIRListWithVersionFields() throws Exception { + BIR birWithVersions = createBIRWithVersions(); + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(birWithVersions, null, null); + assertNotNull(result); + } + + /** + * Tests data retrieval with empty subtype list. + * + * Verifies that BIR data with empty subtype list is handled correctly + * and default "No Subtype" is applied. + * + * @throws Exception if test execution fails + */ + @Test + void getAllLatestDataWithEmptySubtype() throws Exception { + BIR birWithEmptySubtype = createBIRWithEmptySubtype(); + Map result = CbeffValidator.getBDBBasedOnTypeAndSubType(birWithEmptySubtype, null, null); + assertNotNull(result); + } + + // Helper methods for creating test data + + private BIR createValidBIR() { + BIR bir = new BIR.BIRBuilder() + .withBdb("test bdb data".getBytes()) + .withBdbInfo(createValidBDBInfo()) + .withBirInfo(new BIRInfo.BIRInfoBuilder().build()) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BDBInfo createValidBDBInfo() { + RegistryIDType format = new RegistryIDType(); + format.setOrganization("257"); + format.setType(String.valueOf(CbeffConstant.FORMAT_TYPE_FINGER)); + + return new BDBInfo.BDBInfoBuilder() + .withFormat(format) + .withType(Arrays.asList(BiometricType.FINGER)) + .withSubtype(Arrays.asList(VALID_SUBTYPE_LIST)) // Use valid subtype + .withCreationDate(LocalDateTime.now()) + .build(); + } + + private RegistryIDType createValidFormat() { + RegistryIDType format = new RegistryIDType(); + format.setOrganization("257"); + format.setType(String.valueOf(CbeffConstant.FORMAT_TYPE_FINGER)); + return format; + } + + private BIR createBIRWithEmptyBDB() { + BIR bir = new BIR.BIRBuilder() + .withBdb(new byte[0]) + .withBdbInfo(createValidBDBInfo()) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BIR createBIRWithNullBDBInfo() { + BIR bir = new BIR.BIRBuilder() + .withBdb("test data".getBytes()) + .withBdbInfo(null) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BIR createBIRWithNullTypes() { + RegistryIDType format = new RegistryIDType(); + format.setType(String.valueOf(CbeffConstant.FORMAT_TYPE_FINGER)); + + BDBInfo bdbInfo = new BDBInfo.BDBInfoBuilder() + .withFormat(format) + .withType(null) + .build(); + + BIR bir = new BIR.BIRBuilder() + .withBdb("test data".getBytes()) + .withBdbInfo(bdbInfo) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BIR createBIRWithEmptyTypes() { + RegistryIDType format = new RegistryIDType(); + format.setType(String.valueOf(CbeffConstant.FORMAT_TYPE_FINGER)); + + BDBInfo bdbInfo = new BDBInfo.BDBInfoBuilder() + .withFormat(format) + .withType(new ArrayList<>()) + .build(); + + BIR bir = new BIR.BIRBuilder() + .withBdb("test data".getBytes()) + .withBdbInfo(bdbInfo) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BIR createBIRWithInvalidFormat() { + RegistryIDType format = new RegistryIDType(); + format.setType("999"); + + BDBInfo bdbInfo = new BDBInfo.BDBInfoBuilder() + .withFormat(format) + .withType(Arrays.asList(BiometricType.FINGER)) + .build(); + + BIR bir = new BIR.BIRBuilder() + .withBdb("test data".getBytes()) + .withBdbInfo(bdbInfo) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BIR createBIRWithType(BiometricType type, long formatType) { + RegistryIDType format = new RegistryIDType(); + format.setType(String.valueOf(formatType)); + + BDBInfo bdbInfo = new BDBInfo.BDBInfoBuilder() + .withFormat(format) + .withType(Arrays.asList(type)) + .withSubtype(Arrays.asList(VALID_SUBTYPE_LIST)) // Use valid subtype + .withCreationDate(LocalDateTime.now()) + .build(); + + BIR bir = new BIR.BIRBuilder() + .withBdb("test data".getBytes()) + .withBdbInfo(bdbInfo) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BIR createBIRWithVersions() { + VersionType version = new VersionType(); + version.setMajor(1); + version.setMinor(0); + + VersionType cbeffVersion = new VersionType(); + cbeffVersion.setMajor(1); + cbeffVersion.setMinor(1); + + BIR bir = new BIR.BIRBuilder() + .withBdb("test data".getBytes()) + .withBdbInfo(createValidBDBInfo()) + .withBirInfo(new BIRInfo.BIRInfoBuilder().build()) + .withVersion(version) + .withCbeffversion(cbeffVersion) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } + + private BIR createBIRWithEmptySubtype() { + RegistryIDType format = new RegistryIDType(); + format.setType(String.valueOf(CbeffConstant.FORMAT_TYPE_FINGER)); + + BDBInfo bdbInfo = new BDBInfo.BDBInfoBuilder() + .withFormat(format) + .withType(Arrays.asList(BiometricType.FINGER)) + .withSubtype(new ArrayList<>()) + .withCreationDate(LocalDateTime.now()) + .build(); + + BIR bir = new BIR.BIRBuilder() + .withBdb("test data".getBytes()) + .withBdbInfo(bdbInfo) + .build(); + + List birList = new ArrayList<>(); + birList.add(bir); + + BIR rootBir = new BIR.BIRBuilder().build(); + rootBir.setBirs(birList); + return rootBir; + } +} diff --git a/src/test/java/io/mosip/print/util/CryptoCoreUtilTest.java b/src/test/java/io/mosip/print/util/CryptoCoreUtilTest.java new file mode 100644 index 00000000..13e68dbf --- /dev/null +++ b/src/test/java/io/mosip/print/util/CryptoCoreUtilTest.java @@ -0,0 +1,578 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.interfaces.RSAPrivateKey; + +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.digest.DigestUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.slf4j.Logger; +import org.springframework.test.util.ReflectionTestUtils; + +import io.mosip.print.exception.CryptoManagerException; +import io.mosip.print.logger.PrintLogger; + +/** + * Unit tests for {@link CryptoCoreUtil} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the CryptoCoreUtil class, + * including cryptographic operations such as encryption/decryption, key store management, certificate handling, + * symmetric and asymmetric decryption, and various exception scenarios in cryptographic processing.

+ * + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class CryptoCoreUtilTest { + + @InjectMocks + private CryptoCoreUtil cryptoCoreUtil; + + private static final String TEST_FILENAME = "test.p12"; + private static final String TEST_PASSWORD = "password"; + private static final String TEST_ALIAS = "alias"; + private static final String KEY_SPLITTER = "#KEY_SPLITTER#"; + + private KeyStore.PrivateKeyEntry mockPrivateKeyEntry; + private RSAPrivateKey realRSAPrivateKey; + private Certificate mockCertificate; + private SecretKey secretKey; + + /** + * Sets up test fixtures before each test method execution. + * Initializes mock objects, cryptographic keys, and configures the CryptoCoreUtil instance + * with necessary properties for testing encryption and decryption operations. + */ + @BeforeEach + void setUp() throws Exception { + ReflectionTestUtils.setField(cryptoCoreUtil, "fileName", TEST_FILENAME); + ReflectionTestUtils.setField(cryptoCoreUtil, "cyptoPassword", TEST_PASSWORD); + ReflectionTestUtils.setField(cryptoCoreUtil, "alias", TEST_ALIAS); + ReflectionTestUtils.setField(cryptoCoreUtil, "isThumbprint", true); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair keyPair = keyGen.generateKeyPair(); + realRSAPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + + KeyGenerator aesKeyGen = KeyGenerator.getInstance("AES"); + aesKeyGen.init(256); + secretKey = aesKeyGen.generateKey(); + + mockPrivateKeyEntry = mock(KeyStore.PrivateKeyEntry.class); + mockCertificate = mock(Certificate.class); + when(mockPrivateKeyEntry.getPrivateKey()).thenReturn(realRSAPrivateKey); + } + + /** + * Tests successful decryption operation. + * Verifies that encrypted data can be successfully decrypted using the decrypt method + * when valid input data and proper key store entry are provided. + */ + @Test + void decryptShouldSucceedWithValidData() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + String originalText = "test data"; + String encodedData = Base64.encodeBase64String(originalText.getBytes()); + + CryptoCoreUtil spyCrypto = mock(CryptoCoreUtil.class); + when(spyCrypto.loadP12()).thenReturn(mockPrivateKeyEntry); + when(spyCrypto.decryptData(any(byte[].class), any(KeyStore.PrivateKeyEntry.class))) + .thenReturn(originalText.getBytes()); + when(spyCrypto.decrypt(anyString())).thenCallRealMethod(); + + String result = spyCrypto.decrypt(encodedData); + assertEquals(originalText, result); + } + } + + /** + * Tests decrypt method when an exception occurs in the try block. + * Verifies that CryptoManagerException is thrown when the P12 keystore loading fails + * during the decryption process. + */ + @Test + void decryptWithExceptionInTryBlockShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + CryptoCoreUtil spyCrypto = mock(CryptoCoreUtil.class); + when(spyCrypto.loadP12()).thenThrow(new RuntimeException("Load P12 failed")); + when(spyCrypto.decrypt(anyString())).thenCallRealMethod(); + + assertThrows(CryptoManagerException.class, () -> + spyCrypto.decrypt("test-data")); + } + } + + /** + * Tests loadP12 method when KeyStoreException occurs. + * Verifies that CryptoManagerException is thrown when KeyStore.getInstance fails + * with a KeyStoreException during P12 keystore loading. + */ + @Test + void loadP12WithKeyStoreExceptionShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class); + MockedStatic keyStoreMock = mockStatic(KeyStore.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + keyStoreMock.when(() -> KeyStore.getInstance("PKCS12")) + .thenThrow(new java.security.KeyStoreException("Test KeyStore exception")); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.loadP12()); + } + } + + /** + * Tests decryptData method with VERSION_RSA_2048 header for successful path coverage. + * Verifies that the method processes data with VERSION_RSA_2048 header correctly, + * even though it may throw CryptoManagerException due to invalid test data structure. + */ + @Test + void decryptDataWithVersionHeaderShouldProcessCorrectly() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + byte[] versionHeader = CryptoCoreUtil.VERSION_RSA_2048; + byte[] thumbprint = new byte[32]; + byte[] encSymKey = secretKey.getEncoded(); + + byte[] encryptedKey = new byte[versionHeader.length + thumbprint.length + encSymKey.length]; + System.arraycopy(versionHeader, 0, encryptedKey, 0, versionHeader.length); + System.arraycopy(thumbprint, 0, encryptedKey, versionHeader.length, thumbprint.length); + System.arraycopy(encSymKey, 0, encryptedKey, versionHeader.length + thumbprint.length, encSymKey.length); + + byte[] aad = new byte[32]; + byte[] nonce = new byte[12]; + System.arraycopy(nonce, 0, aad, 0, 12); + + byte[] encData = "encrypted content".getBytes(); + + byte[] dataPayload = new byte[aad.length + encData.length]; + System.arraycopy(aad, 0, dataPayload, 0, aad.length); + System.arraycopy(encData, 0, dataPayload, aad.length, encData.length); + + byte[] requestData = buildRequestData(encryptedKey, dataPayload); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.decryptData(requestData, mockPrivateKeyEntry)); + } + } + + /** + * Tests decryptData method with thumbprint but no version header. + * Verifies that the method handles data with thumbprint configuration enabled + * but without version header in the encrypted data. + */ + @Test + void decryptDataWithThumbprintNoVersionShouldHandleCorrectly() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + ReflectionTestUtils.setField(cryptoCoreUtil, "isThumbprint", true); + + byte[] thumbprint = new byte[32]; + byte[] encSymKey = secretKey.getEncoded(); + + byte[] encryptedKey = new byte[thumbprint.length + encSymKey.length]; + System.arraycopy(thumbprint, 0, encryptedKey, 0, thumbprint.length); + System.arraycopy(encSymKey, 0, encryptedKey, thumbprint.length, encSymKey.length); + + byte[] dataPayload = "encrypted content".getBytes(); + byte[] requestData = buildRequestData(encryptedKey, dataPayload); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.decryptData(requestData, mockPrivateKeyEntry)); + } + } + + /** + * Tests decryptData method without thumbprint configuration. + * Verifies that the method handles data processing when thumbprint is disabled + * and follows the appropriate decryption path. + */ + @Test + void decryptDataWithoutThumbprintShouldHandleCorrectly() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + ReflectionTestUtils.setField(cryptoCoreUtil, "isThumbprint", false); + + byte[] encSymKey = secretKey.getEncoded(); + byte[] dataPayload = "encrypted content".getBytes(); + byte[] requestData = buildRequestData(encSymKey, dataPayload); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.decryptData(requestData, mockPrivateKeyEntry)); + } + } + + /** + * Tests decryptData method when an exception occurs in the try block. + * Verifies that CryptoManagerException is thrown when invalid request data + * causes an exception during the decryption process. + */ + @Test + void decryptDataWithExceptionInTryBlockShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + byte[] invalidRequestData = "invalid".getBytes(); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.decryptData(invalidRequestData, mockPrivateKeyEntry)); + } + } + + /** + * Tests parseEncryptKeyHeader method with valid header data. + * Verifies that the method correctly parses and returns the VERSION_RSA_2048 header + * when present at the beginning of the encrypted key data. + */ + @Test + void parseEncryptKeyHeaderWithValidHeaderShouldReturnHeader() { + byte[] encryptedKey = new byte[CryptoCoreUtil.VERSION_RSA_2048.length + 10]; + System.arraycopy(CryptoCoreUtil.VERSION_RSA_2048, 0, encryptedKey, 0, CryptoCoreUtil.VERSION_RSA_2048.length); + + byte[] result = cryptoCoreUtil.parseEncryptKeyHeader(encryptedKey); + assertArrayEquals(CryptoCoreUtil.VERSION_RSA_2048, result); + } + + /** + * Tests parseEncryptKeyHeader method with invalid header data. + * Verifies that the method returns an empty byte array when the encrypted key + * does not contain a valid VERSION_RSA_2048 header. + */ + @Test + void parseEncryptKeyHeaderWithInvalidHeaderShouldReturnEmptyArray() { + byte[] encryptedKey = "INVALID".getBytes(); + + byte[] result = cryptoCoreUtil.parseEncryptKeyHeader(encryptedKey); + assertEquals(0, result.length); + } + + /** + * Tests the getSplitterIndex private method functionality. + * Verifies that the method correctly identifies the position of the key splitter + * within the provided byte array data. + */ + @Test + void getSplitterIndexShouldReturnCorrectIndex() throws Exception { + String testString = "prefix" + KEY_SPLITTER + "suffix"; + byte[] testData = testString.getBytes(); + + java.lang.reflect.Method method = CryptoCoreUtil.class.getDeclaredMethod( + "getSplitterIndex", byte[].class, int.class, String.class); + method.setAccessible(true); + + int result = (int) method.invoke(null, testData, 0, KEY_SPLITTER); + assertEquals(6, result); // "prefix".length() + } + + /** + * Tests getSplitterIndex method when no matching splitter is found. + * Verifies that the method returns the full data length when the key splitter + * is not present in the provided byte array. + */ + @Test + void getSplitterIndexWithNoMatchShouldReturnFullLength() throws Exception { + String testString = "prefix_suffix"; + byte[] testData = testString.getBytes(); + + java.lang.reflect.Method method = CryptoCoreUtil.class.getDeclaredMethod( + "getSplitterIndex", byte[].class, int.class, String.class); + method.setAccessible(true); + + int result = (int) method.invoke(null, testData, 0, KEY_SPLITTER); + assertEquals(testData.length, result); + } + + /** + * Tests asymmetricDecrypt method with exception scenarios. + * Verifies that the method properly handles exceptions during asymmetric decryption + * operations with invalid data. + */ + @Test + void asymmetricDecryptWithExceptionShouldThrowException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + java.lang.reflect.Method method = CryptoCoreUtil.class.getDeclaredMethod( + "asymmetricDecrypt", PrivateKey.class, BigInteger.class, byte[].class); + method.setAccessible(true); + + byte[] testData = "invalid data for decryption".getBytes(); + + assertThrows(Exception.class, () -> { + try { + method.invoke(null, realRSAPrivateKey, realRSAPrivateKey.getModulus(), testData); + } catch (java.lang.reflect.InvocationTargetException e) { + throw e.getCause(); + } + }); + } + } + + /** + * Tests private symmetricDecrypt method with exception handling. + * Verifies that the method returns null when an exception occurs during + * symmetric decryption, covering the empty catch block scenario. + */ + @Test + void privateSymmetricDecryptWithExceptionShouldReturnNull() throws Exception { + java.lang.reflect.Method method = CryptoCoreUtil.class.getDeclaredMethod( + "symmetricDecrypt", SecretKey.class, byte[].class, byte[].class); + method.setAccessible(true); + + byte[] invalidData = "invalid".getBytes(); + byte[] result = (byte[]) method.invoke(null, secretKey, invalidData, null); + + assertNull(result); + } + + /** + * Tests private symmetricDecrypt method with valid data. + * Verifies that the method can successfully decrypt valid encrypted data + * using the AES/GCM/NoPadding algorithm. + */ + @Test + void privateSymmetricDecryptWithValidDataShouldSucceed() throws Exception { + java.lang.reflect.Method method = CryptoCoreUtil.class.getDeclaredMethod( + "symmetricDecrypt", SecretKey.class, byte[].class, byte[].class); + method.setAccessible(true); + + byte[] plaintext = "test data".getBytes(); + javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, secretKey); + byte[] encrypted = cipher.doFinal(plaintext); + byte[] iv = cipher.getIV(); + + byte[] testData = new byte[encrypted.length + iv.length]; + System.arraycopy(encrypted, 0, testData, 0, encrypted.length); + System.arraycopy(iv, 0, testData, encrypted.length, iv.length); + + byte[] result = (byte[]) method.invoke(null, secretKey, testData, null); + + if (result != null) { + assertArrayEquals(plaintext, result); + } else { + assertNull(result); + } + } + + /** + * Tests symmetricDecrypt method with IllegalBlockSizeException. + * Verifies that CryptoManagerException is thrown when IllegalBlockSizeException + * occurs during symmetric decryption operations. + */ + @Test + void symmetricDecryptWithIllegalBlockSizeExceptionShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + byte[] validNonce = new byte[12]; + byte[] emptyData = new byte[0]; + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.symmetricDecrypt(secretKey, emptyData, validNonce, null)); + } + } + + /** + * Tests symmetricDecrypt method with BadPaddingException. + * Verifies that CryptoManagerException is thrown when BadPaddingException + * occurs due to invalid encrypted data during symmetric decryption. + */ + @Test + void symmetricDecryptWithBadPaddingExceptionShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + byte[] validNonce = new byte[12]; + byte[] invalidData = "invalid encrypted data".getBytes(); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.symmetricDecrypt(secretKey, invalidData, validNonce, null)); + } + } + + /** + * Tests symmetricDecrypt method with NoSuchAlgorithmException. + * Verifies that CryptoManagerException is thrown when NoSuchAlgorithmException + * occurs during cipher initialization in symmetric decryption. + */ + @Test + void symmetricDecryptWithNoSuchAlgorithmExceptionShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class); + MockedStatic cipherMock = mockStatic(javax.crypto.Cipher.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + cipherMock.when(() -> javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")) + .thenThrow(new NoSuchAlgorithmException("Test exception")); + + byte[] validNonce = new byte[12]; + byte[] data = "test".getBytes(); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.symmetricDecrypt(secretKey, data, validNonce, null)); + } + } + + /** + * Tests symmetricDecrypt method with NoSuchPaddingException. + * Verifies that CryptoManagerException is thrown when NoSuchPaddingException + * occurs during cipher initialization in symmetric decryption. + */ + @Test + void symmetricDecryptWithNoSuchPaddingExceptionShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class); + MockedStatic cipherMock = mockStatic(javax.crypto.Cipher.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + cipherMock.when(() -> javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")) + .thenThrow(new NoSuchPaddingException("Test exception")); + + byte[] validNonce = new byte[12]; + byte[] data = "test".getBytes(); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.symmetricDecrypt(secretKey, data, validNonce, null)); + } + } + + /** + * Tests symmetricDecrypt method with InvalidKeyException. + * Verifies that CryptoManagerException is thrown when InvalidKeyException + * occurs due to an invalid secret key during symmetric decryption. + */ + @Test + void symmetricDecryptWithInvalidKeyExceptionShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + SecretKeySpec invalidKey = new SecretKeySpec(new byte[10], "AES"); + byte[] validNonce = new byte[12]; + byte[] data = "test".getBytes(); + + assertThrows(CryptoManagerException.class, () -> + cryptoCoreUtil.symmetricDecrypt(invalidKey, data, validNonce, null)); + } + } + + /** + * Tests getCertificateThumbprint method with successful operation. + * Verifies that the method correctly generates a SHA-256 thumbprint + * from the provided certificate data. + */ + @Test + void getCertificateThumbprintShouldSucceedWithValidCertificate() throws Exception { + try (MockedStatic digestUtilsMock = mockStatic(DigestUtils.class)) { + byte[] certData = "certificate data".getBytes(); + byte[] expectedHash = "hash result".getBytes(); + + when(mockCertificate.getEncoded()).thenReturn(certData); + digestUtilsMock.when(() -> DigestUtils.sha256(certData)).thenReturn(expectedHash); + + byte[] result = CryptoCoreUtil.getCertificateThumbprint(mockCertificate); + assertArrayEquals(expectedHash, result); + } + } + + /** + * Tests getCertificateThumbprint method with exception handling. + * Verifies that CryptoManagerException is thrown when CertificateEncodingException + * occurs during certificate thumbprint generation. + */ + @Test + void getCertificateThumbprintWithExceptionShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(CryptoCoreUtil.class)) + .thenReturn(mockLogger); + + when(mockCertificate.getEncoded()).thenThrow(new CertificateEncodingException("Test exception")); + + assertThrows(CryptoManagerException.class, () -> + CryptoCoreUtil.getCertificateThumbprint(mockCertificate)); + } + } + + /** + * Helper method to build request data with key splitter for testing purposes. + * Creates properly formatted request data by combining encrypted key, key splitter, and data payload. + * + * @param encryptedKey the encrypted key bytes + * @param dataPayload the data payload bytes + * @return combined request data with key splitter + */ + private byte[] buildRequestData(byte[] encryptedKey, byte[] dataPayload) { + byte[] keySplitterBytes = KEY_SPLITTER.getBytes(); + byte[] requestData = new byte[encryptedKey.length + keySplitterBytes.length + dataPayload.length]; + + System.arraycopy(encryptedKey, 0, requestData, 0, encryptedKey.length); + System.arraycopy(keySplitterBytes, 0, requestData, encryptedKey.length, keySplitterBytes.length); + System.arraycopy(dataPayload, 0, requestData, encryptedKey.length + keySplitterBytes.length, dataPayload.length); + + return requestData; + } +} diff --git a/src/test/java/io/mosip/print/util/CryptoUtilTest.java b/src/test/java/io/mosip/print/util/CryptoUtilTest.java new file mode 100644 index 00000000..d353714a --- /dev/null +++ b/src/test/java/io/mosip/print/util/CryptoUtilTest.java @@ -0,0 +1,706 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; + +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.NoSuchPaddingException; + +import org.apache.commons.codec.binary.Base64; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import io.mosip.print.dto.CryptoWithPinRequestDto; +import io.mosip.print.exception.CryptoManagerException; +import io.mosip.print.exception.ParseException; + +/** + * Unit tests for {@link CryptoUtil} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the CryptoUtil class, + * including PIN-based decryption operations, cryptographic algorithm handling, exception scenarios, + * key derivation functions, symmetric decryption with various data formats, and edge cases for + * cryptographic operations with user authentication.

+ */ +@ExtendWith(MockitoExtension.class) +class CryptoUtilTest { + + @InjectMocks + private CryptoUtil cryptoUtil; + + private static final String TEST_PIN = "testPin123"; + private static final int SYMMETRIC_KEY_LENGTH = 256; + private static final int ITERATIONS = 100000; + private static final String PASSWORD_ALGORITHM = "PBKDF2WithHmacSHA512"; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the CryptoUtil instance with cryptographic configuration parameters + * including symmetric key length, iteration count, and password hashing algorithm. + */ + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(cryptoUtil, "symmetricKeyLength", SYMMETRIC_KEY_LENGTH); + ReflectionTestUtils.setField(cryptoUtil, "iterations", ITERATIONS); + ReflectionTestUtils.setField(cryptoUtil, "passwordAlgorithm", PASSWORD_ALGORITHM); + } + + /** + * Tests decryption with PIN using valid data structure but invalid encryption. + * Verifies that the method handles properly formatted data that contains invalid + * encryption content and throws appropriate exceptions. + */ + @Test + void decryptWithPinValidStructureShouldThrowExceptionForInvalidEncryption() throws Exception { + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption with PIN when NoSuchAlgorithmException occurs in hash method. + * Verifies that CryptoManagerException is thrown when an invalid password hashing + * algorithm is configured in the system. + */ + @Test + void decryptWithPinNoSuchAlgorithmExceptionShouldThrowCryptoManagerException() throws Exception { + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + ReflectionTestUtils.setField(cryptoUtil, "passwordAlgorithm", "INVALID_ALGORITHM"); + + assertThrows(CryptoManagerException.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption with PIN when IllegalArgumentException occurs due to invalid key length. + * Verifies that IllegalArgumentException is thrown when an invalid symmetric key length + * is configured for the cryptographic operations. + */ + @Test + void decryptWithPinInvalidKeyLengthShouldThrowIllegalArgumentException() throws Exception { + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + ReflectionTestUtils.setField(cryptoUtil, "symmetricKeyLength", -1); + + assertThrows(IllegalArgumentException.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption with PIN when input data is too short. + * Verifies that the method throws appropriate exceptions when the encrypted data + * does not contain sufficient bytes for proper decryption processing. + */ + @Test + void decryptWithPinDataTooShortShouldThrowException() throws Exception { + String shortData = Base64.encodeBase64String(new byte[30]); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(shortData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption with PIN when InvalidKeyException occurs. + * Verifies that the method handles invalid key scenarios appropriately during + * the symmetric decryption process and throws expected exceptions. + */ + @Test + void decryptWithPinInvalidKeyShouldThrowException() throws Exception { + String invalidKeyData = createInvalidKeyData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(invalidKeyData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption with PIN when InvalidAlgorithmParameterException occurs. + * Verifies that the method handles invalid algorithm parameter scenarios during + * cryptographic operations and throws appropriate exceptions. + */ + @Test + void decryptWithPinInvalidAlgorithmParameterShouldThrowException() throws Exception { + String invalidParamData = createInvalidParameterData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(invalidParamData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption with PIN when IllegalBlockSizeException occurs. + * Verifies that the method handles illegal block size scenarios during symmetric + * decryption operations and throws appropriate exceptions. + */ + @Test + void decryptWithPinIllegalBlockSizeShouldThrowException() throws Exception { + String invalidBlockData = createInvalidBlockSizeData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(invalidBlockData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption with PIN when BadPaddingException occurs. + * Verifies that the method handles bad padding scenarios, typically caused by + * incorrect PIN or corrupted encrypted data, and throws appropriate exceptions. + */ + @Test + void decryptWithPinBadPaddingShouldThrowException() throws Exception { + String badPaddingData = createBadPaddingData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(badPaddingData); + requestDto.setUserPin("wrongPin"); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests symmetric decrypt functionality with null IV path. + * Verifies that the method handles null initialization vector scenarios + * appropriately and throws expected exceptions during decryption. + */ + @Test + void symmetricDecryptNullIvPathShouldThrowException() throws Exception { + String nullIvData = createNullIvTestData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(nullIvData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests symmetric decrypt when NoSuchAlgorithmException occurs in cipher creation. + * Verifies that CryptoManagerException is thrown when the cryptographic algorithm + * is not available during cipher initialization. + */ + @Test + void symmetricDecryptCipherNoSuchAlgorithmShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic cipherMock = mockStatic(javax.crypto.Cipher.class)) { + cipherMock.when(() -> javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")) + .thenThrow(new NoSuchAlgorithmException("Algorithm not found")); + + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(CryptoManagerException.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + } + + /** + * Tests symmetric decrypt when NoSuchPaddingException occurs in cipher creation. + * Verifies that CryptoManagerException is thrown when the specified padding scheme + * is not available during cipher initialization. + */ + @Test + void symmetricDecryptCipherNoSuchPaddingShouldThrowCryptoManagerException() throws Exception { + try (MockedStatic cipherMock = mockStatic(javax.crypto.Cipher.class)) { + cipherMock.when(() -> javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")) + .thenThrow(new NoSuchPaddingException("Padding not found")); + + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(CryptoManagerException.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + } + + /** + * Tests static symmetric decrypt method with exception scenarios. + * Verifies that the static decryption method handles various exception conditions + * appropriately during cryptographic operations. + */ + @Test + void staticSymmetricDecryptExceptionShouldThrowException() throws Exception { + String staticDecryptData = createStaticDecryptExceptionData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(staticDecryptData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests static symmetric decrypt with null AAD (Additional Authenticated Data). + * Verifies that the method handles null AAD scenarios appropriately during + * authenticated encryption decryption operations. + */ + @Test + void staticSymmetricDecryptNullAadShouldThrowException() throws Exception { + String nullAadData = createNullAadData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(nullAadData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests static symmetric decrypt with empty AAD (Additional Authenticated Data). + * Verifies that the method handles empty AAD scenarios appropriately during + * authenticated encryption decryption operations. + */ + @Test + void staticSymmetricDecryptEmptyAadShouldThrowException() throws Exception { + String emptyAadData = createEmptyAadData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(emptyAadData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests hash method functionality with invalid algorithm configuration. + * Verifies that CryptoManagerException with NoSuchAlgorithmException cause is thrown + * when an invalid password hashing algorithm is specified. + */ + @Test + void hashWithInvalidAlgorithmShouldThrowCryptoManagerExceptionWithNoSuchAlgorithmCause() throws Exception { + ReflectionTestUtils.setField(cryptoUtil, "passwordAlgorithm", "INVALID_ALGORITHM"); + + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + CryptoManagerException exception = assertThrows(CryptoManagerException.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + + assertTrue(exception.getCause() instanceof NoSuchAlgorithmException); + } + + /** + * Tests hash method with invalid iterations configuration. + * Verifies that the method throws appropriate exceptions when invalid iteration + * count is specified for the password-based key derivation function. + */ + @Test + void hashWithInvalidIterationsShouldThrowException() throws Exception { + ReflectionTestUtils.setField(cryptoUtil, "iterations", 0); + + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests hexDecode functionality with odd length string causing ParseException. + * Verifies that ParseException is thrown when attempting to decode hexadecimal + * strings with odd character lengths that cannot be properly parsed. + */ + @Test + void hexDecodeOddLengthShouldThrowParseException() throws Exception { + try (MockedStatic converterMock = mockStatic(javax.xml.bind.DatatypeConverter.class)) { + converterMock.when(() -> javax.xml.bind.DatatypeConverter.printHexBinary(org.mockito.ArgumentMatchers.any())) + .thenReturn("ABC"); // Odd length hex string + + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(ParseException.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + } + + /** + * Tests decryption functionality with different symmetric key lengths. + * Verifies that the method handles various key length configurations correctly + * and throws appropriate exceptions for unsupported or invalid key sizes. + */ + @Test + void decryptWithDifferentKeyLengthShouldThrowException() throws Exception { + ReflectionTestUtils.setField(cryptoUtil, "symmetricKeyLength", 128); + + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption functionality with different iteration count configurations. + * Verifies that the method handles various iteration count settings for + * password-based key derivation and throws appropriate exceptions. + */ + @Test + void decryptWithDifferentIterationCountShouldThrowException() throws Exception { + ReflectionTestUtils.setField(cryptoUtil, "iterations", 50000); + + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests symmetric decrypt functionality with non-null AAD. + * Verifies that the method handles Additional Authenticated Data scenarios + * appropriately during authenticated encryption decryption operations. + */ + @Test + void symmetricDecryptWithNonNullAadShouldThrowException() throws Exception { + String nonNullAadData = createNonNullAadData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(nonNullAadData); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption functionality with null input data. + * Verifies that the method handles null data input appropriately and throws + * expected exceptions when no encrypted data is provided. + */ + @Test + void decryptWithNullDataShouldThrowException() throws Exception { + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(null); + requestDto.setUserPin(TEST_PIN); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Tests decryption functionality with null PIN parameter. + * Verifies that the method handles null PIN input appropriately and throws + * expected exceptions when no user authentication PIN is provided. + */ + @Test + void decryptWithNullPinShouldThrowException() throws Exception { + String validStructureData = createValidStructureData(); + + CryptoWithPinRequestDto requestDto = new CryptoWithPinRequestDto(); + requestDto.setData(validStructureData); + requestDto.setUserPin(null); + + assertThrows(Exception.class, () -> + cryptoUtil.decryptWithPin(requestDto) + ); + } + + /** + * Helper method to create test data with valid structure for testing purposes. + * Creates properly formatted encrypted data containing salt, nonce, and encrypted content + * for use in cryptographic operation testing. + * + * @return Base64-encoded string containing valid structure test data + */ + private String createValidStructureData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[32]; + + Arrays.fill(salt, (byte) 1); + Arrays.fill(nonce, (byte) 2); + Arrays.fill(encryptedData, (byte) 3); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data that would cause invalid key scenarios. + * Creates data designed to trigger invalid key exceptions during cryptographic operations. + * + * @return Base64-encoded string containing invalid key test data + */ + private String createInvalidKeyData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[16]; + + Arrays.fill(salt, (byte) 255); + Arrays.fill(nonce, (byte) 255); + Arrays.fill(encryptedData, (byte) 255); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data that would cause invalid algorithm parameter exceptions. + * Creates data designed to trigger invalid algorithm parameter exceptions during operations. + * + * @return Base64-encoded string containing invalid parameter test data + */ + private String createInvalidParameterData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[8]; + + Arrays.fill(salt, (byte) 0); + Arrays.fill(nonce, (byte) 0); + Arrays.fill(encryptedData, (byte) 0); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data that would cause invalid block size exceptions. + * Creates data designed to trigger illegal block size exceptions during decryption. + * + * @return Base64-encoded string containing invalid block size test data + */ + private String createInvalidBlockSizeData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[1]; + + Arrays.fill(salt, (byte) 1); + Arrays.fill(nonce, (byte) 1); + Arrays.fill(encryptedData, (byte) 1); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data that would cause bad padding exceptions. + * Creates data designed to trigger bad padding exceptions during decryption operations. + * + * @return Base64-encoded string containing bad padding test data + */ + private String createBadPaddingData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[64]; + + Arrays.fill(salt, (byte) 128); + Arrays.fill(nonce, (byte) 128); + Arrays.fill(encryptedData, (byte) 128); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data for null IV testing scenarios. + * Creates data designed to test null initialization vector handling. + * + * @return Base64-encoded string containing null IV test data + */ + private String createNullIvTestData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[24]; + + Arrays.fill(nonce, (byte) 0); + Arrays.fill(encryptedData, (byte) 4); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data for static decrypt exception scenarios. + * Creates data designed to trigger exceptions in static decryption methods. + * + * @return Base64-encoded string containing static decrypt exception test data + */ + private String createStaticDecryptExceptionData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[48]; + + Arrays.fill(salt, (byte) 200); + Arrays.fill(nonce, (byte) 200); + Arrays.fill(encryptedData, (byte) 200); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data for null AAD testing scenarios. + * Creates data designed to test null Additional Authenticated Data handling. + * + * @return Base64-encoded string containing null AAD test data + */ + private String createNullAadData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[20]; + + Arrays.fill(nonce, (byte) 50); + Arrays.fill(encryptedData, (byte) 50); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data for empty AAD testing scenarios. + * Creates data designed to test empty Additional Authenticated Data handling. + * + * @return Base64-encoded string containing empty AAD test data + */ + private String createEmptyAadData() { + byte[] emptySalt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[36]; + + Arrays.fill(nonce, (byte) 60); + Arrays.fill(encryptedData, (byte) 60); + + byte[] combined = new byte[emptySalt.length + nonce.length + encryptedData.length]; + System.arraycopy(emptySalt, 0, combined, 0, emptySalt.length); + System.arraycopy(nonce, 0, combined, emptySalt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, emptySalt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } + + /** + * Helper method to create test data for non-null AAD testing scenarios. + * Creates data designed to test non-null Additional Authenticated Data handling. + * + * @return Base64-encoded string containing non-null AAD test data + */ + private String createNonNullAadData() { + byte[] salt = new byte[32]; + byte[] nonce = new byte[12]; + byte[] encryptedData = new byte[28]; + + Arrays.fill(salt, (byte) 70); + Arrays.fill(nonce, (byte) 70); + Arrays.fill(encryptedData, (byte) 70); + + byte[] combined = new byte[salt.length + nonce.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(nonce, 0, combined, salt.length, nonce.length); + System.arraycopy(encryptedData, 0, combined, salt.length + nonce.length, encryptedData.length); + + return Base64.encodeBase64String(combined); + } +} diff --git a/src/test/java/io/mosip/print/util/DataShareUtilTest.java b/src/test/java/io/mosip/print/util/DataShareUtilTest.java new file mode 100644 index 00000000..64133066 --- /dev/null +++ b/src/test/java/io/mosip/print/util/DataShareUtilTest.java @@ -0,0 +1,471 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.springframework.http.HttpEntity; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestClientException; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.print.constant.ApiName; +import io.mosip.print.dto.DataShare; +import io.mosip.print.dto.DataShareResponseDto; +import io.mosip.print.dto.ErrorDTO; +import io.mosip.print.exception.ApiNotAccessibleException; +import io.mosip.print.exception.DataShareException; +import io.mosip.print.logger.PrintLogger; +import io.mosip.print.service.PrintRestClientService; + +/** + * Unit tests for {@link DataShareUtil} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the DataShareUtil class, + * including data sharing operations, API communication, error handling scenarios, HTTP exception processing, + * JSON parsing operations, and various edge cases for data share creation and management.

+ */ +@ExtendWith(MockitoExtension.class) +class DataShareUtilTest { + + @Mock + private PrintRestClientService restUtil; + + @Mock + private ObjectMapper mapper; + + @InjectMocks + private DataShareUtil dataShareUtil; + + private static final String POLICY_ID = "test-policy-id"; + private static final String PARTNER_ID = "test-partner-id"; + private static final byte[] TEST_DATA = "test credential data".getBytes(); + private static final String RESPONSE_STRING = "{\"dataShare\":{\"url\":\"http://test.com\"}}"; + + private DataShareResponseDto mockResponseDto; + private DataShare mockDataShare; + private ErrorDTO mockError; + + /** + * Sets up test fixtures before each test method execution. + * Initializes mock objects for data share operations including response DTOs, + * data share objects, and error handling components. + */ + @BeforeEach + void setUp() { + mockResponseDto = new DataShareResponseDto(); + mockDataShare = new DataShare(); + mockDataShare.setUrl("http://test-datashare.com"); + mockError = new ErrorDTO(); + mockError.setErrorCode("ERR-001"); + mockError.setMessage("Test error"); + } + + /** + * Tests successful data share creation operation. + * Verifies that the method correctly creates a data share with valid input parameters + * and returns the expected DataShare object with proper URL configuration. + */ + @Test + void getDataShareShouldSucceedWithValidParameters() throws Exception { + mockResponseDto.setDataShare(mockDataShare); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + DataShare result = dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID); + + assertNotNull(result); + assertEquals("http://test-datashare.com", result.getUrl()); + } + } + + /** + * Tests data share creation when API returns null response. + * Verifies that DataShareException is thrown when the REST API call returns + * null response data during data share creation. + */ + @Test + void getDataShareWithNullResponseShouldThrowDataShareException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(null); + + assertThrows(DataShareException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests data share creation when API response contains error information. + * Verifies that DataShareException is thrown when the response contains + * error details indicating operation failure. + */ + @Test + void getDataShareWithErrorResponseShouldThrowDataShareException() throws Exception { + List errors = new ArrayList<>(); + errors.add(mockError); + mockResponseDto.setErrors(errors); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + assertThrows(DataShareException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests data share creation with empty error list in response. + * Verifies that the method succeeds when the response contains an empty error list + * and returns a valid DataShare object. + */ + @Test + void getDataShareWithEmptyErrorListShouldSucceed() throws Exception { + List emptyErrors = new ArrayList<>(); + mockResponseDto.setErrors(emptyErrors); + mockResponseDto.setDataShare(mockDataShare); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + DataShare result = dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID); + + assertNotNull(result); + assertEquals("http://test-datashare.com", result.getUrl()); + } + } + + /** + * Tests data share creation when HttpClientErrorException is wrapped in RestClientException. + * Verifies that ApiNotAccessibleException is thrown when client-side HTTP errors occur + * during the REST API communication. + */ + @Test + void getDataShareWithHttpClientErrorExceptionShouldThrowApiNotAccessibleException() throws Exception { + HttpClientErrorException clientException = new HttpClientErrorException( + org.springframework.http.HttpStatus.BAD_REQUEST, "Bad Request"); + RestClientException wrapperException = new RestClientException("REST error", clientException); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenThrow(wrapperException); + + assertThrows(ApiNotAccessibleException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests data share creation when HttpServerErrorException is wrapped in RestClientException. + * Verifies that ApiNotAccessibleException is thrown when server-side HTTP errors occur + * during the REST API communication. + */ + @Test + void getDataShareWithHttpServerErrorExceptionShouldThrowApiNotAccessibleException() throws Exception { + HttpServerErrorException serverException = new HttpServerErrorException( + org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error"); + RestClientException wrapperException = new RestClientException("REST error", serverException); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenThrow(wrapperException); + + assertThrows(ApiNotAccessibleException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests data share creation when generic runtime exception occurs. + * Verifies that DataShareException is thrown when unexpected runtime errors occur + * during the data share creation process. + */ + @Test + void getDataShareWithGenericExceptionShouldThrowDataShareException() throws Exception { + RuntimeException genericException = new RuntimeException("Generic error"); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenThrow(genericException); + + assertThrows(DataShareException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests data share creation when IOException occurs during JSON parsing. + * Verifies that DataShareException is thrown when JSON parsing fails during + * response processing operations. + */ + @Test + void getDataShareWithIoExceptionShouldThrowDataShareException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + + when(mapper.readValue(anyString(), eq(DataShareResponseDto.class))) + .thenThrow(new RuntimeException(new IOException("JSON parsing error"))); + + assertThrows(DataShareException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests data share creation with null data parameter. + * Verifies that DataShareException is thrown when null data is provided, + * as ByteArrayResource constructor throws IllegalArgumentException for null data. + */ + @Test + void getDataShareWithNullDataShouldThrowDataShareException() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + assertThrows(DataShareException.class, () -> + dataShareUtil.getDataShare(null, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests data share creation with empty data array. + * Verifies that the method handles empty data gracefully and returns + * a valid DataShare object when other parameters are correct. + */ + @Test + void getDataShareWithEmptyDataShouldSucceed() throws Exception { + byte[] emptyData = new byte[0]; + mockResponseDto.setDataShare(mockDataShare); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + DataShare result = dataShareUtil.getDataShare(emptyData, POLICY_ID, PARTNER_ID); + + assertNotNull(result); + } + } + + /** + * Tests data share creation with null policy ID parameter. + * Verifies that the method handles null policy ID gracefully and still + * creates a valid data share when other parameters are provided. + */ + @Test + void getDataShareWithNullPolicyIdShouldSucceed() throws Exception { + mockResponseDto.setDataShare(mockDataShare); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + DataShare result = dataShareUtil.getDataShare(TEST_DATA, null, PARTNER_ID); + + assertNotNull(result); + } + } + + /** + * Tests data share creation with null partner ID parameter. + * Verifies that the method handles null partner ID gracefully and still + * creates a valid data share when other parameters are provided. + */ + @Test + void getDataShareWithNullPartnerIdShouldSucceed() throws Exception { + mockResponseDto.setDataShare(mockDataShare); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + DataShare result = dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, null); + + assertNotNull(result); + } + } + + /** + * Tests ByteArrayResource getFilename method functionality. + * Verifies that the method correctly processes data through ByteArrayResource + * and returns the expected DataShare with proper URL configuration. + */ + @Test + void byteArrayResourceGetFilenameShouldProcessCorrectly() throws Exception { + mockResponseDto.setDataShare(mockDataShare); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + DataShare result = dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID); + + assertNotNull(result); + assertEquals("http://test-datashare.com", result.getUrl()); + } + } + + /** + * Tests data share creation when response has null errors. + * Verifies that the method handles null error list gracefully and returns + * a valid DataShare when the response is otherwise successful. + */ + @Test + void getDataShareWithNullErrorsShouldSucceed() throws Exception { + mockResponseDto.setErrors(null); + mockResponseDto.setDataShare(mockDataShare); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenReturn(RESPONSE_STRING); + when(mapper.readValue(RESPONSE_STRING, DataShareResponseDto.class)).thenReturn(mockResponseDto); + + DataShare result = dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID); + + assertNotNull(result); + assertEquals("http://test-datashare.com", result.getUrl()); + } + } + + /** + * Tests exception handling with direct HttpClientErrorException. + * Verifies that DataShareException is thrown when HttpClientErrorException occurs + * directly (not wrapped in RestClientException) during API communication. + */ + @Test + void getDataShareWithDirectHttpClientErrorExceptionShouldThrowDataShareException() throws Exception { + HttpClientErrorException clientException = new HttpClientErrorException( + org.springframework.http.HttpStatus.BAD_REQUEST, "Bad Request"); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenThrow(clientException); + + assertThrows(DataShareException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } + + /** + * Tests exception handling with direct HttpServerErrorException. + * Verifies that DataShareException is thrown when HttpServerErrorException occurs + * directly (not wrapped in RestClientException) during API communication. + */ + @Test + void getDataShareWithDirectHttpServerErrorExceptionShouldThrowDataShareException() throws Exception { + HttpServerErrorException serverException = new HttpServerErrorException( + org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR, "Internal Server Error"); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DataShareUtil.class)) + .thenReturn(mockLogger); + + when(restUtil.postApi(eq(ApiName.CREATEDATASHARE), anyList(), anyString(), anyString(), + any(HttpEntity.class), eq(String.class))).thenThrow(serverException); + + assertThrows(DataShareException.class, () -> + dataShareUtil.getDataShare(TEST_DATA, POLICY_ID, PARTNER_ID) + ); + } + } +} diff --git a/src/test/java/io/mosip/print/util/DateAdapterTest.java b/src/test/java/io/mosip/print/util/DateAdapterTest.java new file mode 100644 index 00000000..1773b560 --- /dev/null +++ b/src/test/java/io/mosip/print/util/DateAdapterTest.java @@ -0,0 +1,348 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link DateAdapter} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the DateAdapter class, + * including ISO date-time string parsing, timezone conversion operations, LocalDateTime marshalling, + * unmarshalling processes, precision handling for various time formats, and edge cases for + * date-time adapter operations with different timezone offsets and formatting scenarios.

+ */ +@ExtendWith(MockitoExtension.class) +class DateAdapterTest { + + @InjectMocks + private DateAdapter dateAdapter; + + private static final String ISO_DATE_TIME_STRING = "2025-08-03T10:30:45Z"; + private static final String ISO_DATE_TIME_WITH_OFFSET = "2025-08-03T10:30:45+05:30"; + private static final String ISO_DATE_TIME_WITH_NEGATIVE_OFFSET = "2025-08-03T10:30:45-05:00"; + private static final LocalDateTime TEST_LOCAL_DATE_TIME = LocalDateTime.of(2025, 8, 3, 10, 30, 45); + private static final String EXPECTED_MARSHAL_OUTPUT = "2025-08-03T10:30:45Z"; + + /** + * Tests successful unmarshalling of ISO date-time string with UTC timezone. + * Verifies that the adapter correctly parses UTC timezone formatted date-time strings + * and returns LocalDateTime with accurate year, month, day, hour, minute, and second values. + */ + @Test + void unmarshalShouldSucceedWithUtcTimezone() throws Exception { + LocalDateTime result = dateAdapter.unmarshal(ISO_DATE_TIME_STRING); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + assertEquals(10, result.getHour()); + assertEquals(30, result.getMinute()); + assertEquals(45, result.getSecond()); + } + + /** + * Tests successful unmarshalling of ISO date-time string with positive timezone offset. + * Verifies that the adapter correctly handles positive timezone offsets and converts + * the time to UTC, adjusting the hour and minute values appropriately. + */ + @Test + void unmarshalShouldSucceedWithPositiveOffset() throws Exception { + LocalDateTime result = dateAdapter.unmarshal(ISO_DATE_TIME_WITH_OFFSET); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + assertEquals(5, result.getHour()); + assertEquals(0, result.getMinute()); + assertEquals(45, result.getSecond()); + } + + /** + * Tests successful unmarshalling of ISO date-time string with negative timezone offset. + * Verifies that the adapter correctly handles negative timezone offsets and converts + * the time to UTC, adjusting the hour and minute values appropriately. + */ + @Test + void unmarshalShouldSucceedWithNegativeOffset() throws Exception { + LocalDateTime result = dateAdapter.unmarshal(ISO_DATE_TIME_WITH_NEGATIVE_OFFSET); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + assertEquals(15, result.getHour()); + assertEquals(30, result.getMinute()); + assertEquals(45, result.getSecond()); + } + + /** + * Tests unmarshalling functionality with different date format including milliseconds. + * Verifies that the adapter correctly parses date-time strings with millisecond precision + * and preserves the nanosecond component accurately. + */ + @Test + void unmarshalWithDifferentDateFormatShouldHandleMilliseconds() throws Exception { + String dateWithMilliseconds = "2025-08-03T10:30:45.123Z"; + + LocalDateTime result = dateAdapter.unmarshal(dateWithMilliseconds); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + assertEquals(10, result.getHour()); + assertEquals(30, result.getMinute()); + assertEquals(45, result.getSecond()); + assertEquals(123000000, result.getNano()); + } + + /** + * Tests unmarshalling functionality with microseconds precision. + * Verifies that the adapter correctly handles date-time strings with microsecond precision + * and accurately preserves the nanosecond component with microsecond granularity. + */ + @Test + void unmarshalWithMicrosecondsPrecisionShouldPreservePrecision() throws Exception { + String dateWithMicroseconds = "2025-08-03T10:30:45.123456Z"; + + LocalDateTime result = dateAdapter.unmarshal(dateWithMicroseconds); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + assertEquals(10, result.getHour()); + assertEquals(30, result.getMinute()); + assertEquals(45, result.getSecond()); + assertEquals(123456000, result.getNano()); + } + + /** + * Tests unmarshalling functionality with nanoseconds precision. + * Verifies that the adapter correctly handles date-time strings with full nanosecond precision + * and accurately preserves all nanosecond digits in the LocalDateTime object. + */ + @Test + void unmarshalWithNanosecondsPrecisionShouldPreserveFullPrecision() throws Exception { + String dateWithNanoseconds = "2025-08-03T10:30:45.123456789Z"; + + LocalDateTime result = dateAdapter.unmarshal(dateWithNanoseconds); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + assertEquals(10, result.getHour()); + assertEquals(30, result.getMinute()); + assertEquals(45, result.getSecond()); + assertEquals(123456789, result.getNano()); + } + + /** + * Tests unmarshalling functionality with invalid date format input. + * Verifies that DateTimeParseException is thrown when the adapter encounters + * malformed or invalid date-time string formats. + */ + @Test + void unmarshalWithInvalidDateFormatShouldThrowDateTimeParseException() { + String invalidDateString = "invalid-date-format"; + + assertThrows(DateTimeParseException.class, () -> + dateAdapter.unmarshal(invalidDateString) + ); + } + + /** + * Tests unmarshalling functionality with null input parameter. + * Verifies that the adapter throws an appropriate exception when null input + * is provided for date-time string unmarshalling. + */ + @Test + void unmarshalWithNullInputShouldThrowException() { + assertThrows(Exception.class, () -> + dateAdapter.unmarshal(null) + ); + } + + /** + * Tests unmarshalling functionality with empty string input. + * Verifies that DateTimeParseException is thrown when an empty string is provided + * as input for date-time parsing operations. + */ + @Test + void unmarshalWithEmptyStringShouldThrowDateTimeParseException() { + assertThrows(DateTimeParseException.class, () -> + dateAdapter.unmarshal("") + ); + } + + /** + * Tests unmarshalling functionality with malformed ISO date values. + * Verifies that DateTimeParseException is thrown when the date-time string contains + * invalid values for month, day, hour, minute, or second components. + */ + @Test + void unmarshalWithMalformedIsoDateShouldThrowDateTimeParseException() { + String malformedDate = "2025-13-45T25:70:70Z"; + + assertThrows(DateTimeParseException.class, () -> + dateAdapter.unmarshal(malformedDate) + ); + } + + /** + * Tests successful marshalling of LocalDateTime to ISO string format. + * Verifies that the adapter correctly converts LocalDateTime objects to + * ISO-formatted date-time strings with UTC timezone designation. + */ + @Test + void marshalShouldSucceedWithValidLocalDateTime() throws Exception { + String result = dateAdapter.marshal(TEST_LOCAL_DATE_TIME); + + assertNotNull(result); + assertEquals(EXPECTED_MARSHAL_OUTPUT, result); + } + + /** + * Tests marshalling functionality with different LocalDateTime values. + * Verifies that the adapter correctly handles various LocalDateTime objects + * and produces properly formatted ISO date-time strings. + */ + @Test + void marshalWithDifferentDateTimeShouldReturnCorrectFormat() throws Exception { + LocalDateTime differentDateTime = LocalDateTime.of(2024, 12, 25, 23, 59, 59); + + String result = dateAdapter.marshal(differentDateTime); + + assertNotNull(result); + assertEquals("2024-12-25T23:59:59Z", result); + } + + /** + * Tests marshalling functionality with LocalDateTime containing nanoseconds. + * Verifies that the adapter preserves nanosecond precision when converting + * LocalDateTime objects to ISO-formatted strings. + */ + @Test + void marshalWithNanosecondsShouldPreservePrecision() throws Exception { + LocalDateTime dateTimeWithNanos = LocalDateTime.of(2025, 8, 3, 10, 30, 45, 123456789); + + String result = dateAdapter.marshal(dateTimeWithNanos); + + assertNotNull(result); + assertEquals("2025-08-03T10:30:45.123456789Z", result); + } + + /** + * Tests marshalling functionality with LocalDateTime containing milliseconds. + * Verifies that the adapter correctly formats LocalDateTime objects with millisecond + * precision into ISO-formatted date-time strings. + */ + @Test + void marshalWithMillisecondsShouldPreserveMillisecondPrecision() throws Exception { + LocalDateTime dateTimeWithMillis = LocalDateTime.of(2025, 8, 3, 10, 30, 45, 123000000); + + String result = dateAdapter.marshal(dateTimeWithMillis); + + assertNotNull(result); + assertEquals("2025-08-03T10:30:45.123Z", result); + } + + /** + * Tests marshalling functionality with minimum LocalDateTime value. + * Verifies that the adapter correctly handles epoch time (1970-01-01) and other + * minimum date-time values in the marshalling process. + */ + @Test + void marshalWithMinDateTimeShouldHandleEpochTime() throws Exception { + LocalDateTime minDateTime = LocalDateTime.of(1970, 1, 1, 0, 0, 0); + + String result = dateAdapter.marshal(minDateTime); + + assertNotNull(result); + assertEquals("1970-01-01T00:00:00Z", result); + } + + /** + * Tests marshalling functionality with leap year date values. + * Verifies that the adapter correctly handles leap year dates (February 29th) + * in the marshalling process without validation errors. + */ + @Test + void marshalWithLeapYearDateShouldHandleLeapYearCorrectly() throws Exception { + LocalDateTime leapYearDateTime = LocalDateTime.of(2024, 2, 29, 12, 0, 0); + + String result = dateAdapter.marshal(leapYearDateTime); + + assertNotNull(result); + assertEquals("2024-02-29T12:00:00Z", result); + } + + /** + * Tests marshalling functionality with null LocalDateTime parameter. + * Verifies that the adapter throws an appropriate exception when null LocalDateTime + * is provided for marshalling operations. + */ + @Test + void marshalWithNullDateTimeShouldThrowException() { + assertThrows(Exception.class, () -> + dateAdapter.marshal(null) + ); + } + + /** + * Tests round-trip conversion functionality (unmarshal then marshal). + * Verifies that the adapter maintains data integrity when performing sequential + * unmarshalling and marshalling operations on the same date-time value. + */ + @Test + void roundTripConversionShouldPreserveOriginalValue() throws Exception { + String originalDateString = "2025-08-03T10:30:45.123Z"; + + LocalDateTime unmarshalled = dateAdapter.unmarshal(originalDateString); + String marshalled = dateAdapter.marshal(unmarshalled); + + assertEquals(originalDateString, marshalled); + } + + /** + * Tests round-trip conversion functionality with timezone offset. + * Verifies that the adapter correctly converts timezone offset dates to UTC format + * and maintains consistency in round-trip operations. + */ + @Test + void roundTripConversionWithOffsetShouldConvertToUtc() throws Exception { + String originalDateString = "2025-08-03T15:30:45+05:00"; + + LocalDateTime unmarshalled = dateAdapter.unmarshal(originalDateString); + String marshalled = dateAdapter.marshal(unmarshalled); + + assertEquals("2025-08-03T10:30:45Z", marshalled); + } + + /** + * Tests that unmarshalling then marshalling preserves time precision. + * Verifies that the adapter maintains full nanosecond precision throughout + * the complete unmarshalling and marshalling cycle. + */ + @Test + void preserveTimePrecisionShouldMaintainNanosecondAccuracy() throws Exception { + String originalWithNanos = "2025-08-03T10:30:45.123456789Z"; + + LocalDateTime unmarshalled = dateAdapter.unmarshal(originalWithNanos); + String marshalled = dateAdapter.marshal(unmarshalled); + + assertEquals(originalWithNanos, marshalled); + } +} diff --git a/src/test/java/io/mosip/print/util/DateUtilsTest.java b/src/test/java/io/mosip/print/util/DateUtilsTest.java new file mode 100644 index 00000000..f77db7a3 --- /dev/null +++ b/src/test/java/io/mosip/print/util/DateUtilsTest.java @@ -0,0 +1,916 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.LocalDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.mosip.print.exception.IllegalArgumentException; +import io.mosip.print.exception.NullPointerException; +import io.mosip.print.exception.ParseException; + +/** + * Unit tests for {@link DateUtils} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the DateUtils class, + * including date arithmetic operations, date formatting with various patterns and locales, timezone conversions, + * date comparison operations, ISO string conversions, UTC date-time handling, and various parsing operations + * with exception handling scenarios for date and time utility methods.

+ */ +@ExtendWith(MockitoExtension.class) +class DateUtilsTest { + + private Date testDate; + private Date futureDate; + private Date pastDate; + private LocalDateTime testLocalDateTime; + private LocalDateTime futureLocalDateTime; + private LocalDateTime pastLocalDateTime; + private Calendar testCalendar; + + /** + * Sets up test fixtures before each test method execution. + * Initializes various Date, LocalDateTime, and Calendar objects for comprehensive + * testing of date utility operations including past, present, and future time instances. + */ + @BeforeEach + void setUp() { + testDate = new Date(); + futureDate = new Date(testDate.getTime() + 86400000); // +1 day + pastDate = new Date(testDate.getTime() - 86400000); // -1 day + + testLocalDateTime = LocalDateTime.now(); + futureLocalDateTime = testLocalDateTime.plusDays(1); + pastLocalDateTime = testLocalDateTime.minusDays(1); + + testCalendar = Calendar.getInstance(); + testCalendar.setTime(testDate); + } + + /** + * Tests successful addition of days to a date. + * Verifies that the addDays method correctly adds the specified number of days + * to a given date and returns a future date. + */ + @Test + void addDaysShouldSucceedWithValidInput() { + Date result = DateUtils.addDays(testDate, 5); + + assertNotNull(result); + assertTrue(result.after(testDate)); + } + + /** + * Tests addition of days when null date is provided. + * Verifies that the method throws appropriate exception when null date is passed, + * as Apache Commons DateUtils throws NullPointerException for null input. + */ + @Test + void addDaysWithNullDateShouldThrowException() { + assertThrows(Exception.class, () -> + DateUtils.addDays(null, 5) + ); + } + + /** + * Tests addition of negative days to a date. + * Verifies that the addDays method correctly handles negative day values + * and returns a past date when negative days are added. + */ + @Test + void addDaysWithNegativeDaysShouldReturnPastDate() { + Date result = DateUtils.addDays(testDate, -3); + + assertNotNull(result); + assertTrue(result.before(testDate)); + } + + /** + * Tests successful addition of hours to a date. + * Verifies that the addHours method correctly adds the specified number of hours + * to a given date and returns a future time. + */ + @Test + void addHoursShouldSucceedWithValidInput() { + Date result = DateUtils.addHours(testDate, 2); + + assertNotNull(result); + assertTrue(result.after(testDate)); + } + + /** + * Tests addition of hours when null date is provided. + * Verifies that the method throws appropriate exception when null date is passed + * to the addHours operation. + */ + @Test + void addHoursWithNullDateShouldThrowException() { + assertThrows(Exception.class, () -> + DateUtils.addHours(null, 2) + ); + } + + /** + * Tests successful addition of minutes to a date. + * Verifies that the addMinutes method correctly adds the specified number of minutes + * to a given date and returns a future time. + */ + @Test + void addMinutesShouldSucceedWithValidInput() { + Date result = DateUtils.addMinutes(testDate, 30); + + assertNotNull(result); + assertTrue(result.after(testDate)); + } + + /** + * Tests addition of minutes when null date is provided. + * Verifies that the method throws appropriate exception when null date is passed + * to the addMinutes operation. + */ + @Test + void addMinutesWithNullDateShouldThrowException() { + assertThrows(Exception.class, () -> + DateUtils.addMinutes(null, 30) + ); + } + + /** + * Tests successful addition of seconds to a date. + * Verifies that the addSeconds method correctly adds the specified number of seconds + * to a given date and returns a future time. + */ + @Test + void addSecondsShouldSucceedWithValidInput() { + Date result = DateUtils.addSeconds(testDate, 45); + + assertNotNull(result); + assertTrue(result.after(testDate)); + } + + /** + * Tests addition of seconds when null date is provided. + * Verifies that the method throws appropriate exception when null date is passed + * to the addSeconds operation. + */ + @Test + void addSecondsWithNullDateShouldThrowException() { + assertThrows(Exception.class, () -> + DateUtils.addSeconds(null, 45) + ); + } + + /** + * Tests successful date formatting with specified pattern. + * Verifies that the formatDate method correctly formats a date using the provided + * pattern and returns a properly formatted string. + */ + @Test + void formatDateWithPatternShouldSucceedWithValidInput() { + String result = DateUtils.formatDate(testDate, "yyyy-MM-dd"); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests date formatting when null date is provided. + * Verifies that IllegalArgumentException is thrown when null date is passed + * to the formatDate method. + */ + @Test + void formatDateWithNullDateShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.formatDate(null, "yyyy-MM-dd") + ); + } + + /** + * Tests date formatting when null pattern is provided. + * Verifies that IllegalArgumentException is thrown when null pattern is passed + * to the formatDate method. + */ + @Test + void formatDateWithNullPatternShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.formatDate(testDate, null) + ); + } + + /** + * Tests successful date formatting with pattern and timezone. + * Verifies that the formatDate method correctly formats a date using the provided + * pattern and timezone, returning a properly formatted string. + */ + @Test + void formatDateWithTimezoneShouldSucceedWithValidInput() { + TimeZone utc = TimeZone.getTimeZone("UTC"); + String result = DateUtils.formatDate(testDate, "yyyy-MM-dd HH:mm:ss", utc); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")); + } + + /** + * Tests date formatting with null timezone parameter. + * Verifies that the method handles null timezone gracefully as timezone is optional + * and still produces a properly formatted date string. + */ + @Test + void formatDateWithNullTimezoneShouldHandleGracefully() { + String result = DateUtils.formatDate(testDate, "yyyy-MM-dd", (TimeZone) null); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests successful date formatting with pattern, timezone, and locale. + * Verifies that the formatDate method correctly formats a date using all provided + * parameters including locale-specific formatting rules. + */ + @Test + void formatDateWithTimezoneAndLocaleShouldSucceedWithValidInput() { + TimeZone utc = TimeZone.getTimeZone("UTC"); + Locale locale = Locale.US; + String result = DateUtils.formatDate(testDate, "yyyy-MM-dd", utc, locale); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests successful calendar formatting with specified pattern. + * Verifies that the formatCalendar method correctly formats a calendar using + * the provided pattern and returns a properly formatted string. + */ + @Test + void formatCalendarWithPatternShouldSucceedWithValidInput() { + String result = DateUtils.formatCalendar(testCalendar, "yyyy-MM-dd"); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests calendar formatting when null calendar is provided. + * Verifies that IllegalArgumentException is thrown when null calendar is passed + * to the formatCalendar method. + */ + @Test + void formatCalendarWithNullCalendarShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.formatCalendar(null, "yyyy-MM-dd") + ); + } + + /** + * Tests successful calendar formatting with pattern and timezone. + * Verifies that the formatCalendar method correctly formats a calendar using + * the provided pattern and timezone parameters. + */ + @Test + void formatCalendarWithTimezoneShouldSucceedWithValidInput() { + TimeZone utc = TimeZone.getTimeZone("UTC"); + String result = DateUtils.formatCalendar(testCalendar, "yyyy-MM-dd", utc); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests successful calendar formatting with pattern and locale. + * Verifies that the formatCalendar method correctly formats a calendar using + * the provided pattern and locale-specific formatting rules. + */ + @Test + void formatCalendarWithLocaleShouldSucceedWithValidInput() { + Locale locale = Locale.US; + String result = DateUtils.formatCalendar(testCalendar, "yyyy-MM-dd", locale); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests successful calendar formatting with pattern, timezone, and locale. + * Verifies that the formatCalendar method correctly formats a calendar using all + * provided parameters including timezone and locale-specific formatting. + */ + @Test + void formatCalendarWithTimezoneAndLocaleShouldSucceedWithValidInput() { + TimeZone utc = TimeZone.getTimeZone("UTC"); + Locale locale = Locale.US; + String result = DateUtils.formatCalendar(testCalendar, "yyyy-MM-dd", utc, locale); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests date after comparison returning true. + * Verifies that the after method correctly identifies when the first date + * comes after the second date in chronological order. + */ + @Test + void afterDateShouldReturnTrueForFutureDate() { + boolean result = DateUtils.after(futureDate, testDate); + assertTrue(result); + } + + /** + * Tests date after comparison returning false. + * Verifies that the after method correctly returns false when the first date + * comes before the second date in chronological order. + */ + @Test + void afterDateShouldReturnFalseForPastDate() { + boolean result = DateUtils.after(pastDate, testDate); + assertFalse(result); + } + + /** + * Tests date after comparison with null date parameter. + * Verifies that IllegalArgumentException is thrown when null date is passed + * to the after comparison method. + */ + @Test + void afterDateWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.after(null, testDate) + ); + } + + /** + * Tests date before comparison returning true. + * Verifies that the before method correctly identifies when the first date + * comes before the second date in chronological order. + */ + @Test + void beforeDateShouldReturnTrueForPastDate() { + boolean result = DateUtils.before(pastDate, testDate); + assertTrue(result); + } + + /** + * Tests date before comparison returning false. + * Verifies that the before method correctly returns false when the first date + * comes after the second date in chronological order. + */ + @Test + void beforeDateShouldReturnFalseForFutureDate() { + boolean result = DateUtils.before(futureDate, testDate); + assertFalse(result); + } + + /** + * Tests date before comparison with null date parameter. + * Verifies that IllegalArgumentException is thrown when null date is passed + * to the before comparison method. + */ + @Test + void beforeDateWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.before(null, testDate) + ); + } + + /** + * Tests same day comparison returning false for different days. + * Verifies that the isSameDay method correctly identifies when two dates + * are not on the same calendar day. + */ + @Test + void isSameDayShouldReturnFalseForDifferentDays() { + boolean result = DateUtils.isSameDay(testDate, futureDate); + assertFalse(result); + } + + /** + * Tests same day comparison with null date parameter. + * Verifies that IllegalArgumentException is thrown when null date is passed + * to the isSameDay comparison method. + */ + @Test + void isSameDayWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.isSameDay(null, testDate) + ); + } + + /** + * Tests same instant comparison returning true for identical times. + * Verifies that the isSameInstant method correctly identifies when two dates + * represent the exact same moment in time. + */ + @Test + void isSameInstantShouldReturnTrueForIdenticalTimes() { + Date sameInstant = new Date(testDate.getTime()); + boolean result = DateUtils.isSameInstant(testDate, sameInstant); + assertTrue(result); + } + + /** + * Tests same instant comparison returning false for different times. + * Verifies that the isSameInstant method correctly returns false when two dates + * represent different moments in time. + */ + @Test + void isSameInstantShouldReturnFalseForDifferentTimes() { + boolean result = DateUtils.isSameInstant(testDate, futureDate); + assertFalse(result); + } + + /** + * Tests same instant comparison with null date parameter. + * Verifies that IllegalArgumentException is thrown when null date is passed + * to the isSameInstant comparison method. + */ + @Test + void isSameInstantWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.isSameInstant(null, testDate) + ); + } + + /** + * Tests LocalDateTime after comparison returning true. + * Verifies that the after method correctly identifies when the first LocalDateTime + * comes after the second LocalDateTime in chronological order. + */ + @Test + void afterLocalDateTimeShouldReturnTrueForFutureDateTime() { + boolean result = DateUtils.after(futureLocalDateTime, testLocalDateTime); + assertTrue(result); + } + + /** + * Tests LocalDateTime after comparison with null parameter. + * Verifies that IllegalArgumentException is thrown when null LocalDateTime is passed + * to the after comparison method. + */ + @Test + void afterLocalDateTimeWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.after(null, testLocalDateTime) + ); + } + + /** + * Tests LocalDateTime before comparison returning true. + * Verifies that the before method correctly identifies when the first LocalDateTime + * comes before the second LocalDateTime in chronological order. + */ + @Test + void beforeLocalDateTimeShouldReturnTrueForPastDateTime() { + boolean result = DateUtils.before(pastLocalDateTime, testLocalDateTime); + assertTrue(result); + } + + /** + * Tests LocalDateTime before comparison with null parameter. + * Verifies that IllegalArgumentException is thrown when null LocalDateTime is passed + * to the before comparison method. + */ + @Test + void beforeLocalDateTimeWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.before(null, testLocalDateTime) + ); + } + + /** + * Tests LocalDateTime same day comparison returning true for same day. + * Verifies that the isSameDay method correctly identifies when two LocalDateTime objects + * represent the same calendar day, avoiding invalid hour values. + */ + @Test + void isSameDayLocalDateTimeShouldReturnTrueForSameDay() { + int currentHour = testLocalDateTime.getHour(); + int newHour = currentHour < 22 ? currentHour + 2 : currentHour - 2; + + LocalDateTime sameDay = LocalDateTime.of( + testLocalDateTime.getYear(), + testLocalDateTime.getMonth(), + testLocalDateTime.getDayOfMonth(), + newHour, + testLocalDateTime.getMinute() + ); + + boolean result = DateUtils.isSameDay(testLocalDateTime, sameDay); + assertTrue(result); + } + + /** + * Tests LocalDateTime same day comparison with null parameter. + * Verifies that IllegalArgumentException is thrown when null LocalDateTime is passed + * to the isSameDay comparison method. + */ + @Test + void isSameDayLocalDateTimeWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.isSameDay(null, testLocalDateTime) + ); + } + + /** + * Tests LocalDateTime same instant comparison returning true. + * Verifies that the isSameInstant method correctly identifies when two LocalDateTime objects + * represent the exact same moment in time. + */ + @Test + void isSameInstantLocalDateTimeShouldReturnTrueForIdenticalTimes() { + boolean result = DateUtils.isSameInstant(testLocalDateTime, testLocalDateTime); + assertTrue(result); + } + + /** + * Tests LocalDateTime same instant comparison with null parameter. + * Verifies that IllegalArgumentException is thrown when null LocalDateTime is passed + * to the isSameInstant comparison method. + */ + @Test + void isSameInstantLocalDateTimeWithNullShouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + DateUtils.isSameInstant(null, testLocalDateTime) + ); + } + + /** + * Tests LocalDateTime to ISO string conversion. + * Verifies that the toISOString method correctly converts LocalDateTime objects + * to ISO-formatted strings with proper 'T' separator. + */ + @Test + void toISOStringWithLocalDateTimeShouldReturnIsoFormattedString() { + String result = DateUtils.toISOString(testLocalDateTime); + + assertNotNull(result); + assertTrue(result.contains("T")); + } + + /** + * Tests Date to ISO string conversion. + * Verifies that the toISOString method correctly converts Date objects + * to ISO-formatted strings with UTC timezone designation. + */ + @Test + void toISOStringWithDateShouldReturnUtcFormattedString() { + String result = DateUtils.toISOString(testDate); + + assertNotNull(result); + assertTrue(result.endsWith("Z")); + } + + /** + * Tests LocalDateTime formatting to ISO string. + * Verifies that the formatToISOString method correctly formats LocalDateTime objects + * to ISO-formatted strings with UTC timezone designation. + */ + @Test + void formatToISOStringShouldReturnUtcFormattedString() { + String result = DateUtils.formatToISOString(testLocalDateTime); + + assertNotNull(result); + assertTrue(result.endsWith("Z")); + } + + /** + * Tests retrieval of current UTC date-time. + * Verifies that the getUTCCurrentDateTime method returns a valid LocalDateTime + * representing the current UTC time. + */ + @Test + void getUTCCurrentDateTimeShouldReturnCurrentUtcDateTime() { + LocalDateTime result = DateUtils.getUTCCurrentDateTime(); + + assertNotNull(result); + } + + /** + * Tests retrieval of current UTC date-time as string. + * Verifies that the getUTCCurrentDateTimeString method returns a properly formatted + * string representation of the current UTC date-time. + */ + @Test + void getUTCCurrentDateTimeStringShouldReturnFormattedString() { + String result = DateUtils.getUTCCurrentDateTimeString(); + + assertNotNull(result); + assertTrue(result.contains("T")); + } + + /** + * Tests retrieval of current UTC date-time string with custom pattern. + * Verifies that the getUTCCurrentDateTimeString method correctly formats the current + * UTC date-time using the specified pattern. + */ + @Test + void getUTCCurrentDateTimeStringWithPatternShouldReturnCustomFormattedString() { + String result = DateUtils.getUTCCurrentDateTimeString("yyyy-MM-dd"); + + assertNotNull(result); + assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}")); + } + + /** + * Tests retrieval of current date-time string. + * Verifies that the getCurrentDateTimeString method returns a properly formatted + * string representation of the current date-time. + */ + @Test + void getCurrentDateTimeStringShouldReturnFormattedString() { + String result = DateUtils.getCurrentDateTimeString(); + + assertNotNull(result); + assertTrue(result.contains("T")); + } + + /** + * Tests conversion of UTC string to LocalDateTime. + * Verifies that the convertUTCToLocalDateTime method correctly parses UTC-formatted + * strings and returns LocalDateTime with accurate date components. + */ + @Test + void convertUTCToLocalDateTimeShouldParseUtcString() { + String utcString = "2025-08-03T10:30:45Z"; + LocalDateTime result = DateUtils.convertUTCToLocalDateTime(utcString); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + } + + /** + * Tests parsing of UTC string to LocalDateTime. + * Verifies that the parseUTCToLocalDateTime method correctly processes UTC-formatted + * strings and returns valid LocalDateTime objects. + */ + @Test + void parseUTCToLocalDateTimeShouldParseUtcString() { + String utcString = "2025-08-03T10:30:45Z"; + LocalDateTime result = DateUtils.parseUTCToLocalDateTime(utcString); + + assertNotNull(result); + } + + /** + * Tests parsing to LocalDateTime with UTC pattern. + * Verifies that the parseToLocalDateTime method correctly handles UTC-formatted + * date-time strings with millisecond precision. + */ + @Test + void parseToLocalDateTimeWithUTCPatternShouldParseWithMilliseconds() { + String dateTimeString = "2025-08-03T10:30:45.123Z"; + LocalDateTime result = DateUtils.parseToLocalDateTime(dateTimeString); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + assertEquals(8, result.getMonthValue()); + assertEquals(3, result.getDayOfMonth()); + } + + /** + * Tests parsing to LocalDateTime with ISO pattern fallback. + * Verifies that the parseToLocalDateTime method correctly handles ISO-formatted + * date-time strings using fallback pattern matching. + */ + @Test + void parseToLocalDateTimeWithISOPatternFallbackShouldParseIsoString() { + String dateTimeString = "2025-08-03T10:30:45"; + LocalDateTime result = DateUtils.parseToLocalDateTime(dateTimeString); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + } + + /** + * Tests parsing UTC to LocalDateTime with custom pattern. + * Verifies that the parseUTCToLocalDateTime method correctly processes date-time strings + * using the specified custom pattern format. + */ + @Test + void parseUTCToLocalDateTimeWithPatternShouldParseCustomFormat() { + String dateTimeString = "2025-08-03 10:30:45"; + String pattern = "yyyy-MM-dd HH:mm:ss"; + + LocalDateTime result = DateUtils.parseUTCToLocalDateTime(dateTimeString, pattern); + + assertNotNull(result); + assertEquals(2025, result.getYear()); + } + + /** + * Tests parsing UTC to LocalDateTime with invalid pattern. + * Verifies that ParseException is thrown when the date-time string does not match + * the specified pattern format. + */ + @Test + void parseUTCToLocalDateTimeWithInvalidPatternShouldThrowParseException() { + String dateTimeString = "invalid-date"; + String pattern = "yyyy-MM-dd HH:mm:ss"; + + assertThrows(ParseException.class, () -> + DateUtils.parseUTCToLocalDateTime(dateTimeString, pattern) + ); + } + + /** + * Tests parsing Date to LocalDateTime conversion. + * Verifies that the parseDateToLocalDateTime method correctly converts Date objects + * to LocalDateTime representations. + */ + @Test + void parseDateToLocalDateTimeShouldConvertDateToLocalDateTime() { + LocalDateTime result = DateUtils.parseDateToLocalDateTime(testDate); + + assertNotNull(result); + } + + /** + * Tests parsing UTC string to Date object. + * Verifies that the parseUTCToDate method correctly parses UTC-formatted strings + * and returns valid Date objects. + */ + @Test + void parseUTCToDateShouldParseUtcStringToDate() { + String utcString = "2025-08-03T10:30:45.123Z"; + Date result = DateUtils.parseUTCToDate(utcString); + + assertNotNull(result); + } + + /** + * Tests parsing UTC to Date with invalid string. + * Verifies that ParseException is thrown when an invalid date-time string + * is provided for UTC to Date parsing. + */ + @Test + void parseUTCToDateWithInvalidStringShouldThrowParseException() { + String invalidString = "invalid-date"; + + assertThrows(ParseException.class, () -> + DateUtils.parseUTCToDate(invalidString) + ); + } + + /** + * Tests parsing UTC to Date with custom pattern. + * Verifies that the parseUTCToDate method correctly processes date strings + * using the specified custom pattern format. + */ + @Test + void parseUTCToDateWithPatternShouldParseCustomFormat() { + String dateString = "2025-08-03 10:30:45"; + String pattern = "yyyy-MM-dd HH:mm:ss"; + + Date result = DateUtils.parseUTCToDate(dateString, pattern); + + assertNotNull(result); + } + + /** + * Tests parsing UTC to Date with invalid pattern. + * Verifies that ParseException is thrown when the date string does not match + * the specified pattern during UTC to Date parsing. + */ + @Test + void parseUTCToDateWithInvalidPatternShouldThrowParseException() { + String dateString = "invalid-date"; + String pattern = "yyyy-MM-dd HH:mm:ss"; + + assertThrows(ParseException.class, () -> + DateUtils.parseUTCToDate(dateString, pattern) + ); + } + + /** + * Tests parsing to Date with timezone specification. + * Verifies that the parseToDate method correctly processes date strings + * with the specified pattern and timezone parameters. + */ + @Test + void parseToDateWithTimezoneShouldParseWithTimezoneInfo() { + String dateString = "2025-08-03 10:30:45"; + String pattern = "yyyy-MM-dd HH:mm:ss"; + TimeZone timezone = TimeZone.getTimeZone("UTC"); + + Date result = DateUtils.parseToDate(dateString, pattern, timezone); + + assertNotNull(result); + } + + /** + * Tests parsing to Date with timezone and invalid string. + * Verifies that ParseException is thrown when an invalid date string is provided + * for timezone-aware date parsing. + */ + @Test + void parseToDateWithTimezoneAndInvalidStringShouldThrowParseException() { + String dateString = "invalid-date"; + String pattern = "yyyy-MM-dd HH:mm:ss"; + TimeZone timezone = TimeZone.getTimeZone("UTC"); + + assertThrows(ParseException.class, () -> + DateUtils.parseToDate(dateString, pattern, timezone) + ); + } + + /** + * Tests parsing to Date with specified pattern. + * Verifies that the parseToDate method correctly processes date strings + * using the specified pattern format. + */ + @Test + void parseToDateWithPatternShouldParseCorrectly() { + String dateString = "2025-08-03"; + String pattern = "yyyy-MM-dd"; + + Date result = DateUtils.parseToDate(dateString, pattern); + + assertNotNull(result); + } + + /** + * Tests parsing to Date with null date string. + * Verifies that NullPointerException is thrown when null date string + * is provided for date parsing operations. + */ + @Test + void parseToDateWithNullDateStringShouldThrowNullPointerException() { + String pattern = "yyyy-MM-dd"; + + assertThrows(NullPointerException.class, () -> + DateUtils.parseToDate(null, pattern) + ); + } + + /** + * Tests parsing to Date with null pattern. + * Verifies that NullPointerException is thrown when null pattern + * is provided for date parsing operations. + */ + @Test + void parseToDateWithNullPatternShouldThrowNullPointerException() { + String dateString = "2025-08-03"; + + assertThrows(NullPointerException.class, () -> + DateUtils.parseToDate(dateString, null) + ); + } + + /** + * Tests parsing to Date with invalid date string. + * Verifies that ParseException is thrown when an invalid date string + * is provided for date parsing operations. + */ + @Test + void parseToDateWithInvalidDateStringShouldThrowParseException() { + String dateString = "invalid-date"; + String pattern = "yyyy-MM-dd"; + + assertThrows(ParseException.class, () -> + DateUtils.parseToDate(dateString, pattern) + ); + } + + /** + * Tests retrieval of UTC time string from Date object. + * Verifies that the getUTCTimeFromDate method correctly converts Date objects + * to UTC-formatted strings with proper timezone and separator indicators. + */ + @Test + void getUTCTimeFromDateShouldReturnUtcFormattedString() { + String result = DateUtils.getUTCTimeFromDate(testDate); + + assertNotNull(result); + assertTrue(result.endsWith("Z")); + assertTrue(result.contains("T")); + } + + /** + * Tests private constructor accessibility for code coverage. + * Verifies that the private constructor can be accessed via reflection + * and creates a valid instance of DateUtils. + */ + @Test + void privateConstructorShouldCreateInstance() throws Exception { + java.lang.reflect.Constructor constructor = DateUtils.class.getDeclaredConstructor(); + constructor.setAccessible(true); + DateUtils instance = constructor.newInstance(); + assertNotNull(instance); + } +} diff --git a/src/test/java/io/mosip/print/util/DigitalSignatureUtilityTest.java b/src/test/java/io/mosip/print/util/DigitalSignatureUtilityTest.java new file mode 100644 index 00000000..ff0e6a95 --- /dev/null +++ b/src/test/java/io/mosip/print/util/DigitalSignatureUtilityTest.java @@ -0,0 +1,388 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.springframework.core.env.Environment; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.print.constant.ApiName; +import io.mosip.print.core.http.RequestWrapper; +import io.mosip.print.core.http.ResponseWrapper; +import io.mosip.print.dto.ErrorDTO; +import io.mosip.print.dto.SignResponseDto; +import io.mosip.print.exception.ApisResourceAccessException; +import io.mosip.print.exception.DigitalSignatureException; +import io.mosip.print.logger.PrintLogger; +import io.mosip.print.service.PrintRestClientService; + +/** + * Unit tests for {@link DigitalSignatureUtility} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the DigitalSignatureUtility class, + * including digital signature generation operations, API communication, error handling scenarios, JSON processing, + * exception management, and various edge cases for digital signature creation and cryptographic operations.

+ */ +@ExtendWith(MockitoExtension.class) +class DigitalSignatureUtilityTest { + + @Mock + private PrintRestClientService printRestService; + + @Mock + private Environment env; + + @Mock + private ObjectMapper mapper; + + @InjectMocks + private DigitalSignatureUtility digitalSignatureUtility; + + private static final String DIGITAL_SIGNATURE_ID = "test-digital-signature-id"; + private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + private static final String REG_PROC_APPLICATION_VERSION = "1.0"; + private static final String CURRENT_DATETIME = "2025-08-03T10:00:00.000Z"; + private static final String TEST_DATA = "test-data-to-sign"; + private static final String SIGNATURE_RESPONSE = "test-signature-response"; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the DigitalSignatureUtility instance with environment configuration + * including digital signature ID, datetime patterns, and application version settings. + */ + @BeforeEach + void setUp() { + when(env.getProperty("mosip.registration.processor.digital.signature.id")).thenReturn(DIGITAL_SIGNATURE_ID); + when(env.getProperty("mosip.registration.processor.datetime.pattern")).thenReturn(DATETIME_PATTERN); + when(env.getProperty("mosip.registration.processor.application.version")).thenReturn(REG_PROC_APPLICATION_VERSION); + } + + /** + * Tests successful digital signature generation operation. + * Verifies that the method correctly generates digital signatures for valid input data, + * processes API responses, and returns the expected signature string. + */ + @Test + void getDigitalSignatureShouldSucceedWithValidData() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + responseWrapper.setErrors(new ArrayList<>()); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenReturn("{\"signature\":\"test-signature-response\"}"); + when(mapper.readValue("{\"signature\":\"test-signature-response\"}", SignResponseDto.class)) + .thenReturn(signResponseDto); + + String result = digitalSignatureUtility.getDigitalSignature(TEST_DATA); + + assertEquals(SIGNATURE_RESPONSE, result); + } + } + + /** + * Tests digital signature generation when API response contains errors. + * Verifies that the method handles error responses gracefully and still returns + * the signature when available despite error conditions. + */ + @Test + void getDigitalSignatureWithErrorsShouldHandleErrorsGracefully() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + + List errors = new ArrayList<>(); + ErrorDTO error1 = new ErrorDTO(); + error1.setMessage("Error message 1"); + ErrorDTO error2 = new ErrorDTO(); + error2.setMessage("Error message 2"); + errors.add(error1); + errors.add(error2); + responseWrapper.setErrors(errors); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenReturn("{\"signature\":\"test-signature-response\"}"); + when(mapper.readValue("{\"signature\":\"test-signature-response\"}", SignResponseDto.class)) + .thenReturn(signResponseDto); + + String result = digitalSignatureUtility.getDigitalSignature(TEST_DATA); + + assertEquals(SIGNATURE_RESPONSE, result); + } + } + + /** + * Tests digital signature generation when error list is null. + * Verifies that the method handles null error lists gracefully and processes + * the digital signature operation without encountering null pointer exceptions. + */ + @Test + void getDigitalSignatureWithNullErrorsShouldHandleGracefully() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + responseWrapper.setErrors(null); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenReturn("{\"signature\":\"test-signature-response\"}"); + when(mapper.readValue("{\"signature\":\"test-signature-response\"}", SignResponseDto.class)) + .thenReturn(signResponseDto); + + String result = digitalSignatureUtility.getDigitalSignature(TEST_DATA); + + assertEquals(SIGNATURE_RESPONSE, result); + } + } + + /** + * Tests digital signature generation when error list is empty. + * Verifies that the method handles empty error lists correctly and proceeds + * with normal digital signature processing operations. + */ + @Test + void getDigitalSignatureWithEmptyErrorsShouldProceedNormally() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + responseWrapper.setErrors(new ArrayList<>()); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenReturn("{\"signature\":\"test-signature-response\"}"); + when(mapper.readValue("{\"signature\":\"test-signature-response\"}", SignResponseDto.class)) + .thenReturn(signResponseDto); + + String result = digitalSignatureUtility.getDigitalSignature(TEST_DATA); + + assertEquals(SIGNATURE_RESPONSE, result); + } + } + + /** + * Tests digital signature generation when ApisResourceAccessException occurs. + * Verifies that DigitalSignatureException is thrown when API communication fails, + * wrapping the original exception with appropriate error messaging. + */ + @Test + void getDigitalSignatureWithApisResourceAccessExceptionShouldThrowDigitalSignatureException() throws Exception { + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))) + .thenThrow(new ApisResourceAccessException("API Error")); + + DigitalSignatureException exception = assertThrows(DigitalSignatureException.class, () -> + digitalSignatureUtility.getDigitalSignature(TEST_DATA) + ); + + assertTrue(exception.getMessage().contains("API Error")); + } + } + + /** + * Tests digital signature generation when JsonProcessingException occurs during JSON writing. + * Verifies that DigitalSignatureException is thrown when JSON serialization fails, + * providing appropriate error information for debugging purposes. + */ + @Test + void getDigitalSignatureWithJsonProcessingExceptionFromMapperShouldThrowDigitalSignatureException() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + responseWrapper.setErrors(new ArrayList<>()); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenThrow(new JsonProcessingException("JSON Error") {}); + + DigitalSignatureException exception = assertThrows(DigitalSignatureException.class, () -> + digitalSignatureUtility.getDigitalSignature(TEST_DATA) + ); + + assertTrue(exception.getMessage().contains("JSON Error")); + } + } + + /** + * Tests digital signature generation when IOException occurs during JSON reading. + * Verifies that DigitalSignatureException is thrown when JSON deserialization fails, + * handling read operations gracefully with proper exception wrapping. + */ + @Test + void getDigitalSignatureWithIoExceptionFromMapperReadValueShouldThrowDigitalSignatureException() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + responseWrapper.setErrors(new ArrayList<>()); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenReturn("{\"signature\":\"test-signature-response\"}"); + when(mapper.readValue(anyString(), eq(SignResponseDto.class))) + .thenThrow(new JsonProcessingException("JSON Read Error") {}); + + DigitalSignatureException exception = assertThrows(DigitalSignatureException.class, () -> + digitalSignatureUtility.getDigitalSignature(TEST_DATA) + ); + + assertTrue(exception.getMessage().contains("JSON Read Error")); + } + } + + /** + * Tests digital signature generation with null input data. + * Verifies that the method handles null input gracefully and still processes + * the digital signature request without causing null pointer exceptions. + */ + @Test + void getDigitalSignatureWithNullDataShouldHandleGracefully() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + responseWrapper.setErrors(new ArrayList<>()); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenReturn("{\"signature\":\"test-signature-response\"}"); + when(mapper.readValue("{\"signature\":\"test-signature-response\"}", SignResponseDto.class)) + .thenReturn(signResponseDto); + + String result = digitalSignatureUtility.getDigitalSignature(null); + + assertEquals(SIGNATURE_RESPONSE, result); + } + } + + /** + * Tests digital signature generation with empty input data. + * Verifies that the method handles empty string input appropriately and processes + * the digital signature request for empty data scenarios. + */ + @Test + void getDigitalSignatureWithEmptyDataShouldProcessCorrectly() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + SignResponseDto signResponseDto = new SignResponseDto(); + signResponseDto.setSignature(SIGNATURE_RESPONSE); + responseWrapper.setResponse(signResponseDto); + responseWrapper.setErrors(new ArrayList<>()); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(DigitalSignatureUtility.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + when(printRestService.postApi(eq(ApiName.DIGITALSIGNATURE), anyString(), anyString(), + any(RequestWrapper.class), eq(ResponseWrapper.class))).thenReturn(responseWrapper); + when(mapper.writeValueAsString(signResponseDto)).thenReturn("{\"signature\":\"test-signature-response\"}"); + when(mapper.readValue("{\"signature\":\"test-signature-response\"}", SignResponseDto.class)) + .thenReturn(signResponseDto); + + String result = digitalSignatureUtility.getDigitalSignature(""); + + assertEquals(SIGNATURE_RESPONSE, result); + } + } +} diff --git a/src/test/java/io/mosip/print/util/EmptyCheckUtilsTest.java b/src/test/java/io/mosip/print/util/EmptyCheckUtilsTest.java new file mode 100644 index 00000000..f06c58d8 --- /dev/null +++ b/src/test/java/io/mosip/print/util/EmptyCheckUtilsTest.java @@ -0,0 +1,289 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +/** + * Test class for {@link EmptyCheckUtils}. + * + * This class provides comprehensive unit tests for the EmptyCheckUtils utility class, + * covering all overloaded methods for null and empty checking across different data types + * including Object, String, Collection, and Map. The tests ensure proper validation + * of null values, empty values, and edge cases for each supported data type. + * + * The utility class provides static methods to safely check for null or empty values + * without throwing NullPointerException, making it essential for defensive programming + * and input validation scenarios. + * + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class EmptyCheckUtilsTest { + + /** + * Tests null object validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Object)} returns true + * when the provided object is null. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyObjectWithNull() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty((Object) null)); + } + + /** + * Tests non-null object validation. + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Object)} returns false + * when the provided object is not null, regardless of the object type. + * Note: This specifically tests the Object overload, not Collection overload. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyObjectWithNonNull() throws Exception { + assertFalse(EmptyCheckUtils.isNullEmpty(new Object())); + assertFalse(EmptyCheckUtils.isNullEmpty("test")); + assertFalse(EmptyCheckUtils.isNullEmpty(123)); + assertFalse(EmptyCheckUtils.isNullEmpty(Boolean.TRUE)); + assertFalse(EmptyCheckUtils.isNullEmpty(new StringBuilder())); + + assertFalse(EmptyCheckUtils.isNullEmpty((Object) new ArrayList<>())); + assertFalse(EmptyCheckUtils.isNullEmpty((Object) new HashMap<>())); + } + + + /** + * Tests null string validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(String)} returns true + * when the provided string is null. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyStringWithNull() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty((String) null)); + } + + /** + * Tests empty string validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(String)} returns true + * when the provided string is empty (zero length after trim). + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyStringWithEmpty() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty("")); + } + + /** + * Tests whitespace-only string validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(String)} returns true + * when the provided string contains only whitespace characters, as they + * are trimmed and result in zero length. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyStringWithWhitespace() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty(" ")); + assertTrue(EmptyCheckUtils.isNullEmpty("\t")); + assertTrue(EmptyCheckUtils.isNullEmpty("\n")); + assertTrue(EmptyCheckUtils.isNullEmpty("\r")); + assertTrue(EmptyCheckUtils.isNullEmpty(" \t \n ")); + } + + /** + * Tests non-empty string validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(String)} returns false + * when the provided string contains actual content after trimming. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyStringWithContent() throws Exception { + assertFalse(EmptyCheckUtils.isNullEmpty("test")); + assertFalse(EmptyCheckUtils.isNullEmpty(" test ")); + assertFalse(EmptyCheckUtils.isNullEmpty("a")); + assertFalse(EmptyCheckUtils.isNullEmpty("123")); + } + + /** + * Tests null collection validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Collection)} returns true + * when the provided collection is null. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyCollectionWithNull() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty((Collection) null)); + } + + /** + * Tests empty collection validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Collection)} returns true + * when the provided collection is empty (contains no elements). + * Tests with different collection types including List and Set. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyCollectionWithEmpty() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty(new ArrayList<>())); + assertTrue(EmptyCheckUtils.isNullEmpty(new HashSet<>())); + + List emptyList = new ArrayList<>(); + assertTrue(EmptyCheckUtils.isNullEmpty(emptyList)); + + Set emptySet = new HashSet<>(); + assertTrue(EmptyCheckUtils.isNullEmpty(emptySet)); + } + + /** + * Tests non-empty collection validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Collection)} returns false + * when the provided collection contains at least one element. + * Tests with different collection types and various content types. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyCollectionWithContent() throws Exception { + assertFalse(EmptyCheckUtils.isNullEmpty(Arrays.asList("test"))); + assertFalse(EmptyCheckUtils.isNullEmpty(Arrays.asList(1, 2, 3))); + + List nonEmptyList = new ArrayList<>(); + nonEmptyList.add("element"); + assertFalse(EmptyCheckUtils.isNullEmpty(nonEmptyList)); + + Set nonEmptySet = new HashSet<>(); + nonEmptySet.add("element"); + assertFalse(EmptyCheckUtils.isNullEmpty(nonEmptySet)); + } + + /** + * Tests null map validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Map)} returns true + * when the provided map is null. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyMapWithNull() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty((Map) null)); + } + + /** + * Tests empty map validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Map)} returns true + * when the provided map is empty (contains no key-value pairs). + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyMapWithEmpty() throws Exception { + assertTrue(EmptyCheckUtils.isNullEmpty(new HashMap<>())); + + Map emptyMap = new HashMap<>(); + assertTrue(EmptyCheckUtils.isNullEmpty(emptyMap)); + } + + /** + * Tests non-empty map validation. + * + * Verifies that {@link EmptyCheckUtils#isNullEmpty(Map)} returns false + * when the provided map contains at least one key-value pair. + * Tests with different key-value types. + * + * @throws Exception if test execution fails + */ + @Test + void isNullEmptyMapWithContent() throws Exception { + Map nonEmptyMap = new HashMap<>(); + nonEmptyMap.put("key", "value"); + assertFalse(EmptyCheckUtils.isNullEmpty(nonEmptyMap)); + + Map intStringMap = new HashMap<>(); + intStringMap.put(1, "one"); + assertFalse(EmptyCheckUtils.isNullEmpty(intStringMap)); + + Map mixedMap = new HashMap<>(); + mixedMap.put("test", new Object()); + assertFalse(EmptyCheckUtils.isNullEmpty(mixedMap)); + } + + /** + * Tests edge case scenarios across all method overloads. + * + * Verifies behavior with edge cases such as collections containing null elements, + * maps with null keys or values, and special string characters. + * + * @throws Exception if test execution fails + */ + @Test + void edgeCaseScenarios() throws Exception { + List listWithNull = new ArrayList<>(); + listWithNull.add(null); + assertFalse(EmptyCheckUtils.isNullEmpty(listWithNull)); + + Map mapWithNullValue = new HashMap<>(); + mapWithNullValue.put("key", null); + assertFalse(EmptyCheckUtils.isNullEmpty(mapWithNullValue)); + + Map mapWithNullKey = new HashMap<>(); + mapWithNullKey.put(null, "value"); + assertFalse(EmptyCheckUtils.isNullEmpty(mapWithNullKey)); + + assertFalse(EmptyCheckUtils.isNullEmpty("@#$%")); + assertFalse(EmptyCheckUtils.isNullEmpty("测试")); // Unicode characters + } + + /** + * Tests method overloading disambiguation. + * + * Verifies that the correct overloaded method is called based on the parameter type, + * ensuring that method resolution works as expected for different data types. + * + * @throws Exception if test execution fails + */ + @Test + void methodOverloadingTest() throws Exception { + String nullString = null; + assertTrue(EmptyCheckUtils.isNullEmpty(nullString)); + Collection nullCollection = null; + assertTrue(EmptyCheckUtils.isNullEmpty(nullCollection)); + + Map nullMap = null; + assertTrue(EmptyCheckUtils.isNullEmpty(nullMap)); + + Integer nullInteger = null; + assertTrue(EmptyCheckUtils.isNullEmpty(nullInteger)); + } +} diff --git a/src/test/java/io/mosip/print/util/JsonUtilTest.java b/src/test/java/io/mosip/print/util/JsonUtilTest.java new file mode 100644 index 00000000..1cc02240 --- /dev/null +++ b/src/test/java/io/mosip/print/util/JsonUtilTest.java @@ -0,0 +1,640 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import com.google.gson.JsonSyntaxException; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import io.mosip.print.dto.JsonValue; +import io.mosip.print.exception.FieldNotFoundException; +import io.mosip.print.exception.InstantanceCreationException; + +/** + * Unit tests for {@link JsonUtil} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the JsonUtil class, + * including JSON parsing, object mapping, data conversion between different formats, exception handling, + * and various edge cases for JSON processing operations.

+ */ +@ExtendWith(MockitoExtension.class) +class JsonUtilTest { + + private JSONObject testJsonObject; + private JSONArray testJsonArray; + private LinkedHashMap testMap; + + /** + * Sets up test fixtures before each test method execution. + * Initializes JSON objects, arrays, and maps with test data for comprehensive testing + * of JSON utility operations. + */ + @BeforeEach + void setUp() { + testJsonObject = new JSONObject(); + testJsonArray = new JSONArray(); + testMap = new LinkedHashMap<>(); + + testMap.put("name", "Test Name"); + testMap.put("age", 30); + testJsonObject.put("identity", testMap); + + LinkedHashMap arrayItem1 = new LinkedHashMap<>(); + arrayItem1.put("language", "eng"); + arrayItem1.put("value", "English Value"); + + LinkedHashMap arrayItem2 = new LinkedHashMap<>(); + arrayItem2.put("language", "ara"); + arrayItem2.put("value", "Arabic Value"); + + ArrayList arrayList = new ArrayList<>(); + arrayList.add(arrayItem1); + arrayList.add(arrayItem2); + + testJsonObject.put("fullName", arrayList); + + testJsonArray.add(arrayItem1); + testJsonArray.add(arrayItem2); + } + + /** + * Tests successful conversion of InputStream to Java Object. + * Verifies that the method correctly parses JSON from InputStream and converts it + * to the specified Java object type. + */ + @Test + void inputStreamToJavaObjectShouldSucceedWithValidJson() throws Exception { + String jsonString = "{\"name\":\"Test\",\"value\":\"TestValue\"}"; + InputStream inputStream = new ByteArrayInputStream(jsonString.getBytes("UTF-8")); + + TestDto result = (TestDto) JsonUtil.inputStreamtoJavaObject(inputStream, TestDto.class); + + assertNotNull(result); + assertEquals("Test", result.getName()); + assertEquals("TestValue", result.getValue()); + } + + /** + * Tests InputStream to Java Object conversion with null class parameter. + * Verifies that the method throws UnsupportedEncodingException when null class is provided. + */ + @Test + void inputStreamToJavaObjectWithNullClassShouldThrowException() throws Exception { + String jsonString = "{\"name\":\"Test\"}"; + InputStream inputStream = new ByteArrayInputStream(jsonString.getBytes("UTF-8")); + + assertThrows(UnsupportedEncodingException.class, () -> + JsonUtil.inputStreamtoJavaObject(inputStream, null) + ); + } + + /** + * Tests successful retrieval of JSON object from nested structure. + * Verifies that the method correctly extracts a JSONObject from a parent JSONObject + * using the specified key. + */ + @Test + void getJsonObjectShouldSucceedWithValidKey() { + JSONObject result = JsonUtil.getJSONObject(testJsonObject, "identity"); + + assertNotNull(result); + assertEquals("Test Name", result.get("name")); + assertEquals(30, result.get("age")); + } + + /** + * Tests getJSONObject method with null input parameter. + * Verifies that the method handles null JSONObject input gracefully and returns null. + */ + @Test + void getJsonObjectWithNullInputShouldReturnNull() { + JSONObject result = JsonUtil.getJSONObject(null, "identity"); + + assertNull(result); + } + + /** + * Tests getJSONObject method with non-existent key. + * Verifies that the method returns null when the specified key is not found + * in the JSONObject. + */ + @Test + void getJsonObjectWithNonExistentKeyShouldReturnNull() { + JSONObject result = JsonUtil.getJSONObject(testJsonObject, "nonExistent"); + + assertNull(result); + } + + /** + * Tests getJSONObject method with null key value. + * Verifies that the method returns null when the key exists but its value is null. + */ + @Test + void getJsonObjectWithNullKeyValueShouldReturnNull() { + testJsonObject.put("nullKey", null); + JSONObject result = JsonUtil.getJSONObject(testJsonObject, "nullKey"); + + assertNull(result); + } + + /** + * Tests successful retrieval of JSON array from JSONObject. + * Verifies that the method correctly extracts a JSONArray from a JSONObject + * using the specified key. + */ + @Test + void getJsonArrayShouldSucceedWithValidKey() { + JSONArray result = JsonUtil.getJSONArray(testJsonObject, "fullName"); + + assertNotNull(result); + assertEquals(2, result.size()); + } + + /** + * Tests getJSONArray method with null key. + * Verifies that the method returns null when the specified key is not found + * in the JSONObject. + */ + @Test + void getJsonArrayWithNullKeyShouldReturnNull() { + JSONArray result = JsonUtil.getJSONArray(testJsonObject, "nonExistent"); + + assertNull(result); + } + + /** + * Tests successful conversion of object to JSON string. + * Verifies that the method correctly serializes a Java object to JSON string format. + */ + @Test + void writeValueAsStringShouldSucceedWithValidObject() throws IOException { + TestDto testDto = new TestDto(); + testDto.setName("Test"); + testDto.setValue("TestValue"); + + String result = JsonUtil.writeValueAsString(testDto); + + assertNotNull(result); + assertTrue(result.contains("Test")); + assertTrue(result.contains("TestValue")); + } + + /** + * Tests writeValueAsString method with null object. + * Verifies that the method handles null input and returns "null" string. + */ + @Test + void writeValueAsStringWithNullShouldReturnNullString() throws IOException { + String result = JsonUtil.writeValueAsString(null); + + assertEquals("null", result); + } + + /** + * Tests successful JSON string parsing to Java object. + * Verifies that the method correctly deserializes JSON string to the specified Java object type. + */ + @Test + void readValueShouldSucceedWithValidJsonString() throws IOException { + String jsonString = "{\"name\":\"Test\",\"value\":\"TestValue\"}"; + + TestDto result = JsonUtil.readValue(jsonString, TestDto.class); + + assertNotNull(result); + assertEquals("Test", result.getName()); + assertEquals("TestValue", result.getValue()); + } + + /** + * Tests readValue method with invalid JSON string. + * Verifies that the method throws IOException when provided with malformed JSON. + */ + @Test + void readValueWithInvalidJsonShouldThrowIOException() { + String invalidJsonString = "{invalid json}"; + + assertThrows(IOException.class, () -> + JsonUtil.readValue(invalidJsonString, TestDto.class) + ); + } + + /** + * Tests getJSONValue method with null JSONObject parameter. + * Verifies that the method handles null input gracefully and returns null. + */ + @Test + void getJsonValueWithNullJsonObjectShouldReturnNull() { + Object result = JsonUtil.getJSONValue(null, "key"); + + assertNull(result); + } + + /** + * Tests getJSONValue method with non-existent key. + * Verifies that the method returns null when the specified key is not found. + */ + @Test + void getJsonValueWithNonExistentKeyShouldReturnNull() { + Object result = JsonUtil.getJSONValue(testJsonObject, "nonExistent"); + + assertNull(result); + } + + /** + * Tests successful extraction of JSONObject from JSONArray with LinkedHashMap. + * Verifies that the method correctly converts LinkedHashMap to JSONObject + * when extracting from JSONArray at specified index. + */ + @Test + void getJsonObjectFromArrayWithLinkedHashMapShouldSucceed() { + JSONObject result = JsonUtil.getJSONObjectFromArray(testJsonArray, 0); + + assertNotNull(result); + assertEquals("eng", result.get("language")); + assertEquals("English Value", result.get("value")); + } + + /** + * Tests getJSONObjectFromArray method with existing JSONObject in array. + * Verifies that the method correctly returns JSONObject when the array element + * is already a JSONObject. + */ + @Test + void getJsonObjectFromArrayWithJsonObjectShouldReturnObject() { + JSONArray jsonArrayWithObjects = new JSONArray(); + JSONObject jsonObj = new JSONObject(); + jsonObj.put("test", "value"); + jsonArrayWithObjects.add(jsonObj); + + JSONObject result = JsonUtil.getJSONObjectFromArray(jsonArrayWithObjects, 0); + + assertNotNull(result); + assertEquals("value", result.get("test")); + } + + /** + * Tests successful object mapper read value operation. + * Verifies that the method correctly deserializes JSON string using ObjectMapper. + */ + @Test + void objectMapperReadValueShouldSucceedWithValidJson() throws IOException { + String jsonString = "{\"name\":\"Test\",\"value\":\"TestValue\"}"; + + TestDto result = JsonUtil.objectMapperReadValue(jsonString, TestDto.class); + + assertNotNull(result); + assertEquals("Test", result.getName()); + assertEquals("TestValue", result.getValue()); + } + + /** + * Tests objectMapperReadValue method with invalid JSON. + * Verifies that the method throws IOException when provided with malformed JSON. + */ + @Test + void objectMapperReadValueWithInvalidJsonShouldThrowIOException() { + String invalidJsonString = "{invalid json}"; + + assertThrows(IOException.class, () -> + JsonUtil.objectMapperReadValue(invalidJsonString, TestDto.class) + ); + } + + /** + * Tests successful extraction of JsonValue array from JSONObject. + * Verifies that the method correctly converts JSONArray to JsonValue array + * with proper language and value mappings. + */ + @Test + void getJsonValuesShouldSucceedWithValidData() { + JsonValue[] result = JsonUtil.getJsonValues(testJsonObject, "fullName"); + + assertNotNull(result); + assertEquals(2, result.length); + assertEquals("eng", result[0].getLanguage()); + assertEquals("English Value", result[0].getValue()); + assertEquals("ara", result[1].getLanguage()); + assertEquals("Arabic Value", result[1].getValue()); + } + + /** + * Tests getJsonValues method with null demographic identity. + * Verifies that the method handles null JSONObject input and returns null. + */ + @Test + void getJsonValuesWithNullDemographicIdentityShouldReturnNull() { + JsonValue[] result = JsonUtil.getJsonValues(null, "fullName"); + + assertNull(result); + } + + /** + * Tests getJsonValues method with null JSONArray. + * Verifies that the method returns null when the specified key does not exist + * or contains null value. + */ + @Test + void getJsonValuesWithNullJsonArrayShouldReturnNull() { + JsonValue[] result = JsonUtil.getJsonValues(testJsonObject, "nonExistent"); + + assertNull(result); + } + + /** + * Tests successful mapping of JSONArray to Java object array. + * Verifies that the method correctly converts JSONArray elements to JsonValue objects. + */ + @Test + void mapJsonNodeToJavaObjectShouldSucceedWithValidData() { + JsonValue[] result = JsonUtil.mapJsonNodeToJavaObject(JsonValue.class, testJsonArray); + + assertNotNull(result); + assertEquals(2, result.length); + assertEquals("eng", result[0].getLanguage()); + assertEquals("English Value", result[0].getValue()); + } + + /** + * Tests mapJsonNodeToJavaObject method with instantiation exception. + * Verifies that the method throws InstantanceCreationException when unable + * to instantiate the target class (abstract class in this case). + */ + @Test + void mapJsonNodeToJavaObjectWithInstantiationExceptionShouldThrowException() { + JSONArray jsonArray = new JSONArray(); + LinkedHashMap item = new LinkedHashMap<>(); + item.put("language", "eng"); + item.put("value", "test"); + jsonArray.add(item); + + assertThrows(InstantanceCreationException.class, () -> + JsonUtil.mapJsonNodeToJavaObject(AbstractTestClass.class, jsonArray) + ); + } + + /** + * Tests mapJsonNodeToJavaObject method with field not found exception. + * Verifies that the method throws FieldNotFoundException when target class + * does not have the required fields for mapping. + */ + @Test + void mapJsonNodeToJavaObjectWithFieldNotFoundExceptionShouldThrowException() { + JSONArray jsonArray = new JSONArray(); + LinkedHashMap item = new LinkedHashMap<>(); + item.put("language", "eng"); + item.put("value", "test"); + jsonArray.add(item); + + assertThrows(FieldNotFoundException.class, () -> + JsonUtil.mapJsonNodeToJavaObject(TestDtoWithoutFields.class, jsonArray) + ); + } + + /** + * Tests mapJsonNodeToJavaObject method with null JSONObject in array. + * Verifies that the method handles null elements in JSONArray and creates + * corresponding null objects in the result array. + */ + @Test + void mapJsonNodeToJavaObjectWithNullJsonObjectShouldHandleGracefully() { + JSONArray jsonArray = new JSONArray(); + jsonArray.add(null); + + JsonValue[] result = JsonUtil.mapJsonNodeToJavaObject(JsonValue.class, jsonArray); + + assertNotNull(result); + assertEquals(1, result.length); + assertNull(result[0]); + } + + /** + * Tests successful object to JSON conversion using ObjectMapper. + * Verifies that the method correctly serializes Java object to JSON string + * using ObjectMapper functionality. + */ + @Test + void objectMapperObjectToJsonShouldSucceedWithValidObject() throws IOException { + TestDto testDto = new TestDto(); + testDto.setName("Test"); + testDto.setValue("TestValue"); + + String result = JsonUtil.objectMapperObjectToJson(testDto); + + assertNotNull(result); + assertTrue(result.contains("Test")); + assertTrue(result.contains("TestValue")); + } + + /** + * Tests objectMapperObjectToJson method with null object. + * Verifies that the method handles null input and returns "null" string. + */ + @Test + void objectMapperObjectToJsonWithNullShouldReturnNullString() throws IOException { + String result = JsonUtil.objectMapperObjectToJson(null); + + assertEquals("null", result); + } + + /** + * Tests objectMapperObjectToJson method with complex nested object. + * Verifies that the method correctly serializes complex objects with nested properties. + */ + @Test + void objectMapperObjectToJsonWithComplexObjectShouldSucceed() throws IOException { + ComplexTestDto complexDto = new ComplexTestDto(); + complexDto.setName("Complex"); + complexDto.setNestedDto(new TestDto("Nested", "NestedValue")); + + String result = JsonUtil.objectMapperObjectToJson(complexDto); + + assertNotNull(result); + assertTrue(result.contains("Complex")); + assertTrue(result.contains("Nested")); + } + + /** + * Tests private constructor accessibility for code coverage. + * Verifies that the private constructor can be accessed via reflection + * and creates a valid instance of JsonUtil. + */ + @Test + void privateConstructorShouldCreateInstance() throws Exception { + java.lang.reflect.Constructor constructor = JsonUtil.class.getDeclaredConstructor(); + constructor.setAccessible(true); + JsonUtil instance = constructor.newInstance(); + assertNotNull(instance); + } + + /** + * Tests getJSONArray method with empty ArrayList. + * Verifies that the method correctly handles empty ArrayList and returns + * an empty JSONArray. + */ + @Test + void getJsonArrayWithEmptyArrayListShouldReturnEmptyArray() { + JSONObject jsonObj = new JSONObject(); + jsonObj.put("emptyArray", new ArrayList<>()); + + JSONArray result = JsonUtil.getJSONArray(jsonObj, "emptyArray"); + + assertNotNull(result); + assertEquals(0, result.size()); + } + + /** + * Tests inputStreamtoJavaObject method with invalid JSON that throws JsonSyntaxException. + * Verifies that the method throws JsonSyntaxException when provided with malformed JSON. + */ + @Test + void inputStreamToJavaObjectWithInvalidJsonShouldThrowJsonSyntaxException() throws Exception { + String invalidJsonString = "{invalid json}"; + InputStream inputStream = new ByteArrayInputStream(invalidJsonString.getBytes("UTF-8")); + + assertThrows(JsonSyntaxException.class, () -> + JsonUtil.inputStreamtoJavaObject(inputStream, TestDto.class) + ); + } + + /** + * Tests inputStreamtoJavaObject method with malformed JSON causing Gson exception. + * Verifies that the method throws JsonSyntaxException when Gson cannot parse the JSON. + */ + @Test + void inputStreamToJavaObjectWithGsonExceptionShouldThrowJsonSyntaxException() throws Exception { + String malformedJson = "{\"name\":\"Test\",\"invalidField\":}"; + InputStream inputStream = new ByteArrayInputStream(malformedJson.getBytes("UTF-8")); + + assertThrows(JsonSyntaxException.class, () -> + JsonUtil.inputStreamtoJavaObject(inputStream, TestDto.class) + ); + } + + /** + * Tests successful getJSONValue method with string value. + * Verifies that the method correctly retrieves string values from JSONObject. + */ + @Test + void getJsonValueShouldSucceedWithStringValue() { + testJsonObject.put("stringKey", "stringValue"); + + String result = JsonUtil.getJSONValue(testJsonObject, "stringKey"); + + assertNotNull(result); + assertEquals("stringValue", result); + } + + /** + * Tests getJSONValue method with complex object (LinkedHashMap). + * Verifies that the method correctly retrieves and returns complex objects + * like LinkedHashMap from JSONObject. + */ + @Test + void getJsonValueWithComplexObjectShouldReturnObject() { + LinkedHashMap complexValue = JsonUtil.getJSONValue(testJsonObject, "identity"); + + assertNotNull(complexValue); + assertEquals("Test Name", complexValue.get("name")); + assertEquals(30, complexValue.get("age")); + } + + /** + * Tests inputStreamtoJavaObject method with exception handling. + * Verifies that the method handles type conversion scenarios gracefully + * when Gson encounters type mismatches. + */ + @Test + void inputStreamToJavaObjectWithWrappedExceptionShouldHandleGracefully() throws Exception { + String jsonString = "{\"name\":\"Test\",\"value\":123}"; + InputStream inputStream = new ByteArrayInputStream(jsonString.getBytes("UTF-8")); + + TestDto result = (TestDto) JsonUtil.inputStreamtoJavaObject(inputStream, TestDto.class); + + assertNotNull(result); + } + + /** + * Helper class for testing non-serializable objects and exception scenarios. + * Contains a non-serializable field that may cause Gson processing issues. + */ + public static class NonSerializableClass { + private final Object nonSerializableField = new Object() { + private transient String value = "test"; + }; + + public Object getNonSerializableField() { + return nonSerializableField; + } + } + + /** + * Helper class for testing basic JSON serialization and deserialization. + * Contains simple string fields for name and value properties. + */ + public static class TestDto { + private String name; + private String value; + + public TestDto() {} + + public TestDto(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + } + + /** + * Helper class for testing complex nested object serialization. + * Contains a nested TestDto object to verify complex JSON processing. + */ + public static class ComplexTestDto { + private String name; + private TestDto nestedDto; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public TestDto getNestedDto() { return nestedDto; } + public void setNestedDto(TestDto nestedDto) { this.nestedDto = nestedDto; } + } + + /** + * Abstract helper class for testing instantiation exception scenarios. + * Used to verify that the mapping method throws appropriate exceptions + * when unable to instantiate abstract classes. + */ + public abstract static class AbstractTestClass { + private String language; + private String value; + } + + /** + * Helper class for testing field not found exception scenarios. + * Intentionally lacks 'language' and 'value' fields to trigger FieldNotFoundException. + */ + public static class TestDtoWithoutFields { + private String differentField; + + public String getDifferentField() { return differentField; } + public void setDifferentField(String differentField) { this.differentField = differentField; } + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java b/src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java new file mode 100644 index 00000000..851b9b34 --- /dev/null +++ b/src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java @@ -0,0 +1,481 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import java.time.Instant; +import static org.mockito.Mockito.mockStatic; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; + +import io.mosip.print.dto.PrintResponse; +import io.mosip.print.exception.AccessDeniedException; +import io.mosip.print.exception.InvalidTokenException; +import io.mosip.print.exception.PDFGeneratorException; +import io.mosip.print.exception.PDFSignatureException; +import io.mosip.print.exception.PlatformErrorMessages; +import io.mosip.print.exception.RegPrintAppException; +import io.mosip.print.exception.TemplateProcessingFailureException; +import io.mosip.print.logger.PrintLogger; + +/** + * Unit tests for {@link PrintExceptionHandler} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the PrintExceptionHandler class, + * including exception handling for various print service errors, response formatting, HTTP status codes, + * and proper error message construction for different exception types.

+ */ +@ExtendWith(MockitoExtension.class) +class PrintExceptionHandlerTest { + + @Mock + private Environment env; + + @InjectMocks + private PrintExceptionHandler printExceptionHandler; + + private static final String SERVICE_ID = "mosip.print.service"; + private static final String SERVICE_VERSION = "1.0"; + private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + private static final String CURRENT_DATETIME = "2025-08-03T10:00:00.000Z"; + + /** + * Sets up test fixtures before each test method execution. + * Initializes environment properties and mock configurations required for exception handler testing. + */ + @BeforeEach + void setUp() { + when(env.getProperty("mosip.print.service.id")).thenReturn(SERVICE_ID); + when(env.getProperty("mosip.print.application.version")).thenReturn(SERVICE_VERSION); + when(env.getProperty("mosip.print.datetime.pattern")).thenReturn(DATETIME_PATTERN); + } + + /** + * Tests the PDFGeneratorException handler functionality. + * Verifies that PDFGeneratorException is properly handled with appropriate error codes + * from PlatformErrorMessages and correct response formatting. + */ + @Test + void pdfGeneratorExceptionHandlerShouldReturnProperResponse() { + PDFGeneratorException exception = new PDFGeneratorException( + PlatformErrorMessages.PRT_PIS_IDENTITY_NOT_FOUND.getCode(), + PlatformErrorMessages.PRT_PIS_IDENTITY_NOT_FOUND.getMessage(), + new RuntimeException("PDF generation error")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.pdfgeneratorException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the TemplateProcessingFailureException handler functionality. + * Verifies that template processing failures are properly handled with correct + * error code mapping and response structure. + */ + @Test + void templateFailureExceptionHandlerShouldReturnProperResponse() { + TemplateProcessingFailureException exception = new TemplateProcessingFailureException("ERR-003"); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.templateFailureException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the JsonMappingException handler functionality. + * Verifies that JSON mapping exceptions are properly handled through the badRequest + * handler with appropriate error formatting. + */ + @Test + void jsonMappingExceptionHandlerShouldReturnBadRequestResponse() { + JsonMappingException exception = new JsonMappingException(mock(JsonParser.class), "JSON mapping error"); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.badRequest(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the InvalidFormatException handler functionality. + * Verifies that invalid format exceptions are properly handled with correct + * error message extraction and response formatting. + */ + @Test + void invalidFormatExceptionHandlerShouldReturnBadRequestResponse() { + InvalidFormatException exception = new InvalidFormatException(mock(JsonParser.class), + "Invalid format", "invalidValue", String.class); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.badRequest(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the JsonParseException handler functionality. + * Verifies that JSON parse exceptions are properly handled through the badRequest + * handler with appropriate error processing. + */ + @Test + void jsonParseExceptionHandlerShouldReturnBadRequestResponse() { + JsonParseException exception = new JsonParseException(mock(JsonParser.class), "JSON parse error"); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.badRequest(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the MethodArgumentNotValidException handler functionality. + * Verifies that method argument validation exceptions are properly handled + * with correct binding result processing and error response formatting. + */ + @Test + void methodArgumentNotValidExceptionHandlerShouldReturnBadRequestResponse() { + BindingResult bindingResult = mock(BindingResult.class); + MethodArgumentNotValidException exception = new MethodArgumentNotValidException(null, bindingResult); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.badRequest(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the AccessDeniedException handler functionality. + * Verifies that access denied exceptions are properly handled with security-related + * error codes and appropriate response structure. + */ + @Test + void accessDeniedExceptionHandlerShouldReturnProperResponse() { + AccessDeniedException exception = new AccessDeniedException( + "Access denied error", + new SecurityException("Access denied cause")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.accessDenied(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the InvalidTokenException handler functionality. + * Verifies that invalid token exceptions are properly handled with token-related + * error codes and correct authentication error response. + */ + @Test + void invalidTokenExceptionHandlerShouldReturnProperResponse() { + InvalidTokenException exception = new InvalidTokenException("ERR-005", "Invalid token error"); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.invalidToken(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests the PDFSignatureException handler functionality. + * Verifies that PDF signature exceptions are properly handled with signature-related + * error processing and appropriate response formatting. + */ + @Test + void pdfSignatureExceptionHandlerShouldReturnProperResponse() { + PDFSignatureException exception = new PDFSignatureException( + "PDF signature error", + new RuntimeException("PDF signature cause")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.pdfSignatureException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests BaseCheckedException handling in buildPrintApiExceptionResponse method. + * Verifies that checked exceptions are properly processed through the exception + * response builder with correct categorization. + */ + @Test + void buildPrintApiExceptionResponseWithBaseCheckedExceptionShouldHandleCorrectly() { + RegPrintAppException exception = new RegPrintAppException("ERR-007", "Checked exception"); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.regPrintAppException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests BaseUncheckedException handling in buildPrintApiExceptionResponse method. + * Verifies that unchecked exceptions are properly processed with correct + * exception categorization and response formatting. + */ + @Test + void buildPrintApiExceptionResponseWithBaseUncheckedExceptionShouldHandleCorrectly() { + AccessDeniedException exception = new AccessDeniedException( + "Unchecked exception error", + new SecurityException("Unchecked exception cause")); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.accessDenied(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests response building with null ID scenario. + * Verifies that the exception handler properly handles cases where service ID + * might be null and sets appropriate default values. + */ + @Test + void buildPrintApiExceptionResponseWithNullIdShouldSetDefaultId() { + RegPrintAppException exception = new RegPrintAppException("ERR-009", "Test error"); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.regPrintAppException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(SERVICE_ID, response.getBody().getId()); + } + } + + /** + * Tests exception handling with multiple error codes and messages. + * Verifies that exceptions containing multiple error codes and messages + * are properly processed and formatted in the response. + */ + @Test + void buildPrintApiExceptionResponseWithMultipleErrorsShouldHandleCorrectly() { + TestMultipleErrorsCheckedException exception = new TestMultipleErrorsCheckedException(); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.regPrintAppException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + } + } + + /** + * Tests response building with existing ID scenario. + * Verifies that the exception handler properly uses existing service ID + * when it's already set in the response. + */ + @Test + void buildPrintApiExceptionResponseWithExistingIdShouldPreserveId() { + RegPrintAppException exception = new RegPrintAppException("ERR-011", "Test error"); + + try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) + .thenReturn(mockLogger); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + .thenReturn(CURRENT_DATETIME); + + ResponseEntity response = printExceptionHandler.regPrintAppException(exception); + + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + } + } + + /** + * Helper class for testing multiple errors scenario. + * Extends RegPrintAppException to provide multiple error codes and messages + * for comprehensive exception handling testing. + */ + private static class TestMultipleErrorsCheckedException extends RegPrintAppException { + public TestMultipleErrorsCheckedException() { + super("ERR-010", "Test multiple errors"); + } + + @Override + public List getCodes() { + return Arrays.asList("ERR-001", "ERR-002", "ERR-003"); + } + + @Override + public List getErrorTexts() { + return Arrays.asList("Error message 1", "Error message 2", "Error message 3"); + } + } +} diff --git a/src/test/java/io/mosip/print/util/RestApiClientTest.java b/src/test/java/io/mosip/print/util/RestApiClientTest.java new file mode 100644 index 00000000..d3888f56 --- /dev/null +++ b/src/test/java/io/mosip/print/util/RestApiClientTest.java @@ -0,0 +1,496 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import io.mosip.print.logger.PrintLogger; + +/** + * Unit tests for {@link RestApiClient} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the RestApiClient class, + * including GET and POST API calls, exception handling, HTTP header management, different media types, + * and various request/response scenarios for REST client operations.

+ */ +@ExtendWith(MockitoExtension.class) +class RestApiClientTest { + + @Mock + private RestTemplate restTemplate; + + @Mock + private Environment environment; + + @InjectMocks + private RestApiClient restApiClient; + + private static final String TEST_URL = "http://test-url.com"; + private static final String TEST_RESPONSE = "test-response"; + + /** + * Tests successful GET API call with URI parameter. + * Verifies that the method correctly performs GET requests using URI and returns + * the expected response when the REST call succeeds. + */ + @Test + void getApiWithUriShouldSucceedWithValidRequest() throws Exception { + URI testUri = URI.create(TEST_URL); + ResponseEntity responseEntity = new ResponseEntity<>(TEST_RESPONSE, HttpStatus.OK); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.exchange(eq(testUri), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(responseEntity); + + String result = restApiClient.getApi(testUri, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests GET API call with URI when exception occurs. + * Verifies that the method handles RestClientException gracefully and returns null + * when the REST call fails. + */ + @Test + void getApiWithUriShouldReturnNullWhenExceptionOccurs() throws Exception { + URI testUri = URI.create(TEST_URL); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.exchange(eq(testUri), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenThrow(new RestClientException("REST client error")); + + String result = restApiClient.getApi(testUri, String.class); + + assertNull(result); + } + } + + /** + * Tests successful GET API call with String URL parameter. + * Verifies that the method correctly performs GET requests using string URL and returns + * the expected response when the REST call succeeds. + */ + @Test + void getApiWithStringUrlShouldSucceedWithValidRequest() { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.getForObject(TEST_URL, String.class)).thenReturn(TEST_RESPONSE); + + String result = restApiClient.getApi(TEST_URL, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests GET API call with String URL when exception occurs. + * Verifies that the method handles RestClientException gracefully and returns null + * when the REST call with string URL fails. + */ + @Test + void getApiWithStringUrlShouldReturnNullWhenExceptionOccurs() { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.getForObject(TEST_URL, String.class)) + .thenThrow(new RestClientException("REST client error")); + + String result = restApiClient.getApi(TEST_URL, String.class); + + assertNull(result); + } + } + + /** + * Tests successful POST API call with request data. + * Verifies that the method correctly performs POST requests with JSON media type + * and returns the expected response when the REST call succeeds. + */ + @Test + void postApiShouldSucceedWithValidRequest() throws Exception { + Object requestData = "test-request"; + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, requestData, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests POST API call when exception occurs during execution. + * Verifies that the method handles RestClientException gracefully and returns null + * when the POST request fails. + */ + @Test + void postApiShouldReturnNullWhenExceptionOccurs() throws Exception { + Object requestData = "test-request"; + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenThrow(new RestClientException("POST request failed")); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, requestData, String.class); + + assertNull(result); + } + } + + /** + * Tests POST API call with null media type parameter. + * Verifies that the method handles null media type gracefully and still processes + * the request successfully. + */ + @Test + void postApiWithNullMediaTypeShouldSucceed() throws Exception { + Object requestData = "test-request"; + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, null, requestData, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests POST API call with null request type parameter. + * Verifies that the method handles null request data gracefully and still processes + * the request successfully. + */ + @Test + void postApiWithNullRequestTypeShouldSucceed() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, null, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader functionality with both null parameters. + * Verifies that the method handles null media type and null request data gracefully + * in the header setting process. + */ + @Test + void setRequestHeaderWithNullParametersShouldHandleGracefully() throws Exception { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, null, null, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader functionality with HttpEntity request type. + * Verifies that the method properly handles HttpEntity objects and preserves + * custom headers while adding content type. + */ + @Test + void setRequestHeaderWithHttpEntityShouldPreserveHeaders() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("Custom-Header", "custom-value"); + headers.add("Authorization", "Bearer token"); + HttpEntity httpEntity = new HttpEntity<>("test-body", headers); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, httpEntity, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader with HttpEntity when Content-Type already exists. + * Verifies that the method respects existing Content-Type headers and does not + * override them when they are already present. + */ + @Test + void setRequestHeaderWithHttpEntityContentTypeExistsShouldRespectExisting() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/xml"); + headers.add("Authorization", "Bearer token"); + HttpEntity httpEntity = new HttpEntity<>("test-body", headers); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, httpEntity, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader with HttpEntity having null header values. + * Verifies that the method handles null header values gracefully without + * causing exceptions during header processing. + */ + @Test + void setRequestHeaderWithHttpEntityNullValuesShouldHandleGracefully() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("Valid-Header", "valid-value"); + headers.put("Null-Header", null); + HttpEntity httpEntity = new HttpEntity<>("test-body", headers); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, httpEntity, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader with HttpEntity having empty header values. + * Verifies that the method handles empty header value lists gracefully + * during header processing operations. + */ + @Test + void setRequestHeaderWithHttpEntityEmptyValuesShouldHandleGracefully() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("Valid-Header", "valid-value"); + headers.put("Empty-Header", Arrays.asList()); + HttpEntity httpEntity = new HttpEntity<>("test-body", headers); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, httpEntity, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader functionality when ClassCastException occurs. + * Verifies that the method handles ClassCastException gracefully when the request + * object cannot be cast to HttpEntity. + */ + @Test + void setRequestHeaderWithClassCastExceptionShouldHandleGracefully() throws Exception { + Object nonHttpEntity = "not-an-http-entity"; + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, nonHttpEntity, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader with HttpEntity having null body. + * Verifies that the method handles HttpEntity objects with null body content + * gracefully while preserving header information. + */ + @Test + void setRequestHeaderWithNullHttpEntityBodyShouldHandleGracefully() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.add("Custom-Header", "custom-value"); + HttpEntity httpEntity = new HttpEntity<>(null, headers); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, httpEntity, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests setRequestHeader with multiple header values. + * Verifies that the method correctly processes headers with multiple values + * and handles both single and multi-value header scenarios. + */ + @Test + void setRequestHeaderWithMultipleHeaderValuesShouldProcessCorrectly() throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.put("Multi-Value-Header", Arrays.asList("value1", "value2", "value3")); + headers.add("Single-Value-Header", "single-value"); + HttpEntity httpEntity = new HttpEntity<>("test-body", headers); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_JSON, httpEntity, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } + + /** + * Tests GET API call with different response types. + * Verifies that the method can handle various response types beyond String, + * such as Integer, and properly deserialize the response. + */ + @Test + void getApiWithDifferentResponseTypesShouldHandleVariousTypes() throws Exception { + URI testUri = URI.create(TEST_URL); + Integer intResponse = 42; + ResponseEntity responseEntity = new ResponseEntity<>(intResponse, HttpStatus.OK); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.exchange(eq(testUri), eq(HttpMethod.GET), any(HttpEntity.class), eq(Integer.class))) + .thenReturn(responseEntity); + + Integer result = restApiClient.getApi(testUri, Integer.class); + + assertNotNull(result); + assertEquals(intResponse, result); + } + } + + /** + * Tests POST API call with different media types. + * Verifies that the method correctly handles various media types beyond JSON, + * such as XML, for content type specification. + */ + @Test + void postApiWithDifferentMediaTypesShouldHandleVariousTypes() throws Exception { + Object requestData = "test-request"; + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + Logger mockLogger = mock(Logger.class); + printLoggerMock.when(() -> PrintLogger.getLogger(RestApiClient.class)) + .thenReturn(mockLogger); + + when(restTemplate.postForObject(eq(TEST_URL), any(HttpEntity.class), eq(String.class))) + .thenReturn(TEST_RESPONSE); + + String result = restApiClient.postApi(TEST_URL, MediaType.APPLICATION_XML, requestData, String.class); + + assertNotNull(result); + assertEquals(TEST_RESPONSE, result); + } + } +} \ No newline at end of file diff --git a/src/test/java/io/mosip/print/util/ServerUtilTest.java b/src/test/java/io/mosip/print/util/ServerUtilTest.java new file mode 100644 index 00000000..504734d2 --- /dev/null +++ b/src/test/java/io/mosip/print/util/ServerUtilTest.java @@ -0,0 +1,180 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * Test class for {@link ServerUtil}. + * + * This class provides comprehensive unit tests for the ServerUtil singleton utility class, + * covering all methods including success and exception scenarios. Tests ensure proper + * singleton behavior, server IP retrieval, and server name retrieval functionality. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class ServerUtilTest { + + /** + * Sets up the test environment before each test method execution. + * + * Resets the singleton instance of ServerUtil to null using reflection + * to ensure test isolation and prevent test interference. + */ + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(ServerUtil.class, "serverInstance", null); + } + + /** + * Tests the singleton instance creation on first call. + * + * Verifies that {@link ServerUtil#getServerUtilInstance()} creates and returns + * a non-null instance when called for the first time. + * + * @throws Exception if test execution fails + */ + @Test + void getServerUtilInstanceFirstCall() { + ServerUtil instance = ServerUtil.getServerUtilInstance(); + + assertNotNull(instance); + } + + /** + * Tests the singleton behavior on subsequent calls. + * + * Verifies that {@link ServerUtil#getServerUtilInstance()} returns the same + * instance on multiple calls, ensuring proper singleton pattern implementation. + * + * @throws Exception if test execution fails + */ + @Test + void getServerUtilInstanceSecondCall() { + ServerUtil instance1 = ServerUtil.getServerUtilInstance(); + ServerUtil instance2 = ServerUtil.getServerUtilInstance(); + + assertSame(instance1, instance2); + } + + /** + * Tests successful server IP retrieval. + * + * Mocks {@link InetAddress#getLocalHost()} to return a valid InetAddress + * and verifies that {@link ServerUtil#getServerIp()} returns the correct + * IP address string. + * + * @throws Exception if test execution fails + */ + @Test + void getServerIpSuccess() throws Exception { + try (MockedStatic inetAddressMock = mockStatic(InetAddress.class)) { + InetAddress mockAddress = mock(InetAddress.class); + + inetAddressMock.when(InetAddress::getLocalHost).thenReturn(mockAddress); + when(mockAddress.getHostAddress()).thenReturn("192.168.1.1"); + + ServerUtil serverUtil = ServerUtil.getServerUtilInstance(); + String result = serverUtil.getServerIp(); + + assertEquals("192.168.1.1", result); + } + } + + /** + * Tests server IP retrieval when UnknownHostException occurs. + * + * Mocks {@link InetAddress#getLocalHost()} to throw an {@link UnknownHostException} + * and verifies that {@link ServerUtil#getServerIp()} handles the exception gracefully + * by returning "UNKNOWN-HOST" and logging the error. + * + * @throws Exception if test execution fails + */ + @Test + void getServerIpWithException() throws Exception { + try (MockedStatic inetAddressMock = mockStatic(InetAddress.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(ServerUtil.class)) + .thenReturn(mockLogger); + + inetAddressMock.when(InetAddress::getLocalHost) + .thenThrow(new UnknownHostException("Test exception")); + + ServerUtil serverUtil = ServerUtil.getServerUtilInstance(); + String result = serverUtil.getServerIp(); + + assertEquals("UNKNOWN-HOST", result); + } + } + + /** + * Tests successful server name retrieval. + * + * Mocks {@link InetAddress#getLocalHost()} to return a valid InetAddress + * and verifies that {@link ServerUtil#getServerName()} returns the correct + * hostname string. + * + * @throws Exception if test execution fails + */ + @Test + void getServerNameSuccess() throws Exception { + try (MockedStatic inetAddressMock = mockStatic(InetAddress.class)) { + InetAddress mockAddress = mock(InetAddress.class); + + inetAddressMock.when(InetAddress::getLocalHost).thenReturn(mockAddress); + when(mockAddress.getHostName()).thenReturn("test-server"); + + ServerUtil serverUtil = ServerUtil.getServerUtilInstance(); + String result = serverUtil.getServerName(); + + assertEquals("test-server", result); + } + } + + /** + * Tests server name retrieval when UnknownHostException occurs. + * + * Mocks {@link InetAddress#getLocalHost()} to throw an {@link UnknownHostException} + * and verifies that {@link ServerUtil#getServerName()} handles the exception gracefully + * by returning "UNKNOWN-HOST" and logging the error. + * + * @throws Exception if test execution fails + */ + @Test + void getServerNameWithException() throws Exception { + try (MockedStatic inetAddressMock = mockStatic(InetAddress.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(ServerUtil.class)) + .thenReturn(mockLogger); + + inetAddressMock.when(InetAddress::getLocalHost) + .thenThrow(new UnknownHostException("Test exception")); + + ServerUtil serverUtil = ServerUtil.getServerUtilInstance(); + String result = serverUtil.getServerName(); + + assertEquals("UNKNOWN-HOST", result); + } + } +} diff --git a/src/test/java/io/mosip/print/util/TemplateGeneratorTest.java b/src/test/java/io/mosip/print/util/TemplateGeneratorTest.java new file mode 100644 index 00000000..ca90f065 --- /dev/null +++ b/src/test/java/io/mosip/print/util/TemplateGeneratorTest.java @@ -0,0 +1,394 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.kernel.core.templatemanager.exception.TemplateMethodInvocationException; +import io.mosip.kernel.core.templatemanager.exception.TemplateParsingException; +import io.mosip.kernel.core.templatemanager.exception.TemplateResourceNotFoundException; +import io.mosip.print.constant.ApiName; +import io.mosip.print.core.http.ResponseWrapper; +import io.mosip.print.dto.TemplateDto; +import io.mosip.print.dto.TemplateResponseDto; +import io.mosip.print.exception.ApisResourceAccessException; +import io.mosip.print.exception.TemplateProcessingFailureException; +import io.mosip.print.logger.PrintLogger; +import io.mosip.print.service.PrintRestClientService; +import io.mosip.print.spi.TemplateManager; + +/** + * Unit tests for {@link TemplateGenerator} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the TemplateGenerator class, + * including template retrieval, template processing, template merging operations, exception handling scenarios, + * and template manager configuration with various resource loader settings.

+ * + */ +@ExtendWith(MockitoExtension.class) +class TemplateGeneratorTest { + + @Mock + private PrintRestClientService restClientService; + + @Mock + private ObjectMapper mapper; + + @InjectMocks + private TemplateGenerator templateGenerator; + + private static final String TEMPLATE_TYPE_CODE = "test-template"; + private static final String LANG_CODE = "en"; + private static final String TEMPLATE_CONTENT = "Hello ${name}"; + private static final String MERGED_CONTENT = "Hello John"; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the TemplateGenerator instance with default configuration settings + * including resource loader, template path, cache settings, and encoding. + */ + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(templateGenerator, "resourceLoader", "classpath"); + ReflectionTestUtils.setField(templateGenerator, "templatePath", "."); + ReflectionTestUtils.setField(templateGenerator, "cache", Boolean.TRUE); + ReflectionTestUtils.setField(templateGenerator, "defaultEncoding", "UTF-8"); + } + + /** + * Tests successful template generation and merging process. + * Verifies that the method correctly retrieves template data, processes it through + * the template manager, and returns a merged InputStream with attribute values. + */ + @Test + void getTemplateShouldSucceedWithValidTemplateAndAttributes() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + TemplateResponseDto templateResponseDto = createTemplateResponseDto(); + responseWrapper.setResponse(templateResponseDto); + + Map attributes = new HashMap<>(); + attributes.put("name", "John"); + + InputStream mergedStream = new ByteArrayInputStream(MERGED_CONTENT.getBytes()); + TemplateManager mockTemplateManager = mock(TemplateManager.class); + + when(restClientService.getApi( + eq(ApiName.TEMPLATES), + any(List.class), + anyString(), + anyString(), + eq(ResponseWrapper.class) + )).thenReturn(responseWrapper); + + when(mapper.writeValueAsString(templateResponseDto)).thenReturn("template-json"); + when(mapper.readValue("template-json", TemplateResponseDto.class)).thenReturn(templateResponseDto); + when(mockTemplateManager.merge(any(InputStream.class), eq(attributes))).thenReturn(mergedStream); + + TemplateGenerator spyTemplateGenerator = new TemplateGenerator() { + @Override + public TemplateManager getTemplateManager() { + return mockTemplateManager; + } + }; + ReflectionTestUtils.setField(spyTemplateGenerator, "restClientService", restClientService); + ReflectionTestUtils.setField(spyTemplateGenerator, "mapper", mapper); + + InputStream result = spyTemplateGenerator.getTemplate(TEMPLATE_TYPE_CODE, attributes, LANG_CODE); + + assertNotNull(result); + assertEquals(mergedStream, result); + } + + /** + * Tests template generation when retrieved template is null. + * Verifies that the method handles null template responses gracefully + * and returns null when no template data is available. + */ + @Test + void getTemplateWhenTemplateIsNullShouldReturnNull() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + responseWrapper.setResponse(null); + + Map attributes = new HashMap<>(); + attributes.put("name", "John"); + + when(restClientService.getApi( + eq(ApiName.TEMPLATES), + any(List.class), + anyString(), + anyString(), + eq(ResponseWrapper.class) + )).thenReturn(responseWrapper); + + when(mapper.writeValueAsString(null)).thenReturn("null"); + when(mapper.readValue("null", TemplateResponseDto.class)).thenReturn(null); + + InputStream result = templateGenerator.getTemplate(TEMPLATE_TYPE_CODE, attributes, LANG_CODE); + + assertEquals(null, result); + } + + /** + * Tests template generation when TemplateResourceNotFoundException occurs. + * Verifies that TemplateResourceNotFoundException is properly wrapped and thrown + * as TemplateProcessingFailureException during template processing. + */ + @Test + void getTemplateWithTemplateResourceNotFoundExceptionShouldThrowTemplateProcessingFailureException() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + TemplateResponseDto templateResponseDto = createTemplateResponseDto(); + responseWrapper.setResponse(templateResponseDto); + + Map attributes = new HashMap<>(); + attributes.put("name", "John"); + + TemplateManager mockTemplateManager = mock(TemplateManager.class); + + when(restClientService.getApi( + eq(ApiName.TEMPLATES), + any(List.class), + anyString(), + anyString(), + eq(ResponseWrapper.class) + )).thenReturn(responseWrapper); + + when(mapper.writeValueAsString(templateResponseDto)).thenReturn("template-json"); + when(mapper.readValue("template-json", TemplateResponseDto.class)).thenReturn(templateResponseDto); + when(mockTemplateManager.merge(any(InputStream.class), eq(attributes))) + .thenThrow(new TemplateResourceNotFoundException("ERR-001", "Template not found")); + + TemplateGenerator spyTemplateGenerator = new TemplateGenerator() { + @Override + public TemplateManager getTemplateManager() { + return mockTemplateManager; + } + }; + ReflectionTestUtils.setField(spyTemplateGenerator, "restClientService", restClientService); + ReflectionTestUtils.setField(spyTemplateGenerator, "mapper", mapper); + + assertThrows(TemplateProcessingFailureException.class, () -> + spyTemplateGenerator.getTemplate(TEMPLATE_TYPE_CODE, attributes, LANG_CODE) + ); + } + + /** + * Tests template generation when TemplateParsingException occurs. + * Verifies that TemplateParsingException is properly wrapped and thrown + * as TemplateProcessingFailureException during template parsing operations. + */ + @Test + void getTemplateWithTemplateParsingExceptionShouldThrowTemplateProcessingFailureException() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + TemplateResponseDto templateResponseDto = createTemplateResponseDto(); + responseWrapper.setResponse(templateResponseDto); + + Map attributes = new HashMap<>(); + attributes.put("name", "John"); + + TemplateManager mockTemplateManager = mock(TemplateManager.class); + + when(restClientService.getApi( + eq(ApiName.TEMPLATES), + any(List.class), + anyString(), + anyString(), + eq(ResponseWrapper.class) + )).thenReturn(responseWrapper); + + when(mapper.writeValueAsString(templateResponseDto)).thenReturn("template-json"); + when(mapper.readValue("template-json", TemplateResponseDto.class)).thenReturn(templateResponseDto); + when(mockTemplateManager.merge(any(InputStream.class), eq(attributes))) + .thenThrow(new TemplateParsingException("ERR-002", "Parsing error")); + + TemplateGenerator spyTemplateGenerator = new TemplateGenerator() { + @Override + public TemplateManager getTemplateManager() { + return mockTemplateManager; + } + }; + ReflectionTestUtils.setField(spyTemplateGenerator, "restClientService", restClientService); + ReflectionTestUtils.setField(spyTemplateGenerator, "mapper", mapper); + + assertThrows(TemplateProcessingFailureException.class, () -> + spyTemplateGenerator.getTemplate(TEMPLATE_TYPE_CODE, attributes, LANG_CODE) + ); + } + + /** + * Tests template generation when TemplateMethodInvocationException occurs. + * Verifies that TemplateMethodInvocationException is properly wrapped and thrown + * as TemplateProcessingFailureException during template method execution. + */ + @Test + void getTemplateWithTemplateMethodInvocationExceptionShouldThrowTemplateProcessingFailureException() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + TemplateResponseDto templateResponseDto = createTemplateResponseDto(); + responseWrapper.setResponse(templateResponseDto); + + Map attributes = new HashMap<>(); + attributes.put("name", "John"); + + TemplateManager mockTemplateManager = mock(TemplateManager.class); + + when(restClientService.getApi( + eq(ApiName.TEMPLATES), + any(List.class), + anyString(), + anyString(), + eq(ResponseWrapper.class) + )).thenReturn(responseWrapper); + + when(mapper.writeValueAsString(templateResponseDto)).thenReturn("template-json"); + when(mapper.readValue("template-json", TemplateResponseDto.class)).thenReturn(templateResponseDto); + when(mockTemplateManager.merge(any(InputStream.class), eq(attributes))) + .thenThrow(new TemplateMethodInvocationException("ERR-003", "Method invocation error")); + + TemplateGenerator spyTemplateGenerator = new TemplateGenerator() { + @Override + public TemplateManager getTemplateManager() { + return mockTemplateManager; + } + }; + ReflectionTestUtils.setField(spyTemplateGenerator, "restClientService", restClientService); + ReflectionTestUtils.setField(spyTemplateGenerator, "mapper", mapper); + + assertThrows(TemplateProcessingFailureException.class, () -> + spyTemplateGenerator.getTemplate(TEMPLATE_TYPE_CODE, attributes, LANG_CODE) + ); + } + + /** + * Tests template generation when ApisResourceAccessException is thrown. + * Verifies that ApisResourceAccessException is properly propagated when + * the REST client service fails to access the template API. + */ + @Test + void getTemplateWithApisResourceAccessExceptionShouldThrowException() throws Exception { + Map attributes = new HashMap<>(); + attributes.put("name", "John"); + + when(restClientService.getApi( + eq(ApiName.TEMPLATES), + any(List.class), + anyString(), + anyString(), + eq(ResponseWrapper.class) + )).thenThrow(new ApisResourceAccessException("API Error")); + + assertThrows(ApisResourceAccessException.class, () -> + templateGenerator.getTemplate(TEMPLATE_TYPE_CODE, attributes, LANG_CODE) + ); + } + + /** + * Tests template generation when IOException occurs during JSON processing. + * Verifies that IOException is properly propagated when JSON serialization + * or deserialization fails during template processing. + */ + @Test + void getTemplateWithIoExceptionShouldThrowException() throws Exception { + ResponseWrapper responseWrapper = new ResponseWrapper<>(); + TemplateResponseDto templateResponseDto = createTemplateResponseDto(); + responseWrapper.setResponse(templateResponseDto); + + Map attributes = new HashMap<>(); + attributes.put("name", "John"); + + when(restClientService.getApi( + eq(ApiName.TEMPLATES), + any(List.class), + anyString(), + anyString(), + eq(ResponseWrapper.class) + )).thenReturn(responseWrapper); + + when(mapper.writeValueAsString(templateResponseDto)) + .thenThrow(new JsonProcessingException("JSON Error") {}); + + assertThrows(IOException.class, () -> + templateGenerator.getTemplate(TEMPLATE_TYPE_CODE, attributes, LANG_CODE) + ); + } + + /** + * Tests the getTemplateManager method with default configuration. + * Verifies that the method correctly creates and returns a TemplateManager instance + * with the configured resource loader, path, cache, and encoding settings. + */ + @Test + void getTemplateManagerShouldReturnTemplateManagerInstance() { + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + printLoggerMock.when(() -> PrintLogger.getLogger(TemplateGenerator.class)) + .thenReturn(mock(org.slf4j.Logger.class)); + + TemplateManager result = templateGenerator.getTemplateManager(); + + assertNotNull(result); + } + } + + /** + * Tests the getTemplateManager method with different resource loader settings. + * Verifies that the method correctly handles various configuration settings + * including file resource loader, custom template path, disabled cache, and different encoding. + */ + @Test + void getTemplateManagerWithDifferentSettingsShouldReturnTemplateManagerInstance() { + ReflectionTestUtils.setField(templateGenerator, "resourceLoader", "file"); + ReflectionTestUtils.setField(templateGenerator, "templatePath", "/templates"); + ReflectionTestUtils.setField(templateGenerator, "cache", Boolean.FALSE); + ReflectionTestUtils.setField(templateGenerator, "defaultEncoding", "ISO-8859-1"); + + try (MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + printLoggerMock.when(() -> PrintLogger.getLogger(TemplateGenerator.class)) + .thenReturn(mock(org.slf4j.Logger.class)); + + TemplateManager result = templateGenerator.getTemplateManager(); + + assertNotNull(result); + } + } + + /** + * Helper method to create TemplateResponseDto for testing purposes. + * Creates a mock template response containing template data with placeholder content + * for use in template processing tests. + * + * @return TemplateResponseDto with test template data + */ + private TemplateResponseDto createTemplateResponseDto() { + TemplateResponseDto templateResponseDto = new TemplateResponseDto(); + List templates = new ArrayList<>(); + TemplateDto templateDto = new TemplateDto(); + templateDto.setFileText(TEMPLATE_CONTENT); + templates.add(templateDto); + templateResponseDto.setTemplates(templates); + return templateResponseDto; + } +} diff --git a/src/test/java/io/mosip/print/util/TemplateManagerUtilTest.java b/src/test/java/io/mosip/print/util/TemplateManagerUtilTest.java new file mode 100644 index 00000000..00fd1025 --- /dev/null +++ b/src/test/java/io/mosip/print/util/TemplateManagerUtilTest.java @@ -0,0 +1,225 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.velocity.VelocityContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Unit tests for {@link TemplateManagerUtil} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the TemplateManagerUtil class, + * including input binding to VelocityContext, null and empty input handling, different data types processing, + * complex object binding, and various edge cases for template context management operations.

+ * + */ +@ExtendWith(MockitoExtension.class) +class TemplateManagerUtilTest { + + /** + * Tests successful binding of input map to VelocityContext. + * Verifies that the method correctly converts a map of key-value pairs into a VelocityContext + * and all values are accessible through the context. + */ + @Test + void bindInputToContextShouldSucceedWithValidInputMap() { + Map inputMap = new HashMap<>(); + inputMap.put("name", "John Doe"); + inputMap.put("age", 30); + inputMap.put("city", "New York"); + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + assertEquals("John Doe", result.get("name")); + assertEquals(30, result.get("age")); + assertEquals("New York", result.get("city")); + } + + /** + * Tests binding functionality when null input map is provided. + * Verifies that the method handles null input gracefully and returns null + * when no input data is available. + */ + @Test + void bindInputToContextWithNullInputShouldReturnNull() { + VelocityContext result = TemplateManagerUtil.bindInputToContext(null); + + assertNull(result); + } + + /** + * Tests binding functionality when empty input map is provided. + * Verifies that the method returns null when an empty map is provided, + * indicating no context data is available for template processing. + */ + @Test + void bindInputToContextWithEmptyInputShouldReturnNull() { + Map emptyMap = new HashMap<>(); + + VelocityContext result = TemplateManagerUtil.bindInputToContext(emptyMap); + + assertNull(result); + } + + /** + * Tests binding functionality with single key-value pair. + * Verifies that the method correctly handles maps containing only one entry + * and creates a proper VelocityContext with the single value. + */ + @Test + void bindInputToContextWithSingleEntryShouldCreateContext() { + Map inputMap = new HashMap<>(); + inputMap.put("title", "Mr."); + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + assertEquals("Mr.", result.get("title")); + } + + /** + * Tests binding functionality with different object types. + * Verifies that the method correctly handles various data types including + * strings, integers, booleans, doubles, and null values in the context. + */ + @Test + void bindInputToContextWithDifferentTypesShouldHandleAllTypes() { + Map inputMap = new HashMap<>(); + inputMap.put("stringValue", "test"); + inputMap.put("intValue", 42); + inputMap.put("boolValue", true); + inputMap.put("doubleValue", 3.14); + inputMap.put("nullValue", null); + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + assertEquals("test", result.get("stringValue")); + assertEquals(42, result.get("intValue")); + assertEquals(true, result.get("boolValue")); + assertEquals(3.14, result.get("doubleValue")); + assertNull(result.get("nullValue")); + } + + /** + * Tests binding functionality with complex objects. + * Verifies that the method correctly handles complex data structures including + * nested maps and arrays, preserving their structure in the VelocityContext. + */ + @Test + void bindInputToContextWithComplexObjectsShouldPreserveStructure() { + Map nestedMap = new HashMap<>(); + nestedMap.put("nestedKey", "nestedValue"); + + Map inputMap = new HashMap<>(); + inputMap.put("mapObject", nestedMap); + inputMap.put("arrayObject", new String[]{"item1", "item2", "item3"}); + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + assertEquals(nestedMap, result.get("mapObject")); + assertNotNull(result.get("arrayObject")); + } + + /** + * Tests that binding preserves all map entries. + * Verifies that the method correctly transfers all key-value pairs from the input map + * to the VelocityContext without losing any data. + */ + @Test + void bindInputToContextShouldPreserveAllEntries() { + Map inputMap = new HashMap<>(); + for (int i = 0; i < 10; i++) { + inputMap.put("key" + i, "value" + i); + } + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + for (int i = 0; i < 10; i++) { + assertEquals("value" + i, result.get("key" + i)); + } + } + + /** + * Tests binding functionality with special characters in keys and values. + * Verifies that the method correctly handles keys and values containing + * special characters, underscores, dashes, and alphanumeric combinations. + */ + @Test + void bindInputToContextWithSpecialCharactersShouldHandleCorrectly() { + Map inputMap = new HashMap<>(); + inputMap.put("key_with_underscore", "value with spaces"); + inputMap.put("key-with-dash", "value@with#special$characters"); + inputMap.put("keyWithNumbers123", "value123"); + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + assertEquals("value with spaces", result.get("key_with_underscore")); + assertEquals("value@with#special$characters", result.get("key-with-dash")); + assertEquals("value123", result.get("keyWithNumbers123")); + } + + /** + * Tests that VelocityContext is properly initialized with input map. + * Verifies the correct initialization of VelocityContext and proper handling + * of both existing and non-existing keys in the context. + */ + @Test + void bindInputToContextShouldInitializeProperlyWithInputMap() { + Map inputMap = new HashMap<>(); + inputMap.put("template", "user-card"); + inputMap.put("version", "1.0"); + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + assertEquals("user-card", result.get("template")); + assertEquals("1.0", result.get("version")); + assertNull(result.get("nonExistentKey")); + } + + /** + * Tests binding functionality with large input map. + * Verifies that the method can handle large datasets efficiently and correctly + * transfer all entries from a substantial input map to VelocityContext. + */ + @Test + void bindInputToContextWithLargeMapShouldHandleEfficiently() { + Map inputMap = new HashMap<>(); + for (int i = 0; i < 1000; i++) { + inputMap.put("largeKey" + i, "largeValue" + i); + } + + VelocityContext result = TemplateManagerUtil.bindInputToContext(inputMap); + + assertNotNull(result); + assertEquals("largeValue0", result.get("largeKey0")); + assertEquals("largeValue999", result.get("largeKey999")); + assertEquals("largeValue500", result.get("largeKey500")); + } + + /** + * Tests private constructor accessibility for code coverage. + * Verifies that the private constructor can be accessed via reflection + * and creates a valid instance of TemplateManagerUtil. + */ + @Test + void privateConstructorShouldCreateInstance() throws Exception { + java.lang.reflect.Constructor constructor = + TemplateManagerUtil.class.getDeclaredConstructor(); + constructor.setAccessible(true); + TemplateManagerUtil instance = constructor.newInstance(); + assertNotNull(instance); + } +} diff --git a/src/test/java/io/mosip/print/util/TokenHandlerUtilTest.java b/src/test/java/io/mosip/print/util/TokenHandlerUtilTest.java new file mode 100644 index 00000000..782f9b84 --- /dev/null +++ b/src/test/java/io/mosip/print/util/TokenHandlerUtilTest.java @@ -0,0 +1,393 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; + +import io.mosip.print.exception.ExceptionUtils; + +/** + * Unit tests for {@link TokenHandlerUtil} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the TokenHandlerUtil class, + * including JWT token validation, issuer verification, client ID validation, token expiration checking, + * exception handling scenarios, and various edge cases for bearer token authentication operations.

+ * + */ +@ExtendWith(MockitoExtension.class) +class TokenHandlerUtilTest { + + private static final String VALID_TOKEN = "valid.jwt.token"; + private static final String INVALID_TOKEN = "invalid.jwt.token"; + private static final String ISSUER_URL = "https://issuer.example.com"; + private static final String CLIENT_ID = "test-client-id"; + private static final String DIFFERENT_ISSUER = "https://different-issuer.com"; + private static final String DIFFERENT_CLIENT_ID = "different-client-id"; + + private DecodedJWT mockDecodedJWT; + private Claim mockClaim; + private Map mockClaims; + + /** + * Sets up test fixtures before each test method execution. + * Initializes mock objects for JWT decoding, claims processing, and token validation + * operations required for comprehensive token handler testing. + */ + @BeforeEach + void setUp() { + mockDecodedJWT = mock(DecodedJWT.class); + mockClaim = mock(Claim.class); + mockClaims = new HashMap<>(); + mockClaims.put("clientId", mockClaim); + } + + /** + * Tests successful token validation with all valid parameters. + * Verifies that the method correctly validates a JWT token when the issuer, + * client ID, and expiration time are all valid and meet the expected criteria. + */ + @Test + void isValidBearerTokenShouldSucceedWithAllValidParameters() { + Date futureDate = new Date(System.currentTimeMillis() + 3600000); // +1 hour + LocalDateTime futureTime = LocalDateTime.now().plusHours(1); + LocalDateTime currentTime = LocalDateTime.now(); + + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class); + MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + jwtMock.when(() -> JWT.decode(VALID_TOKEN)).thenReturn(mockDecodedJWT); + when(mockDecodedJWT.getClaims()).thenReturn(mockClaims); + when(mockDecodedJWT.getExpiresAt()).thenReturn(futureDate); + when(mockDecodedJWT.getIssuer()).thenReturn(ISSUER_URL); + when(mockClaim.asString()).thenReturn(CLIENT_ID); + + dateUtilsMock.when(() -> DateUtils.getUTCTimeFromDate(futureDate)) + .thenReturn("2025-08-03T11:30:45.123Z"); + dateUtilsMock.when(() -> DateUtils.convertUTCToLocalDateTime("2025-08-03T11:30:45.123Z")) + .thenReturn(futureTime); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTime()) + .thenReturn(currentTime); + dateUtilsMock.when(() -> DateUtils.before(currentTime, futureTime)) + .thenReturn(true); + + boolean result = TokenHandlerUtil.isValidBearerToken(VALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertTrue(result); + } + } + + /** + * Tests token validation with invalid issuer. + * Verifies that the method correctly rejects tokens when the issuer in the JWT + * does not match the expected issuer URL. + */ + @Test + void isValidBearerTokenWithInvalidIssuerShouldReturnFalse() { + Date futureDate = new Date(System.currentTimeMillis() + 3600000); + + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + jwtMock.when(() -> JWT.decode(VALID_TOKEN)).thenReturn(mockDecodedJWT); + when(mockDecodedJWT.getClaims()).thenReturn(mockClaims); + when(mockDecodedJWT.getExpiresAt()).thenReturn(futureDate); + when(mockDecodedJWT.getIssuer()).thenReturn(DIFFERENT_ISSUER); + + boolean result = TokenHandlerUtil.isValidBearerToken(VALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertFalse(result); + } + } + + /** + * Tests token validation with expired token. + * Verifies that the method correctly rejects tokens when the expiration time + * is in the past, indicating the token has expired. + */ + @Test + void isValidBearerTokenWithExpiredTokenShouldReturnFalse() { + Date pastDate = new Date(System.currentTimeMillis() - 3600000); // -1 hour + LocalDateTime pastTime = LocalDateTime.now().minusHours(1); + LocalDateTime currentTime = LocalDateTime.now(); + + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + jwtMock.when(() -> JWT.decode(VALID_TOKEN)).thenReturn(mockDecodedJWT); + when(mockDecodedJWT.getClaims()).thenReturn(mockClaims); + when(mockDecodedJWT.getExpiresAt()).thenReturn(pastDate); + when(mockDecodedJWT.getIssuer()).thenReturn(ISSUER_URL); + + dateUtilsMock.when(() -> DateUtils.getUTCTimeFromDate(pastDate)) + .thenReturn("2025-08-03T09:30:45.123Z"); + dateUtilsMock.when(() -> DateUtils.convertUTCToLocalDateTime("2025-08-03T09:30:45.123Z")) + .thenReturn(pastTime); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTime()) + .thenReturn(currentTime); + dateUtilsMock.when(() -> DateUtils.before(currentTime, pastTime)) + .thenReturn(false); + + boolean result = TokenHandlerUtil.isValidBearerToken(VALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertFalse(result); + } + } + + /** + * Tests token validation with invalid client ID. + * Verifies that the method correctly rejects tokens when the client ID in the JWT + * claims does not match the expected client ID. + */ + @Test + void isValidBearerTokenWithInvalidClientIdShouldReturnFalse() { + Date futureDate = new Date(System.currentTimeMillis() + 3600000); + LocalDateTime futureTime = LocalDateTime.now().plusHours(1); + LocalDateTime currentTime = LocalDateTime.now(); + + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + jwtMock.when(() -> JWT.decode(VALID_TOKEN)).thenReturn(mockDecodedJWT); + when(mockDecodedJWT.getClaims()).thenReturn(mockClaims); + when(mockDecodedJWT.getExpiresAt()).thenReturn(futureDate); + when(mockDecodedJWT.getIssuer()).thenReturn(ISSUER_URL); + when(mockClaim.asString()).thenReturn(DIFFERENT_CLIENT_ID); + + dateUtilsMock.when(() -> DateUtils.getUTCTimeFromDate(futureDate)) + .thenReturn("2025-08-03T11:30:45.123Z"); + dateUtilsMock.when(() -> DateUtils.convertUTCToLocalDateTime("2025-08-03T11:30:45.123Z")) + .thenReturn(futureTime); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTime()) + .thenReturn(currentTime); + dateUtilsMock.when(() -> DateUtils.before(currentTime, futureTime)) + .thenReturn(true); + + boolean result = TokenHandlerUtil.isValidBearerToken(VALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertFalse(result); + } + } + + /** + * Tests token validation when JWTDecodeException occurs. + * Verifies that the method handles JWTDecodeException gracefully when JWT decoding + * fails due to malformed token format and returns false. + */ + @Test + void isValidBearerTokenWithJwtDecodeExceptionShouldReturnFalse() { + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class); + MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + JWTDecodeException jwtDecodeException = new JWTDecodeException("Invalid JWT token"); + jwtMock.when(() -> JWT.decode(INVALID_TOKEN)).thenThrow(jwtDecodeException); + + exceptionUtilsMock.when(() -> ExceptionUtils.getStackTrace(jwtDecodeException)) + .thenReturn("\n\tat com.auth0.jwt.JWT.decode(JWT.java:123)"); + + boolean result = TokenHandlerUtil.isValidBearerToken(INVALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertFalse(result); + } + } + + /** + * Tests token validation when generic Exception occurs. + * Verifies that the method handles unexpected exceptions gracefully during token + * validation and returns false for any runtime errors. + */ + @Test + void isValidBearerTokenWithGenericExceptionShouldReturnFalse() { + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class); + MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + RuntimeException genericException = new RuntimeException("Generic error"); + jwtMock.when(() -> JWT.decode(INVALID_TOKEN)).thenThrow(genericException); + + exceptionUtilsMock.when(() -> ExceptionUtils.getStackTrace(genericException)) + .thenReturn("\n\tat io.mosip.print.util.TokenHandlerUtil.isValidBearerToken(TokenHandlerUtil.java:45)"); + + boolean result = TokenHandlerUtil.isValidBearerToken(INVALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertFalse(result); + } + } + + /** + * Tests token validation with null token parameter. + * Verifies that the method handles null token input gracefully and returns false + * when attempting to validate a null token. + */ + @Test + void isValidBearerTokenWithNullTokenShouldReturnFalse() { + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class); + MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + IllegalArgumentException nullException = new IllegalArgumentException("Token cannot be null"); + jwtMock.when(() -> JWT.decode(null)).thenThrow(nullException); + + exceptionUtilsMock.when(() -> ExceptionUtils.getStackTrace(nullException)) + .thenReturn("\n\tat com.auth0.jwt.JWT.decode(JWT.java:98)"); + + boolean result = TokenHandlerUtil.isValidBearerToken(null, ISSUER_URL, CLIENT_ID); + + assertFalse(result); + } + } + + /** + * Tests token validation with null clientId claim. + * Verifies that the method handles NullPointerException gracefully when accessing + * the clientId claim and returns false when claim processing fails. + */ + @Test + void isValidBearerTokenWithNullClientIdClaimShouldReturnFalse() { + Date futureDate = new Date(System.currentTimeMillis() + 3600000); + LocalDateTime futureTime = LocalDateTime.now().plusHours(1); + LocalDateTime currentTime = LocalDateTime.now(); + + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class); + MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + jwtMock.when(() -> JWT.decode(VALID_TOKEN)).thenReturn(mockDecodedJWT); + when(mockDecodedJWT.getClaims()).thenReturn(mockClaims); + when(mockDecodedJWT.getExpiresAt()).thenReturn(futureDate); + when(mockDecodedJWT.getIssuer()).thenReturn(ISSUER_URL); + when(mockClaim.asString()).thenThrow(new NullPointerException("Claim is null")); + + dateUtilsMock.when(() -> DateUtils.getUTCTimeFromDate(futureDate)) + .thenReturn("2025-08-03T11:30:45.123Z"); + dateUtilsMock.when(() -> DateUtils.convertUTCToLocalDateTime("2025-08-03T11:30:45.123Z")) + .thenReturn(futureTime); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTime()) + .thenReturn(currentTime); + dateUtilsMock.when(() -> DateUtils.before(currentTime, futureTime)) + .thenReturn(true); + + NullPointerException npe = new NullPointerException("Claim is null"); + exceptionUtilsMock.when(() -> ExceptionUtils.getStackTrace(any(NullPointerException.class))) + .thenReturn("\n\tat io.mosip.print.util.TokenHandlerUtil.isValidBearerToken(TokenHandlerUtil.java:52)"); + + boolean result = TokenHandlerUtil.isValidBearerToken(VALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertFalse(result); + } + } + + /** + * Tests token validation with all conditions passing validation chain. + * Verifies that the method correctly validates tokens when all conditions including + * issuer, expiration, and client ID are valid and meet the requirements. + */ + @Test + void isValidBearerTokenWithAllValidConditionsShouldReturnTrue() { + Date futureDate = new Date(System.currentTimeMillis() + 7200000); // +2 hours + LocalDateTime futureTime = LocalDateTime.now().plusHours(2); + LocalDateTime currentTime = LocalDateTime.now(); + + try (MockedStatic jwtMock = mockStatic(JWT.class); + MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + MockedStatic loggerFactoryMock = mockStatic(LoggerFactory.class)) { + + Logger mockLogger = mock(Logger.class); + loggerFactoryMock.when(() -> LoggerFactory.getLogger(TokenHandlerUtil.class)) + .thenReturn(mockLogger); + + jwtMock.when(() -> JWT.decode(VALID_TOKEN)).thenReturn(mockDecodedJWT); + when(mockDecodedJWT.getClaims()).thenReturn(mockClaims); + when(mockDecodedJWT.getExpiresAt()).thenReturn(futureDate); + when(mockDecodedJWT.getIssuer()).thenReturn(ISSUER_URL); + when(mockClaim.asString()).thenReturn(CLIENT_ID); + + dateUtilsMock.when(() -> DateUtils.getUTCTimeFromDate(futureDate)) + .thenReturn("2025-08-03T12:30:45.123Z"); + dateUtilsMock.when(() -> DateUtils.convertUTCToLocalDateTime("2025-08-03T12:30:45.123Z")) + .thenReturn(futureTime); + dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTime()) + .thenReturn(currentTime); + dateUtilsMock.when(() -> DateUtils.before(currentTime, futureTime)) + .thenReturn(true); + + boolean result = TokenHandlerUtil.isValidBearerToken(VALID_TOKEN, ISSUER_URL, CLIENT_ID); + + assertTrue(result); + } + } + + /** + * Tests private constructor accessibility for code coverage. + * Verifies that the private constructor can be accessed via reflection + * and creates a valid instance of TokenHandlerUtil. + */ + @Test + void privateConstructorShouldCreateInstance() throws Exception { + java.lang.reflect.Constructor constructor = + TokenHandlerUtil.class.getDeclaredConstructor(); + constructor.setAccessible(true); + TokenHandlerUtil instance = constructor.newInstance(); + assertNotNull(instance); + } +} diff --git a/src/test/java/io/mosip/print/test/util/UinCardGeneratorImplTest.java b/src/test/java/io/mosip/print/util/UinCardGeneratorImplTest.java similarity index 97% rename from src/test/java/io/mosip/print/test/util/UinCardGeneratorImplTest.java rename to src/test/java/io/mosip/print/util/UinCardGeneratorImplTest.java index 762fa770..df96e985 100644 --- a/src/test/java/io/mosip/print/test/util/UinCardGeneratorImplTest.java +++ b/src/test/java/io/mosip/print/util/UinCardGeneratorImplTest.java @@ -1,4 +1,4 @@ -package io.mosip.print.test.util; +package io.mosip.print.util; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -23,7 +23,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.env.Environment; import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.util.ReflectionTestUtils; import io.mosip.print.constant.UinCardType; @@ -36,7 +35,7 @@ import io.mosip.print.service.PrintRestClientService; import io.mosip.print.service.impl.UinCardGeneratorImpl; import io.mosip.print.spi.PDFGenerator; -import io.mosip.print.test.TestBootApplication; +import io.mosip.print.TestBootApplication; @SpringBootTest(classes = TestBootApplication.class) @RunWith(MockitoJUnitRunner.class) diff --git a/src/test/java/io/mosip/print/util/UtilitiesTest.java b/src/test/java/io/mosip/print/util/UtilitiesTest.java new file mode 100644 index 00000000..4d927450 --- /dev/null +++ b/src/test/java/io/mosip/print/util/UtilitiesTest.java @@ -0,0 +1,536 @@ +package io.mosip.print.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import org.json.simple.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.print.constant.ApiName; +import io.mosip.print.dto.ErrorDTO; +import io.mosip.print.exception.ExceptionUtils; +import io.mosip.print.exception.IdRepoAppException; +import io.mosip.print.idrepo.dto.IdResponseDTO1; +import io.mosip.print.idrepo.dto.ResponseDTO; +import io.mosip.print.logger.PrintLogger; +import io.mosip.print.service.PrintRestClientService; + +/** + * Unit tests for {@link Utilities} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the Utilities class, + * including JSON retrieval from external sources, identity mapping operations, IdRepo data access, + * UIN retrieval, configuration file processing, and various exception handling scenarios for utility operations.

+ * + */ +@ExtendWith(MockitoExtension.class) +class UtilitiesTest { + + @Mock + private ObjectMapper objMapper; + + @Mock + private RestApiClient restApiClient; + + @Mock + private PrintRestClientService restClientService; + + @InjectMocks + private Utilities utilities; + + private static final String CONFIG_SERVER_URL = "http://config-server/"; + private static final String URI_PATH = "/identity/mapping.json"; + private static final String JSON_RESPONSE = "{\"identity\":{\"name\":{\"value\":\"fullName\"}}}"; + private static final String UIN = "1234567890"; + private static final String REG_ID = "REG123456789"; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the Utilities instance with configuration URLs and file paths + * required for JSON processing, identity mapping, and external service communication. + */ + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(utilities, "configServerFileStorageURL", CONFIG_SERVER_URL); + ReflectionTestUtils.setField(utilities, "getRegProcessorIdentityJson", "/identity.json"); + ReflectionTestUtils.setField(utilities, "getRegProcessorDemographicIdentity", "/demographic.json"); + ReflectionTestUtils.setField(utilities, "registrationProcessorPrintTextFile", "/printtext.json"); + } + + /** + * Tests successful JSON retrieval from external API. + * Verifies that the method correctly retrieves JSON data from a given URL + * and returns the expected JSON response content. + */ + @Test + void getJsonShouldSucceedWithValidUrlAndPath() throws Exception { + when(restApiClient.getApi(any(URI.class), eq(String.class))).thenReturn(JSON_RESPONSE); + + String result = utilities.getJson(CONFIG_SERVER_URL, URI_PATH); + + assertNotNull(result); + assertEquals(JSON_RESPONSE, result); + } + + /** + * Tests JSON retrieval when an exception occurs during API call. + * Verifies that the method handles exceptions gracefully and returns null + * when the external API call fails. + */ + @Test + void getJsonWithExceptionShouldReturnNull() throws Exception { + try (MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class); + MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { + + printLoggerMock.when(() -> PrintLogger.getLogger(Utilities.class)) + .thenReturn(mock(org.slf4j.Logger.class)); + exceptionUtilsMock.when(() -> ExceptionUtils.getStackTrace(any(Exception.class))) + .thenReturn("Stack trace"); + + when(restApiClient.getApi(any(URI.class), eq(String.class))) + .thenThrow(new RuntimeException("API Error")); + + String result = utilities.getJson(CONFIG_SERVER_URL, URI_PATH); + + assertNull(result); + } + } + + /** + * Tests getPrintTextFileJson method when cached value already exists. + * Verifies that the method returns the cached JSON string without making + * an external API call when data is already available. + */ + @Test + void getPrintTextFileJsonWithExistingValueShouldReturnCachedData() throws Exception { + ReflectionTestUtils.setField(utilities, "printTextFileJsonString", JSON_RESPONSE); + + String result = utilities.getPrintTextFileJson(CONFIG_SERVER_URL, URI_PATH); + + assertEquals(JSON_RESPONSE, result); + } + + /** + * Tests getPrintTextFileJson method when no cached value exists. + * Verifies that the method fetches JSON data from external API when + * no cached data is available and returns the retrieved content. + */ + @Test + void getPrintTextFileJsonWithoutExistingValueShouldFetchFromApi() throws Exception { + ReflectionTestUtils.setField(utilities, "printTextFileJsonString", null); + when(restApiClient.getApi(any(URI.class), eq(String.class))).thenReturn(JSON_RESPONSE); + + String result = utilities.getPrintTextFileJson(CONFIG_SERVER_URL, URI_PATH); + + assertEquals(JSON_RESPONSE, result); + } + + /** + * Tests getPrintTextFileJson method when cached value is empty. + * Verifies that the method fetches fresh JSON data from external API + * when the cached value is an empty string. + */ + @Test + void getPrintTextFileJsonWithEmptyExistingValueShouldFetchFromApi() throws Exception { + ReflectionTestUtils.setField(utilities, "printTextFileJsonString", ""); + when(restApiClient.getApi(any(URI.class), eq(String.class))).thenReturn(JSON_RESPONSE); + + String result = utilities.getPrintTextFileJson(CONFIG_SERVER_URL, URI_PATH); + + assertEquals(JSON_RESPONSE, result); + } + + /** + * Tests getIdentityMappingJson method when cached value already exists. + * Verifies that the method returns the cached identity mapping JSON string + * without making an external API call when data is already available. + */ + @Test + void getIdentityMappingJsonWithExistingValueShouldReturnCachedData() throws Exception { + ReflectionTestUtils.setField(utilities, "identityMappingJsonString", JSON_RESPONSE); + + String result = utilities.getIdentityMappingJson(CONFIG_SERVER_URL, URI_PATH); + + assertEquals(JSON_RESPONSE, result); + } + + /** + * Tests getIdentityMappingJson method when no cached value exists. + * Verifies that the method fetches identity mapping JSON data from external API + * when no cached data is available and returns the retrieved content. + */ + @Test + void getIdentityMappingJsonWithoutExistingValueShouldFetchFromApi() throws Exception { + ReflectionTestUtils.setField(utilities, "identityMappingJsonString", null); + when(restApiClient.getApi(any(URI.class), eq(String.class))).thenReturn(JSON_RESPONSE); + + String result = utilities.getIdentityMappingJson(CONFIG_SERVER_URL, URI_PATH); + + assertEquals(JSON_RESPONSE, result); + } + + /** + * Tests getIdentityMappingJson method when cached value is empty. + * Verifies that the method fetches fresh identity mapping JSON data from external API + * when the cached value is an empty string. + */ + @Test + void getIdentityMappingJsonWithEmptyExistingValueShouldFetchFromApi() throws Exception { + ReflectionTestUtils.setField(utilities, "identityMappingJsonString", ""); + when(restApiClient.getApi(any(URI.class), eq(String.class))).thenReturn(JSON_RESPONSE); + + String result = utilities.getIdentityMappingJson(CONFIG_SERVER_URL, URI_PATH); + + assertEquals(JSON_RESPONSE, result); + } + + /** + * Tests successful IdRepo JSON retrieval operation. + * Verifies that the method correctly retrieves identity data from IdRepo service + * using UIN and returns a properly formatted JSONObject. + */ + @Test + void retrieveIdrepoJsonShouldSucceedWithValidUin() throws Exception { + IdResponseDTO1 idResponseDto = createValidIdResponseDto(); + JSONObject expectedJson = new JSONObject(); + expectedJson.put("name", "John Doe"); + + when(restClientService.getApi(eq(ApiName.IDREPOGETIDBYUIN), any(List.class), + anyString(), anyString(), eq(IdResponseDTO1.class))).thenReturn(idResponseDto); + when(objMapper.writeValueAsString(any())).thenReturn("{\"name\":\"John Doe\"}"); + + JSONObject result = utilities.retrieveIdrepoJson(UIN); + + assertNotNull(result); + } + + /** + * Tests IdRepo JSON retrieval with null UIN parameter. + * Verifies that the method handles null UIN input gracefully + * and returns null without attempting API calls. + */ + @Test + void retrieveIdrepoJsonWithNullUinShouldReturnNull() throws Exception { + JSONObject result = utilities.retrieveIdrepoJson(null); + + assertNull(result); + } + + /** + * Tests IdRepo JSON retrieval when API returns null response. + * Verifies that the method handles null API responses gracefully + * and returns null when no data is available from IdRepo service. + */ + @Test + void retrieveIdrepoJsonWithNullResponseShouldReturnNull() throws Exception { + when(restClientService.getApi(eq(ApiName.IDREPOGETIDBYUIN), any(List.class), + anyString(), anyString(), eq(IdResponseDTO1.class))).thenReturn(null); + + JSONObject result = utilities.retrieveIdrepoJson(UIN); + + assertNull(result); + } + + /** + * Tests IdRepo JSON retrieval when response contains errors. + * Verifies that the method throws IdRepoAppException when the API response + * contains error information indicating operation failure. + */ + @Test + void retrieveIdrepoJsonWithErrorsShouldThrowIdRepoAppException() throws Exception { + IdResponseDTO1 idResponseDto = createIdResponseDtoWithErrors(); + + when(restClientService.getApi(eq(ApiName.IDREPOGETIDBYUIN), any(List.class), + anyString(), anyString(), eq(IdResponseDTO1.class))).thenReturn(idResponseDto); + + assertThrows(IdRepoAppException.class, () -> utilities.retrieveIdrepoJson(UIN)); + } + + /** + * Tests IdRepo JSON retrieval when JSON parsing exception occurs. + * Verifies that the method throws IdRepoAppException when JSON parsing + * fails during response processing operations. + */ + @Test + void retrieveIdrepoJsonWithParseExceptionShouldThrowIdRepoAppException() throws Exception { + IdResponseDTO1 idResponseDto = createValidIdResponseDto(); + + try (MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class)) { + exceptionUtilsMock.when(() -> ExceptionUtils.getStackTrace(any(Exception.class))) + .thenReturn("Stack trace"); + + when(restClientService.getApi(eq(ApiName.IDREPOGETIDBYUIN), any(List.class), + anyString(), anyString(), eq(IdResponseDTO1.class))).thenReturn(idResponseDto); + when(objMapper.writeValueAsString(any())).thenReturn("invalid-json{"); + + assertThrows(IdRepoAppException.class, () -> utilities.retrieveIdrepoJson(UIN)); + } + } + + /** + * Tests successful registration processor mapping JSON retrieval. + * Verifies that the method correctly retrieves and processes registration processor + * mapping configuration from external sources when no cached data exists. + */ + @Test + void getRegistrationProcessorMappingJsonShouldSucceedWithFreshData() throws Exception { + ReflectionTestUtils.setField(utilities, "mappingJsonString", null); + + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JSONObject expectedJson = new JSONObject(); + expectedJson.put("name", "value"); + + lenient().when(restApiClient.getApi(any(URI.class), eq(String.class))).thenReturn(JSON_RESPONSE); + lenient().when(objMapper.readValue(anyString(), eq(JSONObject.class))).thenReturn(new JSONObject()); + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(expectedJson); + + JSONObject result = utilities.getRegistrationProcessorMappingJson(); + + assertNotNull(result); + } + } + + /** + * Tests registration processor mapping JSON retrieval with existing cached value. + * Verifies that the method correctly processes cached mapping JSON data + * without making external API calls when data is already available. + */ + @Test + void getRegistrationProcessorMappingJsonWithExistingValueShouldUseCachedData() throws Exception { + ReflectionTestUtils.setField(utilities, "mappingJsonString", JSON_RESPONSE); + + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JSONObject expectedJson = new JSONObject(); + expectedJson.put("name", "value"); + + lenient().when(objMapper.readValue(anyString(), eq(JSONObject.class))).thenReturn(new JSONObject()); + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(expectedJson); + + JSONObject result = utilities.getRegistrationProcessorMappingJson(); + + assertNotNull(result); + } + } + + /** + * Tests getMappingJsonValue method with LinkedHashMap data structure. + * Verifies that the method correctly extracts values from nested LinkedHashMap + * structure within the mapping JSON configuration. + */ + @Test + void getMappingJsonValueWithLinkedHashMapShouldExtractValue() throws Exception { + ReflectionTestUtils.setField(utilities, "mappingJsonString", JSON_RESPONSE); + + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JSONObject mappingJson = new JSONObject(); + LinkedHashMap valueMap = new LinkedHashMap<>(); + valueMap.put("value", "testValue"); + mappingJson.put("testKey", valueMap); + + lenient().when(objMapper.readValue(anyString(), eq(JSONObject.class))).thenReturn(new JSONObject()); + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(mappingJson); + + String result = utilities.getMappingJsonValue("testKey"); + + assertEquals("testValue", result); + } + } + + /** + * Tests getMappingJsonValue method with LinkedHashMap containing null value. + * Verifies that the method handles null values gracefully within LinkedHashMap + * structure and returns null appropriately. + */ + @Test + void getMappingJsonValueWithLinkedHashMapNullValueShouldReturnNull() throws Exception { + ReflectionTestUtils.setField(utilities, "mappingJsonString", JSON_RESPONSE); + + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JSONObject mappingJson = new JSONObject(); + LinkedHashMap valueMap = new LinkedHashMap<>(); + valueMap.put("value", null); + mappingJson.put("testKey", valueMap); + + lenient().when(objMapper.readValue(anyString(), eq(JSONObject.class))).thenReturn(new JSONObject()); + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(mappingJson); + + String result = utilities.getMappingJsonValue("testKey"); + + assertNull(result); + } + } + + /** + * Tests getMappingJsonValue method with direct string value. + * Verifies that the method correctly handles direct string values in mapping JSON + * without nested data structure processing. + */ + @Test + void getMappingJsonValueWithDirectStringValueShouldReturnValue() throws Exception { + ReflectionTestUtils.setField(utilities, "mappingJsonString", JSON_RESPONSE); + + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JSONObject mappingJson = new JSONObject(); + mappingJson.put("testKey", "directValue"); + + lenient().when(objMapper.readValue(anyString(), eq(JSONObject.class))).thenReturn(new JSONObject()); + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(mappingJson); + + String result = utilities.getMappingJsonValue("testKey"); + + assertEquals("directValue", result); + } + } + + /** + * Tests getMappingJsonValue method with null value in mapping JSON. + * Verifies that the method handles null values appropriately in mapping JSON + * and returns null without causing exceptions. + */ + @Test + void getMappingJsonValueWithNullValueShouldReturnNull() throws Exception { + ReflectionTestUtils.setField(utilities, "mappingJsonString", JSON_RESPONSE); + + try (MockedStatic jsonUtilMock = mockStatic(JsonUtil.class)) { + JSONObject mappingJson = new JSONObject(); + mappingJson.put("testKey", null); + + lenient().when(objMapper.readValue(anyString(), eq(JSONObject.class))).thenReturn(new JSONObject()); + jsonUtilMock.when(() -> JsonUtil.getJSONObject(any(JSONObject.class), anyString())) + .thenReturn(mappingJson); + + String result = utilities.getMappingJsonValue("testKey"); + + assertNull(result); + } + } + + /** + * Tests successful UIN retrieval from registration ID. + * Verifies that the method correctly retrieves UIN data from registration service + * using registration ID and returns a properly formatted JSONObject. + */ + @Test + void retrieveUinShouldSucceedWithValidRegistrationId() throws Exception { + IdResponseDTO1 idResponseDto = createValidIdResponseDto(); + JSONObject expectedJson = new JSONObject(); + expectedJson.put("uin", UIN); + + when(restClientService.getApi(eq(ApiName.RETRIEVEIDENTITYFROMRID), any(List.class), + anyString(), anyString(), eq(IdResponseDTO1.class))).thenReturn(idResponseDto); + when(objMapper.writeValueAsString(any())).thenReturn("{\"uin\":\"1234567890\"}"); + + JSONObject result = utilities.retrieveUIN(REG_ID); + + assertNotNull(result); + } + + /** + * Tests UIN retrieval with null registration ID parameter. + * Verifies that the method handles null registration ID input gracefully + * and returns null without attempting API calls. + */ + @Test + void retrieveUinWithNullRegIdShouldReturnNull() throws Exception { + JSONObject result = utilities.retrieveUIN(null); + + assertNull(result); + } + + /** + * Tests UIN retrieval when response contains errors. + * Verifies that the method throws IdRepoAppException when the API response + * contains error information indicating UIN retrieval failure. + */ + @Test + void retrieveUinWithErrorsShouldThrowIdRepoAppException() throws Exception { + IdResponseDTO1 idResponseDto = createIdResponseDtoWithErrors(); + + when(restClientService.getApi(eq(ApiName.RETRIEVEIDENTITYFROMRID), any(List.class), + anyString(), anyString(), eq(IdResponseDTO1.class))).thenReturn(idResponseDto); + + assertThrows(IdRepoAppException.class, () -> utilities.retrieveUIN(REG_ID)); + } + + /** + * Tests UIN retrieval when JSON parsing exception occurs. + * Verifies that the method throws IdRepoAppException when JSON parsing + * fails during UIN retrieval response processing. + */ + @Test + void retrieveUinWithParseExceptionShouldThrowIdRepoAppException() throws Exception { + IdResponseDTO1 idResponseDto = createValidIdResponseDto(); + + try (MockedStatic exceptionUtilsMock = mockStatic(ExceptionUtils.class)) { + exceptionUtilsMock.when(() -> ExceptionUtils.getStackTrace(any(Exception.class))) + .thenReturn("Stack trace"); + + when(restClientService.getApi(eq(ApiName.RETRIEVEIDENTITYFROMRID), any(List.class), + anyString(), anyString(), eq(IdResponseDTO1.class))).thenReturn(idResponseDto); + when(objMapper.writeValueAsString(any())).thenReturn("invalid-json{"); + + assertThrows(IdRepoAppException.class, () -> utilities.retrieveUIN(REG_ID)); + } + } + + /** + * Helper method to create valid IdResponseDTO1 for testing purposes. + * Creates a mock response object with proper identity data and empty error list + * for successful API response simulation. + * + * @return IdResponseDTO1 with valid response data + */ + private IdResponseDTO1 createValidIdResponseDto() { + IdResponseDTO1 idResponseDto = new IdResponseDTO1(); + ResponseDTO responseDto = new ResponseDTO(); + responseDto.setIdentity(new Object()); + idResponseDto.setResponse(responseDto); + idResponseDto.setErrors(new ArrayList<>()); + return idResponseDto; + } + + /** + * Helper method to create IdResponseDTO1 with error information for testing. + * Creates a mock response object containing error data to simulate + * API failure scenarios and error handling. + * + * @return IdResponseDTO1 with error information + */ + private IdResponseDTO1 createIdResponseDtoWithErrors() { + IdResponseDTO1 idResponseDto = new IdResponseDTO1(); + List errors = new ArrayList<>(); + ErrorDTO error = new ErrorDTO(); + error.setMessage("Test error message"); + errors.add(error); + idResponseDto.setErrors(errors); + return idResponseDto; + } +} diff --git a/src/test/java/io/mosip/print/util/WebSubSubscriptionHelperTest.java b/src/test/java/io/mosip/print/util/WebSubSubscriptionHelperTest.java new file mode 100644 index 00000000..b80cd7d7 --- /dev/null +++ b/src/test/java/io/mosip/print/util/WebSubSubscriptionHelperTest.java @@ -0,0 +1,251 @@ +package io.mosip.print.util; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.slf4j.Logger; +import org.springframework.http.HttpHeaders; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import io.mosip.kernel.core.websub.spi.PublisherClient; +import io.mosip.kernel.core.websub.spi.SubscriptionClient; +import io.mosip.kernel.websub.api.exception.WebSubClientException; +import io.mosip.kernel.websub.api.model.SubscriptionChangeRequest; +import io.mosip.kernel.websub.api.model.SubscriptionChangeResponse; +import io.mosip.kernel.websub.api.model.UnsubscriptionRequest; +import io.mosip.print.model.CredentialStatusEvent; + +/** + * Unit tests for {@link WebSubSubscriptionHelper} class. + * + *

This class contains comprehensive test cases for verifying the functionality of the WebSubSubscriptionHelper class, + * including WebSub subscription initialization, event publishing operations, exception handling scenarios, + * configuration management, and various edge cases for WebSub subscription and publishing operations.

+ * + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class WebSubSubscriptionHelperTest { + + @Mock + private SubscriptionClient sb; + + @Mock + private PublisherClient pb; + + @Mock + private RestTemplate restTemplate; + + @Mock + private Logger mockLogger; + + @InjectMocks + private WebSubSubscriptionHelper webSubSubscriptionHelper; + + private static final String WEB_SUB_HUB_URL = "http://websub-hub.test.com"; + private static final String WEB_SUB_SECRET = "test-secret"; + private static final String CALL_BACK_URL = "http://callback.test.com"; + private static final String TOPIC = "test-topic"; + + /** + * Sets up test fixtures before each test method execution. + * Initializes the WebSubSubscriptionHelper instance with WebSub configuration parameters + * including hub URL, secret, callback URL, topic, and logger for comprehensive testing. + */ + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(webSubSubscriptionHelper, "webSubHubUrl", WEB_SUB_HUB_URL); + ReflectionTestUtils.setField(webSubSubscriptionHelper, "webSubSecret", WEB_SUB_SECRET); + ReflectionTestUtils.setField(webSubSubscriptionHelper, "callBackUrl", CALL_BACK_URL); + ReflectionTestUtils.setField(webSubSubscriptionHelper, "topic", TOPIC); + ReflectionTestUtils.setField(webSubSubscriptionHelper, "LOGGER", mockLogger); + } + + /** + * Tests successful WebSub subscription initialization. + * Verifies that the method correctly initializes subscriptions with proper request parameters + * and logs appropriate information messages during the subscription process. + */ + @Test + void initSubscriptionsShouldSucceedWithValidConfiguration() throws Exception { + when(sb.subscribe(any(SubscriptionChangeRequest.class))).thenReturn(mock(SubscriptionChangeResponse.class)); + + webSubSubscriptionHelper.initSubsriptions(); + + verify(sb).subscribe(any(SubscriptionChangeRequest.class)); + verify(mockLogger).info(eq("Initializing subscribptions... {} {}"), + eq("WebSubSubscriptionHelper"), eq("initSubsriptions")); + verify(mockLogger).info(eq("subscription request : {}"), any(SubscriptionChangeRequest.class)); + } + + /** + * Tests subscription initialization when WebSubClientException occurs. + * Verifies that the method handles WebSubClientException gracefully during subscription + * and logs appropriate error messages without propagating the exception. + */ + @Test + void initSubscriptionsWithWebSubClientExceptionShouldHandleGracefully() throws Exception { + doThrow(new WebSubClientException("ERR-001", "Subscription failed")) + .when(sb).subscribe(any(SubscriptionChangeRequest.class)); + + webSubSubscriptionHelper.initSubsriptions(); + + verify(sb).subscribe(any(SubscriptionChangeRequest.class)); + verify(mockLogger).info(eq("Initializing subscribptions... {} {}"), + eq("WebSubSubscriptionHelper"), eq("initSubsriptions")); + verify(mockLogger).info(eq("subscription request : {}"), any(SubscriptionChangeRequest.class)); + verify(mockLogger).info(eq("websub subscription error {} {}"), + eq("WebSubSubscriptionHelper"), eq("initSubsriptions")); + } + + /** + * Tests successful print status update event publishing. + * Verifies that the method correctly publishes credential status events to the specified topic + * with proper headers and WebSub hub URL configuration. + */ + @Test + void printStatusUpdateEventShouldSucceedWithValidParameters() throws Exception { + String testTopic = "print-status-topic"; + CredentialStatusEvent credentialStatusEvent = new CredentialStatusEvent(); + + webSubSubscriptionHelper.printStatusUpdateEvent(testTopic, credentialStatusEvent); + + verify(pb).publishUpdate(eq(testTopic), eq(credentialStatusEvent), + anyString(), any(HttpHeaders.class), eq(WEB_SUB_HUB_URL)); + } + + /** + * Tests print status update event publishing when WebSubClientException occurs. + * Verifies that the method handles WebSubClientException gracefully during publishing + * and logs appropriate error messages for troubleshooting. + */ + @Test + void printStatusUpdateEventWithWebSubClientExceptionShouldHandleGracefully() throws Exception { + String testTopic = "print-status-topic"; + CredentialStatusEvent credentialStatusEvent = new CredentialStatusEvent(); + + doThrow(new WebSubClientException("ERR-002", "Publish failed")) + .when(pb).publishUpdate(anyString(), any(CredentialStatusEvent.class), + anyString(), any(HttpHeaders.class), anyString()); + + webSubSubscriptionHelper.printStatusUpdateEvent(testTopic, credentialStatusEvent); + + verify(pb).publishUpdate(eq(testTopic), eq(credentialStatusEvent), + anyString(), any(HttpHeaders.class), eq(WEB_SUB_HUB_URL)); + verify(mockLogger).info(eq("websub publish update error {} {}"), + eq("WebSubSubscriptionHelper"), eq("initSubsriptions")); + } + + /** + * Tests print status update event publishing with null topic parameter. + * Verifies that the method handles null topic gracefully and still attempts + * to publish the event without causing exceptions. + */ + @Test + void printStatusUpdateEventWithNullTopicShouldHandleGracefully() throws Exception { + CredentialStatusEvent credentialStatusEvent = new CredentialStatusEvent(); + + webSubSubscriptionHelper.printStatusUpdateEvent(null, credentialStatusEvent); + + verify(pb).publishUpdate(eq(null), eq(credentialStatusEvent), + anyString(), any(HttpHeaders.class), eq(WEB_SUB_HUB_URL)); + } + + /** + * Tests print status update event publishing with null credential status event. + * Verifies that the method handles null credential status event gracefully + * and still attempts the publishing operation. + */ + @Test + void printStatusUpdateEventWithNullCredentialStatusEventShouldHandleGracefully() throws Exception { + String testTopic = "print-status-topic"; + + webSubSubscriptionHelper.printStatusUpdateEvent(testTopic, null); + + verify(pb).publishUpdate(eq(testTopic), eq(null), + anyString(), any(HttpHeaders.class), eq(WEB_SUB_HUB_URL)); + } + + /** + * Tests scheduled method annotation functionality. + * Verifies that the scheduled subscription initialization method executes correctly + * and logs appropriate initialization messages. + */ + @Test + void scheduledMethodExecutionShouldInitializeCorrectly() throws Exception { + when(sb.subscribe(any(SubscriptionChangeRequest.class))).thenReturn(mock(SubscriptionChangeResponse.class)); + + webSubSubscriptionHelper.initSubsriptions(); + + verify(mockLogger).info(eq("Initializing subscribptions... {} {}"), + eq("WebSubSubscriptionHelper"), eq("initSubsriptions")); + } + + /** + * Tests subscription functionality with different configuration values. + * Verifies that the method works correctly when WebSub configuration parameters + * are changed including hub URL, secret, callback URL, and topic. + */ + @Test + void subscriptionWithDifferentConfigurationShouldWork() throws Exception { + ReflectionTestUtils.setField(webSubSubscriptionHelper, "webSubHubUrl", "http://different-hub.com"); + ReflectionTestUtils.setField(webSubSubscriptionHelper, "webSubSecret", "different-secret"); + ReflectionTestUtils.setField(webSubSubscriptionHelper, "callBackUrl", "http://different-callback.com"); + ReflectionTestUtils.setField(webSubSubscriptionHelper, "topic", "different-topic"); + + when(sb.subscribe(any(SubscriptionChangeRequest.class))).thenReturn(mock(SubscriptionChangeResponse.class)); + + webSubSubscriptionHelper.initSubsriptions(); + + verify(sb).subscribe(any(SubscriptionChangeRequest.class)); + verify(mockLogger).info(eq("subscription request : {}"), any(SubscriptionChangeRequest.class)); + } + + /** + * Tests HttpHeaders creation in printStatusUpdateEvent method. + * Verifies that the method correctly creates and passes HttpHeaders during + * the event publishing process to the WebSub hub. + */ + @Test + void printStatusUpdateEventHttpHeadersCreationShouldWork() throws Exception { + String testTopic = "test-topic"; + CredentialStatusEvent credentialStatusEvent = new CredentialStatusEvent(); + + webSubSubscriptionHelper.printStatusUpdateEvent(testTopic, credentialStatusEvent); + + verify(pb).publishUpdate(eq(testTopic), eq(credentialStatusEvent), + anyString(), any(HttpHeaders.class), eq(WEB_SUB_HUB_URL)); + } + + /** + * Tests multiple subscription attempts with mixed success and failure scenarios. + * Verifies that the method handles multiple subscription attempts correctly, + * processing both successful and failed subscription operations appropriately. + */ + @Test + void multipleSubscriptionAttemptsShouldHandleMixedResults() throws Exception { + when(sb.subscribe(any(SubscriptionChangeRequest.class))).thenReturn(mock(SubscriptionChangeResponse.class)); + webSubSubscriptionHelper.initSubsriptions(); + + doThrow(new WebSubClientException("ERR-003", "Subscription failed")) + .when(sb).subscribe(any(SubscriptionChangeRequest.class)); + webSubSubscriptionHelper.initSubsriptions(); + + verify(mockLogger).info(eq("websub subscription error {} {}"), + eq("WebSubSubscriptionHelper"), eq("initSubsriptions")); + } +} From a78fccb4e006711fb2fdf8b33251ed6c9d54970e Mon Sep 17 00:00:00 2001 From: rajapandi1234 <138785181+rajapandi1234@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:48:43 +0530 Subject: [PATCH 51/71] Rename NOTICES.txt to NOTICE Signed-off-by: rajapandi1234 <138785181+rajapandi1234@users.noreply.github.com> --- NOTICES.txt => NOTICE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename NOTICES.txt => NOTICE (100%) diff --git a/NOTICES.txt b/NOTICE similarity index 100% rename from NOTICES.txt rename to NOTICE From bae3edbb5308cec50d2b3393e0f0a2a163cc1402 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Thu, 11 Dec 2025 16:31:46 +0530 Subject: [PATCH 52/71] Update Apache-2.0.txt Signed-off-by: Rakshithasai123 --- licenses /Apache-2.0.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/licenses /Apache-2.0.txt b/licenses /Apache-2.0.txt index fe5d55c6..85915ae0 100644 --- a/licenses /Apache-2.0.txt +++ b/licenses /Apache-2.0.txt @@ -174,3 +174,27 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Copyright 2004-2018 The Apache Software Foundation + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. From 012b81abf34144b922686ea1a034f91fb8bb29a9 Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Thu, 11 Dec 2025 17:24:42 +0530 Subject: [PATCH 53/71] Update Apache-2.0.txt Signed-off-by: Rakshithasai123 --- licenses /Apache-2.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licenses /Apache-2.0.txt b/licenses /Apache-2.0.txt index 85915ae0..acd38f08 100644 --- a/licenses /Apache-2.0.txt +++ b/licenses /Apache-2.0.txt @@ -191,7 +191,7 @@ You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -Copyright 2004-2018 The Apache Software Foundation +Copyright © 2004 The Apache Software Foundation Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, From d5940fc5875243fc710f8bda180ee1f71fcef09f Mon Sep 17 00:00:00 2001 From: Rakshithasai123 Date: Fri, 12 Dec 2025 10:53:37 +0530 Subject: [PATCH 54/71] Update THIRD-PARTY-NOTICES.txt Signed-off-by: Rakshithasai123 --- THIRD-PARTY-NOTICES.txt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index b7889bfe..d3cfa5a9 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -32,20 +32,6 @@ License: Apache License 2.0 (Inferred from project’s official repository) Homepage: [https://code.google.com/archive/p/json-simple/](https://code.google.com/archive/p/json-simple/) ================================================================================ -================================================================================ -Package: Central Publishing Maven Plugin (org.sonatype.central:central-publishing-maven-plugin) -Version: 0.7.0 -License: Apache License 2.0 -Homepage: [https://github.com/sonatype-nexus-community/central-publishing](https://github.com/sonatype-nexus-community/central-publishing) -================================================================================ - -================================================================================ -Package: Git Commit ID Maven Plugin (pl.project13.maven:git-commit-id-plugin) -Version: 3.0.1 -License: GNU Lesser General Public License v3.0 (LGPL-3.0) -Homepage: https://github.com/git-commit-id/git-commit-id-maven-plugin -================================================================================ - ================================================================================ Package: Apache Maven Plugins (maven-resources-plugin, maven-shade-plugin, maven-surefire-plugin, maven-gpg-plugin, From 7f8e82dcd64a8fc8499b3f2396308527d82db3a5 Mon Sep 17 00:00:00 2001 From: Chetan Kumar Hirematha Date: Wed, 17 Dec 2025 10:38:46 +0530 Subject: [PATCH 55/71] [MOSIP-44072] : Updated README.md (#310) * [MOSIP-44072] : Updated README.md Signed-off-by: Chetan Kumar Hirematha * [MOSIP-44072] : Updated README.md Signed-off-by: Chetan Kumar Hirematha * [MOSIP-44072] : Updated README.md Signed-off-by: Chetan Kumar Hirematha * [MOSIP-44072] : Updated README.md Signed-off-by: Chetan Kumar Hirematha * Fix typo in README.md for 'Retrieves' Signed-off-by: Chetan Kumar Hirematha * Update DataShare link in README Signed-off-by: Chetan Kumar Hirematha --------- Signed-off-by: Chetan Kumar Hirematha --- README.md | 157 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 143 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 13d3ef3c..7dc45bd5 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,164 @@ -[![Maven Package upon a push](https://github.com/mosip/print/actions/workflows/push-trigger.yml/badge.svg?branch=release-1.3.x)](https://github.com/mosip/print/actions/workflows/push-trigger.yml) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?branch=release-1.3.x&project=mosip_admin-services&id=mosip_admin-services&metric=alert_status)](https://sonarcloud.io/dashboard?branch=release-1.3.x&id=mosip_admin-services) # Print Service +[![Maven Package upon a push](https://github.com/mosip/print/actions/workflows/push-trigger.yml/badge.svg?branch=release-1.3.x)](https://github.com/mosip/print/actions/workflows/push-trigger.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?branch=release-1.3.x&project=mosip_print&id=mosip_print&metric=alert_status)](https://sonarcloud.io/dashboard?branch=release-1.3.x&id=mosip_print) + ## Overview -A reference implementation to print `euin`, `reprint`, `qrcode` [credential types](https://docs.mosip.io/1.2.0/modules/id-repository#credential-types) in PDF format. This service is intended to be customized and used by a card printing agency who need to onboard onto MOSIP as [Credential Partner](https://docs.mosip.io/1.2.0/partners#credential-partner-cp) before deploying the service. + +The **Print Service** is a reference implementation in MOSIP that handles the printing of credentials such as `euin`, `reprint`, and `qrcode` [credential types](https://docs.mosip.io/1.2.0/modules/id-repository#credential-types) in PDF format. This service is designed to be customized and utilized by card printing agencies onboarding as [Credential Partners](https://docs.mosip.io/1.2.0/partners#credential-partner-cp).This reference implementation can be referred by any credential sharing request. + +It operates in an event-driven flow: +1. **Receive Event**: Listens for print request events from WebSub. +2. **Fetch Template**: Retrieves the appropriate template from Masterdata. +3. **Generate PDF**: Decrypts resident data and converts it into a PDF card using the template. +4. **Upload**: Uploads the generated PDF to [DataShare](https://docs.mosip.io/1.2.0/id-lifecycle-management/supporting-components/datashare). +5. **Notify**: Publishes a status event with the DataShare link back to WebSub. + +The flow is visualized below: ![](docs/print-service.png) -1. Receives events from WebSub. -2. Fetches templates from Masterdata. -3. After creating PDF card print service upload the same to [DataShare](https://docs.mosip.io/1.2.0/modules/data-share). -4. Publishes event to WebSub with updated status and DataShare link. +## Features + +- **Template-Based Printing**: Utilizes Velocity and iText to generate PDFs based on customizable templates. +- **Secure Data Handling**: Decrypts sensitive resident data using partner private keys (`.p12`). +- **DataShare Integration**: Securely uploads generated credential documents. +- **Credential Support**: Native support for EUIN, Reprint, and QR Code credential types. -The card data in JSON format is published as WebSub event. The print service consumes the data from event, decrypts using partner private key and converts into PDF using a predefined [template](docs/configuration.md#template). +## Services + +The Print project consists of the following service: + +1. **[Print Service](.)** (`print`) - The core Spring Boot application responsible for processing print requests and generating the credential PDFs. + +## Database +NA (The service relies on Object Store/DataShare and Masterdata; it does not maintain its own primary database). ## Build and run (for developers) Refer [Build and Run](docs/build-and-run.md). - + ## Deploy To deploy print service in production follow the given steps: 1. Onboard your organisation as [Credential Partner](https://docs.mosip.io/1.2.0/partners). 2. Place your `.p12` file in `../src/main/resources` folder. -3. Set configuration as in given [here](docs/configuation.md). +3. Set configuration as in given [here](https://github.com/mosip/mosip-config/blob/release-1.3.x/print-default.properties). 4. Build and run as given [here](docs/build-and-run.md). -## Configuration -Refer to the [configuration guide](docs/configuration.md). - ## Test Automated functional tests available in [Functional Tests repo](https://github.com/mosip/mosip-functional-tests). +## Local Setup + +The project can be set up in two ways: + +1. [Local Setup (for Development or Contribution)](#local-setup-for-development-or-contribution) +2. [Local Setup with Docker (Easy Setup for Demos)](#local-setup-with-docker-easy-setup-for-demos) + +### Prerequisites + +Before you begin, ensure you have the following installed: + +- **JDK**: 21 +- **Maven**: 3.9.6 (or compatible 3.x version) +- **Docker**: Latest stable version (optional for local run) + +### Runtime Dependencies + +Ensure the following artifacts are available in the classpath or loader path: + +- `kernel-auth-adapter.jar` - For IAM authentication. + +## Installation + +### Configuration +Print uses properties from **mosip-config**. +You can check the configuration here: [print-default.properties](https://github.com/mosip/mosip-config/blob/release-1.3.x/print-default.properties) +### Local Setup (for Development or Contribution) + +1. Ensure the **Config Server** is running and accessible.To run config server [check here.](https://github.com/mosip/mosip-config/blob/master/README.md) + +2. Clone the repository: + +```text +git clone https://github.com/mosip/print.git +cd print +``` + +3. Build the project: + +```text +mvn clean install -Dmaven.javadoc.skip=true -Dgpg.skip=true +``` + +4. Start the application: + - Run via IDE or command line: + ```text + java -Dloader.path= \ + -jar target/print-*.jar + ``` + +### Local Setup with Docker (Easy Setup for Demos) + +#### Option 1: Pull from Docker Hub + +Recommended for quick demos and testing. + +```text +docker pull mosipid/print-service:1.3.0 +``` + +Run the service: + +```text +docker run -d -p 8099:8099 --name print-service mosipid/print-service:1.3.0 +``` + +#### Option 2: Build Docker Images Locally + +Recommended for developers. + +1. Build the project (as shown in Local Setup). + +2. Build the Docker image: + +```text +docker build -t print-service:local . +``` + +3. Run the service: + +```text +docker run -d -p 8099:8099 --name print-service print-service:local +``` + +#### Verify Installation + +Check that the container is running: + +```text +docker ps +``` + +The service runs on port `8099` by default. + + + +## Documentation + +For additional details, refer to the documents listed below: + +- **[Build and Run Guide](docs/build-and-run.md)**: Detailed instructions for building and running the service. +- **[Configuration Guide](docs/configuration.md)**: Details on configuration properties and template setup. + +## Contribution & Community + +• To learn how you can contribute code to this application, [click here](https://docs.mosip.io/1.2.0/community/code-contributions). + +• If you have questions or encounter issues, visit the [MOSIP Community](https://community.mosip.io/) for support. + +• For any GitHub issues: [Report here](https://github.com/mosip/print/issues) + ## License -This project is licensed under the terms of [Mozilla Public License 2.0](LICENSE). + +This project is licensed under the [Mozilla Public License 2.0](LICENSE). From 24f02710ae54e8f724eca84cf5301e868cf4ac02 Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Mon, 22 Dec 2025 12:15:53 +0530 Subject: [PATCH 56/71] Updated chart versions, image and tag for release changes (#312) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Prafulrakhade <99539100+Prafulrakhade@users.noreply.github.com> --- deploy/install.sh | 2 +- helm/print/Chart.yaml | 2 +- helm/print/values.yaml | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 9f465cd9..13224952 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -8,7 +8,7 @@ fi NS=print -CHART_VERSION=1.3.0-develop +CHART_VERSION=1.3.0 echo Create $NS namespace kubectl create ns $NS diff --git a/helm/print/Chart.yaml b/helm/print/Chart.yaml index f1211e79..dffb19c5 100644 --- a/helm/print/Chart.yaml +++ b/helm/print/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: print-service description: A Helm chart for sample Print Service type: application -version: 1.3.0-develop +version: 1.3.0 appVersion: "" dependencies: - name: common diff --git a/helm/print/values.yaml b/helm/print/values.yaml index 06baf219..6c8dd76d 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -46,8 +46,8 @@ service: externalTrafficPolicy: Cluster image: registry: docker.io - repository: mosipqa/print - tag: 1.3.x + repository: mosipid/print + tag: 1.3.0 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images @@ -198,7 +198,6 @@ lifecycleHooks: - sh - -c - sleep 30 - ## Termination grace perios : the maximum amount of time (in seconds) Kubernetes will wait for a container to gracefully shut down terminationGracePeriodSeconds: 60 ## Custom Liveness probes for From dd8f5bebf7178603aa6b4320863ad46cb1f3b4ae Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Mon, 22 Dec 2025 12:45:53 +0530 Subject: [PATCH 57/71] Updated Pom versions for release changes (#313) Signed-off-by: GitHub Co-authored-by: Prafulrakhade --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index fb7c68a8..bf1b4ca4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.0-SNAPSHOT + 1.3.0 print https://github.com/mosip/print This is a project for MOSIP printing functionality. @@ -49,8 +49,8 @@ **/dto/**,**/config/**,**/api/** 1.4.2 2.8.4 - 1.3.0-SNAPSHOT - 1.3.0-SNAPSHOT + 1.3.0 + 1.3.0 7.1.0 2.0.0 5.5.13.3 From f2b17095dc55dcd455255768693eaed85c5ac1c6 Mon Sep 17 00:00:00 2001 From: Mahesh-Binayak <76687012+Mahesh-Binayak@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:45:33 +0530 Subject: [PATCH 58/71] [MOSIP-44867]fixing duplicate license issues (#317) * Delete licenses directory to remove duplicates and move the NOTICE * Add NOTICE file for third-party licenses Added NOTICE file with copyright and license information for third-party components used in the project. --- {licenses => licenses }/NOTICE | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {licenses => licenses }/NOTICE (100%) diff --git a/licenses/NOTICE b/licenses /NOTICE similarity index 100% rename from licenses/NOTICE rename to licenses /NOTICE From 095fd4dc4a9aa583b385fec1c112a2c452656fe4 Mon Sep 17 00:00:00 2001 From: amanbh321 Date: Thu, 16 Apr 2026 11:48:38 +0530 Subject: [PATCH 59/71] [MOSIP-44894] Renamed licences (#320) --- .../AGPL-3.0-only.txt\342\200\216" | 0 {licenses => licenses}/Apache-2.0.txt | 0 {licenses => licenses}/BSD-2.0.txt | 0 .../BSD-3-Clause.txt\342\200\216" | 0 {licenses => licenses}/EPL-1.0.txt | 0 {licenses => licenses}/EPL-2.0.txt | 0 .../JSON.txt\342\200\216" => "licenses/JSON.txt\342\200\216" | 0 {licenses => licenses}/LGPL-3.0-only.txt | 0 "licenses /MIT.txt\342\200\216" => "licenses/MIT.txt\342\200\216" | 0 .../MPL-2.0.txt\342\200\216" | 0 {licenses => licenses}/NOTICE | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename "licenses /AGPL-3.0-only.txt\342\200\216" => "licenses/AGPL-3.0-only.txt\342\200\216" (100%) rename {licenses => licenses}/Apache-2.0.txt (100%) rename {licenses => licenses}/BSD-2.0.txt (100%) rename "licenses /BSD-3-Clause.txt\342\200\216" => "licenses/BSD-3-Clause.txt\342\200\216" (100%) rename {licenses => licenses}/EPL-1.0.txt (100%) rename {licenses => licenses}/EPL-2.0.txt (100%) rename "licenses /JSON.txt\342\200\216" => "licenses/JSON.txt\342\200\216" (100%) rename {licenses => licenses}/LGPL-3.0-only.txt (100%) rename "licenses /MIT.txt\342\200\216" => "licenses/MIT.txt\342\200\216" (100%) rename "licenses /MPL-2.0.txt\342\200\216" => "licenses/MPL-2.0.txt\342\200\216" (100%) rename {licenses => licenses}/NOTICE (100%) diff --git "a/licenses /AGPL-3.0-only.txt\342\200\216" "b/licenses/AGPL-3.0-only.txt\342\200\216" similarity index 100% rename from "licenses /AGPL-3.0-only.txt\342\200\216" rename to "licenses/AGPL-3.0-only.txt\342\200\216" diff --git a/licenses /Apache-2.0.txt b/licenses/Apache-2.0.txt similarity index 100% rename from licenses /Apache-2.0.txt rename to licenses/Apache-2.0.txt diff --git a/licenses /BSD-2.0.txt b/licenses/BSD-2.0.txt similarity index 100% rename from licenses /BSD-2.0.txt rename to licenses/BSD-2.0.txt diff --git "a/licenses /BSD-3-Clause.txt\342\200\216" "b/licenses/BSD-3-Clause.txt\342\200\216" similarity index 100% rename from "licenses /BSD-3-Clause.txt\342\200\216" rename to "licenses/BSD-3-Clause.txt\342\200\216" diff --git a/licenses /EPL-1.0.txt b/licenses/EPL-1.0.txt similarity index 100% rename from licenses /EPL-1.0.txt rename to licenses/EPL-1.0.txt diff --git a/licenses /EPL-2.0.txt b/licenses/EPL-2.0.txt similarity index 100% rename from licenses /EPL-2.0.txt rename to licenses/EPL-2.0.txt diff --git "a/licenses /JSON.txt\342\200\216" "b/licenses/JSON.txt\342\200\216" similarity index 100% rename from "licenses /JSON.txt\342\200\216" rename to "licenses/JSON.txt\342\200\216" diff --git a/licenses /LGPL-3.0-only.txt b/licenses/LGPL-3.0-only.txt similarity index 100% rename from licenses /LGPL-3.0-only.txt rename to licenses/LGPL-3.0-only.txt diff --git "a/licenses /MIT.txt\342\200\216" "b/licenses/MIT.txt\342\200\216" similarity index 100% rename from "licenses /MIT.txt\342\200\216" rename to "licenses/MIT.txt\342\200\216" diff --git "a/licenses /MPL-2.0.txt\342\200\216" "b/licenses/MPL-2.0.txt\342\200\216" similarity index 100% rename from "licenses /MPL-2.0.txt\342\200\216" rename to "licenses/MPL-2.0.txt\342\200\216" diff --git a/licenses /NOTICE b/licenses/NOTICE similarity index 100% rename from licenses /NOTICE rename to licenses/NOTICE From b7baebba90d37034e71bcc7b6d45b6ea3f287ac5 Mon Sep 17 00:00:00 2001 From: kameshsr <47484458+kameshsr@users.noreply.github.com> Date: Thu, 21 May 2026 19:27:09 +0530 Subject: [PATCH 60/71] SNAPSHOT changes for licensing folder space issue fix (#326) * SNAPSHOT changes for licensing folder space issue fix Signed-off-by: kameshsr * Corrected mockito versions Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --------- Signed-off-by: kameshsr Signed-off-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> Co-authored-by: GOKULRAJ136 <110164849+GOKULRAJ136@users.noreply.github.com> --- deploy/install.sh | 2 +- helm/print/Chart.yaml | 2 +- helm/print/values.yaml | 4 ++-- pom.xml | 19 ++++++++++++++++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 13224952..2250c92a 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -8,7 +8,7 @@ fi NS=print -CHART_VERSION=1.3.0 +CHART_VERSION=1.3.1-develop echo Create $NS namespace kubectl create ns $NS diff --git a/helm/print/Chart.yaml b/helm/print/Chart.yaml index dffb19c5..308713ce 100644 --- a/helm/print/Chart.yaml +++ b/helm/print/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: print-service description: A Helm chart for sample Print Service type: application -version: 1.3.0 +version: 1.3.1-develop appVersion: "" dependencies: - name: common diff --git a/helm/print/values.yaml b/helm/print/values.yaml index 6c8dd76d..2a443cb4 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -46,8 +46,8 @@ service: externalTrafficPolicy: Cluster image: registry: docker.io - repository: mosipid/print - tag: 1.3.0 + repository: mosipqa/print + tag: 1.3.x ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images diff --git a/pom.xml b/pom.xml index bf1b4ca4..7fdb95ea 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.0 + 1.3.1-SNAPSHOT print https://github.com/mosip/print This is a project for MOSIP printing functionality. @@ -64,7 +64,7 @@ 0.7.0 - 4.11.0 + 4.11.0 5.10.2 @@ -73,6 +73,19 @@ + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + io.mosip.kernel kernel-bom @@ -278,7 +291,7 @@ org.mockito mockito-inline - ${mockito.inline.version} + ${mockito.version} test From a55f36928d2b60aa1b7d0622bc02ddbdf2726fc1 Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Wed, 27 May 2026 16:06:43 +0530 Subject: [PATCH 61/71] Updated chart versions, image and tag for release changes (#334) Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Prafulrakhade <99539100+Prafulrakhade@users.noreply.github.com> --- deploy/install.sh | 2 +- helm/print/Chart.yaml | 2 +- helm/print/values.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/install.sh b/deploy/install.sh index 2250c92a..10c46156 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -8,7 +8,7 @@ fi NS=print -CHART_VERSION=1.3.1-develop +CHART_VERSION=1.3.1 echo Create $NS namespace kubectl create ns $NS diff --git a/helm/print/Chart.yaml b/helm/print/Chart.yaml index 308713ce..832a994c 100644 --- a/helm/print/Chart.yaml +++ b/helm/print/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: print-service description: A Helm chart for sample Print Service type: application -version: 1.3.1-develop +version: 1.3.1 appVersion: "" dependencies: - name: common diff --git a/helm/print/values.yaml b/helm/print/values.yaml index 2a443cb4..d5ca3d75 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -46,8 +46,8 @@ service: externalTrafficPolicy: Cluster image: registry: docker.io - repository: mosipqa/print - tag: 1.3.x + repository: mosipid/print + tag: 1.3.1 ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images From 2f574d773f2158006a3dfeef5e5c8d1d216eabeb Mon Sep 17 00:00:00 2001 From: Chandra Keshav Mishra Date: Wed, 27 May 2026 16:13:47 +0530 Subject: [PATCH 62/71] Updated Pom versions for release changes (#335) Signed-off-by: GitHub Co-authored-by: Prafulrakhade --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7fdb95ea..517c9e54 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.1-SNAPSHOT + 1.3.1 print https://github.com/mosip/print This is a project for MOSIP printing functionality. From 7aced288c01150bfb7229c59c21d37fca9e7fef2 Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Fri, 5 Jun 2026 16:55:19 +0530 Subject: [PATCH 63/71] #1830: taking latest release changes to develop Signed-off-by: nagendra0721 --- pom.xml | 6 +++--- .../io/mosip/print/util/PrintExceptionHandlerTest.java | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index d1bffc99..ce3cd65c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.mosip.print print - 1.3.1 + 1.4.0-SNAPSHOT print https://github.com/mosip/print This is a project for MOSIP printing functionality. @@ -50,8 +50,8 @@ **/dto/**,**/config/**,**/api/** 1.4.2 2.8.4 - 1.3.0 - 1.3.0 + 1.4.0-SNAPSHOT + 1.4.0-SNAPSHOT 7.1.0 2.0.0 5.5.13.3 diff --git a/src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java b/src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java index 809d8b84..c9201620 100644 --- a/src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java +++ b/src/test/java/io/mosip/print/util/PrintExceptionHandlerTest.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import io.mosip.kernel.core.util.DateUtils2; import io.mosip.print.dto.PrintResponse; import io.mosip.print.exception.AccessDeniedException; import io.mosip.print.exception.InvalidTokenException; @@ -58,7 +59,7 @@ class PrintExceptionHandlerTest { private static final String SERVICE_ID = "mosip.print.service"; private static final String SERVICE_VERSION = "1.0"; private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; - private static final String CURRENT_DATETIME = "2025-08-03T10:00:00.000Z"; + private static final String CURRENT_DATETIME = "2026-06-05T11:19:48.774Z"; /** * Sets up test fixtures before each test method execution. @@ -80,13 +81,13 @@ void setUp() { void regPrintAppExceptionHandlerShouldReturnProperResponse() { RegPrintAppException exception = new RegPrintAppException("ERR-001", "Test error message"); - try (MockedStatic dateUtilsMock = mockStatic(DateUtils.class); + try (MockedStatic dateUtilsMock = mockStatic(DateUtils2.class); MockedStatic printLoggerMock = mockStatic(PrintLogger.class)) { Logger mockLogger = mock(Logger.class); printLoggerMock.when(() -> PrintLogger.getLogger(PrintExceptionHandler.class)) .thenReturn(mockLogger); - dateUtilsMock.when(() -> DateUtils.getUTCCurrentDateTimeString(DATETIME_PATTERN)) + dateUtilsMock.when(() -> DateUtils2.getUTCCurrentDateTimeString(DATETIME_PATTERN)) .thenReturn(CURRENT_DATETIME); ResponseEntity response = printExceptionHandler.regPrintAppException(exception); From 257d705399a5f692a6f6603a859e9c343fae5dc0 Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Fri, 5 Jun 2026 16:57:53 +0530 Subject: [PATCH 64/71] #1830: image revert to qa Signed-off-by: nagendra0721 --- helm/print/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/print/values.yaml b/helm/print/values.yaml index 1f3ca2e4..891f0eef 100644 --- a/helm/print/values.yaml +++ b/helm/print/values.yaml @@ -46,7 +46,7 @@ service: externalTrafficPolicy: Cluster image: registry: docker.io - repository: mosipid/print + repository: mosipqa/print tag: develop ## Specify a imagePullPolicy ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' From 38680abb22ad2d088f255228352720d4e2cb929e Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Fri, 5 Jun 2026 17:03:34 +0530 Subject: [PATCH 65/71] #1830: agents.md file added Signed-off-by: nagendra0721 --- AGENTS.md | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..9071d013 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,265 @@ +# AGENTS.md — MOSIP Print Service + +This file provides guidance to AI agents when working with code in this repository. + +--- + +## Project Overview + +The **MOSIP Print Service** is a Spring Boot microservice that generates PDF identity credentials (eUIN cards, reprint cards, QR code cards) for residents enrolled in MOSIP — the Modular Open Source Identity Platform. + +It sits at the tail end of the **ID Lifecycle Management** pipeline: +1. A resident registers or updates their ID at a registration centre. +2. Demographic and biometric data are captured and processed by Registration Processor. +3. Credentials are issued and an event is published to WebSub. +4. **This service** consumes the credential event, decrypts resident data, renders a template, generates a PDF, uploads it to DataShare, and publishes a completion event. + +The service is a **reference implementation** for credential partner onboarding. Deployers are expected to customise templates and partner certificates. + +--- + +## Technology Stack + +| Layer | Technology | +|---|---| +| Language | Java 21 | +| Framework | Spring Boot 3.2.3 + Spring Cloud Config | +| Build | Maven 3.9.6 | +| PDF generation | iText 7 (html2pdf 2.0.0) + iText 5 (legacy) | +| Template engine | Apache Velocity 1.7 | +| QR codes | ZXing (Google) 3.4.1 | +| Cryptography | BouncyCastle 1.66, JWT 3.8.1 | +| Messaging | MOSIP WebSub (kernel-websub-client-api) | +| Biometrics | CBEFF format via kernel-cbeffutil | +| Tests | JUnit 5, Mockito 4.11.0, JaCoCo | +| Containerisation | Docker (Alpine-based), Helm for Kubernetes | + +--- + +## Repository Layout + +``` +print/ +├── src/main/java/io/mosip/print/ +│ ├── PrintPDFApplication.java # Spring Boot entry point +│ ├── controller/Print.java # Single REST endpoint (WebSub callback) +│ ├── service/ +│ │ ├── PrintService.java # Interface +│ │ └── impl/ +│ │ ├── PrintServiceImpl.java # Core orchestration logic (~500 lines) +│ │ ├── PDFGeneratorImpl.java # iText PDF generation +│ │ ├── QrcodeGeneratorImpl.java # ZXing QR code generation +│ │ ├── TemplateManagerImpl.java # Velocity template processing +│ │ ├── CbeffImpl.java # Biometric (CBEFF) handling +│ │ └── UinCardGeneratorImpl.java +│ ├── util/ # 50+ utility classes +│ │ ├── CryptoUtil.java # Encryption / decryption +│ │ ├── DataShareUtil.java # DataShare upload +│ │ ├── TemplateGenerator.java # Template instantiation +│ │ ├── DigitalSignatureUtility.java # PDF digital signing +│ │ ├── WebSubSubscriptionHelper.java # WebSub subscription management +│ │ └── RestApiClient.java # HTTP client wrapper +│ ├── dto/ # 65+ Data Transfer Objects +│ ├── constant/ # 35+ constant classes +│ ├── exception/ # 35+ custom exception classes +│ ├── model/ # Domain models (Event, StatusEvent) +│ └── entity/ # CBEFF entity models +├── src/main/resources/ +│ ├── application-local1.properties # Local dev config (port 8088) +│ ├── bootstrap.properties # Spring Cloud Config bootstrap +│ └── partner.p12 # Sample partner certificate +├── src/test/java/io/mosip/print/ # 45+ unit test files +├── docs/ +│ ├── build-and-run.md +│ ├── configuration.md +│ └── print-service.png # Architecture diagram +├── deploy/ # Shell scripts for bare-metal deploy +├── helm/ # Kubernetes Helm charts +├── Dockerfile +└── pom.xml +``` + +--- + +## Build and Run + +### Build (skip tests) +```bash +mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgpg.skip=true +``` + +### Build with tests +```bash +mvn clean install -Dmaven.javadoc.skip=true -Dgpg.skip=true +``` + +### Run tests only +```bash +mvn test +``` + +### Run the JAR +```bash +java -Dloader.path= \ + -Dspring.profiles.active=local1 \ + -jar target/print-*.jar +``` + +### Docker +```bash +# Build image +docker build -t print-service:local . + +# Run container +docker run -d -p 8099:8099 --name print-service print-service:local +``` + +Service listens on **port 8099** (default) or **port 8088** (local1 profile). + +--- + +## Key Configuration Properties + +All runtime configuration is sourced from a **Spring Cloud Config Server** (mosip-config repository). Local overrides live in `application-local1.properties`. + +| Property | Purpose | +|---|---| +| `mosip.event.hubURL` | WebSub hub URL | +| `mosip.partner.id` | This service's partner ID (`mpartner-default-print`) | +| `mosip.event.topic` | WebSub topic to subscribe to | +| `mosip.event.callBackUrl` | Public URL of the `/print/callback/notifyPrint` endpoint | +| `mosip.event.secret` | WebSub HMAC secret | +| `mosip.datashare.partner.id` | DataShare partner (`mpartner-default-resident`) | +| `mosip.datashare.policy.id` | DataShare policy (`mpolicy-default-resident`) | +| `mosip.print.crypto.p12.filename` | Partner P12 certificate filename | +| `mosip.print.crypto.p12.password` | P12 password | +| `mosip.print.crypto.p12.alias` | P12 key alias | +| `mosip.template-language` | Language code for template lookup (e.g. `eng`) | +| `mosip.kernel.pdf_owner_password` | PDF encryption owner password | +| `mosip.supported-languages` | Comma-separated ISO language codes | +| `mosip.iam.adapter.clientid` | Keycloak client ID for this service | +| `mosip.iam.adapter.issuerURL` | Keycloak realm issuer URL | +| `TEMPLATES` | Masterdata templates endpoint | +| `CREATEDATASHARE` | DataShare create endpoint | +| `PDFSIGN` | Keymanager PDF sign endpoint | + +--- + +## REST API + +### `POST /v1/print/print/callback/notifyPrint` + +The single public endpoint. Called by the WebSub hub when a credential event is published. + +**Headers required:** +- `x-hub-signature` — HMAC-SHA256 signature from WebSub hub (validated by the service) + +**Body:** WebSub notification JSON containing the credential event payload. + +**Flow:** +1. Validates WebSub signature and intent. +2. Extracts and decrypts credential data using the partner P12 key. +3. Fetches the HTML template for the card type from Masterdata. +4. Renders the template with resident data using Velocity. +5. Converts HTML to PDF via iText html2pdf. +6. Optionally embeds a QR code and applies a digital signature. +7. Uploads the PDF to DataShare. +8. Publishes a `CREDENTIAL_STATUS_UPDATE` event back to WebSub. + +--- + +## Data Flow and Integration Points + +``` +WebSub Hub + │ POST /print/callback/notifyPrint + ▼ +Print.java (controller) + │ generateCard(event) + ▼ +PrintServiceImpl.java + ├── KeyManager / CryptoUtil — decrypt credential data + ├── Masterdata (TEMPLATES) — fetch HTML template + ├── TemplateManagerImpl — merge template + data (Velocity) + ├── PDFGeneratorImpl — HTML → PDF (iText) + ├── QrcodeGeneratorImpl — embed QR code (ZXing) + ├── DigitalSignatureUtility — sign PDF (Keymanager PDFSIGN) + ├── DataShareUtil — upload PDF (DataShare) + └── WebSubSubscriptionHelper — publish status event +``` + +External services consumed: +- **Keymanager** — decryption and PDF signing +- **Masterdata** — HTML templates +- **DataShare** — secure PDF upload +- **WebSub** — event subscription and publication +- **IAM (Keycloak)** — token-based authentication for all REST calls + +--- + +## ID Lifecycle Context + +MOSIP's ID Lifecycle Management covers the full journey of an identity: + +- **Registration** — Resident attends a registration centre; demographic and biometric data are captured. +- **Processing** — Registration Processor de-duplicates and validates the data. +- **Activation** — A UIN (Unique Identification Number) is issued; the ID is active. +- **Credential Issuance** — Credentials (VCs, PDF cards) are generated for configured partners. +- **Updates** — Resident can update demographics or biometrics; a new credential is re-issued. +- **Deactivation / Reactivation** — IDs can be deactivated and reactivated via resident services. +- **Print / Reprint** — This service handles the print partner use case: it receives credential events and produces PDF identity cards. + +This service handles the **Credential Issuance → Print** leg. The `UinCardType` constant class enumerates supported card types: `EUIN`, `REPRINT`, `QRCODE`. + +--- + +## Working with This Codebase + +### Before making changes +1. Read `PrintServiceImpl.java` — it is the central orchestrator; most changes flow through it. +2. Check `PrintRestClientServiceImpl.java` for how external REST calls are made before adding new ones. +3. Verify properties exist in `application-local1.properties` or `docs/configuration.md` before introducing new config keys. + +### Adding a new card type +1. Add a constant to `UinCardType.java`. +2. Add a template to Masterdata and reference it in `PrintServiceImpl.generateCard()`. +3. Add a corresponding branch in `TemplateGenerator.java` if template rendering differs. + +### Modifying template rendering +Templates are HTML files processed by Velocity. Variables are injected via `VelocityContext`. See `TemplateManagerImpl.java` and `TemplateGenerator.java`. + +### Cryptography +Do not modify `CryptoUtil.java` without understanding the P12 key usage. The partner certificate must match the credential encryption key registered in Keymanager. + +### Tests +- Unit tests are in `src/test/java/io/mosip/print/`. +- Tests use Mockito; external service calls are mocked. +- Run `mvn test` before committing any change. +- JaCoCo generates a coverage report under `target/site/jacoco/`. + +### Docker image +The `Dockerfile` copies the built JAR and runs it. The image is published to Docker Hub as `mosipid/print-service`. The current version under development is `1.4.0-SNAPSHOT`. + +--- + +## Conventions + +- Package root: `io.mosip.print` +- All Spring beans follow constructor injection where possible. +- REST responses are wrapped in a standard `ResponseWrapper` DTO. +- Exceptions extend `BaseCheckedException` or `BaseUncheckedException` from the MOSIP kernel. +- Logging uses `io.mosip.print.logger.PrintLogger` (SLF4J wrapper); do not use `System.out`. +- Constants live in dedicated files under the `constant/` package; do not hard-code strings inline. +- All external HTTP calls go through `PrintRestClientService` — do not use `RestTemplate` directly in service classes. + +--- + +## Useful References + +- MOSIP documentation: https://docs.mosip.io/1.2.0/ +- ID Lifecycle Management: https://docs.mosip.io/1.2.0/id-lifecycle-management +- mosip-config repository: contains `print-default.properties` with all runtime properties +- MOSIP functional tests: https://github.com/mosip/mosip-functional-tests +- Docker Hub image: `mosipid/print-service` +- Build and run guide: `docs/build-and-run.md` +- Configuration guide: `docs/configuration.md` From 3d028cb962e8dc8ceba2256ece5732ca9e82b508 Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Fri, 5 Jun 2026 17:27:35 +0530 Subject: [PATCH 66/71] #1830: agents.md file modified Signed-off-by: nagendra0721 --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 9071d013..3235f39a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -238,7 +238,7 @@ Do not modify `CryptoUtil.java` without understanding the P12 key usage. The par - JaCoCo generates a coverage report under `target/site/jacoco/`. ### Docker image -The `Dockerfile` copies the built JAR and runs it. The image is published to Docker Hub as `mosipid/print-service`. The current version under development is `1.4.0-SNAPSHOT`. +The `Dockerfile` copies the built JAR and runs it. The image is published to Docker Hub as `mosipid/print-service`. --- From ea20c2e25f5a485e655644ab3730c948de036d11 Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Fri, 5 Jun 2026 17:30:55 +0530 Subject: [PATCH 67/71] #1830: modified as per suggestion Signed-off-by: nagendra0721 --- .github/workflows/push-trigger.yml | 2 -- helm/print/templates/deployment.yaml | 4 +--- src/main/resources/bootstrap.properties | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/push-trigger.yml b/.github/workflows/push-trigger.yml index 2d875000..4f7655d2 100644 --- a/.github/workflows/push-trigger.yml +++ b/.github/workflows/push-trigger.yml @@ -91,7 +91,6 @@ jobs: - SERVICE_LOCATION: '../print' SERVICE_NAME: 'print' BUILD_ARTIFACT: 'print' - SQUASH_LAYERS: '11' fail-fast: false name: ${{ matrix.SERVICE_NAME }} uses: mosip/kattu/.github/workflows/docker-build.yml@master-java21 @@ -99,7 +98,6 @@ jobs: SERVICE_LOCATION: ${{ matrix.SERVICE_LOCATION }} SERVICE_NAME: ${{ matrix.SERVICE_NAME }} BUILD_ARTIFACT: ${{ matrix.BUILD_ARTIFACT }} - SQUASH_LAYERS: ${{ matrix.SQUASH_LAYERS }} secrets: DEV_NAMESPACE_DOCKER_HUB: ${{ secrets.DEV_NAMESPACE_DOCKER_HUB }} ACTOR_DOCKER_HUB: ${{ secrets.ACTOR_DOCKER_HUB }} diff --git a/helm/print/templates/deployment.yaml b/helm/print/templates/deployment.yaml index 2a7ad164..2c852425 100644 --- a/helm/print/templates/deployment.yaml +++ b/helm/print/templates/deployment.yaml @@ -105,10 +105,8 @@ spec: {{- end }} {{- end }} {{- if .Values.extraEnvVarsSecret }} - {{- range .Values.extraEnvVarsSecret }} - secretRef: - name: {{ . }} - {{- end }} + name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }} {{- end }} ports: - name: spring-service diff --git a/src/main/resources/bootstrap.properties b/src/main/resources/bootstrap.properties index 5ebcb87d..0b211aa7 100644 --- a/src/main/resources/bootstrap.properties +++ b/src/main/resources/bootstrap.properties @@ -1,7 +1,7 @@ #spring.cloud.config.uri=localhost #spring.cloud.config.label=develop spring.profiles.active=local1 -spring.cloud.config.name=application,print +#spring.cloud.config.name=print spring.application.name=print management.endpoint.health.show-details=always management.endpoints.web.exposure.include=info,health,refresh From e22baa2a502ca0f04bc9db5737b598a9fe2f7a25 Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Mon, 8 Jun 2026 11:09:08 +0530 Subject: [PATCH 68/71] #1830: update bootstrap fie Signed-off-by: nagendra0721 --- src/main/resources/bootstrap.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/bootstrap.properties b/src/main/resources/bootstrap.properties index 0b211aa7..3eb1b27f 100644 --- a/src/main/resources/bootstrap.properties +++ b/src/main/resources/bootstrap.properties @@ -1,11 +1,11 @@ #spring.cloud.config.uri=localhost #spring.cloud.config.label=develop spring.profiles.active=local1 -#spring.cloud.config.name=print +spring.cloud.config.name=application,print spring.application.name=print management.endpoint.health.show-details=always management.endpoints.web.exposure.include=info,health,refresh server.port=8088 server.servlet.context-path=/v1/print -health.config.enabled=false +health.config.enabled=false \ No newline at end of file From 25f1501a0b1d14afa6b29a14b6e3768163536676 Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Mon, 8 Jun 2026 11:12:50 +0530 Subject: [PATCH 69/71] #1830: update agents file Signed-off-by: nagendra0721 --- AGENTS.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3235f39a..fb70a189 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,18 +20,18 @@ The service is a **reference implementation** for credential partner onboarding. ## Technology Stack -| Layer | Technology | -|---|---| -| Language | Java 21 | -| Framework | Spring Boot 3.2.3 + Spring Cloud Config | -| Build | Maven 3.9.6 | -| PDF generation | iText 7 (html2pdf 2.0.0) + iText 5 (legacy) | -| Template engine | Apache Velocity 1.7 | -| QR codes | ZXing (Google) 3.4.1 | -| Cryptography | BouncyCastle 1.66, JWT 3.8.1 | -| Messaging | MOSIP WebSub (kernel-websub-client-api) | -| Biometrics | CBEFF format via kernel-cbeffutil | -| Tests | JUnit 5, Mockito 4.11.0, JaCoCo | +| Layer | Technology | +|---|--------------------------------------------| +| Language | Java 21 | +| Framework | Spring Boot 3.2.3 + Spring Cloud Config | +| Build | Maven 3.9.6 | +| PDF generation | pdfbox +| Template engine | Apache Velocity 1.7 | +| QR codes | ZXing (Google) 3.4.1 | +| Cryptography | BouncyCastle 1.66, JWT 3.8.1 | +| Messaging | MOSIP WebSub (kernel-websub-client-api) | +| Biometrics | CBEFF format via kernel-cbeffutil | +| Tests | JUnit 5, Mockito 4.11.0, JaCoCo | | Containerisation | Docker (Alpine-based), Helm for Kubernetes | --- @@ -181,7 +181,7 @@ PrintServiceImpl.java ├── KeyManager / CryptoUtil — decrypt credential data ├── Masterdata (TEMPLATES) — fetch HTML template ├── TemplateManagerImpl — merge template + data (Velocity) - ├── PDFGeneratorImpl — HTML → PDF (iText) + ├── PDFGeneratorImpl — HTML → PDF (PDF Box) ├── QrcodeGeneratorImpl — embed QR code (ZXing) ├── DigitalSignatureUtility — sign PDF (Keymanager PDFSIGN) ├── DataShareUtil — upload PDF (DataShare) From 12442909ec1f378ff26a70a8d3cf2a7d4c30a14b Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Mon, 8 Jun 2026 11:19:12 +0530 Subject: [PATCH 70/71] #1830: update pom file Signed-off-by: nagendra0721 --- pom.xml | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/pom.xml b/pom.xml index ce3cd65c..c34dead7 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,6 @@ 3.0.2 3.1.0 0.8.11 - 0.7.0 3.7.0.1746 3.2.0 5.0.5.RELEASE @@ -298,24 +297,15 @@ - - ossrh - LegacyMavenSnapshot - https://oss.sonatype.org/content/repositories/snapshots - default - - true - - - - ossrh-central - MavenCentralRepository - https://central.sonatype.com/repository/maven-snapshots - default - - true - - + + ossrh-central + MavenCentralRepository + https://central.sonatype.com/repository/maven-snapshots + default + + true + + central MavenCentral @@ -330,7 +320,7 @@ ossrh - https://central.sonatype.com/repository/maven-snapshots/ + https://central.sonatype.com/repository/maven-snapshots ossrh From 390fa65e339a1e142f52f0e2e8c6ad66aa7f3749 Mon Sep 17 00:00:00 2001 From: nagendra0721 Date: Mon, 8 Jun 2026 11:25:57 +0530 Subject: [PATCH 71/71] #1830: update readme file Signed-off-by: nagendra0721 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 82fc0278..3473fd1d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ To deploy print service in production follow the given steps: 1. Onboard your organisation as [Credential Partner](https://docs.mosip.io/1.2.0/partners). 2. Place your `.p12` file in `../src/main/resources` folder. -3. Set configuration as in given [here](https://github.com/mosip/mosip-config/blob/develop/print-default.properties). +3. Set configuration as in given [here](https://github.com/mosip/mosip-config/blob/master/print-default.properties). 4. Build and run as given [here](docs/build-and-run.md). ## Test @@ -73,7 +73,7 @@ Ensure the following artifacts are available in the classpath or loader path: ### Configuration Print uses properties from **mosip-config**. -You can check the configuration here: [print-default.properties](https://github.com/mosip/mosip-config/blob/develop/print-default.properties) +You can check the configuration here: [print-default.properties](https://github.com/mosip/mosip-config/blob/master/print-default.properties) ### Local Setup (for Development or Contribution) 1. Ensure the **Config Server** is running and accessible.To run config server [check here.](https://github.com/mosip/mosip-config/blob/master/README.md)