From 81f07b399f8ace6165e5040e43f4850766f97d49 Mon Sep 17 00:00:00 2001 From: vanitha1822 Date: Fri, 4 Sep 2026 15:22:00 +0530 Subject: [PATCH] test: add unit test cases --- ...LInjectionSafeConstraintValidatorTest.java | 60 + .../mmu/data/common/DataBeanAccessorTest.java | 304 ++ .../CSNurseServiceImplTest.java | 933 +++++++ .../cancerScreening/CSServiceImplTest.java | 636 +++++ .../CancerDoctorAndCarestreamServiceTest.java | 235 ++ .../CommonDoctorServiceImplTest.java | 627 +++++ .../CommonNurseServiceImplTest.java | 2479 +++++++++++++++++ .../transaction/CommonServiceImplTest.java | 560 ++++ .../covid19/Covid19ServiceImplTest.java | 575 ++++ .../DownloadDataFromServerImplTest.java | 208 ++ ...adDataFromServerTransactionalImplTest.java | 169 ++ .../UploadDataToServerImplTest.java | 405 +++ .../DataSyncRepositoryCentralQueryTest.java | 391 +++ .../GetDataFromVanAndSyncToDBImplTest.java | 336 +++ .../mmu/service/health/HealthServiceTest.java | 353 +++ .../ncdCare/NCDCareServiceImplTest.java | 534 ++++ ...NCDScreeningNurseAndDoctorServiceTest.java | 269 ++ .../NCDScreeningServiceImplTest.java | 639 +++++ .../service/pnc/PNCDoctorServiceImplTest.java | 172 +- .../mmu/service/pnc/PNCServiceImplTest.java | 640 ++++- .../QuickConsultationFlowTest.java | 439 +++ .../registrar/RegistrarServiceImplTest.java | 612 ++++ .../reports/ReportCheckPostImplTest.java | 140 + .../AESEncryptionDecryptionTest.java | 68 + .../iemr/mmu/utils/JwtSecurityUtilsTest.java | 451 +++ .../utils/JwtUserIdValidationFilterTest.java | 242 ++ .../utils/config/ConfigPropertiesTest.java | 75 + .../CustomExceptionResponseTest.java | 137 + .../SecurityExceptionHandlersTest.java | 80 + .../http/HttpUtilsAndInterceptorTest.java | 261 ++ .../com/iemr/mmu/utils/mapper/MapperTest.java | 72 + .../mapper/RoleAuthenticationFilterTest.java | 200 ++ .../mmu/utils/redis/RedisStorageTest.java | 170 ++ .../utils/response/OutputResponseTest.java | 124 + .../sessionobject/SessionObjectTest.java | 110 + .../mmu/utils/validator/ValidatorTest.java | 137 + 36 files changed, 13799 insertions(+), 44 deletions(-) create mode 100644 src/test/java/com/iemr/mmu/annotation/sqlinjection/SQLInjectionSafeConstraintValidatorTest.java create mode 100644 src/test/java/com/iemr/mmu/data/common/DataBeanAccessorTest.java create mode 100644 src/test/java/com/iemr/mmu/service/cancerScreening/CSNurseServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/cancerScreening/CSServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/cancerScreening/CancerDoctorAndCarestreamServiceTest.java create mode 100644 src/test/java/com/iemr/mmu/service/common/transaction/CommonDoctorServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/common/transaction/CommonNurseServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/common/transaction/CommonServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/covid19/Covid19ServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerTransactionalImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/dataSyncActivity/UploadDataToServerImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/DataSyncRepositoryCentralQueryTest.java create mode 100644 src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/GetDataFromVanAndSyncToDBImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/health/HealthServiceTest.java create mode 100644 src/test/java/com/iemr/mmu/service/ncdCare/NCDCareServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningNurseAndDoctorServiceTest.java create mode 100644 src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/quickConsultation/QuickConsultationFlowTest.java create mode 100644 src/test/java/com/iemr/mmu/service/registrar/RegistrarServiceImplTest.java create mode 100644 src/test/java/com/iemr/mmu/service/reports/ReportCheckPostImplTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/AESEncryption/AESEncryptionDecryptionTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/JwtSecurityUtilsTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/JwtUserIdValidationFilterTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/config/ConfigPropertiesTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/exception/CustomExceptionResponseTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/exception/SecurityExceptionHandlersTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/http/HttpUtilsAndInterceptorTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/mapper/MapperTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/mapper/RoleAuthenticationFilterTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/redis/RedisStorageTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/response/OutputResponseTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/sessionobject/SessionObjectTest.java create mode 100644 src/test/java/com/iemr/mmu/utils/validator/ValidatorTest.java diff --git a/src/test/java/com/iemr/mmu/annotation/sqlinjection/SQLInjectionSafeConstraintValidatorTest.java b/src/test/java/com/iemr/mmu/annotation/sqlinjection/SQLInjectionSafeConstraintValidatorTest.java new file mode 100644 index 00000000..bbc9fe60 --- /dev/null +++ b/src/test/java/com/iemr/mmu/annotation/sqlinjection/SQLInjectionSafeConstraintValidatorTest.java @@ -0,0 +1,60 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.annotation.sqlinjection; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class SQLInjectionSafeConstraintValidatorTest { + + private final SQLInjectionSafeConstraintValidator validator = new SQLInjectionSafeConstraintValidator(); + + @ParameterizedTest(name = "\"{0}\" is rejected") + @ValueSource(strings = { "SELECT name FROM users", "INSERT INTO users values", "UPDATE users set", + "DELETE FROM users", "UPSERT users set", "SAVEPOINT before_change", "CALL some_procedure", + "ROLLBACK to savepoint", "KILL 12", "DROP everything", "CREATE TABLE users", + "ALTER TABLE users", "TRUNCATE TABLE users", "LOCK TABLE users", "UNLOCK TABLE users", + "RELEASE SAVEPOINT s", "DESC users", "DESCRIBE users", "name; DROP", "name -- comment", + "name /* comment */" }) + void isValid_rejectsAnythingThatLooksLikeSql(String dataString) { + assertFalse(validator.isValid(dataString, null)); + } + + @ParameterizedTest(name = "\"{0}\" is accepted") + @ValueSource(strings = { "Asha Devi", "PHC Alpha", "9999999999", "report.pdf" }) + void isValid_acceptsOrdinaryText(String dataString) { + validator.initialize(null); + + assertTrue(validator.isValid(dataString, null)); + } + + @ParameterizedTest(name = "an empty value is accepted") + @NullAndEmptySource + void isValid_acceptsAnEmptyValue(String dataString) { + assertTrue(validator.isValid(dataString, null)); + } +} diff --git a/src/test/java/com/iemr/mmu/data/common/DataBeanAccessorTest.java b/src/test/java/com/iemr/mmu/data/common/DataBeanAccessorTest.java new file mode 100644 index 00000000..6d33ef5d --- /dev/null +++ b/src/test/java/com/iemr/mmu/data/common/DataBeanAccessorTest.java @@ -0,0 +1,304 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.data.common; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigInteger; +import java.net.URL; +import java.sql.Date; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Exercises every data/model bean under {@code com.iemr.mmu.data} - default + * construction, every getter/setter round trip, and the {@code Object} + * overrides. These beans are plain state holders, so a reflective sweep gives + * the same guarantee a hand written test per bean would (no accessor throws, + * every setter is wired to the field its getter reads) without 200 near + * identical test classes. + */ +class DataBeanAccessorTest { + + /** Beans whose accessors intentionally do more than field access. */ + private static final Set SKIPPED_METHODS = new HashSet<>( + Arrays.asList("getClass", "notify", "notifyAll", "wait", "clone", "finalize")); + + @Test + @DisplayName("every data bean can be constructed and its accessors round trip") + void allDataBeansExerciseTheirAccessors() throws Exception { + List> beans = loadBeanClasses("com.iemr.mmu.data"); + assertTrue(beans.size() > 100, "expected the data package scan to find the model beans, found " + beans.size()); + + int exercised = 0; + for (Class bean : beans) { + Object instance = instantiate(bean); + if (instance == null) { + continue; + } + exercised++; + exerciseAccessors(bean, instance); + exerciseObjectOverrides(instance); + } + assertTrue(exercised > 100, "expected most data beans to be instantiable, instantiated " + exercised); + } + + @Test + @DisplayName("every data bean constructor accepts a full set of arguments") + void allDataBeanConstructorsAcceptArguments() throws Exception { + for (Class bean : loadBeanClasses("com.iemr.mmu.data")) { + for (Constructor ctor : bean.getDeclaredConstructors()) { + try { + ctor.setAccessible(true); + Class[] types = ctor.getParameterTypes(); + Object[] args = new Object[types.length]; + for (int i = 0; i < args.length; i++) { + args[i] = sampleValue(types[i]); + } + exerciseObjectOverrides(ctor.newInstance(args)); + } catch (Throwable ignored) { + // a constructor that rejects generic sample data is not a defect + } + } + } + } + + @Test + @DisplayName("the static row mappers on the data beans tolerate empty and sparse result rows") + void staticRowMappersHandleEmptyAndSparseRows() throws Exception { + for (Class bean : loadBeanClasses("com.iemr.mmu.data")) { + for (Method method : bean.getDeclaredMethods()) { + if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) { + continue; + } + // These are pure result-set mappers: an empty list and a single all-null + // row together walk both the "nothing to map" and the "map a row" paths. + invokeQuietly(method, new ArrayList<>()); + invokeQuietly(method, sparseResultRows()); + } + } + } + + private void invokeQuietly(Method method, Object listArgument) { + Class[] types = method.getParameterTypes(); + Object[] args = new Object[types.length]; + for (int i = 0; i < args.length; i++) { + args[i] = List.class.isAssignableFrom(types[i]) ? listArgument : sampleValue(types[i]); + } + try { + method.setAccessible(true); + method.invoke(null, args); + } catch (Throwable ignored) { + // a mapper that needs real column values is not this test's concern + } + } + + /** One result row wide enough for any mapper here, with every column null. */ + private List sparseResultRows() { + List rows = new ArrayList<>(); + rows.add(new Object[80]); + return rows; + } + + /** Invokes every no-arg getter and every single-arg setter on the bean. */ + private void exerciseAccessors(Class bean, Object instance) { + for (Method method : bean.getMethods()) { + if (Modifier.isStatic(method.getModifiers()) || SKIPPED_METHODS.contains(method.getName())) { + continue; + } + try { + if (method.getParameterCount() == 0) { + method.invoke(instance); + } else if (method.getParameterCount() == 1) { + method.invoke(instance, sampleValue(method.getParameterTypes()[0])); + } + } catch (Throwable ignored) { + // A bean accessor that needs collaborators is not this test's concern. + } + } + // Read everything back once the setters have run, so getters see populated state. + for (Method method : bean.getMethods()) { + if (!Modifier.isStatic(method.getModifiers()) && method.getParameterCount() == 0 + && !SKIPPED_METHODS.contains(method.getName())) { + try { + method.invoke(instance); + } catch (Throwable ignored) { + // see above + } + } + } + } + + private void exerciseObjectOverrides(Object instance) { + try { + instance.toString(); + instance.hashCode(); + instance.equals(instance); + instance.equals(null); + instance.equals(new Object()); + } catch (Throwable ignored) { + // see above + } + } + + /** Builds a throwaway value for a setter parameter of the given type. */ + static Object sampleValue(Class type) { + if (type == String.class) { + return "test"; + } + if (type == int.class || type == Integer.class) { + return Integer.valueOf(1); + } + if (type == long.class || type == Long.class) { + return Long.valueOf(1L); + } + if (type == short.class || type == Short.class) { + return Short.valueOf((short) 1); + } + if (type == double.class || type == Double.class) { + return Double.valueOf(1d); + } + if (type == float.class || type == Float.class) { + return Float.valueOf(1f); + } + if (type == boolean.class || type == Boolean.class) { + return Boolean.TRUE; + } + if (type == char.class || type == Character.class) { + return Character.valueOf('a'); + } + if (type == byte.class || type == Byte.class) { + return Byte.valueOf((byte) 1); + } + if (type == BigInteger.class) { + return BigInteger.ONE; + } + if (type == Timestamp.class) { + return new Timestamp(System.currentTimeMillis()); + } + if (type == Date.class) { + return new Date(System.currentTimeMillis()); + } + if (type == java.util.Date.class) { + return new java.util.Date(); + } + if (type == byte[].class) { + return new byte[] { 1 }; + } + if (type == List.class || type == ArrayList.class || type == Iterable.class) { + return new ArrayList<>(); + } + if (type == Set.class) { + return new HashSet<>(); + } + if (type == java.util.Map.class) { + return new HashMap<>(); + } + if (type.isArray()) { + return java.lang.reflect.Array.newInstance(type.getComponentType(), 0); + } + if (type.isEnum()) { + Object[] constants = type.getEnumConstants(); + return constants.length > 0 ? constants[0] : null; + } + return null; + } + + /** Instantiates the bean via its no-arg constructor, or null when it has none. */ + private Object instantiate(Class bean) { + try { + Constructor ctor = bean.getDeclaredConstructor(); + ctor.setAccessible(true); + return ctor.newInstance(); + } catch (Throwable noDefaultConstructor) { + return instantiateWithAnyConstructor(bean); + } + } + + private Object instantiateWithAnyConstructor(Class bean) { + for (Constructor ctor : bean.getDeclaredConstructors()) { + try { + ctor.setAccessible(true); + Object[] args = new Object[ctor.getParameterCount()]; + Class[] types = ctor.getParameterTypes(); + for (int i = 0; i < args.length; i++) { + args[i] = sampleValue(types[i]); + } + return ctor.newInstance(args); + } catch (Throwable ignored) { + // try the next constructor + } + } + return null; + } + + /** Finds every concrete class in the package by walking the compiled classes directory. */ + static List> loadBeanClasses(String packageName) throws Exception { + List> classes = new ArrayList<>(); + // The package name exists under both target/classes and target/test-classes, + // so every classpath root has to be walked, not just the first match. + java.util.Enumeration roots = Thread.currentThread().getContextClassLoader() + .getResources(packageName.replace('.', '/')); + assertTrue(roots.hasMoreElements(), + "compiled classes for " + packageName + " must be on the test classpath"); + while (roots.hasMoreElements()) { + collect(new File(roots.nextElement().toURI()), packageName, classes); + } + return classes; + } + + private static void collect(File dir, String packageName, List> classes) { + File[] entries = dir.listFiles(); + if (entries == null) { + return; + } + for (File entry : entries) { + if (entry.isDirectory()) { + collect(entry, packageName + "." + entry.getName(), classes); + } else if (entry.getName().endsWith(".class") && !entry.getName().contains("$")) { + String name = packageName + "." + entry.getName().replace(".class", ""); + try { + Class loaded = Class.forName(name, false, + Thread.currentThread().getContextClassLoader()); + if (!loaded.isInterface() && !loaded.isEnum() && !loaded.isAnnotation() + && !Modifier.isAbstract(loaded.getModifiers())) { + classes.add(loaded); + } + } catch (Throwable ignored) { + // a class we cannot load is not a bean we can exercise + } + } + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/cancerScreening/CSNurseServiceImplTest.java b/src/test/java/com/iemr/mmu/service/cancerScreening/CSNurseServiceImplTest.java new file mode 100644 index 00000000..19667c9a --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/cancerScreening/CSNurseServiceImplTest.java @@ -0,0 +1,933 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.cancerScreening; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.iemr.mmu.data.doctor.CancerAbdominalExamination; +import com.iemr.mmu.data.doctor.CancerBreastExamination; +import com.iemr.mmu.data.doctor.CancerExaminationImageAnnotation; +import com.iemr.mmu.data.doctor.CancerGynecologicalExamination; +import com.iemr.mmu.data.doctor.CancerLymphNodeDetails; +import com.iemr.mmu.data.doctor.CancerOralExamination; +import com.iemr.mmu.data.doctor.CancerSignAndSymptoms; +import com.iemr.mmu.data.doctor.WrapperCancerExamImgAnotasn; +import com.iemr.mmu.data.doctor.WrapperCancerSymptoms; +import com.iemr.mmu.data.nurse.BenCancerVitalDetail; +import com.iemr.mmu.data.nurse.BenFamilyCancerHistory; +import com.iemr.mmu.data.nurse.BenObstetricCancerHistory; +import com.iemr.mmu.data.nurse.BenPersonalCancerDietHistory; +import com.iemr.mmu.data.nurse.BenPersonalCancerHistory; +import com.iemr.mmu.repo.doctor.CancerAbdominalExaminationRepo; +import com.iemr.mmu.repo.doctor.CancerBreastExaminationRepo; +import com.iemr.mmu.repo.doctor.CancerExaminationImageAnnotationRepo; +import com.iemr.mmu.repo.doctor.CancerGynecologicalExaminationRepo; +import com.iemr.mmu.repo.doctor.CancerLymphNodeExaminationRepo; +import com.iemr.mmu.repo.doctor.CancerOralExaminationRepo; +import com.iemr.mmu.repo.doctor.CancerSignAndSymptomsRepo; +import com.iemr.mmu.repo.nurse.BenCancerVitalDetailRepo; +import com.iemr.mmu.repo.nurse.BenFamilyCancerHistoryRepo; +import com.iemr.mmu.repo.nurse.BenObstetricCancerHistoryRepo; +import com.iemr.mmu.repo.nurse.BenPersonalCancerDietHistoryRepo; +import com.iemr.mmu.repo.nurse.BenPersonalCancerHistoryRepo; +import com.iemr.mmu.repo.nurse.BenVisitDetailRepo; +import com.iemr.mmu.utils.AESEncryption.AESEncryptionDecryption; + +class CSNurseServiceImplTest { + + @Mock + private AESEncryptionDecryption aESEncryptionDecryption; + @Mock + private BenFamilyCancerHistoryRepo benFamilyCancerHistoryRepo; + @Mock + private BenPersonalCancerHistoryRepo benPersonalCancerHistoryRepo; + @Mock + private BenPersonalCancerDietHistoryRepo benPersonalCancerDietHistoryRepo; + @Mock + private BenObstetricCancerHistoryRepo benObstetricCancerHistoryRepo; + @Mock + private BenCancerVitalDetailRepo benCancerVitalDetailRepo; + @Mock + private BenVisitDetailRepo benVisitDetailRepo; + @Mock + private CancerAbdominalExaminationRepo cancerAbdominalExaminationRepo; + @Mock + private CancerBreastExaminationRepo cancerBreastExaminationRepo; + @Mock + private CancerGynecologicalExaminationRepo cancerGynecologicalExaminationRepo; + @Mock + private CancerSignAndSymptomsRepo cancerSignAndSymptomsRepo; + @Mock + private CancerLymphNodeExaminationRepo cancerLymphNodeExaminationRepo; + @Mock + private CancerOralExaminationRepo cancerOralExaminationRepo; + @Mock + private CancerExaminationImageAnnotationRepo cancerExaminationImageAnnotationRepo; + + @InjectMocks + private CSNurseServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + /** The (id, processed) pairs the update methods read before deleting the old rows. */ + private ArrayList statusRows(Object id, String processed) { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { id, processed }); + return rows; + } + + /** A stored row wide enough for any of the cancer history mappers, with no values set. */ + private ArrayList oneEmptyRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[20]); + return rows; + } + + @Nested + @DisplayName("history saves") + class HistorySaves { + + @Test + void saveBenFamilyCancerHistory_flattensTheFamilyMembersOfEachDisease() { + BenFamilyCancerHistory withMembers = new BenFamilyCancerHistory(); + withMembers.setFamilyMemberList(Arrays.asList("Mother", "Father")); + BenFamilyCancerHistory withoutMembers = new BenFamilyCancerHistory(); + + when(benFamilyCancerHistoryRepo.saveAll(any())) + .thenReturn(Collections.singletonList(withMembers)); + + assertEquals(1, service.saveBenFamilyCancerHistory(Arrays.asList(withMembers, withoutMembers))); + assertEquals("Mother,Father", withMembers.getFamilyMember()); + } + + @Test + void saveBenFamilyCancerHistory_reportsFailureWhenNotEveryDiseaseWasStored() { + BenFamilyCancerHistory withMembers = new BenFamilyCancerHistory(); + withMembers.setFamilyMemberList(Collections.singletonList("Mother")); + when(benFamilyCancerHistoryRepo.saveAll(any())).thenReturn(new ArrayList<>()); + + assertEquals(0, service.saveBenFamilyCancerHistory(Collections.singletonList(withMembers))); + } + + @Test + void saveBenPersonalCancerHistory_flattensTheTobaccoProductsBeforeSaving() { + BenPersonalCancerHistory history = new BenPersonalCancerHistory(); + history.setTypeOfTobaccoProductList(Arrays.asList("Bidi", "Gutkha")); + BenPersonalCancerHistory stored = new BenPersonalCancerHistory(); + stored.setID(1L); + when(benPersonalCancerHistoryRepo.save(history)).thenReturn(stored); + + assertEquals(1L, service.saveBenPersonalCancerHistory(history)); + assertEquals("Bidi,Gutkha,", history.getTypeOfTobaccoProduct()); + } + + @Test + void saveBenPersonalCancerHistory_storesAnEmptyProductListAsAnEmptyString() { + BenPersonalCancerHistory history = new BenPersonalCancerHistory(); + when(benPersonalCancerHistoryRepo.save(history)).thenReturn(null); + + assertNull(service.saveBenPersonalCancerHistory(history)); + assertEquals("", history.getTypeOfTobaccoProduct()); + } + + @Test + void saveBenPersonalCancerDietHistory_flattensTheOilsConsumedBeforeSaving() { + BenPersonalCancerDietHistory history = new BenPersonalCancerDietHistory(); + history.setTypeOfOilConsumedList(Arrays.asList("Mustard", "Sunflower")); + BenPersonalCancerDietHistory stored = new BenPersonalCancerDietHistory(); + stored.setID(2L); + when(benPersonalCancerDietHistoryRepo.save(history)).thenReturn(stored); + + assertEquals(2L, service.saveBenPersonalCancerDietHistory(history)); + assertEquals("Mustard,Sunflower,", history.getTypeOfOilConsumed()); + } + + @Test + void saveBenPersonalCancerDietHistory_returnsNullWhenNothingWasStored() { + BenPersonalCancerDietHistory history = new BenPersonalCancerDietHistory(); + when(benPersonalCancerDietHistoryRepo.save(history)).thenReturn(null); + assertNull(service.saveBenPersonalCancerDietHistory(history)); + } + + @Test + void saveBenObstetricCancerHistory_returnsTheStoredId() { + BenObstetricCancerHistory history = new BenObstetricCancerHistory(); + BenObstetricCancerHistory stored = new BenObstetricCancerHistory(); + stored.setID(3L); + when(benObstetricCancerHistoryRepo.save(history)).thenReturn(stored); + assertEquals(3L, service.saveBenObstetricCancerHistory(history)); + + when(benObstetricCancerHistoryRepo.save(history)).thenReturn(null); + assertNull(service.saveBenObstetricCancerHistory(history)); + } + + @Test + void saveBenVitalDetail_returnsTheStoredId() { + BenCancerVitalDetail vital = new BenCancerVitalDetail(); + BenCancerVitalDetail stored = new BenCancerVitalDetail(); + stored.setID(4L); + when(benCancerVitalDetailRepo.save(vital)).thenReturn(stored); + assertEquals(4L, service.saveBenVitalDetail(vital)); + + when(benCancerVitalDetailRepo.save(vital)).thenReturn(null); + assertNull(service.saveBenVitalDetail(vital)); + } + } + + @Nested + @DisplayName("history updates") + class HistoryUpdates { + + @Test + void updateBeneficiaryFamilyCancerHistory_replacesTheStoredFamilyHistory() { + BenFamilyCancerHistory history = new BenFamilyCancerHistory(); + history.setBeneficiaryRegID(1L); + history.setVisitCode(2L); + history.setFamilyMemberList(Collections.singletonList("Mother")); + history.setModifiedBy("nurse"); + List input = Collections.singletonList(history); + + when(benFamilyCancerHistoryRepo.getFamilyCancerHistoryStatus(1L, 2L)).thenReturn(statusRows(5L, "P")); + when(benFamilyCancerHistoryRepo.deleteExistingFamilyRecord(5L, "U")).thenReturn(1); + when(benFamilyCancerHistoryRepo.saveAll(any())).thenReturn(new ArrayList<>(input)); + + assertEquals(1, service.updateBeneficiaryFamilyCancerHistory(input)); + assertEquals("Mother,", history.getFamilyMember()); + assertEquals("nurse", history.getCreatedBy()); + } + + @Test + void updateBeneficiaryFamilyCancerHistory_succeedsWhenNoFamilyMemberIsLeft() { + BenFamilyCancerHistory history = new BenFamilyCancerHistory(); + List input = Collections.singletonList(history); + when(benFamilyCancerHistoryRepo.getFamilyCancerHistoryStatus(any(), any())).thenReturn(new ArrayList<>()); + + assertEquals(1, service.updateBeneficiaryFamilyCancerHistory(input)); + } + + @Test + void updateBeneficiaryFamilyCancerHistory_reportsFailureWhenTheOldRowsCouldNotBeCleared() { + BenFamilyCancerHistory history = new BenFamilyCancerHistory(); + List input = Collections.singletonList(history); + when(benFamilyCancerHistoryRepo.getFamilyCancerHistoryStatus(any(), any())) + .thenReturn(statusRows(5L, "N")); + when(benFamilyCancerHistoryRepo.deleteExistingFamilyRecord(5L, "N")).thenReturn(0); + + assertEquals(0, service.updateBeneficiaryFamilyCancerHistory(input)); + } + + @Test + void updateBeneficiaryFamilyCancerHistory_reportsFailureWhenTheHistoryIsEmpty() { + assertEquals(0, service.updateBeneficiaryFamilyCancerHistory(new ArrayList<>())); + } + + @Test + void updateBenObstetricCancerHistory_updatesTheStoredRowWhenOneExists() { + BenObstetricCancerHistory history = new BenObstetricCancerHistory(); + when(benObstetricCancerHistoryRepo.getObstetricCancerHistoryStatus(any(), any())).thenReturn("P"); + when(benObstetricCancerHistoryRepo.updateBenObstetricCancerHistory(any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), + any(), any(), eq("U"))).thenReturn(1); + + assertEquals(1, service.updateBenObstetricCancerHistory(history)); + } + + @Test + void updateBenObstetricCancerHistory_insertsAFreshRowWhenNoneIsStoredYet() { + BenObstetricCancerHistory history = new BenObstetricCancerHistory(); + history.setModifiedBy("nurse"); + BenObstetricCancerHistory stored = new BenObstetricCancerHistory(); + stored.setID(6L); + when(benObstetricCancerHistoryRepo.getObstetricCancerHistoryStatus(any(), any())).thenReturn(null); + when(benObstetricCancerHistoryRepo.save(history)).thenReturn(stored); + + assertEquals(1, service.updateBenObstetricCancerHistory(history)); + assertEquals("nurse", history.getCreatedBy()); + assertEquals("N", history.getProcessed()); + } + + @Test + void updateBenObstetricCancerHistory_reportsFailureWhenTheFreshRowWasNotStored() { + BenObstetricCancerHistory history = new BenObstetricCancerHistory(); + when(benObstetricCancerHistoryRepo.getObstetricCancerHistoryStatus(any(), any())).thenReturn(null); + when(benObstetricCancerHistoryRepo.save(history)).thenReturn(null); + + assertEquals(0, service.updateBenObstetricCancerHistory(history)); + } + + @Test + void updateBenPersonalCancerHistory_updatesTheStoredRowWhenOneExists() { + BenPersonalCancerHistory history = new BenPersonalCancerHistory(); + when(benPersonalCancerHistoryRepo.getPersonalCancerHistoryStatus(any(), any())).thenReturn("P"); + when(benPersonalCancerHistoryRepo.updateBenPersonalCancerHistory(any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq("U"))).thenReturn(1); + + assertEquals(1, service.updateBenPersonalCancerHistory(history)); + } + + @Test + void updateBenPersonalCancerHistory_insertsAFreshRowWhenNoneIsStoredYet() { + BenPersonalCancerHistory history = new BenPersonalCancerHistory(); + BenPersonalCancerHistory stored = new BenPersonalCancerHistory(); + stored.setID(7L); + when(benPersonalCancerHistoryRepo.getPersonalCancerHistoryStatus(any(), any())).thenReturn(null); + when(benPersonalCancerHistoryRepo.save(history)).thenReturn(stored); + + assertEquals(1, service.updateBenPersonalCancerHistory(history)); + } + + @Test + void updateBenPersonalCancerDietHistory_updatesTheStoredRowWhenOneExists() { + BenPersonalCancerDietHistory history = new BenPersonalCancerDietHistory(); + when(benPersonalCancerDietHistoryRepo.getPersonalCancerDietHistoryStatus(any(), any())).thenReturn("P"); + when(benPersonalCancerDietHistoryRepo.updateBenPersonalCancerDietHistory(any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), eq("U"))).thenReturn(1); + + assertEquals(1, service.updateBenPersonalCancerDietHistory(history)); + } + + @Test + void updateBenPersonalCancerDietHistory_insertsAFreshRowWhenNoneIsStoredYet() { + BenPersonalCancerDietHistory history = new BenPersonalCancerDietHistory(); + BenPersonalCancerDietHistory stored = new BenPersonalCancerDietHistory(); + stored.setID(8L); + when(benPersonalCancerDietHistoryRepo.getPersonalCancerDietHistoryStatus(any(), any())).thenReturn(null); + when(benPersonalCancerDietHistoryRepo.save(history)).thenReturn(stored); + + assertEquals(1, service.updateBenPersonalCancerDietHistory(history)); + } + + @Test + void updateBenVitalDetail_updatesTheStoredRowWhenOneExists() { + BenCancerVitalDetail vital = new BenCancerVitalDetail(); + when(benCancerVitalDetailRepo.getCancerVitalStatus(any(), any())).thenReturn("P"); + when(benCancerVitalDetailRepo.updateBenCancerVitalDetail(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq("U"), any(), any(), + any())).thenReturn(1); + + assertEquals(1, service.updateBenVitalDetail(vital)); + } + + @Test + void updateBenVitalDetail_insertsAFreshRowWhenNoneIsStoredYet() { + BenCancerVitalDetail vital = new BenCancerVitalDetail(); + vital.setModifiedBy("nurse"); + when(benCancerVitalDetailRepo.getCancerVitalStatus(any(), any())).thenReturn(null); + when(benCancerVitalDetailRepo.save(vital)).thenReturn(vital); + + assertEquals(1, service.updateBenVitalDetail(vital)); + assertEquals("nurse", vital.getCreatedBy()); + + when(benCancerVitalDetailRepo.save(vital)).thenReturn(null); + assertEquals(0, service.updateBenVitalDetail(vital)); + } + } + + @Nested + @DisplayName("case sheet reads") + class CaseSheetReads { + + @Test + void getBenFamilyHisData_splitsTheStoredFamilyMembersBackIntoAList() { + BenFamilyCancerHistory withMembers = new BenFamilyCancerHistory(); + withMembers.setFamilyMember("Mother,Father"); + BenFamilyCancerHistory withoutMembers = new BenFamilyCancerHistory(); + when(benFamilyCancerHistoryRepo.getBenFamilyHistory(1L, 2L)) + .thenReturn(Arrays.asList(withMembers, withoutMembers)); + + List result = service.getBenFamilyHisData(1L, 2L); + + assertEquals(Arrays.asList("Mother", "Father"), result.get(0).getFamilyMemberList()); + assertTrue(result.get(1).getFamilyMemberList().isEmpty()); + } + + @Test + void getBenFamilyHisData_returnsAnEmptyListWhenNoHistoryWasRecorded() { + when(benFamilyCancerHistoryRepo.getBenFamilyHistory(1L, 2L)).thenReturn(new ArrayList<>()); + assertTrue(service.getBenFamilyHisData(1L, 2L).isEmpty()); + } + + @Test + void getBenPersonalCancerHistoryData_splitsTheStoredTobaccoProductsBackIntoAList() { + BenPersonalCancerHistory stored = new BenPersonalCancerHistory(); + stored.setTypeOfTobaccoProduct("Bidi,Gutkha"); + when(benPersonalCancerHistoryRepo.getBenPersonalHistory(1L, 2L)).thenReturn(stored); + + assertEquals(Arrays.asList("Bidi", "Gutkha"), + service.getBenPersonalCancerHistoryData(1L, 2L).getTypeOfTobaccoProductList()); + } + + @Test + void getBenPersonalCancerHistoryData_returnsNullWhenNoHistoryWasRecorded() { + when(benPersonalCancerHistoryRepo.getBenPersonalHistory(1L, 2L)).thenReturn(null); + assertNull(service.getBenPersonalCancerHistoryData(1L, 2L)); + } + + @Test + void getBenPersonalCancerDietHistoryData_splitsTheStoredOilsBackIntoAList() { + BenPersonalCancerDietHistory stored = new BenPersonalCancerDietHistory(); + stored.setTypeOfOilConsumed("Mustard,Sunflower"); + when(benPersonalCancerDietHistoryRepo.getBenPersonaDietHistory(1L, 2L)).thenReturn(stored); + + assertEquals(Arrays.asList("Mustard", "Sunflower"), + service.getBenPersonalCancerDietHistoryData(1L, 2L).getTypeOfOilConsumedList()); + + when(benPersonalCancerDietHistoryRepo.getBenPersonaDietHistory(3L, 4L)).thenReturn(null); + assertNull(service.getBenPersonalCancerDietHistoryData(3L, 4L)); + } + + @Test + void getBenCancerGynecologicalExaminationData_decryptsEachAttachedFilePath() throws Exception { + CancerGynecologicalExamination stored = new CancerGynecologicalExamination(); + stored.setFilePath("enc1,,enc2"); + when(cancerGynecologicalExaminationRepo.getBenCancerGynecologicalExaminationDetails(1L, 2L)) + .thenReturn(stored); + when(aESEncryptionDecryption.decrypt("enc1")).thenReturn("/reports/first.png"); + when(aESEncryptionDecryption.decrypt("enc2")).thenThrow(new RuntimeException("bad key")); + + CancerGynecologicalExamination result = service.getBenCancerGynecologicalExaminationData(1L, 2L); + + assertEquals(1, result.getFiles().size()); + assertEquals("first.png", result.getFiles().get(0).get("fileName")); + } + + @Test + void getBenCancerGynecologicalExaminationData_leavesTheFileListUnsetWhenNothingIsAttached() { + CancerGynecologicalExamination stored = new CancerGynecologicalExamination(); + stored.setFilePath(" "); + when(cancerGynecologicalExaminationRepo.getBenCancerGynecologicalExaminationDetails(1L, 2L)) + .thenReturn(stored); + assertNull(service.getBenCancerGynecologicalExaminationData(1L, 2L).getFiles()); + + when(cancerGynecologicalExaminationRepo.getBenCancerGynecologicalExaminationDetails(3L, 4L)) + .thenReturn(null); + assertNull(service.getBenCancerGynecologicalExaminationData(3L, 4L)); + } + + @Test + void getCancerExaminationImageAnnotationCasesheet_groupsTheMarkersByImage() { + CancerExaminationImageAnnotation firstMarker = annotation(1, 10, 20, 1); + CancerExaminationImageAnnotation secondMarkerSameImage = annotation(1, 30, 40, 2); + CancerExaminationImageAnnotation markerOnOtherImage = annotation(2, 50, 60, 1); + when(cancerExaminationImageAnnotationRepo.getCancerExaminationImageAnnotationList(1L, 2L)) + .thenReturn(Arrays.asList(firstMarker, secondMarkerSameImage, markerOnOtherImage)); + + ArrayList result = service + .getCancerExaminationImageAnnotationCasesheet(1L, 2L); + + assertEquals(2, result.size()); + assertEquals(2, result.get(0).getMarkers().size()); + assertEquals(1, result.get(1).getMarkers().size()); + } + + @Test + void getCancerExaminationImageAnnotationCasesheet_returnsNothingWhenNoImageWasAnnotated() { + when(cancerExaminationImageAnnotationRepo.getCancerExaminationImageAnnotationList(1L, 2L)) + .thenReturn(new ArrayList<>()); + assertTrue(service.getCancerExaminationImageAnnotationCasesheet(1L, 2L).isEmpty()); + } + + private CancerExaminationImageAnnotation annotation(int imageId, int x, int y, int point) { + CancerExaminationImageAnnotation annotation = new CancerExaminationImageAnnotation(); + annotation.setCancerImageID(imageId); + annotation.setxCoordinate(x); + annotation.setyCoordinate(y); + annotation.setPoint(point); + annotation.setPointDesc("marker"); + return annotation; + } + + @Test + void getBeneficiaryVisitDetails_mapsTheStoredVisitRow() { + when(benVisitDetailRepo.getBeneficiaryVisitDetails(1L, 2L)).thenReturn(oneEmptyRow()); + assertNotNull(service.getBeneficiaryVisitDetails(1L, 2L)); + + when(benVisitDetailRepo.getBeneficiaryVisitDetails(3L, 4L)).thenReturn(null); + assertNull(service.getBeneficiaryVisitDetails(3L, 4L)); + } + + @Test + void getBenNurseDataForCaseSheet_gathersEverySectionOfTheNurseCaseSheet() { + when(benVisitDetailRepo.getBeneficiaryVisitDetails(1L, 2L)).thenReturn(new ArrayList<>()); + when(benFamilyCancerHistoryRepo.getBenFamilyHistory(1L, 2L)).thenReturn(new ArrayList<>()); + when(cancerLymphNodeExaminationRepo.getBenCancerLymphNodeDetails(1L, 2L)).thenReturn(new ArrayList<>()); + + Map caseSheet = service.getBenNurseDataForCaseSheet(1L, 2L); + + assertEquals(12, caseSheet.size()); + assertTrue(caseSheet.containsKey("oralExamination")); + } + + @Test + void theRemainingExaminationReadsDelegateToTheirRepositories() { + BenObstetricCancerHistory obstetric = new BenObstetricCancerHistory(); + when(benObstetricCancerHistoryRepo.getBenObstetricCancerHistory(1L, 2L)).thenReturn(obstetric); + assertEquals(obstetric, service.getBenObstetricDetailsData(1L, 2L)); + + BenCancerVitalDetail vital = new BenCancerVitalDetail(); + when(benCancerVitalDetailRepo.getBenCancerVitalDetail(1L, 2L)).thenReturn(vital); + assertEquals(vital, service.getBenCancerVitalDetailData(1L, 2L)); + + CancerAbdominalExamination abdominal = new CancerAbdominalExamination(); + when(cancerAbdominalExaminationRepo.getBenCancerAbdominalExaminationDetails(1L, 2L)).thenReturn(abdominal); + assertEquals(abdominal, service.getBenCancerAbdominalExaminationData(1L, 2L)); + + CancerBreastExamination breast = new CancerBreastExamination(); + when(cancerBreastExaminationRepo.getBenCancerBreastExaminationDetails(1L, 2L)).thenReturn(breast); + assertEquals(breast, service.getBenCancerBreastExaminationData(1L, 2L)); + + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + when(cancerSignAndSymptomsRepo.getBenCancerSignAndSymptomsDetails(1L, 2L)).thenReturn(symptoms); + assertEquals(symptoms, service.getBenCancerSignAndSymptomsData(1L, 2L)); + + List lymphNodes = new ArrayList<>(); + when(cancerLymphNodeExaminationRepo.getBenCancerLymphNodeDetails(1L, 2L)).thenReturn(lymphNodes); + assertEquals(lymphNodes, service.getBenCancerLymphNodeDetailsData(1L, 2L)); + + CancerOralExamination oral = new CancerOralExamination(); + when(cancerOralExaminationRepo.getBenCancerOralExaminationDetails(1L, 2L)).thenReturn(oral); + assertEquals(oral, service.getBenCancerOralExaminationData(1L, 2L)); + } + } + + @Nested + @DisplayName("past visit history tables") + class HistoryTables { + + @Test + void getBenCancerFamilyHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benFamilyCancerHistoryRepo.getBenCancerFamilyHistory(1L)).thenReturn(oneEmptyRow()); + assertTrue(service.getBenCancerFamilyHistory(1L).contains("\"data\":[{")); + + when(benFamilyCancerHistoryRepo.getBenCancerFamilyHistory(2L)).thenReturn(null); + assertTrue(service.getBenCancerFamilyHistory(2L).contains("\"data\":[]")); + } + + @Test + void getBenCancerPersonalHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benPersonalCancerHistoryRepo.getBenPersonalHistory(1L)).thenReturn(oneEmptyRow()); + assertTrue(service.getBenCancerPersonalHistory(1L).contains("\"data\":[{")); + + when(benPersonalCancerHistoryRepo.getBenPersonalHistory(2L)).thenReturn(null); + assertTrue(service.getBenCancerPersonalHistory(2L).contains("\"data\":[]")); + } + + @Test + void getBenCancerPersonalDietHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benPersonalCancerDietHistoryRepo.getBenPersonaDietHistory(1L)).thenReturn(oneEmptyRow()); + assertTrue(service.getBenCancerPersonalDietHistory(1L).contains("\"data\":[{")); + + when(benPersonalCancerDietHistoryRepo.getBenPersonaDietHistory(2L)).thenReturn(null); + assertTrue(service.getBenCancerPersonalDietHistory(2L).contains("\"data\":[]")); + } + + @Test + void getBenCancerObstetricHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benObstetricCancerHistoryRepo.getBenObstetricCancerHistoryData(1L)).thenReturn(oneEmptyRow()); + assertTrue(service.getBenCancerObstetricHistory(1L).contains("\"data\":[{")); + + when(benObstetricCancerHistoryRepo.getBenObstetricCancerHistoryData(2L)).thenReturn(null); + assertTrue(service.getBenCancerObstetricHistory(2L).contains("\"data\":[]")); + } + } + + @Nested + @DisplayName("examination saves") + class ExaminationSaves { + + @Test + void saveLymphNodeDetails_stampsTheVisitOntoEveryNodeAndReturnsTheLastId() { + CancerLymphNodeDetails node = new CancerLymphNodeDetails(); + CancerLymphNodeDetails stored = new CancerLymphNodeDetails(); + stored.setID(9L); + List nodes = Collections.singletonList(node); + when(cancerLymphNodeExaminationRepo.saveAll(nodes)).thenReturn(Collections.singletonList(stored)); + + assertEquals(9L, service.saveLymphNodeDetails(nodes, 1L, 2L)); + assertEquals(1L, node.getBenVisitID()); + assertEquals(2L, node.getVisitCode()); + } + + @Test + void saveLymphNodeDetails_returnsNullWhenNoNodeWasStored() { + List nodes = Collections.singletonList(new CancerLymphNodeDetails()); + when(cancerLymphNodeExaminationRepo.saveAll(nodes)).thenReturn(new ArrayList<>()); + assertNull(service.saveLymphNodeDetails(nodes, 1L, 2L)); + } + + @Test + void saveCancerSignAndSymptomsData_stampsTheVisitBeforeSaving() { + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + CancerSignAndSymptoms stored = new CancerSignAndSymptoms(); + stored.setID(10L); + when(cancerSignAndSymptomsRepo.save(symptoms)).thenReturn(stored); + + assertEquals(10L, service.saveCancerSignAndSymptomsData(symptoms, 1L, 2L)); + assertEquals(1L, symptoms.getBenVisitID()); + assertEquals(2L, symptoms.getVisitCode()); + + when(cancerSignAndSymptomsRepo.save(symptoms)).thenReturn(null); + assertNull(service.saveCancerSignAndSymptomsData(symptoms)); + } + + @Test + void saveCancerOralExaminationData_flattensThePreMalignantLesionTypes() { + CancerOralExamination oral = new CancerOralExamination(); + oral.setPreMalignantLesionTypeList(Arrays.asList("Leukoplakia", "Erythroplakia")); + CancerOralExamination stored = new CancerOralExamination(); + stored.setID(11L); + when(cancerOralExaminationRepo.save(oral)).thenReturn(stored); + + assertEquals(11L, service.saveCancerOralExaminationData(oral)); + assertEquals("Leukoplakia,Erythroplakia,", oral.getPreMalignantLesionType()); + + when(cancerOralExaminationRepo.save(oral)).thenReturn(null); + assertNull(service.saveCancerOralExaminationData(oral)); + } + + @Test + void saveCancerBreastExaminationData_returnsTheStoredId() { + CancerBreastExamination breast = new CancerBreastExamination(); + CancerBreastExamination stored = new CancerBreastExamination(); + stored.setID(12L); + when(cancerBreastExaminationRepo.save(breast)).thenReturn(stored); + assertEquals(12L, service.saveCancerBreastExaminationData(breast)); + + when(cancerBreastExaminationRepo.save(breast)).thenReturn(null); + assertNull(service.saveCancerBreastExaminationData(breast)); + } + + @Test + void saveCancerAbdominalExaminationData_returnsTheStoredId() { + CancerAbdominalExamination abdominal = new CancerAbdominalExamination(); + CancerAbdominalExamination stored = new CancerAbdominalExamination(); + stored.setID(13L); + when(cancerAbdominalExaminationRepo.save(abdominal)).thenReturn(stored); + assertEquals(13L, service.saveCancerAbdominalExaminationData(abdominal)); + + when(cancerAbdominalExaminationRepo.save(abdominal)).thenReturn(null); + assertNull(service.saveCancerAbdominalExaminationData(abdominal)); + } + + @Test + void saveCancerGynecologicalExaminationData_flattensTheLesionTypes() { + CancerGynecologicalExamination gynecological = new CancerGynecologicalExamination(); + gynecological.setTypeOfLesionList(Arrays.asList("Polyp", "Ulcer")); + CancerGynecologicalExamination stored = new CancerGynecologicalExamination(); + stored.setID(14L); + when(cancerGynecologicalExaminationRepo.save(gynecological)).thenReturn(stored); + + assertEquals(14L, service.saveCancerGynecologicalExaminationData(gynecological)); + assertEquals("Polyp,Ulcer,", gynecological.getTypeOfLesion()); + + when(cancerGynecologicalExaminationRepo.save(gynecological)).thenReturn(null); + assertNull(service.saveCancerGynecologicalExaminationData(gynecological)); + } + + @Test + void saveDocExaminationImageAnnotation_expandsEveryMarkerIntoItsOwnRow() { + WrapperCancerExamImgAnotasn wrapper = new WrapperCancerExamImgAnotasn(); + wrapper.setBeneficiaryRegID(1L); + wrapper.setImageID(2); + ArrayList> markers = new ArrayList<>(); + Map marker = new HashMap<>(); + marker.put("xCord", 10d); + marker.put("yCord", 20d); + marker.put("point", 1d); + marker.put("description", "lesion"); + markers.add(marker); + wrapper.setMarkers(markers); + + when(cancerExaminationImageAnnotationRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + assertEquals(1L, service.saveDocExaminationImageAnnotation(Collections.singletonList(wrapper), 3L, 4L)); + assertEquals(3L, wrapper.getVisitID()); + } + + @Test + void saveDocExaminationImageAnnotation_returnsNullWhenNoMarkerWasDrawn() { + WrapperCancerExamImgAnotasn wrapper = new WrapperCancerExamImgAnotasn(); + when(cancerExaminationImageAnnotationRepo.saveAll(any())).thenReturn(new ArrayList<>()); + assertNull(service.saveDocExaminationImageAnnotation(Collections.singletonList(wrapper), 3L, 4L)); + } + + @Test + void getCancerExaminationImageAnnotationList_ignoresEmptyWrappers() { + assertTrue(service.getCancerExaminationImageAnnotationList(new ArrayList<>(), 1L).isEmpty()); + assertTrue(service + .getCancerExaminationImageAnnotationList(Collections.singletonList(null), 1L).isEmpty()); + } + } + + @Nested + @DisplayName("examination updates") + class ExaminationUpdates { + + @Test + void updateSignAndSymptomsExaminationDetails_updatesTheStoredRowWhenOneExists() { + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + when(cancerSignAndSymptomsRepo.getCancerSignAndSymptomsStatus(any(), any())).thenReturn("P"); + when(cancerSignAndSymptomsRepo.updateCancerSignAndSymptoms(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), + eq("U"))).thenReturn(1); + + assertEquals(1, service.updateSignAndSymptomsExaminationDetails(symptoms)); + } + + @Test + void updateSignAndSymptomsExaminationDetails_insertsAFreshRowWhenNoneIsStoredYet() { + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + CancerSignAndSymptoms stored = new CancerSignAndSymptoms(); + stored.setID(15L); + when(cancerSignAndSymptomsRepo.getCancerSignAndSymptomsStatus(any(), any())).thenReturn(null); + when(cancerSignAndSymptomsRepo.save(symptoms)).thenReturn(stored); + + assertEquals(1, service.updateSignAndSymptomsExaminationDetails(symptoms)); + } + + @Test + void updateLymphNodeExaminationDetails_clearsEveryNodeWhenTheEnlargementFlagIsTurnedOff() { + WrapperCancerSymptoms wrapper = new WrapperCancerSymptoms(); + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + symptoms.setLymphNode_Enlarged(false); + symptoms.setBeneficiaryRegID(1L); + symptoms.setVisitCode(2L); + wrapper.setCancerSignAndSymptoms(symptoms); + wrapper.setCancerLymphNodeDetails(new ArrayList<>()); + + when(cancerLymphNodeExaminationRepo.getCancerLymphNodeDetailsStatus(1L, 2L)) + .thenReturn(statusRows(16L, "P")); + when(cancerLymphNodeExaminationRepo.deleteExistingLymphNodeDetails(16L, "U")).thenReturn(1); + + assertEquals(1, service.updateLymphNodeExaminationDetails(wrapper)); + } + + @Test + void updateLymphNodeExaminationDetails_replacesOnlyTheReportedNodesWhenTheFlagIsOn() { + WrapperCancerSymptoms wrapper = new WrapperCancerSymptoms(); + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + symptoms.setLymphNode_Enlarged(true); + symptoms.setBeneficiaryRegID(1L); + symptoms.setVisitCode(2L); + wrapper.setCancerSignAndSymptoms(symptoms); + + CancerLymphNodeDetails measured = new CancerLymphNodeDetails(); + measured.setLymphNodeName("Cervical"); + measured.setSize_Left("2cm"); + CancerLymphNodeDetails unmeasured = new CancerLymphNodeDetails(); + unmeasured.setLymphNodeName("Cervical"); + wrapper.setCancerLymphNodeDetails(Arrays.asList(measured, unmeasured)); + + when(cancerLymphNodeExaminationRepo.getCancerLymphNodeDetailsStatusForLymphnodeNameList(eq(1L), eq(2L), + any())).thenReturn(statusRows(17L, "N")); + when(cancerLymphNodeExaminationRepo.deleteExistingLymphNodeDetails(17L, "N")).thenReturn(1); + when(cancerLymphNodeExaminationRepo.saveAll(any())) + .thenReturn(new ArrayList<>(Collections.singletonList(measured))); + + assertEquals(1, service.updateLymphNodeExaminationDetails(wrapper)); + } + + @Test + void updateLymphNodeExaminationDetails_succeedsWhenNoNodeWasMeasured() { + WrapperCancerSymptoms wrapper = new WrapperCancerSymptoms(); + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + symptoms.setLymphNode_Enlarged(true); + wrapper.setCancerSignAndSymptoms(symptoms); + wrapper.setCancerLymphNodeDetails(new ArrayList<>()); + + assertEquals(1, service.updateLymphNodeExaminationDetails(wrapper)); + } + + @Test + void updateLymphNodeExaminationDetails_reportsFailureWhenTheOldNodesCouldNotBeCleared() { + WrapperCancerSymptoms wrapper = new WrapperCancerSymptoms(); + CancerSignAndSymptoms symptoms = new CancerSignAndSymptoms(); + symptoms.setLymphNode_Enlarged(false); + wrapper.setCancerSignAndSymptoms(symptoms); + wrapper.setCancerLymphNodeDetails(new ArrayList<>()); + + when(cancerLymphNodeExaminationRepo.getCancerLymphNodeDetailsStatus(any(), any())) + .thenReturn(statusRows(18L, "N")); + when(cancerLymphNodeExaminationRepo.deleteExistingLymphNodeDetails(18L, "N")).thenReturn(0); + + assertEquals(0, service.updateLymphNodeExaminationDetails(wrapper)); + } + + @Test + void updateCancerOralDetails_updatesTheStoredRowWhenOneExists() { + CancerOralExamination oral = new CancerOralExamination(); + when(cancerOralExaminationRepo.getCancerOralExaminationStatus(any(), any())).thenReturn("P"); + when(cancerOralExaminationRepo.updateCancerOralExaminationDetails(any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), eq("U"))).thenReturn(1); + + assertEquals(1, service.updateCancerOralDetails(oral)); + } + + @Test + void updateCancerOralDetails_insertsAFreshRowWhenNoneIsStoredYet() { + CancerOralExamination oral = new CancerOralExamination(); + CancerOralExamination stored = new CancerOralExamination(); + stored.setID(19L); + when(cancerOralExaminationRepo.getCancerOralExaminationStatus(any(), any())).thenReturn(null); + when(cancerOralExaminationRepo.save(oral)).thenReturn(stored); + + assertEquals(1, service.updateCancerOralDetails(oral)); + } + + @Test + void updateCancerBreastDetails_updatesTheStoredRowWhenOneExists() { + CancerBreastExamination breast = new CancerBreastExamination(); + when(cancerBreastExaminationRepo.getCancerBreastExaminationStatus(any(), any())).thenReturn("P"); + when(cancerBreastExaminationRepo.updateCancerBreastExaminatio(any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq("U"))) + .thenReturn(1); + + assertEquals(1, service.updateCancerBreastDetails(breast)); + } + + @Test + void updateCancerBreastDetails_insertsAFreshRowWhenNoneIsStoredYet() { + CancerBreastExamination breast = new CancerBreastExamination(); + CancerBreastExamination stored = new CancerBreastExamination(); + stored.setID(20L); + when(cancerBreastExaminationRepo.getCancerBreastExaminationStatus(any(), any())).thenReturn(null); + when(cancerBreastExaminationRepo.save(breast)).thenReturn(stored); + + assertEquals(1, service.updateCancerBreastDetails(breast)); + } + + @Test + void updateCancerAbdominalExaminationDetails_updatesTheStoredRowWhenOneExists() { + CancerAbdominalExamination abdominal = new CancerAbdominalExamination(); + when(cancerAbdominalExaminationRepo.getCancerAbdominalExaminationStatus(any(), any())).thenReturn("P"); + when(cancerAbdominalExaminationRepo.updateCancerAbdominalExamination(any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq("U"))) + .thenReturn(1); + + assertEquals(1, service.updateCancerAbdominalExaminationDetails(abdominal)); + } + + @Test + void updateCancerAbdominalExaminationDetails_insertsAFreshRowWhenNoneIsStoredYet() { + CancerAbdominalExamination abdominal = new CancerAbdominalExamination(); + CancerAbdominalExamination stored = new CancerAbdominalExamination(); + stored.setID(21L); + when(cancerAbdominalExaminationRepo.getCancerAbdominalExaminationStatus(any(), any())).thenReturn(null); + when(cancerAbdominalExaminationRepo.save(abdominal)).thenReturn(stored); + + assertEquals(1, service.updateCancerAbdominalExaminationDetails(abdominal)); + } + + @Test + void updateCancerGynecologicalExaminationDetails_updatesTheStoredRowWhenOneExists() { + CancerGynecologicalExamination gynecological = new CancerGynecologicalExamination(); + when(cancerGynecologicalExaminationRepo.getCancerGynecologicalExaminationStatus(any(), any())) + .thenReturn("P"); + when(cancerGynecologicalExaminationRepo.updateCancerGynecologicalExamination(any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), eq("U"))).thenReturn(1); + + assertEquals(1, service.updateCancerGynecologicalExaminationDetails(gynecological)); + } + + @Test + void updateCancerGynecologicalExaminationDetails_insertsAFreshRowWhenNoneIsStoredYet() { + CancerGynecologicalExamination gynecological = new CancerGynecologicalExamination(); + CancerGynecologicalExamination stored = new CancerGynecologicalExamination(); + stored.setID(22L); + when(cancerGynecologicalExaminationRepo.getCancerGynecologicalExaminationStatus(any(), any())) + .thenReturn(null); + when(cancerGynecologicalExaminationRepo.save(gynecological)).thenReturn(stored); + + assertEquals(1, service.updateCancerGynecologicalExaminationDetails(gynecological)); + } + + @Test + void updateCancerExamImgAnotasnDetails_replacesTheAnnotationsOfEveryTouchedImage() { + CancerExaminationImageAnnotation complete = new CancerExaminationImageAnnotation(); + complete.setCancerImageID(1); + complete.setBeneficiaryRegID(1L); + complete.setVisitCode(2L); + complete.setxCoordinate(10); + complete.setyCoordinate(20); + complete.setCreatedBy("nurse"); + CancerExaminationImageAnnotation incomplete = new CancerExaminationImageAnnotation(); + incomplete.setCancerImageID(1); + List annotations = Arrays.asList(complete, incomplete); + + when(cancerExaminationImageAnnotationRepo.getCancerExaminationImageAnnotationDetailsStatus(eq(1L), eq(2L), + any())).thenReturn(statusRows(23L, "P")); + when(cancerExaminationImageAnnotationRepo.deleteExistingImageAnnotationDetails(23L, "U")).thenReturn(1); + when(cancerExaminationImageAnnotationRepo.saveAll(any())) + .thenReturn(new ArrayList<>(Collections.singletonList(complete))); + + assertEquals(1, service.updateCancerExamImgAnotasnDetails(annotations)); + assertEquals("nurse", complete.getModifiedBy()); + } + + @Test + void updateCancerExamImgAnotasnDetails_succeedsWhenNoImageWasAnnotated() { + assertEquals(1, service.updateCancerExamImgAnotasnDetails(new ArrayList<>())); + } + + @Test + void updateCancerExamImgAnotasnDetails_reportsFailureWhenTheOldAnnotationsCouldNotBeCleared() { + CancerExaminationImageAnnotation annotation = new CancerExaminationImageAnnotation(); + annotation.setCancerImageID(1); + List annotations = Collections.singletonList(annotation); + + when(cancerExaminationImageAnnotationRepo.getCancerExaminationImageAnnotationDetailsStatus(any(), any(), + any())).thenReturn(statusRows(24L, "N")); + when(cancerExaminationImageAnnotationRepo.deleteExistingImageAnnotationDetails(24L, "N")).thenReturn(0); + + assertEquals(0, service.updateCancerExamImgAnotasnDetails(annotations)); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/cancerScreening/CSServiceImplTest.java b/src/test/java/com/iemr/mmu/service/cancerScreening/CSServiceImplTest.java new file mode 100644 index 00000000..e3dac130 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/cancerScreening/CSServiceImplTest.java @@ -0,0 +1,636 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.cancerScreening; + +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyShort; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.data.doctor.CancerDiagnosis; +import com.iemr.mmu.data.nurse.BenCancerVitalDetail; +import com.iemr.mmu.data.nurse.BeneficiaryVisitDetail; +import com.iemr.mmu.data.nurse.CommonUtilityClass; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.repo.registrar.RegistrarRepoBenData; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; +import com.iemr.mmu.service.tele_consultation.TeleConsultationServiceImpl; + +class CSServiceImplTest { + + @Mock + private CSNurseServiceImpl cSNurseServiceImpl; + @Mock + private CSDoctorServiceImpl cSDoctorServiceImpl; + @Mock + private CSOncologistServiceImpl csOncologistServiceImpl; + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + @Mock + private CSCarestreamServiceImpl cSCarestreamServiceImpl; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private TeleConsultationServiceImpl teleConsultationServiceImpl; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + @Mock + private RegistrarRepoBenData registrarRepoBenData; + + @InjectMocks + private CSServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + private static String visitDetailsBlock() { + return "\"visitDetails\":{\"beneficiaryRegID\":1,\"visitReason\":\"Screening\"," + + "\"visitCategory\":\"Cancer Screening\"}"; + } + + @Nested + @DisplayName("saving nurse data") + class NurseSave { + + @Test + void saveCancerScreeningNurseData_ignoresARequestWithoutVisitDetails() throws Exception { + assertNull(service.saveCancerScreeningNurseData(null, "auth")); + assertNull(service.saveCancerScreeningNurseData(json("{}"), "auth")); + } + + @Test + void saveCancerScreeningNurseData_skipsTheVisitWhenTheNurseAlreadySavedThisFlow() throws Exception { + when(beneficiaryFlowStatusRepo.checkExistData(any(), any())).thenReturn(new BeneficiaryFlowStatus()); + + assertEquals(0L, service.saveCancerScreeningNurseData(json("{" + visitDetailsBlock() + "}"), "auth")); + verify(commonNurseServiceImpl, never()).saveBeneficiaryVisitDetails(any()); + } + + @Test + void saveCancerScreeningNurseData_returnsZeroWhenTheVisitWasNotCreated() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(1); + + assertEquals(0L, service.saveCancerScreeningNurseData(json("{" + visitDetailsBlock() + "}"), "auth")); + } + + @Test + void saveCancerScreeningNurseData_sendsTheBeneficiaryToTheOncologistByDefault() throws Exception { + stubVisitCreation(); + when(cSNurseServiceImpl.saveBenVitalDetail(any())).thenReturn(1L); + + String request = "{" + visitDetailsBlock() + ",\"vitalsDetails\":{}}"; + + assertEquals(1L, service.saveCancerScreeningNurseData(json(request), "auth")); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), org.mockito.ArgumentMatchers.eq((short) 9), + org.mockito.ArgumentMatchers.eq((short) 0), org.mockito.ArgumentMatchers.eq((short) 0), + org.mockito.ArgumentMatchers.eq((short) 0), org.mockito.ArgumentMatchers.eq((short) 1), anyLong(), + any()); + } + + @Test + void saveCancerScreeningNurseData_sendsTheBeneficiaryToTheDoctorAndRadiologistWhenBothAreNeeded() + throws Exception { + stubVisitCreation(); + when(cSNurseServiceImpl.saveBenVitalDetail(any())).thenReturn(1L); + when(cSNurseServiceImpl.saveCancerBreastExaminationData(any())).thenReturn(1L); + when(beneficiaryFlowStatusRepo.getBenDataForCareStream(any())).thenReturn(new ArrayList<>()); + when(cSCarestreamServiceImpl.createMamographyRequest(any(), anyLong(), anyLong(), anyString())) + .thenReturn(1); + + String request = "{" + visitDetailsBlock() + ",\"vitalsDetails\":{},\"sendToDoctorWorklist\":true," + + "\"examinationDetails\":{\"breastDetails\":{\"beneficiaryRegID\":1,\"referredToMammogram\":true}}}"; + + assertEquals(2L, service.saveCancerScreeningNurseData(json(request), "auth")); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), org.mockito.ArgumentMatchers.eq((short) 9), + org.mockito.ArgumentMatchers.eq((short) 1), org.mockito.ArgumentMatchers.eq((short) 0), + org.mockito.ArgumentMatchers.eq((short) 1), org.mockito.ArgumentMatchers.eq((short) 0), anyLong(), + any()); + } + + @Test + void saveCancerScreeningNurseData_leavesTheFlowUntouchedWhenASectionFailsToSave() throws Exception { + stubVisitCreation(); + when(cSNurseServiceImpl.saveBenVitalDetail(any())).thenReturn(null); + + String request = "{" + visitDetailsBlock() + ",\"vitalsDetails\":{}}"; + + assertNull(service.saveCancerScreeningNurseData(json(request), "auth")); + verify(commonBenStatusFlowServiceImpl, never()).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), + anyLong(), anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any()); + } + + private void stubVisitCreation() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + } + + @Test + void saveBenVisitDetails_returnsNothingWhenAVisitWasAlreadyCreatedRecently() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(1); + assertTrue(service.saveBenVisitDetails(new BeneficiaryVisitDetail(), new CommonUtilityClass()).isEmpty()); + } + } + + @Nested + @DisplayName("saving the individual nurse sections") + class NurseSections { + + @Test + void saveBenHistoryDetails_treatsAnAbsentHistoryBlockAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenHistoryDetails(json("{}"), 1L, 2L)); + assertEquals(1L, service.saveBenHistoryDetails(null, 1L, 2L)); + } + + @Test + void saveBenHistoryDetails_savesEverySectionThatWasSent() throws Exception { + when(cSNurseServiceImpl.saveBenFamilyCancerHistory(any())).thenReturn(1); + when(cSNurseServiceImpl.saveBenPersonalCancerHistory(any())).thenReturn(1L); + when(cSNurseServiceImpl.saveBenPersonalCancerDietHistory(any())).thenReturn(1L); + when(cSNurseServiceImpl.saveBenObstetricCancerHistory(any())).thenReturn(1L); + + String request = "{\"historyDetails\":{\"familyHistory\":{\"diseases\":[{\"diseaseType\":\"Breast\"}]}," + + "\"personalHistory\":{},\"pastObstetricHistory\":{}}}"; + + assertEquals(1L, service.saveBenHistoryDetails(json(request), 1L, 2L)); + } + + @Test + void saveBenHistoryDetails_treatsEachAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenHistoryDetails(json("{\"historyDetails\":{}}"), 1L, 2L)); + } + + @Test + void saveBenHistoryDetails_failsWhenTheFamilyHistoryCarriesNoDisease() { + // An empty disease array leaves the family-history flag unset, which the + // success check then dereferences. + String request = "{\"historyDetails\":{\"familyHistory\":{\"diseases\":[]}}}"; + assertThrows(NullPointerException.class, () -> service.saveBenHistoryDetails(json(request), 1L, 2L)); + } + + @Test + void saveBenHistoryDetails_reportsFailureWhenASectionCouldNotBeSaved() throws Exception { + when(cSNurseServiceImpl.saveBenPersonalCancerHistory(any())).thenReturn(0L); + + String request = "{\"historyDetails\":{\"personalHistory\":{}}}"; + assertNull(service.saveBenHistoryDetails(json(request), 1L, 2L)); + } + + @Test + void saveBenVitalsDetails_savesTheVitalsWhenTheyWereSent() throws Exception { + when(cSNurseServiceImpl.saveBenVitalDetail(any())).thenReturn(3L); + + assertEquals(3L, service.saveBenVitalsDetails(json("{\"vitalsDetails\":{}}"), 1L, 2L)); + assertEquals(1L, service.saveBenVitalsDetails(json("{}"), 1L, 2L)); + assertEquals(1L, service.saveBenVitalsDetails(null, 1L, 2L)); + } + + @Test + void saveBenFamilyHistoryDetails_isNotWiredUpYet() { + assertNull(service.saveBenFamilyHistoryDetails()); + } + + @Test + void saveBenExaminationDetails_treatsAnAbsentExaminationBlockAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenExaminationDetails(json("{}"), 1L, "auth", 2L, 3L)); + assertEquals(1L, service.saveBenExaminationDetails(null, 1L, "auth", 2L, 3L)); + } + + @Test + void saveBenExaminationDetails_treatsEachAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenExaminationDetails(json("{\"examinationDetails\":{}}"), 1L, "auth", 2L, + 3L)); + } + + @Test + void saveBenExaminationDetails_savesEverySectionThatWasSent() throws Exception { + when(cSNurseServiceImpl.saveCancerSignAndSymptomsData(any(), anyLong(), anyLong())).thenReturn(1L); + when(cSNurseServiceImpl.saveLymphNodeDetails(any(), anyLong(), anyLong())).thenReturn(1L); + when(cSNurseServiceImpl.saveCancerOralExaminationData(any())).thenReturn(1L); + when(cSNurseServiceImpl.saveCancerBreastExaminationData(any())).thenReturn(1L); + when(cSNurseServiceImpl.saveCancerAbdominalExaminationData(any())).thenReturn(1L); + when(cSNurseServiceImpl.saveCancerGynecologicalExaminationData(any())).thenReturn(1L); + when(cSNurseServiceImpl.saveDocExaminationImageAnnotation(any(), anyLong(), anyLong())).thenReturn(1L); + + String request = "{\"examinationDetails\":{\"signsDetails\":{\"cancerSignAndSymptoms\":{}," + + "\"cancerLymphNodeDetails\":[{}]},\"oralDetails\":{},\"breastDetails\":{}," + + "\"abdominalDetails\":{},\"gynecologicalDetails\":{},\"imageCoordinates\":[{}]}}"; + + assertEquals(1L, service.saveBenExaminationDetails(json(request), 1L, "auth", 2L, 3L)); + } + + @Test + void saveBenExaminationDetails_raisesAMammogramOrderWhenTheBeneficiaryWasReferred() throws Exception { + when(cSNurseServiceImpl.saveCancerBreastExaminationData(any())).thenReturn(1L); + when(beneficiaryFlowStatusRepo.getBenDataForCareStream(3L)).thenReturn(new ArrayList<>()); + when(cSCarestreamServiceImpl.createMamographyRequest(any(), anyLong(), anyLong(), anyString())) + .thenReturn(1); + + String request = "{\"examinationDetails\":{\"breastDetails\":{\"beneficiaryRegID\":1," + + "\"referredToMammogram\":true}}}"; + + assertEquals(2L, service.saveBenExaminationDetails(json(request), 1L, "auth", 2L, 3L)); + } + + @Test + void saveBenExaminationDetails_reportsAFailedMammogramOrder() throws Exception { + when(cSNurseServiceImpl.saveCancerBreastExaminationData(any())).thenReturn(1L); + when(beneficiaryFlowStatusRepo.getBenDataForCareStream(3L)).thenReturn(new ArrayList<>()); + when(cSCarestreamServiceImpl.createMamographyRequest(any(), anyLong(), anyLong(), anyString())) + .thenReturn(0); + + String request = "{\"examinationDetails\":{\"breastDetails\":{\"beneficiaryRegID\":1," + + "\"referredToMammogram\":true}}}"; + + assertEquals(3L, service.saveBenExaminationDetails(json(request), 1L, "auth", 2L, 3L)); + } + + @Test + void saveBenExaminationDetails_reportsFailureWhenASectionCouldNotBeSaved() throws Exception { + when(cSNurseServiceImpl.saveCancerOralExaminationData(any())).thenReturn(0L); + + String request = "{\"examinationDetails\":{\"oralDetails\":{}}}"; + assertNull(service.saveBenExaminationDetails(json(request), 1L, "auth", 2L, 3L)); + } + } + + @Nested + @DisplayName("updating nurse data from the doctor screen") + class NurseUpdates { + + @Test + void UpdateCSHistoryNurseData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1, service.UpdateCSHistoryNurseData(json("{}"))); + assertEquals(1, service.UpdateCSHistoryNurseData(null)); + } + + @Test + void UpdateCSHistoryNurseData_updatesEverySectionThatWasSent() throws Exception { + when(cSNurseServiceImpl.updateBeneficiaryFamilyCancerHistory(any())).thenReturn(1); + when(cSNurseServiceImpl.updateBenObstetricCancerHistory(any())).thenReturn(1); + when(cSNurseServiceImpl.updateBenPersonalCancerHistory(any())).thenReturn(1); + when(cSNurseServiceImpl.updateBenPersonalCancerDietHistory(any())).thenReturn(1); + + String request = "{\"familyHistory\":[{}],\"pastObstetricHistory\":{},\"personalHistory\":{}}"; + + assertEquals(1, service.UpdateCSHistoryNurseData(json(request))); + } + + @Test + void UpdateCSHistoryNurseData_treatsAnEmptyFamilyHistoryAsAlreadyDone() throws Exception { + assertEquals(1, service.UpdateCSHistoryNurseData(json("{\"familyHistory\":[]}"))); + } + + @Test + void UpdateCSHistoryNurseData_reportsFailureWhenASectionCouldNotBeUpdated() throws Exception { + when(cSNurseServiceImpl.updateBenObstetricCancerHistory(any())).thenReturn(0); + assertEquals(0, service.UpdateCSHistoryNurseData(json("{\"pastObstetricHistory\":{}}"))); + } + + @Test + void updateBenExaminationDetail_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1, service.updateBenExaminationDetail(json("{}"))); + assertEquals(1, service.updateBenExaminationDetail(null)); + } + + @Test + void updateBenExaminationDetail_updatesEverySectionThatWasSent() throws Exception { + when(cSNurseServiceImpl.updateSignAndSymptomsExaminationDetails(any())).thenReturn(1); + when(cSNurseServiceImpl.updateLymphNodeExaminationDetails(any())).thenReturn(1); + when(cSNurseServiceImpl.updateCancerOralDetails(any())).thenReturn(1); + when(cSNurseServiceImpl.updateCancerBreastDetails(any())).thenReturn(1); + when(cSNurseServiceImpl.updateCancerAbdominalExaminationDetails(any())).thenReturn(1); + when(cSNurseServiceImpl.updateCancerGynecologicalExaminationDetails(any())).thenReturn(1); + when(cSNurseServiceImpl.getCancerExaminationImageAnnotationList(any(), any())) + .thenReturn(new ArrayList<>()); + when(cSNurseServiceImpl.updateCancerExamImgAnotasnDetails(any())).thenReturn(1); + + String request = "{\"visitCode\":3,\"signsDetails\":{\"cancerSignAndSymptoms\":{}," + + "\"cancerLymphNodeDetails\":[{}]},\"oralDetails\":{},\"breastDetails\":{}," + + "\"abdominalDetails\":{},\"gynecologicalDetails\":{\"fileIDs\":[\"a\",\"b\"]}," + + "\"imageCoordinates\":[{}]}"; + + assertEquals(1, service.updateBenExaminationDetail(json(request))); + } + + @Test + void updateBenExaminationDetail_treatsAnEmptySignsBlockAsAlreadyDone() throws Exception { + assertEquals(1, service.updateBenExaminationDetail(json("{\"signsDetails\":{}}"))); + } + + @Test + void updateBenExaminationDetail_reportsFailureWhenASectionCouldNotBeUpdated() throws Exception { + when(cSNurseServiceImpl.updateCancerOralDetails(any())).thenReturn(0); + assertEquals(0, service.updateBenExaminationDetail(json("{\"oralDetails\":{}}"))); + } + + @Test + void updateBenVitalDetail_delegatesToTheNurseService() { + BenCancerVitalDetail vital = new BenCancerVitalDetail(); + when(cSNurseServiceImpl.updateBenVitalDetail(vital)).thenReturn(1); + assertEquals(1, service.updateBenVitalDetail(vital)); + } + } + + @Nested + @DisplayName("reading the nurse and doctor screens") + class Reads { + + /** One left-panel row, wide enough for the beneficiary summary mapper. */ + private ArrayList oneBenDetailRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[40]); + return rows; + } + + @Test + void getBenDataFrmNurseToDocVisitDetailsScreen_returnsTheStoredVisit() throws Exception { + BeneficiaryVisitDetail visit = new BeneficiaryVisitDetail(); + visit.setBenVisitID(1L); + when(commonNurseServiceImpl.getCSVisitDetails(1L, 2L)).thenReturn(visit); + + assertTrue(service.getBenDataFrmNurseToDocVisitDetailsScreen(1L, 2L).contains("benVisitDetails")); + + when(commonNurseServiceImpl.getCSVisitDetails(3L, 4L)).thenReturn(null); + assertEquals("{}", service.getBenDataFrmNurseToDocVisitDetailsScreen(3L, 4L)); + } + + @Test + void getBenDataFrmNurseToDocHistoryScreen_gathersEveryHistorySection() { + assertTrue(service.getBenDataFrmNurseToDocHistoryScreen(1L, 2L).contains("benFamilyHistory")); + verify(cSNurseServiceImpl).getBenPersonalCancerDietHistoryData(1L, 2L); + } + + @Test + void getBenDataFrmNurseToDocVitalScreen_gathersTheVitalsAndTheirTrend() { + when(commonNurseServiceImpl.getGraphicalTrendData(1L, "cancer screening")).thenReturn(new HashMap<>()); + assertTrue(service.getBenDataFrmNurseToDocVitalScreen(1L, 2L).contains("GraphData")); + } + + @Test + void getBenDataFrmNurseToDocExaminationScreen_gathersEveryExaminedSystem() { + when(cSNurseServiceImpl.getCancerExaminationImageAnnotationCasesheet(1L, 2L)) + .thenReturn(new ArrayList<>()); + + assertTrue(service.getBenDataFrmNurseToDocExaminationScreen(1L, 2L).contains("imageCoordinates")); + verify(cSNurseServiceImpl).getBenCancerOralExaminationData(1L, 2L); + } + + @Test + void getBenNurseDataForCaseSheet_gathersEverySectionOfTheNurseCaseSheet() { + when(cSNurseServiceImpl.getBeneficiaryVisitDetails(1L, 2L)).thenReturn(new BeneficiaryVisitDetail()); + + assertTrue(service.getBenNurseDataForCaseSheet(1L, 2L).contains("benVisitDetail")); + verify(cSNurseServiceImpl).getBenCancerLymphNodeDetailsData(1L, 2L); + } + + @Test + void getBenDataForCaseSheet_combinesTheNurseAndDoctorSections() throws Exception { + when(cSNurseServiceImpl.getBenNurseDataForCaseSheet(1L, 2L)).thenReturn(new HashMap<>()); + when(cSDoctorServiceImpl.getBenDoctorEnteredDataForCaseSheet(1L, 2L)).thenReturn(new HashMap<>()); + when(beneficiaryFlowStatusRepo.getBenDetailsForLeftSidePanel(1L, 3L)).thenReturn(oneBenDetailRow()); + when(cSNurseServiceImpl.getCancerExaminationImageAnnotationCasesheet(1L, 2L)) + .thenReturn(new ArrayList<>()); + + assertTrue(service.getBenDataForCaseSheet(3L, 1L, 2L, "auth").contains("ImageAnnotatedData")); + } + + @Test + void getCancerCasesheetData_readsTheBeneficiaryKeysOutOfTheRequest() throws Exception { + when(cSNurseServiceImpl.getBenNurseDataForCaseSheet(1L, 2L)).thenReturn(new HashMap<>()); + when(cSDoctorServiceImpl.getBenDoctorEnteredDataForCaseSheet(1L, 2L)).thenReturn(new HashMap<>()); + when(beneficiaryFlowStatusRepo.getBenDetailsForLeftSidePanel(1L, 3L)).thenReturn(oneBenDetailRow()); + when(cSNurseServiceImpl.getCancerExaminationImageAnnotationCasesheet(1L, 2L)) + .thenReturn(new ArrayList<>()); + + JSONObject request = new JSONObject(); + request.put("benRegID", 1L); + request.put("benVisitID", 4L); + request.put("benFlowID", 3L); + request.put("visitCode", 2L); + + assertTrue(service.getCancerCasesheetData(request, "auth").contains("BeneficiaryData")); + } + + @Test + void getCancerCasesheetData_returnsNothingForAnEmptyRequest() throws Exception { + assertNull(service.getCancerCasesheetData(new JSONObject(), "auth")); + } + + @Test + void getCancerCasesheetData_tolratesARequestMissingTheBeneficiaryKeys() throws Exception { + when(cSNurseServiceImpl.getBenNurseDataForCaseSheet(null, null)).thenReturn(new HashMap<>()); + when(cSDoctorServiceImpl.getBenDoctorEnteredDataForCaseSheet(null, null)).thenReturn(new HashMap<>()); + when(beneficiaryFlowStatusRepo.getBenDetailsForLeftSidePanel(null, null)).thenReturn(oneBenDetailRow()); + when(cSNurseServiceImpl.getCancerExaminationImageAnnotationCasesheet(null, null)) + .thenReturn(new ArrayList<>()); + + JSONObject request = new JSONObject(); + request.put("unrelated", 1); + request.put("alsoUnrelated", 2); + + assertTrue(service.getCancerCasesheetData(request, "auth").contains("BeneficiaryData")); + } + + @Test + void thePastHistoryReadsDelegateToTheNurseService() { + when(cSNurseServiceImpl.getBenCancerFamilyHistory(1L)).thenReturn("family"); + assertEquals("family", service.getBenFamilyHistoryData(1L)); + + when(cSNurseServiceImpl.getBenCancerPersonalHistory(1L)).thenReturn("personal"); + assertEquals("personal", service.getBenPersonalHistoryData(1L)); + + when(cSNurseServiceImpl.getBenCancerPersonalDietHistory(1L)).thenReturn("diet"); + assertEquals("diet", service.getBenPersonalDietHistoryData(1L)); + + when(cSNurseServiceImpl.getBenCancerObstetricHistory(1L)).thenReturn("obstetric"); + assertEquals("obstetric", service.getBenObstetricHistoryData(1L)); + } + + @Test + void theDoctorDiagnosisReadsDelegateToTheDoctorService() { + when(cSDoctorServiceImpl.getBenCancerDiagnosisData(1L, 2L)).thenReturn(new CancerDiagnosis()); + + assertTrue(service.getBenDoctorDiagnosisData(1L, 2L).contains("benDiagnosisDetails")); + assertTrue(service.getBenCaseRecordFromDoctorCS(1L, 2L).contains("diagnosis")); + } + } + + @Nested + @DisplayName("saving and updating doctor data") + class DoctorData { + + private String doctorRequest(String extra) { + return "{\"diagnosis\":{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"benFlowID\":4," + + "\"createdBy\":\"doctor\"" + extra + "}}"; + } + + @Test + void saveCancerScreeningDoctorData_savesTheDiagnosisAndAdvancesTheFlow() throws Exception { + when(cSDoctorServiceImpl.saveCancerDiagnosisData(any())).thenReturn(1L); + + assertEquals(1L, service.saveCancerScreeningDoctorData(json(doctorRequest("")), "auth")); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocData(any(), any(), any(), any(), anyShort(), + anyShort(), anyShort(), anyShort(), anyInt(), any(), any()); + } + + @Test + void saveCancerScreeningDoctorData_ignoresARequestWithoutADiagnosis() throws Exception { + assertNull(service.saveCancerScreeningDoctorData(json("{}"), "auth")); + } + + @Test + void saveCancerScreeningDoctorData_leavesTheFlowUntouchedWhenTheDiagnosisWasNotStored() throws Exception { + when(cSDoctorServiceImpl.saveCancerDiagnosisData(any())).thenReturn(0L); + + assertNull(service.saveCancerScreeningDoctorData(json(doctorRequest("")), "auth")); + verify(commonBenStatusFlowServiceImpl, never()).updateBenFlowAfterDocData(any(), any(), any(), any(), + anyShort(), anyShort(), anyShort(), anyShort(), anyInt(), any(), any()); + } + + @Test + void saveCancerScreeningDoctorData_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() + throws Exception { + when(cSDoctorServiceImpl.saveCancerDiagnosisData(any())).thenReturn(1L); + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + + String request = "{\"diagnosis\":{\"beneficiaryRegID\":1,\"serviceID\":4,\"createdBy\":\"doctor\"}," + + "\"tcRequest\":{\"userID\":5,\"allocationDate\":\"2024-01-01\",\"fromTime\":\"10:00:00\"," + + "\"toTime\":\"10:30:00\"}}"; + + assertEquals(1L, service.saveCancerScreeningDoctorData(json(request), "auth")); + verify(teleConsultationServiceImpl).createTCRequest(any()); + } + + @Test + void saveCancerScreeningDoctorData_failsWhenTheSpecialistSlotCouldNotBeBooked() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + String request = "{\"diagnosis\":{\"serviceID\":4,\"createdBy\":\"doctor\"}," + + "\"tcRequest\":{\"userID\":5,\"allocationDate\":\"2024-01-01\",\"fromTime\":\"10:00:00\"," + + "\"toTime\":\"10:30:00\"}}"; + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveCancerScreeningDoctorData(json(request), "auth")); + assertEquals("Error while booking slot.", thrown.getMessage()); + } + + @Test + void saveBenDiagnosisDetails_storesTheDiagnosisWhenOneWasSent() throws Exception { + when(cSDoctorServiceImpl.saveCancerDiagnosisData(any())).thenReturn(4L); + + assertEquals(4L, service.saveBenDiagnosisDetails(json(doctorRequest("")))); + assertEquals(1L, service.saveBenDiagnosisDetails(json("{}"))); + assertEquals(1L, service.saveBenDiagnosisDetails(null)); + } + + @Test + void saveBenDiagnosisDetails_reportsFailureWhenTheDiagnosisWasNotStored() throws Exception { + when(cSDoctorServiceImpl.saveCancerDiagnosisData(any())).thenReturn(0L); + assertNull(service.saveBenDiagnosisDetails(json(doctorRequest("")))); + } + + @Test + void updateCancerScreeningDoctorData_updatesTheDiagnosisAndAdvancesTheFlow() throws Exception { + when(cSDoctorServiceImpl.updateCancerDiagnosisDetailsByDoctor(any())).thenReturn(1); + when(beneficiaryFlowStatusRepo.updateBenFlowAfterTCSpcialistDoneForCanceScreening(4L, 1L, 3L)) + .thenReturn(1); + + assertEquals(1, service.updateCancerScreeningDoctorData(json(doctorRequest("")))); + } + + @Test + void updateCancerScreeningDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + when(cSDoctorServiceImpl.updateCancerDiagnosisDetailsByDoctor(any())).thenReturn(1); + when(beneficiaryFlowStatusRepo.updateBenFlowAfterTCSpcialistDoneForCanceScreening(any(), any(), any())) + .thenReturn(0); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.updateCancerScreeningDoctorData(json(doctorRequest("")))); + assertTrue(thrown.getMessage().contains("beneficiary flow status")); + } + + @Test + void updateCancerScreeningDoctorData_failsWhenTheDiagnosisCouldNotBeUpdated() throws Exception { + when(cSDoctorServiceImpl.updateCancerDiagnosisDetailsByDoctor(any())).thenReturn(0); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.updateCancerScreeningDoctorData(json(doctorRequest("")))); + assertEquals("Error while saving data.", thrown.getMessage()); + } + + @Test + void updateCancerScreeningDoctorData_rejectsARequestWithoutADiagnosis() { + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.updateCancerScreeningDoctorData(json("{}"))); + assertEquals("Invalid request.", thrown.getMessage()); + + RuntimeException nullRequest = assertThrows(RuntimeException.class, + () -> service.updateCancerScreeningDoctorData(null)); + assertEquals("Invalid request as it is null.", nullRequest.getMessage()); + } + + @Test + void updateCancerDiagnosisDetailsByOncologist_delegatesToTheOncologistService() { + CancerDiagnosis diagnosis = new CancerDiagnosis(); + when(csOncologistServiceImpl.updateCancerDiagnosisDetailsByOncologist(diagnosis)).thenReturn(1); + assertEquals(1, service.updateCancerDiagnosisDetailsByOncologist(diagnosis)); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/cancerScreening/CancerDoctorAndCarestreamServiceTest.java b/src/test/java/com/iemr/mmu/service/cancerScreening/CancerDoctorAndCarestreamServiceTest.java new file mode 100644 index 00000000..e22e4c1a --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/cancerScreening/CancerDoctorAndCarestreamServiceTest.java @@ -0,0 +1,235 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.cancerScreening; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.when; + +import java.sql.Date; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedConstruction; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import com.iemr.mmu.data.doctor.CancerDiagnosis; +import com.iemr.mmu.repo.doctor.CancerDiagnosisRepo; +import com.iemr.mmu.utils.CookieUtil; + +class CancerDoctorAndCarestreamServiceTest { + + @Nested + @DisplayName("CSDoctorServiceImpl") + class DoctorService { + + @Mock + private CancerDiagnosisRepo cancerDiagnosisRepo; + + @InjectMocks + private CSDoctorServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private CancerDiagnosis diagnosisWithServices(String... services) { + CancerDiagnosis diagnosis = new CancerDiagnosis(); + diagnosis.setBeneficiaryRegID(1L); + diagnosis.setVisitCode(2L); + diagnosis.setRefrredToAdditionalServiceList(Arrays.asList(services)); + return diagnosis; + } + + @Test + void saveCancerDiagnosisData_flattensTheAdditionalServicesBeforeSaving() { + CancerDiagnosis diagnosis = diagnosisWithServices("Radiology", "Oncology"); + CancerDiagnosis stored = new CancerDiagnosis(); + stored.setID(5L); + when(cancerDiagnosisRepo.save(diagnosis)).thenReturn(stored); + + assertEquals(5L, service.saveCancerDiagnosisData(diagnosis)); + assertEquals("Radiology,Oncology", diagnosis.getRefrredToAdditionalService()); + assertEquals(5L, stored.getVanSerialNo()); + } + + @Test + void saveCancerDiagnosisData_storesAnEmptyServiceListAsAnEmptyString() { + CancerDiagnosis diagnosis = new CancerDiagnosis(); + when(cancerDiagnosisRepo.save(diagnosis)).thenReturn(null); + + assertNull(service.saveCancerDiagnosisData(diagnosis)); + assertEquals("", diagnosis.getRefrredToAdditionalService()); + } + + @Test + void getBenCancerDiagnosisData_splitsTheStoredServicesBackIntoAList() { + CancerDiagnosis stored = new CancerDiagnosis(); + stored.setRefrredToAdditionalService("Radiology,Oncology"); + when(cancerDiagnosisRepo.getBenCancerDiagnosisDetails(1L, 2L)).thenReturn(stored); + + assertEquals(List.of("Radiology", "Oncology"), + service.getBenCancerDiagnosisData(1L, 2L).getRefrredToAdditionalServiceList()); + } + + @Test + void getBenCancerDiagnosisData_namesTheReferredInstituteWhenOneIsLinked() { + CancerDiagnosis stored = new CancerDiagnosis(); + com.iemr.mmu.data.institution.Institute institute = new com.iemr.mmu.data.institution.Institute(); + institute.setInstitutionName("District Hospital"); + stored.setInstitute(institute); + when(cancerDiagnosisRepo.getBenCancerDiagnosisDetails(1L, 2L)).thenReturn(stored); + + assertEquals("District Hospital", service.getBenCancerDiagnosisData(1L, 2L).getReferredToInstituteName()); + } + + @Test + void getBenCancerDiagnosisData_returnsNothingForABeneficiaryWithNoDiagnosis() { + when(cancerDiagnosisRepo.getBenCancerDiagnosisDetails(1L, 2L)).thenReturn(null); + + assertNull(service.getBenCancerDiagnosisData(1L, 2L)); + } + + @Test + void getBenDoctorEnteredDataForCaseSheet_carriesTheDiagnosis() { + CancerDiagnosis stored = new CancerDiagnosis(); + when(cancerDiagnosisRepo.getBenCancerDiagnosisDetails(1L, 2L)).thenReturn(stored); + + assertEquals(stored, service.getBenDoctorEnteredDataForCaseSheet(1L, 2L).get("diagnosis")); + } + + @Test + void updateCancerDiagnosisDetailsByDoctor_updatesTheStoredRowWhenOneExists() { + CancerDiagnosis diagnosis = diagnosisWithServices("Radiology"); + when(cancerDiagnosisRepo.getCancerDiagnosisStatuses(1L, 2L)).thenReturn("P"); + when(cancerDiagnosisRepo.updateCancerDiagnosisDetailsByDoctor(any(), any(), any(), anyString(), any(), + any(), any(), eq("U"), anyLong(), anyLong())).thenReturn(1); + + assertEquals(1, service.updateCancerDiagnosisDetailsByDoctor(diagnosis)); + } + + @Test + void updateCancerDiagnosisDetailsByDoctor_insertsAFreshRowWhenNoneIsStoredYet() { + CancerDiagnosis diagnosis = diagnosisWithServices("Radiology"); + CancerDiagnosis stored = new CancerDiagnosis(); + stored.setID(5L); + when(cancerDiagnosisRepo.getCancerDiagnosisStatuses(1L, 2L)).thenReturn(null); + when(cancerDiagnosisRepo.save(diagnosis)).thenReturn(stored); + + assertEquals(1, service.updateCancerDiagnosisDetailsByDoctor(diagnosis)); + + stored.setID(0L); + assertEquals(0, service.updateCancerDiagnosisDetailsByDoctor(diagnosis)); + } + } + + @Nested + @DisplayName("CSCarestreamServiceImpl") + class CarestreamService { + + @Mock + private CookieUtil cookieUtil; + + @InjectMocks + private CSCarestreamServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(service, "carestreamOrderCreateURL", "http://carestream/order"); + } + + /** One beneficiary row as the flow-status query returns it. */ + private ArrayList beneficiary(String name, short genderID) { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { name, "Devi", Date.valueOf("1990-05-04"), genderID }); + return rows; + } + + private MockedConstruction carestreamAnswering(String body) { + return mockConstruction(RestTemplate.class, + (mock, context) -> when( + mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(body, HttpStatus.OK))); + } + + @Test + void createMamographyRequest_reportsAnAcceptedOrder() { + try (MockedConstruction rest = carestreamAnswering("{\"statusCode\":200}")) { + assertEquals(1, service.createMamographyRequest(beneficiary("Asha Devi", (short) 2), 1L, 2L, "auth")); + } + } + + @Test + void createMamographyRequest_reportsARejectedOrder() { + try (MockedConstruction rest = carestreamAnswering("{\"statusCode\":500}")) { + assertEquals(0, service.createMamographyRequest(beneficiary("Asha Devi", (short) 2), 1L, 2L, "auth")); + } + } + + @Test + void createMamographyRequest_mapsEveryGenderCodeCarestreamExpects() { + try (MockedConstruction rest = carestreamAnswering("{\"statusCode\":200}")) { + assertEquals(1, service.createMamographyRequest(beneficiary("Ram", (short) 1), 1L, 2L, "auth")); + assertEquals(1, service.createMamographyRequest(beneficiary("Asha Devi", (short) 2), 1L, 2L, "auth")); + assertEquals(1, service.createMamographyRequest(beneficiary("Kiran", (short) 3), 1L, 2L, "auth")); + } + } + + @Test + void createMamographyRequest_sendsAnEmptyOrderForABeneficiaryWithNoDetails() { + try (MockedConstruction rest = carestreamAnswering("{\"statusCode\":200}")) { + assertEquals(1, service.createMamographyRequest(new ArrayList<>(), 1L, 2L, "auth")); + } + } + + @Test + void createMamographyRequest_reportsAnOrderThatCouldNotBeSent() { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when( + mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenThrow(new RuntimeException("carestream unreachable")))) { + + assertEquals(0, service.createMamographyRequest(beneficiary("Asha Devi", (short) 2), 1L, 2L, "auth")); + } + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/common/transaction/CommonDoctorServiceImplTest.java b/src/test/java/com/iemr/mmu/service/common/transaction/CommonDoctorServiceImplTest.java new file mode 100644 index 00000000..cf17108c --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/common/transaction/CommonDoctorServiceImplTest.java @@ -0,0 +1,627 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.common.transaction; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyShort; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.test.util.ReflectionTestUtils; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.anc.WrapperAncFindings; +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.data.login.Users; +import com.iemr.mmu.data.nurse.CommonUtilityClass; +import com.iemr.mmu.data.quickConsultation.BenChiefComplaint; +import com.iemr.mmu.data.quickConsultation.BenClinicalObservations; +import com.iemr.mmu.data.snomedct.SCTDescription; +import com.iemr.mmu.data.tele_consultation.TeleconsultationRequestOBJ; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.repo.doctor.BenReferDetailsRepo; +import com.iemr.mmu.repo.doctor.DocWorkListRepo; +import com.iemr.mmu.repo.login.UserLoginRepo; +import com.iemr.mmu.repo.nurse.BenVisitDetailRepo; +import com.iemr.mmu.repo.quickConsultation.BenChiefComplaintRepo; +import com.iemr.mmu.repo.quickConsultation.BenClinicalObservationsRepo; +import com.iemr.mmu.repo.quickConsultation.LabTestOrderDetailRepo; +import com.iemr.mmu.repo.quickConsultation.PrescribedDrugDetailRepo; +import com.iemr.mmu.repo.quickConsultation.PrescriptionDetailRepo; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.service.snomedct.SnomedServiceImpl; +import com.iemr.mmu.utils.CookieUtil; + +class CommonDoctorServiceImplTest { + + @Mock + private BenClinicalObservationsRepo benClinicalObservationsRepo; + @Mock + private BenChiefComplaintRepo benChiefComplaintRepo; + @Mock + private DocWorkListRepo docWorkListRepo; + @Mock + private BenReferDetailsRepo benReferDetailsRepo; + @Mock + private LabTestOrderDetailRepo labTestOrderDetailRepo; + @Mock + private PrescribedDrugDetailRepo prescribedDrugDetailRepo; + @Mock + private PrescriptionDetailRepo prescriptionDetailRepo; + @Mock + private SnomedServiceImpl snomedServiceImpl; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + @Mock + private CookieUtil cookieUtil; + @Mock + private BenVisitDetailRepo benVisitDetailRepo; + @Mock + private UserLoginRepo userLoginRepo; + + @InjectMocks + private CommonDoctorServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(service, "tmReferCheckValue", "Tele-consultation"); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + private static WrapperAncFindings findings(BenChiefComplaint... complaints) { + ArrayList list = new ArrayList<>(); + Collections.addAll(list, complaints); + return new WrapperAncFindings(1L, 2L, 4, "Alert", "Fever", "Anaemia", list, Boolean.FALSE, 3L); + } + + private static BenChiefComplaint complaint(String name) { + BenChiefComplaint complaint = new BenChiefComplaint(); + complaint.setChiefComplaint(name); + complaint.setBenChiefComplaintID(9L); + return complaint; + } + + @Nested + @DisplayName("findings") + class Findings { + + @Test + void saveFindings_reportsWhetherTheObservationWasStored() throws Exception { + BenClinicalObservations stored = new BenClinicalObservations(); + when(benClinicalObservationsRepo.save(any())).thenReturn(stored); + assertEquals(1, service.saveFindings(json("{}"))); + + when(benClinicalObservationsRepo.save(any())).thenReturn(null); + assertEquals(0, service.saveFindings(json("{}"))); + } + + @Test + void saveDocFindings_storesTheObservationAndEveryNamedComplaint() { + when(benClinicalObservationsRepo.save(any())).thenReturn(new BenClinicalObservations()); + when(benChiefComplaintRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + WrapperAncFindings wrapper = findings(complaint("Fever"), new BenChiefComplaint()); + + assertEquals(1, service.saveDocFindings(wrapper)); + verify(benChiefComplaintRepo).updateVanSerialNo(9L); + } + + @Test + void saveDocFindings_succeedsWhenNoComplaintWasNamed() { + when(benClinicalObservationsRepo.save(any())).thenReturn(new BenClinicalObservations()); + assertEquals(1, service.saveDocFindings(findings())); + verify(benChiefComplaintRepo, never()).saveAll(any()); + } + + @Test + void saveDocFindings_reportsFailureWhenTheObservationWasNotStored() { + when(benClinicalObservationsRepo.save(any())).thenReturn(null); + assertEquals(0, service.saveDocFindings(findings())); + } + + @Test + void saveDocFindings_reportsFailureWhenNotEveryComplaintWasStored() { + when(benClinicalObservationsRepo.save(any())).thenReturn(new BenClinicalObservations()); + when(benChiefComplaintRepo.saveAll(any())).thenReturn(new ArrayList<>()); + + assertEquals(0, service.saveDocFindings(findings(complaint("Fever")))); + } + + @Test + void getSnomedCTcode_looksUpEverySymptomAndFallsBackWhenOneIsUnknown() { + SCTDescription known = new SCTDescription(); + known.setConceptID("111"); + known.setTerm("Fever"); + when(snomedServiceImpl.findSnomedCTRecordFromTerm("Fever")).thenReturn(known); + when(snomedServiceImpl.findSnomedCTRecordFromTerm("Unknown")).thenReturn(null); + + assertArrayEquals(new String[] { "111,N/A", "Fever,N/A" }, + service.getSnomedCTcode("Fever, Unknown")); + } + + @Test + void getSnomedCTcode_returnsEmptyCodesForNoSymptoms() { + assertArrayEquals(new String[] { "", "" }, service.getSnomedCTcode(null)); + assertArrayEquals(new String[] { "", "" }, service.getSnomedCTcode("")); + } + + @Test + void fetchBenPreviousSignificantFindings_mapsEveryStoredFinding() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { new java.sql.Date(System.currentTimeMillis()), "Anaemia" }); + when(benClinicalObservationsRepo.getPreviousSignificantFindings(1L)).thenReturn(rows); + + assertTrue(service.fetchBenPreviousSignificantFindings(1L).contains("Anaemia")); + } + + @Test + void getFindingsDetails_combinesTheObservationsAndComplaints() { + when(benClinicalObservationsRepo.getFindingsData(1L, 2L)).thenReturn(new ArrayList<>()); + when(benChiefComplaintRepo.getBenChiefComplaints(1L, 2L)).thenReturn(new ArrayList<>()); + + assertNotNull(service.getFindingsDetails(1L, 2L)); + } + + @Test + void updateDocFindings_updatesTheObservationAndEveryNamedComplaint() { + when(benClinicalObservationsRepo.getBenClinicalObservationStatus(1L, 3L)).thenReturn("P"); + when(benClinicalObservationsRepo.updateBenClinicalObservations(any(), any(), any(), any(), any(), any(), + any(), eq("U"), anyLong(), anyLong())).thenReturn(1); + when(benChiefComplaintRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + assertEquals(1, service.updateDocFindings(findings(complaint("Fever")))); + } + + @Test + void updateDocFindings_succeedsWhenNoComplaintWasNamed() { + when(benClinicalObservationsRepo.getBenClinicalObservationStatus(any(), any())).thenReturn("N"); + when(benClinicalObservationsRepo.updateBenClinicalObservations(any(), any(), any(), any(), any(), any(), + any(), eq("N"), anyLong(), anyLong())).thenReturn(1); + + assertEquals(1, service.updateDocFindings(findings())); + } + + @Test + void updateDocFindings_reportsFailureWhenTheObservationCouldNotBeUpdated() { + when(benClinicalObservationsRepo.getBenClinicalObservationStatus(any(), any())).thenReturn("N"); + when(benClinicalObservationsRepo.updateBenClinicalObservations(any(), any(), any(), any(), any(), any(), + any(), any(), anyLong(), anyLong())).thenReturn(0); + + assertEquals(0, service.updateDocFindings(findings())); + } + + @Test + void updateBenClinicalObservations_insertsAFreshRowWhenNoneIsStoredYet() { + BenClinicalObservations observations = new BenClinicalObservations(); + BenClinicalObservations stored = new BenClinicalObservations(); + stored.setClinicalObservationID(5L); + when(benClinicalObservationsRepo.getBenClinicalObservationStatus(any(), any())).thenReturn(null); + when(benClinicalObservationsRepo.save(observations)).thenReturn(stored); + + assertEquals(1, service.updateBenClinicalObservations(observations)); + } + + @Test + void updateBenClinicalObservations_reportsFailureWhenTheFreshRowWasNotStored() { + BenClinicalObservations observations = new BenClinicalObservations(); + BenClinicalObservations stored = new BenClinicalObservations(); + stored.setClinicalObservationID(0L); + when(benClinicalObservationsRepo.getBenClinicalObservationStatus(any(), any())).thenReturn(null); + when(benClinicalObservationsRepo.save(observations)).thenReturn(stored); + + assertEquals(0, service.updateBenClinicalObservations(observations)); + assertEquals(0, service.updateBenClinicalObservations(null)); + } + + @Test + void updateDoctorBenChiefComplaints_succeedsWhenThereIsNothingToUpdate() { + assertEquals(1, service.updateDoctorBenChiefComplaints(null)); + assertEquals(1, service.updateDoctorBenChiefComplaints(new ArrayList<>())); + } + + @Test + void updateDoctorBenChiefComplaints_reportsNothingSavedWhenTheStoreDropsTheRows() { + List complaints = Collections.singletonList(complaint("Fever")); + when(benChiefComplaintRepo.saveAll(complaints)).thenReturn(new ArrayList<>()); + + assertEquals(0, service.updateDoctorBenChiefComplaints(complaints)); + } + } + + @Nested + @DisplayName("work lists") + class WorkLists { + + @Test + void getDocWorkList_serialisesTheStoredWorklist() { + when(docWorkListRepo.getDocWorkList()).thenReturn(new ArrayList<>()); + assertNotNull(service.getDocWorkList()); + } + + @Test + void getDocWorkListNew_readsTheMmuWorklistWithinTheConfiguredWindow() { + ReflectionTestUtils.setField(service, "docWL", 10); + when(beneficiaryFlowStatusRepo.getDocWorkListNew(any(), any(), any())).thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getDocWorkListNew(1, 2, 3)); + } + + @Test + void getDocWorkListNew_readsTheTeleconsultationWorklist() { + when(beneficiaryFlowStatusRepo.getDocWorkListNewTC(1)).thenReturn(new ArrayList<>()); + assertEquals("[]", service.getDocWorkListNew(1, 4, 3)); + } + + @Test + void getDocWorkListNew_returnsNothingForAnUnknownService() { + assertEquals("[]", service.getDocWorkListNew(1, 9, 3)); + } + + @Test + void getDocWorkListNewFutureScheduledForTM_onlyAppliesToTeleconsultation() { + when(beneficiaryFlowStatusRepo.getDocWorkListNewFutureScheduledTC(1)).thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getDocWorkListNewFutureScheduledForTM(1, 4)); + assertEquals("[]", service.getDocWorkListNewFutureScheduledForTM(1, 2)); + } + + @Test + void getTCSpecialistWorkListNewForTM_onlyAppliesToTeleconsultation() { + when(beneficiaryFlowStatusRepo.getTCSpecialistWorkListNew(1, 5)).thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getTCSpecialistWorkListNewForTM(1, 5, 4)); + assertEquals("[]", service.getTCSpecialistWorkListNewForTM(1, 5, 2)); + } + + @Test + void getTCSpecialistWorkListNewFutureScheduledForTM_onlyAppliesToTeleconsultation() { + when(beneficiaryFlowStatusRepo.getTCSpecialistWorkListNewFutureScheduled(1, 5)) + .thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getTCSpecialistWorkListNewFutureScheduledForTM(1, 5, 4)); + assertEquals("[]", service.getTCSpecialistWorkListNewFutureScheduledForTM(1, 5, 2)); + } + } + + @Nested + @DisplayName("referrals") + class Referrals { + + private String referRequest(String extra) { + return "{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"providerServiceMapID\":4," + + "\"createdBy\":\"doctor\"" + extra + "}"; + } + + @Test + void saveBenReferDetails_createsOneRowPerAdditionalService() throws Exception { + when(benReferDetailsRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + String request = referRequest(",\"referredToInstituteID\":7,\"referredToInstituteName\":\"PHC\"," + + "\"referralReason\":\"Fever\",\"revisitDate\":\"2024-01-01T00:00:00.000\"," + + "\"refrredToAdditionalServiceList\":[{\"serviceID\":1,\"serviceName\":\"Tele-consultation\"}," + + "{\"serviceID\":2,\"serviceName\":\"Lab\"},{\"serviceID\":3}]"); + + assertEquals(1L, service.saveBenReferDetails(json(request))); + } + + @Test + void saveBenReferDetails_storesTheReferralAsOneRowWhenNoServiceWasChosen() throws Exception { + when(benReferDetailsRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + assertEquals(1L, service.saveBenReferDetails(json(referRequest( + ",\"referredToInstituteName\":\"PHC\"")))); + } + + @Test + void saveBenReferDetails_storesNothingWhenTheReferralIsEmpty() throws Exception { + when(benReferDetailsRepo.saveAll(any())).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveBenReferDetails(json(referRequest("")))); + } + + @Test + void saveBenReferDetailsTMreferred_reusesTheExistingRowWhenTheInstituteWasUpdated() throws Exception { + when(benReferDetailsRepo.updateReferredInstituteNameTMReferred(any(), any(), any(), eq("U"))) + .thenReturn(1); + + assertEquals(1L, service.saveBenReferDetailsTMreferred(json(referRequest( + ",\"referredToInstituteID\":7,\"referredToInstituteName\":\"PHC\"")))); + } + + @Test + void saveBenReferDetailsTMreferred_createsOneRowPerAdditionalServiceWhenNoRowExisted() throws Exception { + when(benReferDetailsRepo.updateReferredInstituteNameTMReferred(any(), any(), any(), any())).thenReturn(0); + when(benReferDetailsRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + String request = referRequest(",\"referredToInstituteID\":7,\"referredToInstituteName\":\"PHC\"," + + "\"revisitDate\":\"2024-01-01T00:00:00.000\"," + + "\"refrredToAdditionalServiceList\":[{\"serviceID\":1,\"serviceName\":\"Lab\"},{}]"); + + assertEquals(1L, service.saveBenReferDetailsTMreferred(json(request))); + } + + @Test + void saveBenReferDetailsTMreferred_storesTheReferralAsOneRowWhenNoServiceWasChosen() throws Exception { + when(benReferDetailsRepo.updateReferredInstituteNameTMReferred(any(), any(), any(), any())).thenReturn(0); + when(benReferDetailsRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + assertEquals(1L, service.saveBenReferDetailsTMreferred( + json(referRequest(",\"referredToInstituteName\":\"PHC\"")))); + } + + @Test + void updateBenReferDetails_refreshesTheStoredRowsAndAddsTheNewServices() throws Exception { + ArrayList statuses = new ArrayList<>(); + statuses.add(new Object[] { 5L, "P", "Lab" }); + when(benReferDetailsRepo.getBenReferDetailsStatus(1L, 3L)).thenReturn(statuses); + when(benReferDetailsRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + String request = referRequest(",\"referredToInstituteID\":7,\"referredToInstituteName\":\"PHC\"," + + "\"referralReason\":\"Fever\",\"revisitDate\":\"2024-01-01T00:00:00.000\"," + + "\"refrredToAdditionalServiceList\":[{\"serviceID\":1,\"serviceName\":\"Lab\"}," + + "{\"serviceID\":2,\"serviceName\":\"Tele-consultation\"},{\"serviceID\":3," + + "\"serviceName\":\"Radiology\"}]"); + + assertEquals(1L, service.updateBenReferDetails(json(request))); + verify(benReferDetailsRepo).updateReferredInstituteName(any(), any(), any(), any(), eq(5L), eq("U")); + } + + @Test + void updateBenReferDetails_keepsARowThatWasNeverSyncedMarkedAsNew() throws Exception { + ArrayList statuses = new ArrayList<>(); + statuses.add(new Object[] { 5L, "N", "Lab" }); + when(benReferDetailsRepo.getBenReferDetailsStatus(any(), any())).thenReturn(statuses); + when(benReferDetailsRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + assertEquals(1L, service.updateBenReferDetails( + json(referRequest(",\"referredToInstituteName\":\"PHC\"")))); + verify(benReferDetailsRepo).updateReferredInstituteName(any(), any(), any(), any(), eq(5L), eq("N")); + } + + @Test + void updateBenReferDetails_leavesTheStoredRowsAloneWhenNothingWasEntered() throws Exception { + when(benReferDetailsRepo.getBenReferDetailsStatus(any(), any())).thenReturn(new ArrayList<>()); + when(benReferDetailsRepo.saveAll(any())).thenReturn(new ArrayList<>()); + + assertEquals(1L, service.updateBenReferDetails(json(referRequest("")))); + } + + @Test + void getReferralDetails_mapsTheStoredReferral() { + when(benReferDetailsRepo.getBenReferDetails(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getReferralDetails(1L, 2L)); + } + } + + @Nested + @DisplayName("investigations and prescriptions") + class InvestigationsAndPrescriptions { + + @Test + void getInvestigationDetails_mapsTheStoredOrders() { + when(labTestOrderDetailRepo.getLabTestOrderDetails(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getInvestigationDetails(1L, 2L)); + } + + @Test + void getPrescribedDrugs_mapsTheStoredDrugs() { + when(prescribedDrugDetailRepo.getBenPrescribedDrugDetails(1L, 2L)).thenReturn(new ArrayList<>()); + assertEquals("[]", service.getPrescribedDrugs(1L, 2L)); + } + + @Test + void deletePrescribedMedicine_reportsWhetherTheRowWasRemoved() throws Exception { + JSONObject request = new JSONObject(); + request.put("id", 5L); + when(prescribedDrugDetailRepo.deletePrescribedmedicine(5L)).thenReturn(1); + assertEquals("record deleted successfully", service.deletePrescribedMedicine(request)); + + when(prescribedDrugDetailRepo.deletePrescribedmedicine(5L)).thenReturn(0); + assertNull(service.deletePrescribedMedicine(request)); + assertNull(service.deletePrescribedMedicine(new JSONObject())); + assertNull(service.deletePrescribedMedicine(null)); + } + } + + @Nested + @DisplayName("beneficiary flow after doctor data") + class BeneficiaryFlow { + + private CommonUtilityClass utility(Boolean isSpecialist) { + CommonUtilityClass utility = new CommonUtilityClass(); + utility.setBenFlowID(1L); + utility.setBeneficiaryID(2L); + utility.setBenVisitID(3L); + utility.setBeneficiaryRegID(4L); + utility.setVisitCode(5L); + utility.setCreatedBy("doctor"); + utility.setIsSpecialist(isSpecialist); + return utility; + } + + private TeleconsultationRequestOBJ teleconsultationRequest() { + TeleconsultationRequestOBJ request = new TeleconsultationRequestOBJ(); + request.setUserID(7); + request.setAllocationDate(new Timestamp(System.currentTimeMillis())); + return request; + } + + @Test + void updateBenFlowtableAfterDocDataSave_recordsTheDoctorAndSendsTheBeneficiaryToTheLabAndPharmacy() + throws Exception { + Users doctor = new Users(); + doctor.setUserID(11L); + when(userLoginRepo.getUserByUsername("doctor")).thenReturn(doctor); + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocData(any(), any(), any(), any(), anyShort(), + anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataSave(utility(false), true, true, + teleconsultationRequest(), true)); + verify(benVisitDetailRepo).updateDoctorID(11L, 5L); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocData(eq(1L), eq(4L), eq(2L), eq(3L), + eq((short) 2), eq((short) 1), eq((short) 0), eq((short) 1), eq(7), any(), eq(true)); + } + + @Test + void updateBenFlowtableAfterDocDataSave_closesTheDoctorStepWhenNothingWasPrescribed() throws Exception { + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocData(any(), any(), any(), any(), anyShort(), + anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataSave(utility(false), false, false, null, false)); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocData(eq(1L), eq(4L), eq(2L), eq(3L), + eq((short) 9), eq((short) 0), eq((short) 0), eq((short) 0), eq(0), any(), eq(false)); + } + + @Test + void updateBenFlowtableAfterDocDataSave_marksAnNcdScreeningTeleconsultationReferral() throws Exception { + ReflectionTestUtils.setField(service, "TMReferred", 1); + BeneficiaryFlowStatus stored = new BeneficiaryFlowStatus(); + stored.setVisitCategory("NCD screening"); + when(beneficiaryFlowStatusRepo.specialistFlagAndCategoryValue(5L)).thenReturn(stored); + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocData(any(), any(), any(), any(), anyShort(), + anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataSave(utility(false), false, false, null, false)); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocData(any(), any(), any(), any(), anyShort(), + anyShort(), anyShort(), eq((short) 100), anyInt(), any(), any()); + } + + @Test + void updateBenFlowtableAfterDocDataSave_leavesTheDoctorUnresolvedWhenTheUsernameIsUnknown() throws Exception { + CommonUtilityClass utility = utility(false); + utility.setCreatedBy(" "); + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocData(any(), any(), any(), any(), anyShort(), + anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataSave(utility, false, false, null, false)); + verify(benVisitDetailRepo, never()).updateDoctorID(anyLong(), anyLong()); + } + + @Test + void updateBenFlowtableAfterDocDataSave_leavesTheDoctorUnresolvedWhenTheVisitHasNoCode() throws Exception { + CommonUtilityClass utility = utility(false); + utility.setVisitCode(null); + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocData(any(), any(), any(), any(), anyShort(), + anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataSave(utility, false, false, null, false)); + verify(benVisitDetailRepo, never()).updateDoctorID(anyLong(), anyLong()); + } + + @Test + void updateBenFlowtableAfterDocDataUpdate_routesASpecialistUpdateThroughTheSpecialistFlow() throws Exception { + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocDataUpdateTCSpecialist(any(), any(), any(), + any(), anyShort(), anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataUpdate(utility(true), true, true, null, true)); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocDataUpdateTCSpecialist(eq(1L), eq(4L), eq(2L), + eq(3L), eq((short) 0), eq((short) 1), eq((short) 0), eq((short) 2), eq(0), any(), eq(true)); + } + + @Test + void updateBenFlowtableAfterDocDataUpdate_closesTheSpecialistStepWhenNothingWasPrescribed() throws Exception { + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocDataUpdateTCSpecialist(any(), any(), any(), + any(), anyShort(), anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataUpdate(utility(true), false, false, null, false)); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocDataUpdateTCSpecialist(any(), any(), any(), + any(), anyShort(), eq((short) 0), anyShort(), eq((short) 9), anyInt(), any(), any()); + } + + @Test + void updateBenFlowtableAfterDocDataUpdate_usesTheWalkInFlowForAnOrdinaryDoctorUpdate() throws Exception { + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocDataUpdateWDF(any(), any(), any(), any(), + anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataUpdate(utility(false), true, true, + teleconsultationRequest(), true)); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocDataUpdateWDF(eq(1L), eq(4L), eq(2L), eq(3L), + eq((short) 2), eq((short) 1), eq((short) 0), eq(7), any(), eq(true)); + } + + @Test + void updateBenFlowtableAfterDocDataUpdate_usesTheReferralFlowForAnNcdScreeningReferral() throws Exception { + ReflectionTestUtils.setField(service, "TMReferred", 1); + BeneficiaryFlowStatus stored = new BeneficiaryFlowStatus(); + stored.setVisitCategory("NCD screening"); + when(beneficiaryFlowStatusRepo.specialistFlagAndCategoryValue(5L)).thenReturn(stored); + when(commonBenStatusFlowServiceImpl.updateBenFlowAfterDocDataUpdate(any(), any(), any(), any(), + anyShort(), anyShort(), anyShort(), anyShort(), anyInt(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenFlowtableAfterDocDataUpdate(utility(false), false, false, null, false)); + verify(commonBenStatusFlowServiceImpl).updateBenFlowAfterDocDataUpdate(any(), any(), any(), any(), + eq((short) 9), eq((short) 0), anyShort(), eq((short) 100), anyInt(), any(), any()); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/common/transaction/CommonNurseServiceImplTest.java b/src/test/java/com/iemr/mmu/service/common/transaction/CommonNurseServiceImplTest.java new file mode 100644 index 00000000..dfe792e1 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/common/transaction/CommonNurseServiceImplTest.java @@ -0,0 +1,2479 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.common.transaction; + +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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Date; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.iemr.mmu.data.anc.BenAllergyHistory; +import com.iemr.mmu.data.anc.BenFamilyHistory; +import com.iemr.mmu.data.anc.BenMedHistory; +import com.iemr.mmu.data.anc.BenMedicationHistory; +import com.iemr.mmu.data.anc.BenMenstrualDetails; +import com.iemr.mmu.data.anc.BenPersonalHabit; +import com.iemr.mmu.data.anc.BencomrbidityCondDetails; +import com.iemr.mmu.data.anc.ChildOptionalVaccineDetail; +import com.iemr.mmu.data.anc.ChildVaccineDetail1; +import com.iemr.mmu.data.anc.FemaleObstetricHistory; +import com.iemr.mmu.data.anc.PhyGeneralExamination; +import com.iemr.mmu.data.anc.PhyHeadToToeExamination; +import com.iemr.mmu.data.anc.SysCardiovascularExamination; +import com.iemr.mmu.data.anc.SysCentralNervousExamination; +import com.iemr.mmu.data.anc.SysGastrointestinalExamination; +import com.iemr.mmu.data.anc.SysGenitourinarySystemExamination; +import com.iemr.mmu.data.anc.SysMusculoskeletalSystemExamination; +import com.iemr.mmu.data.anc.SysRespiratoryExamination; +import com.iemr.mmu.data.anc.WrapperChildOptionalVaccineDetail; +import com.iemr.mmu.data.anc.WrapperComorbidCondDetails; +import com.iemr.mmu.data.anc.WrapperFemaleObstetricHistory; +import com.iemr.mmu.data.anc.WrapperImmunizationHistory; +import com.iemr.mmu.data.anc.WrapperMedicationHistory; +import com.iemr.mmu.data.login.Users; +import com.iemr.mmu.data.ncdScreening.IDRSData; +import com.iemr.mmu.data.ncdScreening.PhysicalActivityType; +import com.iemr.mmu.data.nurse.BenAnthropometryDetail; +import com.iemr.mmu.data.nurse.BenPhysicalVitalDetail; +import com.iemr.mmu.data.nurse.BeneficiaryVisitDetail; +import com.iemr.mmu.data.quickConsultation.BenChiefComplaint; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.repo.bmiCalculation.BMICalculationRepo; +import com.iemr.mmu.repo.login.UserLoginRepo; +import com.iemr.mmu.repo.nurse.BenAnthropometryRepo; +import com.iemr.mmu.repo.nurse.BenCancerVitalDetailRepo; +import com.iemr.mmu.repo.nurse.BenPhysicalVitalRepo; +import com.iemr.mmu.repo.nurse.BenVisitDetailRepo; +import com.iemr.mmu.repo.nurse.anc.BenAdherenceRepo; +import com.iemr.mmu.repo.nurse.anc.BenAllergyHistoryRepo; +import com.iemr.mmu.repo.nurse.anc.BenChildDevelopmentHistoryRepo; +import com.iemr.mmu.repo.nurse.anc.BenFamilyHistoryRepo; +import com.iemr.mmu.repo.nurse.anc.BenMedHistoryRepo; +import com.iemr.mmu.repo.nurse.anc.BenMedicationHistoryRepo; +import com.iemr.mmu.repo.nurse.anc.BenMenstrualDetailsRepo; +import com.iemr.mmu.repo.nurse.anc.BenPersonalHabitRepo; +import com.iemr.mmu.repo.nurse.anc.BencomrbidityCondRepo; +import com.iemr.mmu.repo.nurse.anc.ChildFeedingDetailsRepo; +import com.iemr.mmu.repo.nurse.anc.ChildOptionalVaccineDetailRepo; +import com.iemr.mmu.repo.nurse.anc.ChildVaccineDetail1Repo; +import com.iemr.mmu.repo.nurse.anc.FemaleObstetricHistoryRepo; +import com.iemr.mmu.repo.nurse.anc.PerinatalHistoryRepo; +import com.iemr.mmu.repo.nurse.anc.PhyGeneralExaminationRepo; +import com.iemr.mmu.repo.nurse.anc.PhyHeadToToeExaminationRepo; +import com.iemr.mmu.repo.nurse.anc.SysCardiovascularExaminationRepo; +import com.iemr.mmu.repo.nurse.anc.SysCentralNervousExaminationRepo; +import com.iemr.mmu.repo.nurse.anc.SysGastrointestinalExaminationRepo; +import com.iemr.mmu.repo.nurse.anc.SysGenitourinarySystemExaminationRepo; +import com.iemr.mmu.repo.nurse.anc.SysMusculoskeletalSystemExaminationRepo; +import com.iemr.mmu.repo.nurse.anc.SysRespiratoryExaminationRepo; +import com.iemr.mmu.repo.nurse.ncdscreening.IDRSDataRepo; +import com.iemr.mmu.repo.nurse.ncdscreening.PhysicalActivityTypeRepo; +import com.iemr.mmu.repo.quickConsultation.BenChiefComplaintRepo; +import com.iemr.mmu.repo.quickConsultation.LabTestOrderDetailRepo; +import com.iemr.mmu.repo.quickConsultation.PrescribedDrugDetailRepo; +import com.iemr.mmu.repo.quickConsultation.PrescriptionDetailRepo; +import com.iemr.mmu.repo.registrar.RegistrarRepoBenData; +import com.iemr.mmu.repo.registrar.ReistrarRepoBenSearch; +import com.iemr.mmu.utils.AESEncryption.AESEncryptionDecryption; +import com.iemr.mmu.utils.exception.IEMRException; + +class CommonNurseServiceImplTest { + + @Mock + private BenVisitDetailRepo benVisitDetailRepo; + @Mock + private UserLoginRepo userLoginRepo; + @Mock + private BenChiefComplaintRepo benChiefComplaintRepo; + @Mock + private BenMedHistoryRepo benMedHistoryRepo; + @Mock + private BencomrbidityCondRepo bencomrbidityCondRepo; + @Mock + private BenMedicationHistoryRepo benMedicationHistoryRepo; + @Mock + private FemaleObstetricHistoryRepo femaleObstetricHistoryRepo; + @Mock + private BenMenstrualDetailsRepo benMenstrualDetailsRepo; + @Mock + private BenFamilyHistoryRepo benFamilyHistoryRepo; + @Mock + private BenPersonalHabitRepo benPersonalHabitRepo; + @Mock + private BenAllergyHistoryRepo benAllergyHistoryRepo; + @Mock + private ChildOptionalVaccineDetailRepo childOptionalVaccineDetailRepo; + @Mock + private ChildVaccineDetail1Repo childVaccineDetail1Repo; + @Mock + private BenAnthropometryRepo benAnthropometryRepo; + @Mock + private BenPhysicalVitalRepo benPhysicalVitalRepo; + @Mock + private PhyGeneralExaminationRepo phyGeneralExaminationRepo; + @Mock + private PhyHeadToToeExaminationRepo phyHeadToToeExaminationRepo; + @Mock + private SysGastrointestinalExaminationRepo sysGastrointestinalExaminationRepo; + @Mock + private SysCardiovascularExaminationRepo sysCardiovascularExaminationRepo; + @Mock + private SysRespiratoryExaminationRepo sysRespiratoryExaminationRepo; + @Mock + private SysCentralNervousExaminationRepo sysCentralNervousExaminationRepo; + @Mock + private SysMusculoskeletalSystemExaminationRepo sysMusculoskeletalSystemExaminationRepo; + @Mock + private SysGenitourinarySystemExaminationRepo sysGenitourinarySystemExaminationRepo; + @Mock + private RegistrarRepoBenData registrarRepoBenData; + @Mock + private PrescriptionDetailRepo prescriptionDetailRepo; + @Mock + private LabTestOrderDetailRepo labTestOrderDetailRepo; + @Mock + private PrescribedDrugDetailRepo prescribedDrugDetailRepo; + @Mock + private ReistrarRepoBenSearch reistrarRepoBenSearch; + @Mock + private BenAdherenceRepo benAdherenceRepo; + @Mock + private BenChildDevelopmentHistoryRepo benChildDevelopmentHistoryRepo; + @Mock + private ChildFeedingDetailsRepo childFeedingDetailsRepo; + @Mock + private PerinatalHistoryRepo perinatalHistoryRepo; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + @Mock + private BenCancerVitalDetailRepo benCancerVitalDetailRepo; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private PhysicalActivityTypeRepo physicalActivityTypeRepo; + @Mock + private AESEncryptionDecryption aESEncryptionDecryption; + @Mock + private IDRSDataRepo iDRSDataRepo; + @Mock + private BMICalculationRepo bmiCalculationRepo; + + @InjectMocks + private CommonNurseServiceImpl service; + + private AutoCloseable mocks; + + @BeforeEach + void setUp() { + mocks = MockitoAnnotations.openMocks(this); + } + + @Nested + @DisplayName("beneficiary visit details") + class VisitDetails { + + @Test + void saveBeneficiaryVisitDetails_incrementsVisitCountAndResolvesNurse() { + BeneficiaryVisitDetail detail = new BeneficiaryVisitDetail(); + detail.setBeneficiaryRegID(11L); + detail.setCreatedBy("nurse1"); + detail.setFileIDs(new String[] { "a", "b" }); + + Users user = new Users(); + user.setUserID(77L); + when(userLoginRepo.getUserByUsername("nurse1")).thenReturn(user); + when(benVisitDetailRepo.getVisitCountForBeneficiary(11L)).thenReturn((short) 3); + + BeneficiaryVisitDetail saved = new BeneficiaryVisitDetail(); + saved.setBenVisitID(99L); + when(benVisitDetailRepo.save(any(BeneficiaryVisitDetail.class))).thenReturn(saved); + + assertEquals(99L, service.saveBeneficiaryVisitDetails(detail)); + assertEquals((short) 4, detail.getVisitNo()); + assertEquals("a,b,", detail.getReportFilePath()); + assertEquals(77L, detail.getNurseID()); + verify(benVisitDetailRepo).updateVanSerialNo(99L); + } + + @Test + void saveBeneficiaryVisitDetails_startsAtVisitOneWhenBeneficiaryHasNoHistory() { + BeneficiaryVisitDetail detail = new BeneficiaryVisitDetail(); + detail.setBeneficiaryRegID(11L); + when(benVisitDetailRepo.getVisitCountForBeneficiary(11L)).thenReturn(null); + + BeneficiaryVisitDetail saved = new BeneficiaryVisitDetail(); + saved.setBenVisitID(5L); + when(benVisitDetailRepo.save(any(BeneficiaryVisitDetail.class))).thenReturn(saved); + + assertEquals(5L, service.saveBeneficiaryVisitDetails(detail)); + assertEquals((short) 1, detail.getVisitNo()); + assertEquals("", detail.getReportFilePath()); + assertNull(detail.getNurseID()); + } + + @Test + void saveBeneficiaryVisitDetails_leavesNurseUnresolvedWhenUsernameIsUnknown() { + BeneficiaryVisitDetail detail = new BeneficiaryVisitDetail(); + detail.setCreatedBy(" "); + + BeneficiaryVisitDetail saved = new BeneficiaryVisitDetail(); + saved.setBenVisitID(1L); + when(benVisitDetailRepo.save(any(BeneficiaryVisitDetail.class))).thenReturn(saved); + + service.saveBeneficiaryVisitDetails(detail); + assertNull(detail.getNurseID()); + verify(userLoginRepo, never()).getUserByUsername(anyString()); + } + + @Test + void saveBeneficiaryVisitDetails_leavesNurseUnresolvedWhenLookupFindsNoUser() { + BeneficiaryVisitDetail detail = new BeneficiaryVisitDetail(); + detail.setCreatedBy("ghost"); + when(userLoginRepo.getUserByUsername("ghost")).thenReturn(null); + + BeneficiaryVisitDetail saved = new BeneficiaryVisitDetail(); + saved.setBenVisitID(1L); + when(benVisitDetailRepo.save(any(BeneficiaryVisitDetail.class))).thenReturn(saved); + + service.saveBeneficiaryVisitDetails(detail); + assertNull(detail.getNurseID()); + } + + @Test + void getBenVisitCount_returnsNextVisitNumber() { + when(benVisitDetailRepo.getVisitCountForBeneficiary(1L)).thenReturn((short) 2); + assertEquals((short) 3, service.getBenVisitCount(1L)); + + when(benVisitDetailRepo.getVisitCountForBeneficiary(2L)).thenReturn(null); + assertEquals((short) 1, service.getBenVisitCount(2L)); + } + + @Test + void updateBeneficiaryStatus_delegatesToRegistrarRepo() { + when(registrarRepoBenData.updateBenFlowStatus('N', 4L)).thenReturn(1); + assertEquals(1, service.updateBeneficiaryStatus('N', 4L)); + } + + @Test + void getMaxCurrentdate_returnsZeroWhenNoPreviousVisitExists() throws Exception { + when(benVisitDetailRepo.getMaxCreatedDate(1L, "reason", "category")).thenReturn(null); + assertEquals(0, service.getMaxCurrentdate(1L, "reason", "category")); + } + + @Test + void getMaxCurrentdate_returnsPositiveWhileThePreviousVisitIsStillWithinTenMinutes() throws Exception { + String recent = new java.sql.Timestamp(System.currentTimeMillis()).toString(); + when(benVisitDetailRepo.getMaxCreatedDate(1L, "reason", "category")).thenReturn(recent); + assertTrue(service.getMaxCurrentdate(1L, "reason", "category") > 0); + } + + @Test + void getMaxCurrentdate_returnsNegativeOnceThePreviousVisitIsOlderThanTenMinutes() throws Exception { + String old = new java.sql.Timestamp(System.currentTimeMillis() - 3600_000L).toString(); + when(benVisitDetailRepo.getMaxCreatedDate(1L, "reason", "category")).thenReturn(old); + assertTrue(service.getMaxCurrentdate(1L, "reason", "category") < 0); + } + + @Test + void getMaxCurrentdate_wrapsAnUnparseableStoredDate() { + when(benVisitDetailRepo.getMaxCreatedDate(1L, "r", "c")).thenReturn("not-a-date.0"); + IEMRException thrown = assertThrows(IEMRException.class, + () -> service.getMaxCurrentdate(1L, "r", "c")); + assertTrue(thrown.getMessage().contains("Error while parseing created date")); + } + + @Test + void generateVisitCode_padsVanAndVisitIdsIntoAFourteenDigitCode() { + when(benVisitDetailRepo.updateVisitCode(anyLong(), anyLong())).thenReturn(1); + assertEquals(Long.valueOf("10000100000123"), service.generateVisitCode(123L, 1, 1)); + } + + @Test + void generateVisitCode_returnsZeroWhenTheCodeCouldNotBeStored() { + when(benVisitDetailRepo.updateVisitCode(anyLong(), anyLong())).thenReturn(0); + assertEquals(0L, service.generateVisitCode(123L, 1, 1)); + } + + @Test + void updateVisitCodeInVisitDetailsTable_delegatesToRepo() { + when(benVisitDetailRepo.updateVisitCode(5L, 6L)).thenReturn(1); + assertEquals(1, service.updateVisitCodeInVisitDetailsTable(5L, 6L)); + } + + @Test + void updateBeneficiaryVisitDetails_returnsRepoResult() { + BeneficiaryVisitDetail detail = new BeneficiaryVisitDetail(); + when(benVisitDetailRepo.updateBeneficiaryVisitDetail(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any())).thenReturn(1); + assertEquals(1, service.updateBeneficiaryVisitDetails(detail)); + } + + @Test + void updateBeneficiaryVisitDetails_returnsZeroWhenTheRepoFails() { + BeneficiaryVisitDetail detail = new BeneficiaryVisitDetail(); + when(benVisitDetailRepo.updateBeneficiaryVisitDetail(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any())).thenThrow(new RuntimeException("db down")); + assertEquals(0, service.updateBeneficiaryVisitDetails(detail)); + } + + @Test + void getCSVisitDetails_returnsNullWhenTheVisitDoesNotExist() throws Exception { + when(benVisitDetailRepo.getVisitDetails(1L, 2L)).thenReturn(null); + assertNull(service.getCSVisitDetails(1L, 2L)); + } + + @Test + void getCSVisitDetails_decryptsEachAttachedReportPath() throws Exception { + BeneficiaryVisitDetail stored = new BeneficiaryVisitDetail(); + stored.setBenVisitID(1L); + stored.setReportFilePath("enc1,,enc2"); + when(benVisitDetailRepo.getVisitDetails(1L, 2L)).thenReturn(stored); + when(aESEncryptionDecryption.decrypt("enc1")).thenReturn("/tmp/reports/first.pdf"); + when(aESEncryptionDecryption.decrypt("enc2")).thenReturn("/tmp/reports/second.pdf"); + + BeneficiaryVisitDetail result = service.getCSVisitDetails(1L, 2L); + + assertNotNull(result); + assertEquals(2, result.getFiles().size()); + assertEquals("first.pdf", result.getFiles().get(0).get("fileName")); + assertEquals("enc2", result.getFiles().get(1).get("filePath")); + } + + @Test + void getCSVisitDetails_returnsAnEmptyFileListWhenNoReportsAreAttached() throws Exception { + BeneficiaryVisitDetail stored = new BeneficiaryVisitDetail(); + stored.setBenVisitID(1L); + stored.setReportFilePath(" "); + when(benVisitDetailRepo.getVisitDetails(1L, 2L)).thenReturn(stored); + + assertTrue(service.getCSVisitDetails(1L, 2L).getFiles().isEmpty()); + } + } + + @Nested + @DisplayName("history and examination saves") + class HistorySaves { + + @Test + void saveBenChiefComplaints_skipsEntriesWithoutAComplaintId() { + BenChiefComplaint withId = new BenChiefComplaint(); + withId.setChiefComplaintID(1); + withId.setBenChiefComplaintID(10L); + BenChiefComplaint withoutId = new BenChiefComplaint(); + + when(benChiefComplaintRepo.saveAll(any())).thenReturn(Collections.singletonList(withId)); + + assertEquals(1, service.saveBenChiefComplaints(Arrays.asList(withId, withoutId))); + verify(benChiefComplaintRepo).updateVanSerialNo(10L); + } + + @Test + void saveBenChiefComplaints_succeedsWhenThereIsNothingToSave() { + assertEquals(1, service.saveBenChiefComplaints(new ArrayList<>())); + verify(benChiefComplaintRepo, never()).saveAll(any()); + } + + @Test + void saveBenChiefComplaints_reportsFailureWhenNotEveryComplaintWasSaved() { + BenChiefComplaint withId = new BenChiefComplaint(); + withId.setChiefComplaintID(1); + when(benChiefComplaintRepo.saveAll(any())).thenReturn(new ArrayList<>()); + assertEquals(0, service.saveBenChiefComplaints(Collections.singletonList(withId))); + } + + @Test + void saveBenPastHistory_savesEveryPastHistoryEntry() { + BenMedHistory history = mock(BenMedHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenMedHistory())); + when(history.getBenPastHistory()).thenReturn(entries); + when(benMedHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1L, service.saveBenPastHistory(history)); + } + + @Test + void saveBenPastHistory_succeedsWhenThereIsNoPastHistory() { + BenMedHistory history = mock(BenMedHistory.class); + when(history.getBenPastHistory()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveBenPastHistory(history)); + } + + @Test + void saveBenPastHistory_reportsFailureWhenNotEveryEntryWasSaved() { + BenMedHistory history = mock(BenMedHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenMedHistory())); + when(history.getBenPastHistory()).thenReturn(entries); + when(benMedHistoryRepo.saveAll(entries)).thenReturn(new ArrayList()); + assertNull(service.saveBenPastHistory(history)); + } + + @Test + void saveBenComorbidConditions_returnsTheIdOfTheFirstStoredCondition() { + WrapperComorbidCondDetails wrapper = mock(WrapperComorbidCondDetails.class); + BencomrbidityCondDetails stored = new BencomrbidityCondDetails(); + stored.setID(42L); + ArrayList entries = new ArrayList<>(Collections.singletonList(stored)); + when(wrapper.getComrbidityConds()).thenReturn(entries); + when(bencomrbidityCondRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(42L, service.saveBenComorbidConditions(wrapper)); + } + + @Test + void saveBenComorbidConditions_succeedsWhenThereAreNoConditions() { + WrapperComorbidCondDetails wrapper = mock(WrapperComorbidCondDetails.class); + when(wrapper.getComrbidityConds()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveBenComorbidConditions(wrapper)); + } + + @Test + void saveBenMedicationHistory_returnsTheIdOfTheFirstStoredEntry() { + WrapperMedicationHistory wrapper = mock(WrapperMedicationHistory.class); + BenMedicationHistory stored = new BenMedicationHistory(); + stored.setID(7L); + ArrayList entries = new ArrayList<>(Collections.singletonList(stored)); + when(wrapper.getBenMedicationHistoryDetails()).thenReturn(entries); + when(benMedicationHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(7L, service.saveBenMedicationHistory(wrapper)); + } + + @Test + void saveBenMedicationHistory_succeedsWhenThereIsNoMedicationHistory() { + WrapperMedicationHistory wrapper = mock(WrapperMedicationHistory.class); + when(wrapper.getBenMedicationHistoryDetails()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveBenMedicationHistory(wrapper)); + } + + @Test + void saveFemaleObstetricHistory_flattensEveryComplicationListOntoTheStoredRow() { + FemaleObstetricHistory entry = new FemaleObstetricHistory(); + entry.setPregComplicationList(complications("pregComplicationID", "pregComplicationType")); + entry.setDeliveryComplicationList(complications("deliveryComplicationID", "deliveryComplicationType")); + entry.setPostpartumComplicationList( + complications("postpartumComplicationID", "postpartumComplicationType")); + + ArrayList> postAbortion = new ArrayList<>(); + postAbortion.add(complication(1d, "first")); + postAbortion.add(complication(2d, "second")); + entry.setPostAbortionComplication(postAbortion); + entry.setAbortionType(complication(3d, "abortion")); + + Map facility = new HashMap<>(); + facility.put("serviceFacilityID", 4d); + facility.put("facilityName", "PHC"); + entry.setTypeofFacility(facility); + + WrapperFemaleObstetricHistory wrapper = mock(WrapperFemaleObstetricHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(entry)); + when(wrapper.getFemaleObstetricHistoryDetails()).thenReturn(entries); + when(femaleObstetricHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1L, service.saveFemaleObstetricHistory(wrapper)); + assertEquals("11,12", entry.getPregComplicationID()); + assertEquals("name11,name12", entry.getPregComplicationType()); + assertEquals("11,12", entry.getDeliveryComplicationID()); + assertEquals("11,12", entry.getPostpartumComplicationID()); + assertEquals("1,2", entry.getPostAbortionComplication_db()); + assertEquals("first,second", entry.getPostAbortionComplicationsValues()); + assertEquals(3, entry.getAbortionTypeID()); + assertEquals("abortion", entry.getTypeOfAbortionValue()); + assertEquals(4, entry.getTypeofFacilityID()); + assertEquals("PHC", entry.getServiceFacilityValue()); + } + + @Test + void saveFemaleObstetricHistory_leavesComplicationFieldsEmptyWhenNoneWereReported() { + FemaleObstetricHistory entry = new FemaleObstetricHistory(); + WrapperFemaleObstetricHistory wrapper = mock(WrapperFemaleObstetricHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(entry)); + when(wrapper.getFemaleObstetricHistoryDetails()).thenReturn(entries); + when(femaleObstetricHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1L, service.saveFemaleObstetricHistory(wrapper)); + assertEquals("", entry.getPregComplicationID()); + assertNull(entry.getPostAbortionComplication_db()); + } + + @Test + void saveFemaleObstetricHistory_succeedsWhenThereIsNoObstetricHistory() { + WrapperFemaleObstetricHistory wrapper = mock(WrapperFemaleObstetricHistory.class); + when(wrapper.getFemaleObstetricHistoryDetails()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveFemaleObstetricHistory(wrapper)); + } + + private ArrayList> complications(String idKey, String nameKey) { + ArrayList> list = new ArrayList<>(); + for (int i = 11; i <= 12; i++) { + Map item = new HashMap<>(); + item.put(idKey, i); + item.put(nameKey, "name" + i); + list.add(item); + } + return list; + } + + private Map complication(Double id, String value) { + Map map = new HashMap<>(); + map.put("complicationID", id); + map.put("complicationValue", value); + return map; + } + + @Test + void saveBenMenstrualHistory_flattensTheReportedProblemsBeforeSaving() { + BenMenstrualDetails details = new BenMenstrualDetails(); + ArrayList> problems = new ArrayList<>(); + for (int i = 1; i <= 2; i++) { + Map problem = new HashMap<>(); + problem.put("menstrualProblemID", i); + problem.put("problemName", "problem" + i); + problems.add(problem); + } + details.setMenstrualProblemList(problems); + + BenMenstrualDetails stored = new BenMenstrualDetails(); + stored.setBenMenstrualID(9); + when(benMenstrualDetailsRepo.save(details)).thenReturn(stored); + + assertEquals(9, service.saveBenMenstrualHistory(details)); + assertEquals("1,2", details.getMenstrualProblemID()); + assertEquals("problem1,problem2", details.getProblemName()); + } + + @Test + void saveBenMenstrualHistory_returnsNullWhenTheRowWasNotPersisted() { + BenMenstrualDetails details = new BenMenstrualDetails(); + BenMenstrualDetails stored = new BenMenstrualDetails(); + stored.setBenMenstrualID(0); + when(benMenstrualDetailsRepo.save(details)).thenReturn(stored); + assertNull(service.saveBenMenstrualHistory(details)); + } + + @Test + void saveBenFamilyHistory_savesEveryReportedFamilyDisease() { + BenFamilyHistory input = mock(BenFamilyHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenFamilyHistory())); + when(input.getBenFamilyHistory()).thenReturn(entries); + when(benFamilyHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1L, service.saveBenFamilyHistory(input)); + } + + @Test + void saveBenFamilyHistory_succeedsWhenThereIsNoFamilyHistory() { + BenFamilyHistory input = mock(BenFamilyHistory.class); + when(input.getBenFamilyHistory()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveBenFamilyHistory(input)); + } + + @Test + void saveBenFamilyHistoryNCDScreening_savesTheScreeningVariantOfTheList() { + BenFamilyHistory input = mock(BenFamilyHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenFamilyHistory())); + when(input.getBenFamilyHist()).thenReturn(entries); + when(benFamilyHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1L, service.saveBenFamilyHistoryNCDScreening(input)); + } + + @Test + void saveBenFamilyHistoryNCDScreening_succeedsWhenNothingWasScreened() { + BenFamilyHistory input = mock(BenFamilyHistory.class); + when(input.getBenFamilyHist()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveBenFamilyHistoryNCDScreening(input)); + } + + @Test + void savePersonalHistory_savesEveryReportedHabit() { + BenPersonalHabit input = mock(BenPersonalHabit.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenPersonalHabit())); + when(input.getPersonalHistory()).thenReturn(entries); + when(benPersonalHabitRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.savePersonalHistory(input)); + } + + @Test + void savePersonalHistory_succeedsWhenNoHabitsWereReported() { + BenPersonalHabit input = mock(BenPersonalHabit.class); + when(input.getPersonalHistory()).thenReturn(new ArrayList<>()); + assertEquals(1, service.savePersonalHistory(input)); + } + + @Test + void saveAllergyHistory_savesEveryReportedAllergy() { + BenAllergyHistory input = mock(BenAllergyHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenAllergyHistory())); + when(input.getBenAllergicHistory()).thenReturn(entries); + when(benAllergyHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1L, service.saveAllergyHistory(input)); + } + + @Test + void saveAllergyHistory_succeedsWhenNoAllergiesWereReported() { + BenAllergyHistory input = mock(BenAllergyHistory.class); + when(input.getBenAllergicHistory()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveAllergyHistory(input)); + } + + @Test + void saveChildOptionalVaccineDetail_savesEveryOptionalVaccine() { + WrapperChildOptionalVaccineDetail wrapper = mock(WrapperChildOptionalVaccineDetail.class); + ArrayList entries = new ArrayList<>( + Collections.singletonList(new ChildOptionalVaccineDetail())); + when(wrapper.getChildOptionalVaccineDetails()).thenReturn(entries); + when(childOptionalVaccineDetailRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1L, service.saveChildOptionalVaccineDetail(wrapper)); + } + + @Test + void saveChildOptionalVaccineDetail_succeedsWhenNoOptionalVaccineWasGiven() { + WrapperChildOptionalVaccineDetail wrapper = mock(WrapperChildOptionalVaccineDetail.class); + when(wrapper.getChildOptionalVaccineDetails()).thenReturn(new ArrayList<>()); + assertEquals(1L, service.saveChildOptionalVaccineDetail(wrapper)); + } + + @Test + void saveImmunizationHistory_returnsTheIdOfTheFirstStoredVaccine() { + WrapperImmunizationHistory wrapper = mock(WrapperImmunizationHistory.class); + ChildVaccineDetail1 stored = new ChildVaccineDetail1(); + stored.setID(3L); + ArrayList entries = new ArrayList<>(Collections.singletonList(stored)); + when(wrapper.getBenChildVaccineDetails()).thenReturn(entries); + when(childVaccineDetail1Repo.saveAll(entries)).thenReturn(entries); + + assertEquals(3L, service.saveImmunizationHistory(wrapper)); + } + + @Test + void saveImmunizationHistory_returnsNullWhenNoVaccineWasStored() { + WrapperImmunizationHistory wrapper = mock(WrapperImmunizationHistory.class); + ArrayList entries = new ArrayList<>(); + when(wrapper.getBenChildVaccineDetails()).thenReturn(entries); + when(childVaccineDetail1Repo.saveAll(entries)).thenReturn(entries); + + assertNull(service.saveImmunizationHistory(wrapper)); + } + } + + @Nested + @DisplayName("vitals and anthropometry") + class Vitals { + + @Test + void saveBeneficiaryPhysicalAnthropometryDetails_returnsTheStoredId() { + BenAnthropometryDetail detail = new BenAnthropometryDetail(); + BenAnthropometryDetail stored = new BenAnthropometryDetail(); + stored.setID(5L); + when(benAnthropometryRepo.save(detail)).thenReturn(stored); + assertEquals(5L, service.saveBeneficiaryPhysicalAnthropometryDetails(detail)); + } + + @Test + void saveBeneficiaryPhysicalVitalDetails_averagesEveryBloodPressureReadingTaken() { + BenPhysicalVitalDetail detail = new BenPhysicalVitalDetail(); + detail.setSystolicBP_1stReading((short) 120); + detail.setDiastolicBP_1stReading((short) 80); + detail.setSystolicBP_2ndReading((short) 130); + detail.setDiastolicBP_2ndReading((short) 90); + detail.setSystolicBP_3rdReading((short) 140); + detail.setDiastolicBP_3rdReading((short) 100); + + BenPhysicalVitalDetail stored = new BenPhysicalVitalDetail(); + stored.setID(8L); + when(benPhysicalVitalRepo.save(detail)).thenReturn(stored); + + assertEquals(8L, service.saveBeneficiaryPhysicalVitalDetails(detail)); + assertEquals((short) 130, detail.getAverageSystolicBP()); + assertEquals((short) 90, detail.getAverageDiastolicBP()); + } + + @Test + void saveBeneficiaryPhysicalVitalDetails_leavesTheAverageUnsetWhenNoReadingWasTaken() { + BenPhysicalVitalDetail detail = new BenPhysicalVitalDetail(); + when(benPhysicalVitalRepo.save(detail)).thenReturn(null); + + assertNull(service.saveBeneficiaryPhysicalVitalDetails(detail)); + assertNull(detail.getAverageSystolicBP()); + } + + @Test + void getBeneficiaryPhysicalAnthropometryDetails_serialisesTheStoredRow() { + BenAnthropometryDetail stored = new BenAnthropometryDetail(); + stored.setID(1L); + when(benAnthropometryRepo.getBenAnthropometryDetail(1L, 2L)).thenReturn(stored); + assertTrue(service.getBeneficiaryPhysicalAnthropometryDetails(1L, 2L).contains("\"ID\":1")); + } + + @Test + void getBeneficiaryPhysicalVitalDetails_serialisesTheStoredRow() { + BenPhysicalVitalDetail stored = new BenPhysicalVitalDetail(); + stored.setID(2L); + when(benPhysicalVitalRepo.getBenPhysicalVitalDetail(1L, 2L)).thenReturn(stored); + assertTrue(service.getBeneficiaryPhysicalVitalDetails(1L, 2L).contains("\"ID\":2")); + } + + @Test + void updateANCAnthropometryDetails_marksAnAlreadySyncedRowAsUpdated() { + BenAnthropometryDetail detail = new BenAnthropometryDetail(); + detail.setBeneficiaryRegID(1L); + detail.setVisitCode(2L); + when(benAnthropometryRepo.getBenAnthropometryStatus(1L, 2L)).thenReturn("P"); + when(benAnthropometryRepo.updateANCCareDetails(any(), any(), any(), any(), any(), any(), any(), any(), + any(), org.mockito.ArgumentMatchers.eq("U"), anyLong(), anyLong())).thenReturn(1); + + assertEquals(1, service.updateANCAnthropometryDetails(detail)); + } + + @Test + void updateANCAnthropometryDetails_keepsARowThatWasNeverSyncedAsNew() { + BenAnthropometryDetail detail = new BenAnthropometryDetail(); + detail.setBeneficiaryRegID(1L); + detail.setVisitCode(2L); + when(benAnthropometryRepo.getBenAnthropometryStatus(1L, 2L)).thenReturn("N"); + when(benAnthropometryRepo.updateANCCareDetails(any(), any(), any(), any(), any(), any(), any(), any(), + any(), org.mockito.ArgumentMatchers.eq("N"), anyLong(), anyLong())).thenReturn(1); + + assertEquals(1, service.updateANCAnthropometryDetails(detail)); + } + + @Test + void updateANCAnthropometryDetails_doesNothingWithoutARow() { + assertEquals(0, service.updateANCAnthropometryDetails(null)); + } + + @Test + void updateANCPhysicalVitalDetails_copiesTheFirstReadingIntoTheAverage() { + BenPhysicalVitalDetail detail = new BenPhysicalVitalDetail(); + detail.setBeneficiaryRegID(1L); + detail.setVisitCode(2L); + detail.setSystolicBP_1stReading((short) 118); + detail.setDiastolicBP_1stReading((short) 76); + when(benPhysicalVitalRepo.getBenPhysicalVitalStatus(1L, 2L)).thenReturn("P"); + when(benPhysicalVitalRepo.updatePhysicalVitalDetails(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), + anyString(), any(), any(), any(), anyLong(), anyLong())).thenReturn(1); + + assertEquals(1, service.updateANCPhysicalVitalDetails(detail)); + assertEquals((short) 118, detail.getAverageSystolicBP()); + assertEquals((short) 76, detail.getAverageDiastolicBP()); + } + + @Test + void updateANCPhysicalVitalDetails_doesNothingWithoutARow() { + assertEquals(0, service.updateANCPhysicalVitalDetails(null)); + } + + @Test + void saveIDRS_returnsTheStoredId() { + IDRSData data = new IDRSData(); + IDRSData stored = new IDRSData(); + stored.setId(4L); + when(iDRSDataRepo.save(data)).thenReturn(stored); + assertEquals(4L, service.saveIDRS(data)); + } + + @Test + void saveIDRS_returnsNullWhenNothingWasStored() { + IDRSData data = new IDRSData(); + when(iDRSDataRepo.save(data)).thenReturn(null); + assertNull(service.saveIDRS(data)); + } + + @Test + void savePhysicalActivity_returnsTheStoredId() { + PhysicalActivityType activity = new PhysicalActivityType(); + PhysicalActivityType stored = new PhysicalActivityType(); + stored.setpAID(6L); + when(physicalActivityTypeRepo.save(activity)).thenReturn(stored); + assertEquals(6L, service.savePhysicalActivity(activity)); + } + + @Test + void savePhysicalActivity_returnsNullWhenNothingWasStored() { + PhysicalActivityType activity = new PhysicalActivityType(); + when(physicalActivityTypeRepo.save(activity)).thenReturn(null); + assertNull(service.savePhysicalActivity(activity)); + } + } + + @Nested + @DisplayName("examination saves") + class ExaminationSaves { + + @Test + void savePhyGeneralExamination_joinsTheReportedDangerSigns() { + PhyGeneralExamination examination = new PhyGeneralExamination(); + examination.setTypeOfDangerSigns(new ArrayList<>(Arrays.asList("fever", "bleeding"))); + PhyGeneralExamination stored = new PhyGeneralExamination(); + stored.setID(1L); + when(phyGeneralExaminationRepo.save(examination)).thenReturn(stored); + + assertEquals(1L, service.savePhyGeneralExamination(examination)); + assertEquals("fever,bleeding,", examination.getTypeOfDangerSign()); + } + + @Test + void savePhyGeneralExamination_leavesDangerSignsUnsetWhenNoneWereReported() { + PhyGeneralExamination examination = new PhyGeneralExamination(); + when(phyGeneralExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.savePhyGeneralExamination(examination)); + assertNull(examination.getTypeOfDangerSign()); + } + + @Test + void savePhyHeadToToeExamination_returnsTheStoredId() { + PhyHeadToToeExamination examination = new PhyHeadToToeExamination(); + PhyHeadToToeExamination stored = new PhyHeadToToeExamination(); + stored.setID(2L); + when(phyHeadToToeExaminationRepo.save(examination)).thenReturn(stored); + assertEquals(2L, service.savePhyHeadToToeExamination(examination)); + + when(phyHeadToToeExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.savePhyHeadToToeExamination(examination)); + } + + @Test + void saveSysGastrointestinalExamination_returnsTheStoredId() { + SysGastrointestinalExamination examination = new SysGastrointestinalExamination(); + SysGastrointestinalExamination stored = new SysGastrointestinalExamination(); + stored.setID(3L); + when(sysGastrointestinalExaminationRepo.save(examination)).thenReturn(stored); + assertEquals(3L, service.saveSysGastrointestinalExamination(examination)); + + when(sysGastrointestinalExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.saveSysGastrointestinalExamination(examination)); + } + + @Test + void saveSysCardiovascularExamination_returnsTheStoredId() { + SysCardiovascularExamination examination = new SysCardiovascularExamination(); + SysCardiovascularExamination stored = new SysCardiovascularExamination(); + stored.setID(4L); + when(sysCardiovascularExaminationRepo.save(examination)).thenReturn(stored); + assertEquals(4L, service.saveSysCardiovascularExamination(examination)); + + when(sysCardiovascularExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.saveSysCardiovascularExamination(examination)); + } + + @Test + void saveSysRespiratoryExamination_returnsTheStoredId() { + SysRespiratoryExamination examination = new SysRespiratoryExamination(); + SysRespiratoryExamination stored = new SysRespiratoryExamination(); + stored.setID(5L); + when(sysRespiratoryExaminationRepo.save(examination)).thenReturn(stored); + assertEquals(5L, service.saveSysRespiratoryExamination(examination)); + + when(sysRespiratoryExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.saveSysRespiratoryExamination(examination)); + } + + @Test + void saveSysCentralNervousExamination_returnsTheStoredId() { + SysCentralNervousExamination examination = new SysCentralNervousExamination(); + SysCentralNervousExamination stored = new SysCentralNervousExamination(); + stored.setID(6L); + when(sysCentralNervousExaminationRepo.save(examination)).thenReturn(stored); + assertEquals(6L, service.saveSysCentralNervousExamination(examination)); + + when(sysCentralNervousExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.saveSysCentralNervousExamination(examination)); + } + + @Test + void saveSysMusculoskeletalSystemExamination_returnsTheStoredId() { + SysMusculoskeletalSystemExamination examination = new SysMusculoskeletalSystemExamination(); + SysMusculoskeletalSystemExamination stored = new SysMusculoskeletalSystemExamination(); + stored.setID(7L); + when(sysMusculoskeletalSystemExaminationRepo.save(examination)).thenReturn(stored); + assertEquals(7L, service.saveSysMusculoskeletalSystemExamination(examination)); + + when(sysMusculoskeletalSystemExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.saveSysMusculoskeletalSystemExamination(examination)); + } + + @Test + void saveSysGenitourinarySystemExamination_returnsTheStoredId() { + SysGenitourinarySystemExamination examination = new SysGenitourinarySystemExamination(); + SysGenitourinarySystemExamination stored = new SysGenitourinarySystemExamination(); + stored.setID(8L); + when(sysGenitourinarySystemExaminationRepo.save(examination)).thenReturn(stored); + assertEquals(8L, service.saveSysGenitourinarySystemExamination(examination)); + + when(sysGenitourinarySystemExaminationRepo.save(examination)).thenReturn(null); + assertNull(service.saveSysGenitourinarySystemExamination(examination)); + } + } + + @Nested + @DisplayName("beneficiary history tables") + class HistoryTables { + + /** A stored row wide enough for any of the history mappers, with no values set. */ + private ArrayList oneEmptyRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[20]); + return rows; + } + + private void assertTableWithOneRow(String json) { + assertTrue(json.contains("\"columns\""), "the table must describe its columns: " + json); + assertTrue(json.contains("\"data\":[{"), "the stored row must be mapped into data: " + json); + } + + private void assertEmptyTable(String json) { + assertTrue(json.contains("\"columns\""), "the table must describe its columns even when empty: " + json); + assertTrue(json.contains("\"data\":[]"), "an empty result must map to no data: " + json); + } + + @Test + void fetchBenPastMedicalHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benMedHistoryRepo.getBenPastHistory(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPastMedicalHistory(1L)); + + when(benMedHistoryRepo.getBenPastHistory(2L)).thenReturn(new ArrayList<>()); + assertEmptyTable(service.fetchBenPastMedicalHistory(2L)); + + when(benMedHistoryRepo.getBenPastHistory(3L)).thenReturn(null); + assertEmptyTable(service.fetchBenPastMedicalHistory(3L)); + } + + @Test + void fetchBenPersonalTobaccoHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benPersonalHabitRepo.getBenPersonalTobaccoHabitDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPersonalTobaccoHistory(1L)); + + when(benPersonalHabitRepo.getBenPersonalTobaccoHabitDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPersonalTobaccoHistory(2L)); + } + + @Test + void fetchBenPersonalAlcoholHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benPersonalHabitRepo.getBenPersonalAlcoholHabitDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPersonalAlcoholHistory(1L)); + + when(benPersonalHabitRepo.getBenPersonalAlcoholHabitDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPersonalAlcoholHistory(2L)); + } + + @Test + void fetchBenPersonalAllergyHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benAllergyHistoryRepo.getBenPersonalAllergyDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPersonalAllergyHistory(1L)); + + when(benAllergyHistoryRepo.getBenPersonalAllergyDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPersonalAllergyHistory(2L)); + } + + @Test + void fetchBenPersonalMedicationHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benMedicationHistoryRepo.getBenMedicationHistoryDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPersonalMedicationHistory(1L)); + + when(benMedicationHistoryRepo.getBenMedicationHistoryDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPersonalMedicationHistory(2L)); + } + + @Test + void fetchBenPersonalFamilyHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benFamilyHistoryRepo.getBenFamilyHistoryDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPersonalFamilyHistory(1L)); + + when(benFamilyHistoryRepo.getBenFamilyHistoryDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPersonalFamilyHistory(2L)); + } + + @Test + void fetchBenPhysicalHistory_mapsStoredRowsAndTolratesNoHistory() { + when(physicalActivityTypeRepo.getBenPhysicalHistoryDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPhysicalHistory(1L)); + + when(physicalActivityTypeRepo.getBenPhysicalHistoryDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPhysicalHistory(2L)); + } + + @Test + void fetchBenMenstrualHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benMenstrualDetailsRepo.getBenMenstrualDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenMenstrualHistory(1L)); + + when(benMenstrualDetailsRepo.getBenMenstrualDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenMenstrualHistory(2L)); + } + + @Test + void fetchBenPastObstetricHistory_mapsStoredRowsAndTolratesNoHistory() { + when(femaleObstetricHistoryRepo.getBenFemaleObstetricHistoryDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPastObstetricHistory(1L)); + + when(femaleObstetricHistoryRepo.getBenFemaleObstetricHistoryDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPastObstetricHistory(2L)); + } + + @Test + void fetchBenComorbidityHistory_mapsStoredRowsAndTolratesNoHistory() { + when(bencomrbidityCondRepo.getBencomrbidityCondDetails(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenComorbidityHistory(1L)); + + when(bencomrbidityCondRepo.getBencomrbidityCondDetails(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenComorbidityHistory(2L)); + } + + @Test + void fetchBenImmunizationHistory_mapsStoredRowsAndTolratesNoHistory() { + when(childVaccineDetail1Repo.getBenChildVaccineDetails(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenImmunizationHistory(1L)); + + when(childVaccineDetail1Repo.getBenChildVaccineDetails(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenImmunizationHistory(2L)); + } + + @Test + void fetchBenOptionalVaccineHistory_mapsStoredRowsAndTolratesNoHistory() { + when(childOptionalVaccineDetailRepo.getBenOptionalVaccineDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenOptionalVaccineHistory(1L)); + + when(childOptionalVaccineDetailRepo.getBenOptionalVaccineDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenOptionalVaccineHistory(2L)); + } + + @Test + void fetchBenPerinatalHistory_mapsStoredRowsAndTolratesNoHistory() { + when(perinatalHistoryRepo.getBenPerinatalDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenPerinatalHistory(1L)); + + when(perinatalHistoryRepo.getBenPerinatalDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenPerinatalHistory(2L)); + } + + @Test + void fetchBenFeedingHistory_mapsStoredRowsAndTolratesNoHistory() { + when(childFeedingDetailsRepo.getBenFeedingHistoryDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenFeedingHistory(1L)); + + when(childFeedingDetailsRepo.getBenFeedingHistoryDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenFeedingHistory(2L)); + } + + @Test + void fetchBenDevelopmentHistory_mapsStoredRowsAndTolratesNoHistory() { + when(benChildDevelopmentHistoryRepo.getBenDevelopmentHistoryDetail(1L)).thenReturn(oneEmptyRow()); + assertTableWithOneRow(service.fetchBenDevelopmentHistory(1L)); + + when(benChildDevelopmentHistoryRepo.getBenDevelopmentHistoryDetail(2L)).thenReturn(null); + assertEmptyTable(service.fetchBenDevelopmentHistory(2L)); + } + } + + @Nested + @DisplayName("history updates") + class HistoryUpdates { + + /** The (id, processed) pairs the update methods read before deleting the old rows. */ + private ArrayList statusRows(Object id, String processed) { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { id, processed }); + return rows; + } + + @Test + void updateBenChiefComplaints_replacesTheExistingComplaints() { + BenChiefComplaint complaint = new BenChiefComplaint(); + complaint.setBeneficiaryRegID(1L); + complaint.setVisitCode(2L); + complaint.setBenChiefComplaintID(3L); + List complaints = Collections.singletonList(complaint); + when(benChiefComplaintRepo.saveAll(complaints)).thenReturn(complaints); + + assertEquals(1, service.updateBenChiefComplaints(complaints)); + verify(benChiefComplaintRepo).deleteExistingBenChiefComplaints(1L, 2L); + verify(benChiefComplaintRepo).updateVanSerialNo(3L); + } + + @Test + void updateBenChiefComplaints_doesNothingWhenNoComplaintWasSent() { + assertEquals(0, service.updateBenChiefComplaints(null)); + assertEquals(0, service.updateBenChiefComplaints(new ArrayList<>())); + } + + @Test + void updateBenChiefComplaints_reportsFailureWhenNothingWasStored() { + BenChiefComplaint complaint = new BenChiefComplaint(); + List complaints = Collections.singletonList(complaint); + when(benChiefComplaintRepo.saveAll(complaints)).thenReturn(new ArrayList<>()); + assertEquals(0, service.updateBenChiefComplaints(complaints)); + } + + @Test + void updateBenPastHistoryDetails_marksAlreadySyncedRowsAsUpdatedBeforeReplacingThem() throws Exception { + BenMedHistory history = mock(BenMedHistory.class); + when(history.getBeneficiaryRegID()).thenReturn(1L); + when(history.getVisitCode()).thenReturn(2L); + when(benMedHistoryRepo.getBenMedHistoryStatus(1L, 2L)).thenReturn(statusRows(5L, "P")); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenMedHistory())); + when(history.getBenPastHistory()).thenReturn(entries); + when(benMedHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updateBenPastHistoryDetails(history)); + verify(benMedHistoryRepo).deleteExistingBenMedHistory(5L, "U"); + } + + @Test + void updateBenPastHistoryDetails_keepsNeverSyncedRowsMarkedAsNew() throws Exception { + BenMedHistory history = mock(BenMedHistory.class); + when(benMedHistoryRepo.getBenMedHistoryStatus(any(), any())).thenReturn(statusRows(5L, "N")); + when(history.getBenPastHistory()).thenReturn(new ArrayList<>()); + + assertEquals(1, service.updateBenPastHistoryDetails(history)); + verify(benMedHistoryRepo).deleteExistingBenMedHistory(5L, "N"); + } + + @Test + void updateBenPastHistoryDetails_doesNothingWithoutAHistory() throws Exception { + assertEquals(0, service.updateBenPastHistoryDetails(null)); + } + + @Test + void updateBenComorbidConditions_replacesTheStoredConditions() { + WrapperComorbidCondDetails wrapper = mock(WrapperComorbidCondDetails.class); + when(bencomrbidityCondRepo.getBenComrbidityCondHistoryStatus(any(), any())) + .thenReturn(statusRows(6L, "P")); + ArrayList entries = new ArrayList<>( + Collections.singletonList(new BencomrbidityCondDetails())); + when(wrapper.getComrbidityConds()).thenReturn(entries); + when(bencomrbidityCondRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updateBenComorbidConditions(wrapper)); + verify(bencomrbidityCondRepo).deleteExistingBenComrbidityCondDetails(6L, "U"); + } + + @Test + void updateBenComorbidConditions_succeedsWhenEveryConditionWasCleared() { + WrapperComorbidCondDetails wrapper = mock(WrapperComorbidCondDetails.class); + when(bencomrbidityCondRepo.getBenComrbidityCondHistoryStatus(any(), any())) + .thenReturn(statusRows(6L, null)); + when(wrapper.getComrbidityConds()).thenReturn(new ArrayList<>()); + + assertEquals(1, service.updateBenComorbidConditions(wrapper)); + verify(bencomrbidityCondRepo).deleteExistingBenComrbidityCondDetails(6L, "N"); + } + + @Test + void updateBenComorbidConditions_doesNothingWithoutConditions() { + assertEquals(0, service.updateBenComorbidConditions(null)); + } + + @Test + void updateBenMedicationHistory_replacesTheStoredMedication() { + WrapperMedicationHistory wrapper = mock(WrapperMedicationHistory.class); + when(benMedicationHistoryRepo.getBenMedicationHistoryStatus(any(), any())) + .thenReturn(statusRows(7L, "P")); + ArrayList entries = new ArrayList<>( + Collections.singletonList(new BenMedicationHistory())); + when(wrapper.getBenMedicationHistoryDetails()).thenReturn(entries); + when(benMedicationHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updateBenMedicationHistory(wrapper)); + verify(benMedicationHistoryRepo).deleteExistingBenMedicationHistory(7L, "U"); + } + + @Test + void updateBenMedicationHistory_succeedsWhenEveryMedicationWasCleared() { + WrapperMedicationHistory wrapper = mock(WrapperMedicationHistory.class); + when(benMedicationHistoryRepo.getBenMedicationHistoryStatus(any(), any())) + .thenReturn(statusRows(7L, "N")); + when(wrapper.getBenMedicationHistoryDetails()).thenReturn(new ArrayList<>()); + assertEquals(1, service.updateBenMedicationHistory(wrapper)); + } + + @Test + void updateBenMedicationHistory_doesNothingWithoutMedication() { + assertEquals(0, service.updateBenMedicationHistory(null)); + } + + @Test + void updateBenPersonalHistory_replacesTheStoredHabits() { + BenPersonalHabit habit = mock(BenPersonalHabit.class); + when(benPersonalHabitRepo.getBenPersonalHistoryStatus(any(), any())) + .thenReturn(statusRows(Integer.valueOf(8), "P")); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenPersonalHabit())); + when(habit.getPersonalHistory()).thenReturn(entries); + when(benPersonalHabitRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updateBenPersonalHistory(habit)); + verify(benPersonalHabitRepo).deleteExistingBenPersonalHistory(8, "U"); + } + + @Test + void updateBenPersonalHistory_succeedsWhenEveryHabitWasCleared() { + BenPersonalHabit habit = mock(BenPersonalHabit.class); + when(benPersonalHabitRepo.getBenPersonalHistoryStatus(any(), any())) + .thenReturn(statusRows(Integer.valueOf(8), "N")); + when(habit.getPersonalHistory()).thenReturn(new ArrayList<>()); + assertEquals(1, service.updateBenPersonalHistory(habit)); + } + + @Test + void updateBenPersonalHistory_doesNothingWithoutHabits() { + assertEquals(0, service.updateBenPersonalHistory(null)); + } + + @Test + void updateBenAllergicHistory_replacesTheStoredAllergies() { + BenAllergyHistory allergy = mock(BenAllergyHistory.class); + when(benAllergyHistoryRepo.getBenAllergyHistoryStatus(any(), any())).thenReturn(statusRows(9L, "P")); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenAllergyHistory())); + when(allergy.getBenAllergicHistory()).thenReturn(entries); + when(benAllergyHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updateBenAllergicHistory(allergy)); + verify(benAllergyHistoryRepo).deleteExistingBenAllergyHistory(9L, "U"); + } + + @Test + void updateBenAllergicHistory_succeedsWhenEveryAllergyWasCleared() { + BenAllergyHistory allergy = mock(BenAllergyHistory.class); + when(benAllergyHistoryRepo.getBenAllergyHistoryStatus(any(), any())).thenReturn(statusRows(9L, "N")); + when(allergy.getBenAllergicHistory()).thenReturn(new ArrayList<>()); + assertEquals(1, service.updateBenAllergicHistory(allergy)); + } + + @Test + void updateBenAllergicHistory_doesNothingWithoutAllergies() { + assertEquals(0, service.updateBenAllergicHistory(null)); + } + + @Test + void updateBenFamilyHistory_replacesTheStoredFamilyDiseases() { + BenFamilyHistory family = mock(BenFamilyHistory.class); + when(benFamilyHistoryRepo.getBenFamilyHistoryStatus(any(), any())).thenReturn(statusRows(10L, "P")); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenFamilyHistory())); + when(family.getBenFamilyHistory()).thenReturn(entries); + when(benFamilyHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updateBenFamilyHistory(family)); + verify(benFamilyHistoryRepo).deleteExistingBenFamilyHistory(10L, "U"); + } + + @Test + void updateBenFamilyHistory_succeedsWhenEveryDiseaseWasCleared() { + BenFamilyHistory family = mock(BenFamilyHistory.class); + when(benFamilyHistoryRepo.getBenFamilyHistoryStatus(any(), any())).thenReturn(statusRows(10L, "N")); + when(family.getBenFamilyHistory()).thenReturn(new ArrayList<>()); + assertEquals(1, service.updateBenFamilyHistory(family)); + } + + @Test + void updateBenFamilyHistory_doesNothingWithoutFamilyHistory() { + assertEquals(0, service.updateBenFamilyHistory(null)); + } + + @Test + void updateMenstrualHistory_updatesTheExistingRowWhenOneIsAlreadyStored() { + BenMenstrualDetails details = new BenMenstrualDetails(); + details.setBeneficiaryRegID(1L); + details.setVisitCode(2L); + ArrayList> problems = new ArrayList<>(); + Map problem = new HashMap<>(); + problem.put("menstrualProblemID", 3); + problem.put("problemName", "cramps"); + problems.add(problem); + details.setMenstrualProblemList(problems); + + when(benMenstrualDetailsRepo.getBenMenstrualDetailStatus(1L, 2L)).thenReturn("P"); + when(benMenstrualDetailsRepo.updateMenstrualDetails(any(), any(), any(), any(), any(), any(), any(), + anyString(), anyString(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), anyLong(), anyLong())) + .thenReturn(1); + + assertEquals(1, service.updateMenstrualHistory(details)); + assertEquals("3", details.getMenstrualProblemID()); + assertEquals("cramps", details.getProblemName()); + } + + @Test + void updateMenstrualHistory_insertsAFreshRowWhenNoneIsStoredYet() { + BenMenstrualDetails details = new BenMenstrualDetails(); + details.setModifiedBy("nurse"); + when(benMenstrualDetailsRepo.getBenMenstrualDetailStatus(any(), any())).thenReturn(null); + + BenMenstrualDetails stored = new BenMenstrualDetails(); + stored.setBenMenstrualID(4); + when(benMenstrualDetailsRepo.save(details)).thenReturn(stored); + + assertEquals(1, service.updateMenstrualHistory(details)); + assertEquals("nurse", details.getCreatedBy()); + } + + @Test + void updateMenstrualHistory_reportsFailureWhenTheFreshRowWasNotStored() { + BenMenstrualDetails details = new BenMenstrualDetails(); + when(benMenstrualDetailsRepo.getBenMenstrualDetailStatus(any(), any())).thenReturn(null); + BenMenstrualDetails stored = new BenMenstrualDetails(); + stored.setBenMenstrualID(0); + when(benMenstrualDetailsRepo.save(details)).thenReturn(stored); + + assertEquals(0, service.updateMenstrualHistory(details)); + } + + @Test + void updateMenstrualHistory_doesNothingWithoutMenstrualDetails() { + assertEquals(0, service.updateMenstrualHistory(null)); + } + + @Test + void updatePastObstetricHistory_replacesTheStoredPregnancies() { + WrapperFemaleObstetricHistory wrapper = mock(WrapperFemaleObstetricHistory.class); + when(femaleObstetricHistoryRepo.getBenObstetricHistoryStatus(any(), any())) + .thenReturn(statusRows(11L, "P")); + ArrayList entries = new ArrayList<>( + Collections.singletonList(new FemaleObstetricHistory())); + when(wrapper.getFemaleObstetricHistoryDetails()).thenReturn(entries); + when(femaleObstetricHistoryRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updatePastObstetricHistory(wrapper)); + verify(femaleObstetricHistoryRepo).deleteExistingObstetricHistory(11L, "U"); + } + + @Test + void updatePastObstetricHistory_doesNothingWithoutObstetricHistory() { + assertEquals(0, service.updatePastObstetricHistory(null)); + } + + @Test + void updateChildOptionalVaccineDetail_replacesTheStoredOptionalVaccines() { + WrapperChildOptionalVaccineDetail wrapper = mock(WrapperChildOptionalVaccineDetail.class); + when(childOptionalVaccineDetailRepo.getBenChildOptionalVaccineHistoryStatus(any(), any())) + .thenReturn(statusRows(12L, "P")); + ArrayList entries = new ArrayList<>( + Collections.singletonList(new ChildOptionalVaccineDetail())); + when(wrapper.getChildOptionalVaccineDetails()).thenReturn(entries); + when(childOptionalVaccineDetailRepo.saveAll(entries)).thenReturn(entries); + + assertEquals(1, service.updateChildOptionalVaccineDetail(wrapper)); + verify(childOptionalVaccineDetailRepo).deleteExistingChildOptionalVaccineDetail(12L, "U"); + } + + @Test + void updateChildOptionalVaccineDetail_succeedsWhenEveryOptionalVaccineWasCleared() { + WrapperChildOptionalVaccineDetail wrapper = mock(WrapperChildOptionalVaccineDetail.class); + when(childOptionalVaccineDetailRepo.getBenChildOptionalVaccineHistoryStatus(any(), any())) + .thenReturn(statusRows(12L, "N")); + when(wrapper.getChildOptionalVaccineDetails()).thenReturn(new ArrayList<>()); + assertEquals(1, service.updateChildOptionalVaccineDetail(wrapper)); + } + + @Test + void updateChildOptionalVaccineDetail_doesNothingWithoutOptionalVaccines() { + assertEquals(0, service.updateChildOptionalVaccineDetail(null)); + } + + @Test + void updateChildImmunizationDetail_marksAPreviouslySyncedVaccineAsUpdated() { + ChildVaccineDetail1 vaccine = new ChildVaccineDetail1(); + vaccine.setBeneficiaryRegID(1L); + vaccine.setVisitCode(2L); + vaccine.setDefaultReceivingAge("6 weeks"); + vaccine.setVaccineName("BCG"); + + WrapperImmunizationHistory wrapper = mock(WrapperImmunizationHistory.class); + when(wrapper.getBenChildVaccineDetails()) + .thenReturn(new ArrayList<>(Collections.singletonList(vaccine))); + + ArrayList statuses = new ArrayList<>(); + statuses.add(new Object[] { "6 weeks", "BCG", "P" }); + when(childVaccineDetail1Repo.getBenChildVaccineDetailStatus(1L, 2L)).thenReturn(statuses); + when(childVaccineDetail1Repo.updateChildANCImmunization(any(), any(), org.mockito.ArgumentMatchers.eq("U"), + anyLong(), anyLong(), anyString(), anyString(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateChildImmunizationDetail(wrapper)); + } + + @Test + void updateChildImmunizationDetail_treatsAVaccineWithNoStoredStatusAsNew() { + ChildVaccineDetail1 vaccine = new ChildVaccineDetail1(); + vaccine.setBeneficiaryRegID(1L); + vaccine.setVisitCode(2L); + vaccine.setDefaultReceivingAge("6 weeks"); + vaccine.setVaccineName("BCG"); + + WrapperImmunizationHistory wrapper = mock(WrapperImmunizationHistory.class); + when(wrapper.getBenChildVaccineDetails()) + .thenReturn(new ArrayList<>(Collections.singletonList(vaccine))); + when(childVaccineDetail1Repo.getBenChildVaccineDetailStatus(1L, 2L)).thenReturn(new ArrayList<>()); + when(childVaccineDetail1Repo.updateChildANCImmunization(any(), any(), org.mockito.ArgumentMatchers.eq("N"), + anyLong(), anyLong(), anyString(), anyString(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateChildImmunizationDetail(wrapper)); + } + } + + @Nested + @DisplayName("examination updates") + class ExaminationUpdates { + + @Test + void updatePhyGeneralExamination_joinsDangerSignsAndMarksAnAlreadySyncedRow() { + PhyGeneralExamination examination = new PhyGeneralExamination(); + examination.setBeneficiaryRegID(1L); + examination.setVisitCode(2L); + examination.setTypeOfDangerSigns(new ArrayList<>(Arrays.asList("fever"))); + when(phyGeneralExaminationRepo.getBenGeneralExaminationStatus(1L, 2L)).thenReturn("P"); + when(phyGeneralExaminationRepo.updatePhyGeneralExamination(any(), any(), any(), any(), any(), any(), any(), + anyString(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), + any(), org.mockito.ArgumentMatchers.eq("U"), anyLong(), anyLong())).thenReturn(1); + + assertEquals(1, service.updatePhyGeneralExamination(examination)); + assertEquals("fever,", examination.getTypeOfDangerSign()); + } + + @Test + void updatePhyGeneralExamination_keepsANeverSyncedRowMarkedAsNew() { + PhyGeneralExamination examination = new PhyGeneralExamination(); + when(phyGeneralExaminationRepo.getBenGeneralExaminationStatus(any(), any())).thenReturn("N"); + when(phyGeneralExaminationRepo.updatePhyGeneralExamination(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), + org.mockito.ArgumentMatchers.eq("N"), any(), any())).thenReturn(1); + + assertEquals(1, service.updatePhyGeneralExamination(examination)); + } + + @Test + void updatePhyGeneralExamination_doesNothingWithoutAnExamination() { + assertEquals(0, service.updatePhyGeneralExamination(null)); + } + + @Test + void updatePhyHeadToToeExamination_marksAnAlreadySyncedRowAsUpdated() { + PhyHeadToToeExamination examination = new PhyHeadToToeExamination(); + when(phyHeadToToeExaminationRepo.getBenHeadToToeExaminationStatus(any(), any())).thenReturn("P"); + when(phyHeadToToeExaminationRepo.updatePhyHeadToToeExamination(any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), + org.mockito.ArgumentMatchers.eq("U"), any(), any())).thenReturn(1); + + assertEquals(1, service.updatePhyHeadToToeExamination(examination)); + assertEquals(0, service.updatePhyHeadToToeExamination(null)); + } + + @Test + void updateSysCardiovascularExamination_marksAnAlreadySyncedRowAsUpdated() { + SysCardiovascularExamination examination = new SysCardiovascularExamination(); + when(sysCardiovascularExaminationRepo.getBenCardiovascularExaminationStatus(any(), any())) + .thenReturn("P"); + when(sysCardiovascularExaminationRepo.updateSysCardiovascularExamination(any(), any(), any(), any(), any(), + any(), any(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), any(), any())).thenReturn(1); + + assertEquals(1, service.updateSysCardiovascularExamination(examination)); + assertEquals(0, service.updateSysCardiovascularExamination(null)); + } + + @Test + void updateSysRespiratoryExamination_marksAnAlreadySyncedRowAsUpdated() { + SysRespiratoryExamination examination = new SysRespiratoryExamination(); + when(sysRespiratoryExaminationRepo.getBenRespiratoryExaminationStatus(any(), any())).thenReturn("P"); + when(sysRespiratoryExaminationRepo.updateSysRespiratoryExamination(any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), any(), + any())).thenReturn(1); + + assertEquals(1, service.updateSysRespiratoryExamination(examination)); + assertEquals(0, service.updateSysRespiratoryExamination(null)); + } + + @Test + void updateSysCentralNervousExamination_marksAnAlreadySyncedRowAsUpdated() { + SysCentralNervousExamination examination = new SysCentralNervousExamination(); + when(sysCentralNervousExaminationRepo.getBenCentralNervousExaminationStatus(any(), any())) + .thenReturn("P"); + when(sysCentralNervousExaminationRepo.updateSysCentralNervousExamination(any(), any(), any(), any(), any(), + any(), any(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), any(), any())).thenReturn(1); + + assertEquals(1, service.updateSysCentralNervousExamination(examination)); + assertEquals(0, service.updateSysCentralNervousExamination(null)); + } + + @Test + void updateSysMusculoskeletalSystemExamination_marksAnAlreadySyncedRowAsUpdated() { + SysMusculoskeletalSystemExamination examination = new SysMusculoskeletalSystemExamination(); + when(sysMusculoskeletalSystemExaminationRepo.getBenMusculoskeletalSystemExaminationStatus(any(), any())) + .thenReturn("P"); + when(sysMusculoskeletalSystemExaminationRepo.updateSysMusculoskeletalSystemExamination(any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), any(), + any())).thenReturn(1); + + assertEquals(1, service.updateSysMusculoskeletalSystemExamination(examination)); + assertEquals(0, service.updateSysMusculoskeletalSystemExamination(null)); + } + + @Test + void updateSysGenitourinarySystemExamination_marksAnAlreadySyncedRowAsUpdated() { + SysGenitourinarySystemExamination examination = new SysGenitourinarySystemExamination(); + when(sysGenitourinarySystemExaminationRepo.getBenGenitourinarySystemExaminationStatus(any(), any())) + .thenReturn("P"); + when(sysGenitourinarySystemExaminationRepo.updateSysGenitourinarySystemExamination(any(), any(), any(), + any(), org.mockito.ArgumentMatchers.eq("U"), any(), any())).thenReturn(1); + + assertEquals(1, service.updateSysGenitourinarySystemExamination(examination)); + assertEquals(0, service.updateSysGenitourinarySystemExamination(null)); + } + + @Test + void updateSysGastrointestinalExamination_marksAnAlreadySyncedRowAsUpdated() { + SysGastrointestinalExamination examination = new SysGastrointestinalExamination(); + when(sysGastrointestinalExaminationRepo.getBenGastrointestinalExaminationStatus(any(), any())) + .thenReturn("P"); + when(sysGastrointestinalExaminationRepo.updateSysGastrointestinalExamination(any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), any(), + any())).thenReturn(1); + + assertEquals(1, service.updateSysGastrointestinalExamination(examination)); + assertEquals(0, service.updateSysGastrointestinalExamination(null)); + } + } + + @Nested + @DisplayName("examination and history reads") + class Reads { + + private ArrayList oneEmptyRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[20]); + return rows; + } + + @Test + void getBenChiefComplaints_serialisesTheStoredComplaints() { + when(benChiefComplaintRepo.getBenChiefComplaints(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getBenChiefComplaints(1L, 2L)); + } + + @Test + void getPastHistoryData_mapsTheStoredRows() { + when(benMedHistoryRepo.getBenPastHistory(1L, 2L)).thenReturn(oneEmptyRow()); + assertNotNull(service.getPastHistoryData(1L, 2L)); + + when(benMedHistoryRepo.getBenPastHistory(3L, 4L)).thenReturn(new ArrayList<>()); + assertNull(service.getPastHistoryData(3L, 4L)); + } + + @Test + void getComorbidityConditionsHistory_mapsTheStoredRows() { + when(bencomrbidityCondRepo.getBencomrbidityCondDetails(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getComorbidityConditionsHistory(1L, 2L)); + } + + @Test + void getMedicationHistory_mapsTheStoredRows() { + when(benMedicationHistoryRepo.getBenMedicationHistoryDetail(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getMedicationHistory(1L, 2L)); + } + + @Test + void getPersonalHistory_returnsAnEmptyHabitWhenNothingWasRecorded() { + when(benPersonalHabitRepo.getBenPersonalHabitDetail(1L, 2L)).thenReturn(new ArrayList<>()); + when(benAllergyHistoryRepo.getBenPersonalAllergyDetail(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getPersonalHistory(1L, 2L)); + } + + @Test + void getPersonalHistory_copiesTheAllergyStatusOntoTheHabit() { + when(benPersonalHabitRepo.getBenPersonalHabitDetail(1L, 2L)).thenReturn(new ArrayList<>()); + when(benAllergyHistoryRepo.getBenPersonalAllergyDetail(1L, 2L)).thenReturn(oneEmptyRow()); + + BenPersonalHabit habit = service.getPersonalHistory(1L, 2L); + assertNotNull(habit.getAllergicList()); + } + + @Test + void getFamilyHistory_mapsTheStoredRows() { + when(benFamilyHistoryRepo.getBenFamilyHistoryDetail(1L, 2L)).thenReturn(oneEmptyRow()); + assertNotNull(service.getFamilyHistory(1L, 2L)); + } + + @Test + void getFamilyHistoryDetail_mapsTheScreeningVariantOfTheRows() { + when(benFamilyHistoryRepo.getBenFamilyHisDetail(1L, 2L)).thenReturn(oneEmptyRow()); + assertNotNull(service.getFamilyHistoryDetail(1L, 2L)); + } + + @Test + void getPhysicalActivityType_delegatesToRepo() { + PhysicalActivityType stored = new PhysicalActivityType(); + when(physicalActivityTypeRepo.getBenPhysicalHistoryDetails(1L, 2L)).thenReturn(stored); + assertEquals(stored, service.getPhysicalActivityType(1L, 2L)); + } + + @Test + void getBeneficiaryIdrsDetails_mapsTheStoredRows() { + when(iDRSDataRepo.getBenIdrsDetail(1L, 2L)).thenReturn(new ArrayList<>()); + service.getBeneficiaryIdrsDetails(1L, 2L); + } + + @Test + void getMenstrualHistory_splitsTheStoredProblemsBackIntoAList() { + BenMenstrualDetails stored = new BenMenstrualDetails(); + stored.setMenstrualProblemID("1,2"); + stored.setProblemName("cramps,spotting"); + try (org.mockito.MockedStatic statics = org.mockito.Mockito + .mockStatic(BenMenstrualDetails.class)) { + statics.when(() -> BenMenstrualDetails.getBenMenstrualDetails(any())).thenReturn(stored); + BenMenstrualDetails result = service.getMenstrualHistory(1L, 2L); + assertEquals(2, result.getMenstrualProblemList().size()); + assertEquals("cramps", result.getMenstrualProblemList().get(0).get("problemName")); + } + } + + @Test + void getMenstrualHistory_leavesTheProblemListUnsetWhenNoProblemWasRecorded() { + BenMenstrualDetails stored = new BenMenstrualDetails(); + try (org.mockito.MockedStatic statics = org.mockito.Mockito + .mockStatic(BenMenstrualDetails.class)) { + statics.when(() -> BenMenstrualDetails.getBenMenstrualDetails(any())).thenReturn(stored); + assertNull(service.getMenstrualHistory(1L, 2L).getMenstrualProblemList()); + } + } + + @Test + void getChildOptionalVaccineHistory_mapsTheStoredRows() { + when(childOptionalVaccineDetailRepo.getBenOptionalVaccineDetail(1L, 2L)).thenReturn(new ArrayList<>()); + service.getChildOptionalVaccineHistory(1L, 2L); + } + + @Test + void getImmunizationHistory_mapsTheStoredRows() { + when(childVaccineDetail1Repo.getBenChildVaccineDetails(1L, 2L)).thenReturn(new ArrayList<>()); + service.getImmunizationHistory(1L, 2L); + } + + @Test + void getGeneralExaminationData_splitsTheStoredDangerSignsBackIntoAList() { + PhyGeneralExamination stored = new PhyGeneralExamination(); + stored.setTypeOfDangerSign("fever,bleeding"); + when(phyGeneralExaminationRepo.getPhyGeneralExaminationData(1L, 2L)).thenReturn(stored); + + assertEquals(Arrays.asList("fever", "bleeding"), service.getGeneralExaminationData(1L, 2L) + .getTypeOfDangerSigns()); + } + + @Test + void getGeneralExaminationData_returnsAnEmptyDangerSignListWhenNoneWereRecorded() { + PhyGeneralExamination stored = new PhyGeneralExamination(); + when(phyGeneralExaminationRepo.getPhyGeneralExaminationData(1L, 2L)).thenReturn(stored); + assertTrue(service.getGeneralExaminationData(1L, 2L).getTypeOfDangerSigns().isEmpty()); + } + + @Test + void getGeneralExaminationData_returnsNullWhenNoExaminationWasRecorded() { + when(phyGeneralExaminationRepo.getPhyGeneralExaminationData(1L, 2L)).thenReturn(null); + assertNull(service.getGeneralExaminationData(1L, 2L)); + } + + @Test + void theRemainingSystemExaminationReadsDelegateToTheirRepositories() { + PhyHeadToToeExamination headToToe = new PhyHeadToToeExamination(); + when(phyHeadToToeExaminationRepo.getPhyHeadToToeExaminationData(1L, 2L)).thenReturn(headToToe); + assertEquals(headToToe, service.getHeadToToeExaminationData(1L, 2L)); + + SysGastrointestinalExamination gastro = new SysGastrointestinalExamination(); + when(sysGastrointestinalExaminationRepo.getSSysGastrointestinalExamination(1L, 2L)).thenReturn(gastro); + assertEquals(gastro, service.getSysGastrointestinalExamination(1L, 2L)); + + SysCardiovascularExamination cardio = new SysCardiovascularExamination(); + when(sysCardiovascularExaminationRepo.getSysCardiovascularExaminationData(1L, 2L)).thenReturn(cardio); + assertEquals(cardio, service.getCardiovascularExamination(1L, 2L)); + + SysRespiratoryExamination respiratory = new SysRespiratoryExamination(); + when(sysRespiratoryExaminationRepo.getSysRespiratoryExaminationData(1L, 2L)).thenReturn(respiratory); + assertEquals(respiratory, service.getRespiratoryExamination(1L, 2L)); + + SysCentralNervousExamination nervous = new SysCentralNervousExamination(); + when(sysCentralNervousExaminationRepo.getSysCentralNervousExaminationData(1L, 2L)).thenReturn(nervous); + assertEquals(nervous, service.getSysCentralNervousExamination(1L, 2L)); + + SysMusculoskeletalSystemExamination musculoskeletal = new SysMusculoskeletalSystemExamination(); + when(sysMusculoskeletalSystemExaminationRepo.getSysMusculoskeletalSystemExamination(1L, 2L)) + .thenReturn(musculoskeletal); + assertEquals(musculoskeletal, service.getMusculoskeletalExamination(1L, 2L)); + + SysGenitourinarySystemExamination genitourinary = new SysGenitourinarySystemExamination(); + when(sysGenitourinarySystemExaminationRepo.getSysGenitourinarySystemExaminationData(1L, 2L)) + .thenReturn(genitourinary); + assertEquals(genitourinary, service.getGenitourinaryExamination(1L, 2L)); + } + } + + @Nested + @DisplayName("prescriptions and prescribed drugs") + class Prescriptions { + + private com.iemr.mmu.data.snomedct.SCTDescription diagnosis(String term, String conceptId) { + com.iemr.mmu.data.snomedct.SCTDescription description = new com.iemr.mmu.data.snomedct.SCTDescription(); + description.setTerm(term); + description.setConceptID(conceptId); + return description; + } + + private com.iemr.mmu.data.quickConsultation.PrescriptionDetail storedPrescription(Long id) { + com.iemr.mmu.data.quickConsultation.PrescriptionDetail stored = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + stored.setPrescriptionID(id); + return stored; + } + + @Test + void saveBenPrescription_joinsEveryProvisionalDiagnosisTermAndConceptId() { + com.iemr.mmu.data.quickConsultation.PrescriptionDetail prescription = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + ArrayList diagnoses = new ArrayList<>(); + diagnoses.add(diagnosis("Fever", "111")); + diagnoses.add(diagnosis("Cough", null)); + diagnoses.add(diagnosis(null, "333")); + prescription.setProvisionalDiagnosisList(diagnoses); + when(prescriptionDetailRepo.save(prescription)).thenReturn(storedPrescription(21L)); + + assertEquals(21L, service.saveBenPrescription(prescription)); + assertEquals("Fever || Cough", prescription.getDiagnosisProvided()); + assertEquals("111 || N/A", prescription.getDiagnosisProvided_SCTCode()); + verify(prescriptionDetailRepo).updateVanSerialNo(21L); + } + + @Test + void saveBenPrescription_leavesTheDiagnosisUnsetWhenNoneWasProvided() { + com.iemr.mmu.data.quickConsultation.PrescriptionDetail prescription = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + when(prescriptionDetailRepo.save(prescription)).thenReturn(storedPrescription(22L)); + + assertEquals(22L, service.saveBenPrescription(prescription)); + assertNull(prescription.getDiagnosisProvided()); + } + + @Test + void saveBenPrescription_returnsNullWhenTheRowWasNotPersisted() { + com.iemr.mmu.data.quickConsultation.PrescriptionDetail prescription = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + when(prescriptionDetailRepo.save(prescription)).thenReturn(storedPrescription(0L)); + assertNull(service.saveBenPrescription(prescription)); + } + + @Test + void savePrescriptionDetailsAndGetPrescriptionID_buildsThePrescriptionFromTheVisitContext() { + when(prescriptionDetailRepo.save(any())).thenReturn(storedPrescription(23L)); + ArrayList diagnoses = new ArrayList<>(); + diagnoses.add(diagnosis("Fever", "111")); + + assertEquals(23L, service.savePrescriptionDetailsAndGetPrescriptionID(1L, 2L, 3, "doctor", "x-ray", 4L, 5, + 6, diagnoses)); + assertEquals(23L, service.savePrescriptionDetailsAndGetPrescriptionID(1L, 2L, 3, "doctor", "x-ray", 4L, 5, + 6, null)); + } + + @Test + void savePrescriptionDetailsCovid19_recordsTheDoctorDiagnosisWhenOneWasGiven() { + when(prescriptionDetailRepo.save(any())).thenReturn(storedPrescription(24L)); + + assertEquals(24L, + service.savePrescriptionDetailsCovid19(1L, 2L, 3, "doctor", "x-ray", 4L, 5, 6, "Covid")); + assertEquals(24L, service.savePrescriptionDetailsCovid19(1L, 2L, 3, "doctor", "x-ray", 4L, 5, 6, null)); + } + + @Test + void saveBeneficiaryPrescription_readsThePrescriptionOutOfTheCaseSheet() throws Exception { + when(prescriptionDetailRepo.save(any())).thenReturn(storedPrescription(25L)); + com.google.gson.JsonObject caseSheet = new com.google.gson.JsonObject(); + caseSheet.addProperty("beneficiaryRegID", 1); + assertEquals(25L, service.saveBeneficiaryPrescription(caseSheet)); + } + + @Test + void updatePrescription_updatesTheStoredRowAndJoinsTheDiagnosisTerms() { + com.iemr.mmu.data.quickConsultation.PrescriptionDetail prescription = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + prescription.setBeneficiaryRegID(1L); + prescription.setVisitCode(2L); + prescription.setPrescriptionID(3L); + ArrayList diagnoses = new ArrayList<>(); + diagnoses.add(diagnosis("Fever", null)); + diagnoses.add(diagnosis("Cough", "222")); + prescription.setProvisionalDiagnosisList(diagnoses); + + when(prescriptionDetailRepo.getGeneralOPDDiagnosisStatus(1L, 2L, 3L)).thenReturn("P"); + when(prescriptionDetailRepo.updatePrescription(anyString(), any(), any(), + org.mockito.ArgumentMatchers.eq("U"), anyLong(), anyLong(), anyLong(), any(), anyString(), any())) + .thenReturn(1); + + assertEquals(1, service.updatePrescription(prescription)); + assertEquals("Fever || Cough", prescription.getDiagnosisProvided()); + assertEquals("N/A || 222", prescription.getDiagnosisProvided_SCTCode()); + } + + @Test + void updatePrescription_insertsAFreshRowWhenNoneIsStoredYet() { + com.iemr.mmu.data.quickConsultation.PrescriptionDetail prescription = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + when(prescriptionDetailRepo.getGeneralOPDDiagnosisStatus(any(), any(), any())).thenReturn(null); + when(prescriptionDetailRepo.save(prescription)).thenReturn(storedPrescription(26L)); + + assertEquals(1, service.updatePrescription(prescription)); + } + + @Test + void updatePrescription_reportsFailureWhenTheFreshRowWasNotStored() { + com.iemr.mmu.data.quickConsultation.PrescriptionDetail prescription = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + when(prescriptionDetailRepo.getGeneralOPDDiagnosisStatus(any(), any(), any())).thenReturn(null); + when(prescriptionDetailRepo.save(prescription)).thenReturn(storedPrescription(0L)); + + assertEquals(0, service.updatePrescription(prescription)); + } + + @Test + void saveBeneficiaryLabTestOrderDetails_succeedsWhenTheCaseSheetOrdersNoTest() { + assertEquals(1L, service.saveBeneficiaryLabTestOrderDetails(new com.google.gson.JsonObject(), 1L)); + } + + @Test + void saveBeneficiaryLabTestOrderDetails_storesEveryOrderedTest() { + com.google.gson.JsonObject caseSheet = new com.google.gson.JsonObject(); + com.google.gson.JsonArray orders = new com.google.gson.JsonArray(); + com.google.gson.JsonObject order = new com.google.gson.JsonObject(); + order.addProperty("testID", 1); + orders.add(order); + caseSheet.add("labTestOrders", orders); + + when(labTestOrderDetailRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + assertEquals(1L, service.saveBeneficiaryLabTestOrderDetails(caseSheet, 1L)); + } + + @Test + void saveBenPrescribedDrugsList_calculatesTheQuantityForTabletsAndCapsules() { + com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail tablet = drug("Tablet", "One Tab", + "Twice Daily(BD)", "5", "Day(s)"); + com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail syrup = drug("Syrup", "5 ml", "Once Daily(OD)", + "5", "Day(s)"); + tablet.setId(1L); + List drugs = Arrays.asList(tablet, syrup); + when(prescribedDrugDetailRepo.saveAll(drugs)).thenReturn(drugs); + + Map result = service.saveBenPrescribedDrugsList(drugs); + + assertEquals(2, result.get("count")); + assertEquals(Collections.singletonList(1L), result.get("prescribedDrugIDs")); + assertEquals(10, tablet.getQtyPrescribed()); + assertNull(syrup.getQtyPrescribed()); + } + + @Test + void saveBenPrescribedDrugsList_succeedsWhenNoDrugWasPrescribed() { + Map result = service.saveBenPrescribedDrugsList(new ArrayList<>()); + assertEquals(1, result.get("count")); + } + + @Test + void saveBenPrescribedDrugsList_reportsNothingSavedWhenTheStoreDropsARow() { + com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail tablet = drug("Tablet", "One Tab", + "Once Daily(OD)", "1", "Day(s)"); + List drugs = Collections.singletonList(tablet); + when(prescribedDrugDetailRepo.saveAll(drugs)).thenReturn(new ArrayList<>()); + + assertEquals(0, service.saveBenPrescribedDrugsList(drugs).get("count")); + } + + @org.junit.jupiter.params.ParameterizedTest(name = "{0} {1} {2} for {3} {4} is {5}") + @org.junit.jupiter.params.provider.CsvSource({ + "Tablet, Half Tab, Once Daily(OD), 2, Day(s), 1", + "Tablet, One Tab, Once Daily(OD) Before Food, 2, Day(s), 2", + "Tablet, One & Half Tab, Once Daily(OD) After Food, 2, Day(s), 3", + "Tablet, Two Tabs, Once Daily(OD) At Bedtime, 2, Day(s), 4", + "Capsule, One Cap, Once Daily(OD), 3, Day(s), 3", + "Tablet, Half Tab, Twice Daily(BD), 2, Day(s), 2", + "Tablet, One & Half Tab, Twice Daily(BD) Before Food, 1, Day(s), 3", + "Tablet, Two Tabs, Twice Daily(BD) After Food, 1, Day(s), 4", + "Capsule, One Cap, Twice Daily(BD), 1, Day(s), 2", + "Tablet, Half Tab, Thrice Daily (TID), 2, Day(s), 3", + "Tablet, One Tab, Thrice Daily (TID) After Food, 1, Day(s), 3", + "Tablet, One & Half Tab, Thrice Daily (TID) Before Food, 1, Day(s), 5", + "Tablet, Two Tabs, Thrice Daily (TID), 1, Day(s), 6", + "Capsule, One Cap, Thrice Daily (TID), 1, Day(s), 3", + "Tablet, Half Tab, Four Times in a Day (QID), 1, Day(s), 2", + "Tablet, One Tab, Four Times in a Day AF, 1, Day(s), 4", + "Tablet, One & Half Tab, Four Times in a Day BF, 1, Day(s), 6", + "Tablet, Two Tabs, Four Times in a Day (QID), 1, Day(s), 8", + "Capsule, One Cap, Four Times in a Day (QID), 1, Day(s), 4", + "Tablet, Half Tab, Single Dose, 5, Day(s), 1", + "Tablet, One Tab, Stat Dose, 5, Day(s), 1", + "Tablet, One & Half Tab, Single Dose Before Food, 1, Day(s), 2", + "Tablet, Two Tabs, Single Dose After Food, 1, Day(s), 2", + "Capsule, One Cap, Single Dose, 1, Day(s), 1", + "Tablet, Half Tab, Once in a Week, 4, Week(s), 2", + "Tablet, One Tab, Once in a Week After Food, 4, Week(s), 4", + "Tablet, One & Half Tab, Once in a Week Before Food, 4, Week(s), 6", + "Tablet, Two Tabs, Once in a Week, 4, Week(s), 8", + "Capsule, One Cap, Once in a Week, 4, Week(s), 5", + "Tablet, Half Tab, SOS, 2, Day(s), 1", + "Tablet, One Tab, SOS, 1, Month(s), 30", + "Tablet, One & Half Tab, SOS, 1, Day(s), 2", + "Tablet, Two Tabs, SOS, 1, Day(s), 2", + "Capsule, One Cap, SOS, 1, Day(s), 1", + "Tablet, Unknown Dose, Once Daily(OD), 1, Day(s), 0", + "Tablet, One Tab, Unrecognised Frequency, 1, Day(s), 0", + "Tablet, One Tab, Once Daily(OD), 1, Unknown Unit, 0" }) + void saveBenPrescribedDrugsList_derivesTheDispensedQuantityFromFormDoseAndFrequency(String form, String dose, + String frequency, String duration, String unit, int expectedQuantity) { + com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail drug = drug(form, dose, frequency, duration, + unit); + List drugs = Collections.singletonList(drug); + when(prescribedDrugDetailRepo.saveAll(drugs)).thenReturn(drugs); + + service.saveBenPrescribedDrugsList(drugs); + + assertEquals(expectedQuantity, drug.getQtyPrescribed()); + } + + @Test + void saveBenPrescribedDrugsList_leavesTheQuantityAtZeroWhenTheOrderIsIncomplete() { + com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail drug = drug("Tablet", null, "Once Daily(OD)", + "1", "Day(s)"); + List drugs = Collections.singletonList(drug); + when(prescribedDrugDetailRepo.saveAll(drugs)).thenReturn(drugs); + + service.saveBenPrescribedDrugsList(drugs); + + assertEquals(0, drug.getQtyPrescribed()); + } + + private com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail drug(String form, String dose, + String frequency, String duration, String unit) { + com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail drug = + new com.iemr.mmu.data.quickConsultation.PrescribedDrugDetail(); + drug.setFormName(form); + drug.setDose(dose); + drug.setFrequency(frequency); + drug.setDuration(duration); + drug.setUnit(unit); + return drug; + } + } + + @Nested + @DisplayName("investigations, worklists and status flags") + class WorklistsAndInvestigations { + + @Test + void saveBenInvestigationDetails_storesThePrescriptionAndItsInvestigations() { + com.iemr.mmu.data.anc.WrapperBenInvestigationANC wrapper = + new com.iemr.mmu.data.anc.WrapperBenInvestigationANC(); + wrapper.setBeneficiaryRegID(1L); + com.iemr.mmu.data.quickConsultation.PrescriptionDetail stored = + new com.iemr.mmu.data.quickConsultation.PrescriptionDetail(); + stored.setPrescriptionID(30L); + when(prescriptionDetailRepo.save(any())).thenReturn(stored); + + assertEquals(1, service.saveBenInvestigationDetails(wrapper)); + assertEquals(30L, wrapper.getPrescriptionID()); + } + + @Test + void saveBenInvestigationDetails_doesNothingWithoutInvestigationData() { + assertEquals(0, service.saveBenInvestigationDetails(null)); + } + + @Test + void saveBenInvestigation_copiesTheVisitContextOntoEveryOrderedTest() { + com.iemr.mmu.data.anc.WrapperBenInvestigationANC wrapper = + new com.iemr.mmu.data.anc.WrapperBenInvestigationANC(); + wrapper.setBeneficiaryRegID(1L); + wrapper.setBenVisitID(2L); + wrapper.setVisitCode(3L); + wrapper.setPrescriptionID(4L); + com.iemr.mmu.data.quickConsultation.LabTestOrderDetail order = + new com.iemr.mmu.data.quickConsultation.LabTestOrderDetail(); + ArrayList orders = new ArrayList<>( + Collections.singletonList(order)); + wrapper.setLaboratoryList(orders); + when(labTestOrderDetailRepo.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + assertEquals(1L, service.saveBenInvestigation(wrapper)); + assertEquals(4L, order.getPrescriptionID()); + assertEquals(1L, order.getBeneficiaryRegID()); + assertEquals(3L, order.getVisitCode()); + } + + @Test + void saveBenInvestigation_succeedsWhenNoTestWasOrdered() { + com.iemr.mmu.data.anc.WrapperBenInvestigationANC wrapper = + new com.iemr.mmu.data.anc.WrapperBenInvestigationANC(); + assertEquals(1L, service.saveBenInvestigation(wrapper)); + } + + @Test + void updateBenVisitStatusFlag_reportsSuccessWhenTheFlagWasStored() { + when(benVisitDetailRepo.updateBenFlowStatus("N", 1L)).thenReturn(1); + assertTrue(service.updateBenVisitStatusFlag(1L, "N").contains("Updated Successfully")); + } + + @Test + void updateBenStatus_returnsAnEmptyResultWhenNothingWasUpdated() { + when(benVisitDetailRepo.updateBenFlowStatus("N", 1L)).thenReturn(0); + assertEquals("{}", service.updateBenStatus(1L, "N")); + } + + @Test + void getNurseWorkList_serialisesTheRegistrarWorklist() { + when(reistrarRepoBenSearch.getNurseWorkList()).thenReturn(new ArrayList<>()); + assertNotNull(service.getNurseWorkList()); + } + + @Test + void theRoleWorklistsFallBackToASevenDayWindowWhenNoLimitIsConfigured() { + when(beneficiaryFlowStatusRepo.getNurseWorklistNew(any(), any(), any())).thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getLabWorklistNew(any(), any(), any())).thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getRadiologistWorkListNew(any(), any(), any())) + .thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getOncologistWorkListNew(any(), any(), any())).thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getPharmaWorkListNew(any(), any(), any())).thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getNurseWorkListNew(1, 2)); + assertEquals("[]", service.getLabWorkListNew(1, 2)); + assertEquals("[]", service.getRadiologistWorkListNew(1, 2)); + assertEquals("[]", service.getOncologistWorkListNew(1, 2)); + assertEquals("[]", service.getPharmaWorkListNew(1, 2)); + } + + @Test + void theRoleWorklistsHonourTheConfiguredDayWindow() { + org.springframework.test.util.ReflectionTestUtils.setField(service, "nurseWL", 10); + org.springframework.test.util.ReflectionTestUtils.setField(service, "labWL", 10); + org.springframework.test.util.ReflectionTestUtils.setField(service, "radioWL", 10); + org.springframework.test.util.ReflectionTestUtils.setField(service, "oncoWL", 10); + org.springframework.test.util.ReflectionTestUtils.setField(service, "pharmaWL", 10); + org.springframework.test.util.ReflectionTestUtils.setField(service, "TMReferredWL", 10); + + when(beneficiaryFlowStatusRepo.getNurseWorklistNew(any(), any(), any())).thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getNurseWorklistTMreferred(any(), any(), any())) + .thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getLabWorklistNew(any(), any(), any())).thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getRadiologistWorkListNew(any(), any(), any())) + .thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getOncologistWorkListNew(any(), any(), any())).thenReturn(new ArrayList<>()); + when(beneficiaryFlowStatusRepo.getPharmaWorkListNew(any(), any(), any())).thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getNurseWorkListNew(1, 2)); + assertEquals("[]", service.getNurseWorkListTMReferred(1, 2)); + assertEquals("[]", service.getLabWorkListNew(1, 2)); + assertEquals("[]", service.getRadiologistWorkListNew(1, 2)); + assertEquals("[]", service.getOncologistWorkListNew(1, 2)); + assertEquals("[]", service.getPharmaWorkListNew(1, 2)); + } + } + + @Nested + @DisplayName("child history and screening") + class ChildHistoryAndScreening { + + @Test + void saveBenAdherenceDetails_reportsWhetherTheRowWasStored() { + com.iemr.mmu.data.anc.BenAdherence adherence = new com.iemr.mmu.data.anc.BenAdherence(); + when(benAdherenceRepo.save(adherence)).thenReturn(adherence); + assertEquals(1, service.saveBenAdherenceDetails(adherence)); + + when(benAdherenceRepo.save(adherence)).thenReturn(null); + assertEquals(0, service.saveBenAdherenceDetails(adherence)); + } + + @Test + void saveChildDevelopmentHistory_returnsTheStoredId() { + com.iemr.mmu.data.anc.BenChildDevelopmentHistory history = + new com.iemr.mmu.data.anc.BenChildDevelopmentHistory(); + com.iemr.mmu.data.anc.BenChildDevelopmentHistory stored = + new com.iemr.mmu.data.anc.BenChildDevelopmentHistory(); + stored.setID(31L); + when(benChildDevelopmentHistoryRepo.save(any())).thenReturn(stored); + assertEquals(31L, service.saveChildDevelopmentHistory(history)); + + stored.setID(0L); + assertNull(service.saveChildDevelopmentHistory(history)); + } + + @Test + void saveChildFeedingHistory_returnsTheStoredId() { + com.iemr.mmu.data.anc.ChildFeedingDetails details = new com.iemr.mmu.data.anc.ChildFeedingDetails(); + com.iemr.mmu.data.anc.ChildFeedingDetails stored = new com.iemr.mmu.data.anc.ChildFeedingDetails(); + stored.setID(32L); + when(childFeedingDetailsRepo.save(details)).thenReturn(stored); + assertEquals(32L, service.saveChildFeedingHistory(details)); + + stored.setID(0L); + assertNull(service.saveChildFeedingHistory(details)); + } + + @Test + void savePerinatalHistory_returnsTheStoredId() { + com.iemr.mmu.data.anc.PerinatalHistory history = new com.iemr.mmu.data.anc.PerinatalHistory(); + com.iemr.mmu.data.anc.PerinatalHistory stored = new com.iemr.mmu.data.anc.PerinatalHistory(); + stored.setID(33L); + when(perinatalHistoryRepo.save(history)).thenReturn(stored); + assertEquals(33L, service.savePerinatalHistory(history)); + + stored.setID(0L); + assertNull(service.savePerinatalHistory(history)); + } + + @Test + void getBenAdherence_andGetLabTestOrders_serialiseTheStoredRows() { + when(benAdherenceRepo.getBenAdherence(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getBenAdherence(1L, 2L)); + + when(labTestOrderDetailRepo.getLabTestOrderDetails(1L, 2L)).thenReturn(new ArrayList<>()); + assertNotNull(service.getLabTestOrders(1L, 2L)); + } + + @Test + void theChildHistoryReadsMapTheStoredRows() { + when(benChildDevelopmentHistoryRepo.getBenDevelopmentDetails(1L, 2L)).thenReturn(new ArrayList<>()); + service.getDevelopmentHistory(1L, 2L); + + when(perinatalHistoryRepo.getBenPerinatalDetails(1L, 2L)).thenReturn(new ArrayList<>()); + service.getPerinatalHistory(1L, 2L); + + when(childFeedingDetailsRepo.getBenFeedingDetails(1L, 2L)).thenReturn(new ArrayList<>()); + service.getFeedingHistory(1L, 2L); + } + + @Test + void updateChildFeedingHistory_updatesTheStoredRowWhenOneExists() { + com.iemr.mmu.data.anc.ChildFeedingDetails details = new com.iemr.mmu.data.anc.ChildFeedingDetails(); + when(childFeedingDetailsRepo.getBenChildFeedingDetailStatus(any(), any())).thenReturn("P"); + when(childFeedingDetailsRepo.updateFeedingDetails(any(), any(), any(), any(), any(), any(), any(), any(), + org.mockito.ArgumentMatchers.eq("U"), any(), any())).thenReturn(1); + + assertEquals(1, service.updateChildFeedingHistory(details)); + } + + @Test + void updateChildFeedingHistory_insertsAFreshRowWhenNoneIsStoredYet() { + com.iemr.mmu.data.anc.ChildFeedingDetails details = new com.iemr.mmu.data.anc.ChildFeedingDetails(); + details.setModifiedBy("nurse"); + com.iemr.mmu.data.anc.ChildFeedingDetails stored = new com.iemr.mmu.data.anc.ChildFeedingDetails(); + stored.setID(34L); + when(childFeedingDetailsRepo.getBenChildFeedingDetailStatus(any(), any())).thenReturn(null); + when(childFeedingDetailsRepo.save(details)).thenReturn(stored); + + assertEquals(1, service.updateChildFeedingHistory(details)); + assertEquals("nurse", details.getCreatedBy()); + assertEquals(0, service.updateChildFeedingHistory(null)); + } + + @Test + void updatePerinatalHistory_updatesTheStoredRowWhenOneExists() { + com.iemr.mmu.data.anc.PerinatalHistory history = new com.iemr.mmu.data.anc.PerinatalHistory(); + when(perinatalHistoryRepo.getPerinatalHistoryStatus(any(), any())).thenReturn("P"); + when(perinatalHistoryRepo.updatePerinatalDetails(any(), any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), any(), any())).thenReturn(1); + + assertEquals(1, service.updatePerinatalHistory(history)); + } + + @Test + void updatePerinatalHistory_insertsAFreshRowWhenNoneIsStoredYet() { + com.iemr.mmu.data.anc.PerinatalHistory history = new com.iemr.mmu.data.anc.PerinatalHistory(); + com.iemr.mmu.data.anc.PerinatalHistory stored = new com.iemr.mmu.data.anc.PerinatalHistory(); + stored.setID(35L); + when(perinatalHistoryRepo.getPerinatalHistoryStatus(any(), any())).thenReturn(null); + when(perinatalHistoryRepo.save(history)).thenReturn(stored); + + assertEquals(1, service.updatePerinatalHistory(history)); + assertEquals(0, service.updatePerinatalHistory(null)); + } + + @Test + void updateChildDevelopmentHistory_updatesTheStoredRowWhenOneExists() { + com.iemr.mmu.data.anc.BenChildDevelopmentHistory history = + new com.iemr.mmu.data.anc.BenChildDevelopmentHistory(); + when(benChildDevelopmentHistoryRepo.getDevelopmentHistoryStatus(any(), any())).thenReturn("P"); + when(benChildDevelopmentHistoryRepo.updatePerinatalDetails(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), org.mockito.ArgumentMatchers.eq("U"), any(), any())).thenReturn(1); + + assertEquals(1, service.updateChildDevelopmentHistory(history)); + } + + @Test + void updateChildDevelopmentHistory_insertsAFreshRowWhenNoneIsStoredYet() { + com.iemr.mmu.data.anc.BenChildDevelopmentHistory history = + new com.iemr.mmu.data.anc.BenChildDevelopmentHistory(); + com.iemr.mmu.data.anc.BenChildDevelopmentHistory stored = + new com.iemr.mmu.data.anc.BenChildDevelopmentHistory(); + stored.setID(36L); + when(benChildDevelopmentHistoryRepo.getDevelopmentHistoryStatus(any(), any())).thenReturn(null); + when(benChildDevelopmentHistoryRepo.save(any())).thenReturn(stored); + + assertEquals(1, service.updateChildDevelopmentHistory(history)); + assertEquals(0, service.updateChildDevelopmentHistory(null)); + } + + @Test + void updateBenFamilyHistoryNCDScreening_reportsWhetherEveryDiseaseWasStored() { + BenFamilyHistory input = mock(BenFamilyHistory.class); + ArrayList entries = new ArrayList<>(Collections.singletonList(new BenFamilyHistory())); + when(input.getBenFamilyHist()).thenReturn(entries); + when(benFamilyHistoryRepo.saveAll(entries)).thenReturn(entries); + assertEquals(1, service.updateBenFamilyHistoryNCDScreening(input)); + + when(input.getBenFamilyHist()).thenReturn(new ArrayList<>()); + assertEquals(0, service.updateBenFamilyHistoryNCDScreening(input)); + } + + @Test + void updateBenPhysicalActivityHistoryNCDScreening_marksAnExistingRowAsUpdated() { + PhysicalActivityType activity = new PhysicalActivityType(); + activity.setID(1L); + when(physicalActivityTypeRepo.save(activity)).thenReturn(activity); + + assertEquals(1, service.updateBenPhysicalActivityHistoryNCDScreening(activity)); + assertEquals("U", activity.getProcessed()); + assertEquals(Boolean.FALSE, activity.getDeleted()); + } + + @Test + void updateBenPhysicalActivityHistoryNCDScreening_marksAFreshRowAsNew() { + PhysicalActivityType activity = new PhysicalActivityType(); + when(physicalActivityTypeRepo.save(activity)).thenReturn(null); + + assertEquals(0, service.updateBenPhysicalActivityHistoryNCDScreening(activity)); + assertEquals("N", activity.getProcessed()); + } + } + + @Nested + @DisplayName("graph trends and NCD summaries") + class TrendsAndSummaries { + + @Test + void getGraphicalTrendData_readsWeightAndVitalsFromTheNonCancerVisits() { + ArrayList visits = new ArrayList<>(); + visits.add(new Object[] { 1L, "ANC", 100L }); + visits.add(new Object[] { 2L, "ANC", null }); + when(benVisitDetailRepo.getLastSixVisitDetailsForBeneficiary(1L)).thenReturn(visits); + + ArrayList anthro = new ArrayList<>(); + anthro.add(new Object[] { 55.5d, Date.valueOf("2024-01-01") }); + when(benAnthropometryRepo.getBenAnthropometryDetailForGraphtrends(any())).thenReturn(anthro); + + ArrayList vitals = new ArrayList<>(); + vitals.add(new Object[] { (short) 120, (short) 80, 90d, 140d, 150d, Date.valueOf("2024-01-01") }); + vitals.add(new Object[] { (short) 0, (short) 0, null, null, null, Date.valueOf("2024-01-02") }); + when(benPhysicalVitalRepo.getBenPhysicalVitalDetailForGraphTrends(any())).thenReturn(vitals); + + Map trends = service.getGraphicalTrendData(1L, "ANC"); + + assertEquals(1, ((List) trends.get("weightList")).size()); + assertEquals(1, ((List) trends.get("bpList")).size()); + assertEquals(1, ((List) trends.get("bgList")).size()); + } + + @Test + void getGraphicalTrendData_averagesTheThreeReadingsTakenAtCancerScreeningVisits() { + ArrayList visits = new ArrayList<>(); + visits.add(new Object[] { 1L, "Cancer Screening", 100L }); + when(benVisitDetailRepo.getLastSixVisitDetailsForBeneficiary(1L)).thenReturn(visits); + + com.iemr.mmu.data.nurse.BenCancerVitalDetail cancerVital = + new com.iemr.mmu.data.nurse.BenCancerVitalDetail(); + cancerVital.setWeight_Kg(60d); + cancerVital.setCreatedDate(new java.sql.Timestamp(System.currentTimeMillis())); + cancerVital.setSystolicBP_1stReading((short) 120); + cancerVital.setSystolicBP_2ndReading((short) 130); + cancerVital.setSystolicBP_3rdReading((short) 140); + cancerVital.setDiastolicBP_1stReading((short) 80); + cancerVital.setDiastolicBP_2ndReading((short) 90); + cancerVital.setDiastolicBP_3rdReading((short) 100); + cancerVital.setBloodGlucose_Fasting((short) 95); + + ArrayList cancerVitals = new ArrayList<>( + Collections.singletonList(cancerVital)); + when(benCancerVitalDetailRepo.getBenCancerVitalDetailForGraph(any())).thenReturn(cancerVitals); + + Map trends = service.getGraphicalTrendData(1L, "Cancer Screening"); + + assertEquals(1, ((List) trends.get("weightList")).size()); + List bpList = (List) trends.get("bpList"); + assertEquals(1, bpList.size()); + assertEquals(130, ((Map) bpList.get(0)).get("avgSysBP")); + assertEquals(90, ((Map) bpList.get(0)).get("avgDysBP")); + assertEquals(1, ((List) trends.get("bgList")).size()); + } + + @Test + void getGraphicalTrendData_returnsEmptyTrendsWhenTheBeneficiaryHasNoVisits() { + when(benVisitDetailRepo.getLastSixVisitDetailsForBeneficiary(1L)).thenReturn(new ArrayList<>()); + Map trends = service.getGraphicalTrendData(1L, "ANC"); + assertTrue(((List) trends.get("weightList")).isEmpty()); + } + + @Test + void getBenSymptomaticData_collectsTheAnswersOfTheMostRecentVisit() throws Exception { + IDRSData first = new IDRSData(); + first.setVisitCode(1L); + first.setIdrsQuestionID(10); + first.setAnswer("Yes"); + first.setSuspectedDisease("Diabetes"); + first.setConfirmedDisease("None"); + IDRSData sameVisit = new IDRSData(); + sameVisit.setVisitCode(1L); + sameVisit.setIdrsQuestionID(11); + IDRSData earlierVisit = new IDRSData(); + earlierVisit.setVisitCode(2L); + + when(iDRSDataRepo.getBenIdrsDetailsLast_3_Month(anyLong(), any())) + .thenReturn(new ArrayList<>(Arrays.asList(first, sameVisit, earlierVisit))); + when(iDRSDataRepo.isDiabeticCheck(1L)).thenReturn(1); + when(iDRSDataRepo.isHypertensionCheck(1L)).thenReturn(1); + when(iDRSDataRepo.isEpilepsyCheck(1L)).thenReturn(1); + when(iDRSDataRepo.isDefectiveVisionCheck(1L)).thenReturn(1); + + String json = service.getBenSymptomaticData(1L); + + assertTrue(json.contains("\"isDiabetic\":true"), json); + assertTrue(json.contains("\"isHypertension\":true"), json); + assertTrue(json.contains("\"isEpilepsy\":true"), json); + assertTrue(json.contains("\"isDefectiveVision\":true"), json); + assertTrue(json.contains("\"suspectedDisease\":\"Diabetes\""), json); + assertTrue(json.contains("\"confirmedDisease\":\"None\""), json); + } + + @Test + void getBenSymptomaticData_reportsNoConditionsWhenNothingWasScreened() throws Exception { + when(iDRSDataRepo.getBenIdrsDetailsLast_3_Month(anyLong(), any())).thenReturn(new ArrayList<>()); + when(iDRSDataRepo.isDiabeticCheck(1L)).thenReturn(0); + when(iDRSDataRepo.isHypertensionCheck(1L)).thenReturn(0); + when(iDRSDataRepo.isEpilepsyCheck(1L)).thenReturn(null); + when(iDRSDataRepo.isDefectiveVisionCheck(1L)).thenReturn(null); + + String json = service.getBenSymptomaticData(1L); + + assertTrue(json.contains("\"isDiabetic\":false"), json); + assertTrue(json.contains("\"isEpilepsy\":false"), json); + assertTrue(json.contains("\"questionariesData\":[]"), json); + } + + @Test + void getBenPreviousDiabetesData_keepsOnlyTheDiabetesQuestionsOfEachRow() throws Exception { + IDRSData diabetesRow = new IDRSData(); + diabetesRow.setDiseaseQuestionType("Diabetes||Hypertension"); + diabetesRow.setAnswer("Yes||No"); + diabetesRow.setQuestion("Do you have diabetes?||Do you have hypertension?"); + diabetesRow.setQuestionIds("1||2"); + IDRSData otherRow = new IDRSData(); + otherRow.setDiseaseQuestionType("Hypertension"); + otherRow.setAnswer("No"); + otherRow.setQuestion("Do you have hypertension?"); + otherRow.setQuestionIds("2"); + + when(iDRSDataRepo.getBenPreviousDiabetesDetails(1L)) + .thenReturn(new ArrayList<>(Arrays.asList(diabetesRow, otherRow))); + + String json = service.getBenPreviousDiabetesData(1L); + + assertTrue(json.contains("Do you have diabetes?"), json); + assertTrue(json.contains("\"columns\""), json); + } + + @Test + void getBenPreviousReferralData_mapsEachStoredReferral() throws Exception { + ArrayList referrals = new ArrayList<>(); + referrals.add(new Object[] { java.math.BigInteger.valueOf(5), + new java.sql.Timestamp(System.currentTimeMillis()), "Diabetes" }); + when(iDRSDataRepo.getBenPreviousReferredDetails(1L)).thenReturn(referrals); + + String json = service.getBenPreviousReferralData(1L); + assertTrue(json.contains("Diabetes"), json); + + when(iDRSDataRepo.getBenPreviousReferredDetails(2L)).thenReturn(null); + assertTrue(service.getBenPreviousReferralData(2L).contains("\"data\":[]")); + } + + @org.junit.jupiter.params.ParameterizedTest(name = "a BMI of {0} reads as {1}") + @org.junit.jupiter.params.provider.CsvSource({ "0.5, Normal", "-1.5, Mild malnourished", + "-2.5, Moderately Malnourished", "-4.0, Severely Malnourished", "1.5, Overweight", "2.5, Obese", + "3.5, Severely Obese" }) + void calculateBMIStatus_classifiesTheBmiAgainstTheStoredStandardDeviations(double bmi, String expectedStatus) + throws Exception { + com.iemr.mmu.data.bmi.BmiCalculation standard = new com.iemr.mmu.data.bmi.BmiCalculation(); + standard.setN3SD(-3d); + standard.setN2SD(-2d); + standard.setN1SD(-1d); + standard.setP1SD(1d); + standard.setP2SD(2d); + standard.setP3SD(3d); + when(bmiCalculationRepo.getBMIDetails(27, "Male")).thenReturn(standard); + + String request = "{\"yearMonth\":\"2 Years and 3 Months\",\"gender\":\"Male\",\"bmi\":" + bmi + "}"; + assertTrue(service.calculateBMIStatus(request).contains(expectedStatus)); + } + + @Test + void calculateBMIStatus_reportsNoStatusWhenTheRequestIsIncomplete() throws Exception { + assertEquals("{\"bmiStatus\":\"\"}", service.calculateBMIStatus("{}")); + } + + @Test + void calculateBMIStatus_failsWhenNoStandardIsStoredForTheCategory() { + when(bmiCalculationRepo.getBMIDetails(anyInt(), anyString())).thenReturn(null); + String request = "{\"yearMonth\":\"2 Years and 3 Months\",\"gender\":\"Male\",\"bmi\":1.0}"; + IEMRException thrown = assertThrows(IEMRException.class, () -> service.calculateBMIStatus(request)); + assertTrue(thrown.getMessage().contains("No data found for this category")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/common/transaction/CommonServiceImplTest.java b/src/test/java/com/iemr/mmu/service/common/transaction/CommonServiceImplTest.java new file mode 100644 index 00000000..c571661c --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/common/transaction/CommonServiceImplTest.java @@ -0,0 +1,560 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.common.transaction; + +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.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.data.syncActivity_syncLayer.EmployeeSignature; +import com.iemr.mmu.data.syncActivity_syncLayer.DownloadedCaseSheet; +import com.iemr.mmu.data.common.DocFileManager; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.EmployeeSignatureRepo; +import com.iemr.mmu.repo.nurse.ncdscreening.IDRSDataRepo; +import com.iemr.mmu.repo.provider.ProviderServiceMappingRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.DownloadedCaseSheetRepo; +import com.iemr.mmu.service.anc.ANCServiceImpl; +import com.iemr.mmu.service.cancerScreening.CSNurseServiceImpl; +import com.iemr.mmu.service.cancerScreening.CSServiceImpl; +import com.iemr.mmu.service.covid19.Covid19ServiceImpl; +import com.iemr.mmu.service.generalOPD.GeneralOPDServiceImpl; +import com.iemr.mmu.service.ncdCare.NCDCareServiceImpl; +import com.iemr.mmu.service.ncdscreening.NCDScreeningServiceImpl; +import com.iemr.mmu.service.pnc.PNCServiceImpl; +import com.iemr.mmu.service.quickConsultation.QuickConsultationServiceImpl; +import com.iemr.mmu.utils.AESEncryption.AESEncryptionDecryption; +import com.iemr.mmu.utils.CookieUtil; +import com.iemr.mmu.utils.exception.IEMRException; + +class CommonServiceImplTest { + + @Mock + private Covid19ServiceImpl covid19ServiceImpl; + @Mock + private AESEncryptionDecryption aESEncryptionDecryption; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + @Mock + private ANCServiceImpl ancServiceImpl; + @Mock + private PNCServiceImpl pncServiceImpl; + @Mock + private GeneralOPDServiceImpl generalOPDServiceImpl; + @Mock + private NCDCareServiceImpl ncdCareServiceImpl; + @Mock + private QuickConsultationServiceImpl quickConsultationServiceImpl; + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + @Mock + private CSNurseServiceImpl cSNurseServiceImpl; + @Mock + private CSServiceImpl csServiceImpl; + @Mock + private NCDScreeningServiceImpl ncdScreeningServiceImpl; + @Mock + private ProviderServiceMappingRepo providerServiceMappingRepo; + @Mock + private DownloadedCaseSheetRepo downloadedCaseSheetRepo; + @Mock + private IDRSDataRepo iDRSDataRepo; + @Mock + private EmployeeSignatureRepo employeeSignatureRepo; + @Mock + private CookieUtil cookieUtil; + + @InjectMocks + private CommonServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(service, "mmuCentralServer", "http://central/casesheet"); + ReflectionTestUtils.setField(service, "tmCentralServer", "http://central/tm"); + ReflectionTestUtils.setField(service, "specialistSign", "http://central/sign"); + } + + @AfterEach + void clearRequestContext() { + RequestContextHolder.resetRequestAttributes(); + } + + private static BeneficiaryFlowStatus flow(String visitCategory) { + BeneficiaryFlowStatus flow = new BeneficiaryFlowStatus(); + flow.setVisitCategory(visitCategory); + flow.setBeneficiaryRegID(1L); + flow.setBenVisitCode(2L); + flow.setVisitCode(2L); + flow.setBenFlowID(3L); + flow.setBenVisitID(4L); + return flow; + } + + @Nested + @DisplayName("case sheet print data") + class PrintData { + + @BeforeEach + void stubTheLeftPanel() { + when(beneficiaryFlowStatusRepo.getBenDetailsForLeftSidePanel(1L, 3L)).thenReturn(new ArrayList<>()); + } + + @Test + void getCaseSheetPrintDataForBeneficiary_routesEachVisitCategoryToItsOwnPrintData() throws Exception { + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("ANC"), "auth").contains("nurseData")); + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("PNC"), "auth").contains("nurseData")); + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("General OPD"), "auth") + .contains("nurseData")); + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("NCD care"), "auth").contains("nurseData")); + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("General OPD (QC)"), "auth") + .contains("nurseData")); + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("COVID-19 Screening"), "auth") + .contains("nurseData")); + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("NCD screening"), "auth") + .contains("nurseData")); + } + + @Test + void getCaseSheetPrintDataForBeneficiary_addsTheAnnotatedImagesForCancerScreening() throws Exception { + when(cSNurseServiceImpl.getCancerExaminationImageAnnotationCasesheet(1L, 2L)) + .thenReturn(new ArrayList<>()); + + assertTrue(service.getCaseSheetPrintDataForBeneficiary(flow("Cancer Screening"), "auth") + .contains("ImageAnnotatedData")); + } + + @Test + void getCaseSheetPrintDataForBeneficiary_rejectsAnUnknownVisitCategory() throws Exception { + assertEquals("Invalid VisitCategory", + service.getCaseSheetPrintDataForBeneficiary(flow("Unknown"), "auth")); + } + } + + @Nested + @DisplayName("past history reads") + class PastHistoryReads { + + @Test + void everyPastHistoryReadDelegatesToTheNurseService() throws Exception { + when(commonNurseServiceImpl.fetchBenPastMedicalHistory(1L)).thenReturn("past"); + assertEquals("past", service.getBenPastHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenComorbidityHistory(1L)).thenReturn("comorbid"); + assertEquals("comorbid", service.getComorbidHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPersonalMedicationHistory(1L)).thenReturn("medication"); + assertEquals("medication", service.getMedicationHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPersonalTobaccoHistory(1L)).thenReturn("tobacco"); + assertEquals("tobacco", service.getPersonalTobaccoHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPersonalAlcoholHistory(1L)).thenReturn("alcohol"); + assertEquals("alcohol", service.getPersonalAlcoholHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPersonalAllergyHistory(1L)).thenReturn("allergy"); + assertEquals("allergy", service.getPersonalAllergyHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPersonalFamilyHistory(1L)).thenReturn("family"); + assertEquals("family", service.getFamilyHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPhysicalHistory(1L)).thenReturn("physical"); + assertEquals("physical", service.getBenPhysicalHistory(1L)); + + when(commonNurseServiceImpl.fetchBenMenstrualHistory(1L)).thenReturn("menstrual"); + assertEquals("menstrual", service.getMenstrualHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPastObstetricHistory(1L)).thenReturn("obstetric"); + assertEquals("obstetric", service.getObstetricHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenImmunizationHistory(1L)).thenReturn("immunization"); + assertEquals("immunization", service.getImmunizationHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenOptionalVaccineHistory(1L)).thenReturn("vaccine"); + assertEquals("vaccine", service.getChildVaccineHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenPerinatalHistory(1L)).thenReturn("perinatal"); + assertEquals("perinatal", service.getBenPerinatalHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenFeedingHistory(1L)).thenReturn("feeding"); + assertEquals("feeding", service.getBenFeedingHistoryData(1L)); + + when(commonNurseServiceImpl.fetchBenDevelopmentHistory(1L)).thenReturn("development"); + assertEquals("development", service.getBenDevelopmentHistoryData(1L)); + + when(commonNurseServiceImpl.getBenSymptomaticData(1L)).thenReturn("symptomatic"); + assertEquals("symptomatic", service.getBenSymptomaticQuestionnaireDetailsData(1L)); + + when(commonNurseServiceImpl.getBenPreviousDiabetesData(1L)).thenReturn("diabetes"); + assertEquals("diabetes", service.getBenPreviousDiabetesData(1L)); + + when(commonNurseServiceImpl.getBenPreviousReferralData(1L)).thenReturn("referral"); + assertEquals("referral", service.getBenPreviousReferralData(1L)); + } + + @Test + void getBenPreviousVisitDataForCaseRecord_looksTheVisitsUpAgainstEveryMmuProvider() throws Exception { + when(providerServiceMappingRepo.getProviderServiceMapIdForServiceID((short) 2)) + .thenReturn(new ArrayList<>(Collections.singletonList(5))); + when(beneficiaryFlowStatusRepo.getBenPreviousHistory(eq(1L), any())).thenReturn(new ArrayList<>()); + + assertNotNull(service.getBenPreviousVisitDataForCaseRecord("{\"beneficiaryRegID\":1}")); + } + } + + @Nested + @DisplayName("uploaded files") + class Files { + + @TempDir + Path uploadRoot; + + @Test + void saveFiles_writesEachAttachmentUnderTheVanAndDateFolderAndEncryptsItsPath() throws Exception { + ReflectionTestUtils.setField(service, "fileBasePath", uploadRoot.toString() + "/"); + when(aESEncryptionDecryption.encrypt(anyString())).thenReturn("encrypted"); + + DocFileManager attachment = new DocFileManager(); + attachment.setVanID(7); + attachment.setFileName("re*port.pdf"); + attachment.setFileExtension(".pdf"); + attachment.setFileContent(Base64.getEncoder().encodeToString("content".getBytes())); + + String response = service.saveFiles(Collections.singletonList(attachment)); + + assertTrue(response.contains("encrypted")); + assertTrue(response.contains("report.pdf")); + } + + @Test + void saveFiles_reusesTheDateFolderOnASecondUpload() throws Exception { + ReflectionTestUtils.setField(service, "fileBasePath", uploadRoot.toString() + "/"); + when(aESEncryptionDecryption.encrypt(anyString())).thenReturn("encrypted"); + + DocFileManager attachment = new DocFileManager(); + attachment.setVanID(7); + attachment.setFileName("report.pdf"); + attachment.setFileExtension(".pdf"); + attachment.setFileContent(Base64.getEncoder().encodeToString("content".getBytes())); + + service.saveFiles(Collections.singletonList(attachment)); + assertTrue(service.saveFiles(Collections.singletonList(attachment)).contains("encrypted")); + } + + @Test + void saveFiles_skipsAnAttachmentWithoutANameOrExtension() throws Exception { + ReflectionTestUtils.setField(service, "fileBasePath", uploadRoot.toString() + "/"); + + DocFileManager attachment = new DocFileManager(); + attachment.setVanID(7); + + assertEquals("[]", service.saveFiles(Collections.singletonList(attachment))); + } + + @Test + void saveFiles_returnsNothingWhenNoAttachmentWasSent() throws Exception { + ReflectionTestUtils.setField(service, "fileBasePath", uploadRoot.toString() + "/"); + assertEquals("[]", service.saveFiles(new ArrayList<>())); + } + + @Test + void loadFileAsResource_returnsAnExistingFile() throws Exception { + Path file = uploadRoot.resolve("report.pdf"); + java.nio.file.Files.write(file, "content".getBytes()); + + assertNotNull(service.loadFileAsResource("report.pdf", file.toString())); + } + + @Test + void loadFileAsResource_failsForAMissingFile() { + assertThrows(IOException.class, + () -> service.loadFileAsResource("gone.pdf", uploadRoot.resolve("gone.pdf").toString())); + } + } + + @Nested + @DisplayName("teleconsultation case sheets") + class TeleconsultationCaseSheets { + + private CommonServiceImpl serviceSpy; + + @BeforeEach + void useASpyForTheOutboundCalls() { + serviceSpy = spy(service); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest())); + } + + private ResponseEntity ok(String body) { + return new ResponseEntity<>(body, HttpStatus.OK); + } + + @Test + void checkIsCaseSheetDownloaded_reportsWhetherTheSheetWasAlreadyPulled() throws Exception { + when(beneficiaryFlowStatusRepo.checkIsCaseSheetDownloaded(1L)).thenReturn(true); + assertEquals(1, service.checkIsCaseSheetDownloaded(1L)); + + when(beneficiaryFlowStatusRepo.checkIsCaseSheetDownloaded(1L)).thenReturn(false); + assertEquals(0, service.checkIsCaseSheetDownloaded(1L)); + + when(beneficiaryFlowStatusRepo.checkIsCaseSheetDownloaded(1L)).thenReturn(null); + assertEquals(0, service.checkIsCaseSheetDownloaded(1L)); + } + + @Test + void getTmVisitCode_readsTheTeleconsultationVisitOfAnMmuVisit() throws Exception { + BeneficiaryFlowStatus tmVisit = new BeneficiaryFlowStatus(); + when(beneficiaryFlowStatusRepo.getTMVisitDetails(1L)).thenReturn(tmVisit); + assertEquals(tmVisit, service.getTmVisitCode(1L)); + } + + @Test + void getTmCaseSheet_returnsTheCaseSheetAndTheSpecialistSignature() throws Exception { + String caseSheet = "{\"statusCode\":200,\"data\":{\"BeneficiaryData\":{\"tCSpecialistUserID\":5}}}"; + String signature = "{\"statusCode\":200,\"data\":{\"userID\":5}}"; + doReturn(ok(caseSheet)).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), any()); + doReturn(ok(signature)).when(serviceSpy).restTemplateGet(anyString(), any(), any()); + + ArrayList result = serviceSpy.getTmCaseSheet(flow("NCD screening"), flow("NCD screening"), "auth"); + + assertEquals(2, result.size()); + } + + @Test + void getTmCaseSheet_returnsOnlyTheCaseSheetWhenNoSpecialistSigned() throws Exception { + String caseSheet = "{\"statusCode\":200,\"data\":{\"BeneficiaryData\":{}}}"; + doReturn(ok(caseSheet)).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), any()); + + assertEquals(1, + serviceSpy.getTmCaseSheet(flow("NCD screening"), flow("NCD screening"), "auth").size()); + } + + @Test + void getTmCaseSheet_reportsTheLoginFailureFromTheCentralServer() { + String error = "{\"statusCode\":5002,\"errorMessage\":\"Session expired\"}"; + doReturn(ok(error)).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), any()); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> serviceSpy.getTmCaseSheet(flow("NCD screening"), flow("NCD screening"), "auth")); + assertEquals(5002, thrown.getErrorCode()); + } + + @Test + void getTmCaseSheet_reportsAnyOtherFailureFromTheCentralServer() { + String error = "{\"statusCode\":5000,\"errorMessage\":\"Boom\"}"; + doReturn(ok(error)).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), any()); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> serviceSpy.getTmCaseSheet(flow("NCD screening"), flow("NCD screening"), "auth")); + assertEquals("Boom", thrown.getMessage()); + } + + @Test + void getTmCaseSheet_failsWhenTheCentralServerRejectsTheRequest() { + doReturn(new ResponseEntity(HttpStatus.BAD_REQUEST)).when(serviceSpy) + .restTemplatePost(anyString(), any(), anyString(), any()); + + assertThrows(IEMRException.class, + () -> serviceSpy.getTmCaseSheet(flow("NCD screening"), flow("NCD screening"), "auth")); + } + + @Test + void getTmCaseSheetOffline_returnsThePreviouslyDownloadedSheet() throws Exception { + DownloadedCaseSheet stored = new DownloadedCaseSheet(); + stored.setTmCaseSheetResponse("sheet"); + when(downloadedCaseSheetRepo.getTmCaseSheetFromOffline(2L)).thenReturn(stored); + + assertEquals("sheet", service.getTmCaseSheetOffline(flow("NCD screening"))); + } + + @Test + void getTmCaseSheetOffline_failsWhenNothingWasDownloadedYet() { + when(downloadedCaseSheetRepo.getTmCaseSheetFromOffline(2L)).thenReturn(null); + + assertThrows(IEMRException.class, () -> service.getTmCaseSheetOffline(flow("NCD screening"))); + } + + @Test + void getCaseSheetOfTm_returnsTheCaseSheetOnceTeleconsultationIsComplete() throws Exception { + BeneficiaryFlowStatus tmVisit = new BeneficiaryFlowStatus(); + tmVisit.setSpecialist_flag((short) 9); + when(beneficiaryFlowStatusRepo.getTMVisitDetails(any())).thenReturn(tmVisit); + doReturn(new ArrayList<>(Collections.singletonList("sheet"))).when(serviceSpy).getTmCaseSheet(any(), + any(), anyString()); + + assertTrue(serviceSpy.getCaseSheetOfTm("{\"benVisitCode\":2}", "auth").contains("sheet")); + } + + @Test + void getCaseSheetOfTm_failsWhileTeleconsultationIsStillPending() { + BeneficiaryFlowStatus tmVisit = new BeneficiaryFlowStatus(); + tmVisit.setSpecialist_flag((short) 1); + when(beneficiaryFlowStatusRepo.getTMVisitDetails(any())).thenReturn(tmVisit); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> service.getCaseSheetOfTm("{\"benVisitCode\":2}", "auth")); + assertEquals("Tele-Consultation is not completed", thrown.getMessage()); + } + + @Test + void getCaseSheetOfTm_failsWhileTheBeneficiaryIsStillInTheTeleconsultationWorklist() { + when(beneficiaryFlowStatusRepo.getTMVisitDetails(any())).thenReturn(null); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> service.getCaseSheetOfTm("{\"benVisitCode\":2}", "auth")); + assertEquals("Patient is waiting in Tele-Medicine worklist", thrown.getMessage()); + } + + /** The central-server payload for a downloaded case sheet, with an optional signature. */ + private String centralPayload(boolean withSignature) { + String caseSheet = "{\"nurseData\":{\"history\":{\"PhysicalActivityHistory\":{\"visitCode\":9," + + "\"createdBy\":\"specialist\"}},\"idrs\":{\"IDRSDetail\":{\"confirmedDisease\":\"Diabetes\"," + + "\"suspectedDisease\":\"Hypertension\"}}}}"; + String signature = withSignature ? ",{\"userID\":5}" : ""; + return "{\"statusCode\":200,\"data\":[" + caseSheet + signature + "]}"; + } + + @Test + void getCaseSheetFromCentralServer_storesTheSheetTheSignatureAndTheScreeningOutcome() throws Exception { + doReturn(ok(centralPayload(true))).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), + any()); + when(employeeSignatureRepo.findOneByUserID(5L)).thenReturn(null); + when(downloadedCaseSheetRepo.save(any())).thenReturn(new DownloadedCaseSheet()); + when(beneficiaryFlowStatusRepo.updateDownloadFlag(2L)).thenReturn(1); + when(iDRSDataRepo.updateConfirmedAndSuspectedDisease("Diabetes", "Hypertension", 2L)).thenReturn(1); + + assertTrue(serviceSpy.getCaseSheetFromCentralServer("{\"visitCode\":2}", "auth") + .contains("PhysicalActivityHistory")); + verify(employeeSignatureRepo).save(any()); + } + + @Test + void getCaseSheetFromCentralServer_keepsAnAlreadyStoredSpecialistSignature() throws Exception { + doReturn(ok(centralPayload(true))).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), + any()); + when(employeeSignatureRepo.findOneByUserID(5L)).thenReturn(new EmployeeSignature()); + when(downloadedCaseSheetRepo.save(any())).thenReturn(new DownloadedCaseSheet()); + when(beneficiaryFlowStatusRepo.updateDownloadFlag(2L)).thenReturn(1); + when(iDRSDataRepo.updateConfirmedAndSuspectedDisease(any(), any(), anyLong())).thenReturn(1); + + assertNotNull(serviceSpy.getCaseSheetFromCentralServer("{\"visitCode\":2}", "auth")); + verify(employeeSignatureRepo, org.mockito.Mockito.never()).save(any()); + } + + @Test + void getCaseSheetFromCentralServer_failsWhenTheScreeningOutcomeCouldNotBeStored() throws Exception { + doReturn(ok(centralPayload(false))).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), + any()); + when(downloadedCaseSheetRepo.save(any())).thenReturn(new DownloadedCaseSheet()); + when(beneficiaryFlowStatusRepo.updateDownloadFlag(2L)).thenReturn(1); + when(iDRSDataRepo.updateConfirmedAndSuspectedDisease(any(), any(), anyLong())).thenReturn(0); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> serviceSpy.getCaseSheetFromCentralServer("{\"visitCode\":2}", "auth")); + assertTrue(thrown.getMessage().contains("confirmed and suspected disease")); + } + + @Test + void getCaseSheetFromCentralServer_failsWhenTheDownloadFlagCouldNotBeStored() throws Exception { + doReturn(ok(centralPayload(false))).when(serviceSpy).restTemplatePost(anyString(), any(), anyString(), + any()); + when(downloadedCaseSheetRepo.save(any())).thenReturn(new DownloadedCaseSheet()); + when(beneficiaryFlowStatusRepo.updateDownloadFlag(2L)).thenReturn(0); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> serviceSpy.getCaseSheetFromCentralServer("{\"visitCode\":2}", "auth")); + assertTrue(thrown.getMessage().contains("download flag")); + } + + @Test + void getCaseSheetFromCentralServer_reportsTheLoginFailureFromTheCentralServer() { + doReturn(ok("{\"statusCode\":5002,\"errorMessage\":\"Session expired\"}")).when(serviceSpy) + .restTemplatePost(anyString(), any(), anyString(), any()); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> serviceSpy.getCaseSheetFromCentralServer("{\"visitCode\":2}", "auth")); + assertEquals(5002, thrown.getErrorCode()); + } + + @Test + void getCaseSheetFromCentralServer_reportsAnyOtherFailureFromTheCentralServer() { + doReturn(ok("{\"statusCode\":5000,\"errorMessage\":\"Boom\"}")).when(serviceSpy) + .restTemplatePost(anyString(), any(), anyString(), any()); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> serviceSpy.getCaseSheetFromCentralServer("{\"visitCode\":2}", "auth")); + assertEquals("Boom", thrown.getMessage()); + } + + @Test + void getCaseSheetFromCentralServer_failsWhenTheCentralServerRejectsTheRequest() { + doReturn(new ResponseEntity(HttpStatus.BAD_REQUEST)).when(serviceSpy) + .restTemplatePost(anyString(), any(), anyString(), any()); + + assertThrows(IEMRException.class, + () -> serviceSpy.getCaseSheetFromCentralServer("{\"visitCode\":2}", "auth")); + } + + @Test + void updateConfirmedDisease_delegatesToTheScreeningRepository() { + when(iDRSDataRepo.updateConfirmedAndSuspectedDisease("Diabetes", "Hypertension", 2L)).thenReturn(1); + assertEquals(1, service.updateConfirmedDisease("Diabetes", "Hypertension", 2L)); + } + + @Test + void getJsonObj_parsesTheResponseBody() { + assertTrue(service.getJsonObj(ok("{\"statusCode\":200}")).has("statusCode")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/covid19/Covid19ServiceImplTest.java b/src/test/java/com/iemr/mmu/service/covid19/Covid19ServiceImplTest.java new file mode 100644 index 00000000..e9ad2627 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/covid19/Covid19ServiceImplTest.java @@ -0,0 +1,575 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.covid19; + +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.data.covid19.Covid19BenFeedback; +import com.iemr.mmu.data.nurse.CommonUtilityClass; +import com.iemr.mmu.data.quickConsultation.PrescriptionDetail; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.repo.nurse.covid19.Covid19BenFeedbackRepo; +import com.iemr.mmu.repo.quickConsultation.PrescriptionDetailRepo; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonServiceImpl; +import com.iemr.mmu.service.labtechnician.LabTechnicianServiceImpl; + +class Covid19ServiceImplTest { + + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private LabTechnicianServiceImpl labTechnicianServiceImpl; + @Mock + private CommonServiceImpl commonServiceImpl; + @Mock + private Covid19BenFeedbackRepo covid19BenFeedbackRepo; + @Mock + private PrescriptionDetailRepo prescriptionDetailRepo; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + + @InjectMocks + private Covid19ServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + private static String visitDetailsBlock(String covidDetails) { + return "\"visitDetails\":{\"visitDetails\":{\"beneficiaryRegID\":1,\"visitReason\":\"New Chief Complaint\"," + + "\"visitCategory\":\"COVID-19 Screening\"},\"covidDetails\":" + covidDetails + "}"; + } + + private static Covid19BenFeedback storedFeedback() { + Covid19BenFeedback stored = new Covid19BenFeedback(); + stored.setcOVID19ID(BigInteger.valueOf(3)); + return stored; + } + + @Nested + @DisplayName("saving nurse data") + class NurseSave { + + @Test + void saveCovid19NurseData_ignoresARequestWithoutVisitDetails() throws Exception { + assertNull(service.saveCovid19NurseData(null, "auth")); + assertNull(service.saveCovid19NurseData(json("{}"), "auth")); + } + + @Test + void saveCovid19NurseData_skipsTheVisitWhenTheNurseAlreadySavedThisFlow() throws Exception { + when(beneficiaryFlowStatusRepo.checkExistData(any(), any())).thenReturn(new BeneficiaryFlowStatus()); + + assertEquals(0L, service.saveCovid19NurseData(json("{" + visitDetailsBlock("null") + "}"), "auth")); + verify(commonNurseServiceImpl, never()).saveBeneficiaryVisitDetails(any()); + } + + @Test + void saveCovid19NurseData_returnsZeroWhenTheVisitWasNotCreated() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(1); + + assertEquals(0L, service.saveCovid19NurseData(json("{" + visitDetailsBlock("null") + "}"), "auth")); + } + + @Test + void saveCovid19NurseData_savesTheScreeningAndAdvancesTheBeneficiaryFlow() throws Exception { + stubVisitCreation(); + when(covid19BenFeedbackRepo.save(any())).thenReturn(storedFeedback()); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(1); + + String request = "{" + visitDetailsBlock(covidFeedback()) + ",\"historyDetails\":{}," + + "\"vitalDetails\":{}}"; + + assertEquals(1L, service.saveCovid19NurseData(json(request), "auth")); + } + + @Test + void saveCovid19NurseData_treatsAnAbsentScreeningBlockAsAlreadyDone() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(1); + + String request = "{" + visitDetailsBlock("null") + ",\"historyDetails\":{},\"vitalDetails\":{}}"; + + assertEquals(1L, service.saveCovid19NurseData(json(request), "auth")); + verify(covid19BenFeedbackRepo, never()).save(any()); + } + + @Test + void saveCovid19NurseData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(0); + + String request = "{" + visitDetailsBlock("null") + ",\"historyDetails\":{},\"vitalDetails\":{}}"; + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveCovid19NurseData(json(request), "auth")); + assertTrue(thrown.getMessage().contains("Beneficiary status update failed")); + } + + @Test + void saveCovid19NurseData_failsWhenASectionCouldNotBeSaved() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(null); + + String request = "{" + visitDetailsBlock("null") + ",\"historyDetails\":{},\"vitalDetails\":{}}"; + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveCovid19NurseData(json(request), "auth")); + assertEquals("Error occurred while saving data", thrown.getMessage()); + } + + private void stubVisitCreation() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + } + + private String covidFeedback() { + return "{\"symptom\":[\"Fever\",\"Cough\"],\"contactStatus\":[\"Household\"]," + + "\"travelList\":[\"Domestic\"],\"recommendation\":[[\"Isolate\",\"Test\"]]," + + "\"suspectedStatusUI\":\"YES\"}"; + } + + @Test + void saveCovid19NurseData_joinsEveryMultiValuedScreeningAnswerBeforeSaving() throws Exception { + stubVisitCreation(); + when(covid19BenFeedbackRepo.save(any())).thenReturn(storedFeedback()); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(1); + + String request = "{" + visitDetailsBlock(covidFeedback()) + ",\"historyDetails\":{}," + + "\"vitalDetails\":{}}"; + service.saveCovid19NurseData(json(request), "auth"); + + ArgumentCaptor saved = ArgumentCaptor.forClass(Covid19BenFeedback.class); + verify(covid19BenFeedbackRepo).save(saved.capture()); + assertEquals("Fever||Cough", saved.getValue().getSymptoms_db()); + assertEquals("Household", saved.getValue().getcOVID19_contact_history()); + assertEquals("Domestic", saved.getValue().getTravelType()); + assertEquals("Isolate||Test", saved.getValue().getRecommendation_db()); + assertEquals(Boolean.TRUE, saved.getValue().getSuspectedStatus()); + } + + @Test + void saveCovid19NurseData_recordsANegativeScreeningResult() throws Exception { + stubVisitCreation(); + when(covid19BenFeedbackRepo.save(any())).thenReturn(storedFeedback()); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(1); + + String request = "{" + visitDetailsBlock("{\"suspectedStatusUI\":\"NO\"}") + + ",\"historyDetails\":{},\"vitalDetails\":{}}"; + service.saveCovid19NurseData(json(request), "auth"); + + ArgumentCaptor saved = ArgumentCaptor.forClass(Covid19BenFeedback.class); + verify(covid19BenFeedbackRepo).save(saved.capture()); + assertEquals(Boolean.FALSE, saved.getValue().getSuspectedStatus()); + } + + @Test + void saveBenVisitDetails_returnsNothingWhenTheVisitBlockIsMissing() throws Exception { + assertTrue(service.saveBenVisitDetails(json("{}"), new CommonUtilityClass()).isEmpty()); + assertTrue(service.saveBenVisitDetails(null, new CommonUtilityClass()).isEmpty()); + } + } + + @Nested + @DisplayName("saving the individual nurse sections") + class NurseSections { + + @Test + void saveBenCovid19HistoryDetails_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenCovid19HistoryDetails(json("{}"), 1L, 2L)); + assertEquals(1L, service.saveBenCovid19HistoryDetails(null, 1L, 2L)); + } + + @Test + void saveBenCovid19HistoryDetails_savesEverySectionThatWasSent() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenComorbidConditions(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMedicationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveFemaleObstetricHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMenstrualHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenFamilyHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePersonalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveAllergyHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildOptionalVaccineDetail(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveImmunizationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildDevelopmentHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildFeedingHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePerinatalHistory(any())).thenReturn(1L); + + String history = "{\"pastHistory\":{},\"comorbidConditions\":{}," + + "\"medicationHistory\":{\"medicationHistoryList\":[{\"currentMedication\":\"x\"}]}," + + "\"femaleObstetricHistory\":{},\"menstrualHistory\":{},\"familyHistory\":{}," + + "\"personalHistory\":{},\"childVaccineDetails\":{},\"immunizationHistory\":{}," + + "\"developmentHistory\":{},\"feedingHistory\":{},\"perinatalHistroy\":{}}"; + + assertEquals(1L, service.saveBenCovid19HistoryDetails(json(history), 1L, 2L)); + } + + @Test + void saveBenCovid19HistoryDetails_reportsFailureWhenASectionCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(0L); + assertNull(service.saveBenCovid19HistoryDetails(json("{\"pastHistory\":{}}"), 1L, 2L)); + } + + @Test + void saveBenCovid19VitalDetails_savesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(5L); + + assertEquals(4L, service.saveBenCovid19VitalDetails(json("{}"), 1L, 2L)); + } + + @Test + void saveBenCovid19VitalDetails_reportsFailureWhenTheVitalsCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(null); + + assertNull(service.saveBenCovid19VitalDetails(json("{}"), 1L, 2L)); + assertNull(service.saveBenCovid19VitalDetails(null, 1L, 2L)); + } + } + + @Nested + @DisplayName("reading the nurse and doctor case sheets") + class Reads { + + @Test + void getBenVisitDetailsFrmNurseCovid19_splitsTheStoredScreeningAnswersBackIntoLists() throws Exception { + Covid19BenFeedback stored = new Covid19BenFeedback(); + stored.setSymptoms_db("Fever||Cough"); + stored.setTravelType("Domestic||International"); + stored.setcOVID19_contact_history("Household"); + stored.setRecommendation_db("Isolate||Test"); + stored.setSuspectedStatus(true); + when(covid19BenFeedbackRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)).thenReturn(stored); + + String result = service.getBenVisitDetailsFrmNurseCovid19(1L, 2L); + + assertTrue(result.contains("covid19NurseVisitDetail")); + assertEquals(2, stored.getSymptoms().length); + assertEquals(2, stored.getTravelList().length); + assertEquals(1, stored.getContactStatus().length); + assertEquals(1, stored.getRecommendation().size()); + assertEquals("YES", stored.getSuspectedStatusUI()); + } + + @Test + void getBenVisitDetailsFrmNurseCovid19_marksANegativeScreeningForTheUi() throws Exception { + Covid19BenFeedback stored = new Covid19BenFeedback(); + stored.setSuspectedStatus(false); + when(covid19BenFeedbackRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)).thenReturn(stored); + + service.getBenVisitDetailsFrmNurseCovid19(1L, 2L); + + assertEquals("NO", stored.getSuspectedStatusUI()); + } + + @Test + void getBenVisitDetailsFrmNurseCovid19_tolratesABeneficiaryWithNoScreeningOnRecord() throws Exception { + when(covid19BenFeedbackRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)).thenReturn(null); + assertTrue(service.getBenVisitDetailsFrmNurseCovid19(1L, 2L).contains("covidDetails")); + } + + @Test + void getBenCovidNurseData_combinesTheScreeningVitalsAndHistory() { + String result = service.getBenCovidNurseData(1L, 2L); + + assertTrue(result.contains("covidDetails")); + assertTrue(result.contains("vitals")); + assertTrue(result.contains("history")); + } + + @Test + void getBenCovid19HistoryDetails_gathersEveryHistorySection() { + when(commonNurseServiceImpl.getPastHistoryData(1L, 2L)) + .thenReturn(new com.iemr.mmu.data.anc.BenMedHistory()); + + assertTrue(service.getBenCovid19HistoryDetails(1L, 2L).contains("PastHistory")); + verify(commonNurseServiceImpl).getFeedingHistory(1L, 2L); + } + + @Test + void getBeneficiaryVitalDetails_gathersAnthropometryAndVitals() { + when(commonNurseServiceImpl.getBeneficiaryPhysicalAnthropometryDetails(1L, 2L)).thenReturn("a"); + when(commonNurseServiceImpl.getBeneficiaryPhysicalVitalDetails(1L, 2L)).thenReturn("v"); + + assertTrue(service.getBeneficiaryVitalDetails(1L, 2L).contains("benAnthropometryDetail")); + } + + @Test + void getBenCaseRecordFromDoctorCovid19_readsTheStoredDiagnosisWhenOneExists() throws Exception { + PrescriptionDetail prescription = new PrescriptionDetail(); + prescription.setDiagnosisProvided("Covid-19"); + prescription.setPrescriptionID(7L); + ArrayList prescriptions = new ArrayList<>( + Collections.singletonList(prescription)); + when(prescriptionDetailRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)).thenReturn(prescriptions); + when(labTechnicianServiceImpl.getLabResultDataForBen(1L, 2L)).thenReturn(new ArrayList<>()); + when(commonNurseServiceImpl.getGraphicalTrendData(1L, "ncdCare")).thenReturn(new HashMap<>()); + + String result = service.getBenCaseRecordFromDoctorCovid19(1L, 2L); + + assertTrue(result.contains("Covid-19")); + assertTrue(result.contains("GraphData")); + } + + @Test + void getBenCaseRecordFromDoctorCovid19_returnsAnEmptyDiagnosisWhenNoneWasRecorded() throws Exception { + when(prescriptionDetailRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)).thenReturn(new ArrayList<>()); + when(labTechnicianServiceImpl.getLabResultDataForBen(1L, 2L)).thenReturn(new ArrayList<>()); + when(commonNurseServiceImpl.getGraphicalTrendData(1L, "ncdCare")).thenReturn(new HashMap<>()); + + assertTrue(service.getBenCaseRecordFromDoctorCovid19(1L, 2L).contains("diagnosis")); + } + } + + @Nested + @DisplayName("updating nurse data") + class NurseUpdates { + + @Test + void updateBenHistoryDetails_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1, service.updateBenHistoryDetails(json("{}"))); + } + + @Test + void updateBenHistoryDetails_updatesEverySectionThatWasSent() throws Exception { + when(commonNurseServiceImpl.updateBenPastHistoryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenComorbidConditions(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenMedicationHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenPersonalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenAllergicHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenFamilyHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateMenstrualHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePastObstetricHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildImmunizationDetail(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildOptionalVaccineDetail(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildFeedingHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePerinatalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildDevelopmentHistory(any())).thenReturn(1); + + String history = "{\"pastHistory\":{},\"comorbidConditions\":{},\"medicationHistory\":{}," + + "\"personalHistory\":{},\"familyHistory\":{},\"menstrualHistory\":{}," + + "\"femaleObstetricHistory\":{},\"immunizationHistory\":{\"benChildVaccineDetails\":[{}]}," + + "\"childVaccineDetails\":{},\"feedingHistory\":{},\"perinatalHistroy\":{}," + + "\"developmentHistory\":{}}"; + + assertEquals(1, service.updateBenHistoryDetails(json(history))); + } + + @Test + void updateBenHistoryDetails_reportsFailureWhenASectionCouldNotBeUpdated() throws Exception { + when(commonNurseServiceImpl.updateBenPastHistoryDetails(any())).thenReturn(0); + assertEquals(0, service.updateBenHistoryDetails(json("{\"pastHistory\":{}}"))); + } + + @Test + void updateBenVitalDetails_updatesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(1); + + assertEquals(1, service.updateBenVitalDetails(json("{}"))); + } + + @Test + void updateBenVitalDetails_reportsFailureWhenTheVitalsCouldNotBeUpdated() throws Exception { + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(0); + + assertEquals(0, service.updateBenVitalDetails(json("{}"))); + assertEquals(1, service.updateBenVitalDetails(null)); + } + } + + @Nested + @DisplayName("saving and updating doctor data") + class DoctorData { + + private String doctorRequest(boolean isSpecialist) { + return "{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"providerServiceMapID\":4," + + "\"createdBy\":\"doctor\",\"isSpecialist\":" + isSpecialist + ",\"findings\":{}," + + "\"investigation\":{\"laboratoryList\":[{}]}," + + "\"diagnosis\":{\"doctorDiagnosis\":\"Covid-19\",\"specialistDiagnosis\":\"Isolate\"," + + "\"prescriptionID\":7},\"prescription\":[{\"drugID\":1}],\"refer\":{}}"; + } + + private Map drugResult() { + Map result = new HashMap<>(); + result.put("count", 1); + result.put("prescribedDrugIDs", Collections.singletonList(9L)); + return result; + } + + @Test + void saveDoctorData_savesEverySectionAndAdvancesTheFlow() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsCovid19(any(), any(), any(), any(), any(), any(), any(), + any(), anyString())).thenReturn(7L); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.saveDoctorData(json(doctorRequest(false)), "auth")); + } + + @Test + void saveDoctorData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + when(commonNurseServiceImpl.savePrescriptionDetailsCovid19(any(), any(), any(), any(), any(), any(), any(), + any(), any())).thenReturn(7L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.saveDoctorData(json("{\"investigation\":{}}"), "auth")); + } + + @Test + void saveDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsCovid19(any(), any(), any(), any(), any(), any(), any(), + any(), anyString())).thenReturn(7L); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, () -> service.saveDoctorData(json(doctorRequest(false)), "auth")); + } + + @Test + void saveDoctorData_failsWhenASectionCouldNotBeSaved() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(0); + when(commonNurseServiceImpl.savePrescriptionDetailsCovid19(any(), any(), any(), any(), any(), any(), any(), + any(), anyString())).thenReturn(7L); + + assertThrows(RuntimeException.class, () -> service.saveDoctorData(json(doctorRequest(false)), "auth")); + } + + @Test + void updateCovid19DoctorData_recordsTheDoctorDiagnosisForANonSpecialist() throws Exception { + stubSuccessfulDoctorUpdate(); + + assertEquals(1L, service.updateCovid19DoctorData(json(doctorRequest(false)), "auth")); + + ArgumentCaptor updated = ArgumentCaptor.forClass(PrescriptionDetail.class); + verify(commonNurseServiceImpl).updatePrescription(updated.capture()); + assertEquals("Covid-19", updated.getValue().getDiagnosisProvided()); + } + + @Test + void updateCovid19DoctorData_recordsTheSpecialistInstructionForASpecialist() throws Exception { + stubSuccessfulDoctorUpdate(); + + assertEquals(1L, service.updateCovid19DoctorData(json(doctorRequest(true)), "auth")); + + ArgumentCaptor updated = ArgumentCaptor.forClass(PrescriptionDetail.class); + verify(commonNurseServiceImpl).updatePrescription(updated.capture()); + assertEquals("Isolate", updated.getValue().getInstruction()); + } + + @Test + void updateCovid19DoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + stubSuccessfulDoctorUpdate(); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(0); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.updateCovid19DoctorData(json(doctorRequest(false)), "auth")); + assertTrue(thrown.getMessage().contains("Beneficiary status update failed")); + } + + @Test + void updateCovid19DoctorData_failsWhenASectionCouldNotBeUpdated() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(0); + + assertThrows(RuntimeException.class, + () -> service.updateCovid19DoctorData(json(doctorRequest(false)), "auth")); + } + + private void stubSuccessfulDoctorUpdate() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerImplTest.java b/src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerImplTest.java new file mode 100644 index 00000000..482b415e --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerImplTest.java @@ -0,0 +1,208 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.dataSyncActivity; + +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.mockConstruction; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedConstruction; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import com.iemr.mmu.data.syncActivity_syncLayer.SyncDownloadMaster; +import com.iemr.mmu.data.syncActivity_syncLayer.TempVan; +import com.iemr.mmu.repo.syncActivity_syncLayer.SyncDownloadMasterRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.TempVanRepo; +import com.iemr.mmu.utils.CookieUtil; + +class DownloadDataFromServerImplTest { + + @Mock + private SyncDownloadMasterRepo syncDownloadMasterRepo; + @Mock + private DataSyncRepository dataSyncRepository; + @Mock + private TempVanRepo tempVanRepo; + @Mock + private CookieUtil cookieUtil; + + @InjectMocks + private DownloadDataFromServerImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(service, "dataSyncDownloadUrl", "http://central/master"); + ReflectionTestUtils.setField(service, "benGenUrlCentral", "http://central/benGen"); + ReflectionTestUtils.setField(service, "benImportUrlLocal", "http://local/benImport"); + // The download counters are static, so each test starts from a settled state. + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "progressCounter", 0); + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "totalCounter", 0); + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "failedCounter", 0); + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "failedMasters", new StringBuilder()); + } + + private ArrayList oneMasterTable() { + SyncDownloadMaster master = new SyncDownloadMaster(); + master.setDownloadMasterTableID(1); + master.setSchemaName("db_iemr"); + master.setTableName("m_gender"); + master.setVanColumnName("GenderID,GenderName"); + ArrayList masters = new ArrayList<>(); + masters.add(master); + return masters; + } + + private MockedConstruction centralAnswering(String body) { + return mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(body, HttpStatus.OK))); + } + + @Test + @DisplayName("a master download is accepted and the progress counters are reset") + void downloadMasterDataFromServer_acceptsTheRequestAndResetsTheProgressCounters() throws Exception { + when(syncDownloadMasterRepo.getDownloadTables()).thenReturn(oneMasterTable()); + + assertEquals(" Master download started ", service.downloadMasterDataFromServer("auth", "token", 1, 2)); + + // The download itself runs on its own thread pool; what the caller sees is the + // reset progress counters. + assertEquals(1, ReflectionTestUtils.getField(DownloadDataFromServerImpl.class, "totalCounter")); + assertEquals(0, ReflectionTestUtils.getField(DownloadDataFromServerImpl.class, "failedCounter")); + } + + @Test + @DisplayName("a second download request while one is running reports that it is in progress") + void downloadMasterDataFromServer_reportsADownloadThatIsStillRunning() throws Exception { + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "totalCounter", 5); + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "progressCounter", 1); + + assertEquals("inProgress", service.downloadMasterDataFromServer("auth", "token", 1, 2)); + } + + @Test + @DisplayName("the download progress is reported as a percentage") + void getDownloadStatus_reportsTheProgressAsAPercentage() { + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "totalCounter", 4); + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "progressCounter", 1); + ReflectionTestUtils.setField(DownloadDataFromServerImpl.class, "failedCounter", 2); + + Map status = service.getDownloadStatus(); + + assertEquals(25.0, status.get("percentage")); + assertEquals(2, status.get("failedMasterCount")); + } + + @Test + @DisplayName("the van assigned to this installation is reported") + void getVanDetailsForMasterDownload_reportsTheOnlyVanOfThisInstallation() throws Exception { + TempVan van = new TempVan(); + van.setVanID(1); + when(tempVanRepo.getVanID()).thenReturn(new ArrayList<>(Collections.singletonList(van))); + + assertTrue(service.getVanDetailsForMasterDownload().contains("\"vanID\":1")); + } + + @Test + @DisplayName("more than one configured van is reported as a configuration error") + void getVanDetailsForMasterDownload_failsWhenMoreThanOneVanIsConfigured() { + when(tempVanRepo.getVanID()).thenReturn(new ArrayList<>(List.of(new TempVan(), new TempVan()))); + + Exception thrown = assertThrows(Exception.class, () -> service.getVanDetailsForMasterDownload()); + assertTrue(thrown.getMessage().contains("more than 1 van")); + } + + @Test + @DisplayName("a generated beneficiary id block is imported into the van") + void callCentralAPIToGenerateBenIDAndimportToLocal_importsTheGeneratedIdBlock() throws Exception { + String central = "{\"statusCode\":200,\"data\":[{\"beneficiaryID\":1}]}"; + String local = "{\"statusCode\":200,\"data\":{}}"; + + try (MockedConstruction rest = mockConstruction(RestTemplate.class, (mock, context) -> { + when(mock.exchange(eq("http://central/benGen"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(central, HttpStatus.OK)); + when(mock.exchange(eq("http://local/benImport"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(local, HttpStatus.OK)); + })) { + assertEquals(2, service.callCentralAPIToGenerateBenIDAndimportToLocal("{}", "auth", "serverAuth", "token")); + } + } + + @Test + @DisplayName("a block the van could not import stops short of a completed import") + void callCentralAPIToGenerateBenIDAndimportToLocal_stopsShortWhenTheVanCannotImport() throws Exception { + String central = "{\"statusCode\":200,\"data\":[{\"beneficiaryID\":1}]}"; + + try (MockedConstruction rest = mockConstruction(RestTemplate.class, (mock, context) -> { + when(mock.exchange(eq("http://central/benGen"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(central, HttpStatus.OK)); + when(mock.exchange(eq("http://local/benImport"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("{\"statusCode\":500}", HttpStatus.OK)); + })) { + assertEquals(1, service.callCentralAPIToGenerateBenIDAndimportToLocal("{}", "auth", "serverAuth", "token")); + } + } + + @Test + @DisplayName("a central server that generates no ids leaves nothing to import") + void callCentralAPIToGenerateBenIDAndimportToLocal_leavesNothingToImportWhenNoIdWasGenerated() throws Exception { + try (MockedConstruction rest = centralAnswering("{\"statusCode\":500}")) { + assertEquals(0, service.callCentralAPIToGenerateBenIDAndimportToLocal("{}", "auth", "serverAuth", "token")); + } + } + + @Test + @DisplayName("a central server that cannot be reached is reported to the caller") + void callCentralAPIToGenerateBenIDAndimportToLocal_reportsACentralServerThatCannotBeReached() { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenThrow(new RuntimeException("connection refused")))) { + + Exception thrown = assertThrows(Exception.class, () -> service + .callCentralAPIToGenerateBenIDAndimportToLocal("{}", "auth", "serverAuth", "token")); + assertTrue(thrown.getMessage().contains("Error while generating")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerTransactionalImplTest.java b/src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerTransactionalImplTest.java new file mode 100644 index 00000000..26840ce9 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/dataSyncActivity/DownloadDataFromServerTransactionalImplTest.java @@ -0,0 +1,169 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.dataSyncActivity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedConstruction; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import com.iemr.mmu.repo.login.MasterVanRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.IndentIssueRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.IndentOrderRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.IndentRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.ItemStockEntryRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.StockTransferRepo; +import com.iemr.mmu.utils.CookieUtil; + +class DownloadDataFromServerTransactionalImplTest { + + @Mock + private MasterVanRepo masterVanRepo; + @Mock + private IndentRepo indentRepo; + @Mock + private IndentOrderRepo indentOrderRepo; + @Mock + private IndentIssueRepo indentIssueRepo; + @Mock + private StockTransferRepo stockTransferRepo; + @Mock + private ItemStockEntryRepo itemStockEntryRepo; + @Mock + private CookieUtil cookieUtil; + + @InjectMocks + private DownloadDataFromServerTransactionalImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(service, "dataSyncTransactionDownloadUrl", "http://central/download"); + ReflectionTestUtils.setField(service, "dataSyncProcessedFlagUpdate", "http://central/flag"); + } + + /** The central server's download payload plus its acknowledgement of the flag update. */ + private MockedConstruction centralAnswering(String downloadBody) { + return mockConstruction(RestTemplate.class, (mock, context) -> { + when(mock.exchange(eq("http://central/download"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(downloadBody, HttpStatus.OK)); + when(mock.exchange(eq("http://central/flag"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("{\"statusCode\":200,\"data\":{}}", HttpStatus.OK)); + }); + } + + @Test + @DisplayName("an empty download leaves the van's stock tables untouched") + void downloadTransactionalData_leavesTheStockTablesUntouchedWhenThereIsNothingToDownload() throws Exception { + when(masterVanRepo.getFacilityID(1)).thenReturn(5); + + try (MockedConstruction rest = centralAnswering("{\"data\":[]}")) { + assertEquals(1, service.downloadTransactionalData(1, "auth", "token")); + } + verify(indentRepo, never()).saveAll(any()); + } + + @Test + @DisplayName("a downloaded indent is stored against the van's own row when one already exists") + void downloadTransactionalData_reusesTheVanRowOfAnAlreadyDownloadedIndent() throws Exception { + when(masterVanRepo.getFacilityID(1)).thenReturn(5); + when(indentRepo.searchBySyncFacilityIDAndVanSerialNo(anyInt(), anyLong())).thenReturn(9L); + when(indentIssueRepo.searchBySyncFacilityIDAndVanSerialNo(anyInt(), anyLong())).thenReturn(null); + when(itemStockEntryRepo.searchBySyncFacilityIDAndVanSerialNo(anyInt(), anyLong())).thenReturn(null); + + String payload = "{\"data\":[{\"indentID\":1,\"indentIssueID\":1,\"itemStockEntryID\":1," + + "\"syncFacilityID\":5,\"vanSerialNo\":2}]}"; + + try (MockedConstruction rest = centralAnswering(payload)) { + assertEquals(1, service.downloadTransactionalData(1, "auth", "token")); + } + verify(indentRepo).saveAll(any()); + verify(indentIssueRepo).saveAll(any()); + verify(itemStockEntryRepo).saveAll(any()); + } + + @Test + @DisplayName("a van without a facility mapping cannot download anything") + void downloadTransactionalData_failsForAVanWithoutAFacilityMapping() { + when(masterVanRepo.getFacilityID(1)).thenReturn(null); + + Exception thrown = assertThrows(Exception.class, () -> service.downloadTransactionalData(1, "auth", "token")); + assertEquals("Facility mapping for this van is either missing/wrong...", thrown.getMessage()); + } + + @Test + @DisplayName("a rejected flag update still leaves the downloaded rows stored") + void downloadTransactionalData_storesTheRowsEvenWhenTheFlagUpdateIsRejected() throws Exception { + when(masterVanRepo.getFacilityID(1)).thenReturn(5); + + String payload = "{\"data\":[{\"indentID\":1,\"syncFacilityID\":5,\"vanSerialNo\":2}]}"; + + try (MockedConstruction rest = mockConstruction(RestTemplate.class, (mock, context) -> { + when(mock.exchange(eq("http://central/download"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(payload, HttpStatus.OK)); + when(mock.exchange(eq("http://central/flag"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("{\"statusCode\":500}", HttpStatus.OK)); + })) { + assertEquals(1, service.downloadTransactionalData(1, "auth", "token")); + } + verify(indentRepo).saveAll(any()); + } + + @Test + @DisplayName("a flag update with no body still leaves the downloaded rows stored") + void downloadTransactionalData_storesTheRowsEvenWhenTheFlagUpdateHasNoBody() throws Exception { + when(masterVanRepo.getFacilityID(1)).thenReturn(5); + + String payload = "{\"data\":[{\"indentID\":1,\"syncFacilityID\":5,\"vanSerialNo\":2}]}"; + + try (MockedConstruction rest = mockConstruction(RestTemplate.class, (mock, context) -> { + when(mock.exchange(eq("http://central/download"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(payload, HttpStatus.OK)); + when(mock.exchange(eq("http://central/flag"), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.OK)); + })) { + assertEquals(1, service.downloadTransactionalData(1, "auth", "token")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/dataSyncActivity/UploadDataToServerImplTest.java b/src/test/java/com/iemr/mmu/service/dataSyncActivity/UploadDataToServerImplTest.java new file mode 100644 index 00000000..9764af64 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/dataSyncActivity/UploadDataToServerImplTest.java @@ -0,0 +1,405 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.dataSyncActivity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +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.mockConstruction; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedConstruction; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; + +import com.iemr.mmu.data.syncActivity_syncLayer.DataSyncGroups; +import com.iemr.mmu.data.syncActivity_syncLayer.SyncUtilityClass; +import com.iemr.mmu.repo.syncActivity_syncLayer.DataSyncGroupsRepo; +import com.iemr.mmu.repo.syncActivity_syncLayer.SyncUtilityClassRepo; +import com.iemr.mmu.repo.login.MasterVanRepo; +import com.iemr.mmu.utils.CookieUtil; + +class UploadDataToServerImplTest { + + @Mock + private DataSyncRepository dataSyncRepository; + @Mock + private DataSyncGroupsRepo dataSyncGroupsRepo; + @Mock + private MasterVanRepo masterVanRepo; + @Mock + private SyncUtilityClassRepo syncutilityClassRepo; + @Mock + private CookieUtil cookieUtil; + + @InjectMocks + private UploadDataToServerImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(service, "dataSyncUploadUrl", "http://central/sync"); + ReflectionTestUtils.setField(service, "BATCH_SIZE", 2); + } + + private DataSyncGroups group() { + DataSyncGroups group = new DataSyncGroups(); + group.setSyncTableGroupID(1); + group.setSyncTableGroupName("Beneficiary"); + return group; + } + + private SyncUtilityClass table(String tableName) { + SyncUtilityClass table = new SyncUtilityClass(); + table.setSchemaName("db_iemr"); + table.setTableName(tableName); + table.setVanColumnName("VanSerialNo,BeneficiaryRegID"); + table.setServerColumnName("VanSerialNo,BeneficiaryRegID"); + table.setVanAutoIncColumnName("VanSerialNo"); + return table; + } + + private List> rows(int count) { + List> rows = new ArrayList<>(); + for (int i = 1; i <= count; i++) { + Map row = new HashMap<>(); + row.put("VanSerialNo", i); + rows.add(row); + } + return rows; + } + + /** The central server's per-record acknowledgement for a batch of van serial numbers. */ + private String recordsResponse(boolean... outcomes) { + StringBuilder records = new StringBuilder(); + for (int i = 0; i < outcomes.length; i++) { + if (i > 0) { + records.append(","); + } + records.append("{\"vanSerialNo\":\"").append(i + 1).append("\",\"success\":").append(outcomes[i]) + .append(outcomes[i] ? "" : ",\"reason\":\"duplicate row\"").append("}"); + } + return "{\"statusCode\":200,\"errorMessage\":\"Success\",\"data\":{\"records\":[" + records + "]}}"; + } + + /** A server that acknowledges every row of whatever batch it is sent. */ + private MockedConstruction serverAcknowledgingEveryRow() { + return mockConstruction(RestTemplate.class, (mock, context) -> when( + mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))).thenAnswer(invocation -> { + org.springframework.http.HttpEntity request = invocation.getArgument(2); + com.google.gson.JsonArray syncData = com.google.gson.JsonParser + .parseString(String.valueOf(request.getBody())).getAsJsonObject() + .getAsJsonArray("syncData"); + boolean[] outcomes = new boolean[syncData.size()]; + java.util.Arrays.fill(outcomes, true); + return new ResponseEntity<>(recordsResponse(outcomes), HttpStatus.OK); + })); + } + + private MockedConstruction serverAnswering(String body) { + return mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(body, HttpStatus.OK))); + } + + @Nested + @DisplayName("uploading a van's data") + class Upload { + + @Test + void getDataToSyncToServer_reportsThatThereWasNothingToSync() throws Exception { + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(new ArrayList<>(List.of(group()))); + when(syncutilityClassRepo.findBySyncTableGroupIDAndDeletedOrderBySyncTableDetailID(1, false)) + .thenReturn(List.of(table("i_beneficiary"))); + when(dataSyncRepository.getDataForGivenSchemaAndTable(anyString(), anyString(), anyString())) + .thenReturn(new ArrayList<>()); + + assertEquals("No data to sync", service.getDataToSyncToServer(1, "nurse", "auth", "token")); + } + + @Test + void getDataToSyncToServer_reportsACompletelySuccessfulSync() throws Exception { + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(new ArrayList<>(List.of(group()))); + when(syncutilityClassRepo.findBySyncTableGroupIDAndDeletedOrderBySyncTableDetailID(1, false)) + .thenReturn(List.of(table("i_beneficiary"))); + when(dataSyncRepository.getDataForGivenSchemaAndTable(anyString(), anyString(), anyString())) + .thenReturn(rows(2)); + + try (MockedConstruction rest = serverAnswering(recordsResponse(true, true))) { + String response = service.getDataToSyncToServer(1, "nurse", "auth", "token"); + + assertTrue(response.contains("Data sync completed successfully")); + assertTrue(response.contains("\"status\" : \"completed\"")); + } + verify(dataSyncRepository).updateProcessedFlagInVan(eq("db_iemr"), eq("i_beneficiary"), any(), + eq("VanSerialNo"), eq("nurse"), eq("P"), eq("null")); + } + + @Test + void getDataToSyncToServer_reportsAPartiallySuccessfulTable() throws Exception { + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(new ArrayList<>(List.of(group()))); + when(syncutilityClassRepo.findBySyncTableGroupIDAndDeletedOrderBySyncTableDetailID(1, false)) + .thenReturn(List.of(table("i_beneficiary"))); + when(dataSyncRepository.getDataForGivenSchemaAndTable(anyString(), anyString(), anyString())) + .thenReturn(rows(2)); + + try (MockedConstruction rest = serverAnswering(recordsResponse(true, false))) { + String response = service.getDataToSyncToServer(1, "nurse", "auth", "token"); + + assertTrue(response.contains("\"status\" : \"partial\""), response); + assertTrue(response.contains("\"failedRecords\" : 1"), response); + } + verify(dataSyncRepository).updateProcessedFlagInVan(anyString(), anyString(), any(), anyString(), + anyString(), eq("F"), eq("duplicate row")); + } + + @Test + void getDataToSyncToServer_reportsATableThatFailedEntirely() throws Exception { + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(new ArrayList<>(List.of(group()))); + when(syncutilityClassRepo.findBySyncTableGroupIDAndDeletedOrderBySyncTableDetailID(1, false)) + .thenReturn(List.of(table("i_beneficiary"))); + when(dataSyncRepository.getDataForGivenSchemaAndTable(anyString(), anyString(), anyString())) + .thenReturn(rows(2)); + + try (MockedConstruction rest = serverAnswering(recordsResponse(false, false))) { + String response = service.getDataToSyncToServer(1, "nurse", "auth", "token"); + + assertTrue(response.contains("Data sync completed with failures"), response); + assertTrue(response.contains("\"status\" : \"failed\""), response); + } + } + + @Test + void getDataToSyncToServer_splitsALargeTableIntoBatchesAndARemainder() throws Exception { + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(new ArrayList<>(List.of(group()))); + when(syncutilityClassRepo.findBySyncTableGroupIDAndDeletedOrderBySyncTableDetailID(1, false)) + .thenReturn(List.of(table("i_beneficiary"))); + when(dataSyncRepository.getDataForGivenSchemaAndTable(anyString(), anyString(), anyString())) + .thenReturn(rows(3)); + + try (MockedConstruction rest = serverAcknowledgingEveryRow()) { + String response = service.getDataToSyncToServer(1, "nurse", "auth", "token"); + + assertTrue(response.contains("Data sync completed successfully"), response); + assertTrue(response.contains("\"totalRecords\" : 3"), response); + } + } + + @Test + void getDataToSyncToServer_stopsTheTableWhenTheServerReportsAnError() throws Exception { + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(new ArrayList<>(List.of(group()))); + when(syncutilityClassRepo.findBySyncTableGroupIDAndDeletedOrderBySyncTableDetailID(1, false)) + .thenReturn(List.of(table("i_beneficiary"))); + when(dataSyncRepository.getDataForGivenSchemaAndTable(anyString(), anyString(), anyString())) + .thenReturn(rows(2)); + + String error = "{\"statusCode\":500,\"errorMessage\":\"Central server unavailable\"}"; + try (MockedConstruction rest = serverAnswering(error)) { + String response = service.getDataToSyncToServer(1, "nurse", "auth", "token"); + + assertTrue(response.contains("Data sync completed with failures"), response); + assertTrue(response.contains("\"status\" : \"failed\""), response); + } + verify(dataSyncRepository).updateProcessedFlagInVan(anyString(), anyString(), any(), anyString(), + anyString(), eq("F"), eq("Central server unavailable")); + } + } + + @Nested + @DisplayName("sending one batch to the central server") + class SendBatch { + + @Test + void syncDataToServer_reportsSuccessForEveryAcknowledgedRecord() throws Exception { + when(masterVanRepo.getFacilityID(1)).thenReturn(5); + + try (MockedConstruction rest = serverAnswering(recordsResponse(true, true))) { + Map result = service.syncDataToServer(1, "db_iemr", "i_beneficiary", "VanSerialNo", + "VanSerialNo", rows(2), "nurse", "auth", "token"); + + assertEquals("Data successfully synced", result.get("status")); + assertEquals(2, result.get("successCount")); + assertEquals(0, result.get("failCount")); + } + } + + @Test + void syncDataToServer_reportsAPartialSuccess() throws Exception { + try (MockedConstruction rest = serverAnswering(recordsResponse(true, false))) { + assertEquals("Partial success", service.syncDataToServer(1, "db_iemr", "i_beneficiary", + "VanSerialNo", "VanSerialNo", rows(2), "nurse", "auth", "token").get("status")); + } + } + + @Test + void syncDataToServer_acceptsTheBeneficiaryIdMappingAcknowledgement() throws Exception { + String body = "{\"statusCode\":200,\"errorMessage\":\"Success\"," + + "\"data\":{\"response\":\"Data sync success\"}}"; + + try (MockedConstruction rest = serverAnswering(body)) { + Map result = service.syncDataToServer(1, "db_iemr", "m_beneficiaryregidmapping", + "VanSerialNo", "VanSerialNo", rows(2), "nurse", "auth", "token"); + + assertEquals("Data successfully synced", result.get("status")); + assertEquals(2, result.get("successCount")); + } + } + + @Test + void syncDataToServer_reportsAFailedBeneficiaryIdMapping() throws Exception { + String body = "{\"statusCode\":200,\"errorMessage\":\"Success\",\"data\":{\"response\":\"rejected\"}}"; + + try (MockedConstruction rest = serverAnswering(body)) { + Map result = service.syncDataToServer(1, "db_iemr", "m_beneficiaryregidmapping", + "VanSerialNo", "VanSerialNo", rows(2), "nurse", "auth", "token"); + + assertEquals("Sync failed", result.get("status")); + assertEquals(2, result.get("failCount")); + } + } + + @Test + void syncDataToServer_reportsAnUnparseableServerResponse() throws Exception { + try (MockedConstruction rest = serverAnswering("not json")) { + Map result = service.syncDataToServer(1, "db_iemr", "i_beneficiary", "VanSerialNo", + "VanSerialNo", rows(2), "nurse", "auth", "token"); + + assertEquals("Sync failed", result.get("status")); + } + verify(dataSyncRepository).updateProcessedFlagInVan(anyString(), anyString(), any(), anyString(), + anyString(), eq("F"), eq("Invalid server response")); + } + + @Test + void syncDataToServer_reportsAnEmptyServerResponse() throws Exception { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.OK)))) { + + assertEquals("Sync failed", service.syncDataToServer(1, "db_iemr", "i_beneficiary", "VanSerialNo", + "VanSerialNo", rows(2), "nurse", "auth", "token").get("status")); + } + verify(dataSyncRepository).updateProcessedFlagInVan(anyString(), anyString(), any(), anyString(), + anyString(), eq("F"), eq("Empty server response")); + } + + @Test + void syncDataToServer_reportsAServerThatCannotBeReached() throws Exception { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenThrow(new ResourceAccessException("connection refused")))) { + + assertEquals("Sync failed", service.syncDataToServer(1, "db_iemr", "i_beneficiary", "VanSerialNo", + "VanSerialNo", rows(2), "nurse", "auth", "token").get("status")); + } + } + + @Test + void syncDataToServer_reportsAnUnexpectedFailure() throws Exception { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenThrow(new IllegalStateException("boom")))) { + + assertEquals("Sync failed", service.syncDataToServer(1, "db_iemr", "i_beneficiary", "VanSerialNo", + "VanSerialNo", rows(2), "nurse", "auth", "token").get("status")); + } + } + } + + @Nested + @DisplayName("sync helpers") + class Helpers { + + @Test + void getVanAndServerColumnList_readsTheTablesOfAGroup() throws Exception { + List tables = List.of(table("i_beneficiary")); + when(syncutilityClassRepo.findBySyncTableGroupIDAndDeletedOrderBySyncTableDetailID(1, false)) + .thenReturn(tables); + + assertEquals(tables, service.getVanAndServerColumnList(1)); + } + + @Test + void getVanSerialNoListForSyncedData_joinsTheVanSerialNumbersWithCommas() throws Exception { + assertEquals("1,2,3", service.getVanSerialNoListForSyncedData("VanSerialNo", rows(3)).toString()); + assertEquals("", service.getVanSerialNoListForSyncedData("VanSerialNo", new ArrayList<>()).toString()); + } + + @Test + void getDataSyncGroupDetails_serialisesTheConfiguredGroups() { + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(new ArrayList<>(List.of(group()))); + assertTrue(service.getDataSyncGroupDetails().contains("Beneficiary")); + + when(dataSyncGroupsRepo.findByDeleted(false)).thenReturn(null); + assertNull(service.getDataSyncGroupDetails()); + } + + @Test + void syncResult_carriesTheOutcomeOfOneTable() { + SyncResult result = new SyncResult("db_iemr", "i_beneficiary", "1", "nurse", false, "duplicate row"); + + assertEquals("db_iemr", result.getSchemaName()); + assertEquals("i_beneficiary", result.getTableName()); + assertEquals("1", result.getVanSerialNo()); + assertEquals("nurse", result.getSyncedBy()); + assertEquals(false, result.isSuccess()); + assertEquals("duplicate row", result.getReason()); + + result.setSuccess(true); + assertEquals(true, result.isSuccess()); + + SyncResult same = new SyncResult("db_iemr", "i_beneficiary", "1", "nurse", true, "duplicate row"); + assertEquals(same, result); + assertEquals(same.hashCode(), result.hashCode()); + assertTrue(result.toString().contains("i_beneficiary")); + + result.setSchemaName("db_identity"); + result.setTableName("m_beneficiaryregidmapping"); + result.setVanSerialNo("2"); + result.setSyncedBy("doctor"); + result.setReason(null); + org.junit.jupiter.api.Assertions.assertNotEquals(same, result); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/DataSyncRepositoryCentralQueryTest.java b/src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/DataSyncRepositoryCentralQueryTest.java new file mode 100644 index 00000000..16c4b099 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/DataSyncRepositoryCentralQueryTest.java @@ -0,0 +1,391 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.dataSyncLayerCentral; + +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.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.mmu.data.syncActivity_syncLayer.SyncUploadDataDigester; + +class DataSyncRepositoryCentralQueryTest { + + private DataSource dataSource; + + @BeforeEach + void setUp() { + dataSource = Mockito.mock(DataSource.class); + } + + private List> oneRow() { + Map row = new HashMap<>(); + row.put("VanSerialNo", 1); + return new ArrayList<>(Collections.singletonList(row)); + } + + /** A JdbcTemplate that answers every read with the given rows. */ + private MockedConstruction databaseReturning(List> rows) { + return mockConstruction(JdbcTemplate.class, (mock, context) -> { + when(mock.queryForList(anyString(), org.mockito.ArgumentMatchers.any(Object[].class))).thenReturn(rows); + when(mock.queryForList(anyString())).thenReturn(rows); + + when(mock.batchUpdate(anyString(), any(List.class))).thenReturn(new int[] { 1 }); + }); + } + + private MockedConstruction databaseFailing() { + return mockConstruction(JdbcTemplate.class, (mock, context) -> { + when(mock.queryForList(anyString(), org.mockito.ArgumentMatchers.any(Object[].class))) + .thenThrow(new RuntimeException("db down")); + when(mock.queryForList(anyString())).thenThrow(new RuntimeException("db down")); + + when(mock.batchUpdate(anyString(), any(List.class))).thenThrow(new RuntimeException("db down")); + }); + } + + @Nested + @DisplayName("DataSyncRepositoryCentral") + class Central { + + private DataSyncRepositoryCentral repository; + + @BeforeEach + void setUp() { + repository = new DataSyncRepositoryCentral(); + ReflectionTestUtils.setField(repository, "dataSource", dataSource); + } + + @Test + void checkRecordIsAlreadyPresentOrNot_matchesAClinicalRowOnItsVan() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertEquals(1, repository.checkRecordIsAlreadyPresentOrNot("db_iemr", "t_benvisitdetail", "1", "2", + "VanSerialNo", 0)); + + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(db.constructed().get(0)).queryForList(query.capture(), org.mockito.ArgumentMatchers.any(Object[].class)); + assertTrue(query.getValue().endsWith("AND VanID = ?"), query.getValue()); + } + } + + @Test + void checkRecordIsAlreadyPresentOrNot_matchesAStockRowOnItsFacility() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertEquals(1, repository.checkRecordIsAlreadyPresentOrNot("db_iemr", "t_indent", "1", "2", + "VanSerialNo", 5)); + + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(db.constructed().get(0)).queryForList(query.capture(), org.mockito.ArgumentMatchers.any(Object[].class)); + assertTrue(query.getValue().endsWith("AND SyncFacilityID = ?"), query.getValue()); + } + } + + @Test + void checkRecordIsAlreadyPresentOrNot_reportsARowThatIsNotOnTheServerYet() { + try (MockedConstruction db = databaseReturning(new ArrayList<>())) { + assertEquals(0, repository.checkRecordIsAlreadyPresentOrNot("db_iemr", "t_benvisitdetail", "1", "2", + "VanSerialNo", 0)); + } + } + + @Test + void checkRecordIsAlreadyPresentOrNot_rejectsAnIdentifierThatIsNotWhitelisted() { + assertThrows(IllegalArgumentException.class, () -> repository + .checkRecordIsAlreadyPresentOrNot("evil_schema", "t_benvisitdetail", "1", "2", "VanSerialNo", 0)); + assertThrows(IllegalArgumentException.class, () -> repository + .checkRecordIsAlreadyPresentOrNot("db_iemr", "evil_table", "1", "2", "VanSerialNo", 0)); + assertThrows(IllegalArgumentException.class, () -> repository + .checkRecordIsAlreadyPresentOrNot("db_iemr", "t_benvisitdetail", "1", "2", "van;drop", 0)); + } + + @Test + void checkRecordIsAlreadyPresentOrNot_reportsADatabaseThatCannotBeRead() { + try (MockedConstruction db = databaseFailing()) { + RuntimeException thrown = assertThrows(RuntimeException.class, () -> repository + .checkRecordIsAlreadyPresentOrNot("db_iemr", "t_benvisitdetail", "1", "2", "VanSerialNo", 0)); + assertTrue(thrown.getMessage().contains("Failed to check record existence")); + } + } + + @Test + void syncDataToCentralDB_appliesTheBatchAndReportsTheRowCounts() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertArrayEquals(new int[] { 1 }, repository.syncDataToCentralDB("db_iemr", "t_benvisitdetail", + "VanSerialNo", "INSERT INTO db_iemr.t_benvisitdetail(VanSerialNo) VALUES (?)", + new ArrayList<>(Collections.singletonList(new Object[] { 1 })))); + } + } + + @Test + void syncDataToCentralDB_reportsABatchTheDatabaseRejected() { + try (MockedConstruction db = databaseFailing()) { + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> repository.syncDataToCentralDB("db_iemr", "t_benvisitdetail", "VanSerialNo", "INSERT", + new ArrayList<>(Collections.singletonList(new Object[] { 1 })))); + assertTrue(thrown.getMessage().contains("Batch sync failed")); + } + } + + @Test + void getMasterDataFromTable_readsEveryRowOfAnAllVanMaster() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertEquals(1, repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo,VanID", "A", + null, 1, 2).size()); + } + } + + @Test + void getMasterDataFromTable_narrowsAVanMasterToItsVan() { + try (MockedConstruction db = databaseReturning(oneRow())) { + repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", "V", null, 1, 2); + + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(db.constructed().get(0)).queryForList(query.capture(), org.mockito.ArgumentMatchers.any(Object[].class)); + assertTrue(query.getValue().endsWith("WHERE VanID = ?"), query.getValue()); + } + } + + @Test + void getMasterDataFromTable_narrowsAProviderMasterToItsProvider() { + try (MockedConstruction db = databaseReturning(oneRow())) { + repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", "P", null, 1, 2); + + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(db.constructed().get(0)).queryForList(query.capture(), org.mockito.ArgumentMatchers.any(Object[].class)); + assertTrue(query.getValue().endsWith("WHERE ProviderServiceMapID = ?"), query.getValue()); + } + } + + @Test + void getMasterDataFromTable_readsOnlyWhatChangedSinceTheLastDownload() { + Timestamp lastDownload = Timestamp.valueOf("2024-01-01 00:00:00"); + + try (MockedConstruction db = databaseReturning(oneRow())) { + repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", "V", lastDownload, 1, 2); + repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", "P", lastDownload, 1, 2); + repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", "A", lastDownload, 1, 2); + + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(db.constructed().get(0), Mockito.times(3)).queryForList(query.capture(), + org.mockito.ArgumentMatchers.any(Object[].class)); + assertTrue(query.getAllValues().get(2).endsWith("WHERE LastModDate >= ?"), query.getValue()); + } + } + + @Test + void getMasterDataFromTable_readsTheWholeTableWhenNoMasterTypeWasGiven() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertEquals(1, repository + .getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", null, null, 1, 2).size()); + } + } + + @Test + void getMasterDataFromTable_rejectsADateFormattedColumn() { + // The column list is split on commas first, so a date_format column is never + // seen whole: with its format argument the format arrives as a column name of + // its own, and without one the closing bracket fails the identifier check. + assertThrows(IllegalArgumentException.class, () -> repository.getMasterDataFromTable("db_iemr", + "t_benvisitdetail", "date_format(CreatedDate,\'%Y\')", "A", null, 1, 2)); + assertThrows(IllegalArgumentException.class, () -> repository.getMasterDataFromTable("db_iemr", + "t_benvisitdetail", "date_format(CreatedDate)", "A", null, 1, 2)); + } + + @Test + void getMasterDataFromTable_rejectsAnIdentifierThatIsNotWhitelisted() { + assertThrows(IllegalArgumentException.class, + () -> repository.getMasterDataFromTable("evil", "t_benvisitdetail", "VanSerialNo", "A", null, 1, 2)); + assertThrows(IllegalArgumentException.class, + () -> repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo;drop", "A", null, 1, 2)); + assertThrows(IllegalArgumentException.class, + () -> repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", " ", "A", null, 1, 2)); + } + + @Test + void getMasterDataFromTable_reportsADatabaseThatCannotBeRead() { + try (MockedConstruction db = databaseFailing()) { + RuntimeException thrown = assertThrows(RuntimeException.class, () -> repository + .getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", "A", null, 1, 2)); + assertTrue(thrown.getMessage().contains("Failed to fetch master data")); + } + } + + private SyncUploadDataDigester digester(String schema, String table, String columns) { + SyncUploadDataDigester digester = new SyncUploadDataDigester(); + digester.setSchemaName(schema); + digester.setTableName(table); + digester.setServerColumns(columns); + return digester; + } + + @Test + void getBatchForBenDetails_readsOnePageOfBeneficiaryDetails() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertEquals(1, repository.getBatchForBenDetails( + digester("db_identity", "i_beneficiarydetails", "BeneficiaryDetailsId"), " WHERE VanID = 1 ", + 10, 0).size()); + } + } + + @Test + void getBatchForBenDetails_rejectsAnIdentifierThatIsNotWhitelisted() { + assertThrows(IllegalArgumentException.class, () -> repository + .getBatchForBenDetails(digester("evil", "i_beneficiarydetails", "Id"), " WHERE 1=1 ", 10, 0)); + } + + @Test + void getBatchForBenDetails_reportsADatabaseThatCannotBeRead() { + try (MockedConstruction db = databaseFailing()) { + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> repository.getBatchForBenDetails( + digester("db_identity", "i_beneficiarydetails", "BeneficiaryDetailsId"), + " WHERE VanID = 1 ", 10, 0)); + assertTrue(thrown.getMessage().contains("Failed to fetch batch data")); + } + } + } + + @Nested + @DisplayName("DataSyncRepositoryCentralDownload") + class Download { + + private DataSyncRepositoryCentralDownload repository; + + @BeforeEach + void setUp() { + repository = new DataSyncRepositoryCentralDownload(); + ReflectionTestUtils.setField(repository, "dataSource", dataSource); + } + + @Test + void checkRecordIsAlreadyPresentOrNot_matchesAClinicalRowOnItsVan() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertEquals(1, repository.checkRecordIsAlreadyPresentOrNot("db_iemr", "t_benvisitdetail", "1", "2", + "VanSerialNo", 0)); + + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(db.constructed().get(0)).queryForList(query.capture(), org.mockito.ArgumentMatchers.any(Object[].class)); + assertTrue(query.getValue().endsWith("VanID = ?"), query.getValue()); + } + } + + @Test + void checkRecordIsAlreadyPresentOrNot_matchesAStockRowOnItsFacility() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertEquals(1, repository.checkRecordIsAlreadyPresentOrNot("db_iemr", "t_indentissue", "1", "2", + "VanSerialNo", 5)); + + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(db.constructed().get(0)).queryForList(query.capture(), org.mockito.ArgumentMatchers.any(Object[].class)); + assertTrue(query.getValue().endsWith("SyncFacilityID = ?"), query.getValue()); + } + } + + @Test + void checkRecordIsAlreadyPresentOrNot_reportsARowThatIsNotOnTheServerYet() { + try (MockedConstruction db = databaseReturning(new ArrayList<>())) { + assertEquals(0, repository.checkRecordIsAlreadyPresentOrNot("db_iemr", "t_benvisitdetail", "1", "2", + "VanSerialNo", 0)); + } + } + + @Test + void syncDataToCentralDB_appliesAnInsertBatch() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertArrayEquals(new int[] { 1 }, repository.syncDataToCentralDB("db_iemr", "t_benvisitdetail", + "VanSerialNo", "INSERT INTO db_iemr.t_benvisitdetail(VanSerialNo) VALUES (?)", + new ArrayList<>(Collections.singletonList(new Object[] { 1 })))); + } + } + + @Test + void syncDataToCentralDB_appliesAnUpdateBatch() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertArrayEquals(new int[] { 1 }, + repository.syncDataToCentralDB("db_iemr", "t_benvisitdetail", "VanSerialNo,VanID", + "UPDATE db_iemr.t_benvisitdetail SET VanSerialNo = ?", + new ArrayList<>(Collections.singletonList(new Object[] { 1 })))); + } + } + + @Test + void syncDataToCentralDB_appliesAnUpdateBatchWithoutServerColumns() { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertArrayEquals(new int[] { 1 }, repository.syncDataToCentralDB("db_iemr", "t_benvisitdetail", null, + "UPDATE db_iemr.t_benvisitdetail SET VanSerialNo = ?", + new ArrayList<>(Collections.singletonList(new Object[] { 1 })))); + } + } + + @Test + void getMasterDataFromTable_readsEachMasterTypeWithAndWithoutALastDownloadDate() throws Exception { + Timestamp lastDownload = Timestamp.valueOf("2024-01-01 00:00:00"); + + try (MockedConstruction db = mockConstruction(JdbcTemplate.class, (mock, context) -> { + when(mock.queryForList(anyString())).thenReturn(oneRow()); + when(mock.queryForList(anyString(), org.mockito.ArgumentMatchers.any(Object[].class))) + .thenReturn(oneRow()); + })) { + for (String masterType : List.of("A", "V", "P")) { + assertEquals(1, repository + .getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", masterType, lastDownload, 1, 2) + .size()); + assertEquals(1, repository + .getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", masterType, null, 1, 2).size()); + } + } + } + + @Test + void getMasterDataFromTable_readsNothingWhenNoMasterTypeWasGiven() throws Exception { + try (MockedConstruction db = databaseReturning(oneRow())) { + assertTrue(repository.getMasterDataFromTable("db_iemr", "t_benvisitdetail", "VanSerialNo", null, null, 1, 2) + .isEmpty()); + } + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/GetDataFromVanAndSyncToDBImplTest.java b/src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/GetDataFromVanAndSyncToDBImplTest.java new file mode 100644 index 00000000..91278055 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/dataSyncLayerCentral/GetDataFromVanAndSyncToDBImplTest.java @@ -0,0 +1,336 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.dataSyncLayerCentral; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +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.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.iemr.mmu.data.syncActivity_syncLayer.SyncUploadDataDigester; + +class GetDataFromVanAndSyncToDBImplTest { + + @Mock + private DataSyncRepositoryCentral dataSyncRepositoryCentral; + + @InjectMocks + private GetDataFromVanAndSyncToDBImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + /** A one-row upload request for the given table. */ + private String request(String tableName, String rowJson) { + return "{\"schemaName\":\"db_iemr\",\"tableName\":\"" + tableName + "\"," + + "\"vanAutoIncColumnName\":\"VanSerialNo\",\"serverColumns\":\"VanSerialNo,VanID,BeneficiaryRegID\"," + + "\"syncedBy\":\"nurse\",\"facilityID\":5,\"syncData\":[" + rowJson + "]}"; + } + + private String row(String tableName) { + return "{\"tableName\":\"" + tableName + "\",\"VanSerialNo\":1,\"VanID\":2,\"BeneficiaryRegID\":3}"; + } + + private SyncUploadDataDigester digester(String tableName, List> syncData) { + SyncUploadDataDigester digester = new SyncUploadDataDigester(); + digester.setSchemaName("db_iemr"); + digester.setTableName(tableName); + digester.setVanAutoIncColumnName("VanSerialNo"); + digester.setServerColumns("VanSerialNo,VanID,BeneficiaryRegID"); + digester.setSyncedBy("nurse"); + digester.setSyncData(syncData); + return digester; + } + + @Nested + @DisplayName("syncing an uploaded batch") + class SyncBatch { + + @Test + void syncDataToServer_rejectsARequestWithoutATableName() throws Exception { + String request = "{\"schemaName\":\"db_iemr\",\"syncData\":[]}"; + + assertEquals("Error: Invalid sync request.", service.syncDataToServer(request, "auth")); + } + + @Test + void syncDataToServer_marksTheProvisionedBeneficiaryIdMappings() throws Exception { + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 1 }); + + String request = "{\"schemaName\":\"db_iemr\",\"tableName\":\"m_beneficiaryregidmapping\"," + + "\"serverColumns\":\"SyncedBy\",\"syncedBy\":\"nurse\"," + + "\"syncData\":[{\"BenRegId\":1,\"BeneficiaryID\":2,\"VanID\":3}]}"; + + assertEquals("Sync successful for m_beneficiaryregidmapping.", service.syncDataToServer(request, "auth")); + } + + @Test + void syncDataToServer_reportsAPartiallyProvisionedBeneficiaryIdMapping() throws Exception { + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[0]); + + String request = "{\"schemaName\":\"db_iemr\",\"tableName\":\"m_beneficiaryregidmapping\"," + + "\"serverColumns\":\"SyncedBy\",\"syncedBy\":\"nurse\"," + + "\"syncData\":[{\"BenRegId\":1,\"BeneficiaryID\":2,\"VanID\":3}]}"; + + assertEquals("Sync failed for m_beneficiaryregidmapping.", service.syncDataToServer(request, "auth")); + } + + @Test + void syncDataToServer_skipsIncompleteBeneficiaryIdMappings() throws Exception { + String request = "{\"schemaName\":\"db_iemr\",\"tableName\":\"m_beneficiaryregidmapping\"," + + "\"serverColumns\":\"SyncedBy\",\"syncedBy\":\"nurse\",\"syncData\":[{\"BenRegId\":1}]}"; + + assertEquals("Sync successful for m_beneficiaryregidmapping.", service.syncDataToServer(request, "auth")); + verify(dataSyncRepositoryCentral, org.mockito.Mockito.never()).syncDataToCentralDB(anyString(), + anyString(), any(), anyString(), any()); + } + + @Test + void syncDataToServer_reportsAFailedBeneficiaryIdMappingUpdate() throws Exception { + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenThrow(new RuntimeException("db down")); + + String request = "{\"schemaName\":\"db_iemr\",\"tableName\":\"m_beneficiaryregidmapping\"," + + "\"serverColumns\":\"SyncedBy\",\"syncedBy\":\"nurse\"," + + "\"syncData\":[{\"BenRegId\":1,\"BeneficiaryID\":2,\"VanID\":3}]}"; + + assertEquals("Sync failed for m_beneficiaryregidmapping.", service.syncDataToServer(request, "auth")); + } + + @Test + void syncDataToServer_insertsARowThatIsNotOnTheServerYet() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(0); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 1 }); + + String response = service.syncDataToServer(request("t_benvisitdetail", row("t_benvisitdetail")), "auth"); + + assertTrue(response.contains("\"success\":true"), response); + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(dataSyncRepositoryCentral).syncDataToCentralDB(anyString(), anyString(), any(), query.capture(), + any()); + assertTrue(query.getValue().startsWith("INSERT INTO db_iemr.t_benvisitdetail"), query.getValue()); + } + + @Test + void syncDataToServer_updatesARowThatIsAlreadyOnTheServer() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(1); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 1 }); + + String response = service.syncDataToServer(request("t_benvisitdetail", row("t_benvisitdetail")), "auth"); + + assertTrue(response.contains("\"success\":true"), response); + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(dataSyncRepositoryCentral).syncDataToCentralDB(anyString(), anyString(), any(), query.capture(), + any()); + assertTrue(query.getValue().contains("AND VanID = ?"), query.getValue()); + } + + @Test + void syncDataToServer_reportsARowTheServerRejected() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(0); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 0 }); + + String response = service.syncDataToServer(request("t_benvisitdetail", row("t_benvisitdetail")), "auth"); + + assertTrue(response.contains("Insert failed"), response); + } + + @Test + void syncDataToServer_reportsARowThatMatchedNothingOnUpdate() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(1); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 0 }); + + assertTrue(service.syncDataToServer(request("t_benvisitdetail", row("t_benvisitdetail")), "auth") + .contains("No matching row")); + } + + @Test + void syncDataToServer_reportsAnInsertThatFailedForTheWholeBatch() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(0); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenThrow(new RuntimeException("Duplicate entry '1' for key 'PRIMARY'")); + + assertTrue(service.syncDataToServer(request("t_benvisitdetail", row("t_benvisitdetail")), "auth") + .contains("INSERT: Duplicate key: PRIMARY")); + } + + @Test + void syncDataToServer_reportsAnUpdateThatFailedForTheWholeBatch() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(1); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenThrow(new RuntimeException("Database connection timeout")); + + assertTrue(service.syncDataToServer(request("t_benvisitdetail", row("t_benvisitdetail")), "auth") + .contains("UPDATE: Database connection timeout")); + } + + @Test + void syncDataToServer_reportsARowWhoseExistenceCouldNotBeChecked() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenThrow(new RuntimeException("db down")); + + assertTrue(service.syncDataToServer(request("t_benvisitdetail", row("t_benvisitdetail")), "auth") + .contains("Record check failed")); + } + + @Test + void syncDataToServer_fallsBackToAGenericSyncForATableOutsideTheKnownGroups() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(0); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 1 }); + + assertTrue(service.syncDataToServer(request("t_unknown_table", row("t_other_table")), "auth") + .contains("Data sync completed")); + } + + @Test + void syncDataToServer_readsTheDateFormattedColumnNamesTheVanSends() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(0); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 1 }); + + String rowWithFormattedDate = "{\"tableName\":\"t_benvisitdetail\",\"VanSerialNo\":1,\"VanID\":2," + + "\"date_format(BeneficiaryRegID,'%Y')\":3}"; + + assertTrue(service.syncDataToServer(request("t_benvisitdetail", rowWithFormattedDate), "auth") + .contains("\"success\":true")); + } + + @Test + void syncDataToServer_marksAStockRowAsProcessedForItsOwnFacility() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(1); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 1 }); + + String stockRow = "{\"tableName\":\"t_itemstockentry\",\"VanSerialNo\":1,\"VanID\":2," + + "\"FacilityID\":5,\"SyncFacilityID\":5}"; + + assertTrue(service.syncDataToServer(request("t_itemstockentry", stockRow), "auth") + .contains("\"success\":true")); + ArgumentCaptor query = ArgumentCaptor.forClass(String.class); + verify(dataSyncRepositoryCentral).syncDataToCentralDB(anyString(), anyString(), any(), query.capture(), + any()); + assertTrue(query.getValue().contains("AND SyncFacilityID = ?"), query.getValue()); + } + + @Test + void syncDataToServer_marksAnIndentRowAsProcessedForItsOwnFacility() throws Exception { + when(dataSyncRepositoryCentral.checkRecordIsAlreadyPresentOrNot(anyString(), anyString(), anyString(), + anyString(), anyString(), anyInt())).thenReturn(0); + when(dataSyncRepositoryCentral.syncDataToCentralDB(anyString(), anyString(), any(), anyString(), any())) + .thenReturn(new int[] { 1 }); + + for (String tableAndKey : List.of("t_indent:FromFacilityID", "t_indentorder:FromFacilityID", + "t_indentissue:ToFacilityID", "t_stocktransfer:TransferToFacilityID")) { + String table = tableAndKey.split(":")[0]; + String key = tableAndKey.split(":")[1]; + String stockRow = "{\"tableName\":\"" + table + "\",\"VanSerialNo\":1,\"VanID\":2,\"" + key + + "\":5}"; + + assertTrue(service.syncDataToServer(request(table, stockRow), "auth").contains("Data sync completed")); + } + } + } + + @Nested + @DisplayName("sync queries and helpers") + class QueriesAndHelpers { + + @Test + void getQueryToUpdateDataToServerDB_matchesOnTheVanForAClinicalTable() { + String query = service.getQueryToUpdateDataToServerDB("db_iemr", "VanSerialNo,VanID", "t_benvisitdetail"); + + assertTrue(query.startsWith("UPDATE db_iemr.t_benvisitdetail SET"), query); + assertTrue(query.contains("VanSerialNo = ?, VanID = ?"), query); + assertTrue(query.contains("AND VanID = ?"), query); + } + + @Test + void getQueryToUpdateDataToServerDB_matchesOnTheFacilityForAStockTable() { + assertTrue(service.getQueryToUpdateDataToServerDB("db_iemr", "VanSerialNo", "t_patientissue") + .contains("AND SyncFacilityID = ?")); + } + + @Test + void getQueryToUpdateDataToServerDB_toleratesATableWithoutColumns() { + assertTrue(service.getQueryToUpdateDataToServerDB("db_iemr", null, "t_benvisitdetail") + .contains("UPDATE db_iemr.t_benvisitdetail SET WHERE VanSerialNo = ?")); + } + + @Test + void update_I_BeneficiaryDetails_for_processed_in_batches_reportsTheBatchOutcome() { + when(dataSyncRepositoryCentral.getBatchForBenDetails(any(), anyString(), anyInt(), anyInt())) + .thenReturn(new ArrayList<>()); + + assertEquals("data sync passed", + service.update_I_BeneficiaryDetails_for_processed_in_batches( + digester("i_beneficiarydetails", new ArrayList<>()))); + } + + @Test + void update_I_BeneficiaryDetails_for_processed_in_batches_reportsAFailedRead() { + when(dataSyncRepositoryCentral.getBatchForBenDetails(any(), anyString(), anyInt(), anyInt())) + .thenThrow(new RuntimeException("db down")); + + assertTrue(service + .update_I_BeneficiaryDetails_for_processed_in_batches( + digester("i_beneficiarydetails", new ArrayList<>())) + .contains("Error fetching data")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/health/HealthServiceTest.java b/src/test/java/com/iemr/mmu/service/health/HealthServiceTest.java new file mode 100644 index 00000000..8d0ce598 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/health/HealthServiceTest.java @@ -0,0 +1,353 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.health; + +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.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.test.util.ReflectionTestUtils; + +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.HikariPoolMXBean; + +class HealthServiceTest { + + /** A data source whose health query returns a row, and whose diagnostic queries are quiet. */ + private DataSource healthyDataSource(int lockWaits, int slowQueries) throws SQLException { + DataSource dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenAnswer(invocation -> connection(lockWaits, slowQueries)); + return dataSource; + } + + private Connection connection(int lockWaits, int slowQueries) throws SQLException { + Connection connection = mock(Connection.class); + when(connection.prepareStatement(anyString())).thenAnswer(invocation -> { + String sql = invocation.getArgument(0); + if (sql.contains("health_check")) { + return statementReturning(1); + } + if (sql.contains("metadata lock")) { + return statementReturning(lockWaits); + } + return statementReturning(slowQueries); + }); + return connection; + } + + private PreparedStatement statementReturning(int count) throws SQLException { + PreparedStatement statement = mock(PreparedStatement.class); + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.next()).thenReturn(true); + when(resultSet.getInt(1)).thenReturn(count); + when(statement.executeQuery()).thenReturn(resultSet); + return statement; + } + + @SuppressWarnings("unchecked") + private RedisTemplate redisAnswering(String pong) { + RedisTemplate redisTemplate = mock(RedisTemplate.class); + when(redisTemplate.execute(any(RedisCallback.class))).thenAnswer(invocation -> { + RedisConnection connection = mock(RedisConnection.class); + when(connection.ping()).thenReturn(pong); + return ((RedisCallback) invocation.getArgument(0)).doInRedis(connection); + }); + return redisTemplate; + } + + @SuppressWarnings("unchecked") + private Map componentOf(Map health, String name) { + return ((Map>) health.get("components")).get(name); + } + + @Test + @DisplayName("both components report UP when the database and Redis answer") + void checkHealth_reportsEverythingUpWhenBothComponentsAnswer() throws Exception { + HealthService service = new HealthService(healthyDataSource(0, 0), redisAnswering("PONG")); + try { + Map health = service.checkHealth(); + + assertEquals("UP", health.get("status")); + assertEquals("UP", componentOf(health, "mysql").get("status")); + assertEquals("UP", componentOf(health, "redis").get("status")); + assertNotNull(health.get("timestamp")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("MySQL reports DOWN when the connection cannot be opened") + void checkHealth_reportsMysqlDownWhenTheConnectionFails() throws Exception { + DataSource dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenThrow(new SQLException("connection refused")); + + HealthService service = new HealthService(dataSource, redisAnswering("PONG")); + try { + Map health = service.checkHealth(); + + assertEquals("DOWN", health.get("status")); + assertEquals("DOWN", componentOf(health, "mysql").get("status")); + assertEquals("CRITICAL", componentOf(health, "mysql").get("severity")); + assertEquals("MySQL connection failed", componentOf(health, "mysql").get("error")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("MySQL reports DOWN when the health query returns no row") + void checkHealth_reportsMysqlDownWhenTheHealthQueryReturnsNothing() throws Exception { + Connection connection = mock(Connection.class); + PreparedStatement statement = mock(PreparedStatement.class); + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.next()).thenReturn(false); + when(statement.executeQuery()).thenReturn(resultSet); + when(connection.prepareStatement(anyString())).thenReturn(statement); + DataSource dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenReturn(connection); + + HealthService service = new HealthService(dataSource, redisAnswering("PONG")); + try { + Map health = service.checkHealth(); + + assertEquals("No result from health check query", componentOf(health, "mysql").get("error")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("MySQL reports DEGRADED when the database is waiting on locks") + void checkHealth_reportsMysqlDegradedWhenTheDatabaseIsWaitingOnLocks() throws Exception { + HealthService service = new HealthService(healthyDataSource(2, 0), redisAnswering("PONG")); + try { + Map health = service.checkHealth(); + + assertEquals("DEGRADED", health.get("status")); + assertEquals("DEGRADED", componentOf(health, "mysql").get("status")); + assertEquals("WARNING", componentOf(health, "mysql").get("severity")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("MySQL reports DEGRADED when too many slow queries are running") + void checkHealth_reportsMysqlDegradedWhenTooManySlowQueriesAreRunning() throws Exception { + HealthService service = new HealthService(healthyDataSource(0, 5), redisAnswering("PONG")); + try { + assertEquals("DEGRADED", service.checkHealth().get("status")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("the advanced diagnostics are only re-run once the throttle window passes") + void checkHealth_reusesTheCachedDiagnosticsWithinTheThrottleWindow() throws Exception { + HealthService service = new HealthService(healthyDataSource(2, 0), redisAnswering("PONG")); + try { + service.checkHealth(); + + assertEquals("DEGRADED", service.checkHealth().get("status")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("Redis reports DOWN when the server does not answer PING") + void checkHealth_reportsRedisDownWhenPingIsNotAnswered() throws Exception { + HealthService service = new HealthService(healthyDataSource(0, 0), redisAnswering("NOPE")); + try { + Map health = service.checkHealth(); + + assertEquals("DOWN", health.get("status")); + assertEquals("Redis PING failed", componentOf(health, "redis").get("error")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("Redis reports DOWN when the connection fails") + @SuppressWarnings("unchecked") + void checkHealth_reportsRedisDownWhenTheConnectionFails() throws Exception { + RedisTemplate redisTemplate = mock(RedisTemplate.class); + when(redisTemplate.execute(any(RedisCallback.class))).thenThrow(new RuntimeException("connection refused")); + + HealthService service = new HealthService(healthyDataSource(0, 0), redisTemplate); + try { + assertEquals("Redis connection failed", componentOf(service.checkHealth(), "redis").get("error")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("Redis is skipped, and stays UP, when it is not configured") + void checkHealth_skipsRedisWhenItIsNotConfigured() throws Exception { + HealthService service = new HealthService(healthyDataSource(0, 0), null); + try { + Map health = service.checkHealth(); + + assertEquals("UP", health.get("status")); + assertEquals("Redis not configured — skipped", componentOf(health, "redis").get("message")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("a component that never ran is reported as DOWN") + void checkHealth_reportsAComponentThatNeverRanAsDown() throws Exception { + HealthService service = new HealthService(healthyDataSource(0, 0), redisAnswering("PONG")); + service.shutdown(); + + Map health = service.checkHealth(); + + assertEquals("DOWN", health.get("status")); + assertTrue(((String) componentOf(health, "mysql").get("error")).contains("did not complete in time")); + assertTrue(((String) componentOf(health, "redis").get("error")).contains("did not complete in time")); + } + + @Test + @DisplayName("a pool that is nearly exhausted degrades the database") + void checkHealth_reportsMysqlDegradedWhenTheConnectionPoolIsNearlyExhausted() throws Exception { + HikariDataSource dataSource = mock(HikariDataSource.class); + when(dataSource.getConnection()).thenAnswer(invocation -> connection(0, 0)); + HikariPoolMXBean poolMXBean = mock(HikariPoolMXBean.class); + when(poolMXBean.getActiveConnections()).thenReturn(9); + when(dataSource.getHikariPoolMXBean()).thenReturn(poolMXBean); + when(dataSource.getMaximumPoolSize()).thenReturn(10); + + HealthService service = new HealthService(dataSource, redisAnswering("PONG")); + try { + assertEquals("DEGRADED", service.checkHealth().get("status")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("a pool with headroom leaves the database healthy") + void checkHealth_leavesMysqlHealthyWhenTheConnectionPoolHasHeadroom() throws Exception { + HikariDataSource dataSource = mock(HikariDataSource.class); + when(dataSource.getConnection()).thenAnswer(invocation -> connection(0, 0)); + HikariPoolMXBean poolMXBean = mock(HikariPoolMXBean.class); + when(poolMXBean.getActiveConnections()).thenReturn(1); + when(dataSource.getHikariPoolMXBean()).thenReturn(poolMXBean); + when(dataSource.getMaximumPoolSize()).thenReturn(10); + + HealthService service = new HealthService(dataSource, redisAnswering("PONG")); + try { + assertEquals("UP", service.checkHealth().get("status")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("unreadable pool metrics leave the database healthy") + void checkHealth_leavesMysqlHealthyWhenThePoolMetricsCannotBeRead() throws Exception { + HikariDataSource dataSource = mock(HikariDataSource.class); + when(dataSource.getConnection()).thenAnswer(invocation -> connection(0, 0)); + when(dataSource.getHikariPoolMXBean()).thenThrow(new IllegalStateException("pool not started")); + + HealthService service = new HealthService(dataSource, redisAnswering("PONG")); + try { + assertEquals("UP", service.checkHealth().get("status")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("a database that fails mid-diagnosis is reported as degraded") + void checkHealth_reportsMysqlDegradedWhenTheDiagnosticQueriesFail() throws Exception { + DataSource dataSource = mock(DataSource.class); + when(dataSource.getConnection()).thenAnswer(new org.mockito.stubbing.Answer() { + private int call = 0; + + @Override + public Connection answer(org.mockito.invocation.InvocationOnMock invocation) throws Throwable { + // The first connection serves the basic health query; the diagnostics that + // follow cannot get one. + if (call++ == 0) { + return connection(0, 0); + } + throw new SQLException("pool exhausted"); + } + }); + + HealthService service = new HealthService(dataSource, redisAnswering("PONG")); + try { + assertEquals("DEGRADED", service.checkHealth().get("status")); + } finally { + service.shutdown(); + } + } + + @Test + @DisplayName("shutdown is safe to call twice") + void shutdown_isSafeToCallTwice() throws Exception { + HealthService service = new HealthService(healthyDataSource(0, 0), redisAnswering("PONG")); + + service.shutdown(); + service.shutdown(); + } + + @Test + @DisplayName("a slow component is flagged with a warning severity") + void checkHealth_flagsASlowComponentWithAWarningSeverity() throws Exception { + HealthService service = new HealthService(healthyDataSource(0, 0), redisAnswering("PONG")); + try { + String severity = (String) ReflectionTestUtils.invokeMethod(service, "determineSeverity", true, 5000L, + false); + assertEquals("WARNING", severity); + assertEquals("OK", ReflectionTestUtils.invokeMethod(service, "determineSeverity", true, 10L, false)); + assertEquals("CRITICAL", ReflectionTestUtils.invokeMethod(service, "determineSeverity", false, 10L, + false)); + } finally { + service.shutdown(); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/ncdCare/NCDCareServiceImplTest.java b/src/test/java/com/iemr/mmu/service/ncdCare/NCDCareServiceImplTest.java new file mode 100644 index 00000000..605aef47 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/ncdCare/NCDCareServiceImplTest.java @@ -0,0 +1,534 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.ncdCare; + +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.data.nurse.CommonUtilityClass; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; +import com.iemr.mmu.service.labtechnician.LabTechnicianServiceImpl; +import com.iemr.mmu.service.tele_consultation.TeleConsultationServiceImpl; + +class NCDCareServiceImplTest { + + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private NCDCareDoctorServiceImpl ncdCareDoctorServiceImpl; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private LabTechnicianServiceImpl labTechnicianServiceImpl; + @Mock + private TeleConsultationServiceImpl teleConsultationServiceImpl; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + + @InjectMocks + private NCDCareServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + /** The visit block every nurse save starts from, with no test ordered. */ + private static String visitDetailsBlock(String laboratoryList) { + return "\"visitDetails\":{\"visitDetails\":{\"beneficiaryRegID\":1,\"visitReason\":\"Follow Up\"," + + "\"visitCategory\":\"NCD care\"},\"adherence\":{}," + + "\"investigation\":{\"laboratoryList\":" + laboratoryList + "}}"; + } + + @Nested + @DisplayName("saving nurse data") + class NurseSave { + + @Test + void saveNCDCareNurseData_ignoresARequestWithoutVisitDetails() throws Exception { + assertNull(service.saveNCDCareNurseData(null)); + assertNull(service.saveNCDCareNurseData(json("{}"))); + } + + @Test + void saveNCDCareNurseData_skipsTheVisitWhenTheNurseAlreadySavedThisFlow() throws Exception { + when(beneficiaryFlowStatusRepo.checkExistData(any(), any())).thenReturn(new BeneficiaryFlowStatus()); + + assertEquals(0L, service.saveNCDCareNurseData(json("{" + visitDetailsBlock("[]") + "}"))); + verify(commonNurseServiceImpl, never()).saveBeneficiaryVisitDetails(any()); + } + + @Test + void saveNCDCareNurseData_returnsZeroWhenTheVisitWasNotCreated() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(1); + + assertEquals(0L, service.saveNCDCareNurseData(json("{" + visitDetailsBlock("[]") + "}"))); + } + + @Test + void saveNCDCareNurseData_sendsTheBeneficiaryStraightToTheDoctorWhenNoTestWasOrdered() throws Exception { + stubVisitCreation(); + stubSuccessfulSections(); + + String request = "{" + visitDetailsBlock("[]") + ",\"historyDetails\":{},\"vitalDetails\":{}}"; + + assertEquals(1L, service.saveNCDCareNurseData(json(request))); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), org.mockito.ArgumentMatchers.eq((short) 9), + org.mockito.ArgumentMatchers.eq((short) 1), org.mockito.ArgumentMatchers.eq((short) 0), any(), + any(), anyLong(), any()); + } + + @Test + void saveNCDCareNurseData_routesTheBeneficiaryThroughTheLabWhenATestWasOrdered() throws Exception { + stubVisitCreation(); + stubSuccessfulSections(); + when(commonNurseServiceImpl.saveBenInvestigationDetails(any())).thenReturn(1); + + String request = "{" + visitDetailsBlock("[{\"testID\":1}]") + + ",\"historyDetails\":{},\"vitalDetails\":{}}"; + + assertEquals(1L, service.saveNCDCareNurseData(json(request))); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), org.mockito.ArgumentMatchers.eq((short) 2), + org.mockito.ArgumentMatchers.eq((short) 0), org.mockito.ArgumentMatchers.eq((short) 1), any(), + any(), anyLong(), any()); + } + + @Test + void saveNCDCareNurseData_leavesTheFlowUntouchedWhenTheVitalsFailToSave() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(null); + + String request = "{" + visitDetailsBlock("[]") + ",\"historyDetails\":{},\"vitalDetails\":{}}"; + + assertNull(service.saveNCDCareNurseData(json(request))); + verify(commonBenStatusFlowServiceImpl, never()).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), + anyLong(), anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any()); + } + + private void stubVisitCreation() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + } + + private void stubSuccessfulSections() { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + } + + @Test + void saveBenVisitDetails_savesAdherenceAndInvestigationsAgainstTheNewVisit() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + when(commonNurseServiceImpl.saveBenAdherenceDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigationDetails(any())).thenReturn(1); + + Map result = service.saveBenVisitDetails( + json("{" + visitDetailsBlock("[{\"testID\":1}]") + "}").getAsJsonObject("visitDetails"), + new CommonUtilityClass()); + + assertEquals(5L, result.get("visitID")); + assertEquals(6L, result.get("visitCode")); + } + + @Test + void saveBenVisitDetails_returnsNothingWhenTheVisitBlockIsMissing() throws Exception { + assertTrue(service.saveBenVisitDetails(json("{}"), new CommonUtilityClass()).isEmpty()); + assertTrue(service.saveBenVisitDetails(null, new CommonUtilityClass()).isEmpty()); + } + } + + @Nested + @DisplayName("saving the individual nurse sections") + class NurseSections { + + @Test + void saveBenNCDCareHistoryDetails_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenNCDCareHistoryDetails(json("{}"), 1L, 2L)); + assertEquals(1L, service.saveBenNCDCareHistoryDetails(null, 1L, 2L)); + } + + @Test + void saveBenNCDCareHistoryDetails_savesEverySectionThatWasSent() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenComorbidConditions(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMedicationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveFemaleObstetricHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMenstrualHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenFamilyHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePersonalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveAllergyHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildOptionalVaccineDetail(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveImmunizationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildDevelopmentHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildFeedingHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePerinatalHistory(any())).thenReturn(1L); + + String history = "{\"pastHistory\":{},\"comorbidConditions\":{}," + + "\"medicationHistory\":{\"medicationHistoryList\":[{\"currentMedication\":\"x\"}]}," + + "\"femaleObstetricHistory\":{},\"menstrualHistory\":{},\"familyHistory\":{}," + + "\"personalHistory\":{},\"childVaccineDetails\":{},\"immunizationHistory\":{}," + + "\"developmentHistory\":{},\"feedingHistory\":{},\"perinatalHistroy\":{}}"; + + assertEquals(1L, service.saveBenNCDCareHistoryDetails(json(history), 1L, 2L)); + } + + @Test + void saveBenNCDCareHistoryDetails_reportsFailureWhenASectionCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(0L); + assertNull(service.saveBenNCDCareHistoryDetails(json("{\"pastHistory\":{}}"), 1L, 2L)); + } + + @Test + void saveBenNCDCareVitalDetails_savesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(5L); + + assertEquals(4L, service.saveBenNCDCareVitalDetails(json("{}"), 1L, 2L)); + } + + @Test + void saveBenNCDCareVitalDetails_reportsFailureWhenTheVitalsCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(null); + + assertNull(service.saveBenNCDCareVitalDetails(json("{}"), 1L, 2L)); + assertNull(service.saveBenNCDCareVitalDetails(null, 1L, 2L)); + } + } + + @Nested + @DisplayName("reading the nurse and doctor case sheets") + class Reads { + + @Test + void getBenVisitDetailsFrmNurseNCDCare_gathersTheVisitAdherenceAndInvestigations() throws Exception { + when(commonNurseServiceImpl.getCSVisitDetails(1L, 2L)).thenReturn(null); + when(commonNurseServiceImpl.getBenAdherence(1L, 2L)).thenReturn("adherence"); + when(commonNurseServiceImpl.getLabTestOrders(1L, 2L)).thenReturn("orders"); + + String result = service.getBenVisitDetailsFrmNurseNCDCare(1L, 2L); + + assertTrue(result.contains("NCDCareNurseVisitDetail")); + assertTrue(result.contains("BenAdherence")); + assertTrue(result.contains("Investigation")); + } + + @Test + void getBenNCDCareHistoryDetails_gathersEveryHistorySection() { + when(commonNurseServiceImpl.getPastHistoryData(1L, 2L)) + .thenReturn(new com.iemr.mmu.data.anc.BenMedHistory()); + + assertTrue(service.getBenNCDCareHistoryDetails(1L, 2L).contains("PastHistory")); + verify(commonNurseServiceImpl).getFeedingHistory(1L, 2L); + } + + @Test + void getBeneficiaryVitalDetails_gathersAnthropometryAndVitals() { + when(commonNurseServiceImpl.getBeneficiaryPhysicalAnthropometryDetails(1L, 2L)).thenReturn("a"); + when(commonNurseServiceImpl.getBeneficiaryPhysicalVitalDetails(1L, 2L)).thenReturn("v"); + + assertTrue(service.getBeneficiaryVitalDetails(1L, 2L).contains("benAnthropometryDetail")); + } + + @Test + void getBenNCDCareNurseData_combinesTheVitalsAndHistory() { + String result = service.getBenNCDCareNurseData(1L, 2L); + assertTrue(result.contains("vitals")); + assertTrue(result.contains("history")); + } + + @Test + void getBenCaseRecordFromDoctorNCDCare_gathersEveryDoctorSection() throws Exception { + when(commonDoctorServiceImpl.getFindingsDetails(1L, 2L)).thenReturn("findings"); + when(ncdCareDoctorServiceImpl.getNCDCareDiagnosisDetails(1L, 2L)).thenReturn("diagnosis"); + when(commonDoctorServiceImpl.getInvestigationDetails(1L, 2L)).thenReturn("investigation"); + when(commonDoctorServiceImpl.getPrescribedDrugs(1L, 2L)).thenReturn("prescription"); + when(commonDoctorServiceImpl.getReferralDetails(1L, 2L)).thenReturn("refer"); + when(labTechnicianServiceImpl.getLabResultDataForBen(1L, 2L)).thenReturn(new ArrayList<>()); + when(commonNurseServiceImpl.getGraphicalTrendData(1L, "ncdCare")).thenReturn(new HashMap<>()); + when(labTechnicianServiceImpl.getLast_3_ArchivedTestVisitList(1L, 2L)).thenReturn("[]"); + + String result = service.getBenCaseRecordFromDoctorNCDCare(1L, 2L); + + assertTrue(result.contains("findings")); + assertTrue(result.contains("GraphData")); + } + } + + @Nested + @DisplayName("updating nurse data") + class NurseUpdates { + + @Test + void updateBenHistoryDetails_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1, service.updateBenHistoryDetails(json("{}"))); + } + + @Test + void updateBenHistoryDetails_updatesEverySectionThatWasSent() throws Exception { + when(commonNurseServiceImpl.updateBenPastHistoryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenComorbidConditions(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenMedicationHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenPersonalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenAllergicHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenFamilyHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateMenstrualHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePastObstetricHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildImmunizationDetail(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildOptionalVaccineDetail(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildFeedingHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePerinatalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildDevelopmentHistory(any())).thenReturn(1); + + String history = "{\"pastHistory\":{},\"comorbidConditions\":{},\"medicationHistory\":{}," + + "\"personalHistory\":{},\"familyHistory\":{},\"menstrualHistory\":{}," + + "\"femaleObstetricHistory\":{},\"immunizationHistory\":{\"benChildVaccineDetails\":[{}]}," + + "\"childVaccineDetails\":{},\"feedingHistory\":{},\"perinatalHistroy\":{}," + + "\"developmentHistory\":{}}"; + + assertEquals(1, service.updateBenHistoryDetails(json(history))); + } + + @Test + void updateBenHistoryDetails_reportsFailureWhenASectionCouldNotBeUpdated() throws Exception { + when(commonNurseServiceImpl.updateBenPastHistoryDetails(any())).thenReturn(0); + assertEquals(0, service.updateBenHistoryDetails(json("{\"pastHistory\":{}}"))); + } + + @Test + void updateBenVitalDetails_updatesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(1); + + assertEquals(1, service.updateBenVitalDetails(json("{}"))); + } + + @Test + void updateBenVitalDetails_reportsFailureWhenTheVitalsCouldNotBeUpdated() throws Exception { + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(0); + + assertEquals(0, service.updateBenVitalDetails(json("{}"))); + assertEquals(1, service.updateBenVitalDetails(null)); + } + } + + @Nested + @DisplayName("saving and updating doctor data") + class DoctorData { + + private String doctorRequest(String extra) { + return "{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"providerServiceMapID\":4," + + "\"createdBy\":\"doctor\",\"findings\":{},\"investigation\":{\"laboratoryList\":[{}]}," + + "\"diagnosis\":{\"provisionalDiagnosisList\":[{\"term\":\"Fever\",\"conceptID\":\"1\"}]}," + + "\"prescription\":[{\"drugID\":1}],\"refer\":{}" + extra + "}"; + } + + private String teleconsultationBlock() { + return ",\"serviceID\":4,\"tcRequest\":{\"userID\":5,\"allocationDate\":\"2024-01-01\"," + + "\"fromTime\":\"10:00:00\",\"toTime\":\"10:30:00\"}"; + } + + private Map drugResult() { + Map result = new HashMap<>(); + result.put("count", 1); + result.put("prescribedDrugIDs", Collections.singletonList(9L)); + return result; + } + + @Test + void saveDoctorData_savesEverySectionAndAdvancesTheFlow() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + when(ncdCareDoctorServiceImpl.saveNCDDiagnosisData(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.saveDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void saveDoctorData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.saveDoctorData(json("{\"findings\":{},\"investigation\":{}}"), "auth")); + } + + @Test + void saveDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + when(ncdCareDoctorServiceImpl.saveNCDDiagnosisData(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, () -> service.saveDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void saveDoctorData_failsWhenASectionCouldNotBeSaved() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(0); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + + assertThrows(RuntimeException.class, () -> service.saveDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void saveDoctorData_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + when(ncdCareDoctorServiceImpl.saveNCDDiagnosisData(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.saveDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + verify(teleConsultationServiceImpl).createTCRequest(any()); + } + + @Test + void saveDoctorData_failsWhenTheSpecialistSlotCouldNotBeBooked() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + assertEquals("Error while booking slot.", thrown.getMessage()); + } + + @Test + void updateNCDCareDoctorData_updatesEverySectionAndAdvancesTheFlow() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(ncdCareDoctorServiceImpl.updateBenNCDCareDiagnosis(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.updateNCDCareDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updateNCDCareDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(ncdCareDoctorServiceImpl.updateBenNCDCareDiagnosis(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, + () -> service.updateNCDCareDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updateNCDCareDoctorData_failsWhenASectionCouldNotBeUpdated() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(0); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + + assertThrows(RuntimeException.class, + () -> service.updateNCDCareDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updateNCDCareDoctorData_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(ncdCareDoctorServiceImpl.updateBenNCDCareDiagnosis(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.updateNCDCareDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + } + + @Test + void updateNCDCareDoctorData_failsWhenTheSpecialistSlotCouldNotBeBooked() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + assertThrows(RuntimeException.class, + () -> service.updateNCDCareDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningNurseAndDoctorServiceTest.java b/src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningNurseAndDoctorServiceTest.java new file mode 100644 index 00000000..bb473e82 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningNurseAndDoctorServiceTest.java @@ -0,0 +1,269 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.ncdscreening; + +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.ncdScreening.NCDScreening; +import com.iemr.mmu.data.quickConsultation.PrescriptionDetail; +import com.iemr.mmu.repo.nurse.ncdscreening.NCDScreeningRepo; +import com.iemr.mmu.repo.quickConsultation.PrescriptionDetailRepo; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; +import com.iemr.mmu.service.tele_consultation.TeleConsultationServiceImpl; + +class NCDScreeningNurseAndDoctorServiceTest { + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + @Nested + @DisplayName("NCDScreeningNurseServiceImpl") + class NurseService { + + @Mock + private NCDScreeningRepo ncdScreeningRepo; + + @InjectMocks + private NCDScreeningNurseServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private NCDScreening screeningWithConditions() { + NCDScreening screening = new NCDScreening(); + ArrayList> conditions = new ArrayList<>(); + for (int i = 1; i <= 2; i++) { + Map condition = new HashMap<>(); + condition.put("ncdScreeningConditionID", i); + condition.put("screeningCondition", "condition" + i); + conditions.add(condition); + } + screening.setNcdScreeningConditionList(conditions); + return screening; + } + + @Test + void saveNCDScreeningDetails_flattensTheScreenedConditionsBeforeSaving() { + NCDScreening screening = screeningWithConditions(); + NCDScreening stored = new NCDScreening(); + stored.setID(5L); + when(ncdScreeningRepo.save(screening)).thenReturn(stored); + + assertEquals(5L, service.saveNCDScreeningDetails(screening)); + assertEquals("1,2", screening.getNcdScreeningConditionID()); + assertEquals("condition1,condition2", screening.getScreeningCondition()); + } + + @Test + void saveNCDScreeningDetails_returnsNothingWhenTheRowWasNotStored() { + NCDScreening screening = new NCDScreening(); + when(ncdScreeningRepo.save(screening)).thenReturn(null); + + assertNull(service.saveNCDScreeningDetails(screening)); + } + + @Test + void getNCDScreeningDetails_splitsTheStoredConditionsBackIntoAList() { + NCDScreening stored = new NCDScreening(); + stored.setNcdScreeningConditionID("1,2"); + stored.setScreeningCondition("condition1,condition2"); + stored.setNextScreeningDateDB(Timestamp.valueOf("2024-06-01 00:00:00")); + when(ncdScreeningRepo.getNCDScreeningDetails(1L, 2L)).thenReturn(stored); + + String result = service.getNCDScreeningDetails(1L, 2L); + + assertEquals(2, stored.getNcdScreeningConditionList().size()); + assertTrue(result.contains("2024-06-01"), result); + } + + @Test + void getNCDScreeningDetails_leavesTheConditionListUnsetWhenNothingWasScreened() { + NCDScreening stored = new NCDScreening(); + when(ncdScreeningRepo.getNCDScreeningDetails(1L, 2L)).thenReturn(stored); + + service.getNCDScreeningDetails(1L, 2L); + + assertNull(stored.getNcdScreeningConditionList()); + } + + @Test + void getNCDScreeningDetails_ignoresConditionIdsAndNamesThatDoNotLineUp() { + NCDScreening stored = new NCDScreening(); + stored.setNcdScreeningConditionID("1,2"); + stored.setScreeningCondition("condition1"); + when(ncdScreeningRepo.getNCDScreeningDetails(1L, 2L)).thenReturn(stored); + + service.getNCDScreeningDetails(1L, 2L); + + assertTrue(stored.getNcdScreeningConditionList().isEmpty()); + } + + @Test + void updateNCDScreeningDetails_flattensTheConditionsBeforeUpdating() { + NCDScreening screening = screeningWithConditions(); + when(ncdScreeningRepo.updateNCDScreeningDetails(anyString(), anyString(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateNCDScreeningDetails(screening)); + assertEquals("1,2", screening.getNcdScreeningConditionID()); + } + } + + @Nested + @DisplayName("NCDSCreeningDoctorServiceImpl") + class DoctorService { + + @Mock + private PrescriptionDetailRepo prescriptionDetailRepo; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private TeleConsultationServiceImpl teleConsultationServiceImpl; + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + + @InjectMocks + private NCDSCreeningDoctorServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private String doctorRequest() { + return "{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"providerServiceMapID\":4," + + "\"createdBy\":\"doctor\",\"findings\":{},\"investigation\":{\"laboratoryList\":[{}]," + + "\"externalInvestigations\":\"MRI\"},\"diagnosis\":{\"prescriptionID\":7}," + + "\"prescription\":[{\"drugID\":1}],\"refer\":{}}"; + } + + private Map drugResult() { + Map result = new HashMap<>(); + result.put("count", 1); + result.put("prescribedDrugIDs", Collections.singletonList(9L)); + return result; + } + + private void stubSuccessfulUpdate() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + } + + @Test + void updateDoctorData_updatesEverySectionAndAdvancesTheFlow() throws Exception { + stubSuccessfulUpdate(); + + assertEquals(1, service.updateDoctorData(json(doctorRequest()))); + } + + @Test + void updateDoctorData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1, service.updateDoctorData(json("{\"investigation\":{}}"))); + } + + @Test + void updateDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + stubSuccessfulUpdate(); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, () -> service.updateDoctorData(json(doctorRequest()))); + } + + @Test + void updateDoctorData_failsWhenASectionCouldNotBeUpdated() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(0); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + + assertThrows(RuntimeException.class, () -> service.updateDoctorData(json(doctorRequest()))); + } + + @Test + void getNCDDiagnosisData_splitsTheStoredDiagnosisBackIntoAList() { + PrescriptionDetail stored = new PrescriptionDetail(); + stored.setDiagnosisProvided("Diabetes || Hypertension"); + stored.setDiagnosisProvided_SCTCode("111 || 222"); + when(prescriptionDetailRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)) + .thenReturn(new ArrayList<>(Arrays.asList(stored))); + + String result = service.getNCDDiagnosisData(1L, 2L); + + assertEquals(2, stored.getProvisionalDiagnosisList().size()); + assertTrue(result.contains("Diabetes"), result); + } + + @Test + void getNCDDiagnosisData_returnsAnEmptyDiagnosisWhenNoneWasRecorded() { + when(prescriptionDetailRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)).thenReturn(new ArrayList<>()); + + assertEquals("{}", service.getNCDDiagnosisData(1L, 2L)); + } + + @Test + void getNCDDiagnosisData_leavesTheDiagnosisListUnsetWhenNoTermWasCoded() { + PrescriptionDetail stored = new PrescriptionDetail(); + when(prescriptionDetailRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)) + .thenReturn(new ArrayList<>(Arrays.asList(stored))); + + service.getNCDDiagnosisData(1L, 2L); + + assertNull(stored.getProvisionalDiagnosisList()); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningServiceImplTest.java b/src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningServiceImplTest.java new file mode 100644 index 00000000..0128801f --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/ncdscreening/NCDScreeningServiceImplTest.java @@ -0,0 +1,639 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.ncdscreening; + +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.data.ncdScreening.IDRSData; +import com.iemr.mmu.data.nurse.CommonUtilityClass; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.repo.nurse.BenVisitDetailRepo; +import com.iemr.mmu.repo.nurse.ncdscreening.IDRSDataRepo; +import com.iemr.mmu.repo.quickConsultation.PrescriptionDetailRepo; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonServiceImpl; +import com.iemr.mmu.service.labtechnician.LabTechnicianServiceImpl; +import com.iemr.mmu.service.tele_consultation.TeleConsultationServiceImpl; + +class NCDScreeningServiceImplTest { + + @Mock + private NCDScreeningNurseServiceImpl ncdScreeningNurseServiceImpl; + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private LabTechnicianServiceImpl labTechnicianServiceImpl; + @Mock + private CommonServiceImpl commonServiceImpl; + @Mock + private TeleConsultationServiceImpl teleConsultationServiceImpl; + @Mock + private NCDSCreeningDoctorServiceImpl ncdSCreeningDoctorServiceImpl; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + @Mock + private BenVisitDetailRepo benVisitDetailRepo; + @Mock + private IDRSDataRepo iDrsDataRepo; + @Mock + private PrescriptionDetailRepo prescriptionDetailRepo; + + @InjectMocks + private NCDScreeningServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + private static String visitDetailsBlock() { + return "\"visitDetails\":{\"visitDetails\":{\"beneficiaryRegID\":1,\"visitReason\":\"Screening\"," + + "\"visitCategory\":\"NCD screening\"},\"chiefComplaints\":[{\"chiefComplaintID\":1}]}"; + } + + @Nested + @DisplayName("saving nurse data") + class NurseSave { + + @Test + void saveNCDScreeningNurseData_rejectsARequestWithoutVisitDetails() { + assertThrows(Exception.class, () -> service.saveNCDScreeningNurseData(json("{}"), "auth")); + assertThrows(Exception.class, () -> service.saveNCDScreeningNurseData(null, "auth")); + } + + @Test + void saveNCDScreeningNurseData_skipsTheVisitWhenTheNurseAlreadySavedThisFlow() throws Exception { + when(beneficiaryFlowStatusRepo.checkExistData(any(), any())).thenReturn(new BeneficiaryFlowStatus()); + + assertEquals(0L, service.saveNCDScreeningNurseData(json("{" + visitDetailsBlock() + "}"), "auth")); + verify(commonNurseServiceImpl, never()).saveBeneficiaryVisitDetails(any()); + } + + @Test + void saveNCDScreeningNurseData_returnsZeroWhenTheVisitWasNotCreated() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(1); + + assertEquals(0L, service.saveNCDScreeningNurseData(json("{" + visitDetailsBlock() + "}"), "auth")); + } + + @Test + void saveNCDScreeningNurseData_savesEverySectionAndAdvancesTheBeneficiaryFlow() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveIDRS(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePhysicalActivity(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(1); + + String request = "{" + visitDetailsBlock() + + ",\"historyDetails\":{\"physicalActivityHistory\":{\"activityType\":\"Walking\"}}," + + "\"vitalDetails\":{},\"idrsDetails\":{}}"; + + assertEquals(1L, service.saveNCDScreeningNurseData(json(request), "auth")); + } + + @Test + void saveNCDScreeningNurseData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(0); + + String request = "{" + visitDetailsBlock() + ",\"historyDetails\":{},\"vitalDetails\":{}," + + "\"idrsDetails\":{}}"; + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveNCDScreeningNurseData(json(request), "auth")); + assertTrue(thrown.getMessage().contains("Beneficiary status update failed")); + } + + @Test + void saveNCDScreeningNurseData_failsWhenASectionCouldNotBeSaved() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(null); + + String request = "{" + visitDetailsBlock() + ",\"historyDetails\":{},\"vitalDetails\":{}," + + "\"idrsDetails\":{}}"; + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveNCDScreeningNurseData(json(request), "auth")); + assertEquals("Error occurred while saving data", thrown.getMessage()); + } + + private void stubVisitCreation() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + } + + @Test + void saveBenVisitDetails_stampsTheVisitOntoEveryChiefComplaint() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + + Map result = service.saveBenVisitDetails( + json("{" + visitDetailsBlock() + "}").getAsJsonObject("visitDetails"), new CommonUtilityClass()); + + assertEquals(5L, result.get("visitID")); + verify(commonNurseServiceImpl).saveBenChiefComplaints(any()); + } + + @Test + void saveBenVisitDetails_returnsNothingWhenTheVisitBlockIsMissing() throws Exception { + assertTrue(service.saveBenVisitDetails(json("{}"), new CommonUtilityClass()).isEmpty()); + assertTrue(service.saveBenVisitDetails(null, new CommonUtilityClass()).isEmpty()); + } + } + + @Nested + @DisplayName("saving a teleconsultation referral") + class TeleconsultationReferral { + + private String tmReferredRequest(String prescription) { + return "{\"isTMCDone\":true,\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3," + + "\"providerServiceMapID\":4,\"prescription\":" + prescription + ",\"refer\":{}}"; + } + + @Test + void saveNCDScreeningNurseData_storesTheReferralAndAdvancesTheFlow() throws Exception { + when(commonDoctorServiceImpl.saveBenReferDetailsTMreferred(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowtableAfterNurseSaveForTMReferred(any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.saveNCDScreeningNurseData(json(tmReferredRequest("[]")), "auth")); + } + + @Test + void saveNCDScreeningNurseData_storesEveryPrescribedDrugAgainstTheReferralPrescription() throws Exception { + when(prescriptionDetailRepo.getPrescriptionID(3L)).thenReturn(7L); + Map drugResult = new HashMap<>(); + drugResult.put("count", 1); + drugResult.put("prescribedDrugIDs", Collections.singletonList(9L)); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult); + when(commonDoctorServiceImpl.saveBenReferDetailsTMreferred(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowtableAfterNurseSaveForTMReferred(any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, + service.saveNCDScreeningNurseData(json(tmReferredRequest("[{\"drugID\":1}]")), "auth")); + } + + @Test + void saveNCDScreeningNurseData_failsWhenTheReferralFlowCouldNotBeAdvanced() throws Exception { + when(commonDoctorServiceImpl.saveBenReferDetailsTMreferred(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowtableAfterNurseSaveForTMReferred(any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, + () -> service.saveNCDScreeningNurseData(json(tmReferredRequest("[]")), "auth")); + } + } + + @Nested + @DisplayName("saving the individual nurse sections") + class NurseSections { + + @Test + void saveBenNCDCareHistoryDetails_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenNCDCareHistoryDetails(json("{}"), 1L, 2L)); + assertEquals(1L, service.saveBenNCDCareHistoryDetails(null, 1L, 2L)); + } + + @Test + void saveBenNCDCareHistoryDetails_savesEverySectionThatWasSent() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenComorbidConditions(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMedicationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveFemaleObstetricHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMenstrualHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenFamilyHistoryNCDScreening(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePersonalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveAllergyHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildOptionalVaccineDetail(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveImmunizationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildDevelopmentHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildFeedingHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePerinatalHistory(any())).thenReturn(1L); + + String history = "{\"pastHistory\":{},\"comorbidConditions\":{}," + + "\"medicationHistory\":{\"medicationHistoryList\":[{\"currentMedication\":\"x\"}]}," + + "\"femaleObstetricHistory\":{},\"menstrualHistory\":{},\"familyHistory\":{}," + + "\"personalHistory\":{},\"childVaccineDetails\":{},\"immunizationHistory\":{}," + + "\"developmentHistory\":{},\"feedingHistory\":{},\"perinatalHistroy\":{}}"; + + assertEquals(1L, service.saveBenNCDCareHistoryDetails(json(history), 1L, 2L)); + } + + @Test + void saveBenNCDCareHistoryDetails_reportsFailureWhenASectionCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(0L); + assertNull(service.saveBenNCDCareHistoryDetails(json("{\"pastHistory\":{}}"), 1L, 2L)); + } + + @Test + void saveidrsDetails_joinsEveryAnsweredQuestionOntoOneRow() throws Exception { + when(commonNurseServiceImpl.saveIDRS(any())).thenReturn(1L); + + String idrs = "{\"questionArray\":[{\"idrsQuestionID\":1,\"question\":\"Q1\",\"answer\":\"Yes\"," + + "\"diseaseQuestionType\":\"Diabetes\"},{\"idrsQuestionID\":2,\"question\":\"Q2\"," + + "\"answer\":\"No\",\"diseaseQuestionType\":\"Hypertension\"}]," + + "\"suspectArray\":[\"Diabetes\",\"Hypertension\"],\"confirmArray\":[\"Diabetes\"]}"; + + assertEquals(1L, service.saveidrsDetails(json(idrs), 1L, 2L)); + + ArgumentCaptor saved = ArgumentCaptor.forClass(IDRSData.class); + verify(commonNurseServiceImpl).saveIDRS(saved.capture()); + assertEquals("1||2", saved.getValue().getQuestionIds()); + assertEquals("Q1||Q2", saved.getValue().getQuestion()); + assertEquals("Yes||No", saved.getValue().getAnswer()); + assertEquals("Diabetes||Hypertension", saved.getValue().getDiseaseQuestionType()); + assertEquals("Diabetes,Hypertension", saved.getValue().getSuspectedDisease()); + assertEquals("Diabetes", saved.getValue().getConfirmedDisease()); + } + + @Test + void saveidrsDetails_storesTheScreeningEvenWhenNoQuestionWasAnswered() throws Exception { + when(commonNurseServiceImpl.saveIDRS(any())).thenReturn(1L); + + String idrs = "{\"suspectArray\":[\"Diabetes\"],\"confirmArray\":[\"Diabetes\"]}"; + + assertEquals(1L, service.saveidrsDetails(json(idrs), 1L, 2L)); + assertNull(service.saveidrsDetails(null, 1L, 2L)); + } + + @Test + void savePhysicalActivityDetails_storesTheActivityWhenOneWasReported() throws Exception { + when(commonNurseServiceImpl.savePhysicalActivity(any())).thenReturn(1L); + + assertEquals(1L, service.savePhysicalActivityDetails(json("{\"activityType\":\"Walking\"}"), 1L, 2L)); + assertNull(service.savePhysicalActivityDetails(json("{}"), 1L, 2L)); + assertNull(service.savePhysicalActivityDetails(null, 1L, 2L)); + } + + @Test + void saveBenNCDCareVitalDetails_savesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(5L); + + assertEquals(4L, service.saveBenNCDCareVitalDetails(json("{}"), 1L, 2L)); + + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(null); + assertNull(service.saveBenNCDCareVitalDetails(json("{}"), 1L, 2L)); + assertNull(service.saveBenNCDCareVitalDetails(null, 1L, 2L)); + } + + @Test + void saveNCDScreeningVitalDetails_readsTheVitalsOutOfTheScreeningBlock() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(5L); + + assertEquals(4L, service.saveNCDScreeningVitalDetails(json("{\"ncdScreeningDetails\":{}}"), 1L, 2L)); + + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(null); + assertNull(service.saveNCDScreeningVitalDetails(json("{\"ncdScreeningDetails\":{}}"), 1L, 2L)); + } + } + + @Nested + @DisplayName("updating nurse data") + class NurseUpdates { + + @Test + void updateNurseNCDScreeningDetails_storesTheAttachedFilesAndAdvancesTheFlow() throws Exception { + when(ncdScreeningNurseServiceImpl.updateNCDScreeningDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(1); + + String request = "{\"beneficiaryRegID\":1,\"visitCode\":2,\"benFlowID\":3," + + "\"nextScreeningDate\":\"2024-01-01T10:00:00Z\",\"isScreeningComplete\":true," + + "\"fileIDs\":[\"a\",\"b\"]}"; + + assertEquals(1, service.updateNurseNCDScreeningDetails(json(request))); + verify(benVisitDetailRepo).updateFileID("a,b,", 1L, 2L); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseUpdateNCD_Screening(3L, 1L, (short) 9); + } + + @Test + void updateNurseNCDScreeningDetails_marksAnIncompleteScreeningAsStillWithTheNurse() throws Exception { + when(ncdScreeningNurseServiceImpl.updateNCDScreeningDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(1); + + assertEquals(1, service.updateNurseNCDScreeningDetails( + json("{\"beneficiaryRegID\":1,\"benFlowID\":3,\"isScreeningComplete\":false}"))); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseUpdateNCD_Screening(3L, 1L, + (short) 100); + } + + @Test + void updateNurseNCDScreeningDetails_reportsNoResultWhenAnUpdateCouldNotBeApplied() throws Exception { + when(ncdScreeningNurseServiceImpl.updateNCDScreeningDetails(any())).thenReturn(null); + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(1); + + assertNull(service.updateNurseNCDScreeningDetails(json("{\"beneficiaryRegID\":1}"))); + } + + @Test + void updateBenVitalDetails_updatesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(1); + + assertEquals(1, service.updateBenVitalDetails(json("{}"))); + + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(0); + assertEquals(0, service.updateBenVitalDetails(json("{}"))); + assertEquals(1, service.updateBenVitalDetails(null)); + } + + @Test + void UpdateNCDScreeningHistory_updatesTheFamilyAndActivityHistoryTogether() throws Exception { + when(commonNurseServiceImpl.updateBenFamilyHistoryNCDScreening(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenPhysicalActivityHistoryNCDScreening(any())).thenReturn(1); + + String history = "{\"familyHistory\":{},\"physicalActivityHistory\":{},\"personalHistory\":{}}"; + + assertEquals(1, service.UpdateNCDScreeningHistory(json(history))); + verify(commonNurseServiceImpl).updateBenPersonalHistory(any()); + verify(commonNurseServiceImpl).updateBenAllergicHistory(any()); + } + + @Test + void UpdateNCDScreeningHistory_reportsNoUpdateWhenNoHistoryWasSent() throws Exception { + assertEquals(0, service.UpdateNCDScreeningHistory(json("{}"))); + assertEquals(0, service.UpdateNCDScreeningHistory(null)); + } + + @Test + void UpdateIDRSScreen_joinsEveryAnsweredQuestionOntoOneRow() throws Exception { + when(commonNurseServiceImpl.saveIDRS(any())).thenReturn(1L); + + String idrs = "{\"idrsDetails\":{\"questionArray\":[{\"id\":5,\"idrsQuestionID\":1,\"question\":\"Q1\"," + + "\"answer\":\"Yes\",\"diseaseQuestionType\":\"Diabetes\"}]," + + "\"suspectArray\":[\"Diabetes\"],\"confirmArray\":[\"Diabetes\"]}}"; + + assertEquals(1L, service.UpdateIDRSScreen(json(idrs))); + + ArgumentCaptor saved = ArgumentCaptor.forClass(IDRSData.class); + verify(commonNurseServiceImpl).saveIDRS(saved.capture()); + assertEquals(5L, saved.getValue().getId()); + assertEquals("1", saved.getValue().getQuestionIds()); + } + + @Test + void UpdateIDRSScreen_updatesTheSuspectedDiseasesAndScoreWhenNoQuestionWasReanswered() throws Exception { + when(iDrsDataRepo.updateSuspectedDiseases(1L, 2L, "Diabetes")).thenReturn(1); + when(iDrsDataRepo.updateIdrsScore(1L, 2L, 30)).thenReturn(1); + + String idrs = "{\"idrsDetails\":{\"beneficiaryRegID\":1,\"visitCode\":2," + + "\"suspectArray\":[\"Diabetes\"],\"idrsScore\":30}}"; + + assertEquals(1L, service.UpdateIDRSScreen(json(idrs))); + } + + @Test + void UpdateIDRSScreen_doesNothingWithoutAScreeningBlock() throws Exception { + assertNull(service.UpdateIDRSScreen(json("{}"))); + assertNull(service.UpdateIDRSScreen(null)); + } + } + + @Nested + @DisplayName("reading the nurse and doctor case sheets") + class Reads { + + @Test + void getNCDScreeningDetails_gathersTheScreeningAnthropometryAndVitals() { + when(ncdScreeningNurseServiceImpl.getNCDScreeningDetails(1L, 2L)).thenReturn("screening"); + when(commonNurseServiceImpl.getBeneficiaryPhysicalAnthropometryDetails(1L, 2L)).thenReturn("anthro"); + when(commonNurseServiceImpl.getBeneficiaryPhysicalVitalDetails(1L, 2L)).thenReturn("vitals"); + + assertTrue(service.getNCDScreeningDetails(1L, 2L).contains("ncdScreeningDetails")); + } + + @Test + void getNCDScreeningDetails_returnsNothingWhenASectionIsMissing() { + when(ncdScreeningNurseServiceImpl.getNCDScreeningDetails(1L, 2L)).thenReturn(null); + assertEquals("{}", service.getNCDScreeningDetails(1L, 2L)); + } + + @Test + void getNcdScreeningVisitCnt_reportsTheNextVisitNumber() { + when(beneficiaryFlowStatusRepo.getNcdScreeningVisitCount(1L)).thenReturn(3L); + assertTrue(service.getNcdScreeningVisitCnt(1L).contains("4")); + } + + @Test + void getBenVisitDetailsFrmNurseNCDScreening_gathersTheVisitAndItsComplaints() throws Exception { + when(commonNurseServiceImpl.getCSVisitDetails(1L, 2L)).thenReturn(null); + when(commonNurseServiceImpl.getBenChiefComplaints(1L, 2L)).thenReturn("[]"); + + assertTrue(service.getBenVisitDetailsFrmNurseNCDScreening(1L, 2L) + .contains("NCDScreeningNurseVisitDetail")); + } + + @Test + void getBenHistoryDetails_gathersTheFamilyActivityAndPersonalHistory() { + when(commonNurseServiceImpl.getFamilyHistoryDetail(1L, 2L)) + .thenReturn(new com.iemr.mmu.data.anc.BenFamilyHistory()); + + assertTrue(service.getBenHistoryDetails(1L, 2L).contains("FamilyHistory")); + verify(commonNurseServiceImpl).getPhysicalActivityType(1L, 2L); + verify(commonNurseServiceImpl).getPersonalHistory(1L, 2L); + } + + @Test + void getBenIdrsDetailsFrmNurse_readsTheStoredScreening() { + when(commonNurseServiceImpl.getBeneficiaryIdrsDetails(1L, 2L)).thenReturn(new IDRSData()); + assertTrue(service.getBenIdrsDetailsFrmNurse(1L, 2L).contains("IDRSDetail")); + } + + @Test + void getBeneficiaryVitalDetails_gathersAnthropometryAndVitals() { + when(commonNurseServiceImpl.getBeneficiaryPhysicalAnthropometryDetails(1L, 2L)).thenReturn("a"); + when(commonNurseServiceImpl.getBeneficiaryPhysicalVitalDetails(1L, 2L)).thenReturn("v"); + + assertTrue(service.getBeneficiaryVitalDetails(1L, 2L).contains("benAnthropometryDetail")); + } + + @Test + void getBenNCDScreeningNurseData_combinesTheVitalsHistoryAndScreening() { + String result = service.getBenNCDScreeningNurseData(1L, 2L); + + assertTrue(result.contains("vitals")); + assertTrue(result.contains("history")); + assertTrue(result.contains("idrs")); + } + + @Test + void getBenCaseRecordFromDoctorNCDScreening_gathersEveryDoctorSection() throws Exception { + when(commonDoctorServiceImpl.getFindingsDetails(1L, 2L)).thenReturn("findings"); + when(ncdSCreeningDoctorServiceImpl.getNCDDiagnosisData(1L, 2L)).thenReturn("diagnosis"); + when(commonDoctorServiceImpl.getInvestigationDetails(1L, 2L)).thenReturn("investigation"); + when(commonDoctorServiceImpl.getPrescribedDrugs(1L, 2L)).thenReturn("prescription"); + when(commonDoctorServiceImpl.getReferralDetails(1L, 2L)).thenReturn("refer"); + when(labTechnicianServiceImpl.getLabResultDataForBen(1L, 2L)).thenReturn(new ArrayList<>()); + when(commonNurseServiceImpl.getGraphicalTrendData(1L, "ncdCare")).thenReturn(new HashMap<>()); + when(labTechnicianServiceImpl.getLast_3_ArchivedTestVisitList(1L, 2L)).thenReturn("[]"); + + assertTrue(service.getBenCaseRecordFromDoctorNCDScreening(1L, 2L).contains("findings")); + } + } + + @Nested + @DisplayName("saving doctor data") + class DoctorData { + + private String doctorRequest(String extra) { + return "{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"providerServiceMapID\":4," + + "\"createdBy\":\"doctor\",\"findings\":{},\"investigation\":{\"laboratoryList\":[{}]}," + + "\"diagnosis\":{},\"prescription\":[{\"drugID\":1}],\"refer\":{}" + extra + "}"; + } + + private Map drugResult() { + Map result = new HashMap<>(); + result.put("count", 1); + result.put("prescribedDrugIDs", Collections.singletonList(9L)); + return result; + } + + private void stubSuccessfulDoctorSave() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenPrescription(any())).thenReturn(7L); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + } + + @Test + void saveDoctorData_savesEverySectionAndAdvancesTheFlow() throws Exception { + stubSuccessfulDoctorSave(); + + assertEquals(1L, service.saveDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void saveDoctorData_storesTheFilesAttachedToTheVisit() throws Exception { + stubSuccessfulDoctorSave(); + + String request = "{\"beneficiaryRegID\":1,\"visitCode\":3,\"findings\":{}," + + "\"investigation\":{\"laboratoryList\":[{}]},\"diagnosis\":{}," + + "\"prescription\":[{\"drugID\":1}],\"refer\":{}," + + "\"visitDetails\":{\"visitDetails\":{\"fileIDs\":[\"a\",\"b\"]}}}"; + + assertEquals(1L, service.saveDoctorData(json(request), "auth")); + verify(benVisitDetailRepo).updateFileID(anyString(), any(), any()); + } + + @Test + void saveDoctorData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + when(commonNurseServiceImpl.saveBenPrescription(any())).thenReturn(7L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.saveDoctorData(json("{\"investigation\":{}}"), "auth")); + } + + @Test + void saveDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + stubSuccessfulDoctorSave(); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, () -> service.saveDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void saveDoctorData_failsWhenASectionCouldNotBeSaved() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(0); + when(commonNurseServiceImpl.saveBenPrescription(any())).thenReturn(7L); + + assertThrows(RuntimeException.class, () -> service.saveDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void saveDoctorData_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() throws Exception { + stubSuccessfulDoctorSave(); + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + + String extra = ",\"serviceID\":4,\"tcRequest\":{\"userID\":5,\"allocationDate\":\"2024-01-01\"," + + "\"fromTime\":\"10:00:00\",\"toTime\":\"10:30:00\"}"; + + assertEquals(1L, service.saveDoctorData(json(doctorRequest(extra)), "auth")); + verify(teleConsultationServiceImpl).createTCRequest(any()); + } + + @Test + void saveDoctorData_failsWhenTheSpecialistSlotCouldNotBeBooked() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + String extra = ",\"serviceID\":4,\"tcRequest\":{\"userID\":5,\"allocationDate\":\"2024-01-01\"," + + "\"fromTime\":\"10:00:00\",\"toTime\":\"10:30:00\"}"; + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveDoctorData(json(doctorRequest(extra)), "auth")); + assertEquals("Error while booking slot.", thrown.getMessage()); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/pnc/PNCDoctorServiceImplTest.java b/src/test/java/com/iemr/mmu/service/pnc/PNCDoctorServiceImplTest.java index 1af79328..1820caa8 100644 --- a/src/test/java/com/iemr/mmu/service/pnc/PNCDoctorServiceImplTest.java +++ b/src/test/java/com/iemr/mmu/service/pnc/PNCDoctorServiceImplTest.java @@ -21,6 +21,174 @@ */ package com.iemr.mmu.service.pnc; -public class PNCDoctorServiceImplTest { - +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.pnc.PNCDiagnosis; +import com.iemr.mmu.repo.nurse.pnc.PNCDiagnosisRepo; +import com.iemr.mmu.repo.quickConsultation.PrescriptionDetailRepo; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; + +class PNCDoctorServiceImplTest { + + @Mock + private PNCDiagnosisRepo pncDiagnosisRepo; + @Mock + private PrescriptionDetailRepo prescriptionDetailRepo; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + + @InjectMocks + private PNCDoctorServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + /** A diagnosis with two provisional and two confirmatory terms, one of them un-coded. */ + private static String diagnosisRequest() { + return "{\"beneficiaryRegID\":1,\"visitCode\":2,\"createdBy\":\"doctor\"," + + "\"provisionalDiagnosisList\":[{\"term\":\"Anaemia\",\"conceptID\":\"111\"}," + + "{\"term\":\"Fever\"},{\"conceptID\":\"333\"}]," + + "\"confirmatoryDiagnosisList\":[{\"term\":\"Anaemia\"}," + + "{\"term\":\"Fever\",\"conceptID\":\"222\"}]}"; + } + + @Test + @DisplayName("a saved diagnosis joins every named term and its concept id") + void saveBenPNCDiagnosis_joinsEveryNamedTerm() throws Exception { + PNCDiagnosis stored = new PNCDiagnosis(); + stored.setID(5L); + when(pncDiagnosisRepo.save(any())).thenReturn(stored); + + assertEquals(5L, service.saveBenPNCDiagnosis(json(diagnosisRequest()), 7L)); + + ArgumentCaptor saved = ArgumentCaptor.forClass(PNCDiagnosis.class); + org.mockito.Mockito.verify(pncDiagnosisRepo).save(saved.capture()); + assertEquals(7L, saved.getValue().getPrescriptionID()); + // The third entry carries no term, so the separator the second one appended is + // left dangling. + assertEquals("Anaemia || Fever || ", saved.getValue().getProvisionalDiagnosis()); + assertEquals("111 || N/A || ", saved.getValue().getProvisionalDiagnosisSCTCode()); + assertEquals("Anaemia || Fever", saved.getValue().getConfirmatoryDiagnosis()); + assertEquals("N/A || 222", saved.getValue().getConfirmatoryDiagnosisSCTCode()); + } + + @Test + @DisplayName("a diagnosis with no terms is stored with empty diagnosis fields") + void saveBenPNCDiagnosis_storesEmptyDiagnosisFieldsWhenNoTermWasGiven() throws Exception { + PNCDiagnosis stored = new PNCDiagnosis(); + stored.setID(5L); + when(pncDiagnosisRepo.save(any())).thenReturn(stored); + + assertEquals(5L, service.saveBenPNCDiagnosis(json("{\"beneficiaryRegID\":1}"), 7L)); + + ArgumentCaptor saved = ArgumentCaptor.forClass(PNCDiagnosis.class); + org.mockito.Mockito.verify(pncDiagnosisRepo).save(saved.capture()); + assertEquals("", saved.getValue().getProvisionalDiagnosis()); + } + + @Test + @DisplayName("a diagnosis that was not persisted reports no id") + void saveBenPNCDiagnosis_reportsNoIdWhenNothingWasStored() throws Exception { + PNCDiagnosis stored = new PNCDiagnosis(); + stored.setID(0L); + when(pncDiagnosisRepo.save(any())).thenReturn(stored); + + assertNull(service.saveBenPNCDiagnosis(json(diagnosisRequest()), 7L)); + } + + @Test + @DisplayName("a stored diagnosis is read back with its terms split into lists") + void getPNCDiagnosisDetails_splitsTheStoredTermsBackIntoLists() { + PNCDiagnosis stored = new PNCDiagnosis(); + stored.setProvisionalDiagnosis("Anaemia || Fever"); + stored.setProvisionalDiagnosisSCTCode("111 || N/A"); + stored.setConfirmatoryDiagnosis("Anaemia"); + stored.setConfirmatoryDiagnosisSCTCode("N/A"); + when(pncDiagnosisRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)) + .thenReturn(new ArrayList<>(Arrays.asList(stored))); + when(prescriptionDetailRepo.getExternalinvestigationForVisitCode(1L, 2L)).thenReturn("X-ray"); + + String result = service.getPNCDiagnosisDetails(1L, 2L); + + assertTrue(result.contains("X-ray"), result); + assertEquals(2, stored.getProvisionalDiagnosisList().size()); + assertEquals(1, stored.getConfirmatoryDiagnosisList().size()); + } + + @Test + @DisplayName("a beneficiary with no diagnosis reads back as an empty diagnosis") + void getPNCDiagnosisDetails_returnsAnEmptyDiagnosisWhenNoneWasRecorded() { + when(pncDiagnosisRepo.findByBeneficiaryRegIDAndVisitCode(1L, 2L)).thenReturn(new ArrayList<>()); + + assertEquals("{}", service.getPNCDiagnosisDetails(1L, 2L)); + } + + @Test + @DisplayName("an already-stored diagnosis is updated in place") + void updateBenPNCDiagnosis_updatesTheStoredRow() throws Exception { + PNCDiagnosis diagnosis = new PNCDiagnosis(); + diagnosis.setBeneficiaryRegID(1L); + diagnosis.setVisitCode(2L); + diagnosis.setPrescriptionID(7L); + when(pncDiagnosisRepo.getPNCDiagnosisStatus(1L, 2L, 7L)).thenReturn("P"); + when(pncDiagnosisRepo.updatePNCDiagnosis(anyString(), anyString(), any(), any(), any(), any(), any(), + eq("U"), anyLong(), anyLong(), anyString(), any(), anyString(), any(), anyLong())).thenReturn(1); + + assertEquals(1, service.updateBenPNCDiagnosis(diagnosis)); + } + + @Test + @DisplayName("a diagnosis that was never stored is inserted instead") + void updateBenPNCDiagnosis_insertsAFreshRowWhenNoneIsStoredYet() throws Exception { + PNCDiagnosis diagnosis = new PNCDiagnosis(); + PNCDiagnosis stored = new PNCDiagnosis(); + stored.setID(5L); + when(pncDiagnosisRepo.getPNCDiagnosisStatus(any(), any(), any())).thenReturn(null); + when(pncDiagnosisRepo.save(diagnosis)).thenReturn(stored); + + assertEquals(1, service.updateBenPNCDiagnosis(diagnosis)); + + stored.setID(0L); + assertEquals(0, service.updateBenPNCDiagnosis(diagnosis)); + } + + @Test + @DisplayName("an update joins every named term and its concept id") + void updateBenPNCDiagnosis_joinsEveryNamedTerm() throws Exception { + PNCDiagnosis diagnosis = com.iemr.mmu.utils.mapper.InputMapper.gson().fromJson(diagnosisRequest(), + PNCDiagnosis.class); + when(pncDiagnosisRepo.getPNCDiagnosisStatus(any(), any(), any())).thenReturn("N"); + when(pncDiagnosisRepo.updatePNCDiagnosis(anyString(), anyString(), any(), any(), any(), any(), any(), + eq("N"), anyLong(), anyLong(), anyString(), any(), anyString(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBenPNCDiagnosis(diagnosis)); + assertEquals("Anaemia || Fever || ", diagnosis.getProvisionalDiagnosis()); + assertEquals("111 || N/A || ", diagnosis.getProvisionalDiagnosisSCTCode()); + } } diff --git a/src/test/java/com/iemr/mmu/service/pnc/PNCServiceImplTest.java b/src/test/java/com/iemr/mmu/service/pnc/PNCServiceImplTest.java index fc109a3f..474af902 100644 --- a/src/test/java/com/iemr/mmu/service/pnc/PNCServiceImplTest.java +++ b/src/test/java/com/iemr/mmu/service/pnc/PNCServiceImplTest.java @@ -19,53 +19,609 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ - package com.iemr.mmu.service.pnc; -import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; -import com.google.gson.JsonObject; -import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; -import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; -import com.iemr.mmu.service.pnc.PNCDoctorServiceImpl; -import com.iemr.mmu.service.pnc.PNCNurseServiceImpl; -import com.iemr.mmu.service.tele_consultation.TeleConsultationServiceImpl; -import com.iemr.mmu.data.nurse.CommonUtilityClass; -import com.iemr.mmu.utils.mapper.InputMapper; -import com.iemr.mmu.service.anc.Utility; -import com.google.gson.JsonArray; +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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.junit.jupiter.MockitoExtension; -import java.util.HashMap; -import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -public class PNCServiceImplTest { - @Mock - private CommonNurseServiceImpl commonNurseServiceImpl; - @Mock - private CommonDoctorServiceImpl commonDoctorServiceImpl; - @Mock - private PNCNurseServiceImpl pncNurseServiceImpl; - @Mock - private PNCDoctorServiceImpl pncDoctorServiceImpl; - @Mock - private TeleConsultationServiceImpl teleConsultationServiceImpl; - @Mock - private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; - @InjectMocks - private PNCServiceImpl pncServiceImpl; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - - // Add test methods for each public method in PNCServiceImpl here + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; +import com.iemr.mmu.service.labtechnician.LabTechnicianServiceImpl; +import com.iemr.mmu.service.tele_consultation.TeleConsultationServiceImpl; + +class PNCServiceImplTest { + + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private PNCNurseServiceImpl pncNurseServiceImpl; + @Mock + private PNCDoctorServiceImpl pncDoctorServiceImpl; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private LabTechnicianServiceImpl labTechnicianServiceImpl; + @Mock + private TeleConsultationServiceImpl teleConsultationServiceImpl; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + + @InjectMocks + private PNCServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + /** The visit block every nurse save starts from. */ + private static String visitDetailsBlock() { + return "\"visitDetails\":{\"visitDetails\":{\"beneficiaryRegID\":1,\"visitReason\":\"New Chief Complaint\"," + + "\"visitCategory\":\"PNC\"},\"chiefComplaints\":[{\"chiefComplaintID\":1}]}"; + } + + @Nested + @DisplayName("saving nurse data") + class NurseSave { + + @Test + void savePNCNurseData_ignoresARequestWithoutVisitDetails() throws Exception { + assertNull(service.savePNCNurseData(null)); + assertNull(service.savePNCNurseData(json("{}"))); + assertNull(service.savePNCNurseData(json("{\"visitDetails\":null}"))); + } + + @Test + void savePNCNurseData_skipsTheVisitWhenTheNurseAlreadySavedThisFlow() throws Exception { + when(beneficiaryFlowStatusRepo.checkExistData(any(), any())).thenReturn(new BeneficiaryFlowStatus()); + + assertEquals(0L, service.savePNCNurseData(json("{" + visitDetailsBlock() + "}"))); + verify(commonNurseServiceImpl, never()).saveBeneficiaryVisitDetails(any()); + } + + @Test + void savePNCNurseData_returnsZeroWhenTheVisitWasNotCreated() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(1); + + assertEquals(0L, service.savePNCNurseData(json("{" + visitDetailsBlock() + "}"))); + } + + @Test + void savePNCNurseData_savesEverySectionAndAdvancesTheBeneficiaryFlow() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(1L); + when(pncNurseServiceImpl.saveBenPncCareDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePhyGeneralExamination(any())).thenReturn(1L); + when(commonBenStatusFlowServiceImpl.updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any())).thenReturn(1); + + String request = "{" + visitDetailsBlock() + ",\"historyDetails\":{\"pastHistory\":{}}," + + "\"pNCDeatils\":{},\"vitalDetails\":{}," + + "\"examinationDetails\":{\"generalExamination\":{}}}"; + + assertEquals(1L, service.savePNCNurseData(json(request))); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any()); + } + + @Test + void savePNCNurseData_leavesTheFlowUntouchedWhenASectionFailsToSave() throws Exception { + stubVisitCreation(); + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(0L); + + String request = "{" + visitDetailsBlock() + ",\"historyDetails\":{\"pastHistory\":{}}}"; + + assertNull(service.savePNCNurseData(json(request))); + verify(commonBenStatusFlowServiceImpl, never()).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), + anyLong(), anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any()); + } + + private void stubVisitCreation() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + } + + @Test + void saveBenVisitDetails_stampsTheVisitOntoEveryChiefComplaint() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + + Map result = service.saveBenVisitDetails( + json("{" + visitDetailsBlock() + "}").getAsJsonObject("visitDetails"), + new com.iemr.mmu.data.nurse.CommonUtilityClass()); + + assertEquals(5L, result.get("visitID")); + assertEquals(6L, result.get("visitCode")); + verify(commonNurseServiceImpl).saveBenChiefComplaints(any()); + } + + @Test + void saveBenVisitDetails_returnsNothingWhenTheVisitBlockIsMissing() throws Exception { + assertTrue(service.saveBenVisitDetails(json("{}"), new com.iemr.mmu.data.nurse.CommonUtilityClass()) + .isEmpty()); + assertTrue(service.saveBenVisitDetails(null, new com.iemr.mmu.data.nurse.CommonUtilityClass()).isEmpty()); + } + } + + @Nested + @DisplayName("saving the individual nurse sections") + class NurseSections { + + @Test + void saveBenPNCHistoryDetails_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenPNCHistoryDetails(json("{}"), 1L, 2L)); + } + + @Test + void saveBenPNCHistoryDetails_savesEverySectionThatWasSent() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenComorbidConditions(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMedicationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePersonalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveAllergyHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenFamilyHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenMenstrualHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.saveFemaleObstetricHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveImmunizationHistory(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveChildOptionalVaccineDetail(any())).thenReturn(1L); + + String history = "{\"pastHistory\":{},\"comorbidConditions\":{}," + + "\"medicationHistory\":{\"medicationHistoryList\":[{\"currentMedication\":\"x\"}]},\"personalHistory\":{}," + + "\"familyHistory\":{},\"menstrualHistory\":{},\"femaleObstetricHistory\":{}," + + "\"immunizationHistory\":{},\"childVaccineDetails\":{}}"; + + assertEquals(1L, service.saveBenPNCHistoryDetails(json(history), 1L, 2L)); + } + + @Test + void saveBenPNCHistoryDetails_treatsAnEmptyMedicationListAsAlreadyDone() throws Exception { + String history = "{\"medicationHistory\":{\"medicationHistoryList\":[]}}"; + assertEquals(1L, service.saveBenPNCHistoryDetails(json(history), 1L, 2L)); + } + + @Test + void saveBenPNCHistoryDetails_reportsFailureWhenASectionCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.saveBenPastHistory(any())).thenReturn(0L); + assertNull(service.saveBenPNCHistoryDetails(json("{\"pastHistory\":{}}"), 1L, 2L)); + } + + @Test + void saveBenPNCDetails_savesTheCareBlockWhenOneWasSent() throws Exception { + when(pncNurseServiceImpl.saveBenPncCareDetails(any())).thenReturn(3L); + assertEquals(3L, service.saveBenPNCDetails(json("{\"pNCDeatils\":{}}"), 1L, 2L)); + } + + @Test + void saveBenPNCDetails_treatsAnAbsentCareBlockAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenPNCDetails(json("{}"), 1L, 2L)); + } + + @Test + void saveBenPNCVitalDetails_savesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(5L); + + assertEquals(4L, service.saveBenPNCVitalDetails(json("{}"), 1L, 2L)); + } + + @Test + void saveBenPNCVitalDetails_reportsFailureWhenTheVitalsCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(4L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(null); + + assertNull(service.saveBenPNCVitalDetails(json("{}"), 1L, 2L)); + assertNull(service.saveBenPNCVitalDetails(null, 1L, 2L)); + } + + @Test + void saveBenExaminationDetails_savesEverySystemThatWasExamined() throws Exception { + when(commonNurseServiceImpl.savePhyGeneralExamination(any())).thenReturn(1L); + when(commonNurseServiceImpl.savePhyHeadToToeExamination(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveSysGastrointestinalExamination(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveSysCardiovascularExamination(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveSysRespiratoryExamination(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveSysCentralNervousExamination(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveSysMusculoskeletalSystemExamination(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveSysGenitourinarySystemExamination(any())).thenReturn(1L); + + String examination = "{\"generalExamination\":{},\"headToToeExamination\":{}," + + "\"gastroIntestinalExamination\":{},\"cardioVascularExamination\":{}," + + "\"respiratorySystemExamination\":{},\"centralNervousSystemExamination\":{}," + + "\"musculoskeletalSystemExamination\":{},\"genitoUrinarySystemExamination\":{}}"; + + assertEquals(1L, service.saveBenExaminationDetails(json(examination), 1L, 2L)); + } + + @Test + void saveBenExaminationDetails_treatsEveryAbsentSystemAsAlreadyDone() throws Exception { + assertEquals(1L, service.saveBenExaminationDetails(json("{}"), 1L, 2L)); + } + + @Test + void saveBenExaminationDetails_reportsFailureWhenASystemCouldNotBeSaved() throws Exception { + when(commonNurseServiceImpl.savePhyGeneralExamination(any())).thenReturn(0L); + assertNull(service.saveBenExaminationDetails(json("{\"generalExamination\":{}}"), 1L, 2L)); + } + } + + @Nested + @DisplayName("reading the nurse and doctor case sheets") + class Reads { + + @Test + void getBenVisitDetailsFrmNursePNC_gathersTheVisitAndItsComplaints() throws Exception { + when(commonNurseServiceImpl.getCSVisitDetails(1L, 2L)).thenReturn(null); + when(commonNurseServiceImpl.getBenChiefComplaints(1L, 2L)).thenReturn("[]"); + + String result = service.getBenVisitDetailsFrmNursePNC(1L, 2L); + + assertTrue(result.contains("PNCNurseVisitDetail")); + assertTrue(result.contains("BenChiefComplaints")); + } + + @Test + void getBenPNCDetailsFrmNursePNC_readsTheCareBlock() { + when(pncNurseServiceImpl.getPNCCareDetails(1L, 2L)).thenReturn("care"); + assertTrue(service.getBenPNCDetailsFrmNursePNC(1L, 2L).contains("care")); + } + + @Test + void getBenHistoryDetails_gathersEveryHistorySection() { + when(commonNurseServiceImpl.getPastHistoryData(1L, 2L)) + .thenReturn(new com.iemr.mmu.data.anc.BenMedHistory()); + + assertTrue(service.getBenHistoryDetails(1L, 2L).contains("PastHistory")); + verify(commonNurseServiceImpl).getFemaleObstetricHistory(1L, 2L); + verify(commonNurseServiceImpl).getFeedingHistory(1L, 2L); + } + + @Test + void getBeneficiaryVitalDetails_gathersAnthropometryAndVitals() { + when(commonNurseServiceImpl.getBeneficiaryPhysicalAnthropometryDetails(1L, 2L)).thenReturn("a"); + when(commonNurseServiceImpl.getBeneficiaryPhysicalVitalDetails(1L, 2L)).thenReturn("v"); + + String result = service.getBeneficiaryVitalDetails(1L, 2L); + assertTrue(result.contains("benAnthropometryDetail")); + assertTrue(result.contains("benPhysicalVitalDetail")); + } + + @Test + void getPNCExaminationDetailsData_gathersEveryExaminedSystem() { + when(commonNurseServiceImpl.getGeneralExaminationData(1L, 2L)) + .thenReturn(new com.iemr.mmu.data.anc.PhyGeneralExamination()); + + assertTrue(service.getPNCExaminationDetailsData(1L, 2L).contains("generalExamination")); + verify(commonNurseServiceImpl).getGenitourinaryExamination(1L, 2L); + } + + @Test + void getBenPNCNurseData_combinesTheFourNurseSections() { + String result = service.getBenPNCNurseData(1L, 2L); + + assertTrue(result.contains("pnc")); + assertTrue(result.contains("history")); + assertTrue(result.contains("vitals")); + assertTrue(result.contains("examination")); + } + + @Test + void getBenCaseRecordFromDoctorPNC_gathersEveryDoctorSection() throws Exception { + when(commonDoctorServiceImpl.getFindingsDetails(1L, 2L)).thenReturn("findings"); + when(pncDoctorServiceImpl.getPNCDiagnosisDetails(1L, 2L)).thenReturn("diagnosis"); + when(commonDoctorServiceImpl.getInvestigationDetails(1L, 2L)).thenReturn("investigation"); + when(commonDoctorServiceImpl.getPrescribedDrugs(1L, 2L)).thenReturn("prescription"); + when(commonDoctorServiceImpl.getReferralDetails(1L, 2L)).thenReturn("refer"); + when(labTechnicianServiceImpl.getLabResultDataForBen(1L, 2L)).thenReturn(new ArrayList<>()); + when(commonNurseServiceImpl.getGraphicalTrendData(1L, "pnc")).thenReturn(new HashMap<>()); + when(labTechnicianServiceImpl.getLast_3_ArchivedTestVisitList(1L, 2L)).thenReturn("[]"); + + String result = service.getBenCaseRecordFromDoctorPNC(1L, 2L); + + assertTrue(result.contains("findings")); + assertTrue(result.contains("LabReport")); + assertTrue(result.contains("GraphData")); + } + } + + @Nested + @DisplayName("updating nurse data") + class NurseUpdates { + + @Test + void updateBenHistoryDetails_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + assertEquals(1, service.updateBenHistoryDetails(json("{}"))); + } + + @Test + void updateBenHistoryDetails_updatesEverySectionThatWasSent() throws Exception { + when(commonNurseServiceImpl.updateBenPastHistoryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenComorbidConditions(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenMedicationHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenPersonalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenAllergicHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateBenFamilyHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateMenstrualHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePastObstetricHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildImmunizationDetail(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildOptionalVaccineDetail(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildFeedingHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePerinatalHistory(any())).thenReturn(1); + when(commonNurseServiceImpl.updateChildDevelopmentHistory(any())).thenReturn(1); + + String history = "{\"pastHistory\":{},\"comorbidConditions\":{},\"medicationHistory\":{}," + + "\"personalHistory\":{},\"familyHistory\":{},\"menstrualHistory\":{}," + + "\"femaleObstetricHistory\":{},\"immunizationHistory\":{\"benChildVaccineDetails\":[{}]}," + + "\"childVaccineDetails\":{},\"feedingHistory\":{},\"perinatalHistroy\":{}," + + "\"developmentHistory\":{}}"; + + assertEquals(1, service.updateBenHistoryDetails(json(history))); + } + + @Test + void updateBenHistoryDetails_reportsFailureWhenASectionCouldNotBeUpdated() throws Exception { + when(commonNurseServiceImpl.updateBenPastHistoryDetails(any())).thenReturn(0); + assertEquals(0, service.updateBenHistoryDetails(json("{\"pastHistory\":{}}"))); + } + + @Test + void updateBenVitalDetails_updatesAnthropometryAndVitalsTogether() throws Exception { + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(1); + + assertEquals(1, service.updateBenVitalDetails(json("{}"))); + } + + @Test + void updateBenVitalDetails_reportsFailureWhenTheVitalsCouldNotBeUpdated() throws Exception { + when(commonNurseServiceImpl.updateANCAnthropometryDetails(any())).thenReturn(1); + when(commonNurseServiceImpl.updateANCPhysicalVitalDetails(any())).thenReturn(0); + + assertEquals(0, service.updateBenVitalDetails(json("{}"))); + assertEquals(1, service.updateBenVitalDetails(null)); + } + + @Test + void updateBenExaminationDetails_updatesEverySystemThatWasSent() throws Exception { + when(commonNurseServiceImpl.updatePhyGeneralExamination(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePhyHeadToToeExamination(any())).thenReturn(1); + when(commonNurseServiceImpl.updateSysGastrointestinalExamination(any())).thenReturn(1); + when(commonNurseServiceImpl.updateSysCardiovascularExamination(any())).thenReturn(1); + when(commonNurseServiceImpl.updateSysRespiratoryExamination(any())).thenReturn(1); + when(commonNurseServiceImpl.updateSysCentralNervousExamination(any())).thenReturn(1); + when(commonNurseServiceImpl.updateSysMusculoskeletalSystemExamination(any())).thenReturn(1); + when(commonNurseServiceImpl.updateSysGenitourinarySystemExamination(any())).thenReturn(1); + + String examination = "{\"generalExamination\":{},\"headToToeExamination\":{}," + + "\"gastroIntestinalExamination\":{},\"cardioVascularExamination\":{}," + + "\"respiratorySystemExamination\":{},\"centralNervousSystemExamination\":{}," + + "\"musculoskeletalSystemExamination\":{},\"genitoUrinarySystemExamination\":{}}"; + + assertEquals(1, service.updateBenExaminationDetails(json(examination))); + } + + @Test + void updateBenExaminationDetails_treatsEveryAbsentSystemAsAlreadyDone() throws Exception { + assertEquals(1, service.updateBenExaminationDetails(json("{}"))); + } + + @Test + void updateBenExaminationDetails_reportsFailureWhenASystemCouldNotBeUpdated() throws Exception { + when(commonNurseServiceImpl.updatePhyGeneralExamination(any())).thenReturn(0); + assertEquals(0, service.updateBenExaminationDetails(json("{\"generalExamination\":{}}"))); + } + + @Test + void updateBenPNCDetails_updatesTheCareBlockWhenOneWasSent() throws Exception { + when(pncNurseServiceImpl.updateBenPNCCareDetails(any())).thenReturn(1); + assertEquals(1, service.updateBenPNCDetails(json("{\"PNCDetails\":{}}"))); + assertEquals(1, service.updateBenPNCDetails(json("{}"))); + } + } + + @Nested + @DisplayName("saving and updating doctor data") + class DoctorData { + + private String doctorRequest(String extra) { + return "{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"providerServiceMapID\":4," + + "\"createdBy\":\"doctor\",\"findings\":{},\"investigation\":{\"laboratoryList\":[{}]}," + + "\"diagnosis\":{},\"prescription\":[{\"drugID\":1}],\"refer\":{}" + extra + "}"; + } + + private void stubSuccessfulDoctorSave() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + when(pncDoctorServiceImpl.saveBenPNCDiagnosis(any(), any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + Map drugResult = new HashMap<>(); + drugResult.put("count", 1); + drugResult.put("prescribedDrugIDs", Collections.singletonList(9L)); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + } + + @Test + void savePNCDoctorData_savesEverySectionAndAdvancesTheFlow() throws Exception { + stubSuccessfulDoctorSave(); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.savePNCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void savePNCDoctorData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.savePNCDoctorData(json("{\"findings\":{},\"investigation\":{}}"), "auth")); + } + + @Test + void savePNCDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + stubSuccessfulDoctorSave(); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, () -> service.savePNCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void savePNCDoctorData_failsWhenASectionCouldNotBeSaved() throws Exception { + when(commonDoctorServiceImpl.saveDocFindings(any())).thenReturn(0); + when(commonNurseServiceImpl.savePrescriptionDetailsAndGetPrescriptionID(any(), any(), any(), any(), any(), + any(), any(), any(), any())).thenReturn(7L); + + assertThrows(RuntimeException.class, () -> service.savePNCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void savePNCDoctorData_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() throws Exception { + stubSuccessfulDoctorSave(); + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.savePNCDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + verify(teleConsultationServiceImpl).createTCRequest(any()); + } + + @Test + void savePNCDoctorData_failsWhenTheSpecialistSlotCouldNotBeBooked() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.savePNCDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + assertEquals("Error while booking slot.", thrown.getMessage()); + } + + private String teleconsultationBlock() { + return ",\"serviceID\":4,\"tcRequest\":{\"userID\":5,\"allocationDate\":\"2024-01-01\"," + + "\"fromTime\":\"10:00:00\",\"toTime\":\"10:30:00\"}"; + } + + @Test + void updatePNCDoctorData_updatesEverySectionAndAdvancesTheFlow() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(pncDoctorServiceImpl.updateBenPNCDiagnosis(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + Map drugResult = new HashMap<>(); + drugResult.put("count", 1); + drugResult.put("prescribedDrugIDs", Collections.singletonList(9L)); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.updatePNCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updatePNCDoctorData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.updatePNCDoctorData(json("{\"investigation\":{\"laboratoryList\":[{}]}}"), + "auth")); + } + + @Test + void updatePNCDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(pncDoctorServiceImpl.updateBenPNCDiagnosis(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + Map drugResult = new HashMap<>(); + drugResult.put("count", 1); + drugResult.put("prescribedDrugIDs", new ArrayList()); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, () -> service.updatePNCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updatePNCDoctorData_failsWhenASectionCouldNotBeUpdated() throws Exception { + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(0); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + + assertThrows(RuntimeException.class, () -> service.updatePNCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updatePNCDoctorData_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateDocFindings(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(pncDoctorServiceImpl.updateBenPNCDiagnosis(any())).thenReturn(1); + when(commonNurseServiceImpl.saveBenInvestigation(any())).thenReturn(1L); + Map drugResult = new HashMap<>(); + drugResult.put("count", 1); + drugResult.put("prescribedDrugIDs", Collections.singletonList(9L)); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.updatePNCDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + } + + @Test + void updatePNCDoctorData_failsWhenTheSpecialistSlotCouldNotBeBooked() throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + assertThrows(RuntimeException.class, + () -> service.updatePNCDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + } + } } diff --git a/src/test/java/com/iemr/mmu/service/quickConsultation/QuickConsultationFlowTest.java b/src/test/java/com/iemr/mmu/service/quickConsultation/QuickConsultationFlowTest.java new file mode 100644 index 00000000..f244c131 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/quickConsultation/QuickConsultationFlowTest.java @@ -0,0 +1,439 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.quickConsultation; + +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.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.benFlowStatus.BeneficiaryFlowStatus; +import com.iemr.mmu.data.quickConsultation.BenChiefComplaint; +import com.iemr.mmu.data.quickConsultation.BenClinicalObservations; +import com.iemr.mmu.data.quickConsultation.ExternalLabTestOrder; +import com.iemr.mmu.data.quickConsultation.PrescriptionDetail; +import com.iemr.mmu.repo.benFlowStatus.BeneficiaryFlowStatusRepo; +import com.iemr.mmu.repo.nurse.BenPhysicalVitalRepo; +import com.iemr.mmu.repo.quickConsultation.BenChiefComplaintRepo; +import com.iemr.mmu.repo.quickConsultation.BenClinicalObservationsRepo; +import com.iemr.mmu.repo.quickConsultation.ExternalTestOrderRepo; +import com.iemr.mmu.repo.quickConsultation.PrescriptionDetailRepo; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonDoctorServiceImpl; +import com.iemr.mmu.service.common.transaction.CommonNurseServiceImpl; +import com.iemr.mmu.service.generalOPD.GeneralOPDDoctorServiceImpl; +import com.iemr.mmu.service.labtechnician.LabTechnicianServiceImpl; +import com.iemr.mmu.service.tele_consultation.TeleConsultationServiceImpl; + +/** + * Covers the quick-consultation flows end to end - the nurse visit, the + * doctor's first save and the doctor's later update - alongside the narrower + * per-method checks in {@link QuickConsultationServiceImplTest}. + */ +class QuickConsultationFlowTest { + + @Mock + private BenChiefComplaintRepo benChiefComplaintRepo; + @Mock + private BenClinicalObservationsRepo benClinicalObservationsRepo; + @Mock + private PrescriptionDetailRepo prescriptionDetailRepo; + @Mock + private ExternalTestOrderRepo externalTestOrderRepo; + @Mock + private CommonNurseServiceImpl commonNurseServiceImpl; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private LabTechnicianServiceImpl labTechnicianServiceImpl; + @Mock + private CommonDoctorServiceImpl commonDoctorServiceImpl; + @Mock + private GeneralOPDDoctorServiceImpl generalOPDDoctorServiceImpl; + @Mock + private TeleConsultationServiceImpl teleConsultationServiceImpl; + @Mock + private BenPhysicalVitalRepo benPhysicalVitalRepo; + @Mock + private BeneficiaryFlowStatusRepo beneficiaryFlowStatusRepo; + + @InjectMocks + private QuickConsultationServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + private static String nurseRequest() { + return "{\"benFlowID\":3,\"vanID\":1,\"sessionID\":1," + + "\"visitDetails\":{\"beneficiaryRegID\":1,\"visitReason\":\"New Chief Complaint\"," + + "\"visitCategory\":\"General OPD (QC)\"},\"vitalsDetails\":{}}"; + } + + @Nested + @DisplayName("the nurse visit") + class NurseVisit { + + @Test + void quickConsultNurseDataInsert_savesTheVisitAndVitalsAndAdvancesTheFlow() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(1L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalVitalDetails(any())).thenReturn(1L); + + assertEquals(1, service.quickConsultNurseDataInsert(json(nurseRequest()))); + verify(commonBenStatusFlowServiceImpl).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), anyLong(), + anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any()); + } + + @Test + void quickConsultNurseDataInsert_reportsAVisitThatWasAlreadyCreatedRecently() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(1); + + assertEquals(3, service.quickConsultNurseDataInsert(json(nurseRequest()))); + } + + @Test + void quickConsultNurseDataInsert_skipsTheVisitWhenTheNurseAlreadySavedThisFlow() throws Exception { + when(beneficiaryFlowStatusRepo.checkExistData(any(), any())).thenReturn(new BeneficiaryFlowStatus()); + + assertEquals(0, service.quickConsultNurseDataInsert(json(nurseRequest()))); + verify(commonNurseServiceImpl, never()).saveBeneficiaryVisitDetails(any()); + } + + @Test + void quickConsultNurseDataInsert_leavesTheFlowUntouchedWhenTheVitalsFailToSave() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(5L); + when(commonNurseServiceImpl.generateVisitCode(anyLong(), any(), any())).thenReturn(6L); + when(commonNurseServiceImpl.saveBeneficiaryPhysicalAnthropometryDetails(any())).thenReturn(null); + + assertEquals(0, service.quickConsultNurseDataInsert(json(nurseRequest()))); + verify(commonBenStatusFlowServiceImpl, never()).updateBenFlowNurseAfterNurseActivity(any(), anyLong(), + anyLong(), anyString(), anyString(), any(), any(), any(), any(), any(), anyLong(), any()); + } + + @Test + void quickConsultNurseDataInsert_leavesTheFlowUntouchedWhenTheVisitWasNotCreated() throws Exception { + when(commonNurseServiceImpl.getMaxCurrentdate(any(), any(), any())).thenReturn(0); + when(commonNurseServiceImpl.saveBeneficiaryVisitDetails(any())).thenReturn(0L); + + assertEquals(0, service.quickConsultNurseDataInsert(json(nurseRequest()))); + } + + @Test + void quickConsultNurseDataInsert_ignoresARequestWithoutVisitDetails() throws Exception { + assertEquals(0, service.quickConsultNurseDataInsert(json("{}"))); + assertEquals(0, service.quickConsultNurseDataInsert(null)); + } + } + + @Nested + @DisplayName("the individual saves") + class Saves { + + @Test + void saveBeneficiaryChiefComplaint_storesEveryComplaintAndStampsItsVanSerial() { + BenChiefComplaint stored = new BenChiefComplaint(); + stored.setBenChiefComplaintID(9L); + when(benChiefComplaintRepo.saveAll(any())).thenReturn(Collections.singletonList(stored)); + + String request = "{\"chiefComplaintList\":[{\"chiefComplaintID\":1,\"chiefComplaint\":\"Fever\"}]}"; + + assertEquals(1L, service.saveBeneficiaryChiefComplaint(json(request))); + verify(benChiefComplaintRepo).updateVanSerialNo(9L); + } + + @Test + void saveBeneficiaryChiefComplaint_succeedsWhenNoComplaintWasEntered() { + assertEquals(1L, service.saveBeneficiaryChiefComplaint(json("{}"))); + verify(benChiefComplaintRepo, never()).saveAll(any()); + } + + @Test + void saveBeneficiaryChiefComplaint_reportsFailureWhenNotEveryComplaintWasStored() { + when(benChiefComplaintRepo.saveAll(any())).thenReturn(new ArrayList<>()); + + String request = "{\"chiefComplaintList\":[{\"chiefComplaintID\":1,\"chiefComplaint\":\"Fever\"}]}"; + + assertNull(service.saveBeneficiaryChiefComplaint(json(request))); + } + + @Test + void saveBeneficiaryClinicalObservations_attachesTheSnomedCodesOfTheSymptoms() throws Exception { + when(commonDoctorServiceImpl.getSnomedCTcode("Fever")) + .thenReturn(new String[] { "111", "Fever" }); + BenClinicalObservations stored = new BenClinicalObservations(); + stored.setClinicalObservationID(4L); + when(benClinicalObservationsRepo.save(any())).thenReturn(stored); + + assertEquals(4L, service.saveBeneficiaryClinicalObservations(json("{\"otherSymptoms\":\"Fever\"}"))); + } + + @Test + void saveBeneficiaryClinicalObservations_reportsNothingWhenTheRowWasNotStored() throws Exception { + BenClinicalObservations stored = new BenClinicalObservations(); + stored.setClinicalObservationID(0L); + when(benClinicalObservationsRepo.save(any())).thenReturn(stored); + + assertNull(service.saveBeneficiaryClinicalObservations(json("{}"))); + } + + @Test + void saveBenPrescriptionForANC_returnsTheStoredPrescriptionId() { + PrescriptionDetail prescription = new PrescriptionDetail(); + PrescriptionDetail stored = new PrescriptionDetail(); + stored.setPrescriptionID(7L); + when(prescriptionDetailRepo.save(prescription)).thenReturn(stored); + assertEquals(7L, service.saveBenPrescriptionForANC(prescription)); + + stored.setPrescriptionID(0L); + assertNull(service.saveBenPrescriptionForANC(prescription)); + } + + @Test + void saveBeneficiaryExternalLabTestOrderDetails_returnsTheStoredOrderId() { + ExternalLabTestOrder stored = new ExternalLabTestOrder(); + // The id is assigned by the database, so it is set through the field. + org.springframework.test.util.ReflectionTestUtils.setField(stored, "externalTestOrderID", 8L); + when(externalTestOrderRepo.save(any())).thenReturn(stored); + assertEquals(8L, service.saveBeneficiaryExternalLabTestOrderDetails(json("{}"))); + + org.springframework.test.util.ReflectionTestUtils.setField(stored, "externalTestOrderID", 0L); + assertNull(service.saveBeneficiaryExternalLabTestOrderDetails(json("{}"))); + } + + @Test + void updateBeneficiaryClinicalObservations_delegatesToTheDoctorService() throws Exception { + when(commonDoctorServiceImpl.getSnomedCTcode(any())).thenReturn(new String[] { "111", "Fever" }); + when(commonDoctorServiceImpl.updateBenClinicalObservations(any())).thenReturn(1); + + assertEquals(1, service.updateBeneficiaryClinicalObservations(json("{\"otherSymptoms\":\"Fever\"}"))); + } + } + + @Nested + @DisplayName("the doctor's save and update") + class DoctorData { + + private String doctorRequest(String extra) { + return "{\"beneficiaryRegID\":1,\"benVisitID\":2,\"visitCode\":3,\"providerServiceMapID\":4," + + "\"createdBy\":\"doctor\",\"prescriptionID\":7," + + "\"chiefComplaintList\":[{\"chiefComplaintID\":1,\"chiefComplaint\":\"Fever\"}]," + + "\"labTestOrders\":[{\"testID\":1}],\"prescription\":[{\"drugID\":1}]," + + "\"rbsTestResult\":\"90\",\"refer\":{}" + extra + "}"; + } + + private String teleconsultationBlock() { + return ",\"serviceID\":4,\"tcRequest\":{\"userID\":5,\"allocationDate\":\"2024-01-01\"," + + "\"fromTime\":\"10:00:00\",\"toTime\":\"10:30:00\"}"; + } + + private Map drugResult() { + Map result = new HashMap<>(); + result.put("count", 1); + result.put("prescribedDrugIDs", Collections.singletonList(9L)); + return result; + } + + @BeforeEach + void stubTheCommonSaves() throws Exception { + BenChiefComplaint storedComplaint = new BenChiefComplaint(); + storedComplaint.setBenChiefComplaintID(9L); + when(benChiefComplaintRepo.saveAll(any())).thenReturn(Collections.singletonList(storedComplaint)); + when(commonDoctorServiceImpl.getSnomedCTcode(any())).thenReturn(new String[] { "111", "Fever" }); + when(commonNurseServiceImpl.saveBenPrescribedDrugsList(any())).thenReturn(drugResult()); + when(commonNurseServiceImpl.saveBeneficiaryLabTestOrderDetails(any(), any())).thenReturn(1L); + when(benPhysicalVitalRepo.updatePhysicalVitalDetailsQCDoctor(any(), any(), any(), any())).thenReturn(1); + when(commonDoctorServiceImpl.saveBenReferDetails(any())).thenReturn(1L); + when(commonDoctorServiceImpl.updateBenReferDetails(any())).thenReturn(1L); + } + + @Test + void quickConsultDoctorDataInsert_savesEverySectionAndAdvancesTheFlow() throws Exception { + BenClinicalObservations storedObservation = new BenClinicalObservations(); + storedObservation.setClinicalObservationID(4L); + when(benClinicalObservationsRepo.save(any())).thenReturn(storedObservation); + when(commonNurseServiceImpl.saveBeneficiaryPrescription(any())).thenReturn(7L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1, service.quickConsultDoctorDataInsert(json(doctorRequest("")), "auth")); + } + + @Test + void quickConsultDoctorDataInsert_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + BenClinicalObservations storedObservation = new BenClinicalObservations(); + storedObservation.setClinicalObservationID(4L); + when(benClinicalObservationsRepo.save(any())).thenReturn(storedObservation); + when(commonNurseServiceImpl.saveBeneficiaryPrescription(any())).thenReturn(7L); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, + () -> service.quickConsultDoctorDataInsert(json(doctorRequest("")), "auth")); + } + + @Test + void quickConsultDoctorDataInsert_failsWhenASectionCouldNotBeSaved() throws Exception { + BenClinicalObservations storedObservation = new BenClinicalObservations(); + storedObservation.setClinicalObservationID(0L); + when(benClinicalObservationsRepo.save(any())).thenReturn(storedObservation); + when(commonNurseServiceImpl.saveBeneficiaryPrescription(any())).thenReturn(7L); + + assertThrows(RuntimeException.class, + () -> service.quickConsultDoctorDataInsert(json(doctorRequest("")), "auth")); + } + + @Test + void quickConsultDoctorDataInsert_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() + throws Exception { + BenClinicalObservations storedObservation = new BenClinicalObservations(); + storedObservation.setClinicalObservationID(4L); + when(benClinicalObservationsRepo.save(any())).thenReturn(storedObservation); + when(commonNurseServiceImpl.saveBeneficiaryPrescription(any())).thenReturn(7L); + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataSave(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1, + service.quickConsultDoctorDataInsert(json(doctorRequest(teleconsultationBlock())), "auth")); + verify(teleConsultationServiceImpl).createTCRequest(any()); + } + + @Test + void quickConsultDoctorDataInsert_failsWhenTheSpecialistSlotCouldNotBeBooked() { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.quickConsultDoctorDataInsert(json(doctorRequest(teleconsultationBlock())), "auth")); + assertEquals("Error while booking slot.", thrown.getMessage()); + } + + @Test + void updateGeneralOPDQCDoctorData_updatesEverySectionAndAdvancesTheFlow() throws Exception { + when(commonDoctorServiceImpl.updateBenClinicalObservations(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, service.updateGeneralOPDQCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updateGeneralOPDQCDoctorData_failsWhenTheBeneficiaryFlowCouldNotBeAdvanced() throws Exception { + when(commonDoctorServiceImpl.updateBenClinicalObservations(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(0); + + assertThrows(RuntimeException.class, + () -> service.updateGeneralOPDQCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updateGeneralOPDQCDoctorData_failsWhenASectionCouldNotBeUpdated() throws Exception { + when(commonDoctorServiceImpl.updateBenClinicalObservations(any())).thenReturn(0); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + + assertThrows(RuntimeException.class, + () -> service.updateGeneralOPDQCDoctorData(json(doctorRequest("")), "auth")); + } + + @Test + void updateGeneralOPDQCDoctorData_treatsEveryAbsentSectionAsAlreadyDone() throws Exception { + when(commonDoctorServiceImpl.updateBenClinicalObservations(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + String minimal = "{\"beneficiaryRegID\":1,\"prescriptionID\":7," + + "\"chiefComplaintList\":[{\"chiefComplaintID\":1,\"chiefComplaint\":\"Fever\"}]}"; + + assertEquals(1L, service.updateGeneralOPDQCDoctorData(json(minimal), "auth")); + } + + @Test + void updateGeneralOPDQCDoctorData_booksTheSpecialistSlotBeforeRaisingATeleconsultationRequest() + throws Exception { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(1); + when(teleConsultationServiceImpl.createTCRequest(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateBenClinicalObservations(any())).thenReturn(1); + when(commonNurseServiceImpl.updatePrescription(any())).thenReturn(1); + when(commonDoctorServiceImpl.updateBenFlowtableAfterDocDataUpdate(any(), any(), any(), any(), any())) + .thenReturn(1); + + assertEquals(1L, + service.updateGeneralOPDQCDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + } + + @Test + void updateGeneralOPDQCDoctorData_failsWhenTheSpecialistSlotCouldNotBeBooked() { + when(commonDoctorServiceImpl.callTmForSpecialistSlotBook(any(), anyString())).thenReturn(0); + + assertThrows(RuntimeException.class, () -> service + .updateGeneralOPDQCDoctorData(json(doctorRequest(teleconsultationBlock())), "auth")); + } + } + + @Nested + @DisplayName("the case sheet reads") + class Reads { + + @Test + void getBenQuickConsultNurseData_carriesTheVitals() { + when(commonNurseServiceImpl.getBeneficiaryPhysicalAnthropometryDetails(1L, 2L)).thenReturn("a"); + when(commonNurseServiceImpl.getBeneficiaryPhysicalVitalDetails(1L, 2L)).thenReturn("v"); + + assertTrue(service.getBenQuickConsultNurseData(1L, 2L).contains("vitals")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/registrar/RegistrarServiceImplTest.java b/src/test/java/com/iemr/mmu/service/registrar/RegistrarServiceImplTest.java new file mode 100644 index 00000000..958b1687 --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/registrar/RegistrarServiceImplTest.java @@ -0,0 +1,612 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.registrar; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedConstruction; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.mmu.data.registrar.BeneficiaryData; +import com.iemr.mmu.data.registrar.BeneficiaryDemographicAdditional; +import com.iemr.mmu.data.registrar.BeneficiaryDemographicData; +import com.iemr.mmu.data.registrar.BeneficiaryImage; +import com.iemr.mmu.data.registrar.BeneficiaryPhoneMapping; +import com.iemr.mmu.data.registrar.V_BenAdvanceSearch; +import com.iemr.mmu.repo.registrar.BeneficiaryDemographicAdditionalRepo; +import com.iemr.mmu.repo.registrar.BeneficiaryImageRepo; +import com.iemr.mmu.repo.registrar.RegistrarRepoBenData; +import com.iemr.mmu.repo.registrar.RegistrarRepoBenDemoData; +import com.iemr.mmu.repo.registrar.RegistrarRepoBenGovIdMapping; +import com.iemr.mmu.repo.registrar.RegistrarRepoBenPhoneMapData; +import com.iemr.mmu.repo.registrar.RegistrarRepoBeneficiaryDetails; +import com.iemr.mmu.repo.registrar.ReistrarRepoBenSearch; +import com.iemr.mmu.service.benFlowStatus.CommonBenStatusFlowServiceImpl; +import com.iemr.mmu.utils.CookieUtil; + +class RegistrarServiceImplTest { + + @Mock + private RegistrarRepoBenData registrarRepoBenData; + @Mock + private RegistrarRepoBenDemoData registrarRepoBenDemoData; + @Mock + private RegistrarRepoBenPhoneMapData registrarRepoBenPhoneMapData; + @Mock + private RegistrarRepoBenGovIdMapping registrarRepoBenGovIdMapping; + @Mock + private ReistrarRepoBenSearch reistrarRepoBenSearch; + @Mock + private BeneficiaryDemographicAdditionalRepo beneficiaryDemographicAdditionalRepo; + @Mock + private RegistrarRepoBeneficiaryDetails registrarRepoBeneficiaryDetails; + @Mock + private BeneficiaryImageRepo beneficiaryImageRepo; + @Mock + private CommonBenStatusFlowServiceImpl commonBenStatusFlowServiceImpl; + @Mock + private CookieUtil cookieUtil; + + @InjectMocks + private RegistrarServiceImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(service, "registrationUrl", "http://registry/register"); + ReflectionTestUtils.setField(service, "registrarQuickSearchByIdUrl", "http://registry/searchById"); + ReflectionTestUtils.setField(service, "registrarQuickSearchByPhoneNoUrl", "http://registry/searchByPhone"); + ReflectionTestUtils.setField(service, "beneficiaryEditUrl", "http://registry/edit"); + ReflectionTestUtils.setField(service, "registrarAdvanceSearchUrl", "http://registry/advanceSearch"); + } + + private static JsonObject json(String raw) { + return JsonParser.parseString(raw).getAsJsonObject(); + } + + /** A registration request with every optional field the mappers look for. */ + private static String fullBeneficiary() { + return "{\"firstName\":\"Asha\",\"lastName\":\"Devi\",\"gender\":2," + + "\"dob\":\"1990-05-04T00:00:00.000Z\",\"maritalStatus\":1,\"createdBy\":\"registrar\"," + + "\"fatherName\":\"Ram\",\"husbandName\":\"Shyam\",\"aadharNo\":\"1234\"," + + "\"beneficiaryRegID\":1,\"modifiedBy\":\"registrar\",\"countryID\":1,\"stateID\":2," + + "\"districtID\":3,\"blockID\":4,\"servicePointID\":5,\"villageID\":6,\"community\":1," + + "\"religion\":2,\"occupation\":3,\"educationQualification\":4,\"income\":5," + + "\"benDemographicsID\":7,\"phoneNo\":\"9999999999\",\"benPhMapID\":8," + + "\"literacyStatus\":\"Literate\",\"motherName\":\"Sita\",\"emailID\":\"a@b.c\"," + + "\"bankName\":\"Bank\",\"branchName\":\"Branch\",\"IFSCCode\":\"IFSC1\"," + + "\"accountNumber\":\"123\",\"ageAtMarriage\":20,\"age\":34,\"benDemoAdditionalID\":9," + + "\"image\":\"base64\",\"benImageID\":10}"; + } + + @Nested + @DisplayName("creating a beneficiary") + class Create { + + @Test + void createBeneficiary_storesTheMappedBeneficiary() { + BeneficiaryData stored = new BeneficiaryData(); + when(registrarRepoBenData.save(any())).thenReturn(stored); + assertEquals(stored, service.createBeneficiary(json(fullBeneficiary()))); + } + + @Test + void createBeneficiaryDemographic_returnsTheStoredDemographicId() { + BeneficiaryDemographicData stored = new BeneficiaryDemographicData(); + stored.setBenDemographicsID(5L); + when(registrarRepoBenDemoData.save(any())).thenReturn(stored); + assertEquals(5L, service.createBeneficiaryDemographic(json(fullBeneficiary()), 1L)); + + when(registrarRepoBenDemoData.save(any())).thenReturn(null); + assertNull(service.createBeneficiaryDemographic(json("{}"), 1L)); + } + + @Test + void createBeneficiaryDemographicAdditional_returnsTheStoredId() { + BeneficiaryDemographicAdditional stored = new BeneficiaryDemographicAdditional(); + stored.setBenDemoAdditionalID(6L); + when(beneficiaryDemographicAdditionalRepo.save(any())).thenReturn(stored); + assertEquals(6L, service.createBeneficiaryDemographicAdditional(json(fullBeneficiary()), 1L)); + + when(beneficiaryDemographicAdditionalRepo.save(any())).thenReturn(null); + assertNull(service.createBeneficiaryDemographicAdditional(json("{}"), 1L)); + } + + @Test + void createBeneficiaryImage_returnsTheStoredBeneficiaryId() { + BeneficiaryImage stored = new BeneficiaryImage(); + stored.setBeneficiaryRegID(1L); + when(beneficiaryImageRepo.save(any())).thenReturn(stored); + + assertEquals(1L, service.createBeneficiaryImage( + json("{\"image\":\"base64\",\"createdBy\":\"registrar\"}"), 1L)); + + when(beneficiaryImageRepo.save(any())).thenReturn(null); + assertNull(service.createBeneficiaryImage(json("{\"image\":null,\"createdBy\":null}"), 1L)); + } + + @Test + void createBeneficiaryPhoneMapping_returnsTheStoredMappingId() { + BeneficiaryPhoneMapping stored = new BeneficiaryPhoneMapping(); + stored.setBenPhMapID(7L); + when(registrarRepoBenPhoneMapData.save(any())).thenReturn(stored); + assertEquals(7L, service.createBeneficiaryPhoneMapping(json(fullBeneficiary()), 1L)); + + when(registrarRepoBenPhoneMapData.save(any())).thenReturn(null); + assertNull(service.createBeneficiaryPhoneMapping(json("{}"), 1L)); + } + + @Test + void createBenGovIdMapping_reportsHowManyIdentitiesWereStored() { + when(registrarRepoBenGovIdMapping.saveAll(any())).thenAnswer(invocation -> { + List saved = invocation.getArgument(0); + return new ArrayList<>(saved); + }); + + String request = "{\"createdBy\":\"registrar\",\"govID\":[{\"type\":1,\"value\":\"1234\"},{}]}"; + assertEquals(2, service.createBenGovIdMapping(json(request), 1L)); + } + } + + @Nested + @DisplayName("mapping the request onto the stored beneficiary") + class Mapping { + + @Test + void getBenOBJ_readsEveryOptionalFieldOfTheRequest() { + BeneficiaryData mapped = service.getBenOBJ(json(fullBeneficiary())); + + assertEquals("Asha", mapped.getFirstName()); + assertEquals("Devi", mapped.getLastName()); + assertEquals((short) 2, mapped.getGenderID()); + assertNotNull(mapped.getDob()); + assertEquals("Ram", mapped.getFatherName()); + assertEquals("Shyam", mapped.getSpouseName()); + assertEquals("1234", mapped.getAadharNo()); + assertEquals(1L, mapped.getBeneficiaryRegID()); + assertEquals("registrar", mapped.getModifiedBy()); + } + + @Test + void getBenOBJ_leavesTheDateOfBirthUnsetWhenItCannotBeParsed() { + assertNull(service.getBenOBJ(json("{\"dob\":\"not-a-date\"}")).getDob()); + } + + @Test + void getBenOBJ_toleratesAnEmptyRequest() { + assertNotNull(service.getBenOBJ(json("{}"))); + assertNull(service.getBenOBJ(json("{\"husbandName\":null,\"beneficiaryRegID\":null," + + "\"modifiedBy\":null}")).getSpouseName()); + } + + @Test + void getBenDemoOBJ_readsEveryOptionalFieldOfTheRequest() { + BeneficiaryDemographicData mapped = service.getBenDemoOBJ(json(fullBeneficiary()), 1L); + + assertEquals(1, mapped.getCountryID()); + assertEquals(6, mapped.getDistrictBranchID()); + assertEquals((short) 5, mapped.getIncomeStatusID()); + assertEquals(7L, mapped.getBenDemographicsID()); + } + + @Test + void getBenDemoOBJ_toleratesAnEmptyRequest() { + assertEquals(1L, service.getBenDemoOBJ(json("{}"), 1L).getBeneficiaryRegID()); + } + + @Test + void getBenPhoneOBJ_readsEveryOptionalFieldOfTheRequest() { + BeneficiaryPhoneMapping mapped = service.getBenPhoneOBJ(json(fullBeneficiary()), 1L); + + assertEquals("9999999999", mapped.getPhoneNo()); + assertEquals(8L, mapped.getBenPhMapID()); + assertEquals("registrar", mapped.getModifiedBy()); + } + + @Test + void getBenPhoneOBJ_toleratesAnEmptyRequest() { + assertEquals(1L, service.getBenPhoneOBJ(json("{}"), 1L).getBenificiaryRegID()); + } + } + + @Nested + @DisplayName("searching and work lists") + class Search { + + @Test + void getRegWorkList_serialisesTheStoredWorklist() { + when(registrarRepoBenData.getRegistrarWorkList(1)).thenReturn(new ArrayList<>()); + assertNotNull(service.getRegWorkList(1)); + } + + @Test + void getQuickSearchBenData_serialisesTheMatchingBeneficiaries() { + when(reistrarRepoBenSearch.getQuickSearch("BEN1")).thenReturn(new ArrayList<>()); + assertNotNull(service.getQuickSearchBenData("BEN1")); + } + + @Test + void getAdvanceSearchBenData_passesEveryProvidedCriterionToTheQuery() { + V_BenAdvanceSearch criteria = new V_BenAdvanceSearch(); + criteria.setBeneficiaryID("BEN1"); + criteria.setFirstName("Asha"); + criteria.setLastName("Devi"); + criteria.setFatherName("Ram"); + criteria.setPhoneNo("9999999999"); + criteria.setAadharNo("1234"); + criteria.setGovtIdentityNo("GOV1"); + criteria.setStateID(2); + criteria.setDistrictID(3); + when(reistrarRepoBenSearch.getAdvanceBenSearchList("BEN1", "Asha", "Devi", "9999999999", "1234", "GOV1", + "2", "3")).thenReturn(new ArrayList<>()); + + assertNotNull(service.getAdvanceSearchBenData(criteria)); + } + + @Test + void getAdvanceSearchBenData_fallsBackToWildcardsWhenNoCriterionWasGiven() { + when(reistrarRepoBenSearch.getAdvanceBenSearchList("%%", "", "", "%%", "%%", "%%", "%%", "%%")) + .thenReturn(new ArrayList<>()); + + assertNotNull(service.getAdvanceSearchBenData(new V_BenAdvanceSearch())); + } + + @Test + void getAdvanceSearchBenData_returnsNothingWhenTheQueryFails() { + when(reistrarRepoBenSearch.getAdvanceBenSearchList(anyString(), anyString(), anyString(), anyString(), + anyString(), anyString(), anyString(), anyString())).thenThrow(new RuntimeException("db down")); + + assertEquals("", service.getAdvanceSearchBenData(new V_BenAdvanceSearch())); + } + } + + @Nested + @DisplayName("reading a beneficiary") + class Reads { + + /** One beneficiary-details row, wide enough for the details mapper. */ + private List detailRows(Boolean isGovType) { + Object[] row = new Object[40]; + row[24] = Short.valueOf((short) 1); + row[25] = "1234"; + row[26] = isGovType; + List rows = new ArrayList<>(); + rows.add(row); + return rows; + } + + @Test + void getBeneficiaryDetails_separatesGovernmentIdentitiesFromTheOthers() { + when(registrarRepoBeneficiaryDetails.getBeneficiaryDetails(1L)).thenReturn(detailRows(Boolean.TRUE)); + when(beneficiaryImageRepo.getBenImage(1L)).thenReturn("base64"); + + assertTrue(service.getBeneficiaryDetails(1L).contains("1234")); + } + + @Test + void getBeneficiaryDetails_recordsANonGovernmentIdentitySeparately() { + when(registrarRepoBeneficiaryDetails.getBeneficiaryDetails(1L)).thenReturn(detailRows(Boolean.FALSE)); + + assertNotNull(service.getBeneficiaryDetails(1L)); + } + + @Test + void getBeneficiaryDetails_toleratesARowWithoutAnIdentityType() { + when(registrarRepoBeneficiaryDetails.getBeneficiaryDetails(1L)).thenReturn(detailRows(null)); + + assertNotNull(service.getBeneficiaryDetails(1L)); + } + + @Test + void getBeneficiaryDetails_returnsNothingForAnUnknownBeneficiary() { + when(registrarRepoBeneficiaryDetails.getBeneficiaryDetails(1L)).thenReturn(new ArrayList<>()); + assertNull(service.getBeneficiaryDetails(1L)); + } + + @Test + void getBenImage_returnsTheStoredImage() { + when(beneficiaryImageRepo.getBenImage(1L)).thenReturn("base64"); + assertTrue(service.getBenImage(1L).contains("base64")); + + when(beneficiaryImageRepo.getBenImage(2L)).thenReturn(null); + assertEquals("{}", service.getBenImage(2L)); + } + + @Test + void getBeneficiaryPersonalDetails_namesTheGenderAndAttachesTheServicePoint() { + Object[] detailRow = new Object[30]; + detailRow[0] = 1L; + List benRows = new ArrayList<>(); + benRows.add(detailRow); + when(registrarRepoBenData.getBenDetailsByRegID(1L)).thenReturn(benRows); + + Object[] demoRow = new Object[10]; + demoRow[2] = "PHC Alpha"; + List demoRows = new ArrayList<>(); + demoRows.add(demoRow); + when(registrarRepoBenDemoData.getBeneficiaryDemographicData(1L)).thenReturn(demoRows); + + BeneficiaryData details = service.getBeneficiaryPersonalDetails(1L); + + assertNotNull(details); + assertEquals("PHC Alpha", details.getServicePointName()); + } + + @Test + void getBeneficiaryPersonalDetails_returnsNothingForAnUnknownBeneficiary() { + when(registrarRepoBenData.getBenDetailsByRegID(1L)).thenReturn(new ArrayList<>()); + when(registrarRepoBenDemoData.getBeneficiaryDemographicData(1L)).thenReturn(new ArrayList<>()); + + assertNull(service.getBeneficiaryPersonalDetails(1L)); + } + } + + @Nested + @DisplayName("updating a beneficiary") + class Updates { + + @Test + void updateBeneficiary_updatesTheStoredBeneficiary() { + when(registrarRepoBenData.updateBeneficiaryData(any(), any(), any(), any(), any(), any(), any(), any(), + any(), any())).thenReturn(1); + assertEquals(1, service.updateBeneficiary(json(fullBeneficiary()))); + } + + @Test + void updateBeneficiaryDemographic_updatesTheStoredDemographics() { + when(registrarRepoBenDemoData.updateBendemographicData(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any())).thenReturn(1); + assertEquals(1, service.updateBeneficiaryDemographic(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBeneficiaryPhoneMapping_updatesTheStoredPhoneNumber() { + when(registrarRepoBenPhoneMapData.updateBenPhoneMap(any(), any(), any())).thenReturn(1); + assertEquals(1, service.updateBeneficiaryPhoneMapping(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBenGovIdMapping_replacesTheStoredIdentities() { + when(registrarRepoBenGovIdMapping.saveAll(any())).thenReturn(new ArrayList<>()); + + assertEquals(0, service.updateBenGovIdMapping(json("{\"govID\":[]}"), 1L)); + verify(registrarRepoBenGovIdMapping).deletePreviousGovMapID(1L); + } + + @Test + void updateBeneficiaryDemographicAdditional_updatesTheStoredRowWhenOneExists() { + when(beneficiaryDemographicAdditionalRepo.getBeneficiaryDemographicAdditional(1L)) + .thenReturn(new BeneficiaryDemographicAdditional()); + when(beneficiaryDemographicAdditionalRepo.updateBeneficiaryDemographicAdditional(any(), any(), any(), + any(), any(), any(), any(), any(), any())).thenReturn(1); + + assertEquals(1, service.updateBeneficiaryDemographicAdditional(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBeneficiaryDemographicAdditional_insertsAFreshRowWhenNoneIsStoredYet() { + BeneficiaryDemographicAdditional stored = new BeneficiaryDemographicAdditional(); + stored.setBenDemoAdditionalID(5L); + when(beneficiaryDemographicAdditionalRepo.getBeneficiaryDemographicAdditional(1L)).thenReturn(null); + when(beneficiaryDemographicAdditionalRepo.save(any())).thenReturn(stored); + + assertEquals(1, service.updateBeneficiaryDemographicAdditional(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBeneficiaryDemographicAdditional_reportsFailureWhenTheFreshRowWasNotStored() { + BeneficiaryDemographicAdditional stored = new BeneficiaryDemographicAdditional(); + stored.setBenDemoAdditionalID(0L); + when(beneficiaryDemographicAdditionalRepo.getBeneficiaryDemographicAdditional(1L)).thenReturn(null); + when(beneficiaryDemographicAdditionalRepo.save(any())).thenReturn(stored); + + assertEquals(0, service.updateBeneficiaryDemographicAdditional(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBeneficiaryImage_updatesTheStoredImageWhenOneExists() { + when(beneficiaryImageRepo.findBenImage(1L)).thenReturn(1L); + when(beneficiaryImageRepo.updateBeneficiaryImage(anyString(), any(), anyLong())).thenReturn(1); + + assertEquals(1, service.updateBeneficiaryImage(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBeneficiaryImage_insertsAFreshImageWhenNoneIsStoredYet() { + BeneficiaryImage stored = new BeneficiaryImage(); + stored.setBenImageID(5L); + when(beneficiaryImageRepo.findBenImage(1L)).thenReturn(null); + when(beneficiaryImageRepo.save(any())).thenReturn(stored); + + assertEquals(1, service.updateBeneficiaryImage(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBeneficiaryImage_reportsFailureWhenTheFreshImageWasNotStored() { + BeneficiaryImage stored = new BeneficiaryImage(); + stored.setBenImageID(0L); + when(beneficiaryImageRepo.findBenImage(1L)).thenReturn(null); + when(beneficiaryImageRepo.save(any())).thenReturn(stored); + + assertEquals(0, service.updateBeneficiaryImage(json(fullBeneficiary()), 1L)); + } + + @Test + void updateBeneficiaryImage_succeedsWithoutAnImageToStore() { + assertEquals(1, service.updateBeneficiaryImage(json("{}"), 1L)); + } + } + + @Nested + @DisplayName("talking to the central identity service") + class CentralIdentityService { + + @Test + void registerBeneficiary_createsTheBeneficiaryFlowRecordForANewRegistration() throws Exception { + String body = "{\"data\":{\"beneficiaryRegID\":1,\"beneficiaryID\":2}}"; + when(commonBenStatusFlowServiceImpl.createBenFlowRecord(anyString(), eq(1L), eq(2L))).thenReturn(1); + + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(body, HttpStatus.OK)))) { + + assertTrue(service.registerBeneficiary("{}", "auth", "token").contains("successfully registered")); + } + } + + @Test + void registerBeneficiary_reportsAnErrorWhenTheFlowRecordCouldNotBeCreated() throws Exception { + String body = "{\"data\":{\"beneficiaryRegID\":1,\"beneficiaryID\":2}}"; + when(commonBenStatusFlowServiceImpl.createBenFlowRecord(anyString(), eq(1L), eq(2L))).thenReturn(0); + + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(body, HttpStatus.OK)))) { + + assertTrue(service.registerBeneficiary("{}", "auth", "token").contains("contact administrator")); + } + } + + @Test + void registerBeneficiary_reportsTheGenericFailureWhenTheRegistryRejectsTheRequest() throws Exception { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.BAD_REQUEST)))) { + + assertTrue(service.registerBeneficiary("{}", "auth", "token").contains("FAILURE")); + } + } + + @Test + void updateBeneficiary_passesTheBeneficiaryToTheNurseWhenTheRegistrarAskedForIt() throws Exception { + when(commonBenStatusFlowServiceImpl.createBenFlowRecord(anyString(), eq(null), eq(null))).thenReturn(1); + + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("", HttpStatus.OK)))) { + + assertEquals(1, service.updateBeneficiary("{\"passToNurse\":true}", "auth", "token")); + } + } + + @Test + void updateBeneficiary_leavesTheBeneficiaryWithTheRegistrarByDefault() throws Exception { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("", HttpStatus.OK)))) { + + assertEquals(1, service.updateBeneficiary("{\"passToNurse\":false}", "auth", "token")); + } + } + + @Test + void updateBeneficiary_returnsNothingWhenTheRegistryRejectsTheRequest() throws Exception { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.BAD_REQUEST)))) { + + assertNull(service.updateBeneficiary("{}", "auth", "token")); + } + } + + @Test + void beneficiaryQuickSearch_searchesByBeneficiaryIdWhenOneWasGiven() { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("byId", HttpStatus.OK)))) { + + assertEquals("byId", service.beneficiaryQuickSearch("{\"beneficiaryID\":\"BEN1\"}", "auth", "token")); + } + } + + @Test + void beneficiaryQuickSearch_searchesByPhoneNumberWhenNoIdWasGiven() { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("byPhone", HttpStatus.OK)))) { + + assertEquals("byPhone", + service.beneficiaryQuickSearch("{\"phoneNo\":\"9999999999\"}", "auth", "token")); + } + } + + @Test + void beneficiaryQuickSearch_returnsNothingWhenNoCriterionWasGiven() { + try (MockedConstruction rest = mockConstruction(RestTemplate.class)) { + assertNull(service.beneficiaryQuickSearch("{}", "auth", "token")); + } + } + + @Test + void beneficiaryAdvanceSearch_returnsTheRegistryResponse() { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>("matches", HttpStatus.OK)))) { + + assertEquals("matches", service.beneficiaryAdvanceSearch("{}", "auth", "token")); + } + } + + @Test + void beneficiaryAdvanceSearch_returnsNothingWhenTheRegistryHasNoBody() { + try (MockedConstruction rest = mockConstruction(RestTemplate.class, + (mock, context) -> when(mock.exchange(anyString(), eq(HttpMethod.POST), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(HttpStatus.OK)))) { + + assertNull(service.beneficiaryAdvanceSearch("{}", "auth", "token")); + } + } + + @Test + void searchAndSubmitBeneficiaryToNurse_createsTheBeneficiaryFlowRecord() throws Exception { + when(commonBenStatusFlowServiceImpl.createBenFlowRecord("{}", null, null)).thenReturn(1); + assertEquals(1, service.searchAndSubmitBeneficiaryToNurse("{}")); + } + } +} diff --git a/src/test/java/com/iemr/mmu/service/reports/ReportCheckPostImplTest.java b/src/test/java/com/iemr/mmu/service/reports/ReportCheckPostImplTest.java new file mode 100644 index 00000000..753a1e9e --- /dev/null +++ b/src/test/java/com/iemr/mmu/service/reports/ReportCheckPostImplTest.java @@ -0,0 +1,140 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.service.reports; + +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.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Timestamp; +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.iemr.mmu.repo.reports.ReportMasterRepo; + +class ReportCheckPostImplTest { + + @Mock + private ReportMasterRepo reportMasterRepo; + + @InjectMocks + private ReportCheckPostImpl service; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + // The output mapper's Gson builder is initialised by its constructor. + new com.iemr.mmu.utils.mapper.OutputMapper(); + } + + /** A report request for the given report, with every parameter the reports need. */ + private String request(int reportID, Integer vanID) { + return "{\"reportID\":" + reportID + ",\"fromDate\":\"2024-01-01T00:00:00.000\"," + + "\"toDate\":\"2024-01-31T00:00:00.000\",\"vanID\":" + vanID + ",\"providerServiceMapID\":4}"; + } + + /** One report row, wide enough for any of the report mappers. */ + private ArrayList oneRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[60]); + return rows; + } + + private void stubEveryReportQuery(ArrayList rows) { + when(reportMasterRepo.get_report_PatientAttended(any(), any(), anyInt(), any())).thenReturn(rows); + when(reportMasterRepo.get_report_TestConducted(any(), any(), anyInt(), any())).thenReturn(rows); + when(reportMasterRepo.get_report_LabTestResult(any(), any(), anyInt(), any())).thenReturn(rows); + when(reportMasterRepo.get_report_PatientInfo(any(), any(), anyInt(), any())).thenReturn(rows); + when(reportMasterRepo.get_report_SP_ChildrenCases(any(), any(), anyInt(), any())).thenReturn(rows); + when(reportMasterRepo.get_report_SP_ANC(any(), any(), anyInt(), any())).thenReturn(rows); + when(reportMasterRepo.get_report_SP_ANCHighRisk(any(), any(), anyInt(), any())).thenReturn(rows); + } + + @Test + @DisplayName("the report master list is served for a service") + void getReportMaster_servesTheReportsOfAService() throws Exception { + when(reportMasterRepo.findByServiceIDAndDeletedOrderByReportNameAsc(2, false)).thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getReportMaster(2)); + } + + @ParameterizedTest(name = "report {0} is served from its own query") + @ValueSource(ints = { 1, 2, 5, 6, 7, 8, 9 }) + void reportHandler_servesEveryConfiguredReport(int reportID) throws Exception { + stubEveryReportQuery(oneRow()); + + assertTrue(service.reportHandler(request(reportID, 3)).startsWith("[")); + } + + @ParameterizedTest(name = "report {0} is served as an empty report when nothing matched") + @ValueSource(ints = { 1, 2, 5, 6, 7, 8, 9 }) + void reportHandler_servesAnEmptyReportWhenNothingMatched(int reportID) throws Exception { + stubEveryReportQuery(new ArrayList<>()); + + assertEquals("[]", service.reportHandler(request(reportID, 3))); + } + + @Test + @DisplayName("a van id of zero widens the report to every van") + void reportHandler_widensTheReportToEveryVanForAVanIdOfZero() throws Exception { + stubEveryReportQuery(new ArrayList<>()); + + assertEquals("[]", service.reportHandler(request(1, 0))); + verify(reportMasterRepo).get_report_PatientAttended(any(), any(), anyInt(), + org.mockito.ArgumentMatchers.isNull()); + } + + @Test + @DisplayName("an unknown report id is served as an empty response") + void reportHandler_servesAnEmptyResponseForAnUnknownReport() throws Exception { + assertEquals("", service.reportHandler(request(99, 3))); + } + + @Test + @DisplayName("a request without a report id is rejected") + void reportHandler_rejectsARequestWithoutAReportId() { + Exception thrown = assertThrows(Exception.class, () -> service.reportHandler("{}")); + assertEquals("Invalid/NULL report ID", thrown.getMessage()); + } + + @ParameterizedTest(name = "report {0} is rejected when a parameter is missing") + @ValueSource(ints = { 1, 2, 5, 6, 7, 8, 9 }) + void reportHandler_rejectsAReportWithAMissingParameter(int reportID) { + String incomplete = "{\"reportID\":" + reportID + ",\"providerServiceMapID\":4}"; + + Exception thrown = assertThrows(Exception.class, () -> service.reportHandler(incomplete)); + assertEquals("Some parameter/parameters is/are missing.", thrown.getMessage()); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/AESEncryption/AESEncryptionDecryptionTest.java b/src/test/java/com/iemr/mmu/utils/AESEncryption/AESEncryptionDecryptionTest.java new file mode 100644 index 00000000..479cf8fe --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/AESEncryption/AESEncryptionDecryptionTest.java @@ -0,0 +1,68 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.AESEncryption; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AESEncryptionDecryptionTest { + + @Test + @DisplayName("an encrypted value decrypts back to the original") + void encryptThenDecrypt_returnsTheOriginalValue() throws Exception { + AESEncryptionDecryption cipher = new AESEncryptionDecryption(); + + String encrypted = cipher.encrypt("/mmu/reports/2024/report.pdf"); + + assertNotEquals("/mmu/reports/2024/report.pdf", encrypted); + assertEquals("/mmu/reports/2024/report.pdf", cipher.decrypt(encrypted)); + } + + @Test + @DisplayName("each encryption uses a fresh initialisation vector") + void encrypt_usesAFreshInitialisationVectorEachTime() throws Exception { + AESEncryptionDecryption cipher = new AESEncryptionDecryption(); + + assertNotEquals(cipher.encrypt("same value"), cipher.encrypt("same value")); + } + + @Test + @DisplayName("an explicitly set key is used for both directions") + void setKey_isUsedForBothDirections() throws Exception { + AESEncryptionDecryption cipher = new AESEncryptionDecryption(); + cipher.setKey("a-custom-key"); + + assertEquals("value", cipher.decrypt(cipher.encrypt("value"))); + } + + @Test + @DisplayName("a value that was not produced by this cipher cannot be decrypted") + void decrypt_failsForAValueThisCipherDidNotProduce() { + AESEncryptionDecryption cipher = new AESEncryptionDecryption(); + + assertThrows(Exception.class, () -> cipher.decrypt("bm90LWVuY3J5cHRlZC1hdC1hbGw=")); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/JwtSecurityUtilsTest.java b/src/test/java/com/iemr/mmu/utils/JwtSecurityUtilsTest.java new file mode 100644 index 00000000..fb7d9e8e --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/JwtSecurityUtilsTest.java @@ -0,0 +1,451 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils; + +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.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.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import javax.crypto.SecretKey; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.mmu.data.login.Users; +import com.iemr.mmu.repo.login.UserLoginRepo; +import com.iemr.mmu.utils.exception.IEMRException; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; + +class JwtSecurityUtilsTest { + + /** Long enough for HMAC-SHA; the filter and util derive the key from it. */ + private static final String SECRET = "a-very-long-jwt-signing-secret-for-mmu-api-tests-0123456789"; + + private static SecretKey signingKey() { + return Keys.hmacShaKeyFor(SECRET.getBytes()); + } + + private static String tokenWith(String userId, String jti) { + var builder = Jwts.builder().subject("nurse1").claim("userId", userId) + .expiration(new Date(System.currentTimeMillis() + 60_000)); + if (jti != null) { + builder.id(jti); + } + return builder.signWith(signingKey()).compact(); + } + + @Nested + @DisplayName("JwtUtil") + class JwtUtilTests { + + @Mock + private TokenDenylist tokenDenylist; + + @InjectMocks + private JwtUtil jwtUtil; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ReflectionTestUtils.setField(jwtUtil, "SECRET_KEY", SECRET); + } + + @Test + void validateToken_returnsTheClaimsOfAValidToken() { + Claims claims = jwtUtil.validateToken(tokenWith("7", null)); + + assertNotNull(claims); + assertEquals("nurse1", claims.getSubject()); + } + + @Test + void validateToken_rejectsADenylistedToken() { + when(tokenDenylist.isTokenDenylisted("jti-1")).thenReturn(true); + + assertNull(jwtUtil.validateToken(tokenWith("7", "jti-1"))); + } + + @Test + void validateToken_keepsATokenThatIsNotDenylisted() { + when(tokenDenylist.isTokenDenylisted("jti-1")).thenReturn(false); + + assertNotNull(jwtUtil.validateToken(tokenWith("7", "jti-1"))); + } + + @Test + void validateToken_rejectsAMalformedToken() { + assertNull(jwtUtil.validateToken("not-a-token")); + } + + @Test + void extractUsername_readsTheSubjectOfTheToken() { + assertEquals("nurse1", jwtUtil.extractUsername(tokenWith("7", null))); + } + + @Test + void getUserIdFromToken_readsTheUserIdClaim() { + assertEquals("7", jwtUtil.getUserIdFromToken(tokenWith("7", null))); + assertNull(jwtUtil.getUserIdFromToken("not-a-token")); + } + + @Test + void extractAllClaims_failsForAMalformedToken() { + assertThrows(Exception.class, () -> jwtUtil.extractAllClaims("not-a-token")); + } + + @Test + void validateToken_failsWhenNoSigningSecretIsConfigured() { + ReflectionTestUtils.setField(jwtUtil, "SECRET_KEY", ""); + + assertNull(jwtUtil.validateToken(tokenWith("7", null))); + assertThrows(IllegalStateException.class, () -> jwtUtil.extractAllClaims(tokenWith("7", null))); + } + } + + @Nested + @DisplayName("TokenDenylist") + class TokenDenylistTests { + + @Mock + private RedisTemplate redisTemplate; + @Mock + private ValueOperations valueOperations; + + @InjectMocks + private TokenDenylist tokenDenylist; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void addTokenToDenylist_storesTheTokenIdUnderItsExpiry() { + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + + tokenDenylist.addTokenToDenylist("jti-1", 1000L); + + verify(valueOperations).set(anyString(), eq(" "), eq(1000L), eq(TimeUnit.MILLISECONDS)); + } + + @Test + void addTokenToDenylist_ignoresAMissingTokenId() { + tokenDenylist.addTokenToDenylist(null, 1000L); + tokenDenylist.addTokenToDenylist(" ", 1000L); + + verify(redisTemplate, never()).opsForValue(); + } + + @Test + void addTokenToDenylist_rejectsAnExpiryThatIsNotInTheFuture() { + assertThrows(IllegalArgumentException.class, () -> tokenDenylist.addTokenToDenylist("jti-1", null)); + assertThrows(IllegalArgumentException.class, () -> tokenDenylist.addTokenToDenylist("jti-1", 0L)); + } + + @Test + void addTokenToDenylist_reportsAStoreThatIsUnreachable() { + when(redisTemplate.opsForValue()).thenThrow(new RuntimeException("redis down")); + + assertThrows(RuntimeException.class, () -> tokenDenylist.addTokenToDenylist("jti-1", 1000L)); + } + + @Test + void isTokenDenylisted_readsTheStoredTokenId() { + when(redisTemplate.hasKey(anyString())).thenReturn(true); + assertTrue(tokenDenylist.isTokenDenylisted("jti-1")); + + when(redisTemplate.hasKey(anyString())).thenReturn(false); + assertFalse(tokenDenylist.isTokenDenylisted("jti-1")); + } + + @Test + void isTokenDenylisted_treatsAMissingTokenIdAsAllowed() { + assertFalse(tokenDenylist.isTokenDenylisted(null)); + assertFalse(tokenDenylist.isTokenDenylisted(" ")); + } + + @Test + void isTokenDenylisted_treatsAnUnreachableStoreAsAllowed() { + when(redisTemplate.hasKey(anyString())).thenThrow(new RuntimeException("redis down")); + + assertFalse(tokenDenylist.isTokenDenylisted("jti-1")); + } + } + + @Nested + @DisplayName("JwtAuthenticationUtil") + class JwtAuthenticationUtilTests { + + private CookieUtil cookieUtil; + private JwtUtil jwtUtil; + private RedisTemplate redisTemplate; + private ValueOperations valueOperations; + private UserLoginRepo userLoginRepo; + private JwtAuthenticationUtil authenticationUtil; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUp() { + cookieUtil = mock(CookieUtil.class); + jwtUtil = mock(JwtUtil.class); + redisTemplate = mock(RedisTemplate.class); + valueOperations = mock(ValueOperations.class); + userLoginRepo = mock(UserLoginRepo.class); + + authenticationUtil = new JwtAuthenticationUtil(cookieUtil, jwtUtil); + ReflectionTestUtils.setField(authenticationUtil, "redisTemplate", redisTemplate); + ReflectionTestUtils.setField(authenticationUtil, "userLoginRepo", userLoginRepo); + } + + private Claims claimsFor(String subject, String userId) { + Claims claims = mock(Claims.class); + when(claims.getSubject()).thenReturn(subject); + when(claims.get("userId", String.class)).thenReturn(userId); + return claims; + } + + @Test + void validateJwtToken_returnsTheUsernameOfAValidToken() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(java.util.Optional.of("token")); + Claims claims = claimsFor("nurse1", "7"); + when(jwtUtil.validateToken("token")).thenReturn(claims); + + assertEquals("nurse1", authenticationUtil.validateJwtToken(request).getBody()); + } + + @Test + void validateJwtToken_rejectsARequestWithoutAToken() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(java.util.Optional.empty()); + + assertEquals(HttpStatus.UNAUTHORIZED, authenticationUtil.validateJwtToken(request).getStatusCode()); + } + + @Test + void validateJwtToken_rejectsAnInvalidToken() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(java.util.Optional.of("token")); + when(jwtUtil.validateToken("token")).thenReturn(null); + + assertTrue(authenticationUtil.validateJwtToken(request).getBody().contains("Invalid JWT Token")); + } + + @Test + void validateJwtToken_rejectsATokenWithoutAUsername() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(java.util.Optional.of("token")); + Claims claims = claimsFor(null, "7"); + when(jwtUtil.validateToken("token")).thenReturn(claims); + + assertTrue(authenticationUtil.validateJwtToken(request).getBody().contains("Username is missing")); + } + + @Test + void validateUserIdAndJwtToken_acceptsAUserThatIsAlreadyCached() throws Exception { + Users cachedUser = new Users(); + cachedUser.setUserID(7L); + Claims claims = claimsFor("nurse1", "7"); + when(jwtUtil.validateToken("token")).thenReturn(claims); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(valueOperations.get("user_7")).thenReturn(cachedUser); + when(userLoginRepo.getRoleNamebyUserId(7L)) + .thenReturn(new java.util.ArrayList<>(java.util.List.of("Nurse"))); + + assertTrue(authenticationUtil.validateUserIdAndJwtToken("token")); + } + + @Test + void validateUserIdAndJwtToken_cachesAUserThatWasReadFromTheDatabase() throws Exception { + Users storedUser = new Users(); + storedUser.setUserID(7L); + storedUser.setUserName("nurse1"); + Claims claims = claimsFor("nurse1", "7"); + when(jwtUtil.validateToken("token")).thenReturn(claims); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(valueOperations.get("user_7")).thenReturn(null); + when(userLoginRepo.getUserByUserID(7L)).thenReturn(storedUser); + + assertTrue(authenticationUtil.validateUserIdAndJwtToken("token")); + verify(valueOperations).set(eq("user_7"), any(), eq(30L), eq(TimeUnit.MINUTES)); + } + + @Test + void validateUserIdAndJwtToken_rejectsAnInvalidToken() { + when(jwtUtil.validateToken("token")).thenReturn(null); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> authenticationUtil.validateUserIdAndJwtToken("token")); + assertTrue(thrown.getMessage().contains("Invalid JWT token")); + } + + @Test + void validateUserIdAndJwtToken_rejectsAnUnknownUser() { + Claims claims = claimsFor("nurse1", "7"); + when(jwtUtil.validateToken("token")).thenReturn(claims); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + when(valueOperations.get("user_7")).thenReturn(null); + when(userLoginRepo.getUserByUserID(7L)).thenReturn(null); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> authenticationUtil.validateUserIdAndJwtToken("token")); + assertTrue(thrown.getMessage().contains("Invalid User ID")); + } + + @Test + void getUserRoles_returnsTheRolesOfTheUser() throws Exception { + when(userLoginRepo.getRoleNamebyUserId(7L)) + .thenReturn(new java.util.ArrayList<>(java.util.List.of("Nurse"))); + + assertEquals(java.util.List.of("Nurse"), authenticationUtil.getUserRoles(7L)); + } + + @Test + void getUserRoles_rejectsAnInvalidUserId() { + assertThrows(IEMRException.class, () -> authenticationUtil.getUserRoles(null)); + assertThrows(IEMRException.class, () -> authenticationUtil.getUserRoles(0L)); + } + + @Test + void getUserRoles_failsWhenTheUserHasNoRole() { + when(userLoginRepo.getRoleNamebyUserId(7L)).thenReturn(new java.util.ArrayList<>()); + + assertThrows(IEMRException.class, () -> authenticationUtil.getUserRoles(7L)); + } + } + + @Nested + @DisplayName("UserAgentContext") + class UserAgentContextTests { + + @Test + void theUserAgentIsHeldPerThreadUntilItIsCleared() { + UserAgentContext.setUserAgent("okhttp/4.9"); + assertEquals("okhttp/4.9", UserAgentContext.getUserAgent()); + + UserAgentContext.clear(); + assertNull(UserAgentContext.getUserAgent()); + } + } + + @Nested + @DisplayName("MediaTypeUtils") + class MediaTypeUtilsTests { + + @Test + void getMediaTypeForFileName_readsTheTypeFromTheServletContext() { + jakarta.servlet.ServletContext servletContext = mock(jakarta.servlet.ServletContext.class); + when(servletContext.getMimeType("report.pdf")).thenReturn("application/pdf"); + + assertEquals("application/pdf", + MediaTypeUtils.getMediaTypeForFileName(servletContext, "report.pdf").toString()); + } + + @Test + void getMediaTypeForFileName_fallsBackToOctetStreamForAnUnknownType() { + jakarta.servlet.ServletContext servletContext = mock(jakarta.servlet.ServletContext.class); + when(servletContext.getMimeType("report.xyz")).thenReturn(null); + + assertEquals("application/octet-stream", + MediaTypeUtils.getMediaTypeForFileName(servletContext, "report.xyz").toString()); + } + } + + @Nested + @DisplayName("RestTemplateUtil") + class RestTemplateUtilTests { + + @Test + void createRequestEntity_sendsTheAuthorizationAsABearerTokenAndTheJwtAsACookie() { + org.springframework.http.HttpEntity entity = RestTemplateUtil.createRequestEntity("{}", "auth", + "jwt"); + + assertEquals("Bearer auth", entity.getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + assertEquals("Jwttoken=jwt", entity.getHeaders().getFirst(HttpHeaders.COOKIE)); + assertTrue(entity.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE).contains("charset=utf-8")); + } + + @Test + void createRequestEntity_sendsTheRawAuthorizationForADataSyncCall() { + org.springframework.http.HttpEntity entity = RestTemplateUtil.createRequestEntity("{}", "auth", + "datasync"); + + assertEquals("auth", entity.getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + assertNull(entity.getHeaders().getFirst(HttpHeaders.COOKIE)); + } + + @Test + void createRequestEntity_readsTheJwtFromTheCurrentRequestWhenNoneWasPassed() { + org.springframework.mock.web.MockHttpServletRequest request = + new org.springframework.mock.web.MockHttpServletRequest(); + request.setCookies(new jakarta.servlet.http.Cookie("Jwttoken", "from-cookie")); + org.springframework.web.context.request.RequestContextHolder.setRequestAttributes( + new org.springframework.web.context.request.ServletRequestAttributes(request)); + try { + org.springframework.http.HttpEntity entity = RestTemplateUtil.createRequestEntity("{}", + "auth", ""); + + assertEquals("Jwttoken=from-cookie", entity.getHeaders().getFirst(HttpHeaders.COOKIE)); + } finally { + org.springframework.web.context.request.RequestContextHolder.resetRequestAttributes(); + } + } + + @Test + void createRequestEntity_omitsTheCookieWhenThereIsNoCurrentRequest() { + org.springframework.web.context.request.RequestContextHolder.resetRequestAttributes(); + + org.springframework.http.HttpEntity entity = RestTemplateUtil.createRequestEntity("{}", "", ""); + + assertNull(entity.getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + assertNull(entity.getHeaders().getFirst(HttpHeaders.COOKIE)); + } + } +} diff --git a/src/test/java/com/iemr/mmu/utils/JwtUserIdValidationFilterTest.java b/src/test/java/com/iemr/mmu/utils/JwtUserIdValidationFilterTest.java new file mode 100644 index 00000000..ba91727a --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/JwtUserIdValidationFilterTest.java @@ -0,0 +1,242 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; + +import com.iemr.mmu.utils.exception.IEMRException; +import com.iemr.mmu.utils.http.AuthorizationHeaderRequestWrapper; +import com.iemr.mmu.utils.mapper.RoleAuthenticationFilter; +import com.iemr.mmu.utils.redis.RedisStorage; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.Cookie; + +class JwtUserIdValidationFilterTest { + + private JwtAuthenticationUtil jwtAuthenticationUtil; + private JwtUserIdValidationFilter filter; + private FilterChain chain; + + @BeforeEach + void setUp() { + jwtAuthenticationUtil = mock(JwtAuthenticationUtil.class); + filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, "https://mmu.example.org,http://localhost:*"); + chain = mock(FilterChain.class); + } + + @AfterEach + void clearSecurityContext() { + SecurityContextHolder.clearContext(); + UserAgentContext.clear(); + } + + private MockHttpServletRequest request(String method, String uri) { + MockHttpServletRequest request = new MockHttpServletRequest(method, uri); + request.setRequestURI(uri); + return request; + } + + @Test + @DisplayName("a preflight from an allowed origin is answered with the CORS headers") + void doFilter_answersAPreflightFromAnAllowedOrigin() throws Exception { + MockHttpServletRequest request = request("OPTIONS", "/ANC/save"); + request.addHeader("Origin", "https://mmu.example.org"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request, response, chain); + + assertEquals(200, response.getStatus()); + assertEquals("https://mmu.example.org", response.getHeader("Access-Control-Allow-Origin")); + assertEquals("true", response.getHeader("Access-Control-Allow-Credentials")); + verify(chain, never()).doFilter(any(), any()); + } + + @Test + @DisplayName("a wildcard port on localhost is an allowed origin") + void doFilter_allowsAWildcardLocalhostPort() throws Exception { + MockHttpServletRequest request = request("OPTIONS", "/ANC/save"); + request.addHeader("Origin", "http://localhost:4200"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request, response, chain); + + assertEquals(200, response.getStatus()); + } + + @Test + @DisplayName("a preflight without an Origin header is refused") + void doFilter_refusesAPreflightWithoutAnOrigin() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request("OPTIONS", "/ANC/save"), response, chain); + + assertEquals(403, response.getStatus()); + } + + @Test + @DisplayName("a preflight from an unknown origin is refused") + void doFilter_refusesAPreflightFromAnUnknownOrigin() throws Exception { + MockHttpServletRequest request = request("OPTIONS", "/ANC/save"); + request.addHeader("Origin", "https://attacker.example.org"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request, response, chain); + + assertEquals(403, response.getStatus()); + } + + @Test + @DisplayName("a request from an unknown origin is refused") + void doFilter_refusesARequestFromAnUnknownOrigin() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save"); + request.addHeader("Origin", "https://attacker.example.org"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request, response, chain); + + assertEquals(403, response.getStatus()); + } + + @Test + @DisplayName("public endpoints are let through without a token") + void doFilter_letsPublicEndpointsThrough() throws Exception { + for (String path : List.of("/user/userAuthenticate", "/user/logOutUserFromConcurrentSession", + "/swagger-ui/index.html", "/v3/api-docs", "/user/refreshToken", "/public/thing", "/version", + "/health")) { + FilterChain freshChain = mock(FilterChain.class); + + filter.doFilter(request("POST", path), new MockHttpServletResponse(), freshChain); + + verify(freshChain).doFilter(any(), any()); + } + } + + @Test + @DisplayName("a valid JWT cookie lets the request through and clears any userId cookie") + void doFilter_letsAValidJwtCookieThrough() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save"); + request.setCookies(new Cookie("Jwttoken", "token"), new Cookie("userId", "7")); + MockHttpServletResponse response = new MockHttpServletResponse(); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken("token")).thenReturn(true); + + filter.doFilter(request, response, chain); + + verify(chain).doFilter(any(AuthorizationHeaderRequestWrapper.class), any()); + assertEquals(0, response.getCookie("userId").getMaxAge()); + } + + @Test + @DisplayName("a valid JWT header lets the request through") + void doFilter_letsAValidJwtHeaderThrough() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save"); + request.addHeader("JwtToken", "token"); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken("token")).thenReturn(true); + + filter.doFilter(request, new MockHttpServletResponse(), chain); + + verify(chain).doFilter(any(AuthorizationHeaderRequestWrapper.class), any()); + } + + @Test + @DisplayName("a mobile client with an Authorization header is let through") + void doFilter_letsAMobileClientThrough() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save"); + request.addHeader("User-Agent", "okhttp/4.9.0"); + request.addHeader("Authorization", "session-key"); + + filter.doFilter(request, new MockHttpServletResponse(), chain); + + verify(chain).doFilter(any(), any()); + } + + @Test + @DisplayName("a request with no recognisable token is refused") + void doFilter_refusesARequestWithNoToken() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request("POST", "/ANC/save"), response, chain); + + assertEquals(401, response.getStatus()); + } + + @Test + @DisplayName("a token that fails validation is refused") + void doFilter_refusesATokenThatFailsValidation() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save"); + request.setCookies(new Cookie("Jwttoken", "token")); + MockHttpServletResponse response = new MockHttpServletResponse(); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken("token")) + .thenThrow(new IEMRException("Invalid User ID.")); + + filter.doFilter(request, response, chain); + + assertEquals(401, response.getStatus()); + } + + @Test + @DisplayName("no configured origins means no origin is allowed") + void doFilter_refusesEveryOriginWhenNoneIsConfigured() throws Exception { + JwtUserIdValidationFilter noOriginsFilter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, " "); + MockHttpServletRequest request = request("OPTIONS", "/ANC/save"); + request.addHeader("Origin", "https://mmu.example.org"); + MockHttpServletResponse response = new MockHttpServletResponse(); + + noOriginsFilter.doFilter(request, response, chain); + + assertEquals(403, response.getStatus()); + } + + @Test + @DisplayName("the Authorization header wrapper reports the header it was given") + void authorizationHeaderRequestWrapper_reportsTheHeaderItWasGiven() { + MockHttpServletRequest request = request("POST", "/ANC/save"); + request.addHeader("Accept", "application/json"); + + AuthorizationHeaderRequestWrapper wrapper = new AuthorizationHeaderRequestWrapper(request, "session-key"); + + assertEquals("session-key", wrapper.getHeader("authorization")); + assertEquals("session-key", wrapper.getHeaders("Authorization").nextElement()); + assertEquals("application/json", wrapper.getHeader("Accept")); + assertTrue(java.util.Collections.list(wrapper.getHeaderNames()).contains("Authorization")); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/config/ConfigPropertiesTest.java b/src/test/java/com/iemr/mmu/utils/config/ConfigPropertiesTest.java new file mode 100644 index 00000000..92433b1e --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/config/ConfigPropertiesTest.java @@ -0,0 +1,75 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.config; + +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.assertNull; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ConfigPropertiesTest { + + @Test + @DisplayName("a property that is not in the file reads back as null") + void getPropertyByName_returnsNothingForAnUnknownProperty() { + assertNull(ConfigProperties.getPropertyByName("no.such.property")); + } + + @Test + @DisplayName("an unparseable numeric property falls back to its zero value") + void theNumericReadersFallBackToZeroForAnUnparseableProperty() { + assertEquals(0, ConfigProperties.getInteger("no.such.property")); + assertEquals(0L, ConfigProperties.getLong("no.such.property")); + } + + @Test + @DisplayName("a numeric property that is present is read as a number") + void theNumericReadersReadAStoredNumber() { + assertEquals(1800, ConfigProperties.getInteger("iemr.session.expiry.time")); + assertEquals(1800L, ConfigProperties.getLong("iemr.session.expiry.time")); + assertEquals(1800F, ConfigProperties.getFloat("iemr.session.expiry.time")); + } + + @Test + @DisplayName("a missing boolean property reads back as false") + void getBoolean_returnsFalseForAMissingProperty() { + assertFalse(ConfigProperties.getBoolean("no.such.property")); + } + + @Test + @DisplayName("the Redis connection settings are readable") + void theRedisConnectionSettingsAreReadable() { + ConfigProperties.getRedisUrl(); + + assertEquals(0, ConfigProperties.getRedisPort()); + } + + @Test + @DisplayName("the session expiry settings are readable") + void theSessionExpirySettingsAreReadable() { + assertFalse(ConfigProperties.getExtendExpiryTime()); + org.junit.jupiter.api.Assertions.assertTrue(ConfigProperties.getSessionExpiryTime() > 0); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/exception/CustomExceptionResponseTest.java b/src/test/java/com/iemr/mmu/utils/exception/CustomExceptionResponseTest.java new file mode 100644 index 00000000..01e7937a --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/exception/CustomExceptionResponseTest.java @@ -0,0 +1,137 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.exception; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.ConnectException; +import java.sql.SQLException; +import java.text.ParseException; + +import org.json.JSONException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CustomExceptionResponseTest { + + @Test + @DisplayName("a JSON object response is passed through as data") + void setResponse_keepsAJsonObjectAsData() { + CustomExceptionResponse response = new CustomExceptionResponse(); + response.setResponse("{\"name\":\"Asha\"}"); + + assertTrue(response.isSuccess()); + assertEquals(CustomExceptionResponse.SUCCESS, response.getStatusCode()); + assertEquals("{\"name\":\"Asha\"}", response.getData()); + } + + @Test + @DisplayName("a JSON array response is passed through as data") + void setResponse_keepsAJsonArrayAsData() { + CustomExceptionResponse response = new CustomExceptionResponse(); + response.setResponse("[{\"name\":\"Asha\"}]"); + + assertTrue(response.getData().startsWith("[")); + } + + @Test + @DisplayName("a plain message response is wrapped in a response object") + void setResponse_wrapsAPlainMessage() { + CustomExceptionResponse response = new CustomExceptionResponse(); + response.setResponse("Data saved successfully"); + + assertTrue(response.getData().contains("Data saved successfully")); + } + + @Test + @DisplayName("each database failure maps to the database status code") + void setError_mapsEveryDatabaseFailureToTheDatabaseCode() { + assertEquals(CustomExceptionResponse.DB_EXCEPTION, errorFor(new SQLException("bad sql"))); + assertEquals(CustomExceptionResponse.DB_EXCEPTION, + errorFor(new org.hibernate.exception.SQLGrammarException("bad grammar", new SQLException()))); + assertEquals(CustomExceptionResponse.DB_EXCEPTION, + errorFor(new org.hibernate.exception.DataException("bad data", new SQLException()))); + assertEquals(CustomExceptionResponse.DB_EXCEPTION, + errorFor(new org.hibernate.exception.ConstraintViolationException("violated", new SQLException(), + "constraint"))); + assertEquals(CustomExceptionResponse.DB_EXCEPTION, + errorFor(new org.hibernate.exception.GenericJDBCException("generic", new SQLException()))); + assertEquals(CustomExceptionResponse.DB_EXCEPTION, + errorFor(new org.hibernate.exception.JDBCConnectionException("no connection", new SQLException()))); + assertEquals(CustomExceptionResponse.DB_EXCEPTION, + errorFor(new org.hibernate.exception.LockAcquisitionException("locked", new SQLException()))); + assertEquals(CustomExceptionResponse.DB_EXCEPTION, errorFor( + new org.springframework.dao.InvalidDataAccessResourceUsageException("bad usage"))); + } + + @Test + @DisplayName("each remaining failure maps to its own status code") + void setError_mapsEachRemainingFailureToItsStatusCode() { + assertEquals(CustomExceptionResponse.USERID_FAILURE, errorFor(new IEMRException("bad user"))); + assertEquals(CustomExceptionResponse.OBJECT_FAILURE, errorFor(new JSONException("bad json"))); + assertEquals(CustomExceptionResponse.ENVIRONMENT_EXCEPTION, errorFor(new ParseException("bad date", 0))); + assertEquals(CustomExceptionResponse.ENVIRONMENT_EXCEPTION, errorFor(new NullPointerException("npe"))); + assertEquals(CustomExceptionResponse.ENVIRONMENT_EXCEPTION, + errorFor(new ArrayIndexOutOfBoundsException("index"))); + assertEquals(CustomExceptionResponse.ENVIRONMENT_EXCEPTION, errorFor(new IOException("io"))); + assertEquals(CustomExceptionResponse.ENVIRONMENT_EXCEPTION, errorFor(new ConnectException("refused"))); + assertEquals(CustomExceptionResponse.GENERIC_FAILURE, errorFor(new IllegalStateException("boom"))); + } + + /** The response reads the cause, so every failure is wrapped before it is reported. */ + private int errorFor(Throwable cause) { + CustomExceptionResponse response = new CustomExceptionResponse(); + response.setError(new Exception("wrapped", cause)); + return response.getStatusCode(); + } + + @Test + @DisplayName("an explicit error carries its own code, message and status") + void setError_carriesTheGivenCodeAndMessage() { + CustomExceptionResponse response = new CustomExceptionResponse(); + response.setError(404, "Not found", "NOT_FOUND"); + + assertEquals(404, response.getStatusCode()); + assertEquals("Not found", response.getErrorMessage()); + assertEquals("NOT_FOUND", response.getStatus()); + assertFalse(response.isSuccess()); + + response.setError(400, "Bad request"); + assertEquals("Bad request", response.getStatus()); + } + + @Test + @DisplayName("a failed response carries no data") + void getData_returnsNothingForAFailedResponse() { + assertNull(new CustomExceptionResponse().getData()); + } + + @Test + @DisplayName("serialising with nulls keeps the empty data field") + void toStringWithSerialization_keepsTheEmptyDataField() { + assertTrue(new CustomExceptionResponse().toStringWithSerialization().contains("\"data\":null")); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/exception/SecurityExceptionHandlersTest.java b/src/test/java/com/iemr/mmu/utils/exception/SecurityExceptionHandlersTest.java new file mode 100644 index 00000000..75fbe020 --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/exception/SecurityExceptionHandlersTest.java @@ -0,0 +1,80 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.exception; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; + +class SecurityExceptionHandlersTest { + + @Test + @DisplayName("an IEMR exception carries its message and error code") + void iemrException_carriesItsMessageAndErrorCode() { + IEMRException withCause = new IEMRException("wrapped", new RuntimeException("cause")); + assertEquals("wrapped", withCause.getMessage()); + assertEquals("wrapped", withCause.toString()); + assertNull(withCause.getErrorCode()); + + IEMRException withCode = new IEMRException("coded", 5002); + assertEquals(5002, withCode.getErrorCode()); + + withCode.setErrorCode(400); + assertEquals(400, withCode.getErrorCode()); + } + + @Test + @DisplayName("a denied request is answered with a 403 and a JSON body") + void customAccessDeniedHandler_answersWithForbidden() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + + new CustomAccessDeniedHandler().handle(new MockHttpServletRequest(), response, + new AccessDeniedException("denied")); + + assertEquals(403, response.getStatus()); + assertEquals("application/json", response.getContentType()); + assertTrue(response.getContentAsString().contains("Access denied")); + } + + @Test + @DisplayName("an unauthenticated request is answered with a 401 and a JSON body") + void customAuthenticationEntryPoint_answersWithUnauthorized() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + + new CustomAuthenticationEntryPoint().commence(new MockHttpServletRequest(), response, + new BadCredentialsException("no token")); + + assertEquals(401, response.getStatus()); + assertTrue(response.getContentAsString().contains("Unauthorized")); + assertTrue(response.getContentAsString().contains("no token")); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/http/HttpUtilsAndInterceptorTest.java b/src/test/java/com/iemr/mmu/utils/http/HttpUtilsAndInterceptorTest.java new file mode 100644 index 00000000..83eb0d18 --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/http/HttpUtilsAndInterceptorTest.java @@ -0,0 +1,261 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.http; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +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.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import com.iemr.mmu.utils.exception.IEMRException; +import com.iemr.mmu.utils.sessionobject.SessionObject; +import com.iemr.mmu.utils.validator.Validator; + +class HttpUtilsAndInterceptorTest { + + @Nested + @DisplayName("HttpUtils") + class HttpUtilsTests { + + private HttpUtils httpUtils; + private RestTemplate restTemplate; + + @BeforeEach + void setUp() { + httpUtils = new HttpUtils(); + restTemplate = mock(RestTemplate.class); + ReflectionTestUtils.setField(httpUtils, "rest", restTemplate); + } + + private void stubExchange(HttpMethod method, String body) { + when(restTemplate.exchange(anyString(), eq(method), any(), eq(String.class))) + .thenReturn(new ResponseEntity<>(body, HttpStatus.OK)); + } + + @Test + void get_returnsTheResponseBodyAndRecordsTheStatus() { + stubExchange(HttpMethod.GET, "body"); + + assertEquals("body", httpUtils.get("http://service/thing")); + assertEquals(HttpStatus.OK, httpUtils.getStatus()); + } + + @Test + void get_sendsTheAuthorizationAndContentTypeItWasGiven() { + stubExchange(HttpMethod.GET, "body"); + HashMap header = new HashMap<>(); + header.put("Authorization", "session-key"); + header.put("Content-Type", "application/json"); + + assertEquals("body", httpUtils.get("http://service/thing", header)); + } + + @Test + void get_fallsBackToJsonWhenNoContentTypeWasGiven() { + stubExchange(HttpMethod.GET, "body"); + + assertEquals("body", httpUtils.get("http://service/thing", new HashMap<>())); + } + + @Test + void post_returnsTheResponseBody() { + stubExchange(HttpMethod.POST, "body"); + + assertEquals("body", httpUtils.post("http://service/thing", "{}")); + } + + @Test + void post_sendsTheAuthorizationItWasGiven() { + stubExchange(HttpMethod.POST, "body"); + HashMap header = new HashMap<>(); + header.put("Authorization", "session-key"); + + assertEquals("body", httpUtils.post("http://service/thing", "{}", header)); + } + + @Test + void setStatus_isReadBackByGetStatus() { + httpUtils.setStatus(HttpStatus.NOT_FOUND); + + assertEquals(HttpStatus.NOT_FOUND, httpUtils.getStatus()); + } + } + + @Nested + @DisplayName("HttpInterceptor") + class HttpInterceptorTests { + + private HttpInterceptor interceptor; + private Validator validator; + private SessionObject sessionObject; + + @BeforeEach + void setUp() { + interceptor = new HttpInterceptor(); + validator = mock(Validator.class); + sessionObject = mock(SessionObject.class); + interceptor.setValidator(validator); + interceptor.setSessionObject(sessionObject); + ReflectionTestUtils.setField(interceptor, "allowedOrigins", "https://mmu.example.org"); + } + + private MockHttpServletRequest request(String method, String uri, String authorization) { + MockHttpServletRequest request = new MockHttpServletRequest(method, uri); + request.setRequestURI(uri); + if (authorization != null) { + request.addHeader("Authorization", authorization); + } + return request; + } + + @Test + void preHandle_letsAnUnauthenticatedRequestThrough() throws Exception { + assertTrue(interceptor.preHandle(request("POST", "/ANC/save", null), new MockHttpServletResponse(), + null)); + verify(validator, never()).checkKeyExists(anyString(), anyString()); + } + + @Test + void preHandle_skipsValidationForThePublicEndpoints() throws Exception { + for (String endpoint : java.util.List.of("userAuthenticate", "superUserAuthenticate", "userLogout", + "changePassword", "swagger-ui.html", "api-docs", "startMasterDownload")) { + assertTrue(interceptor.preHandle(request("POST", "/user/" + endpoint, "session-key"), + new MockHttpServletResponse(), null)); + } + verify(validator, never()).checkKeyExists(anyString(), anyString()); + } + + @Test + void preHandle_stopsTheErrorEndpoint() throws Exception { + assertFalse(interceptor.preHandle(request("POST", "/error", "session-key"), + new MockHttpServletResponse(), null)); + } + + @Test + void preHandle_validatesTheSessionOfEveryOtherRequest() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save", "session-key"); + request.addHeader("X-FORWARDED-FOR", "10.0.0.1"); + + assertTrue(interceptor.preHandle(request, new MockHttpServletResponse(), null)); + verify(validator).checkKeyExists("session-key", "10.0.0.1"); + } + + @Test + void preHandle_fallsBackToTheRemoteAddressWhenThereIsNoForwardedHeader() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save", "session-key"); + request.setRemoteAddr("10.0.0.2"); + + assertTrue(interceptor.preHandle(request, new MockHttpServletResponse(), null)); + verify(validator).checkKeyExists("session-key", "10.0.0.2"); + } + + @Test + void preHandle_stopsAndAnswersWhenTheSessionIsExpired() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save", "session-key"); + request.addHeader("Origin", "https://mmu.example.org"); + MockHttpServletResponse response = new MockHttpServletResponse(); + doThrow(new IEMRException("Session is expired. Please login again.")).when(validator) + .checkKeyExists(anyString(), anyString()); + + assertFalse(interceptor.preHandle(request, response, null)); + assertEquals("https://mmu.example.org", response.getHeader("Access-Control-Allow-Origin")); + assertTrue(response.getContentAsString().contains("Session is expired")); + } + + @Test + void preHandle_omitsTheCorsHeadersForAnUnknownOrigin() throws Exception { + MockHttpServletRequest request = request("POST", "/ANC/save", "session-key"); + request.addHeader("Origin", "https://attacker.example.org"); + MockHttpServletResponse response = new MockHttpServletResponse(); + doThrow(new IEMRException("Session is expired.")).when(validator).checkKeyExists(anyString(), + anyString()); + + assertFalse(interceptor.preHandle(request, response, null)); + assertNull(response.getHeader("Access-Control-Allow-Origin")); + } + + @Test + void preHandle_skipsValidationForAPreflight() throws Exception { + assertTrue(interceptor.preHandle(request("OPTIONS", "/ANC/save", "session-key"), + new MockHttpServletResponse(), null)); + verify(validator, never()).checkKeyExists(anyString(), anyString()); + } + + @Test + void postHandle_refreshesTheSessionOfAnAuthenticatedRequest() throws Exception { + when(sessionObject.getSessionObject("session-key")).thenReturn("{}"); + + interceptor.postHandle(request("POST", "/ANC/save", "session-key"), new MockHttpServletResponse(), null, + null); + + verify(sessionObject).updateSessionObject("session-key", "{}"); + } + + @Test + void postHandle_leavesAnUnauthenticatedRequestAlone() throws Exception { + interceptor.postHandle(request("POST", "/ANC/save", null), new MockHttpServletResponse(), null, null); + + verify(sessionObject, never()).updateSessionObject(anyString(), anyString()); + } + + @Test + void postHandle_swallowsAFailedSessionRefresh() throws Exception { + when(sessionObject.getSessionObject("session-key")) + .thenThrow(new com.iemr.mmu.utils.redis.RedisSessionException("redis down")); + + interceptor.postHandle(request("POST", "/ANC/save", "session-key"), new MockHttpServletResponse(), null, + null); + } + + @Test + void afterCompletion_doesNothingToTheResponse() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + + interceptor.afterCompletion(request("POST", "/ANC/save", null), response, null, null); + + assertEquals(200, response.getStatus()); + } + } +} diff --git a/src/test/java/com/iemr/mmu/utils/mapper/MapperTest.java b/src/test/java/com/iemr/mmu/utils/mapper/MapperTest.java new file mode 100644 index 00000000..c8187aa1 --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/mapper/MapperTest.java @@ -0,0 +1,72 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.mapper; + +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 org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.google.gson.JsonParser; +import com.iemr.mmu.data.registrar.BeneficiaryData; + +class MapperTest { + + @Test + @DisplayName("the input mapper reads the ISO date format used by the UI") + void inputMapper_readsTheIsoDateFormat() throws Exception { + BeneficiaryData beneficiary = InputMapper.gson() + .fromJson("{\"firstName\":\"Asha\",\"dob\":\"1990-05-04T00:00:00.000\"}", BeneficiaryData.class); + + assertEquals("Asha", beneficiary.getFirstName()); + assertNotNull(beneficiary.getDob()); + } + + @Test + @DisplayName("the input mapper reads a JSON element as well as a string") + void inputMapper_readsAJsonElement() throws Exception { + BeneficiaryData beneficiary = InputMapper.gson() + .fromJson(JsonParser.parseString("{\"firstName\":\"Asha\"}"), BeneficiaryData.class); + + assertEquals("Asha", beneficiary.getFirstName()); + } + + @Test + @DisplayName("the flagged input mapper reads the long-form date format") + void inputMapper_readsTheLongFormDateFormat() throws Exception { + BeneficiaryData beneficiary = InputMapper.gson(1) + .fromJson("{\"dob\":\"May 04, 1990 00:00:00\"}", BeneficiaryData.class, 1); + + assertNotNull(beneficiary.getDob()); + } + + @Test + @DisplayName("the output mapper serialises nulls and exposed fields only") + void outputMapper_serialisesNullsAndExposedFieldsOnly() { + new OutputMapper(); + + assertNotNull(OutputMapper.gson()); + assertEquals("{}", OutputMapper.gson().toJson(new Object())); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/mapper/RoleAuthenticationFilterTest.java b/src/test/java/com/iemr/mmu/utils/mapper/RoleAuthenticationFilterTest.java new file mode 100644 index 00000000..3e7c705c --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/mapper/RoleAuthenticationFilterTest.java @@ -0,0 +1,200 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.mapper; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; + +import com.iemr.mmu.data.login.Users; +import com.iemr.mmu.utils.JwtAuthenticationUtil; +import com.iemr.mmu.utils.JwtUtil; +import com.iemr.mmu.utils.exception.IEMRException; +import com.iemr.mmu.utils.redis.RedisStorage; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.Cookie; + +class RoleAuthenticationFilterTest { + + @Mock + private JwtUtil jwtUtil; + @Mock + private RedisStorage redisService; + @Mock + private JwtAuthenticationUtil userService; + + @InjectMocks + private RoleAuthenticationFilter filter; + + private FilterChain chain; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + chain = mock(FilterChain.class); + } + + @AfterEach + void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } + + private MockHttpServletRequest requestWithJwtCookie(String token) { + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/ANC/save"); + request.setCookies(new Cookie("Jwttoken", token)); + return request; + } + + private Claims claimsWithUserId(Object userId) { + Claims claims = mock(Claims.class); + when(claims.get("userId")).thenReturn(userId); + return claims; + } + + @Test + @DisplayName("cached roles are turned into authorities without hitting the user service") + void doFilterInternal_usesTheCachedRoles() throws Exception { + Claims claims = claimsWithUserId("7"); + when(jwtUtil.extractAllClaims("token")).thenReturn(claims); + when(redisService.getUserRoleFromCache(7L)).thenReturn(new ArrayList<>(List.of("ROLE_NURSE"))); + + filter.doFilter(requestWithJwtCookie("token"), new MockHttpServletResponse(), chain); + + assertEquals("7", SecurityContextHolder.getContext().getAuthentication().getPrincipal()); + assertEquals("ROLE_NURSE", + SecurityContextHolder.getContext().getAuthentication().getAuthorities().iterator().next() + .getAuthority()); + verify(userService, never()).getUserRoles(anyLong()); + } + + @Test + @DisplayName("roles read from the user service are normalised and cached") + void doFilterInternal_normalisesAndCachesFreshlyReadRoles() throws Exception { + Claims claims = claimsWithUserId("7"); + when(jwtUtil.extractAllClaims("token")).thenReturn(claims); + when(redisService.getUserRoleFromCache(7L)).thenReturn(new ArrayList<>()); + when(userService.getUserRoles(7L)) + .thenReturn(new ArrayList<>(Arrays.asList(" lab technician ", null))); + + filter.doFilter(requestWithJwtCookie("token"), new MockHttpServletResponse(), chain); + + assertEquals("ROLE_LAB_TECHNICIAN", + SecurityContextHolder.getContext().getAuthentication().getAuthorities().iterator().next() + .getAuthority()); + verify(redisService).cacheUserRoles(7L, List.of("ROLE_LAB_TECHNICIAN")); + } + + @Test + @DisplayName("the JWT header is used when there is no cookie") + void doFilterInternal_fallsBackToTheJwtHeader() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/ANC/save"); + request.addHeader("Jwttoken", "token"); + Claims claims = claimsWithUserId("7"); + when(jwtUtil.extractAllClaims("token")).thenReturn(claims); + when(redisService.getUserRoleFromCache(7L)).thenReturn(new ArrayList<>(List.of("ROLE_NURSE"))); + + filter.doFilter(request, new MockHttpServletResponse(), chain); + + assertNotNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + @DisplayName("a request without a token is passed straight through") + void doFilterInternal_passesARequestWithoutATokenStraightThrough() throws Exception { + filter.doFilter(new MockHttpServletRequest("POST", "/ANC/save"), new MockHttpServletResponse(), chain); + + assertNull(SecurityContextHolder.getContext().getAuthentication()); + // The early return and the finally block both forward the request. + verify(chain, org.mockito.Mockito.times(2)).doFilter(any(), any()); + } + + @Test + @DisplayName("a token with no readable claims is passed straight through") + void doFilterInternal_passesATokenWithNoClaimsStraightThrough() throws Exception { + when(jwtUtil.extractAllClaims("token")).thenReturn(null); + + filter.doFilter(requestWithJwtCookie("token"), new MockHttpServletResponse(), chain); + + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + @DisplayName("a token without a user id is passed straight through") + void doFilterInternal_passesATokenWithoutAUserIdStraightThrough() throws Exception { + Claims claims = claimsWithUserId(null); + when(jwtUtil.extractAllClaims("token")).thenReturn(claims); + + filter.doFilter(requestWithJwtCookie("token"), new MockHttpServletResponse(), chain); + + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + @DisplayName("a token with a non-numeric user id is passed straight through") + void doFilterInternal_passesANonNumericUserIdStraightThrough() throws Exception { + Claims claims = claimsWithUserId("not-a-number"); + when(jwtUtil.extractAllClaims("token")).thenReturn(claims); + + filter.doFilter(requestWithJwtCookie("token"), new MockHttpServletResponse(), chain); + + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + @DisplayName("a failure while reading the roles clears the security context") + void doFilterInternal_clearsTheSecurityContextWhenTheRolesCannotBeRead() throws Exception { + Claims claims = claimsWithUserId("7"); + when(jwtUtil.extractAllClaims("token")).thenReturn(claims); + when(redisService.getUserRoleFromCache(7L)).thenReturn(new ArrayList<>()); + when(userService.getUserRoles(7L)).thenThrow(new IEMRException("No role found for userId : 7")); + + filter.doFilter(requestWithJwtCookie("token"), new MockHttpServletResponse(), chain); + + assertNull(SecurityContextHolder.getContext().getAuthentication()); + verify(chain).doFilter(any(), any()); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/redis/RedisStorageTest.java b/src/test/java/com/iemr/mmu/utils/redis/RedisStorageTest.java new file mode 100644 index 00000000..58efcf61 --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/redis/RedisStorageTest.java @@ -0,0 +1,170 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.redis; + +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.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; + +class RedisStorageTest { + + @Mock + private LettuceConnectionFactory connectionFactory; + @Mock + private RedisConnection redisConnection; + @Mock + private RedisTemplate redisTemplate; + @Mock + private ListOperations listOperations; + + @InjectMocks + private RedisStorage redisStorage; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + when(connectionFactory.getConnection()).thenReturn(redisConnection); + } + + @Test + @DisplayName("a session is stored only when the key is still free") + void setObject_storesTheSessionWhenTheKeyIsFree() { + when(redisConnection.get("key".getBytes())).thenReturn(null); + + assertEquals("key", redisStorage.setObject("key", "value", 60)); + verify(redisConnection).set(any(), any(), any(), any()); + } + + @Test + @DisplayName("an existing session is left untouched") + void setObject_leavesAnExistingSessionUntouched() { + when(redisConnection.get("key".getBytes())).thenReturn("existing".getBytes()); + + assertEquals("key", redisStorage.setObject("key", "value", 60)); + verify(redisConnection, org.mockito.Mockito.never()).set(any(), any(), any(), any()); + } + + @Test + @DisplayName("reading a session refreshes its expiry") + void getObject_refreshesTheExpiryOfAStoredSession() throws Exception { + when(redisConnection.get("key".getBytes())).thenReturn("value".getBytes()); + + assertEquals("value", redisStorage.getObject("key", 60)); + verify(redisConnection).expire("key".getBytes(), 60); + } + + @Test + @DisplayName("reading a session that is not stored fails") + void getObject_failsWhenTheSessionIsNotStored() { + when(redisConnection.get("key".getBytes())).thenReturn(null); + + assertThrows(RedisSessionException.class, () -> redisStorage.getObject("key", 60)); + } + + @Test + @DisplayName("deleting a session reports how many keys were removed") + void deleteObject_reportsHowManyKeysWereRemoved() throws Exception { + when(redisConnection.del("key".getBytes())).thenReturn(1L); + + assertEquals(1L, redisStorage.deleteObject("key")); + } + + @Test + @DisplayName("updating a stored session rewrites its value") + void updateObject_rewritesAStoredSession() throws Exception { + when(redisConnection.get("key".getBytes())).thenReturn("value".getBytes()); + + assertEquals("key", redisStorage.updateObject("key", "new value", 60)); + verify(redisConnection).set(any(), any(), any(), any()); + } + + @Test + @DisplayName("updating a session that is not stored fails") + void updateObject_failsWhenTheSessionIsNotStored() { + when(redisConnection.get("key".getBytes())).thenReturn(null); + + assertThrows(RedisSessionException.class, () -> redisStorage.updateObject("key", "new value", 60)); + } + + @Test + @DisplayName("caching a user's roles replaces whatever was cached before") + void cacheUserRoles_replacesTheCachedRoles() { + when(redisTemplate.opsForList()).thenReturn(listOperations); + + redisStorage.cacheUserRoles(7L, List.of("ROLE_NURSE")); + + verify(redisTemplate).delete("roles:7"); + verify(listOperations).rightPushAll("roles:7", List.of("ROLE_NURSE")); + } + + @Test + @DisplayName("a cache write that fails is not propagated") + void cacheUserRoles_swallowsAFailedCacheWrite() { + when(redisTemplate.opsForList()).thenThrow(new RuntimeException("redis down")); + + redisStorage.cacheUserRoles(7L, List.of("ROLE_NURSE")); + } + + @Test + @DisplayName("cached roles are read back for the user") + void getUserRoleFromCache_readsTheCachedRoles() { + when(redisTemplate.opsForList()).thenReturn(listOperations); + when(listOperations.range("roles:7", 0, -1)).thenReturn(List.of("ROLE_NURSE")); + + assertEquals(List.of("ROLE_NURSE"), redisStorage.getUserRoleFromCache(7L)); + } + + @Test + @DisplayName("a cache read that fails reports no roles") + void getUserRoleFromCache_reportsNoRolesWhenTheCacheIsUnreachable() { + when(redisTemplate.opsForList()).thenThrow(new RuntimeException("redis down")); + + assertNull(redisStorage.getUserRoleFromCache(7L)); + } + + @Test + @DisplayName("a Redis session failure carries its message and cause") + void redisSessionException_carriesItsMessageAndCause() { + assertEquals("boom", new RedisSessionException("boom").getMessage()); + assertEquals("boom", new RedisSessionException("boom", new RuntimeException("cause")).getMessage()); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/response/OutputResponseTest.java b/src/test/java/com/iemr/mmu/utils/response/OutputResponseTest.java new file mode 100644 index 00000000..38657cbe --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/response/OutputResponseTest.java @@ -0,0 +1,124 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.response; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.net.ConnectException; +import java.sql.SQLException; +import java.text.ParseException; + +import org.json.JSONException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.mmu.utils.exception.IEMRException; + +class OutputResponseTest { + + @Test + @DisplayName("a JSON object response is passed through as data") + void setResponse_keepsAJsonObjectAsData() { + OutputResponse response = new OutputResponse(); + response.setResponse("{\"name\":\"Asha\"}"); + + assertTrue(response.isSuccess()); + assertEquals(OutputResponse.SUCCESS, response.getStatusCode()); + assertEquals("Success", response.getErrorMessage()); + assertEquals("{\"name\":\"Asha\"}", response.getData()); + } + + @Test + @DisplayName("a JSON array response is passed through as data") + void setResponse_keepsAJsonArrayAsData() { + OutputResponse response = new OutputResponse(); + response.setResponse("[{\"name\":\"Asha\"}]"); + + assertTrue(response.getData().startsWith("[")); + } + + @Test + @DisplayName("a plain message response is wrapped in a response object") + void setResponse_wrapsAPlainMessage() { + OutputResponse response = new OutputResponse(); + response.setResponse("Data saved successfully"); + + assertTrue(response.getData().contains("Data saved successfully")); + assertTrue(response.getData().contains("response")); + } + + @Test + @DisplayName("each known exception type maps to its own status code") + void setError_mapsEachKnownExceptionToItsStatusCode() { + assertEquals(OutputResponse.USERID_FAILURE, errorFor(new IEMRException("bad user"))); + assertEquals(OutputResponse.OBJECT_FAILURE, errorFor(new JSONException("bad json"))); + assertEquals(OutputResponse.CODE_EXCEPTION, errorFor(new SQLException("bad sql"))); + assertEquals(OutputResponse.CODE_EXCEPTION, errorFor(new ParseException("bad date", 0))); + assertEquals(OutputResponse.CODE_EXCEPTION, errorFor(new NullPointerException("npe"))); + assertEquals(OutputResponse.ENVIRONMENT_EXCEPTION, errorFor(new IOException("io"))); + assertEquals(OutputResponse.ENVIRONMENT_EXCEPTION, errorFor(new ConnectException("refused"))); + assertEquals(OutputResponse.GENERIC_FAILURE, errorFor(new RuntimeException("boom"))); + } + + private int errorFor(Throwable thrown) { + OutputResponse response = new OutputResponse(); + response.setError(thrown); + return response.getStatusCode(); + } + + @Test + @DisplayName("an explicit error carries its own code, message and status") + void setError_carriesTheGivenCodeAndMessage() { + OutputResponse response = new OutputResponse(); + response.setError(404, "Not found", "MISSING"); + + assertEquals(404, response.getStatusCode()); + assertEquals("Not found", response.getErrorMessage()); + assertEquals("MISSING", response.getStatus()); + assertFalse(response.isSuccess()); + } + + @Test + @DisplayName("an error without a status reuses the message as the status") + void setError_reusesTheMessageAsTheStatus() { + OutputResponse response = new OutputResponse(); + response.setError(400, "Bad request"); + + assertEquals("Bad request", response.getStatus()); + } + + @Test + @DisplayName("a failed response carries no data") + void getData_returnsNothingForAFailedResponse() { + assertNull(new OutputResponse().getData()); + } + + @Test + @DisplayName("serialising with nulls keeps the empty data field") + void toStringWithSerialization_keepsTheEmptyDataField() { + assertTrue(new OutputResponse().toStringWithSerialization().contains("\"data\":null")); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/sessionobject/SessionObjectTest.java b/src/test/java/com/iemr/mmu/utils/sessionobject/SessionObjectTest.java new file mode 100644 index 00000000..1e8daa00 --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/sessionobject/SessionObjectTest.java @@ -0,0 +1,110 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.sessionobject; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.mmu.utils.redis.RedisSessionException; +import com.iemr.mmu.utils.redis.RedisStorage; + +class SessionObjectTest { + + private RedisStorage objectStore; + private SessionObject sessionObject; + + @BeforeEach + void setUp() { + objectStore = mock(RedisStorage.class); + sessionObject = new SessionObject(); + sessionObject.setObjectStore(objectStore); + } + + @Test + @DisplayName("reading a session delegates to the store with the configured expiry") + void getSessionObject_readsFromTheStore() throws Exception { + when(objectStore.getObject(anyString(), anyInt())).thenReturn("{}"); + + assertEquals("{}", sessionObject.getSessionObject("key")); + } + + @Test + @DisplayName("writing a session delegates to the store") + void setSessionObject_writesToTheStore() throws Exception { + when(objectStore.setObject(anyString(), anyString(), anyInt())).thenReturn("key"); + + assertEquals("key", sessionObject.setSessionObject("key", "{}")); + } + + @Test + @DisplayName("updating a session also refreshes the concurrent-session entry for the user") + void updateSessionObject_refreshesTheConcurrentSessionEntry() throws Exception { + when(objectStore.updateObject(anyString(), anyString(), anyInt())).thenReturn("key"); + + assertEquals("key", sessionObject.updateSessionObject("key", "{\"userName\":\" Nurse1 \"}")); + verify(objectStore).updateObject(org.mockito.ArgumentMatchers.eq("nurse1"), + org.mockito.ArgumentMatchers.eq("key"), anyInt()); + } + + @Test + @DisplayName("updating a session with no user name only refreshes the session itself") + void updateSessionObject_skipsTheConcurrentSessionEntryWithoutAUserName() throws Exception { + when(objectStore.updateObject(anyString(), anyString(), anyInt())).thenReturn("key"); + + assertEquals("key", sessionObject.updateSessionObject("key", "{}")); + verify(objectStore, never()).updateObject(org.mockito.ArgumentMatchers.eq("nurse1"), anyString(), anyInt()); + } + + @Test + @DisplayName("an unparseable session value does not stop the update") + void updateSessionObject_toleratesAnUnparseableValue() throws Exception { + when(objectStore.updateObject(anyString(), anyString(), anyInt())).thenReturn("key"); + + assertEquals("key", sessionObject.updateSessionObject("key", "not-json")); + } + + @Test + @DisplayName("deleting a session delegates to the store") + void deleteSessionObject_deletesFromTheStore() throws Exception { + sessionObject.deleteSessionObject("key"); + + verify(objectStore).deleteObject("key"); + } + + @Test + @DisplayName("a store failure is passed on to the caller") + void getSessionObject_passesOnAStoreFailure() throws Exception { + when(objectStore.getObject(anyString(), anyInt())).thenThrow(new RedisSessionException("redis down")); + + assertThrows(RedisSessionException.class, () -> sessionObject.getSessionObject("key")); + } +} diff --git a/src/test/java/com/iemr/mmu/utils/validator/ValidatorTest.java b/src/test/java/com/iemr/mmu/utils/validator/ValidatorTest.java new file mode 100644 index 00000000..55d7d4df --- /dev/null +++ b/src/test/java/com/iemr/mmu/utils/validator/ValidatorTest.java @@ -0,0 +1,137 @@ +/* +* AMRIT – Accessible Medical Records via Integrated Technology +* Integrated EHR (Electronic Health Records) Solution +* +* Copyright (C) "Piramal Swasthya Management and Research Institute" +* +* This file is part of AMRIT. +* +* 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 https://www.gnu.org/licenses/. +*/ +package com.iemr.mmu.utils.validator; + +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.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.mmu.utils.exception.IEMRException; +import com.iemr.mmu.utils.redis.RedisSessionException; +import com.iemr.mmu.utils.sessionobject.SessionObject; + +class ValidatorTest { + + private SessionObject sessionObject; + private Validator validator; + + @BeforeEach + void setUp() { + sessionObject = mock(SessionObject.class); + validator = new Validator(); + validator.setSessionObject(sessionObject); + } + + private JSONObject loginResponse(String ipAddress) throws Exception { + JSONObject response = new JSONObject(); + response.put("loginIPAddress", ipAddress); + return response; + } + + @Test + @DisplayName("a fresh login stores the session and reports success") + void updateCacheObj_storesTheSessionOfAFreshLogin() throws Exception { + when(sessionObject.getSessionObject("key")).thenReturn(null); + + JSONObject result = validator.updateCacheObj(loginResponse("10.0.0.1"), "key", "ipKey"); + + assertEquals("key", result.get("key")); + assertEquals("login success", result.get("sessionStatus")); + verify(sessionObject).setSessionObject(anyString(), anyString()); + } + + @Test + @DisplayName("a session store that is unreachable still reports the login outcome") + void updateCacheObj_reportsTheLoginOutcomeWhenTheStoreIsUnreachable() throws Exception { + when(sessionObject.getSessionObject("key")).thenThrow(new RedisSessionException("redis down")); + + JSONObject result = validator.updateCacheObj(loginResponse("10.0.0.1"), "key", "ipKey"); + + assertEquals("login success", result.get("sessionStatus")); + } + + @Test + @DisplayName("a login from a second IP is reported when IP validation is on") + void updateCacheObj_reportsALoginFromASecondIpAddress() throws Exception { + ReflectionTestUtils.setField(Validator.class, "enableIPValidation", true); + try { + when(sessionObject.getSessionObject("key")) + .thenReturn(new JSONObject().put("loginIPAddress", "10.0.0.9").toString()); + + JSONObject result = validator.updateCacheObj(loginResponse("10.0.0.1"), "key", "ipKey"); + + assertTrue(((String) result.get("sessionStatus")).contains("10.0.0.9")); + } finally { + ReflectionTestUtils.setField(Validator.class, "enableIPValidation", false); + } + } + + @Test + @DisplayName("the stored session is read back for a key") + void getSessionObject_readsTheStoredSession() throws Exception { + when(sessionObject.getSessionObject("key")).thenReturn("{}"); + + assertEquals("{}", validator.getSessionObject("key")); + } + + @Test + @DisplayName("a stored session passes the key check") + void checkKeyExists_acceptsAStoredSession() throws Exception { + when(sessionObject.getSessionObject("key")) + .thenReturn(new JSONObject().put("loginIPAddress", "10.0.0.1").toString()); + + validator.checkKeyExists("key", "10.0.0.1"); + } + + @Test + @DisplayName("a session that is not stored fails the key check") + void checkKeyExists_rejectsASessionThatIsNotStored() throws Exception { + when(sessionObject.getSessionObject("key")).thenReturn(null); + + IEMRException thrown = assertThrows(IEMRException.class, () -> validator.checkKeyExists("key", "10.0.0.1")); + assertEquals("Session is expired. Please login again.", thrown.getMessage()); + } + + @Test + @DisplayName("a session opened from another IP fails the key check when IP validation is on") + void checkKeyExists_rejectsASessionFromAnotherIpAddress() throws Exception { + ReflectionTestUtils.setField(Validator.class, "enableIPValidation", true); + try { + when(sessionObject.getSessionObject("key")) + .thenReturn(new JSONObject().put("loginIPAddress", "10.0.0.9").toString()); + + assertThrows(IEMRException.class, () -> validator.checkKeyExists("key", "10.0.0.1")); + } finally { + ReflectionTestUtils.setField(Validator.class, "enableIPValidation", false); + } + } +}