diff --git a/hcx-qr-code-generator/pom.xml b/hcx-qr-code-generator/pom.xml new file mode 100644 index 000000000..212fe42bb --- /dev/null +++ b/hcx-qr-code-generator/pom.xml @@ -0,0 +1,121 @@ + + 4.0.0 + + org.swasth + hcx-platform + 1.0-SNAPSHOT + + + hcx-qr-code-generator + jar + + 17 + 17 + 17 + + + hcx-qr-code-generator + http://maven.apache.org + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + com.google.zxing + core + 3.5.1 + + + com.google.zxing + javase + 3.4.0 + + + com.google.code.gson + gson + 2.10.1 + + + org.freemarker + freemarker + 2.3.31 + + + org.swasth + hcx-common + 1.0-SNAPSHOT + + + + junit + junit + 4.13.2 + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.target.runtime} + ${java.target.runtime} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + + + + org.swasth.HcxQRCodeGenerator + + + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + default-prepare-agent + + prepare-agent + + + + default-report + prepare-package + + report + + + + + + + diff --git a/hcx-qr-code-generator/src/main/java/org/swasth/HcxQRCodeGenerator.java b/hcx-qr-code-generator/src/main/java/org/swasth/HcxQRCodeGenerator.java new file mode 100644 index 000000000..d2a423bd4 --- /dev/null +++ b/hcx-qr-code-generator/src/main/java/org/swasth/HcxQRCodeGenerator.java @@ -0,0 +1,142 @@ +package org.swasth; + +import com.google.gson.Gson; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import org.apache.commons.io.IOUtils; +import org.swasth.service.EncDeCode; +import org.swasth.utils.JWSUtils; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HcxQRCodeGenerator { + private static final Logger logger = Logger.getLogger(String.valueOf(HcxQRCodeGenerator.class)); + private static int width; + private static int height; + private static String privatekey; + + static { + try { + loadConfig(); + } catch (Exception e) { + logger.info(e.getMessage()); + } + } + + private static void loadConfig() { + Yaml yaml = new Yaml(); + try (InputStream inputStream = HcxQRCodeGenerator.class.getResourceAsStream("/application.yml")) { + Map config = yaml.load(inputStream); + Map qrCodeConfig = (Map) config.get("qr_code"); + width = parseWidthHeight((String) qrCodeConfig.get("width")); + height = parseWidthHeight((String) qrCodeConfig.get("height")); + privatekey = resolvePlaceholder((String) qrCodeConfig.get("private_key")); + }catch (Exception e){ + logger.info(e.getMessage()); + } + } + + private static int parseWidthHeight(String value) { + if (value.startsWith("${") && value.endsWith("}")) { + Pattern pattern = Pattern.compile("\\$\\{(.+?):(\\d+)}"); + Matcher matcher = pattern.matcher(value); + if (matcher.find()) { + return Integer.parseInt(matcher.group(2)); + } + } + return Integer.parseInt(value); + } + + protected static String resolvePlaceholder(String value) { + if (value.startsWith("${") && value.endsWith("}")) { + int colonIndex = value.indexOf(':'); + if (colonIndex != -1) { + return value.substring(colonIndex + 1, value.length() - 1); + } + } + return value; + } + + public static void main(String[] args) throws TemplateException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, WriterException, URISyntaxException { + if (args.length > 0) { + String json = args[0]; + Gson gson = new Gson(); + Map map = gson.fromJson(json, HashMap.class); + logger.info("Map received from command line argument:"); + String certificate = IOUtils.toString(new URI(privatekey), StandardCharsets.UTF_8); + generateQrToken((Map) map.get("payload"), certificate); + } else { + logger.info("No input to process"); + } + } + + private static String getPrivateKey(String privateKey) { + privateKey = privateKey + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + return privateKey; + } + + private static String generateQrToken(Map requestBody, String privateKey) throws TemplateException, IOException, WriterException, NoSuchAlgorithmException, InvalidKeySpecException { + Map headers = new HashMap<>(); + String jwsToken = JWSUtils.generate(headers, requestBody, HcxQRCodeGenerator.getPrivateKey(privateKey)); + String participantCode = null; + if (requestBody.containsKey("participantCode")) { + participantCode = (String) requestBody.get("participantCode"); + } + logger.info("QR Token generated"); + String payload = createVerifiableCredential(requestBody, jwsToken); + generateQRCode(EncDeCode.encodePayload(payload), participantCode); + return payload; + } + private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + private static String createVerifiableCredential(Map payload, String proofValue) throws IOException, TemplateException { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); + cfg.setClassForTemplateLoading(HcxQRCodeGenerator.class, "/templates"); + Template template = cfg.getTemplate("verifiable-credential.ftl"); + LocalDateTime issuanceDate = LocalDateTime.now(); + LocalDateTime expirationDate = LocalDateTime.now().plusYears(1); + Map data = new HashMap<>(); + data.put("issuanceDate", formatter.format(issuanceDate)); + data.put("expirationDate", formatter.format(expirationDate)); + data.put("subjectId", UUID.randomUUID()); + data.put("payload", new Gson().toJson(payload)); + data.put("proofCreated", LocalDateTime.now()); + data.put("proofValue", proofValue); + StringWriter out = new StringWriter(); + template.process(data, out); + return out.toString(); + } + + private static void generateQRCode(String content, String participantCode) throws WriterException, IOException { + MultiFormatWriter writer = new MultiFormatWriter(); + BitMatrix matrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height); + String currentDir = System.getProperty("user.dir"); + Path path = FileSystems.getDefault().getPath(currentDir + "/" + participantCode + "_qr_code_" + System.currentTimeMillis() + ".png"); + MatrixToImageWriter.writeToPath(matrix, "PNG", path); + logger.info("QR code image generated and saved to: " + path.toAbsolutePath()); + } +} diff --git a/hcx-qr-code-generator/src/main/java/org/swasth/service/EncDeCode.java b/hcx-qr-code-generator/src/main/java/org/swasth/service/EncDeCode.java new file mode 100644 index 000000000..078e71f67 --- /dev/null +++ b/hcx-qr-code-generator/src/main/java/org/swasth/service/EncDeCode.java @@ -0,0 +1,26 @@ +package org.swasth.service; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; +import java.util.logging.Logger; + +import static org.swasth.common.utils.JSONUtils.deserialize; + +public class EncDeCode { + private static final Logger logger = Logger.getLogger(String.valueOf(EncDeCode.class)); + public static String encodePayload(String payload) { + String base64EncodedSignature = Base64.getEncoder().encodeToString(payload.getBytes()); + logger.info("Encoded Payload"); + return base64EncodedSignature; + } + + public static Map decodePayload(String payload) throws JsonProcessingException { + byte[] decodedBytes = Base64.getDecoder().decode(payload); + String decodedString = new String(decodedBytes, StandardCharsets.UTF_8); + return deserialize(decodedString, Map.class); + } + +} diff --git a/hcx-qr-code-generator/src/main/java/org/swasth/service/VerifyQrCode.java b/hcx-qr-code-generator/src/main/java/org/swasth/service/VerifyQrCode.java new file mode 100644 index 000000000..f43f44e96 --- /dev/null +++ b/hcx-qr-code-generator/src/main/java/org/swasth/service/VerifyQrCode.java @@ -0,0 +1,49 @@ +package org.swasth.service; + +import org.apache.commons.io.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Arrays; +import java.util.Base64; +import java.util.Map; +import java.util.logging.Logger; + +public class VerifyQrCode { + private static final Logger logger = Logger.getLogger(String.valueOf(VerifyQrCode.class)); + + public static Map getToken(Map payload) throws CertificateException, IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { + String publicKeyUrl = "https://raw.githubusercontent.com/Swasth-Digital-Health-Foundation/hcx-platform/main/hcx-apis/src/test/resources/examples/test-keys/public-key.pem"; + Map token = (Map) payload.get("proof"); + if (token.containsKey("proofValue")) { + String jwsToken = (String) token.get("proofValue"); + boolean isSignatureValid = isValidSignature(jwsToken, publicKeyUrl); + logger.info(isSignatureValid + " Valid Signature"); + } else logger.info("proofValue is empty or null"); + return token; + } + + public static boolean isValidSignature(String payload, String publicKeyUrl) throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { + String certificate = IOUtils.toString(new URL(publicKeyUrl), StandardCharsets.UTF_8); + byte[] certificateBytes = certificate.getBytes(StandardCharsets.UTF_8); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + InputStream stream = new ByteArrayInputStream(certificateBytes); + Certificate cert = cf.generateCertificate(stream); + PublicKey publicKey = cert.getPublicKey(); + String[] parts = payload.split("\\."); + String data = String.join(".", Arrays.copyOfRange(parts, 0, 2)); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(publicKey); + sig.update(data.getBytes(StandardCharsets.UTF_8)); + byte[] decodedSignature = Base64.getUrlDecoder().decode(parts[2]); + return sig.verify(decodedSignature); + } + +} diff --git a/hcx-qr-code-generator/src/main/java/org/swasth/utils/JWSUtils.java b/hcx-qr-code-generator/src/main/java/org/swasth/utils/JWSUtils.java new file mode 100644 index 000000000..93eaedca1 --- /dev/null +++ b/hcx-qr-code-generator/src/main/java/org/swasth/utils/JWSUtils.java @@ -0,0 +1,21 @@ +package org.swasth.utils; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Map; + +public class JWSUtils { + public static String generate(Map headers, Map payload, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException { + byte[] privateKeyDecoded = Base64.getDecoder().decode(privateKey); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyDecoded); + PrivateKey rsaPrivateKey = KeyFactory.getInstance("RSA").generatePrivate(spec); + return Jwts.builder().setHeader(headers).setClaims(payload).signWith(SignatureAlgorithm.RS256, rsaPrivateKey).compact(); + } +} diff --git a/hcx-qr-code-generator/src/main/resources/application.yml b/hcx-qr-code-generator/src/main/resources/application.yml new file mode 100644 index 000000000..30b11b3ea --- /dev/null +++ b/hcx-qr-code-generator/src/main/resources/application.yml @@ -0,0 +1,4 @@ +qr_code: + width: ${width:150} + height: ${height:150} + private_key: ${private_key:https://raw.githubusercontent.com/Swasth-Digital-Health-Foundation/hcx-platform/main/hcx-apis/src/test/resources/examples/test-keys/private-key.pem} diff --git a/hcx-qr-code-generator/src/main/resources/templates/verifiable-credential.ftl b/hcx-qr-code-generator/src/main/resources/templates/verifiable-credential.ftl new file mode 100644 index 000000000..e9829f1ce --- /dev/null +++ b/hcx-qr-code-generator/src/main/resources/templates/verifiable-credential.ftl @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://hcxprotocol.io/credentials/3732", + "type": ["VerifiableCredential"], + "issuer": "https://hcxprotocol.io/participant/565049", + "issuanceDate": "${issuanceDate}", + "expirationDate": "${expirationDate}", + "preferredHCXPath": "http://hcx.swasth.app/api/v0.8/", + "credentialSubject": { + "id": "${subjectId}", + "payload": ${payload} + }, + "proof": { + "type": "Ed25519Signature2020", + "created": "${proofCreated}", + "verificationMethod": "https://hcxprotocol.io/issuers/565049#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "${proofValue}" + } +} diff --git a/hcx-qr-code-generator/src/test/java/org/swasth/HcxQRCodeGeneratorTest.java b/hcx-qr-code-generator/src/test/java/org/swasth/HcxQRCodeGeneratorTest.java new file mode 100644 index 000000000..a07644b5a --- /dev/null +++ b/hcx-qr-code-generator/src/test/java/org/swasth/HcxQRCodeGeneratorTest.java @@ -0,0 +1,130 @@ +package org.swasth; + + +import org.junit.Test; +import org.swasth.service.EncDeCode; +import org.swasth.service.VerifyQrCode; +import org.swasth.utils.JWSUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +public class HcxQRCodeGeneratorTest { + + HcxQRCodeGenerator hcxQRCodeGenerator = new HcxQRCodeGenerator(); + + VerifyQrCode verifyQRCode = new VerifyQrCode(); + + EncDeCode encDeCode = new EncDeCode(); + + + JWSUtils jwsUtils = new JWSUtils(); + + + @Test + public void test_hcx_qr_generator_success() throws Exception { + String[] args = {"{\"payload\":{\"participantCode\":\"test_user_55.yopmail@swasth-hcx\",\"email\":\"test_user_555@yopmail.com\",\"mobile\":\"9899912323\"}}"}; + hcxQRCodeGenerator.main(args); + } + + @Test + public void test_hcx_qr_generator() throws Exception { + String[] args = {"{\"payload\":{\"email\":\"test_user_555@yopmail.com\",\"mobile\":\"9899912323\"}}"}; + hcxQRCodeGenerator.main(args); + } + + @Test + public void test_hcx_qr_generator_no_input_exception() throws Exception { + String[] args = {}; + HcxQRCodeGenerator.main(args); + } + + @Test + public void test_decode() throws Exception { + String encodedString = "ew0KICAiQGNvbnRleHQiOiBbDQogICAgImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwNCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiDQogIF0sDQogICJpZCI6ICJodHRwOi8vaGN4cHJvdG9jb2wuaW8vY3JlZGVudGlhbHMvMzczMiIsDQogICJ0eXBlIjogWyJWZXJpZmlhYmxlQ3JlZGVudGlhbCJdLA0KICAiaXNzdWVyIjogImh0dHBzOi8vaGN4cHJvdG9jb2wuaW8vcGFydGljaXBhbnQvNTY1MDQ5IiwNCiAgImlzc3VhbmNlRGF0ZSI6ICIyMDI0LTAyLTE5VDEyOjMzOjU1LjI3OTkzNjMiLA0KICAiZXhwaXJhdGlvbkRhdGUiOiAiMjAyNS0wMi0xOVQxMjozMzo1NS4yNzk5MzYzIiwNCiAgInByZWZlcnJlZEhDWFBhdGgiOiAiaHR0cDovL2hjeC5zd2FzdGguYXBwL2FwaS92MC44LyIsDQogICJjcmVkZW50aWFsU3ViamVjdCI6IHsNCiAgICAiaWQiOiAiY2I0YzE5MjQtOGUzZi00NTgxLThjZWEtNDg4ZmMxOTBiZTA3IiwNCiAgICAicGF5bG9hZCI6IHsicGFydGljaXBhbnRDb2RlIjoidGVzdF91c2VyXzU1LnlvcG1haWxAc3dhc3RoLWhjeCIsImVtYWlsIjoidGVzdF91c2VyXzU1NUB5b3BtYWlsLmNvbSIsIm1vYmlsZSI6Ijk4OTk5MTIzMjMifQ0KICB9LA0KICAicHJvb2YiOiB7DQogICAgInR5cGUiOiAiRWQyNTUxOVNpZ25hdHVyZTIwMjAiLA0KICAgICJjcmVhdGVkIjogIjIwMjQtMDItMTlUMTI6MzM6NTUuMjg0MjMxMTAwIiwNCiAgICAidmVyaWZpY2F0aW9uTWV0aG9kIjogImh0dHBzOi8vaGN4cHJvdG9jb2wuaW8vaXNzdWVycy81NjUwNDkja2V5LTEiLA0KICAgICJwcm9vZlB1cnBvc2UiOiAiYXNzZXJ0aW9uTWV0aG9kIiwNCiAgICAicHJvb2ZWYWx1ZSI6ICJleUpoYkdjaU9pSlNVekkxTmlKOS5leUp3WVhKMGFXTnBjR0Z1ZEVOdlpHVWlPaUowWlhOMFgzVnpaWEpmTlRVdWVXOXdiV0ZwYkVCemQyRnpkR2d0YUdONElpd2laVzFoYVd3aU9pSjBaWE4wWDNWelpYSmZOVFUxUUhsdmNHMWhhV3d1WTI5dElpd2liVzlpYVd4bElqb2lPVGc1T1RreE1qTXlNeUo5Lkk4c2VZMWQ0eUZ6NE5mX3RmZlkxOF9mZWpxZTFfaEJ5T3JsVnJENEMxUDNyQ19WamNhOUhHN19lRkJDUHFIdWh6RXJoeFJUb0FWTzZaMnJYVWNOUmhjc3diS2VWZFpVUFVfb3V4QXJCdUJZUVl4dlFybHNVX25vc2huT0pQYUZlNFk4bmNaWURWYnpPRjRXeVpsUFk3VDZ5UXBJMTJDUFIyMEJYVjhqOFFiaENkMEZ5eW9LN051VVhIcWlYZlFBeEY5YkJEamtoTlNNZzZ1Tjc2dFk1OEI3T0xVakhNYkUzOWU0QTNnQ3pTX0lNNk5uYzdaMHQ4ektsNXJTTFl1elptXzFCWDBPTXc4OGY0WVZENEF6X1FqWDVtNkNzZmVjMWk2X3NHZFZXbzN5em5rdXJ2SVNMdlZLaWNXRFFyMEdkVUVFazg1RUpEX3A5OVhmMktxNTBpQSINCiAgfQ0KfQ0K"; + encDeCode.decodePayload(encodedString); + } + + @Test + public void test_isValid_signature_success() throws Exception { + String publicKeyUrl = "https://raw.githubusercontent.com/Swasth-Digital-Health-Foundation/hcx-platform/main/hcx-apis/src/test/resources/examples/test-keys/public-key.pem"; + Map jsonData = new HashMap<>(); + + Map credentialSubject = new HashMap<>(); + Map proof = new HashMap<>(); + + credentialSubject.put("id", "cb4c1924-8e3f-4581-8cea-488fc190be07"); + Map payload = new HashMap<>(); + payload.put("participantCode", "test_user_55.yopmail@swasth-hcx"); + payload.put("email", "test_user_555@yopmail.com"); + payload.put("mobile", "9899912323"); + credentialSubject.put("payload", payload); + + proof.put("proofPurpose", "assertionMethod"); + proof.put("proofValue", "eyJhbGciOiJSUzI1NiJ9.eyJwYXJ0aWNpcGFudENvZGUiOiJ0ZXN0X3VzZXJfNTUueW9wbWFpbEBzd2FzdGgtaGN4IiwiZW1haWwiOiJ0ZXN0X3VzZXJfNTU1QHlvcG1haWwuY29tIiwibW9iaWxlIjoiOTg5OTkxMjMyMyJ9.I8seY1d4yFz4Nf_tffY18_fejqe1_hByOrlVrD4C1P3rC_Vjca9HG7_eFBCPqHuhzErhxRToAVO6Z2rXUcNRhcswbKeVdZUPU_ouxArBuBYQYxvQrlsU_noshnOJPaFe4Y8ncZYDVbzOF4WyZlPY7T6yQpI12CPR20BXV8j8QbhCd0FyyoK7NuUXHqiXfQAxF9bBDjkhNSMg6uN76tY58B7OLUjHMbE39e4A3gCzS_IM6Nnc7Z0t8zKl5rSLYuzZm_1BX0OMw88f4YVD4Az_QjX5m6Csfec1i6_sGdVWo3yznkurvISLvVKicWDQr0GdUEEk85EJD_p99Xf2Kq50iA"); + + jsonData.put("@context", Arrays.asList("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1")); + jsonData.put("type", Collections.singletonList("VerifiableCredential")); + jsonData.put("issuanceDate", "2024-02-19T12:33:55.2799363"); + jsonData.put("expirationDate", "2025-02-19T12:33:55.2799363"); + jsonData.put("credentialSubject", credentialSubject); + jsonData.put("proof", proof); + Map verify = verifyQRCode.getToken(jsonData); + String token = (String) verify.get("proofValue"); + boolean assertvalue = verifyQRCode.isValidSignature(token,publicKeyUrl); + assertTrue(assertvalue); + } + + @Test + public void test_isValid_signature_invaild_proofValue_exception() throws Exception { + Map jsonData = new HashMap<>(); + Map proof = new HashMap<>(); + proof.put("type", "Ed25519Signature2020"); + proof.put("created", "2024-02-19T12:33:55.284231100"); + proof.put("verificationMethod", "https://hcxprotocol.io/issuers/565049#key-1"); + jsonData.put("proof", proof); + verifyQRCode.getToken(jsonData); + } + + @Test + public void testResolvePlaceholder_NoPlaceholder() { + String result = HcxQRCodeGenerator.resolvePlaceholder("testValue"); + assertEquals("testValue", result); + } + + @Test + public void testResolvePlaceholder_WithColon() { + String result = HcxQRCodeGenerator.resolvePlaceholder("${placeholder:value}"); + assertEquals("value", result); + } + + @Test + public void testResolvePlaceholder_WithoutColon() { + String result = HcxQRCodeGenerator.resolvePlaceholder("${placeholder}"); + assertEquals("${placeholder}", result); + } + + @Test + public void testResolvePlaceholder_NotStartingWithDollarAndCurlyBraces() { + String result = HcxQRCodeGenerator.resolvePlaceholder("value}"); + assertEquals("value}", result); + } + + @Test + public void testResolvePlaceholder_NotEndingWithCurlyBrace() { + String result = HcxQRCodeGenerator.resolvePlaceholder("${value"); + assertEquals("${value", result); + } + + @Test + public void testResolvePlaceholder_EmptyString() { + String result = HcxQRCodeGenerator.resolvePlaceholder(""); + assertEquals("", result); + } + +} diff --git a/hcx-qr-code-generator/src/test/resources/application-test.yml b/hcx-qr-code-generator/src/test/resources/application-test.yml new file mode 100644 index 000000000..30b11b3ea --- /dev/null +++ b/hcx-qr-code-generator/src/test/resources/application-test.yml @@ -0,0 +1,4 @@ +qr_code: + width: ${width:150} + height: ${height:150} + private_key: ${private_key:https://raw.githubusercontent.com/Swasth-Digital-Health-Foundation/hcx-platform/main/hcx-apis/src/test/resources/examples/test-keys/private-key.pem} diff --git a/pom.xml b/pom.xml index 386219fc9..ac7a0aa9e 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,7 @@ api-gateway hcx-scheduler-jobs hcx-onboard + hcx-qr-code-generator