diff --git a/pom.xml b/pom.xml index 062df58..4bc3e13 100644 --- a/pom.xml +++ b/pom.xml @@ -280,6 +280,17 @@ h2 runtime + + org.springframework.boot + spring-boot-starter-test + test + + + jakarta.xml.bind + jakarta.xml.bind-api + + + diff --git a/src/test/java/com/iemr/admin/controller/blocking/BlockingControllerTest.java b/src/test/java/com/iemr/admin/controller/blocking/BlockingControllerTest.java new file mode 100644 index 0000000..b93dab4 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/blocking/BlockingControllerTest.java @@ -0,0 +1,577 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.blocking; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.blocking.M_Providerservicemapping_Blocking; +import com.iemr.admin.data.blocking.M_Serviceprovider_Blocking; +import com.iemr.admin.data.blocking.M_Status1; +import com.iemr.admin.data.blocking.T_Providerservicemappingdetail; +import com.iemr.admin.data.blocking.T_Serviceproviderdetail; +import com.iemr.admin.data.blocking.T_Userdetail; +import com.iemr.admin.data.blocking.UserForBlocking; +import com.iemr.admin.data.blocking.V_Showproviderservicemapping; +import com.iemr.admin.service.blocking.BlockingInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The blocking endpoints suspend and reinstate a provider - whole, per service + * line, per state, or one user at a time - and write an audit row for each + * change of status. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("BlockingController Test Suite") +class BlockingControllerTest { + + private static final Integer PROVIDER_ID = 77; + private static final Integer SERVICE_ID = 3; + private static final Integer STATE_ID = 29; + + @Mock + private BlockingInter blockingInter; + + @InjectMocks + private BlockingController controller; + + private static M_Providerservicemapping_Blocking mapping(Integer mapId) { + M_Providerservicemapping_Blocking mapping = new M_Providerservicemapping_Blocking(); + mapping.setProviderServiceMapID(mapId); + mapping.setServiceProviderID(PROVIDER_ID); + mapping.setServiceID(SERVICE_ID); + mapping.setStateID(STATE_ID); + mapping.setStatusID(1); + return mapping; + } + + private static V_Showproviderservicemapping view(Integer mapId) { + V_Showproviderservicemapping view = new V_Showproviderservicemapping(); + view.setProviderServiceMapID(mapId); + view.setServiceProviderID(PROVIDER_ID); + return view; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("blockProvider1 should record the previous status before the provider is blocked") + void blockProvider1_shouldRecordPreviousStatus() { + M_Serviceprovider_Blocking stored = new M_Serviceprovider_Blocking(); + stored.setServiceProviderID(PROVIDER_ID); + stored.setServiceProviderName("Piramal Swasthya"); + stored.setStatusID(1); + T_Serviceproviderdetail saved = new T_Serviceproviderdetail(); + saved.setServiceProviderDetailID(9001); + when(blockingInter.getProviderDetailsById(PROVIDER_ID)).thenReturn(stored); + when(blockingInter.saveData(any())).thenReturn(saved); + + String response = controller.blockProvider1( + "{\"serviceProviderID\":77,\"statusID\":2,\"reason\":\"contract ended\"}"); + + assertSuccessContaining(response, "9001"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(T_Serviceproviderdetail.class); + verify(blockingInter).saveData(captor.capture()); + assertEquals(1, captor.getValue().getPreviousStatusID()); + assertEquals(2, captor.getValue().getUpdatedStatusID()); + assertEquals("contract ended", captor.getValue().getReason()); + assertEquals(2, stored.getStatusID(), "the provider must carry the new status when it is saved"); + verify(blockingInter).blockServiceProvider(stored); + } + + @Test + @DisplayName("blockProvider1 should answer an error envelope for a provider that does not exist") + void blockProvider1_shouldAnswerErrorEnvelopeForUnknownProvider() { + when(blockingInter.getProviderDetailsById(PROVIDER_ID)).thenReturn(null); + + assertCodeException(controller.blockProvider1("{\"serviceProviderID\":77,\"statusID\":2}")); + } + + @Test + @DisplayName("blockProvider should write one audit row per service mapping it blocks") + void blockProvider_shouldWriteOneAuditRowPerMapping() { + when(blockingInter.getProviderStatus(PROVIDER_ID)) + .thenReturn(new ArrayList<>(List.of(mapping(4001), mapping(4002)))); + T_Providerservicemappingdetail saved = new T_Providerservicemappingdetail(); + saved.setProviderServiceMapID(4001); + when(blockingInter.savetpsmd(anyList())).thenReturn(new ArrayList<>(List.of(saved))); + + String response = controller.blockProvider( + "{\"serviceProviderID\":77,\"statusID\":2,\"reason\":\"contract ended\"}"); + + assertSuccessContaining(response, "4001"); + verify(blockingInter).blockProvider(PROVIDER_ID, 2); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(blockingInter).savetpsmd(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals("contract ended", captor.getValue().get(0).getReason()); + } + + @Test + @DisplayName("blockProvider should answer an error envelope when the mappings cannot be read") + void blockProvider_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderStatus(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.blockProvider("{\"serviceProviderID\":77,\"statusID\":2}")); + } + + @Test + @DisplayName("blockProviderByServiceId should block only the service line the caller names") + void blockProviderByServiceId_shouldBlockOnlyNamedServiceLine() { + when(blockingInter.getProviderStatusByProviderAndServiceId(PROVIDER_ID, SERVICE_ID)) + .thenReturn(new ArrayList<>(List.of(mapping(4001)))); + T_Providerservicemappingdetail saved = new T_Providerservicemappingdetail(); + saved.setProviderServiceMapID(4001); + when(blockingInter.savetpsmd(anyList())).thenReturn(new ArrayList<>(List.of(saved))); + + assertSuccessContaining(controller.blockProviderByServiceId( + "{\"serviceProviderID\":77,\"serviceID\":3,\"statusID\":2}"), "4001"); + verify(blockingInter).blockProviderByProviderIdAndServiceId(PROVIDER_ID, SERVICE_ID, 2); + } + + @Test + @DisplayName("blockProviderByServiceId should answer an error envelope when the mappings cannot be read") + void blockProviderByServiceId_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderStatusByProviderAndServiceId(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure( + controller.blockProviderByServiceId("{\"serviceProviderID\":77,\"serviceID\":3,\"statusID\":2}")); + } + + @Test + @DisplayName("getProviderStatus should answer the mapping view for the provider") + void getProviderStatus_shouldAnswerMappingView() { + when(blockingInter.getProviderStatus1(PROVIDER_ID)).thenReturn(new ArrayList<>(List.of(view(4001)))); + + assertSuccessContaining(controller.getProviderStatus("{\"serviceProviderID\":77}"), "4001"); + } + + @Test + @DisplayName("getProviderStatus should answer an error envelope when the view cannot be read") + void getProviderStatus_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderStatus1(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getProviderStatus("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getProviderStatus1 should answer the second mapping view for the provider") + void getProviderStatus1_shouldAnswerSecondView() { + when(blockingInter.getProviderStatus2(PROVIDER_ID)).thenReturn(new ArrayList<>(List.of(view(4001)))); + + assertSuccessContaining(controller.getProviderStatus1("{\"serviceProviderID\":77}"), "4001"); + } + + @Test + @DisplayName("getProviderStatus1 should answer an error envelope when the view cannot be read") + void getProviderStatus1_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderStatus2(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getProviderStatus1("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getServiceLiensUsingProvider should answer the service lines the provider runs") + void getServiceLiensUsingProvider_shouldAnswerServiceLines() { + when(blockingInter.getServiceLiensUsingProvider(PROVIDER_ID)) + .thenReturn(new ArrayList<>(List.of(mapping(4001)))); + + assertSuccessContaining(controller.getServiceLiensUsingProvider("{\"serviceProviderID\":77}"), "4001"); + } + + @Test + @DisplayName("getServiceLiensUsingProvider should answer an error envelope when the lookup fails") + void getServiceLiensUsingProvider_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getServiceLiensUsingProvider(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getServiceLiensUsingProvider("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getProviderStatusByProviderAndServiceId should narrow the view to the service line") + void getProviderStatusByProviderAndServiceId_shouldNarrowToServiceLine() { + when(blockingInter.getProviderStatusByProviderAndServiceId2(PROVIDER_ID, SERVICE_ID)) + .thenReturn(new ArrayList<>(List.of(view(4001)))); + + assertSuccessContaining(controller.getProviderStatusByProviderAndServiceId( + "{\"serviceProviderID\":77,\"serviceID\":3}"), "4001"); + } + + @Test + @DisplayName("getProviderStatusByProviderAndServiceId should answer an error envelope when the lookup fails") + void getProviderStatusByProviderAndServiceId_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderStatusByProviderAndServiceId2(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller + .getProviderStatusByProviderAndServiceId("{\"serviceProviderID\":77,\"serviceID\":3}")); + } + + @Test + @DisplayName("blockProviderByService should record the previous status of the one mapping it blocks") + void blockProviderByService_shouldRecordPreviousStatus() { + M_Providerservicemapping_Blocking stored = mapping(4001); + T_Providerservicemappingdetail saved = new T_Providerservicemappingdetail(); + saved.setProviderServiceMapID(4001); + when(blockingInter.getProviderServiceMappingDetails(PROVIDER_ID, STATE_ID, SERVICE_ID)).thenReturn(stored); + when(blockingInter.savetpsdData(any())).thenReturn(saved); + + assertSuccessContaining(controller.blockProviderByService("{\"serviceProviderID\":77,\"stateID\":29," + + "\"serviceID\":3,\"statusID\":2,\"reason\":\"suspended\"}"), "4001"); + verify(blockingInter).blockProviderByService(PROVIDER_ID, STATE_ID, SERVICE_ID, 2); + } + + @Test + @DisplayName("blockProviderByService should answer an error envelope for a mapping that does not exist") + void blockProviderByService_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(blockingInter.getProviderServiceMappingDetails(any(), any(), any())).thenReturn(null); + + assertCodeException(controller.blockProviderByService( + "{\"serviceProviderID\":77,\"stateID\":29,\"serviceID\":3,\"statusID\":2}")); + } + + @Test + @DisplayName("getProviderStatusByService should answer the view for the service line in the state") + void getProviderStatusByService_shouldAnswerViewForServiceInState() { + when(blockingInter.getProviderServiceMappingDetails2(PROVIDER_ID, STATE_ID, SERVICE_ID)) + .thenReturn(new ArrayList<>(List.of(view(4001)))); + + assertSuccessContaining(controller.getProviderStatusByService( + "{\"serviceProviderID\":77,\"stateID\":29,\"serviceID\":3}"), "4001"); + } + + @Test + @DisplayName("getProviderStatusByService should answer an error envelope when the lookup fails") + void getProviderStatusByService_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderServiceMappingDetails2(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getProviderStatusByService("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("blockProviderByState should write one audit row per mapping in the state") + void blockProviderByState_shouldWriteOneAuditRowPerMapping() { + when(blockingInter.getProviderStateMappingDetails(PROVIDER_ID, STATE_ID)) + .thenReturn(List.of(mapping(4001), mapping(4002))); + T_Providerservicemappingdetail saved = new T_Providerservicemappingdetail(); + saved.setProviderServiceMapID(4001); + when(blockingInter.savetpsmd(anyList())).thenReturn(new ArrayList<>(List.of(saved))); + + assertSuccessContaining(controller.blockProviderByState( + "{\"serviceProviderID\":77,\"stateID\":29,\"statusID\":2,\"reason\":\"suspended\"}"), "4001"); + verify(blockingInter).blockProviderByState(PROVIDER_ID, STATE_ID, 2); + } + + @Test + @DisplayName("blockProviderByState should answer an error envelope when the mappings cannot be read") + void blockProviderByState_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderStateMappingDetails(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure( + controller.blockProviderByState("{\"serviceProviderID\":77,\"stateID\":29,\"statusID\":2}")); + } + + @Test + @DisplayName("getProviderStatusByState should answer the view for the state") + void getProviderStatusByState_shouldAnswerViewForState() { + when(blockingInter.getProviderStateMappingDetails1(PROVIDER_ID, STATE_ID)) + .thenReturn(new ArrayList<>(List.of(view(4001)))); + + assertSuccessContaining( + controller.getProviderStatusByState("{\"serviceProviderID\":77,\"stateID\":29}"), "4001"); + } + + @Test + @DisplayName("getProviderStatusByState should answer an error envelope when the lookup fails") + void getProviderStatusByState_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getProviderStateMappingDetails1(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getProviderStatusByState("{\"serviceProviderID\":77,\"stateID\":29}")); + } + + @Test + @DisplayName("blockUser should record the user's previous credentials before the block takes effect") + void blockUser_shouldRecordPreviousCredentials() { + UserForBlocking stored = new UserForBlocking(); + stored.setUserID(3117); + stored.setUserName("asha.rao"); + stored.setPassword("old-secret"); + stored.setStatusID(1); + when(blockingInter.getUserDetailByUserId(3117)).thenReturn(stored); + + String response = controller.blockUser("{\"userID\":3117,\"statusID\":2," + + "\"updatedPassword\":\"new-secret\",\"updatedStatusID\":2}"); + + assertSuccessContaining(response, "jai"); + verify(blockingInter).blockUser(3117, 2); + + ArgumentCaptor captor = ArgumentCaptor.forClass(T_Userdetail.class); + verify(blockingInter).saveUserDetails(captor.capture()); + assertEquals("old-secret", captor.getValue().getPreviousPassword()); + assertEquals("new-secret", captor.getValue().getUpdatedPassword()); + assertEquals(1, captor.getValue().getPreviousStatusID()); + } + + @Test + @DisplayName("blockUser should answer an error envelope for a user that does not exist") + void blockUser_shouldAnswerErrorEnvelopeForUnknownUser() { + when(blockingInter.getUserDetailByUserId(3117)).thenReturn(null); + + assertCodeException(controller.blockUser("{\"userID\":3117,\"statusID\":2}")); + } + + @Test + @DisplayName("getStatus should answer every status on record") + void getStatus_shouldAnswerEveryStatus() { + M_Status1 status = new M_Status1(); + status.setStatusID(1); + status.setStatus("Active"); + when(blockingInter.getStatusData()).thenReturn(new ArrayList<>(List.of(status))); + + assertSuccessContaining(controller.getStatus("{}"), "Active"); + } + + @Test + @DisplayName("getStatus should answer an error envelope when the lookup fails") + void getStatus_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getStatusData()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getStatus("{}")); + } + + @Test + @DisplayName("ProviderStateAndServiceLines should create one mapping per state the request names") + void providerStateAndServiceLines_shouldCreateOneMappingPerState() { + when(blockingInter.AddServiceProvider(anyList())) + .thenReturn(new ArrayList<>(List.of(mapping(4001)))); + + String response = controller.ProviderStateAndServiceLines("[{\"serviceProviderID\":77,\"serviceID\":3," + + "\"createdBy\":\"admin\",\"statusID\":1,\"stateID1\":[29,30]}]"); + + assertSuccessContaining(response, "4001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(blockingInter).AddServiceProvider(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals(29, captor.getValue().get(0).getStateID()); + } + + @Test + @DisplayName("ProviderStateAndServiceLines should fall back to the state on the record when none is listed") + void providerStateAndServiceLines_shouldFallBackToRecordState() { + when(blockingInter.AddServiceProvider(anyList())).thenReturn(new ArrayList<>()); + + controller.ProviderStateAndServiceLines("[{\"serviceProviderID\":77,\"serviceID\":3," + + "\"stateID\":29,\"createdBy\":\"admin\",\"statusID\":1,\"stateID1\":[]}]"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(blockingInter).AddServiceProvider(captor.capture()); + assertEquals(1, captor.getValue().size()); + assertEquals(29, captor.getValue().get(0).getStateID()); + } + + @Test + @DisplayName("ProviderStateAndServiceLines should answer an error envelope when no states are named") + void providerStateAndServiceLines_shouldAnswerErrorEnvelopeWithoutStates() { + assertCodeException(controller.ProviderStateAndServiceLines("[{\"serviceProviderID\":77}]")); + } + + @Test + @DisplayName("deleteProviderStateAndServiceLines should build one record per service line named") + void deleteProviderStateAndServiceLines_shouldBuildOneRecordPerService() { + when(blockingInter.AddServiceProvider(anyList())).thenReturn(new ArrayList<>(List.of(mapping(4001)))); + + String response = controller.deleteProviderStateAndServiceLines("{\"serviceProviderID\":77," + + "\"stateID\":29,\"createdBy\":\"admin\",\"statusID\":1,\"serviceID1\":[3,4]}"); + + assertSuccessContaining(response, "4001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(blockingInter).AddServiceProvider(captor.capture()); + assertEquals(2, captor.getValue().size()); + } + + @Test + @DisplayName("deleteProviderStateAndServiceLines should answer an error envelope when no services are named") + void deleteProviderStateAndServiceLines_shouldAnswerErrorEnvelopeWithoutServices() { + assertCodeException(controller.deleteProviderStateAndServiceLines("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("createCitMappingwithServiceLines should answer the summary the service reports") + void createCitMappingwithServiceLines_shouldAnswerSummary() { + when(blockingInter.mapctidata(anyList())).thenReturn("2 campaigns mapped"); + + assertSuccessContaining(controller.createCitMappingwithServiceLines( + "[{\"providerServiceMapID\":4001,\"cTI_CampaignName\":\"104\"}]"), "2 campaigns mapped"); + } + + @Test + @DisplayName("createCitMappingwithServiceLines should answer an error envelope when the mapping fails") + void createCitMappingwithServiceLines_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.mapctidata(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createCitMappingwithServiceLines("[{\"providerServiceMapID\":4001}]")); + } + + @Test + @DisplayName("getMappedServiceLinesAndStatetoProvider should answer the mappings the service resolves") + void getMappedServiceLines_shouldAnswerResolvedMappings() { + when(blockingInter.getServiceLiensUsingProvider1(any())) + .thenReturn(new ArrayList<>(List.of(mapping(4001)))); + + assertSuccessContaining( + controller.getMappedServiceLinesAndStatetoProvider("{\"serviceProviderID\":77}"), "4001"); + } + + @Test + @DisplayName("getMappedServiceLinesAndStatetoProvider should answer an error envelope when the lookup fails") + void getMappedServiceLines_shouldAnswerErrorEnvelopeOnFailure() { + when(blockingInter.getServiceLiensUsingProvider1(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getMappedServiceLinesAndStatetoProvider("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("mapProviderAndServiceLines should mark every new mapping active") + void mapProviderAndServiceLines_shouldMarkNewMappingsActive() { + when(blockingInter.AddServiceProvider(anyList())).thenReturn(new ArrayList<>(List.of(mapping(4001)))); + + String response = controller.mapProviderAndServiceLines("[{\"serviceProviderID\":77,\"serviceID\":3," + + "\"createdBy\":\"admin\",\"stateID1\":[29,30]}]"); + + assertSuccessContaining(response, "4001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(blockingInter).AddServiceProvider(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals(1, captor.getValue().get(0).getStatusID(), "a new mapping starts active"); + } + + @Test + @DisplayName("mapProviderAndServiceLines should fall back to the state on the record when none is listed") + void mapProviderAndServiceLines_shouldFallBackToRecordState() { + when(blockingInter.AddServiceProvider(anyList())).thenReturn(new ArrayList<>()); + + controller.mapProviderAndServiceLines("[{\"serviceProviderID\":77,\"serviceID\":3," + + "\"stateID\":29,\"createdBy\":\"admin\",\"stateID1\":[]}]"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(blockingInter).AddServiceProvider(captor.capture()); + assertEquals(1, captor.getValue().size()); + } + + @Test + @DisplayName("mapProviderAndServiceLines should answer an error envelope when no states are named") + void mapProviderAndServiceLines_shouldAnswerErrorEnvelopeWithoutStates() { + assertCodeException(controller.mapProviderAndServiceLines("[{\"serviceProviderID\":77}]")); + } + + @Test + @DisplayName("editMappedServiceLinesAndStatetoProvider should copy the edits onto the stored mapping") + void editMappedServiceLines_shouldCopyEdits() { + M_Providerservicemapping_Blocking stored = mapping(4001); + when(blockingInter.getDataByProviderServiceMapId(4001)).thenReturn(stored); + when(blockingInter.updateProviderData(stored)).thenReturn(stored); + + String response = controller.editMappedServiceLinesAndStatetoProvider("{\"providerServiceMapID\":4001," + + "\"serviceProviderID\":78,\"serviceID\":4,\"stateID\":30,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "4001"); + assertEquals(78, stored.getServiceProviderID()); + assertEquals(30, stored.getStateID()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("editMappedServiceLinesAndStatetoProvider should answer an error envelope for an unknown mapping") + void editMappedServiceLines_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(blockingInter.getDataByProviderServiceMapId(4001)).thenReturn(null); + + assertCodeException( + controller.editMappedServiceLinesAndStatetoProvider("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("deleteMappedServiceLinesAndStatetoProvider should mark the mapping deleted") + void deleteMappedServiceLines_shouldMarkMappingDeleted() { + M_Providerservicemapping_Blocking stored = mapping(4001); + when(blockingInter.getDataByProviderServiceMapId(4001)).thenReturn(stored); + when(blockingInter.updateProviderData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteMappedServiceLinesAndStatetoProvider( + "{\"providerServiceMapID\":4001,\"deleted\":true}"), "4001"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteMappedServiceLinesAndStatetoProvider should answer an error envelope for an unknown mapping") + void deleteMappedServiceLines_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(blockingInter.getDataByProviderServiceMapId(4001)).thenReturn(null); + + assertCodeException(controller.deleteMappedServiceLinesAndStatetoProvider( + "{\"providerServiceMapID\":4001,\"deleted\":true}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/bulkRegistration/BulkRegistrationControllerTest.java b/src/test/java/com/iemr/admin/controller/bulkRegistration/BulkRegistrationControllerTest.java new file mode 100644 index 0000000..ab12766 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/bulkRegistration/BulkRegistrationControllerTest.java @@ -0,0 +1,185 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.bulkRegistration; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; + +import com.iemr.admin.data.bulkuser.BulkRegistrationError; +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.repo.employeemaster.EmployeeMasterRepoo; +import com.iemr.admin.service.bulkRegistration.BulkRegistrationService; +import com.iemr.admin.service.bulkRegistration.BulkRegistrationServiceImpl; +import com.iemr.admin.service.bulkRegistration.EmployeeXmlService; +import com.iemr.admin.service.locationmaster.LocationMasterServiceInter; + +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.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; + +/** + * The bulk registration endpoint reports how much of an uploaded sheet was + * accepted, and hands the rejected rows back as a spreadsheet the uploader can + * correct and resubmit. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("BulkRegistrationController Test Suite") +class BulkRegistrationControllerTest { + + @Mock + private EmployeeXmlService employeeXmlService; + + @Spy + private BulkRegistrationServiceImpl bulkRegistrationServiceimpl; + + @Mock + private BulkRegistrationService bulkRegistrationService; + + @Mock + private EmployeeMasterRepoo employeeMasterRepoo; + + @Mock + private LocationMasterServiceInter locationMasterServiceInter; + + @InjectMocks + private BulkRegistrationController controller; + + @Test + @DisplayName("registerBulkUser should report how many rows were accepted and what was rejected") + void registerBulkUser_shouldReportAcceptedAndRejectedRows() throws Exception { + doAnswer(call -> { + bulkRegistrationServiceimpl.totalEmployeeListSize = 3; + bulkRegistrationServiceimpl.m_bulkUser.add(new M_User1()); + bulkRegistrationServiceimpl.m_bulkUser.add(new M_User1()); + bulkRegistrationServiceimpl.errorLogs.add("Row 3: Title is missing."); + return null; + }).when(bulkRegistrationService).registerBulkUser(anyString(), anyString(), anyString(), anyInt()); + + ResponseEntity> response = controller.registerBulkUser( + "", "auth", "admin", new MockHttpServletRequest(), 77); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Success", response.getBody().get("status")); + assertEquals(200, response.getBody().get("statusCode")); + assertEquals(3, response.getBody().get("totalUser")); + assertEquals(2, response.getBody().get("registeredUser")); + assertTrue(response.getBody().get("error").toString().contains("Title is missing.")); + } + + @Test + @DisplayName("registerBulkUser should clear the accumulated state so the next upload starts clean") + void registerBulkUser_shouldClearAccumulatedState() throws Exception { + doAnswer(call -> { + bulkRegistrationServiceimpl.totalEmployeeListSize = 1; + bulkRegistrationServiceimpl.m_bulkUser.add(new M_User1()); + bulkRegistrationServiceimpl.errorLogs.add("Row 1: Title is missing."); + return null; + }).when(bulkRegistrationService).registerBulkUser(anyString(), anyString(), anyString(), anyInt()); + + controller.registerBulkUser("", "auth", "admin", new MockHttpServletRequest(), 77); + + assertTrue(bulkRegistrationServiceimpl.m_bulkUser.isEmpty()); + assertTrue(bulkRegistrationServiceimpl.errorLogs.isEmpty()); + assertEquals(0, bulkRegistrationServiceimpl.totalEmployeeListSize); + } + + @Test + @DisplayName("registerBulkUser should discard the rejected rows of a previous upload before it starts") + void registerBulkUser_shouldDiscardPreviousRejectedRows() throws Exception { + bulkRegistrationServiceimpl.bulkRegistrationErrors.add(new BulkRegistrationError()); + + controller.registerBulkUser("", "auth", "admin", new MockHttpServletRequest(), 77); + + verify(bulkRegistrationService).registerBulkUser("", "auth", "admin", 77); + } + + @Test + @DisplayName("registerBulkUser should report the failure rather than a partial success") + void registerBulkUser_shouldReportFailure() throws Exception { + doThrow(new IllegalStateException("the upload could not be read")) + .when(bulkRegistrationService).registerBulkUser(anyString(), anyString(), anyString(), anyInt()); + + ResponseEntity> response = controller.registerBulkUser( + "", "auth", "admin", new MockHttpServletRequest(), 77); + + assertEquals(500, response.getBody().get("statusCode")); + assertEquals("the upload could not be read", response.getBody().get("message")); + } + + @Test + @DisplayName("downloadErrorSheet should answer the rejected rows as a spreadsheet attachment") + void downloadErrorSheet_shouldAnswerSpreadsheetAttachment() { + BulkRegistrationError error = new BulkRegistrationError(); + error.setUserName("EMP-1"); + error.setError(java.util.List.of("Title is missing.")); + bulkRegistrationServiceimpl.bulkRegistrationErrors.add(error); + + ResponseEntity response = controller.downloadErrorSheet(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertTrue(response.getBody().length > 0); + assertTrue(response.getHeaders().getFirst("Content-Disposition").contains("error_log.xlsx")); + assertTrue(bulkRegistrationServiceimpl.bulkRegistrationErrors.isEmpty(), + "the rejected rows are handed over once, then cleared"); + } + + @Test + @DisplayName("downloadErrorSheet should still answer a sheet when nothing was rejected") + void downloadErrorSheet_shouldAnswerSheetWithoutRejectedRows() { + ResponseEntity response = controller.downloadErrorSheet(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.getBody().length > 0); + } + + @Test + @DisplayName("downloadErrorSheet should answer a failure rather than an empty file when the sheet cannot be built") + void downloadErrorSheet_shouldAnswerFailureWhenSheetCannotBeBuilt() { + doThrow(new IllegalStateException("out of memory")).when(bulkRegistrationServiceimpl).insertErrorLog(); + + ResponseEntity response = controller.downloadErrorSheet(); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertNull(response.getBody()); + } +} diff --git a/src/test/java/com/iemr/admin/controller/calibration/CalibrationControllerTest.java b/src/test/java/com/iemr/admin/controller/calibration/CalibrationControllerTest.java new file mode 100644 index 0000000..1f3a60e --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/calibration/CalibrationControllerTest.java @@ -0,0 +1,177 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.calibration; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.calibration.CalibrationStrip; +import com.iemr.admin.service.calibration.CalibrationService; +import com.iemr.admin.utils.exception.IEMRException; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * The calibration screen keeps the test strip codes a provider calibrates + * against. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("CalibrationController Test Suite") +class CalibrationControllerTest { + + private static final String REQUEST = "{\"calibrationStripID\":6601,\"stripCode\":\"STRIP-77\"," + + "\"providerServiceMapID\":4001,\"deleted\":false,\"createdBy\":\"admin\"}"; + + @Mock + private CalibrationService calibrationService; + + @InjectMocks + private CalibrationController controller; + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static String errorMessageOf(String response) { + return new JSONObject(response).getString("errorMessage"); + } + + @Test + @DisplayName("createCalibrationStrip should confirm the strip it recorded") + void create_shouldConfirmRecordedStrip() throws Exception { + when(calibrationService.saveData(any(CalibrationStrip.class))).thenReturn(1); + + String response = controller.createCalibrationStrip(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Data saved successfully"), response); + } + + @Test + @DisplayName("createCalibrationStrip should report the failure when no strip was recorded") + void create_shouldReportNothingRecorded() throws Exception { + when(calibrationService.saveData(any(CalibrationStrip.class))).thenReturn(0); + + assertEquals("Error while saving Calibration master data", + errorMessageOf(controller.createCalibrationStrip(REQUEST))); + } + + @Test + @DisplayName("createCalibrationStrip should pass on the refusal in the service's own words") + void create_shouldPassOnRefusal() throws Exception { + when(calibrationService.saveData(any(CalibrationStrip.class))) + .thenThrow(new IEMRException("Strip code already exists")); + + assertEquals("Strip code already exists", errorMessageOf(controller.createCalibrationStrip(REQUEST))); + } + + @Test + @DisplayName("fetchCalibrationStrips should answer the strips the provider holds") + void fetch_shouldAnswerHeldStrips() throws Exception { + when(calibrationService.fetchData(any(CalibrationStrip.class))) + .thenReturn("{\"calibrationData\":[{\"stripCode\":\"STRIP-77\"}]}"); + + String response = controller.fetchCalibrationStrips(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("STRIP-77"), response); + } + + @Test + @DisplayName("fetchCalibrationStrips should report the failure when the strips cannot be answered") + void fetch_shouldReportLookupFailure() throws Exception { + when(calibrationService.fetchData(any(CalibrationStrip.class))) + .thenThrow(new IEMRException("Error while fetching Calibration data")); + + assertEquals(OutputResponse.USERID_FAILURE, statusCodeOf(controller.fetchCalibrationStrips(REQUEST))); + } + + @Test + @DisplayName("deleteCalibrationStrip should confirm the retirement it recorded") + void delete_shouldConfirmRetirement() throws Exception { + when(calibrationService.deleteData(any(CalibrationStrip.class))).thenReturn(1); + + String response = controller.deleteCalibrationStrip(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Data deleted successfully"), response); + } + + @Test + @DisplayName("deleteCalibrationStrip should report the failure when no strip was retired") + void delete_shouldReportNothingRetired() throws Exception { + when(calibrationService.deleteData(any(CalibrationStrip.class))).thenReturn(0); + + assertEquals("Error while updating Calibration master data", + errorMessageOf(controller.deleteCalibrationStrip(REQUEST))); + } + + @Test + @DisplayName("deleteCalibrationStrip should report the failure when the request is refused") + void delete_shouldReportRefusal() throws Exception { + when(calibrationService.deleteData(any(CalibrationStrip.class))) + .thenThrow(new IEMRException("Invalid request")); + + assertEquals(OutputResponse.USERID_FAILURE, statusCodeOf(controller.deleteCalibrationStrip(REQUEST))); + } + + @Test + @DisplayName("updateCalibrationStrip should confirm the change it recorded") + void update_shouldConfirmRecordedChange() throws Exception { + when(calibrationService.updateData(any(CalibrationStrip.class))).thenReturn(1); + + String response = controller.updateCalibrationStrip(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Data updated successfully"), response); + } + + @Test + @DisplayName("updateCalibrationStrip should report the failure when no strip was changed") + void update_shouldReportNothingChanged() throws Exception { + when(calibrationService.updateData(any(CalibrationStrip.class))).thenReturn(0); + + assertEquals("Error while updating Calibration master data", + errorMessageOf(controller.updateCalibrationStrip(REQUEST))); + } + + @Test + @DisplayName("updateCalibrationStrip should pass on the refusal in the service's own words") + void update_shouldPassOnRefusal() throws Exception { + when(calibrationService.updateData(any(CalibrationStrip.class))) + .thenThrow(new IEMRException("Error while updating data")); + + assertEquals("Error while updating data", errorMessageOf(controller.updateCalibrationStrip(REQUEST))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/createorder/CareStreamCreateOrderControllerTest.java b/src/test/java/com/iemr/admin/controller/createorder/CareStreamCreateOrderControllerTest.java new file mode 100644 index 0000000..8c209fc --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/createorder/CareStreamCreateOrderControllerTest.java @@ -0,0 +1,66 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.createorder; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * The imaging order screens talk to a radiology server over a hard-coded socket + * address, so only the request reading that happens before the connection is + * opened can be checked here; a request the screen cannot read is refused + * without any connection being attempted at all. + */ +@DisplayName("CareStreamCreateOrderController Test Suite") +class CareStreamCreateOrderControllerTest { + + private static final String UNREADABLE_REQUEST = "{not json"; + + private final CareStreamCreateOrderController controller = new CareStreamCreateOrderController(); + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + @Test + @DisplayName("createOrder should refuse a request it cannot read rather than reach the radiology server") + void createOrder_shouldRefuseUnreadableRequest() throws Exception { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.createOrder(UNREADABLE_REQUEST))); + } + + @Test + @DisplayName("UpdateOrder should refuse a request it cannot read rather than reach the radiology server") + void updateOrder_shouldRefuseUnreadableRequest() throws Exception { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.UpdateOrder(UNREADABLE_REQUEST))); + } + + @Test + @DisplayName("deleteOrder should refuse a request it cannot read rather than reach the radiology server") + void deleteOrder_shouldRefuseUnreadableRequest() throws Exception { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.deleteOrder(UNREADABLE_REQUEST))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/drugstrength/DrugStrengthTest.java b/src/test/java/com/iemr/admin/controller/drugstrength/DrugStrengthTest.java new file mode 100644 index 0000000..def2c57 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/drugstrength/DrugStrengthTest.java @@ -0,0 +1,157 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.drugstrength; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.drugstrangth.M_104DrugStrength; +import com.iemr.admin.service.drugstrangth.DrugStrangthInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The drug strength screen keeps the strengths a drug can be dispensed in. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("DrugStrength controller Test Suite") +class DrugStrengthTest { + + private static final Integer STRENGTH_ID = 33; + + @Mock + private DrugStrangthInter durgStrangthInter; + + @InjectMocks + private DrugStrength controller; + + private static M_104DrugStrength strength() { + M_104DrugStrength strength = new M_104DrugStrength(); + strength.setDrugStrengthID(STRENGTH_ID); + strength.setDrugStrength("500 mg"); + strength.setDrugStrengthDesc("Standard adult dose"); + return strength; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("createDrugStrangth should answer the strengths it added") + void create_shouldAnswerAddedStrengths() { + when(durgStrangthInter.createDrugStrangth(anyList())).thenReturn(new ArrayList<>(List.of(strength()))); + + assertSuccessContaining(controller.createDrugStrangth("[{\"drugStrength\":\"500 mg\"}]"), "500 mg"); + } + + @Test + @DisplayName("createDrugStrangth should report the failure when the strength cannot be added") + void create_shouldReportStorageFailure() { + when(durgStrangthInter.createDrugStrangth(anyList())).thenThrow(new RuntimeException("already on file")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.createDrugStrangth("[{\"drugStrength\":\"500 mg\"}]"))); + } + + @Test + @DisplayName("getDrugStrangth should answer the strengths on file") + void get_shouldAnswerStrengthsOnFile() { + when(durgStrangthInter.getDrugStrangth()).thenReturn(new ArrayList<>(List.of(strength()))); + + assertSuccessContaining(controller.getDrugStrangth("{}"), "500 mg"); + } + + @Test + @DisplayName("getDrugStrangth should report the failure when the list cannot be answered") + void get_shouldReportLookupFailure() { + when(durgStrangthInter.getDrugStrangth()).thenThrow(new RuntimeException("connection reset")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getDrugStrangth("{}"))); + } + + @Test + @DisplayName("updateDrugStrangth should answer the strength whose details it changed") + void update_shouldAnswerChangedStrength() { + M_104DrugStrength stored = strength(); + when(durgStrangthInter.updateDrugStrangth(STRENGTH_ID)).thenReturn(stored); + when(durgStrangthInter.saveupdatedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.updateDrugStrangth( + "{\"drugStrengthID\":33,\"drugStrength\":\"650 mg\",\"drugStrengthDesc\":\"Higher dose\"," + + "\"modifiedBy\":\"admin\"}"), + "650 mg"); + assertEquals("Higher dose", stored.getDrugStrengthDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("updateDrugStrangth should report the failure when the strength is unknown") + void update_shouldReportUnknownStrength() { + when(durgStrangthInter.updateDrugStrangth(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, + statusCodeOf(controller.updateDrugStrangth("{\"drugStrengthID\":-1}"))); + } + + @Test + @DisplayName("deleteDrugStrangth should answer the strength it retired") + void delete_shouldAnswerRetiredStrength() { + M_104DrugStrength stored = strength(); + when(durgStrangthInter.updateDrugStrangth(STRENGTH_ID)).thenReturn(stored); + when(durgStrangthInter.saveupdatedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteDrugStrangth("{\"drugStrengthID\":33,\"deleted\":true}"), "33"); + assertEquals(Boolean.TRUE, stored.getDeleted()); + } + + @Test + @DisplayName("deleteDrugStrangth should report the failure when the strength is unknown") + void delete_shouldReportUnknownStrength() { + when(durgStrangthInter.updateDrugStrangth(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, + statusCodeOf(controller.deleteDrugStrangth("{\"drugStrengthID\":-1,\"deleted\":true}"))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/drugtype/DrugtypeControllerTest.java b/src/test/java/com/iemr/admin/controller/drugtype/DrugtypeControllerTest.java new file mode 100644 index 0000000..91d9d01 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/drugtype/DrugtypeControllerTest.java @@ -0,0 +1,160 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.drugtype; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.drugtype.M_Drugtype; +import com.iemr.admin.service.drugtype.DrugtypeInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The drug type screen keeps the dosage forms a provider stocks. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("DrugtypeController Test Suite") +class DrugtypeControllerTest { + + private static final Integer DRUG_TYPE_ID = 21; + + @Mock + private DrugtypeInter drugtypeInter; + + @InjectMocks + private DrugtypeController controller; + + private static M_Drugtype drugType() { + M_Drugtype drugType = new M_Drugtype(); + drugType.setDrugTypeID(DRUG_TYPE_ID); + drugType.setDrugTypeName("Tablet"); + drugType.setDrugTypeCode("TAB"); + drugType.setProviderServiceMapID(4001); + return drugType; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("createManufacturer should answer the drug types it added") + void create_shouldAnswerAddedDrugTypes() { + when(drugtypeInter.createDrugtypeData(anyList())).thenReturn(new ArrayList<>(List.of(drugType()))); + + assertSuccessContaining( + controller.createManufacturer("[{\"drugTypeName\":\"Tablet\",\"drugTypeCode\":\"TAB\"}]"), "Tablet"); + } + + @Test + @DisplayName("createManufacturer should report the failure when the drug type cannot be added") + void create_shouldReportStorageFailure() { + when(drugtypeInter.createDrugtypeData(anyList())).thenThrow(new RuntimeException("already on file")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.createManufacturer("[{\"drugTypeName\":\"Tablet\"}]"))); + } + + @Test + @DisplayName("getManufacturer should answer the drug types the provider stocks") + void get_shouldAnswerStockedDrugTypes() { + when(drugtypeInter.getDrugtypeData(4001)).thenReturn(new ArrayList<>(List.of(drugType()))); + + assertSuccessContaining(controller.getManufacturer("{\"providerServiceMapID\":4001}"), "Tablet"); + } + + @Test + @DisplayName("getManufacturer should report the failure when the list cannot be answered") + void get_shouldReportLookupFailure() { + when(drugtypeInter.getDrugtypeData(any())).thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getManufacturer("{\"providerServiceMapID\":4001}"))); + } + + @Test + @DisplayName("editManufacturer should answer the drug type whose details it changed") + void edit_shouldAnswerChangedDrugType() { + M_Drugtype stored = drugType(); + when(drugtypeInter.editDrugtypeData(DRUG_TYPE_ID)).thenReturn(stored); + when(drugtypeInter.saveeditDrugtype(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editManufacturer( + "{\"drugTypeID\":21,\"drugTypeName\":\"Capsule\",\"drugTypeCode\":\"CAP\",\"status\":\"A\"," + + "\"modifiedBy\":\"admin\"}"), + "Capsule"); + assertEquals("CAP", stored.getDrugTypeCode()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("editManufacturer should report the failure when the drug type is unknown") + void edit_shouldReportUnknownDrugType() { + when(drugtypeInter.editDrugtypeData(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, + statusCodeOf(controller.editManufacturer("{\"drugTypeID\":-1}"))); + } + + @Test + @DisplayName("deleteManufacturer should answer the drug type it retired") + void delete_shouldAnswerRetiredDrugType() { + M_Drugtype stored = drugType(); + when(drugtypeInter.editDrugtypeData(DRUG_TYPE_ID)).thenReturn(stored); + when(drugtypeInter.saveeditDrugtype(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteManufacturer("{\"drugTypeID\":21,\"deleted\":true}"), "21"); + assertEquals(Boolean.TRUE, stored.getDeleted()); + } + + @Test + @DisplayName("deleteManufacturer should report the failure when the drug type is unknown") + void delete_shouldReportUnknownDrugType() { + when(drugtypeInter.editDrugtypeData(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, + statusCodeOf(controller.deleteManufacturer("{\"drugTypeID\":-1,\"deleted\":true}"))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/emailconfig/EmailConfigControllerTest.java b/src/test/java/com/iemr/admin/controller/emailconfig/EmailConfigControllerTest.java new file mode 100644 index 0000000..de89ef1 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/emailconfig/EmailConfigControllerTest.java @@ -0,0 +1,147 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.emailconfig; + +import java.util.ArrayList; +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.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletRequest; + +import com.iemr.admin.model.emailconfig.AuthEmailRequest; +import com.iemr.admin.model.emailconfig.AuthEmailResponse; +import com.iemr.admin.model.emailconfig.CreateAuthEmailRequestModel; +import com.iemr.admin.model.emailconfig.UpdateAuthEmailRequest; +import com.iemr.admin.service.emailconfig.EmailConfigService; +import com.iemr.admin.utils.mapper.OutputMapper; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The email config screen keeps the authority mailboxes a complaint is copied + * to. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("EmailConfigController Test Suite") +class EmailConfigControllerTest { + + @Mock + private EmailConfigService emailConfigService; + + @InjectMocks + private EmailConfigController controller; + + private final MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + + @BeforeEach + @DisplayName("Prime the shared output builder the screens publish through") + void setUp() { + new OutputMapper(); + } + + private static AuthEmailResponse mailbox() { + AuthEmailResponse mailbox = new AuthEmailResponse(); + mailbox.setEmailID("dho.bengaluru@example.org"); + return mailbox; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + @Test + @DisplayName("saveConfig should answer the mailboxes it recorded") + void save_shouldAnswerRecordedMailboxes() { + when(emailConfigService.saveEmailConfigs(anyList())).thenReturn(List.of(mailbox())); + + String response = controller.saveConfig(List.of(new CreateAuthEmailRequestModel()), servletRequest); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("dho.bengaluru@example.org"), response); + } + + @Test + @DisplayName("saveConfig should report the failure when the mailbox cannot be recorded") + void save_shouldReportStorageFailure() { + when(emailConfigService.saveEmailConfigs(anyList())).thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.saveConfig(new ArrayList<>(), servletRequest))); + } + + @Test + @DisplayName("getEmailConfigs should answer the mailboxes matching what the caller narrowed by") + void get_shouldAnswerMatchingMailboxes() { + when(emailConfigService.getAllEmailConfigs(any(AuthEmailRequest.class))).thenReturn(List.of(mailbox())); + + String response = controller.getEmailConfigs(new AuthEmailRequest(), servletRequest); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("dho.bengaluru@example.org"), response); + } + + @Test + @DisplayName("getEmailConfigs should report the failure when the mailboxes cannot be answered") + void get_shouldReportLookupFailure() { + when(emailConfigService.getAllEmailConfigs(any(AuthEmailRequest.class))) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getEmailConfigs(new AuthEmailRequest(), servletRequest))); + } + + @Test + @DisplayName("updateEmailConfig should answer the mailbox as it stands after the change") + void update_shouldAnswerChangedMailbox() { + when(emailConfigService.updateEmailConfigs(any(UpdateAuthEmailRequest.class))).thenReturn(mailbox()); + + String response = controller.updateEmailConfig(new UpdateAuthEmailRequest(), servletRequest); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("dho.bengaluru@example.org"), response); + } + + @Test + @DisplayName("updateEmailConfig should report the failure when the change cannot be recorded") + void update_shouldReportStorageFailure() { + when(emailConfigService.updateEmailConfigs(any(UpdateAuthEmailRequest.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.updateEmailConfig(new UpdateAuthEmailRequest(), servletRequest))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/employeemaster/AshaSupervisorMappingControllerTest.java b/src/test/java/com/iemr/admin/controller/employeemaster/AshaSupervisorMappingControllerTest.java new file mode 100644 index 0000000..108c41f --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/employeemaster/AshaSupervisorMappingControllerTest.java @@ -0,0 +1,356 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.employeemaster; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.employeemaster.AshaSupervisorMapping; +import com.iemr.admin.data.employeemaster.M_UserServiceRoleMapping2; +import com.iemr.admin.data.store.M_Facility; +import com.iemr.admin.repo.employeemaster.EmployeeMasterRepo; +import com.iemr.admin.repository.store.MainStoreRepo; +import com.iemr.admin.service.employeemaster.AshaSupervisorMappingService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The ASHA supervisor endpoints decide which ASHAs each supervisor oversees at + * each facility, so a wrong mapping puts a health worker under the wrong + * supervisor. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("AshaSupervisorMappingController Test Suite") +class AshaSupervisorMappingControllerTest { + + @Mock + private AshaSupervisorMappingService ashaSupervisorMappingService; + + @Mock + private EmployeeMasterRepo employeeMasterRepo; + + @Mock + private MainStoreRepo mainStoreRepo; + + @InjectMocks + private AshaSupervisorMappingController controller; + + private static AshaSupervisorMapping mapping(Long id, Integer supervisorId, Integer ashaId) { + AshaSupervisorMapping mapping = new AshaSupervisorMapping(); + mapping.setId(id); + mapping.setSupervisorUserID(supervisorId); + mapping.setAshaUserID(ashaId); + return mapping; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + @Test + @DisplayName("getAshasByFacility should answer the ASHAs attached to the facilities the caller lists") + void getAshasByFacility_shouldAnswerAshasForListedFacilities() { + M_UserServiceRoleMapping2 asha = new M_UserServiceRoleMapping2(); + asha.setuSRMappingID(9001); + asha.setUserID(3117); + when(ashaSupervisorMappingService.getAshasByFacility(List.of(501, 502))) + .thenReturn(new ArrayList<>(List.of(asha))); + + assertSuccessContaining(controller.getAshasByFacility("{\"facilityIDs\":[501,502]}"), "9001"); + } + + @Test + @DisplayName("getAshasByFacility should fall back to the single facility id when no list is sent") + void getAshasByFacility_shouldFallBackToSingleFacilityId() { + when(ashaSupervisorMappingService.getAshasByFacility(List.of(501))).thenReturn(new ArrayList<>()); + + controller.getAshasByFacility("{\"facilityID\":501}"); + + verify(ashaSupervisorMappingService).getAshasByFacility(List.of(501)); + } + + @Test + @DisplayName("getAshasByFacility should ask for nothing when the request names no facility at all") + void getAshasByFacility_shouldAskForNothingWithoutAFacility() { + when(ashaSupervisorMappingService.getAshasByFacility(null)).thenReturn(new ArrayList<>()); + + controller.getAshasByFacility("{}"); + + verify(ashaSupervisorMappingService).getAshasByFacility(null); + } + + @Test + @DisplayName("getAshasByFacility should answer an error envelope when the lookup fails") + void getAshasByFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(ashaSupervisorMappingService.getAshasByFacility(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getAshasByFacility("{\"facilityIDs\":[501]}"))); + } + + @Test + @DisplayName("saveAshaSupervisorMapping should answer the mappings the service stored") + void saveAshaSupervisorMapping_shouldAnswerStoredMappings() { + when(ashaSupervisorMappingService.saveAshaSupervisorMappings(anyList())) + .thenReturn(new ArrayList<>(List.of(mapping(1L, 3117, 4001)))); + + assertSuccessContaining( + controller.saveAshaSupervisorMapping("[{\"supervisorUserID\":3117,\"ashaUserID\":4001}]"), "3117"); + } + + @Test + @DisplayName("saveAshaSupervisorMapping should answer an error envelope when the store fails") + void saveAshaSupervisorMapping_shouldAnswerErrorEnvelopeOnFailure() { + when(ashaSupervisorMappingService.saveAshaSupervisorMappings(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.saveAshaSupervisorMapping("[{\"supervisorUserID\":3117}]"))); + } + + @Test + @DisplayName("getSupervisorMappingByFacility should answer the mappings at the facility") + void getSupervisorMappingByFacility_shouldAnswerFacilityMappings() { + when(ashaSupervisorMappingService.getSupervisorMappingByFacility(501)) + .thenReturn(new ArrayList<>(List.of(mapping(1L, 3117, 4001)))); + + assertSuccessContaining(controller.getSupervisorMappingByFacility("{\"facilityID\":501}"), "3117"); + } + + @Test + @DisplayName("getSupervisorMappingByFacility should answer an error envelope when the lookup fails") + void getSupervisorMappingByFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(ashaSupervisorMappingService.getSupervisorMappingByFacility(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getSupervisorMappingByFacility("{\"facilityID\":501}"))); + } + + @Test + @DisplayName("deleteAshaSupervisorMapping should retire the mappings the caller names") + void deleteAshaSupervisorMapping_shouldRetireNamedMappings() { + String response = controller + .deleteAshaSupervisorMapping("{\"supervisorUserID\":3117,\"facilityIDs\":[501,502]}"); + + assertSuccessContaining(response, "Deleted successfully"); + verify(ashaSupervisorMappingService).deleteBySupervisorAndFacilities(3117, List.of(501, 502), "Admin"); + } + + @Test + @DisplayName("deleteAshaSupervisorMapping should refuse a request that names no supervisor") + void deleteAshaSupervisorMapping_shouldRefuseRequestWithoutSupervisor() { + String response = controller.deleteAshaSupervisorMapping("{\"facilityIDs\":[501]}"); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response)); + assertTrue(response.contains("supervisorUserID and facilityIDs are required"), response); + verify(ashaSupervisorMappingService, never()).deleteBySupervisorAndFacilities(anyInt(), anyList(), anyString()); + } + + @Test + @DisplayName("deleteAshaSupervisorMapping should refuse a request that names no facilities") + void deleteAshaSupervisorMapping_shouldRefuseRequestWithoutFacilities() { + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.deleteAshaSupervisorMapping("{\"supervisorUserID\":3117}"))); + } + + @Test + @DisplayName("deleteAshaSupervisorMapping should answer an error envelope when the retirement fails") + void deleteAshaSupervisorMapping_shouldAnswerErrorEnvelopeOnFailure() { + org.mockito.Mockito.doThrow(new IllegalStateException("no connection")) + .when(ashaSupervisorMappingService) + .deleteBySupervisorAndFacilities(anyInt(), anyList(), anyString()); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller + .deleteAshaSupervisorMapping("{\"supervisorUserID\":3117,\"facilityIDs\":[501]}"))); + } + + @Test + @DisplayName("updateAshaSupervisorMappingAtomically should replace the old mappings in one step") + void updateAtomically_shouldReplaceMappingsInOneStep() { + when(ashaSupervisorMappingService.updateAshaMappingsAtomically(anyInt(), anyList(), anyList(), anyString())) + .thenReturn(new ArrayList<>(List.of(mapping(1L, 3117, 4001)))); + + String response = controller.updateAshaSupervisorMappingAtomically("{\"supervisorUserID\":3117," + + "\"modifiedBy\":\"admin\",\"facilityIDs\":[501,502]," + + "\"newMappings\":[{\"supervisorUserID\":3117,\"ashaUserID\":4001,\"facilityID\":501}]}"); + + assertSuccessContaining(response, "3117"); + verify(ashaSupervisorMappingService).updateAshaMappingsAtomically( + org.mockito.ArgumentMatchers.eq(3117), org.mockito.ArgumentMatchers.eq(List.of(501, 502)), + anyList(), org.mockito.ArgumentMatchers.eq("admin")); + } + + @Test + @DisplayName("updateAshaSupervisorMappingAtomically should attribute the change to Admin when none is named") + void updateAtomically_shouldAttributeToAdminByDefault() { + when(ashaSupervisorMappingService.updateAshaMappingsAtomically(anyInt(), anyList(), anyList(), anyString())) + .thenReturn(new ArrayList<>()); + + controller.updateAshaSupervisorMappingAtomically("{\"supervisorUserID\":3117,\"facilityIDs\":[501]}"); + + verify(ashaSupervisorMappingService).updateAshaMappingsAtomically( + org.mockito.ArgumentMatchers.eq(3117), anyList(), anyList(), + org.mockito.ArgumentMatchers.eq("Admin")); + } + + @Test + @DisplayName("updateAshaSupervisorMappingAtomically should answer an error envelope without a supervisor") + void updateAtomically_shouldAnswerErrorEnvelopeWithoutSupervisor() { + assertEquals(OutputResponse.CODE_EXCEPTION, + statusCodeOf(controller.updateAshaSupervisorMappingAtomically("{\"facilityIDs\":[501]}"))); + } + + @Test + @DisplayName("restoreAshaSupervisorMapping should reinstate the mappings the caller names") + void restoreAshaSupervisorMapping_shouldReinstateNamedMappings() { + String response = controller.restoreAshaSupervisorMapping("{\"ids\":[1,2]}"); + + assertSuccessContaining(response, "Restored successfully"); + verify(ashaSupervisorMappingService).restoreMappings(List.of(1L, 2L), "Admin"); + } + + @Test + @DisplayName("restoreAshaSupervisorMapping should refuse a request that names no mappings") + void restoreAshaSupervisorMapping_shouldRefuseRequestWithoutIds() { + String response = controller.restoreAshaSupervisorMapping("{}"); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response)); + assertTrue(response.contains("ids are required"), response); + } + + @Test + @DisplayName("restoreAshaSupervisorMapping should answer an error envelope when the reinstatement fails") + void restoreAshaSupervisorMapping_shouldAnswerErrorEnvelopeOnFailure() { + org.mockito.Mockito.doThrow(new IllegalStateException("no connection")) + .when(ashaSupervisorMappingService).restoreMappings(anyList(), anyString()); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.restoreAshaSupervisorMapping("{\"ids\":[1]}"))); + } + + @Test + @DisplayName("getFacilityByMappingID should answer the facility the mapping points at") + void getFacilityByMappingID_shouldAnswerMappedFacility() { + M_UserServiceRoleMapping2 mapping = new M_UserServiceRoleMapping2(); + mapping.setuSRMappingID(9001); + mapping.setFacilityID(501); + M_Facility facility = new M_Facility(); + facility.setFacilityID(501); + facility.setFacilityName("PHC North"); + facility.setFacilityTypeID(3); + facility.setRuralUrban("Rural"); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(mapping)); + when(mainStoreRepo.findByFacilityIDAndDeleted(501, false)).thenReturn(facility); + + String response = controller.getFacilityByMappingID("{\"uSRMappingID\":9001}"); + + assertSuccessContaining(response, "PHC North"); + assertTrue(response.contains("Rural"), response); + } + + @Test + @DisplayName("getFacilityByMappingID should report a facility that has since been deleted") + void getFacilityByMappingID_shouldReportDeletedFacility() { + M_UserServiceRoleMapping2 mapping = new M_UserServiceRoleMapping2(); + mapping.setuSRMappingID(9001); + mapping.setFacilityID(501); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(mapping)); + when(mainStoreRepo.findByFacilityIDAndDeleted(501, false)).thenReturn(null); + + assertSuccessContaining(controller.getFacilityByMappingID("{\"uSRMappingID\":9001}"), "facilityDeleted"); + } + + @Test + @DisplayName("getFacilityByMappingID should answer no facility for a mapping that was retired") + void getFacilityByMappingID_shouldAnswerNoFacilityForRetiredMapping() { + M_UserServiceRoleMapping2 mapping = new M_UserServiceRoleMapping2(); + mapping.setuSRMappingID(9001); + mapping.setFacilityID(501); + mapping.setDeleted(Boolean.TRUE); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(mapping)); + + assertSuccessContaining(controller.getFacilityByMappingID("{\"uSRMappingID\":9001}"), "\"data\":{}"); + verify(mainStoreRepo, never()).findByFacilityIDAndDeleted(anyInt(), org.mockito.ArgumentMatchers.anyBoolean()); + } + + @Test + @DisplayName("getFacilityByMappingID should answer no facility for a mapping that names none") + void getFacilityByMappingID_shouldAnswerNoFacilityWhenMappingNamesNone() { + M_UserServiceRoleMapping2 mapping = new M_UserServiceRoleMapping2(); + mapping.setuSRMappingID(9001); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(mapping)); + + assertSuccessContaining(controller.getFacilityByMappingID("{\"uSRMappingID\":9001}"), "\"data\":{}"); + } + + @Test + @DisplayName("getFacilityByMappingID should answer no facility for a mapping that does not exist") + void getFacilityByMappingID_shouldAnswerNoFacilityForUnknownMapping() { + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.empty()); + + assertSuccessContaining(controller.getFacilityByMappingID("{\"uSRMappingID\":9001}"), "\"data\":{}"); + } + + @Test + @DisplayName("getFacilityByMappingID should answer no facility when the request names no mapping") + void getFacilityByMappingID_shouldAnswerNoFacilityWithoutMappingId() { + assertSuccessContaining(controller.getFacilityByMappingID("{}"), "\"data\":{}"); + verify(employeeMasterRepo, never()).findById(anyInt()); + } + + @Test + @DisplayName("getFacilityByMappingID should answer an error envelope when the lookup fails") + void getFacilityByMappingID_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterRepo.findById(9001)).thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getFacilityByMappingID("{\"uSRMappingID\":9001}"))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterAgentControllerTest.java b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterAgentControllerTest.java new file mode 100644 index 0000000..f3e07c2 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterAgentControllerTest.java @@ -0,0 +1,155 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.employeemaster; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.data.employeemaster.USRAgentMapping; +import com.iemr.admin.utils.exception.IEMRException; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The agent endpoints hand out and reclaim the CTI agent ids an employee logs + * into the call centre with. + */ +@DisplayName("EmployeeMasterController agent Test Suite") +class EmployeeMasterAgentControllerTest extends EmployeeMasterFixture { + + private static final String AGENT_REQUEST = "{\"providerServiceMapID\":4001,\"cti_CampaignName\":\"104\"}"; + + private static USRAgentMapping agent(Integer id, String agentId) { + return USRAgentMapping.initializeAllUSRAgentMapping(id, 9001, null, 4001, null, agentId, + "agent-secret", "104", Boolean.TRUE); + } + + @Test + @DisplayName("getAvailableAgentIds should answer the agent ids still free on the campaign") + void getAvailableAgentIds_shouldAnswerFreeAgentIds() throws Exception { + when(usrAgentMappingService.getAvailableAgentIds(AGENT_REQUEST)).thenReturn(List.of(agent(1, "A-1"))); + + assertSuccessContaining(controller.getAvailableAgentIds(AGENT_REQUEST), "A-1"); + } + + @Test + @DisplayName("getAvailableAgentIds should answer an error envelope when the lookup fails") + void getAvailableAgentIds_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(usrAgentMappingService.getAvailableAgentIds(anyString())) + .thenThrow(new IEMRException("providerServiceMapID is required")); + + assertIemrFailure(controller.getAvailableAgentIds("{}")); + } + + @Test + @DisplayName("createUSRAgentMapping should answer the mappings the service stored") + void createUSRAgentMapping_shouldAnswerStoredMappings() throws Exception { + when(usrAgentMappingService.createUSRAgentMapping(anyString())).thenReturn(List.of(agent(1, "A-1"))); + + assertSuccessContaining( + controller.createUSRAgentMapping("[{\"agentID\":\"A-1\",\"providerServiceMapID\":4001}]"), "A-1"); + } + + @Test + @DisplayName("createUSRAgentMapping should answer an error envelope when the store fails") + void createUSRAgentMapping_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(usrAgentMappingService.createUSRAgentMapping(anyString())) + .thenThrow(new IEMRException("agentID is required")); + + assertIemrFailure(controller.createUSRAgentMapping("[{}]")); + } + + @Test + @DisplayName("getAvailableCampaigns should answer the campaigns configured for the mapping") + void getAvailableCampaigns_shouldAnswerConfiguredCampaigns() throws Exception { + when(usrAgentMappingService.getAvailableCampaigns(anyString())).thenReturn(List.of("104", "1097")); + + assertSuccessContaining(controller.getAvailableCampaigns(AGENT_REQUEST), "104"); + } + + @Test + @DisplayName("getAvailableCampaigns should answer an error envelope when the lookup fails") + void getAvailableCampaigns_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(usrAgentMappingService.getAvailableCampaigns(anyString())) + .thenThrow(new IEMRException("providerServiceMapID is required")); + + assertIemrFailure(controller.getAvailableCampaigns("{}")); + } + + @Test + @DisplayName("updateAgentIds should answer how many agent ids the service changed") + void updateAgentIds_shouldAnswerChangedCount() throws Exception { + when(usrAgentMappingService.updateAgentIds(anyString())).thenReturn(1); + + assertSuccessContaining(controller.updateAgentIds("{\"isAvailable\":false,\"usrMappingID\":9001}"), "1"); + verify(usrAgentMappingService).updateAgentIds("{\"isAvailable\":false,\"usrMappingID\":9001}"); + } + + @Test + @DisplayName("updateAgentIds should answer an error envelope when the change fails") + void updateAgentIds_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(usrAgentMappingService.updateAgentIds(anyString())) + .thenThrow(new IEMRException("usrAgentMappingID is required")); + + assertIemrFailure(controller.updateAgentIds("{}")); + } + + @Test + @DisplayName("getAllAgentIds should answer every agent id under the mapping") + void getAllAgentIds_shouldAnswerEveryAgentId() throws Exception { + when(usrAgentMappingService.getAllAgentIds(anyString())).thenReturn(List.of(agent(1, "A-1"))); + + assertSuccessContaining(controller.getAllAgentIds(AGENT_REQUEST), "A-1"); + } + + @Test + @DisplayName("getAllAgentIds should answer an error envelope when the lookup fails") + void getAllAgentIds_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(usrAgentMappingService.getAllAgentIds(anyString())) + .thenThrow(new IEMRException("providerServiceMapID is required")); + + assertIemrFailure(controller.getAllAgentIds("{}")); + } + + @Test + @DisplayName("updateCTICampaignNameMapping should answer how many mappings moved campaign") + void updateCTICampaignNameMapping_shouldAnswerChangedCount() throws Exception { + when(usrAgentMappingService.updateCTICampaignNameMapping(anyString())).thenReturn(1); + + assertSuccessContaining( + controller.updateCTICampaignNameMapping("{\"cti_CampaignName\":\"1097\",\"usrAgentMappingID\":1}"), + "1"); + } + + @Test + @DisplayName("updateCTICampaignNameMapping should answer an error envelope when the change fails") + void updateCTICampaignNameMapping_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(usrAgentMappingService.updateCTICampaignNameMapping(anyString())) + .thenThrow(new IEMRException("usrAgentMappingID is required")); + + assertIemrFailure(controller.updateCTICampaignNameMapping("{}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterFixture.java b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterFixture.java new file mode 100644 index 0000000..f446f3c --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterFixture.java @@ -0,0 +1,117 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.employeemaster; + +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletRequest; + +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.data.employeemaster.M_UserDemographics; +import com.iemr.admin.data.employeemaster.M_UserServiceRoleMapping2; +import com.iemr.admin.service.employeemaster.EmployeeMasterInter; +import com.iemr.admin.service.employeemaster.M_DesignationInter; +import com.iemr.admin.service.employeemaster.USRAgentMappingService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** Shared mocks and helpers for the suites over the employee master controller. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +abstract class EmployeeMasterFixture { + + protected static final String AUTH_HEADER = "sess-0d5f3a7c"; + + @Mock + protected M_DesignationInter m_DesignationInter; + + @Mock + protected EmployeeMasterInter employeeMasterInter; + + @Mock + protected USRAgentMappingService usrAgentMappingService; + + @InjectMocks + protected EmployeeMasterController controller; + + protected MockHttpServletRequest request; + + @BeforeEach + void prepareRequest() { + request = new MockHttpServletRequest(); + request.addHeader("Authorization", AUTH_HEADER); + } + + protected static M_User1 user(Integer id, String userName) { + M_User1 user = new M_User1(); + user.setUserID(id); + user.setUserName(userName); + return user; + } + + protected static M_UserDemographics demographics(Integer userId, String fathersName) { + M_UserDemographics demographics = new M_UserDemographics(); + demographics.setUserID(userId); + demographics.setFathersName(fathersName); + return demographics; + } + + protected static M_UserServiceRoleMapping2 roleMapping(Integer id, Integer userId) { + M_UserServiceRoleMapping2 mapping = new M_UserServiceRoleMapping2(); + mapping.setuSRMappingID(id); + mapping.setUserID(userId); + return mapping; + } + + protected static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + protected static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + protected static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + /** + * Asserts the envelope reports the failure the response builder maps an + * {@link com.iemr.admin.utils.exception.IEMRException} onto. + */ + protected static void assertIemrFailure(String response) { + assertEquals(OutputResponse.USERID_FAILURE, statusCodeOf(response), response); + } + + protected static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } +} diff --git a/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterLookupControllerTest.java b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterLookupControllerTest.java new file mode 100644 index 0000000..2676ee6 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterLookupControllerTest.java @@ -0,0 +1,555 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.employeemaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.data.employeemaster.M_Community; +import com.iemr.admin.data.employeemaster.M_Designation; +import com.iemr.admin.data.employeemaster.M_Gender; +import com.iemr.admin.data.employeemaster.M_ProviderServiceMap1; +import com.iemr.admin.data.employeemaster.M_Religion; +import com.iemr.admin.data.employeemaster.M_Role; +import com.iemr.admin.data.employeemaster.M_Title; +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.data.employeemaster.M_UserServiceRoleMapping2; +import com.iemr.admin.data.employeemaster.M_Userqualification; +import com.iemr.admin.data.employeemaster.Showofficedetails1; +import com.iemr.admin.data.employeemaster.Showuserdetailsfromuserservicerolemapping; +import com.iemr.admin.data.employeemaster.V_Showuser; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The read-only endpoints back the employee search screens and the drop-downs + * that feed the employee forms. + */ +@DisplayName("EmployeeMasterController lookup Test Suite") +class EmployeeMasterLookupControllerTest extends EmployeeMasterFixture { + + private static Showuserdetailsfromuserservicerolemapping userDetail(Integer userId, String name) { + Showuserdetailsfromuserservicerolemapping detail = new Showuserdetailsfromuserservicerolemapping(); + detail.setUserID(userId); + detail.setUserName(name); + return detail; + } + + private static V_Showuser showUser(Integer userId, String name) { + V_Showuser user = new V_Showuser(); + user.setUserID(userId); + user.setUserName(name); + return user; + } + + @Test + @DisplayName("getAllRole should answer every role on record") + void getAllRole_shouldAnswerEveryRole() { + M_Role role = new M_Role(); + role.setRoleID(11); + role.setRoleName("Provider Admin"); + when(employeeMasterInter.getAllRole()).thenReturn(new ArrayList<>(List.of(role))); + + assertSuccessContaining(controller.getAllRole("{}"), "Provider Admin"); + } + + @Test + @DisplayName("getAllRole should answer an error envelope when the lookup fails") + void getAllRole_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getAllRole()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllRole("{}")); + } + + @Test + @DisplayName("searchEmployee should answer the role mappings on record") + void searchEmployee_shouldAnswerRoleMappings() { + when(employeeMasterInter.getEmployeeDetails()) + .thenReturn(new ArrayList<>(List.of(roleMapping(9001, 3117)))); + + assertSuccessContaining(controller.searchEmployee("{}"), "9001"); + } + + @Test + @DisplayName("searchEmployee should answer an error envelope when the search fails") + void searchEmployee_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee("{}")); + } + + @Test + @DisplayName("searchEmployee1 should answer the second view of the role mappings") + void searchEmployee1_shouldAnswerRoleMappings() { + when(employeeMasterInter.getEmployeeDetails1()) + .thenReturn(new ArrayList<>(List.of(roleMapping(9001, 3117)))); + + assertSuccessContaining(controller.searchEmployee1("{}"), "9001"); + } + + @Test + @DisplayName("searchEmployee1 should answer an error envelope when the search fails") + void searchEmployee1_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails1()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee1("{}")); + } + + @Test + @DisplayName("searchEmployee2 should narrow the search to the provider and its state") + void searchEmployee2_shouldNarrowByProviderAndState() { + when(employeeMasterInter.getEmployeeDetails2(77, 29)) + .thenReturn(new ArrayList<>(List.of(userDetail(3117, "dr.mehta")))); + + assertSuccessContaining( + controller.searchEmployee2("{\"serviceProviderID\":77,\"pSMStateID\":29}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee2 should answer an error envelope when the search fails") + void searchEmployee2_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails2(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee2("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchEmployee3 should narrow the search to the provider and role") + void searchEmployee3_shouldNarrowByProviderAndRole() { + when(employeeMasterInter.getEmployeeDetails3(77, 11)) + .thenReturn(new ArrayList<>(List.of(userDetail(3117, "dr.mehta")))); + + assertSuccessContaining( + controller.searchEmployee3("{\"serviceProviderID\":77,\"roleID\":11}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee3 should answer an error envelope when the search fails") + void searchEmployee3_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails3(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee3("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchEmployee4 should narrow the search to the provider") + void searchEmployee4_shouldNarrowByProvider() { + when(employeeMasterInter.getEmployeeDetails4(77)) + .thenReturn(new ArrayList<>(List.of(showUser(3117, "dr.mehta")))); + + assertSuccessContaining(controller.searchEmployee4("{\"serviceProviderID\":77}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee4 should answer an error envelope when the search fails") + void searchEmployee4_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails4(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee4("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchEmployee5 should answer every user on record") + void searchEmployee5_shouldAnswerEveryUser() { + when(employeeMasterInter.getEmployeeDetails5()) + .thenReturn(new ArrayList<>(List.of(showUser(3117, "dr.mehta")))); + + assertSuccessContaining(controller.searchEmployee5("{}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee5 should answer an error envelope when the search fails") + void searchEmployee5_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails5()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee5("{}")); + } + + @Test + @DisplayName("searchEmployee6 should narrow the search to one user under the provider") + void searchEmployee6_shouldNarrowByProviderAndUser() { + when(employeeMasterInter.getEmployeeDetails6(77, 3117)) + .thenReturn(new ArrayList<>(List.of(userDetail(3117, "dr.mehta")))); + + assertSuccessContaining( + controller.searchEmployee6("{\"serviceProviderID\":77,\"userID\":3117}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee6 should answer an error envelope when the search fails") + void searchEmployee6_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails6(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee6("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchEmployee7 should narrow the search down to the working district") + void searchEmployee7_shouldNarrowByDistrict() { + when(employeeMasterInter.getEmployeeDetails7(77, 29, 301)) + .thenReturn(new ArrayList<>(List.of(userDetail(3117, "dr.mehta")))); + + assertSuccessContaining(controller.searchEmployee7( + "{\"serviceProviderID\":77,\"pSMStateID\":29,\"workingDistrictID\":301}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee7 should answer an error envelope when the search fails") + void searchEmployee7_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails7(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee7("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchEmployee8 should narrow the search down to the working location") + void searchEmployee8_shouldNarrowByLocation() { + when(employeeMasterInter.getEmployeeDetails8(77, 29, 301, 401)) + .thenReturn(new ArrayList<>(List.of(userDetail(3117, "dr.mehta")))); + + assertSuccessContaining(controller.searchEmployee8("{\"serviceProviderID\":77,\"pSMStateID\":29," + + "\"workingDistrictID\":301,\"workingLocationID\":401}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee8 should answer an error envelope when the search fails") + void searchEmployee8_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails8(any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee8("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchEmployee9 should narrow the search to the role within the state") + void searchEmployee9_shouldNarrowByStateAndRole() { + when(employeeMasterInter.getEmployeeDetails9(77, 29, 11)) + .thenReturn(new ArrayList<>(List.of(userDetail(3117, "dr.mehta")))); + + assertSuccessContaining(controller.searchEmployee9( + "{\"serviceProviderID\":77,\"pSMStateID\":29,\"roleID\":11}"), "dr.mehta"); + } + + @Test + @DisplayName("searchEmployee9 should answer an error envelope when the search fails") + void searchEmployee9_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails9(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee9("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchEmployee10 should pass every filter the screen sends through") + void searchEmployee10_shouldPassEveryFilterThrough() { + when(employeeMasterInter.getEmployeeDetails11(77, 29, 3, 11, "dr.mehta", 3117)) + .thenReturn(new ArrayList<>(List.of(userDetail(3117, "dr.mehta")))); + + assertSuccessContaining(controller.searchEmployee10("{\"serviceProviderID\":77,\"pSMStateID\":29," + + "\"serviceID\":3,\"roleID\":11,\"userName\":\"dr.mehta\",\"userID\":3117}"), "dr.mehta"); + verify(employeeMasterInter).getEmployeeDetails11(77, 29, 3, 11, "dr.mehta", 3117); + } + + @Test + @DisplayName("searchEmployee10 should answer an error envelope when the search fails") + void searchEmployee10_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeDetails11(any(), any(), any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchEmployee10("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getAgentID should answer a success envelope without reaching a service") + void getAgentID_shouldAnswerSuccessEnvelope() { + assertGenericFailure(controller.getAgentID("{}")); + } + + @Test + @DisplayName("getAllTitle should answer every title on record") + void getAllTitle_shouldAnswerEveryTitle() { + M_Title title = new M_Title(); + title.setTitleID(1); + title.setTitleName("Dr"); + when(employeeMasterInter.getAllTitle()).thenReturn(new ArrayList<>(List.of(title))); + + assertSuccessContaining(controller.getAllTitle("{}"), "Dr"); + } + + @Test + @DisplayName("getAllTitle should answer an error envelope when the lookup fails") + void getAllTitle_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getAllTitle()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllTitle("{}")); + } + + @Test + @DisplayName("getAllGender should answer every gender on record") + void getAllGender_shouldAnswerEveryGender() { + M_Gender gender = new M_Gender(); + gender.setGenderID(1); + gender.setGenderName("Female"); + when(employeeMasterInter.getAllGender()).thenReturn(new ArrayList<>(List.of(gender))); + + assertSuccessContaining(controller.getAllGender("{}"), "Female"); + } + + @Test + @DisplayName("getAllGender should answer an error envelope when the lookup fails") + void getAllGender_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getAllGender()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllGender("{}")); + } + + @Test + @DisplayName("getAlllocation should resolve the mapping before reading the office details") + void getAlllocation_shouldResolveMappingFirst() { + M_ProviderServiceMap1 mapping = new M_ProviderServiceMap1(); + mapping.setProviderServiceMapID(4001); + Showofficedetails1 office = new Showofficedetails1(); + office.setLocationName("Bengaluru Office"); + when(employeeMasterInter.getAllByMapId2(77, 29, 3)).thenReturn(new ArrayList<>(List.of(mapping))); + when(employeeMasterInter.getlocationByMapid2(4001, 301)).thenReturn(new ArrayList<>(List.of(office))); + + assertSuccessContaining(controller.getAlllocation("{\"serviceProviderID\":77,\"stateID\":29," + + "\"serviceID\":3,\"districtID\":301}"), "Bengaluru Office"); + } + + @Test + @DisplayName("getAlllocation should fall back to no mapping when the provider has none") + void getAlllocation_shouldFallBackWithoutMapping() { + when(employeeMasterInter.getAllByMapId2(any(), any(), any())).thenReturn(new ArrayList<>()); + when(employeeMasterInter.getlocationByMapid2(0, 301)).thenReturn(new ArrayList<>()); + + assertSuccessContaining(controller.getAlllocation("{\"serviceProviderID\":77,\"districtID\":301}"), + "statusCode"); + verify(employeeMasterInter).getlocationByMapid2(0, 301); + } + + @Test + @DisplayName("getAlllocation should answer an error envelope when the lookup fails") + void getAlllocation_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getAllByMapId2(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAlllocation("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("FindEmployeeName should answer whether the user name is taken") + void findEmployeeName_shouldAnswerWhetherNameIsTaken() { + when(employeeMasterInter.FindEmployeeName("dr.mehta")).thenReturn("dr.mehta"); + + assertSuccessContaining(controller.FindEmployeeName("{\"userName\":\"dr.mehta\"}"), "dr.mehta"); + } + + @Test + @DisplayName("FindEmployeeName should answer an error envelope when the check fails") + void findEmployeeName_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.FindEmployeeName(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.FindEmployeeName("{\"userName\":\"dr.mehta\"}")); + } + + @Test + @DisplayName("Qualification should answer every qualification on record") + void qualification_shouldAnswerEveryQualification() { + M_Userqualification qualification = new M_Userqualification(); + qualification.setQualificationID(5); + qualification.setName("MBBS"); + when(employeeMasterInter.getQualification()).thenReturn(new ArrayList<>(List.of(qualification))); + + assertSuccessContaining(controller.Qualification("{}"), "MBBS"); + } + + @Test + @DisplayName("Qualification should answer an error envelope when the lookup fails") + void qualification_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getQualification()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.Qualification("{}")); + } + + @Test + @DisplayName("checkingEmpDetails should report that the identifiers are already in use") + void checkingEmpDetails_shouldReportIdentifiersInUse() { + when(employeeMasterInter.checkingEmpDetails("dr.mehta", "111122223333", "ABCDE1234F", "EMP-1", "HP-1")) + .thenReturn(Boolean.TRUE); + + assertSuccessContaining(controller.checkingEmpDetails("{\"userName\":\"dr.mehta\"," + + "\"aadhaarNo\":\"111122223333\",\"pAN\":\"ABCDE1234F\",\"employeeID\":\"EMP-1\"," + + "\"healthProfessionalID\":\"HP-1\"}"), "true"); + } + + @Test + @DisplayName("checkingEmpDetails should answer an error envelope when the check fails") + void checkingEmpDetails_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.checkingEmpDetails(any(), any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkingEmpDetails("{\"userName\":\"dr.mehta\"}")); + } + + @Test + @DisplayName("getDesignation should answer every designation on record") + void getDesignation_shouldAnswerEveryDesignation() { + M_Designation designation = new M_Designation(); + designation.setDesignationID(7); + designation.setDesignationName("ASHA"); + when(m_DesignationInter.getDesinationlist()).thenReturn(new ArrayList<>(List.of(designation))); + + assertSuccessContaining(controller.getDesignation("{}"), "ASHA"); + } + + @Test + @DisplayName("getDesignation should answer an error envelope when the lookup fails") + void getDesignation_shouldAnswerErrorEnvelopeOnFailure() { + when(m_DesignationInter.getDesinationlist()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getDesignation("{}")); + } + + @Test + @DisplayName("getEmployeeByDesignation should answer the employees holding the designation") + void getEmployeeByDesignation_shouldAnswerMatchingEmployees() { + when(employeeMasterInter.getEmployeeByDesiganationID(7, 77)) + .thenReturn(new ArrayList<>(List.of(user(3117, "dr.mehta")))); + + assertSuccessContaining( + controller.getEmployeeByDesignation("{\"designationID\":7,\"serviceProviderID\":77}"), "dr.mehta"); + } + + @Test + @DisplayName("getEmployeeByDesignation should answer an error envelope when the lookup fails") + void getEmployeeByDesignation_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getEmployeeByDesiganationID(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getEmployeeByDesignation("{\"designationID\":7}")); + } + + @Test + @DisplayName("completeUserDetails should answer the full user view") + void completeUserDetails_shouldAnswerFullUserView() { + when(employeeMasterInter.getcompleteUserDetails()) + .thenReturn(new ArrayList<>(List.of(showUser(3117, "dr.mehta")))); + + assertSuccessContaining(controller.completeUserDetails("{}"), "dr.mehta"); + } + + @Test + @DisplayName("completeUserDetails should answer an error envelope when the lookup fails") + void completeUserDetails_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getcompleteUserDetails()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.completeUserDetails("{}")); + } + + @Test + @DisplayName("getReligion should answer every religion on record") + void getReligion_shouldAnswerEveryReligion() { + M_Religion religion = new M_Religion(); + religion.setReligionID(1); + religion.setReligionType("Hindu"); + when(employeeMasterInter.getAllReligion()).thenReturn(new ArrayList<>(List.of(religion))); + + assertSuccessContaining(controller.getReligion("{}"), "Hindu"); + } + + @Test + @DisplayName("getReligion should answer an error envelope when the lookup fails") + void getReligion_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getAllReligion()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getReligion("{}")); + } + + @Test + @DisplayName("getCommunity should answer every community on record") + void getCommunity_shouldAnswerEveryCommunity() { + M_Community community = new M_Community(); + community.setCommunityID(1); + community.setCommunityType("General"); + when(employeeMasterInter.getAllCommunity()).thenReturn(new ArrayList<>(List.of(community))); + + assertSuccessContaining(controller.getCommunity("{}"), "General"); + } + + @Test + @DisplayName("getCommunity should answer an error envelope when the lookup fails") + void getCommunity_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getAllCommunity()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getCommunity("{}")); + } + + @Test + @DisplayName("FindEmployeeDetailsByUserName should answer the user the service resolves") + void findEmployeeDetailsByUserName_shouldAnswerResolvedUser() { + when(employeeMasterInter.FindEmployeeName1("dr.mehta")).thenReturn(user(3117, "dr.mehta")); + + assertSuccessContaining( + controller.FindEmployeeDetailsByUserName("{\"userName\":\"dr.mehta\"}"), "dr.mehta"); + } + + @Test + @DisplayName("FindEmployeeDetailsByUserName should answer an error envelope for an unknown user") + void findEmployeeDetailsByUserName_shouldAnswerErrorEnvelopeForUnknownUser() { + when(employeeMasterInter.FindEmployeeName1("dr.mehta")).thenReturn(null); + + assertCodeException(controller.FindEmployeeDetailsByUserName("{\"userName\":\"dr.mehta\"}")); + } + + @Test + @DisplayName("searchEmployee should answer a plain envelope for an empty result") + void searchEmployee_shouldAnswerPlainEnvelopeForEmptyResult() { + when(employeeMasterInter.getEmployeeDetails()).thenReturn(new ArrayList()); + + assertSuccessContaining(controller.searchEmployee("{}"), "\"statusCode\":200"); + } + + @Test + @DisplayName("getEmployeeByDesignation should read the provider from the user carrier, not the designation") + void getEmployeeByDesignation_shouldReadProviderFromUserCarrier() { + ArrayList employees = new ArrayList<>(List.of(user(3117, "dr.mehta"))); + when(employeeMasterInter.getEmployeeByDesiganationID(7, 77)).thenReturn(employees); + + controller.getEmployeeByDesignation("{\"designationID\":7,\"serviceProviderID\":77}"); + + verify(employeeMasterInter).getEmployeeByDesiganationID(7, 77); + } +} diff --git a/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterWriteControllerTest.java b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterWriteControllerTest.java new file mode 100644 index 0000000..ccdb99e --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeMasterWriteControllerTest.java @@ -0,0 +1,760 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.employeemaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.data.employeemaster.M_UserDemographics; +import com.iemr.admin.data.employeemaster.M_UserLangMapping; +import com.iemr.admin.data.employeemaster.M_UserServiceRoleMapping2; +import com.iemr.admin.data.employeemaster.V_Userservicerolemapping; +import com.iemr.admin.data.rolemaster.UserRole; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The write endpoints create and amend employees, their demographics, and the + * language and role mappings that decide what each of them may do. + */ +@DisplayName("EmployeeMasterController write Test Suite") +class EmployeeMasterWriteControllerTest extends EmployeeMasterFixture { + + private static final String ADD_EMPLOYEE_REQUEST = "{\"firstName\":\"Asha\",\"lastName\":\"Rao\"," + + "\"userName\":\"asha.rao\",\"createdBy\":\"admin\",\"titleID\":1,\"genderID\":1," + + "\"fathersName\":\"Ravi\",\"languageID\":[1,2],\"weightage\":[5,3]," + + "\"canRead\":[true,false],\"canWrite\":[true,false],\"canSpeak\":[true,true]," + + "\"previleges\":[{\"providerServiceMapID\":4001,\"workingLocationID\":401,\"roleID\":[11,12]}]}"; + + private static M_UserLangMapping langMapping(Integer id, Integer languageId) { + M_UserLangMapping mapping = new M_UserLangMapping(); + mapping.setUserLangID(id); + mapping.setLanguageID(languageId); + return mapping; + } + + @Test + @DisplayName("addEmployee should store the user, the demographics, the languages and the roles") + void addEmployee_shouldStoreEveryPartOfTheEmployee() throws Exception { + when(employeeMasterInter.saveEmployee(any())).thenReturn(3117); + when(employeeMasterInter.saveDemography(any())).thenReturn(5001); + when(employeeMasterInter.mapLanguage(anyList())).thenReturn(new ArrayList<>(List.of(langMapping(7001, 1)))); + when(employeeMasterInter.mapRole(anyList(), anyString())) + .thenReturn(new ArrayList<>(List.of(roleMapping(9001, 3117)))); + + assertSuccessContaining(controller.addEmployee(ADD_EMPLOYEE_REQUEST, request), "9001"); + + ArgumentCaptor> languages = ArgumentCaptor.forClass(List.class); + verify(employeeMasterInter).mapLanguage(languages.capture()); + assertEquals(2, languages.getValue().size(), "each language in the request must be mapped"); + assertEquals(3117, languages.getValue().get(0).getUserID()); + + ArgumentCaptor> roles = ArgumentCaptor.forClass(List.class); + verify(employeeMasterInter).mapRole(roles.capture(), anyString()); + assertEquals(4001, roles.getValue().get(0).getProviderServiceMapID()); + } + + @Test + @DisplayName("addEmployee should pass the caller's authorization on to the role mapping") + void addEmployee_shouldPassAuthorizationOn() throws Exception { + when(employeeMasterInter.saveEmployee(any())).thenReturn(3117); + when(employeeMasterInter.mapLanguage(anyList())).thenReturn(new ArrayList<>()); + when(employeeMasterInter.mapRole(anyList(), anyString())).thenReturn(new ArrayList<>()); + + controller.addEmployee(ADD_EMPLOYEE_REQUEST, request); + + verify(employeeMasterInter).mapRole(anyList(), org.mockito.ArgumentMatchers.eq(AUTH_HEADER)); + } + + @Test + @DisplayName("addEmployee should answer an error envelope for a request that names no languages") + void addEmployee_shouldAnswerErrorEnvelopeWithoutLanguages() { + when(employeeMasterInter.saveEmployee(any())).thenReturn(3117); + + assertCodeException(controller.addEmployee("{\"firstName\":\"Asha\"}", request)); + } + + @Test + @DisplayName("addEmployee should answer an error envelope when the user cannot be stored") + void addEmployee_shouldAnswerErrorEnvelopeOnStoreFailure() { + when(employeeMasterInter.saveEmployee(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.addEmployee(ADD_EMPLOYEE_REQUEST, request)); + } + + @Test + @DisplayName("editEmployee should copy the edits onto both the user and the demographics") + void editEmployee_shouldCopyEditsOntoUserAndDemographics() { + M_User1 stored = user(3117, "old.name"); + M_UserDemographics storedDemographics = demographics(3117, "old father"); + when(employeeMasterInter.editEmployee(3117)).thenReturn(stored); + when(employeeMasterInter.saveEditData(stored)).thenReturn(stored); + when(employeeMasterInter.mdedit(3117)).thenReturn(storedDemographics); + when(employeeMasterInter.saveeditDemo(storedDemographics)).thenReturn(5001); + + String response = controller.editEmployee("{\"userID\":3117,\"firstName\":\"Asha\"," + + "\"userName\":\"asha.rao\",\"fathersName\":\"Ravi\",\"pinCode\":\"560001\"}"); + + assertSuccessContaining(response, "5001"); + assertEquals("Asha", stored.getFirstName()); + assertEquals("Ravi", storedDemographics.getFathersName()); + assertEquals("560001", storedDemographics.getPinCode()); + } + + @Test + @DisplayName("editEmployee should answer an error envelope for a user that does not exist") + void editEmployee_shouldAnswerErrorEnvelopeForUnknownUser() { + when(employeeMasterInter.editEmployee(3117)).thenReturn(null); + + assertCodeException(controller.editEmployee("{\"userID\":3117}")); + } + + @Test + @DisplayName("deleteEmployee should mark the employee deleted and answer what was saved") + void deleteEmployee_shouldMarkEmployeeDeleted() { + M_User1 stored = user(3117, "asha.rao"); + when(employeeMasterInter.editEmployee(3117)).thenReturn(stored); + when(employeeMasterInter.saveEditData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteEmployee("{\"userID\":3117}"), "asha.rao"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteEmployee should answer an error envelope for a user that does not exist") + void deleteEmployee_shouldAnswerErrorEnvelopeForUnknownUser() { + when(employeeMasterInter.editEmployee(3117)).thenReturn(null); + + assertCodeException(controller.deleteEmployee("{\"userID\":3117}")); + } + + @Test + @DisplayName("updateEmployee should build one role mapping per role the request names") + void updateEmployee_shouldBuildOneMappingPerRole() { + when(employeeMasterInter.mapRoleUpdation(anyList())) + .thenReturn(new ArrayList<>(List.of(roleMapping(9001, 3117)))); + + String response = controller.updateEmployee("{\"userID\":3117,\"roleID1\":[11,12]," + + "\"providerServiceMapID\":4001,\"workingLocationID\":401,\"createdBy\":\"admin\"}"); + + assertSuccessContaining(response, "9001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(employeeMasterInter).mapRoleUpdation(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals(11, captor.getValue().get(0).getRoleID()); + assertEquals(4001, captor.getValue().get(0).getProviderServiceMapID()); + } + + @Test + @DisplayName("updateEmployee should answer an error envelope when the request names no roles") + void updateEmployee_shouldAnswerErrorEnvelopeWithoutRoles() { + assertCodeException(controller.updateEmployee("{\"userID\":3117}")); + } + + @Test + @DisplayName("deleteEmployeeRole should mark the role mapping deleted") + void deleteEmployeeRole_shouldMarkRoleMappingDeleted() { + M_UserServiceRoleMapping2 stored = roleMapping(9001, 3117); + when(employeeMasterInter.uRoledelte(9001)).thenReturn(stored); + when(employeeMasterInter.saveRoleEdit(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteEmployeeRole("{\"uSRMappingID\":9001}"), "9001"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteEmployeeRole should answer an error envelope for a mapping that does not exist") + void deleteEmployeeRole_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(employeeMasterInter.uRoledelte(9001)).thenReturn(null); + + assertCodeException(controller.deleteEmployeeRole("{\"uSRMappingID\":9001}")); + } + + @Test + @DisplayName("usrRoleAndCtiMapping should answer the summary the service reports") + void usrRoleAndCtiMapping_shouldAnswerSummary() throws Exception { + when(employeeMasterInter.mapctiAgent(anyList())).thenReturn("2 agents mapped"); + + assertSuccessContaining(controller.usrRoleAndCtiMapping("[{\"uSRMappingID\":9001,\"agentID\":\"A-1\"}]"), + "2 agents mapped"); + } + + @Test + @DisplayName("usrRoleAndCtiMapping should answer an error envelope when the mapping fails") + void usrRoleAndCtiMapping_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(employeeMasterInter.mapctiAgent(anyList())).thenThrow(new IllegalStateException("cti is unreachable")); + + assertGenericFailure(controller.usrRoleAndCtiMapping("[{\"uSRMappingID\":9001}]")); + } + + @Test + @DisplayName("ResetUserPassword should answer the outcome the service reports") + void resetUserPassword_shouldAnswerOutcome() { + when(employeeMasterInter.ResetPassword(any())).thenReturn("password reset"); + + assertSuccessContaining(controller.ResetUserPassword("{\"userID\":3117}"), "password reset"); + } + + @Test + @DisplayName("ResetUserPassword should answer an error envelope when the reset fails") + void resetUserPassword_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.ResetPassword(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.ResetUserPassword("{\"userID\":3117}")); + } + + @Test + @DisplayName("createProviderAdmin should answer the admins the service stored") + void createProviderAdmin_shouldAnswerStoredAdmins() throws Exception { + when(employeeMasterInter.createProviderAdmin(anyList())) + .thenReturn(new ArrayList<>(List.of(user(3117, "pa.user")))); + + assertSuccessContaining(controller.createProviderAdmin("[{\"userName\":\"pa.user\"}]"), "pa.user"); + } + + @Test + @DisplayName("createProviderAdmin should answer an error envelope when the store fails") + void createProviderAdmin_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(employeeMasterInter.createProviderAdmin(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createProviderAdmin("[{\"userName\":\"pa.user\"}]")); + } + + @Test + @DisplayName("getProviderAdmin should answer every provider admin on record") + void getProviderAdmin_shouldAnswerEveryAdmin() { + when(employeeMasterInter.getProviderAdmin()).thenReturn(new ArrayList<>(List.of(user(3117, "pa.user")))); + + assertSuccessContaining(controller.getProviderAdmin("{}"), "pa.user"); + } + + @Test + @DisplayName("getProviderAdmin should answer an error envelope when the lookup fails") + void getProviderAdmin_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getProviderAdmin()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getProviderAdmin("{}")); + } + + @Test + @DisplayName("editProviderAdmin should copy the edited contact details onto the stored admin") + void editProviderAdmin_shouldCopyContactDetails() { + M_User1 stored = user(3117, "pa.user"); + when(employeeMasterInter.getProviderAdminForEdit(3117)).thenReturn(stored); + when(employeeMasterInter.saveeditedData(stored)).thenReturn(stored); + + String response = controller.editProviderAdmin("{\"userID\":3117,\"firstName\":\"Asha\"," + + "\"emailID\":\"asha@example.org\",\"contactNo\":\"9000000001\",\"remarks\":\"promoted\"," + + "\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "Asha"); + assertEquals("asha@example.org", stored.getEmailID()); + assertEquals("promoted", stored.getRemarks()); + } + + @Test + @DisplayName("editProviderAdmin should answer an error envelope for an admin that does not exist") + void editProviderAdmin_shouldAnswerErrorEnvelopeForUnknownAdmin() { + when(employeeMasterInter.getProviderAdminForEdit(3117)).thenReturn(null); + + assertCodeException(controller.editProviderAdmin("{\"userID\":3117}")); + } + + @Test + @DisplayName("deleteProviderAdmin should mark the admin deleted") + void deleteProviderAdmin_shouldMarkAdminDeleted() { + M_User1 stored = user(3117, "pa.user"); + when(employeeMasterInter.getProviderAdminForEdit(3117)).thenReturn(stored); + when(employeeMasterInter.saveeditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteProviderAdmin("{\"userID\":3117,\"deleted\":true}"), "pa.user"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteProviderAdmin should answer an error envelope for an admin that does not exist") + void deleteProviderAdmin_shouldAnswerErrorEnvelopeForUnknownAdmin() { + when(employeeMasterInter.getProviderAdminForEdit(3117)).thenReturn(null); + + assertCodeException(controller.deleteProviderAdmin("{\"userID\":3117,\"deleted\":true}")); + } + + @Test + @DisplayName("createNewUser should store the demographics and register the user with the call centre") + void createNewUser_shouldStoreDemographicsAndRegisterUser() throws Exception { + M_User1 created = user(3117, "asha.rao"); + created.setCreatedBy("admin"); + when(employeeMasterInter.createNewUser(anyList())).thenReturn(new ArrayList<>(List.of(created))); + when(employeeMasterInter.SaveDemographics(any())).thenReturn(new ArrayList<>()); + + String response = controller.createNewUser( + "[{\"userName\":\"asha.rao\",\"createdBy\":\"admin\",\"fathersName\":\"Ravi\"}]", request); + + assertSuccessContaining(response, "asha.rao"); + verify(employeeMasterInter).createUserInCallCentre(created, AUTH_HEADER); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(employeeMasterInter).SaveDemographics(captor.capture()); + assertEquals("Ravi", captor.getValue().get(0).getFathersName()); + assertEquals(3117, captor.getValue().get(0).getUserID()); + } + + @Test + @DisplayName("createNewUser should answer an error envelope when the store fails") + void createNewUser_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(employeeMasterInter.createNewUser(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createNewUser("[{\"userName\":\"asha.rao\"}]", request)); + } + + @Test + @DisplayName("editUserDetails should copy the edits onto both the user and the demographics") + void editUserDetails_shouldCopyEditsOntoBoth() { + M_User1 stored = user(3117, "asha.rao"); + M_UserDemographics storedDemographics = demographics(3117, "old father"); + when(employeeMasterInter.editData(3117)).thenReturn(stored); + when(employeeMasterInter.saveeditedData(stored)).thenReturn(stored); + when(employeeMasterInter.DataByUserID(3117)).thenReturn(storedDemographics); + when(employeeMasterInter.saveeditedDemoData(storedDemographics)).thenReturn(storedDemographics); + + String response = controller.editUserDetails("{\"userID\":3117,\"firstName\":\"Asha\"," + + "\"emailID\":\"asha@example.org\",\"fathersName\":\"Ravi\",\"permPinCode\":\"560002\"," + + "\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "Ravi"); + assertEquals("Asha", stored.getFirstName()); + assertEquals(560002, storedDemographics.getPermPinCode()); + } + + @Test + @DisplayName("editUserDetails should answer an empty envelope when the user has no demographics on record") + void editUserDetails_shouldAnswerEmptyEnvelopeWithoutDemographics() { + M_User1 stored = user(3117, "asha.rao"); + when(employeeMasterInter.editData(3117)).thenReturn(stored); + when(employeeMasterInter.saveeditedData(stored)).thenReturn(stored); + when(employeeMasterInter.DataByUserID(3117)).thenReturn(null); + + assertGenericFailure(controller.editUserDetails("{\"userID\":3117,\"firstName\":\"Asha\"}")); + verify(employeeMasterInter, never()).saveeditedDemoData(any()); + } + + @Test + @DisplayName("editUserDetails should answer an error envelope for a user that does not exist") + void editUserDetails_shouldAnswerErrorEnvelopeForUnknownUser() { + when(employeeMasterInter.editData(3117)).thenReturn(null); + + assertCodeException(controller.editUserDetails("{\"userID\":3117}")); + } + + @Test + @DisplayName("deletedUserDetails should release the agent id and expire the session for a deleted user") + void deletedUserDetails_shouldReleaseAgentIdAndExpireSession() { + M_User1 stored = user(3117, "asha.rao"); + stored.setAgentID("A-1"); + M_UserDemographics storedDemographics = demographics(3117, "Ravi"); + when(employeeMasterInter.editData(3117)).thenReturn(stored); + when(employeeMasterInter.saveeditedData(stored)).thenReturn(stored); + when(employeeMasterInter.DataByUserID(3117)).thenReturn(storedDemographics); + when(employeeMasterInter.saveeditedDemoData(storedDemographics)).thenReturn(storedDemographics); + + assertSuccessContaining( + controller.deletedUserDetails("{\"userID\":3117,\"deleted\":true}", request), "Ravi"); + verify(usrAgentMappingService).updateDeletedAgentIDStatus("A-1"); + verify(employeeMasterInter).expireAuth(stored, AUTH_HEADER); + } + + @Test + @DisplayName("deletedUserDetails should leave the agent id alone when the user is only reinstated") + void deletedUserDetails_shouldLeaveAgentIdAloneWhenReinstating() { + M_User1 stored = user(3117, "asha.rao"); + M_UserDemographics storedDemographics = demographics(3117, "Ravi"); + when(employeeMasterInter.editData(3117)).thenReturn(stored); + when(employeeMasterInter.saveeditedData(stored)).thenReturn(stored); + when(employeeMasterInter.DataByUserID(3117)).thenReturn(storedDemographics); + when(employeeMasterInter.saveeditedDemoData(storedDemographics)).thenReturn(storedDemographics); + + controller.deletedUserDetails("{\"userID\":3117,\"deleted\":false}", request); + + verify(usrAgentMappingService, never()).updateDeletedAgentIDStatus(anyString()); + } + + @Test + @DisplayName("deletedUserDetails should answer an error envelope for a user that does not exist") + void deletedUserDetails_shouldAnswerErrorEnvelopeForUnknownUser() { + when(employeeMasterInter.editData(3117)).thenReturn(null); + + assertCodeException(controller.deletedUserDetails("{\"userID\":3117,\"deleted\":true}", request)); + } + + @Test + @DisplayName("searchMappedLanguageByUserId should answer the languages mapped to the user") + void searchMappedLanguageByUserId_shouldAnswerMappedLanguages() { + when(employeeMasterInter.searchMappedLangugeByUserId(3117)) + .thenReturn(new ArrayList<>(List.of(langMapping(7001, 1)))); + + assertSuccessContaining(controller.searchMappedLanguageByUserId("{\"userID\":3117}"), "7001"); + } + + @Test + @DisplayName("searchMappedLanguageByUserId should answer an error envelope when the lookup fails") + void searchMappedLanguageByUserId_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.searchMappedLangugeByUserId(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchMappedLanguageByUserId("{\"userID\":3117}")); + } + + @Test + @DisplayName("getUserMappedLanguage should answer the languages mapped under the provider") + void getUserMappedLanguage_shouldAnswerProviderLanguages() { + when(employeeMasterInter.getMappedLanguge(77)) + .thenReturn(new ArrayList<>(List.of(langMapping(7001, 1)))); + + assertSuccessContaining(controller.getUserMappedLanguage("{\"serviceProviderID\":77}"), "7001"); + } + + @Test + @DisplayName("getUserMappedLanguage should answer an error envelope when the lookup fails") + void getUserMappedLanguage_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getMappedLanguge(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getUserMappedLanguage("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("UserLangMapping should build one mapping per language in the request") + void userLangMapping_shouldBuildOneMappingPerLanguage() { + when(employeeMasterInter.mapLanguage(anyList())) + .thenReturn(new ArrayList<>(List.of(langMapping(7001, 1)))); + + String response = controller.UserLangMapping("[{\"userID\":3117,\"createdBy\":\"admin\"," + + "\"serviceProviderID\":77,\"languageID\":[1,2],\"weightage\":[5,3]," + + "\"canRead\":[true,false],\"canWrite\":[true,false],\"canSpeak\":[true,true]," + + "\"weightage_Read\":[5,3],\"weightage_Write\":[5,3],\"weightage_Speak\":[5,3]}]"); + + assertSuccessContaining(response, "7001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(employeeMasterInter).mapLanguage(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals(77, captor.getValue().get(0).getServiceProviderID()); + } + + @Test + @DisplayName("UserLangMapping should answer an error envelope when the request names no languages") + void userLangMapping_shouldAnswerErrorEnvelopeWithoutLanguages() { + assertCodeException(controller.UserLangMapping("[{\"userID\":3117}]")); + } + + @Test + @DisplayName("updateUserLanguageMapping should copy the edits onto the stored mapping") + void updateUserLanguageMapping_shouldCopyEdits() { + M_UserLangMapping stored = langMapping(7001, 1); + when(employeeMasterInter.updateLangMapping(7001)).thenReturn(stored); + when(employeeMasterInter.saveUserLangEditedData(stored)).thenReturn(stored); + + String response = controller.updateUserLanguageMapping("{\"userLangID\":7001,\"userID\":3117," + + "\"languageID\":2,\"weightage\":9,\"canRead\":true,\"canWrite\":false,\"canSpeak\":true," + + "\"weightage_Read\":9,\"weightage_Write\":1,\"weightage_Speak\":9,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "7001"); + assertEquals(2, stored.getLanguageID()); + assertEquals(9, stored.getWeightage()); + } + + @Test + @DisplayName("updateUserLanguageMapping should answer an error envelope for a mapping that does not exist") + void updateUserLanguageMapping_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(employeeMasterInter.updateLangMapping(7001)).thenReturn(null); + + assertCodeException(controller.updateUserLanguageMapping("{\"userLangID\":7001}")); + } + + @Test + @DisplayName("UserLanguageMapping should mark the language mapping deleted") + void userLanguageMapping_shouldMarkMappingDeleted() { + M_UserLangMapping stored = langMapping(7001, 1); + when(employeeMasterInter.updateLangMapping(7001)).thenReturn(stored); + when(employeeMasterInter.saveUserLangEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.UserLanguageMapping("{\"userLangID\":7001,\"deleted\":true}"), "7001"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("UserLanguageMapping should answer an error envelope for a mapping that does not exist") + void userLanguageMapping_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(employeeMasterInter.updateLangMapping(7001)).thenReturn(null); + + assertCodeException(controller.UserLanguageMapping("{\"userLangID\":7001,\"deleted\":true}")); + } + + @Test + @DisplayName("UserRoleMapping should build one mapping per role under each privilege") + void userRoleMapping_shouldBuildOneMappingPerRole() throws Exception { + when(employeeMasterInter.mapRole(anyList(), anyString())) + .thenReturn(new ArrayList<>(List.of(roleMapping(9001, 3117)))); + + String response = controller.UserRoleMapping("[{\"userID\":3117,\"createdBy\":\"admin\"," + + "\"serviceProviderID\":77,\"previleges\":[{\"roleID\":[11,12]," + + "\"providerServiceMapID\":4001,\"workingLocationID\":401}]}]", request); + + assertSuccessContaining(response, "9001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(employeeMasterInter).mapRole(captor.capture(), anyString()); + assertEquals(2, captor.getValue().size()); + assertEquals(11, captor.getValue().get(0).getRoleID()); + } + + @Test + @DisplayName("UserRoleMapping should answer an error envelope when the request names no privileges") + void userRoleMapping_shouldAnswerErrorEnvelopeWithoutPrivileges() { + assertCodeException(controller.UserRoleMapping("[{\"userID\":3117}]", request)); + } + + @Test + @DisplayName("UserRoleMappings should carry the 1097 helpline flags onto the mapping") + void userRoleMappings_shouldCarryHelplineFlags() throws Exception { + when(employeeMasterInter.mapRole(anyList(), anyString())) + .thenReturn(new ArrayList<>(List.of(roleMapping(9001, 3117)))); + + String response = controller.UserRoleMappings("[{\"userID\":3117,\"createdBy\":\"admin\"," + + "\"serviceProviderID\":77,\"previleges\":[{\"providerServiceMapID\":4001," + + "\"workingLocationID\":401,\"stateID\":29,\"districtID\":301,\"blockID\":401," + + "\"blockName\":\"North\",\"facilityID\":501," + + "\"ID\":[{\"roleID\":11,\"inbound\":true,\"outbound\":false," + + "\"teleConsultation\":\"Y\"}]}]}]", request); + + assertSuccessContaining(response, "9001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(employeeMasterInter).mapRole(captor.capture(), anyString()); + M_UserServiceRoleMapping2 built = captor.getValue().get(0); + assertEquals(11, built.getRoleID()); + assertTrue(built.getInbound()); + assertEquals("Y", built.getTeleConsultation()); + assertEquals("North", built.getBlockName()); + } + + @Test + @DisplayName("UserRoleMappings should answer an error envelope when the request names no privileges") + void userRoleMappings_shouldAnswerErrorEnvelopeWithoutPrivileges() throws Exception { + assertCodeException(controller.UserRoleMappings("[{\"userID\":3117}]", request)); + } + + @Test + @DisplayName("updateUserRoleMapping should cascade the supervisor mappings when the role changes") + void updateUserRoleMapping_shouldCascadeWhenRoleChanges() throws Exception { + M_UserServiceRoleMapping2 stored = roleMapping(9001, 3117); + stored.setRoleID(11); + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(stored); + when(employeeMasterInter.saveRoleMappingeditedData(any(), anyString())).thenReturn(stored); + + String response = controller.updateUserRoleMapping("{\"uSRMappingID\":9001,\"userID\":3117," + + "\"roleID\":12,\"modifiedBy\":\"admin\"}", request); + + assertSuccessContaining(response, "9001"); + verify(employeeMasterInter).cascadeDeleteAshaMappingsForUser(3117); + assertEquals(12, stored.getRoleID()); + } + + @Test + @DisplayName("updateUserRoleMapping should cascade the supervisor mappings when the facility changes") + void updateUserRoleMapping_shouldCascadeWhenFacilityChanges() throws Exception { + M_UserServiceRoleMapping2 stored = roleMapping(9001, 3117); + stored.setRoleID(11); + stored.setFacilityID(501); + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(stored); + when(employeeMasterInter.saveRoleMappingeditedData(any(), anyString())).thenReturn(stored); + + controller.updateUserRoleMapping("{\"uSRMappingID\":9001,\"userID\":3117,\"roleID\":11," + + "\"facilityID\":502}", request); + + verify(employeeMasterInter).cascadeDeleteAshaMappingsForUser(3117); + } + + @Test + @DisplayName("updateUserRoleMapping should leave the supervisor mappings alone when nothing changed") + void updateUserRoleMapping_shouldLeaveSupervisorMappingsAlone() throws Exception { + M_UserServiceRoleMapping2 stored = roleMapping(9001, 3117); + stored.setRoleID(11); + stored.setFacilityID(501); + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(stored); + when(employeeMasterInter.saveRoleMappingeditedData(any(), anyString())).thenReturn(stored); + + controller.updateUserRoleMapping("{\"uSRMappingID\":9001,\"userID\":3117,\"roleID\":11," + + "\"facilityID\":501}", request); + + verify(employeeMasterInter, never()).cascadeDeleteAshaMappingsForUser(anyInt()); + } + + @Test + @DisplayName("updateUserRoleMapping should carry the optional call flags only when the request sets them") + void updateUserRoleMapping_shouldCarryOptionalFlagsOnlyWhenSet() throws Exception { + M_UserServiceRoleMapping2 stored = roleMapping(9001, 3117); + stored.setRoleID(11); + stored.setInbound(Boolean.TRUE); + stored.setTeleConsultation("Y"); + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(stored); + when(employeeMasterInter.saveRoleMappingeditedData(any(), anyString())).thenReturn(stored); + + controller.updateUserRoleMapping("{\"uSRMappingID\":9001,\"userID\":3117,\"roleID\":11}", request); + + assertTrue(stored.getInbound(), "an unset flag must keep the value already on record"); + assertEquals("Y", stored.getTeleConsultation()); + } + + @Test + @DisplayName("updateUserRoleMapping should answer an error envelope for a mapping that does not exist") + void updateUserRoleMapping_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(null); + + assertCodeException(controller.updateUserRoleMapping("{\"uSRMappingID\":9001}", request)); + } + + @Test + @DisplayName("deleteUserRoleMapping should cascade the supervisor mappings before marking it deleted") + void deleteUserRoleMapping_shouldCascadeBeforeDeleting() throws Exception { + M_UserServiceRoleMapping2 stored = roleMapping(9001, 3117); + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(stored); + when(employeeMasterInter.saveRoleMappingeditedData(any(), anyString())).thenReturn(stored); + + assertSuccessContaining( + controller.deleteUserRoleMapping("{\"uSRMappingID\":9001,\"deleted\":true}", request), "9001"); + verify(employeeMasterInter).cascadeDeleteAshaMappingsForDeactivation(stored); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteUserRoleMapping should not cascade when the mapping is being reinstated") + void deleteUserRoleMapping_shouldNotCascadeWhenReinstating() throws Exception { + M_UserServiceRoleMapping2 stored = roleMapping(9001, 3117); + stored.setDeleted(Boolean.TRUE); + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(stored); + when(employeeMasterInter.saveRoleMappingeditedData(any(), anyString())).thenReturn(stored); + + controller.deleteUserRoleMapping("{\"uSRMappingID\":9001,\"deleted\":false}", request); + + verify(employeeMasterInter, never()).cascadeDeleteAshaMappingsForDeactivation(any()); + } + + @Test + @DisplayName("deleteUserRoleMapping should answer an error envelope for a mapping that does not exist") + void deleteUserRoleMapping_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(employeeMasterInter.getDataUsrId(9001)).thenReturn(null); + + assertCodeException(controller.deleteUserRoleMapping("{\"uSRMappingID\":9001,\"deleted\":true}", request)); + } + + @Test + @DisplayName("getUserRoleMapped should answer the mapped roles for the provider") + void getUserRoleMapped_shouldAnswerMappedRoles() { + V_Userservicerolemapping mapped = new V_Userservicerolemapping(); + mapped.setuSRMappingID(9001); + mapped.setName("Asha Rao"); + when(employeeMasterInter.getMappedRole(77)).thenReturn(new ArrayList<>(List.of(mapped))); + + assertSuccessContaining(controller.getUserRoleMapped("{\"serviceProviderID\":77}"), "Asha Rao"); + } + + @Test + @DisplayName("getUserRoleMapped should answer an error envelope when the lookup fails") + void getUserRoleMapped_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getMappedRole(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getUserRoleMapped("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("searchMappedRoleByNameorUserId should answer the roles matching the name or id") + void searchMappedRoleByNameorUserId_shouldAnswerMatchingRoles() { + V_Userservicerolemapping mapped = new V_Userservicerolemapping(); + mapped.setuSRMappingID(9001); + mapped.setName("Asha Rao"); + when(employeeMasterInter.getMappedRole("Asha Rao", 3117)).thenReturn(new ArrayList<>(List.of(mapped))); + + assertSuccessContaining( + controller.searchMappedRoleByNameorUserId("{\"name\":\"Asha Rao\",\"userID\":3117}"), "Asha Rao"); + } + + @Test + @DisplayName("searchMappedRoleByNameorUserId should answer an error envelope when the search fails") + void searchMappedRoleByNameorUserId_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getMappedRole(anyString(), anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchMappedRoleByNameorUserId("{\"name\":\"Asha Rao\",\"userID\":3117}")); + } + + @Test + @DisplayName("getUserRoleTM should answer the telemedicine roles the service resolves") + void getUserRoleTM_shouldAnswerTelemedicineRoles() { + UserRole role = new UserRole(); + role.setRoleID(11); + role.setRolename("Specialist"); + when(employeeMasterInter.getUserRoleTM(any())).thenReturn(new ArrayList<>(List.of(role))); + + assertSuccessContaining(controller.getUserRoleTM("{\"userID\":3117}"), "Specialist"); + } + + @Test + @DisplayName("getUserRoleTM should answer an error envelope when the lookup fails") + void getUserRoleTM_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeMasterInter.getUserRoleTM(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getUserRoleTM("{\"userID\":3117}")); + } + + @Test + @DisplayName("deleteUserRoleMappingTM should answer the mapping the service retired") + void deleteUserRoleMappingTM_shouldAnswerRetiredMapping() throws Exception { + when(employeeMasterInter.deleteuserrolemapTM(any())).thenReturn(roleMapping(9001, 3117)); + + assertSuccessContaining( + controller.deleteUserRoleMappingTM("{\"uSRMappingID\":9001,\"deleted\":true}", request), "9001"); + } + + @Test + @DisplayName("deleteUserRoleMappingTM should answer an error envelope when the retirement fails") + void deleteUserRoleMappingTM_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(employeeMasterInter.deleteuserrolemapTM(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.deleteUserRoleMappingTM("{\"uSRMappingID\":9001}", request)); + } +} diff --git a/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeSignatureControllerTest.java b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeSignatureControllerTest.java new file mode 100644 index 0000000..87a59d5 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/employeemaster/EmployeeSignatureControllerTest.java @@ -0,0 +1,238 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.employeemaster; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; + +import com.iemr.admin.data.employeemaster.EmployeeSignature; +import com.iemr.admin.service.employeemaster.EmployeeSignatureServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +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.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * A user's signature is stamped onto the prescriptions they sign, so the upload, + * download and activation endpoints all guard a clinically meaningful artefact. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("EmployeeSignatureController Test Suite") +class EmployeeSignatureControllerTest { + + private static final byte[] SIGNATURE = "a-png-body".getBytes(StandardCharsets.UTF_8); + + @Mock + private EmployeeSignatureServiceImpl employeeSignatureServiceImpl; + + @InjectMocks + private EmployeeSignatureController controller; + + private static EmployeeSignature signature(String fileName, String fileType) { + EmployeeSignature signature = new EmployeeSignature(); + signature.setUserID(3117L); + signature.setFileName(fileName); + signature.setFileType(fileType); + signature.setSignature(SIGNATURE); + return signature; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + @Test + @DisplayName("uploadFile should decode the uploaded body and answer the stored signature id") + void uploadFile_shouldDecodeAndStore() { + EmployeeSignature uploaded = new EmployeeSignature(); + uploaded.setUserID(3117L); + uploaded.setFileContent(Base64.getEncoder().encodeToString(SIGNATURE)); + when(employeeSignatureServiceImpl.uploadSignature(any())).thenReturn(9001L); + + String response = controller.uploadFile(uploaded); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("9001"), response); + assertArrayEquals(SIGNATURE, uploaded.getSignature(), + "the base64 body must be decoded before it is stored"); + } + + @Test + @DisplayName("uploadFile should answer an error envelope for a body that is not valid base64") + void uploadFile_shouldAnswerErrorEnvelopeForInvalidBody() { + EmployeeSignature uploaded = new EmployeeSignature(); + uploaded.setFileContent("not base 64 !!"); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.uploadFile(uploaded))); + } + + @Test + @DisplayName("uploadFile should answer an error envelope when the store fails") + void uploadFile_shouldAnswerErrorEnvelopeOnStoreFailure() { + EmployeeSignature uploaded = new EmployeeSignature(); + uploaded.setFileContent(Base64.getEncoder().encodeToString(SIGNATURE)); + when(employeeSignatureServiceImpl.uploadSignature(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.uploadFile(uploaded))); + } + + @Test + @DisplayName("fetchFile should answer the signature as an attachment of its own type") + void fetchFile_shouldAnswerSignatureAsAttachment() throws Exception { + when(employeeSignatureServiceImpl.fetchSignature(3117L)) + .thenReturn(signature("asha-signature.png", MediaType.IMAGE_PNG_VALUE)); + + ResponseEntity response = controller.fetchFile(3117L); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.IMAGE_PNG, response.getHeaders().getContentType()); + assertArrayEquals(SIGNATURE, response.getBody()); + assertTrue(response.getHeaders().getContentDisposition().toString().contains("asha-signature.png")); + } + + @Test + @DisplayName("fetchFile should fall back to a plain download for a file type it cannot parse") + void fetchFile_shouldFallBackForUnparseableType() throws Exception { + when(employeeSignatureServiceImpl.fetchSignature(3117L)) + .thenReturn(signature("asha-signature.png", "not/a/media/type")); + + ResponseEntity response = controller.fetchFile(3117L); + + assertEquals(MediaType.APPLICATION_OCTET_STREAM, response.getHeaders().getContentType()); + } + + @Test + @DisplayName("fetchFile should fall back to a plain download when no file type is on record") + void fetchFile_shouldFallBackWithoutAType() throws Exception { + when(employeeSignatureServiceImpl.fetchSignature(3117L)) + .thenReturn(signature("asha-signature.png", null)); + + assertEquals(MediaType.APPLICATION_OCTET_STREAM, controller.fetchFile(3117L).getHeaders().getContentType()); + } + + @Test + @DisplayName("fetchFile should raise rather than answer an empty download for a missing signature") + void fetchFile_shouldRaiseForMissingSignature() { + when(employeeSignatureServiceImpl.fetchSignature(3117L)).thenReturn(null); + + Exception thrown = assertThrows(Exception.class, () -> controller.fetchFile(3117L)); + assertTrue(thrown.getMessage().contains("Error while downloading file"), thrown.getMessage()); + } + + @Test + @DisplayName("existFile should report both that a signature exists and whether it is active") + void existFile_shouldReportExistenceAndActivation() throws Exception { + when(employeeSignatureServiceImpl.existSignature(3117L)).thenReturn(Boolean.TRUE); + when(employeeSignatureServiceImpl.isSignatureActive(3117L)).thenReturn(Boolean.TRUE); + + String response = controller.existFile(3117L); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("\"signStatus\":\"true\""), response); + assertTrue(response.contains("\"response\":\"true\""), response); + } + + @Test + @DisplayName("existFile should report a signature that exists but has been deactivated") + void existFile_shouldReportDeactivatedSignature() throws Exception { + when(employeeSignatureServiceImpl.existSignature(3117L)).thenReturn(Boolean.TRUE); + when(employeeSignatureServiceImpl.isSignatureActive(3117L)).thenReturn(Boolean.FALSE); + + assertTrue(controller.existFile(3117L).contains("\"signStatus\":\"false\"")); + } + + @Test + @DisplayName("existFile should answer an error envelope when the check fails") + void existFile_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(employeeSignatureServiceImpl.existSignature(anyLong())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.existFile(3117L))); + } + + @Test + @DisplayName("ActivateUser should report the signature as active once it is reinstated") + void activateUser_shouldReportSignatureActive() { + EmployeeSignature updated = signature("asha-signature.png", MediaType.IMAGE_PNG_VALUE); + updated.setDeleted(Boolean.FALSE); + when(employeeSignatureServiceImpl.updateUserSignatureStatus(anyString())).thenReturn(updated); + + String response = controller.ActivateUser("{\"userID\":3117,\"deleted\":false}", + new MockHttpServletRequest()); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("\"active\":true"), response); + } + + @Test + @DisplayName("ActivateUser should report the signature as inactive once it is deactivated") + void activateUser_shouldReportSignatureInactive() { + EmployeeSignature updated = signature("asha-signature.png", MediaType.IMAGE_PNG_VALUE); + updated.setDeleted(Boolean.TRUE); + when(employeeSignatureServiceImpl.updateUserSignatureStatus(anyString())).thenReturn(updated); + + assertTrue(controller.ActivateUser("{\"userID\":3117,\"deleted\":true}", new MockHttpServletRequest()) + .contains("\"active\":false")); + } + + @Test + @DisplayName("ActivateUser should treat a signature with no flag on record as inactive") + void activateUser_shouldTreatMissingFlagAsInactive() { + EmployeeSignature updated = signature("asha-signature.png", MediaType.IMAGE_PNG_VALUE); + when(employeeSignatureServiceImpl.updateUserSignatureStatus(anyString())).thenReturn(updated); + + assertTrue(controller.ActivateUser("{\"userID\":3117}", new MockHttpServletRequest()) + .contains("\"active\":false")); + } + + @Test + @DisplayName("ActivateUser should answer an error envelope when the change fails") + void activateUser_shouldAnswerErrorEnvelopeOnFailure() { + when(employeeSignatureServiceImpl.updateUserSignatureStatus(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.ActivateUser("{\"userID\":3117}", new MockHttpServletRequest()))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/facilitytype/FacilitytypeControllerTest.java b/src/test/java/com/iemr/admin/controller/facilitytype/FacilitytypeControllerTest.java new file mode 100644 index 0000000..0dce546 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/facilitytype/FacilitytypeControllerTest.java @@ -0,0 +1,314 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.facilitytype; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.facilitytype.M_facilitytype; +import com.iemr.admin.data.store.M_Facility; +import com.iemr.admin.data.store.M_FacilityLevel; +import com.iemr.admin.repository.store.MainStoreRepo; +import com.iemr.admin.service.facilitytype.M_facilitytypeInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The facility type endpoints define the levels of the health facility + * hierarchy, so a type still in use may not be retired out from under the + * facilities that carry it. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("FacilitytypeController Test Suite") +class FacilitytypeControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer TYPE_ID = 3; + + @Mock + private M_facilitytypeInter m_facilitytypeInter; + + @Mock + private MainStoreRepo mainStoreRepo; + + @InjectMocks + private FacilitytypeController controller; + + private static M_facilitytype type(Integer id, String name) { + M_facilitytype type = new M_facilitytype(); + type.setFacilityTypeID(id); + type.setFacilityTypeName(name); + return type; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("getFacility should answer the facility types of the provider") + void getFacility_shouldAnswerProviderTypes() { + when(m_facilitytypeInter.getAllFicilityData(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(type(TYPE_ID, "PHC")))); + + assertSuccessContaining(controller.getFacility("{\"providerServiceMapID\":4001}"), "PHC"); + } + + @Test + @DisplayName("getFacility should answer an error envelope when the lookup fails") + void getFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.getAllFicilityData(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFacility("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("addFacility should answer the facility types the service stored") + void addFacility_shouldAnswerStoredTypes() { + when(m_facilitytypeInter.addAllFicilityData(anyList())) + .thenReturn(new ArrayList<>(List.of(type(TYPE_ID, "PHC")))); + + assertSuccessContaining(controller.addFacility("[{\"facilityTypeName\":\"PHC\"}]"), "PHC"); + } + + @Test + @DisplayName("addFacility should answer an error envelope when the store fails") + void addFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.addAllFicilityData(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.addFacility("[{\"facilityTypeName\":\"PHC\"}]")); + } + + @Test + @DisplayName("editFacility should copy only the fields the request actually sets") + void editFacility_shouldCopyOnlySuppliedFields() { + M_facilitytype stored = type(TYPE_ID, "old name"); + stored.setRuralUrban("Rural"); + stored.setFacilityTypeDesc("old description"); + when(m_facilitytypeInter.editAllFicilityData(TYPE_ID)).thenReturn(stored); + when(m_facilitytypeInter.updateFacilityData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editFacility( + "{\"facilityTypeID\":3,\"facilityTypeName\":\"PHC\",\"modifiedBy\":\"admin\"}"), "PHC"); + assertEquals("Rural", stored.getRuralUrban(), "an unset field keeps the value on record"); + assertEquals("old description", stored.getFacilityTypeDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("editFacility should copy the rural-urban split and description when the request sets them") + void editFacility_shouldCopySuppliedSplitAndDescription() { + M_facilitytype stored = type(TYPE_ID, "old name"); + when(m_facilitytypeInter.editAllFicilityData(TYPE_ID)).thenReturn(stored); + when(m_facilitytypeInter.updateFacilityData(stored)).thenReturn(stored); + + controller.editFacility("{\"facilityTypeID\":3,\"ruralUrban\":\"Urban\"," + + "\"facilityTypeDesc\":\"Urban primary centre\",\"modifiedBy\":\"admin\"}"); + + assertEquals("Urban", stored.getRuralUrban()); + assertEquals("Urban primary centre", stored.getFacilityTypeDesc()); + } + + @Test + @DisplayName("editFacility should answer an error envelope for a facility type that does not exist") + void editFacility_shouldAnswerErrorEnvelopeForUnknownType() { + when(m_facilitytypeInter.editAllFicilityData(TYPE_ID)).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.editFacility("{\"facilityTypeID\":3}"))); + } + + @Test + @DisplayName("deleteFacility should refuse to retire a facility type facilities still carry") + void deleteFacility_shouldRefuseTypeInUse() { + when(m_facilitytypeInter.editAllFicilityData(TYPE_ID)).thenReturn(type(TYPE_ID, "PHC")); + when(mainStoreRepo.findByFacilityTypeIDAndDeletedFalse(TYPE_ID)) + .thenReturn(List.of(new M_Facility(), new M_Facility())); + + String response = controller.deleteFacility("{\"facilityTypeID\":3,\"deleted\":true}"); + + assertGenericFailure(response); + assertTrue(response.contains("in use by 2 active facilities"), response); + } + + @Test + @DisplayName("deleteFacility should retire a facility type nothing carries") + void deleteFacility_shouldRetireUnusedType() { + M_facilitytype stored = type(TYPE_ID, "PHC"); + when(m_facilitytypeInter.editAllFicilityData(TYPE_ID)).thenReturn(stored); + when(mainStoreRepo.findByFacilityTypeIDAndDeletedFalse(TYPE_ID)).thenReturn(new ArrayList<>()); + when(m_facilitytypeInter.updateFacilityData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteFacility("{\"facilityTypeID\":3,\"deleted\":true}"), "PHC"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteFacility should reinstate a facility type without checking what carries it") + void deleteFacility_shouldReinstateWithoutChecking() { + M_facilitytype stored = type(TYPE_ID, "PHC"); + stored.setDeleted(Boolean.TRUE); + when(m_facilitytypeInter.editAllFicilityData(TYPE_ID)).thenReturn(stored); + when(m_facilitytypeInter.updateFacilityData(stored)).thenReturn(stored); + + controller.deleteFacility("{\"facilityTypeID\":3,\"deleted\":false}"); + + verify(mainStoreRepo, never()).findByFacilityTypeIDAndDeletedFalse(anyInt()); + } + + @Test + @DisplayName("checkFacilityTypeCode should report whether the code is already taken") + void checkFacilityTypeCode_shouldReportWhetherCodeIsTaken() { + when(m_facilitytypeInter.checkFacilityTypeCode(any())).thenReturn(Boolean.TRUE); + + assertSuccessContaining(controller.checkFacilityTypeCode("{\"facilityTypeCode\":\"PHC-1\"}"), "true"); + } + + @Test + @DisplayName("checkFacilityTypeCode should answer an error envelope when the check fails") + void checkFacilityTypeCode_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.checkFacilityTypeCode(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkFacilityTypeCode("{\"facilityTypeCode\":\"PHC-1\"}")); + } + + @Test + @DisplayName("getFacilityTypesByRuralUrban should narrow the types to the split the caller names") + void getByRuralUrban_shouldNarrowToSplit() { + when(m_facilitytypeInter.getFacilityTypesByRuralUrban(PSM_ID, "Rural")) + .thenReturn(new ArrayList<>(List.of(type(TYPE_ID, "PHC")))); + + assertSuccessContaining(controller.getFacilityTypesByRuralUrban( + "{\"providerServiceMapID\":4001,\"ruralUrban\":\"Rural\"}"), "PHC"); + } + + @Test + @DisplayName("getFacilityTypesByRuralUrban should answer an error envelope when the lookup fails") + void getByRuralUrban_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.getFacilityTypesByRuralUrban(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFacilityTypesByRuralUrban("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getFacilityLevels should answer the hierarchy levels on record") + void getFacilityLevels_shouldAnswerHierarchyLevels() { + M_FacilityLevel level = new M_FacilityLevel(); + when(m_facilitytypeInter.getFacilityLevels()).thenReturn(new ArrayList<>(List.of(level))); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(controller.getFacilityLevels())); + } + + @Test + @DisplayName("getFacilityLevels should answer an error envelope when the lookup fails") + void getFacilityLevels_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.getFacilityLevels()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFacilityLevels()); + } + + @Test + @DisplayName("getFacilityTypesByBlock should answer the types used in the block") + void getByBlock_shouldAnswerBlockTypes() { + when(m_facilitytypeInter.getFacilityTypesByBlock(401)) + .thenReturn(new ArrayList<>(List.of(type(TYPE_ID, "PHC")))); + + assertSuccessContaining(controller.getFacilityTypesByBlock("{\"blockID\":401}"), "PHC"); + } + + @Test + @DisplayName("getFacilityTypesByBlock should answer an error envelope when the lookup fails") + void getByBlock_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.getFacilityTypesByBlock(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFacilityTypesByBlock("{\"blockID\":401}")); + } + + @Test + @DisplayName("getFacilityTypesByState should answer the types used in the state") + void getByState_shouldAnswerStateTypes() { + when(m_facilitytypeInter.getFacilityTypesByState(29)) + .thenReturn(new ArrayList<>(List.of(type(TYPE_ID, "PHC")))); + + assertSuccessContaining(controller.getFacilityTypesByState("{\"stateID\":29}"), "PHC"); + } + + @Test + @DisplayName("getFacilityTypesByState should answer an error envelope when the lookup fails") + void getByState_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.getFacilityTypesByState(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFacilityTypesByState("{\"stateID\":29}")); + } + + @Test + @DisplayName("checkFacilityTypeName should report whether the name is already used in the state") + void checkFacilityTypeName_shouldReportWhetherNameIsUsed() { + when(m_facilitytypeInter.checkFacilityTypeNameExists("PHC", 29)).thenReturn(true); + + assertSuccessContaining( + controller.checkFacilityTypeName("{\"facilityTypeName\":\"PHC\",\"stateID\":29}"), "true"); + } + + @Test + @DisplayName("checkFacilityTypeName should answer an error envelope when the check fails") + void checkFacilityTypeName_shouldAnswerErrorEnvelopeOnFailure() { + when(m_facilitytypeInter.checkFacilityTypeNameExists(anyString(), anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkFacilityTypeName("{\"facilityTypeName\":\"PHC\",\"stateID\":29}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/foetalmonitormaster/FoetalMonitorControllerTest.java b/src/test/java/com/iemr/admin/controller/foetalmonitormaster/FoetalMonitorControllerTest.java new file mode 100644 index 0000000..caf6063 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/foetalmonitormaster/FoetalMonitorControllerTest.java @@ -0,0 +1,355 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.foetalmonitormaster; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.foetalmonitormaster.FoetalMonitorDeviceID; +import com.iemr.admin.service.foetalmonitormaster.FoetalMonitorService; +import com.iemr.admin.utils.exception.IEMRException; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The fetosense endpoints keep the device catalogue and the pairing between a + * device and the van it travels in. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("FoetalMonitorController Test Suite") +class FoetalMonitorControllerTest { + + private static final Integer PSM_ID = 4001; + private static final String AUTH = "sess-0d5f3a7c"; + private static final String DEVICE_JSON = "{\"vfdID\":9001,\"deviceID\":\"FS-1\",\"vanID\":71," + + "\"providerServiceMapID\":4001,\"deactivated\":false}"; + + @Mock + private FoetalMonitorService foetalMonitorService; + + @InjectMocks + private FoetalMonitorController controller; + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + private static void assertInvalidRequest(String response) { + assertEquals(OutputResponse.USERID_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("createFoetalMonitorTestMaster should answer the tests the service stored") + void createTestMaster_shouldAnswerStoredTests() throws Exception { + when(foetalMonitorService.createFoetalMonitorTestMaster(anyString())) + .thenReturn("[{\"testName\":\"Non stress test\"}]"); + + assertSuccessContaining(controller.createFoetalMonitorTestMaster("[{}]"), "Non stress test"); + } + + @Test + @DisplayName("createFoetalMonitorTestMaster should stay at its default when the service stored nothing") + void createTestMaster_shouldStayAtDefaultWhenNothingStored() throws Exception { + when(foetalMonitorService.createFoetalMonitorTestMaster(anyString())).thenReturn(null); + + assertGenericFailure(controller.createFoetalMonitorTestMaster("[{}]")); + } + + @Test + @DisplayName("createFoetalMonitorTestMaster should answer an error envelope when the store fails") + void createTestMaster_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(foetalMonitorService.createFoetalMonitorTestMaster(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createFoetalMonitorTestMaster("[{}]")); + } + + @Test + @DisplayName("fetchFoetalMonitorTestMaster should answer the tests of the provider") + void fetchTestMaster_shouldAnswerProviderTests() { + when(foetalMonitorService.getFoetalMonitorTestMaster(PSM_ID)) + .thenReturn("[{\"testName\":\"Non stress test\"}]"); + + assertSuccessContaining(controller.fetchFoetalMonitorTestMaster(PSM_ID), "Non stress test"); + } + + @Test + @DisplayName("fetchFoetalMonitorTestMaster should refuse a request that names no provider") + void fetchTestMaster_shouldRefuseRequestWithoutProvider() { + assertInvalidRequest(controller.fetchFoetalMonitorTestMaster(0)); + verify(foetalMonitorService, never()).getFoetalMonitorTestMaster(anyInt()); + } + + @Test + @DisplayName("fetchFoetalMonitorTestMaster should answer an error envelope when the lookup fails") + void fetchTestMaster_shouldAnswerErrorEnvelopeOnFailure() { + when(foetalMonitorService.getFoetalMonitorTestMaster(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.fetchFoetalMonitorTestMaster(PSM_ID)); + } + + @Test + @DisplayName("updateProcedureMaster should answer the test once the edit lands") + void updateTestMaster_shouldAnswerEditedTest() { + when(foetalMonitorService.updateFoetalMonitorTestMaster(anyString())) + .thenReturn("{\"testName\":\"Non stress test\"}"); + + assertSuccessContaining(controller.updateProcedureMaster("{}"), "Non stress test"); + } + + @Test + @DisplayName("updateProcedureMaster should report an edit the service could not apply") + void updateTestMaster_shouldReportUnappliedEdit() { + when(foetalMonitorService.updateFoetalMonitorTestMaster(anyString())).thenReturn(null); + + String response = controller.updateProcedureMaster("{}"); + + assertInvalidRequest(response); + assertTrue(response.contains("Failed to update procedure details"), response); + } + + @Test + @DisplayName("updateProcedureStatus should answer the test once its status has changed") + void updateTestStatus_shouldAnswerChangedTest() throws Exception { + when(foetalMonitorService.updateFoetalMonitorTestMasterStatus(11, true)) + .thenReturn("{\"testName\":\"Non stress test\"}"); + + assertSuccessContaining( + controller.updateProcedureStatus("{\"foetalMonitorTestID\":11,\"deleted\":true}"), + "Non stress test"); + } + + @Test + @DisplayName("updateProcedureStatus should report a status the service could not change") + void updateTestStatus_shouldReportUnchangedStatus() throws Exception { + when(foetalMonitorService.updateFoetalMonitorTestMasterStatus(anyInt(), anyBoolean())).thenReturn(null); + + assertInvalidRequest(controller.updateProcedureStatus("{\"foetalMonitorTestID\":11,\"deleted\":true}")); + } + + @Test + @DisplayName("updateProcedureStatus should refuse a request that names no test") + void updateTestStatus_shouldRefuseRequestWithoutTest() { + assertInvalidRequest(controller.updateProcedureStatus("{\"deleted\":true}")); + } + + @Test + @DisplayName("saveFoetalMonitorDeviceID should report the devices it stored") + void saveDeviceID_shouldReportStoredDevices() throws Exception { + when(foetalMonitorService.saveFoetalMonitorDeviceID(any())).thenReturn(1); + + assertSuccessContaining( + controller.saveFoetalMonitorDeviceID(new ArrayList<>(List.of(new FoetalMonitorDeviceID())), AUTH), + "Device ID saved successfully"); + } + + @Test + @DisplayName("saveFoetalMonitorDeviceID should report the reason the service refused the device") + void saveDeviceID_shouldReportRefusal() throws Exception { + when(foetalMonitorService.saveFoetalMonitorDeviceID(any())) + .thenThrow(new IEMRException("Error in saving foetal monitor device ID")); + + String response = controller.saveFoetalMonitorDeviceID(new ArrayList<>(), AUTH); + + assertGenericFailure(response); + assertTrue(response.contains("Error in saving foetal monitor device ID"), response); + } + + @Test + @DisplayName("saveVanIDandDeviceIDMapping should report a pairing the service accepted") + void saveMapping_shouldReportAcceptedPairing() throws Exception { + when(foetalMonitorService.vanIDAndDeviceIDMapping(any())).thenReturn(1); + + assertSuccessContaining(controller.saveVanIDandDeviceIDMapping(DEVICE_JSON, AUTH), + "Mapping Done successfully"); + } + + @Test + @DisplayName("saveVanIDandDeviceIDMapping should report the reason the service refused the pairing") + void saveMapping_shouldReportRefusal() throws Exception { + when(foetalMonitorService.vanIDAndDeviceIDMapping(any())) + .thenThrow(new IEMRException("Error in updating the VanID")); + + assertGenericFailure(controller.saveVanIDandDeviceIDMapping(DEVICE_JSON, AUTH)); + } + + @Test + @DisplayName("getFoetalMonitorDeviceID should answer the devices the service publishes") + void getDeviceID_shouldAnswerPublishedDevices() throws Exception { + when(foetalMonitorService.getFoetalMonitorDeviceID(any())) + .thenReturn("{\"fetosenseDeviceIDs\":[{\"deviceID\":\"FS-1\"}]}"); + + assertSuccessContaining(controller.getFoetalMonitorDeviceID(DEVICE_JSON), "FS-1"); + } + + @Test + @DisplayName("getFoetalMonitorDeviceID should report the reason the lookup failed") + void getDeviceID_shouldReportFailure() throws Exception { + when(foetalMonitorService.getFoetalMonitorDeviceID(any())) + .thenThrow(new IEMRException("Error in getting foetal monitor DeviceID")); + + assertGenericFailure(controller.getFoetalMonitorDeviceID(DEVICE_JSON)); + } + + @Test + @DisplayName("getVanIDAndDeviceID should answer the vans and devices still free to pair") + void getVanAndDevice_shouldAnswerFreePairs() throws Exception { + when(foetalMonitorService.getvanIDAndFoetalMonitorDeviceID(any())) + .thenReturn("{\"VanIDs\":[],\"deviceIDs\":[]}"); + + assertSuccessContaining(controller.getVanIDAndDeviceID(DEVICE_JSON), "VanIDs"); + } + + @Test + @DisplayName("getVanIDAndDeviceID should report the reason the lookup failed") + void getVanAndDevice_shouldReportFailure() throws Exception { + when(foetalMonitorService.getvanIDAndFoetalMonitorDeviceID(any())) + .thenThrow(new IEMRException("Error in getting vanID and foetalMonitorID")); + + assertGenericFailure(controller.getVanIDAndDeviceID(DEVICE_JSON)); + } + + @Test + @DisplayName("getMappedWorklist should answer the pairings on record") + void getWorklist_shouldAnswerPairings() throws Exception { + when(foetalMonitorService.getVanIDMappingWorklist(any())) + .thenReturn("[{\"deviceID\":\"FS-1\"}]"); + + assertSuccessContaining(controller.getMappedWorklist(DEVICE_JSON), "FS-1"); + } + + @Test + @DisplayName("getMappedWorklist should report the reason the lookup failed") + void getWorklist_shouldReportFailure() throws Exception { + when(foetalMonitorService.getVanIDMappingWorklist(any())) + .thenThrow(new IEMRException("Error in getting vanID mapping worklist")); + + assertGenericFailure(controller.getMappedWorklist(DEVICE_JSON)); + } + + @Test + @DisplayName("updateFoetalMonitorDeviceID should report a device the service saved") + void updateDeviceID_shouldReportSavedDevice() throws Exception { + when(foetalMonitorService.updateFoetalMonitorDeviceID(any())).thenReturn(1); + + assertSuccessContaining(controller.updateFoetalMonitorDeviceID(DEVICE_JSON, AUTH), + "DeviceID updated successfully"); + } + + @Test + @DisplayName("updateFoetalMonitorDeviceID should report the reason the service refused the edit") + void updateDeviceID_shouldReportRefusal() throws Exception { + when(foetalMonitorService.updateFoetalMonitorDeviceID(any())) + .thenThrow(new IEMRException("Error in updating the Device ID")); + + String response = controller.updateFoetalMonitorDeviceID(DEVICE_JSON, AUTH); + + assertGenericFailure(response); + assertTrue(response.contains("Unable to update deviceID"), response); + } + + @Test + @DisplayName("deleteFoetalMonitorDeviceID should report a device the service retired") + void deleteDeviceID_shouldReportRetiredDevice() throws Exception { + when(foetalMonitorService.deleteFoetalMonitorDeviceID(any())).thenReturn(1); + + assertSuccessContaining(controller.deleteFoetalMonitorDeviceID(DEVICE_JSON, AUTH), + "Device ID de-activated successfully"); + } + + @Test + @DisplayName("deleteFoetalMonitorDeviceID should report the reason the service refused the retirement") + void deleteDeviceID_shouldReportRefusal() throws Exception { + when(foetalMonitorService.deleteFoetalMonitorDeviceID(any())) + .thenThrow(new IEMRException("Error in de-activating the device ID")); + + assertGenericFailure(controller.deleteFoetalMonitorDeviceID(DEVICE_JSON, AUTH)); + } + + @Test + @DisplayName("updateMapping should report a pairing the service moved") + void updateMapping_shouldReportMovedPairing() throws Exception { + when(foetalMonitorService.updatingvanIDAndDeviceIDMapping(any())).thenReturn(1); + + assertSuccessContaining(controller.updateMapping(DEVICE_JSON, AUTH), "Mapping updated successfully"); + } + + @Test + @DisplayName("updateMapping should report the reason the service refused to move the pairing") + void updateMapping_shouldReportRefusal() throws Exception { + when(foetalMonitorService.updatingvanIDAndDeviceIDMapping(any())) + .thenThrow(new IEMRException("Error in updating van details")); + + assertGenericFailure(controller.updateMapping(DEVICE_JSON, AUTH)); + } + + @Test + @DisplayName("deleteVanIDAndFoetalMonitorDeviceID should report a pairing the service released") + void deleteMapping_shouldReportReleasedPairing() throws Exception { + when(foetalMonitorService.deleteVanIDAndDeviceIDMapping(any())).thenReturn(1); + + assertSuccessContaining(controller.deleteVanIDAndFoetalMonitorDeviceID(DEVICE_JSON, AUTH), + "Mapped deactivated successfully"); + } + + @Test + @DisplayName("deleteVanIDAndFoetalMonitorDeviceID should report the reason the service refused the release") + void deleteMapping_shouldReportRefusal() throws Exception { + when(foetalMonitorService.deleteVanIDAndDeviceIDMapping(any())) + .thenThrow(new IEMRException("The Van is already mapped with a device")); + + String response = controller.deleteVanIDAndFoetalMonitorDeviceID(DEVICE_JSON, AUTH); + + assertGenericFailure(response); + assertTrue(response.contains("already mapped with a device"), response); + } +} diff --git a/src/test/java/com/iemr/admin/controller/health/HealthControllerTest.java b/src/test/java/com/iemr/admin/controller/health/HealthControllerTest.java new file mode 100644 index 0000000..a8fcd38 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/health/HealthControllerTest.java @@ -0,0 +1,102 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import com.iemr.admin.service.health.HealthService; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +/** + * Monitoring reads the HTTP status of this endpoint, so a degraded deployment + * has to stay a 200 while an unreachable one becomes a 503. + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("HealthController Test Suite") +class HealthControllerTest { + + @Mock + private HealthService healthService; + + private static Map health(String status) { + Map health = new LinkedHashMap<>(); + health.put("status", status); + health.put("checkedAt", "2026-02-17T09:30:00Z"); + return health; + } + + @Test + @DisplayName("checkHealth should answer 200 for a deployment that is up") + void checkHealth_shouldAnswerOkWhenUp() { + when(healthService.checkHealth()).thenReturn(health("UP")); + + ResponseEntity> response = new HealthController(healthService).checkHealth(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("UP", response.getBody().get("status")); + } + + @Test + @DisplayName("checkHealth should answer 200 for a deployment that is degraded but serving") + void checkHealth_shouldAnswerOkWhenDegraded() { + when(healthService.checkHealth()).thenReturn(health("DEGRADED")); + + ResponseEntity> response = new HealthController(healthService).checkHealth(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("DEGRADED", response.getBody().get("status")); + } + + @Test + @DisplayName("checkHealth should answer 503 for a deployment that is down") + void checkHealth_shouldAnswerUnavailableWhenDown() { + when(healthService.checkHealth()).thenReturn(health("DOWN")); + + ResponseEntity> response = new HealthController(healthService).checkHealth(); + + assertEquals(HttpStatus.SERVICE_UNAVAILABLE, response.getStatusCode()); + } + + @Test + @DisplayName("checkHealth should answer 503 rather than propagate a failure of the check itself") + void checkHealth_shouldAnswerUnavailableWhenCheckFails() { + when(healthService.checkHealth()).thenThrow(new IllegalStateException("diagnostics unavailable")); + + ResponseEntity> response = new HealthController(healthService).checkHealth(); + + assertEquals(HttpStatus.SERVICE_UNAVAILABLE, response.getStatusCode()); + assertEquals("DOWN", response.getBody().get("status")); + assertNotNull(response.getBody().get("timestamp")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/item/ItemControllerTest.java b/src/test/java/com/iemr/admin/controller/item/ItemControllerTest.java new file mode 100644 index 0000000..e4fb8b1 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/item/ItemControllerTest.java @@ -0,0 +1,545 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.item; + +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.items.CodeChecker; +import com.iemr.admin.data.items.ItemMaster; +import com.iemr.admin.data.items.M_ItemCategory; +import com.iemr.admin.data.items.M_ItemForm; +import com.iemr.admin.data.items.M_Route; +import com.iemr.admin.service.item.ItemService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The item endpoints keep the inventory catalogue: the drugs and consumables a + * facility can issue, and the categories, forms and routes that classify them. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ItemController Test Suite") +class ItemControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer ITEM_ID = 101; + + @Mock + private ItemService itemService; + + @InjectMocks + private ItemController controller; + + private static ItemMaster item(Integer id, String name) { + ItemMaster item = new ItemMaster(); + item.setItemID(id); + item.setItemName(name); + return item; + } + + private static M_ItemCategory category(Integer id, String name) { + M_ItemCategory category = new M_ItemCategory(); + category.setItemCategoryID(id); + category.setItemCategoryName(name); + return category; + } + + private static M_ItemForm form(Integer id, String name) { + M_ItemForm form = new M_ItemForm(); + form.setItemFormID(id); + form.setItemForm(name); + return form; + } + + private static M_Route route(Integer id, String name) { + M_Route route = new M_Route(); + route.setRouteID(id); + route.setRouteName(name); + return route; + } + + private static CodeChecker codeChecker(String name, String code) { + CodeChecker checker = new CodeChecker(); + checker.setName(name); + checker.setCode(code); + checker.setProviderServiceMapID(PSM_ID); + return checker; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("getItemForm should answer the item forms configured for the provider") + void getItemForm_shouldAnswerConfiguredForms() { + when(itemService.getItemFormProviderServiceMapID(PSM_ID)).thenReturn(List.of(form(11, "Tablet"))); + + assertSuccessContaining(controller.getItemForm(PSM_ID), "Tablet"); + } + + @Test + @DisplayName("getItemForm should answer an error envelope when the lookup fails") + void getItemForm_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.getItemFormProviderServiceMapID(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getItemForm(PSM_ID)); + } + + @Test + @DisplayName("getItemRoute should answer the routes configured for the provider") + void getItemRoute_shouldAnswerConfiguredRoutes() { + when(itemService.getItemRouteProviderServiceMapID(PSM_ID)).thenReturn(List.of(route(21, "Oral"))); + + assertSuccessContaining(controller.getItemRoute(PSM_ID), "Oral"); + } + + @Test + @DisplayName("getItemRoute should answer an error envelope when the lookup fails") + void getItemRoute_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.getItemRouteProviderServiceMapID(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getItemRoute(PSM_ID)); + } + + @Test + @DisplayName("getItemCategory should ask for every category when the caller sends zero") + void getItemCategory_shouldAskForEveryCategoryOnZero() { + when(itemService.getItemCategory(true, PSM_ID)).thenReturn(List.of(category(31, "Drugs"))); + + assertSuccessContaining(controller.getItemCategory(PSM_ID, 0), "Drugs"); + verify(itemService).getItemCategory(true, PSM_ID); + } + + @Test + @DisplayName("getItemCategory should ask for the live categories only for any other flag") + void getItemCategory_shouldAskForLiveCategoriesOtherwise() { + when(itemService.getItemCategory(false, PSM_ID)).thenReturn(List.of(category(31, "Drugs"))); + + assertSuccessContaining(controller.getItemCategory(PSM_ID, 1), "Drugs"); + verify(itemService).getItemCategory(false, PSM_ID); + } + + @Test + @DisplayName("getItemCategory should answer an error envelope when the lookup fails") + void getItemCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.getItemCategory(anyBoolean(), anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getItemCategory(PSM_ID, 0)); + } + + @Test + @DisplayName("createItemMaster should answer the items the service stored") + void createItemMaster_shouldAnswerStoredItems() { + when(itemService.addAllItemMaster(anyList())).thenReturn(List.of(item(ITEM_ID, "Paracetamol"))); + + assertSuccessContaining(controller.createItemMaster(new ItemMaster[] { item(null, "Paracetamol") }), + "Paracetamol"); + } + + @Test + @DisplayName("createItemMaster should answer an error envelope when the store fails") + void createItemMaster_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.addAllItemMaster(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createItemMaster(new ItemMaster[] { item(null, "Paracetamol") })); + } + + @Test + @DisplayName("getItemMaster should answer the catalogue of the provider") + void getItemMaster_shouldAnswerProviderCatalogue() { + when(itemService.getItemMaster(PSM_ID)).thenReturn(List.of(item(ITEM_ID, "Paracetamol"))); + + assertSuccessContaining(controller.getItemMaster(PSM_ID), "Paracetamol"); + } + + @Test + @DisplayName("getItemMaster should answer an error envelope when the lookup fails") + void getItemMaster_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.getItemMaster(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getItemMaster(PSM_ID)); + } + + @Test + @DisplayName("blockItemMaster should answer how many items the service blocked") + void blockItemMaster_shouldAnswerBlockedCount() { + when(itemService.blockItemMaster(ITEM_ID, true)).thenReturn(1); + + assertSuccessContaining(controller.blockItemMaster(ITEM_ID, true), "1"); + } + + @Test + @DisplayName("blockItemMaster should answer an error envelope when the block fails") + void blockItemMaster_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.blockItemMaster(anyInt(), anyBoolean())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.blockItemMaster(ITEM_ID, true)); + } + + @Test + @DisplayName("discontinueItemMaster should answer how many items the service discontinued") + void discontinueItemMaster_shouldAnswerDiscontinuedCount() { + when(itemService.discontinueItemMaster(ITEM_ID, true)).thenReturn(1); + + assertSuccessContaining(controller.discontinueItemMaster(ITEM_ID, true), "1"); + } + + @Test + @DisplayName("discontinueItemMaster should answer an error envelope when the change fails") + void discontinueItemMaster_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.discontinueItemMaster(anyInt(), anyBoolean())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.discontinueItemMaster(ITEM_ID, true)); + } + + @Test + @DisplayName("editItemMaster should copy the edited fields onto the stored item") + void editItemMaster_shouldCopyEditedFields() { + ItemMaster stored = item(ITEM_ID, "Paracetamol"); + ItemMaster request = item(ITEM_ID, "Paracetamol"); + request.setIsMedical(Boolean.TRUE); + request.setItemCategoryID(31); + request.setPharmacologyCategoryID(41); + request.setManufacturerID(51); + request.setIsScheduledDrug(Boolean.FALSE); + request.setItemDesc("Antipyretic"); + request.setSctCode("SCT-1"); + request.setSctTerm("Paracetamol 500mg"); + request.setModifiedBy("admin"); + when(itemService.getItemMasterByID(ITEM_ID)).thenReturn(stored); + when(itemService.createItemMaster(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editItemMaster(request), "Paracetamol"); + assertEquals("Antipyretic", stored.getItemDesc()); + assertEquals("SCT-1", stored.getSctCode()); + assertEquals(31, stored.getItemCategoryID()); + } + + @Test + @DisplayName("editItemMaster should answer an error envelope for an item that does not exist") + void editItemMaster_shouldAnswerErrorEnvelopeForUnknownItem() { + when(itemService.getItemMasterByID(ITEM_ID)).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.editItemMaster(item(ITEM_ID, "x")))); + } + + @Test + @DisplayName("configItemIssue should answer how many categories the service reconfigured") + void configItemIssue_shouldAnswerReconfiguredCount() { + when(itemService.updateItemIssueConfig(anyList())).thenReturn(2); + + assertSuccessContaining(controller.configItemIssue(new M_ItemCategory[] { category(31, "Drugs") }), "2"); + } + + @Test + @DisplayName("configItemIssue should answer an error envelope when the change fails") + void configItemIssue_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.updateItemIssueConfig(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.configItemIssue(new M_ItemCategory[] { category(31, "Drugs") })); + } + + @Test + @DisplayName("configexpiryalert should answer how many categories had their alert window changed") + void configexpiryalert_shouldAnswerChangedCount() { + when(itemService.updateExpiryAlert(anyList())).thenReturn(1); + + assertSuccessContaining(controller.configexpiryalert(new M_ItemCategory[] { category(31, "Drugs") }), "1"); + } + + @Test + @DisplayName("configexpiryalert should answer an error envelope when the change fails") + void configexpiryalert_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.updateExpiryAlert(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.configexpiryalert(new M_ItemCategory[] { category(31, "Drugs") })); + } + + @Test + @DisplayName("getItem should answer the items in the category") + void getItem_shouldAnswerItemsInCategory() { + when(itemService.getItemMasters(PSM_ID, 31)).thenReturn(List.of(item(ITEM_ID, "Paracetamol"))); + + assertSuccessContaining( + controller.getItem("{\"providerServiceMapID\":4001,\"itemCategoryID\":31}"), "Paracetamol"); + } + + @Test + @DisplayName("getItem should answer an error envelope when the lookup fails") + void getItem_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.getItemMasters(any(), any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getItem("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("createItemCategories should report success only when the service stored something") + void createItemCategories_shouldReportOutcome() { + when(itemService.createItemCategories(anyList())).thenReturn(1); + assertSuccessContaining(controller.createItemCategories(new M_ItemCategory[] { category(null, "Drugs") }), + "Item Categories saved successfully"); + + when(itemService.createItemCategories(anyList())).thenReturn(0); + assertSuccessContaining(controller.createItemCategories(new M_ItemCategory[] { category(null, "Drugs") }), + "Failed to store Item Categories"); + } + + @Test + @DisplayName("createItemCategories should answer an error envelope when the store fails") + void createItemCategories_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.createItemCategories(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createItemCategories(new M_ItemCategory[] { category(null, "Drugs") })); + } + + @Test + @DisplayName("editItemCategory should report success only when the service changed something") + void editItemCategory_shouldReportOutcome() { + when(itemService.editItemCategory(any())).thenReturn(1); + assertSuccessContaining(controller.editItemCategory(category(31, "Drugs")), + "Item Category updated successfully"); + + when(itemService.editItemCategory(any())).thenReturn(0); + assertSuccessContaining(controller.editItemCategory(category(31, "Drugs")), + "Failed to update Item Category"); + } + + @Test + @DisplayName("editItemCategory should answer an error envelope when the change fails") + void editItemCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.editItemCategory(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.editItemCategory(category(31, "Drugs"))); + } + + @Test + @DisplayName("blockItemCategory should report success only when the service blocked something") + void blockItemCategory_shouldReportOutcome() { + when(itemService.blockItemCategory(any())).thenReturn(1); + assertSuccessContaining(controller.blockItemCategory(category(31, "Drugs")), + "Item Category blocked successfully"); + + when(itemService.blockItemCategory(any())).thenReturn(0); + assertSuccessContaining(controller.blockItemCategory(category(31, "Drugs")), + "Failed to block Item Category"); + } + + @Test + @DisplayName("blockItemCategory should answer an error envelope when the block fails") + void blockItemCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.blockItemCategory(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.blockItemCategory(category(31, "Drugs"))); + } + + @Test + @DisplayName("createItemForms should report success only when the service stored something") + void createItemForms_shouldReportOutcome() { + when(itemService.createItemForms(anyList())).thenReturn(1); + assertSuccessContaining(controller.createItemForms(new M_ItemForm[] { form(null, "Tablet") }), + "Item Forms saved successfully"); + + when(itemService.createItemForms(anyList())).thenReturn(0); + assertSuccessContaining(controller.createItemForms(new M_ItemForm[] { form(null, "Tablet") }), + "Failed to store Item Forms"); + } + + @Test + @DisplayName("createItemForms should answer an error envelope when the store fails") + void createItemForms_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.createItemForms(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createItemForms(new M_ItemForm[] { form(null, "Tablet") })); + } + + @Test + @DisplayName("editItemForm should report success only when the service changed something") + void editItemForm_shouldReportOutcome() { + when(itemService.editItemForm(any())).thenReturn(1); + assertSuccessContaining(controller.editItemForm(form(11, "Tablet")), "Item Form updated successfully"); + + when(itemService.editItemForm(any())).thenReturn(0); + assertSuccessContaining(controller.editItemForm(form(11, "Tablet")), "Failed to update Item Form"); + } + + @Test + @DisplayName("editItemForm should answer an error envelope when the change fails") + void editItemForm_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.editItemForm(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.editItemForm(form(11, "Tablet"))); + } + + @Test + @DisplayName("blockItemForm should report success only when the service blocked something") + void blockItemForm_shouldReportOutcome() { + when(itemService.blockItemForm(any())).thenReturn(1); + assertSuccessContaining(controller.blockItemForm(form(11, "Tablet")), "Item Form blocked successfully"); + + when(itemService.blockItemForm(any())).thenReturn(0); + assertSuccessContaining(controller.blockItemForm(form(11, "Tablet")), "Failed to block Item Form"); + } + + @Test + @DisplayName("blockItemForm should answer an error envelope when the block fails") + void blockItemForm_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.blockItemForm(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.blockItemForm(form(11, "Tablet"))); + } + + @Test + @DisplayName("createRoutes should report success only when the service stored something") + void createRoutes_shouldReportOutcome() { + when(itemService.createRoutes(anyList())).thenReturn(1); + assertSuccessContaining(controller.createRoutes(new M_Route[] { route(null, "Oral") }), + "Routes saved successfully"); + + when(itemService.createRoutes(anyList())).thenReturn(0); + assertSuccessContaining(controller.createRoutes(new M_Route[] { route(null, "Oral") }), + "Failed to store Routes"); + } + + @Test + @DisplayName("createRoutes should answer an error envelope when the store fails") + void createRoutes_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.createRoutes(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createRoutes(new M_Route[] { route(null, "Oral") })); + } + + @Test + @DisplayName("editRoute should report success only when the service changed something") + void editRoute_shouldReportOutcome() { + when(itemService.editRoute(any())).thenReturn(1); + assertSuccessContaining(controller.editRoute(route(21, "Oral")), "Route data updated successfully"); + + when(itemService.editRoute(any())).thenReturn(0); + assertSuccessContaining(controller.editRoute(route(21, "Oral")), "Failed to update Route data"); + } + + @Test + @DisplayName("editRoute should answer an error envelope when the change fails") + void editRoute_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.editRoute(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.editRoute(route(21, "Oral"))); + } + + @Test + @DisplayName("blockRoute should report success only when the service blocked something") + void blockRoute_shouldReportOutcome() { + when(itemService.blockRoute(any())).thenReturn(1); + assertSuccessContaining(controller.blockRoute(route(21, "Oral")), "Route blocked successfully"); + + when(itemService.blockRoute(any())).thenReturn(0); + assertSuccessContaining(controller.blockRoute(route(21, "Oral")), "Failed to block Route"); + } + + @Test + @DisplayName("blockRoute should answer an error envelope when the block fails") + void blockRoute_shouldAnswerErrorEnvelopeOnFailure() { + when(itemService.blockRoute(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.blockRoute(route(21, "Oral"))); + } + + @Test + @DisplayName("checkCode should route the check to the master the caller names") + void checkCode_shouldRouteToNamedMaster() { + when(itemService.checkCodeCategory(any())).thenReturn(Boolean.TRUE); + when(itemService.checkCodeForm(any())).thenReturn(Boolean.FALSE); + when(itemService.checkCodeItem(any())).thenReturn(Boolean.TRUE); + when(itemService.checkCodeRoute(any())).thenReturn(Boolean.FALSE); + + assertSuccessContaining(controller.blockRoute(codeChecker("itemCategory", "CAT-1")), "true"); + assertSuccessContaining(controller.blockRoute(codeChecker("itemForm", "FRM-1")), "false"); + assertSuccessContaining(controller.blockRoute(codeChecker("itemMaster", "ITM-1")), "true"); + assertSuccessContaining(controller.blockRoute(codeChecker("route", "RTE-1")), "false"); + } + + @Test + @DisplayName("checkCode should refuse a master it does not know how to check") + void checkCode_shouldRefuseUnknownMaster() { + String response = controller.blockRoute(codeChecker("something-else", "X-1")); + + assertGenericFailure(response); + assertTrue(response.contains("Failed to check code for something-else"), response); + verify(itemService, never()).checkCodeCategory(any()); + } + + @Test + @DisplayName("checkCode should refuse a request that leaves out what to check") + void checkCode_shouldRefuseIncompleteRequest() { + CodeChecker incomplete = new CodeChecker(); + incomplete.setName("itemCategory"); + + String response = controller.blockRoute(incomplete); + + assertGenericFailure(response); + assertTrue(response.contains("Name, Code and ProviderServiceMapID is mandatory"), response); + } + + @Test + @DisplayName("checkCode should refuse a request that names no provider mapping") + void checkCode_shouldRefuseRequestWithoutProviderMapping() { + CodeChecker incomplete = codeChecker("itemCategory", "CAT-1"); + incomplete.setProviderServiceMapID(0); + + assertGenericFailure(controller.blockRoute(incomplete)); + } +} diff --git a/src/test/java/com/iemr/admin/controller/itemfacilitymapping/MItemFacilityMappingControllerTest.java b/src/test/java/com/iemr/admin/controller/itemfacilitymapping/MItemFacilityMappingControllerTest.java new file mode 100644 index 0000000..85bbc0a --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/itemfacilitymapping/MItemFacilityMappingControllerTest.java @@ -0,0 +1,259 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.itemfacilitymapping; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.items.ItemInStore; +import com.iemr.admin.data.itemfacilitymapping.M_itemfacilitymapping; +import com.iemr.admin.data.itemfacilitymapping.V_fetchItemFacilityMap; +import com.iemr.admin.service.itemfacilitymapping.M_itemfacilitymappingInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The item-facility endpoints decide which items each store may issue, so an + * unmapped item is one the store cannot dispense. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("MItemFacilityMappingController Test Suite") +class MItemFacilityMappingControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer FACILITY_ID = 501; + + @Mock + private M_itemfacilitymappingInter M_itemfacilitymappingInter; + + @InjectMocks + private MItemFacilityMappingController controller; + + private static M_itemfacilitymapping mapping(Integer id, Integer itemId) { + M_itemfacilitymapping mapping = new M_itemfacilitymapping(); + mapping.setItemStoreMapID(id); + mapping.setItemID(itemId); + mapping.setFacilityID(FACILITY_ID); + return mapping; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + private static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } + + @Test + @DisplayName("mapItemtoStrore should create one mapping per item the request lists") + void mapItemtoStrore_shouldCreateOneMappingPerItem() { + when(M_itemfacilitymappingInter.mapItemtoStore(anyList())) + .thenReturn(new ArrayList<>(List.of(mapping(9001, 101)))); + + String response = controller.mapItemtoStrore("[{\"facilityID\":501,\"mappingType\":\"Store\"," + + "\"providerServiceMapID\":4001,\"status\":\"Active\",\"createdBy\":\"admin\"," + + "\"itemID1\":[101,102]}]"); + + assertSuccessContaining(response, "9001"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(M_itemfacilitymappingInter).mapItemtoStore(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals(FACILITY_ID, captor.getValue().get(0).getFacilityID()); + } + + @Test + @DisplayName("mapItemtoStrore should answer an error envelope when no items are listed") + void mapItemtoStrore_shouldAnswerErrorEnvelopeWithoutItems() { + assertCodeException(controller.mapItemtoStrore("[{\"facilityID\":501}]")); + } + + @Test + @DisplayName("editItemtoStrore should copy the edits onto the stored mapping") + void editItemtoStrore_shouldCopyEdits() { + M_itemfacilitymapping stored = mapping(9001, 101); + when(M_itemfacilitymappingInter.editdata(9001)).thenReturn(stored); + when(M_itemfacilitymappingInter.saveEditedItem(stored)).thenReturn(stored); + + String response = controller.editItemtoStrore("{\"itemFacilityMapID\":9001,\"facilityID\":502," + + "\"itemID\":102,\"mappingType\":\"Sub Store\",\"providerServiceMapID\":4001," + + "\"status\":\"Inactive\"}"); + + assertSuccessContaining(response, "9001"); + assertEquals(502, stored.getFacilityID()); + assertEquals("Sub Store", stored.getMappingType()); + assertEquals("Inactive", stored.getStatus()); + } + + @Test + @DisplayName("editItemtoStrore should answer an error envelope for a mapping that does not exist") + void editItemtoStrore_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(M_itemfacilitymappingInter.editdata(9001)).thenReturn(null); + + assertCodeException(controller.editItemtoStrore("{\"itemFacilityMapID\":9001}")); + } + + @Test + @DisplayName("deleteItemtoStrore should mark the mapping deleted") + void deleteItemtoStrore_shouldMarkMappingDeleted() { + M_itemfacilitymapping stored = mapping(9001, 101); + when(M_itemfacilitymappingInter.editdata(9001)).thenReturn(stored); + when(M_itemfacilitymappingInter.saveEditedItem(stored)).thenReturn(stored); + + assertSuccessContaining( + controller.deleteItemtoStrore("{\"itemFacilityMapID\":9001,\"deleted\":true}"), "9001"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteItemtoStrore should answer an error envelope for a mapping that does not exist") + void deleteItemtoStrore_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(M_itemfacilitymappingInter.editdata(9001)).thenReturn(null); + + assertCodeException(controller.deleteItemtoStrore("{\"itemFacilityMapID\":9001,\"deleted\":true}")); + } + + @Test + @DisplayName("getSubStroreitem should answer the items mapped to the sub store") + void getSubStroreitem_shouldAnswerMappedItems() { + when(M_itemfacilitymappingInter.getsubitemforsubStote(PSM_ID, FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(mapping(9001, 101)))); + + assertSuccessContaining( + controller.getSubStroreitem("{\"providerServiceMapID\":4001,\"facilityID\":501}"), "9001"); + } + + @Test + @DisplayName("getSubStroreitem should answer an error envelope when the lookup fails") + void getSubStroreitem_shouldAnswerErrorEnvelopeOnFailure() { + when(M_itemfacilitymappingInter.getsubitemforsubStote(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getSubStroreitem("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getAllFacilityMappedData should answer the mappings of the provider") + void getAllFacilityMappedData_shouldAnswerProviderMappings() { + V_fetchItemFacilityMap view = new V_fetchItemFacilityMap(); + view.setItemFacilityMapID(9001); + when(M_itemfacilitymappingInter.getAllFacilityMappedData(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(view))); + + assertSuccessContaining( + controller.getAllFacilityMappedData("{\"providerServiceMapID\":4001}"), "9001"); + } + + @Test + @DisplayName("getAllFacilityMappedData should answer an error envelope when the lookup fails") + void getAllFacilityMappedData_shouldAnswerErrorEnvelopeOnFailure() { + when(M_itemfacilitymappingInter.getAllFacilityMappedData(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllFacilityMappedData("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getItemMappingsByFacility should answer the mappings of the facility") + void getItemMappingsByFacility_shouldAnswerFacilityMappings() { + V_fetchItemFacilityMap view = new V_fetchItemFacilityMap(); + view.setItemFacilityMapID(9001); + when(M_itemfacilitymappingInter.getItemMappingsByFacilityID(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(view))); + + assertSuccessContaining(controller.getItemMappingsByFacility("{\"facilityID\":501}"), "9001"); + } + + @Test + @DisplayName("getItemMappingsByFacility should answer an error envelope when the lookup fails") + void getItemMappingsByFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(M_itemfacilitymappingInter.getItemMappingsByFacilityID(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getItemMappingsByFacility("{\"facilityID\":501}")); + } + + @Test + @DisplayName("getItemFromStoreID should answer the items the store holds") + void getItemFromStoreID_shouldAnswerStoreItems() { + when(M_itemfacilitymappingInter.getItemMastersFromStoreID(FACILITY_ID)) + .thenReturn(List.of(new ItemInStore(FACILITY_ID, 101, "Paracetamol", 25L))); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(controller.getItemFromStoreID(FACILITY_ID))); + } + + @Test + @DisplayName("getItemFromStoreID should answer an error envelope when the lookup fails") + void getItemFromStoreID_shouldAnswerErrorEnvelopeOnFailure() { + when(M_itemfacilitymappingInter.getItemMastersFromStoreID(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getItemFromStoreID(FACILITY_ID)); + } + + @Test + @DisplayName("deleteItemStoreMapping should answer how many mappings the service released") + void deleteItemStoreMapping_shouldAnswerReleasedCount() { + M_itemfacilitymapping request = mapping(9001, 101); + when(M_itemfacilitymappingInter.deleteItemStoreMapping(request)).thenReturn(1); + + assertSuccessContaining(controller.deleteItemStoreMapping(request), "1"); + } + + @Test + @DisplayName("deleteItemStoreMapping should answer an error envelope when the release fails") + void deleteItemStoreMapping_shouldAnswerErrorEnvelopeOnFailure() { + when(M_itemfacilitymappingInter.deleteItemStoreMapping(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.deleteItemStoreMapping(mapping(9001, 101))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/labmodule/LabModuleControllerTest.java b/src/test/java/com/iemr/admin/controller/labmodule/LabModuleControllerTest.java new file mode 100644 index 0000000..0db8a08 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/labmodule/LabModuleControllerTest.java @@ -0,0 +1,356 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.labmodule; + +import org.json.JSONObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.sevice.labmodule.MastersCreationServiceImpl; +import com.iemr.admin.sevice.labmodule.MastersFetchingServiceImpl; +import com.iemr.admin.sevice.labmodule.MastersMappingServiceImpl; +import com.iemr.admin.sevice.labmodule.MastersStatusUpdateImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The lab module endpoints let a provider admin define the diagnostic tests and + * their result components, and refuse a request that does not name what to act + * on. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("LabModuleController Test Suite") +class LabModuleControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer PROCEDURE_ID = 71; + private static final Integer COMPONENT_ID = 81; + private static final String PROCEDURE_JSON = "{\"procedureID\":71,\"procedureName\":\"Haemoglobin\"}"; + + @Mock + private MastersCreationServiceImpl mastersCreationServiceImpl; + + @Mock + private MastersMappingServiceImpl mastersMappingServiceImpl; + + @Mock + private MastersFetchingServiceImpl mastersFetchingServiceImpl; + + @Mock + private MastersStatusUpdateImpl mastersStatusUpdateImpl; + + private LabModuleController controller; + + @BeforeEach + void setUp() { + controller = new LabModuleController(); + controller.setMastersCreationServiceImpl(mastersCreationServiceImpl); + controller.setMastersMappingServiceImpl(mastersMappingServiceImpl); + controller.setMastersFetchingServiceImpl(mastersFetchingServiceImpl); + controller.setMastersStatusUpdateImpl(mastersStatusUpdateImpl); + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertInvalidRequest(String response) { + assertEquals(OutputResponse.USERID_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("createProcedureMaster should answer the procedure the service stored") + void createProcedureMaster_shouldAnswerStoredProcedure() throws Exception { + when(mastersCreationServiceImpl.createProcedureMaster(anyString())).thenReturn(PROCEDURE_JSON); + + assertSuccessContaining(controller.createProcedureMaster(PROCEDURE_JSON), "Haemoglobin"); + } + + @Test + @DisplayName("createProcedureMaster should stay at its default when the service stored nothing") + void createProcedureMaster_shouldStayAtDefaultWhenNothingStored() throws Exception { + when(mastersCreationServiceImpl.createProcedureMaster(anyString())).thenReturn(null); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.createProcedureMaster("{}"))); + } + + @Test + @DisplayName("createProcedureMaster should answer an error envelope when the store fails") + void createProcedureMaster_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(mastersCreationServiceImpl.createProcedureMaster(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.createProcedureMaster("{}"))); + } + + @Test + @DisplayName("createComponentMaster should answer the component the service stored") + void createComponentMaster_shouldAnswerStoredComponent() throws Exception { + when(mastersCreationServiceImpl.createComponentMaster(anyString())) + .thenReturn("{\"testComponentName\":\"Haemoglobin count\"}"); + + assertSuccessContaining(controller.createComponentMaster("{}"), "Haemoglobin count"); + } + + @Test + @DisplayName("createComponentMaster should answer an error envelope when the store fails") + void createComponentMaster_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(mastersCreationServiceImpl.createComponentMaster(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.createComponentMaster("{}"))); + } + + @Test + @DisplayName("createProcedureComponentMapping should answer the mapping the service stored") + void createMapping_shouldAnswerStoredMapping() throws Exception { + when(mastersMappingServiceImpl.createProcedureComponentMapping(anyString())) + .thenReturn("[{\"procedureName\":\"Haemoglobin\"}]"); + + assertSuccessContaining(controller.createProcedureComponentMapping("{}"), "Haemoglobin"); + } + + @Test + @DisplayName("createProcedureComponentMapping should refuse a request that maps no components") + void createMapping_shouldRefuseEmptyRequest() throws Exception { + when(mastersMappingServiceImpl.createProcedureComponentMapping(anyString())).thenReturn("1"); + + String response = controller.createProcedureComponentMapping("{}"); + + assertInvalidRequest(response); + assertTrue(response.contains("Invalid request."), response); + } + + @Test + @DisplayName("createProcedureComponentMapping should report a mapping the service could not store") + void createMapping_shouldReportUnstoredMapping() throws Exception { + when(mastersMappingServiceImpl.createProcedureComponentMapping(anyString())).thenReturn(null); + + String response = controller.createProcedureComponentMapping("{}"); + + assertInvalidRequest(response); + assertTrue(response.contains("Error while saving the data"), response); + } + + @Test + @DisplayName("createProcedureComponentMapping should answer an error envelope when the store fails") + void createMapping_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(mastersMappingServiceImpl.createProcedureComponentMapping(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.createProcedureComponentMapping("{}"))); + } + + @Test + @DisplayName("the fetch endpoints should each answer what their own service publishes") + void fetchEndpoints_shouldAnswerTheirOwnService() throws Exception { + when(mastersFetchingServiceImpl.getProcedureMaster(PSM_ID)).thenReturn("[{\"a\":1}]"); + when(mastersFetchingServiceImpl.getComponentMaster(PSM_ID)).thenReturn("[{\"b\":2}]"); + when(mastersFetchingServiceImpl.getProcedureMasterDelFalse(PSM_ID)).thenReturn("[{\"c\":3}]"); + when(mastersFetchingServiceImpl.getComponentMasterDelFalse(PSM_ID)).thenReturn("[{\"d\":4}]"); + when(mastersFetchingServiceImpl.getProcCompMappingDelFalse(PSM_ID)).thenReturn("[{\"e\":5}]"); + when(mastersFetchingServiceImpl.getProcCompMappingForProcedureID(PROCEDURE_ID)).thenReturn("[{\"f\":6}]"); + + assertSuccessContaining(controller.fetchProcedureMaster(PSM_ID), "\"a\":1"); + assertSuccessContaining(controller.fetchComponentMaster(PSM_ID), "\"b\":2"); + assertSuccessContaining(controller.fetchProcedureMasterDelFalse(PSM_ID), "\"c\":3"); + assertSuccessContaining(controller.fetchComponentMasterDelFalse(PSM_ID), "\"d\":4"); + assertSuccessContaining(controller.fetchProcCompMappingDelFalse(PSM_ID), "\"e\":5"); + assertSuccessContaining(controller.fetchProcCompMappingForSingleProcedure(PROCEDURE_ID), "\"f\":6"); + } + + @Test + @DisplayName("the fetch endpoints should refuse a request that names no record to read") + void fetchEndpoints_shouldRefuseRequestWithoutRecord() throws Exception { + assertInvalidRequest(controller.fetchProcedureMaster(0)); + assertInvalidRequest(controller.fetchComponentMaster(0)); + assertInvalidRequest(controller.fetchProcedureMasterDelFalse(0)); + assertInvalidRequest(controller.fetchComponentMasterDelFalse(0)); + assertInvalidRequest(controller.fetchProcCompMappingDelFalse(0)); + assertInvalidRequest(controller.fetchProcCompMappingForSingleProcedure(0)); + assertInvalidRequest(controller.fetchComponentDetailsForComponentID(0)); + verify(mastersFetchingServiceImpl, never()).getProcedureMaster(anyInt()); + } + + @Test + @DisplayName("fetchProcedureMaster should answer an error envelope when the lookup fails") + void fetchProcedureMaster_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(mastersFetchingServiceImpl.getProcedureMaster(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.fetchProcedureMaster(PSM_ID))); + } + + @Test + @DisplayName("fetchComponentDetailsForComponentID should answer the component the service publishes") + void fetchComponentDetails_shouldAnswerPublishedComponent() throws Exception { + when(mastersFetchingServiceImpl.getComponentDetailsForComponentID(COMPONENT_ID)) + .thenReturn("{\"testComponentName\":\"Haemoglobin count\"}"); + + assertSuccessContaining(controller.fetchComponentDetailsForComponentID(COMPONENT_ID), + "Haemoglobin count"); + } + + @Test + @DisplayName("fetchComponentDetailsForComponentID should say so when the component is not on record") + void fetchComponentDetails_shouldSaySoForUnknownComponent() throws Exception { + when(mastersFetchingServiceImpl.getComponentDetailsForComponentID(COMPONENT_ID)).thenReturn(null); + + assertSuccessContaining(controller.fetchComponentDetailsForComponentID(COMPONENT_ID), + "Component Details not found in Database."); + } + + @Test + @DisplayName("updateProcedureStatus should answer the procedure once its status has changed") + void updateProcedureStatus_shouldAnswerChangedProcedure() throws Exception { + when(mastersStatusUpdateImpl.updateProcedureStatus(PROCEDURE_ID, true)).thenReturn(PROCEDURE_JSON); + + assertSuccessContaining( + controller.updateProcedureStatus("{\"procedureID\":71,\"deleted\":true}"), "Haemoglobin"); + } + + @Test + @DisplayName("updateProcedureStatus should report a status the service could not change") + void updateProcedureStatus_shouldReportUnchangedStatus() throws Exception { + when(mastersStatusUpdateImpl.updateProcedureStatus(anyInt(), anyBoolean())).thenReturn(null); + + String response = controller.updateProcedureStatus("{\"procedureID\":71,\"deleted\":true}"); + + assertInvalidRequest(response); + assertTrue(response.contains("Failed to update the status"), response); + } + + @Test + @DisplayName("updateProcedureStatus should refuse a request that names no procedure") + void updateProcedureStatus_shouldRefuseRequestWithoutProcedure() { + assertInvalidRequest(controller.updateProcedureStatus("{\"deleted\":true}")); + assertInvalidRequest(controller.updateProcedureStatus("{\"procedureID\":0,\"deleted\":true}")); + } + + @Test + @DisplayName("updateProcedureStatus should answer an error envelope for a body it cannot read") + void updateProcedureStatus_shouldAnswerErrorEnvelopeForMalformedBody() { + assertEquals(OutputResponse.OBJECT_FAILURE, statusCodeOf(controller.updateProcedureStatus("not json"))); + } + + @Test + @DisplayName("updateComponentStatus should answer the component once its status has changed") + void updateComponentStatus_shouldAnswerChangedComponent() throws Exception { + when(mastersStatusUpdateImpl.updateComponentStatus(COMPONENT_ID, true)) + .thenReturn("{\"testComponentName\":\"Haemoglobin count\"}"); + + assertSuccessContaining( + controller.updateComponentStatus("{\"componentID\":81,\"deleted\":true}"), "Haemoglobin count"); + } + + @Test + @DisplayName("updateComponentStatus should report a status the service could not change") + void updateComponentStatus_shouldReportUnchangedStatus() throws Exception { + when(mastersStatusUpdateImpl.updateComponentStatus(anyInt(), anyBoolean())).thenReturn(null); + + assertInvalidRequest(controller.updateComponentStatus("{\"componentID\":81,\"deleted\":true}")); + } + + @Test + @DisplayName("updateComponentStatus should refuse a request that names no component") + void updateComponentStatus_shouldRefuseRequestWithoutComponent() { + assertInvalidRequest(controller.updateComponentStatus("{\"deleted\":true}")); + } + + @Test + @DisplayName("updateProcedureMaster should answer the procedure once the edit lands") + void updateProcedureMaster_shouldAnswerEditedProcedure() throws Exception { + when(mastersStatusUpdateImpl.updateProcedureMaster(anyString())).thenReturn(PROCEDURE_JSON); + + assertSuccessContaining(controller.updateProcedureMaster(PROCEDURE_JSON), "Haemoglobin"); + } + + @Test + @DisplayName("updateProcedureMaster should report an edit the service could not apply") + void updateProcedureMaster_shouldReportUnappliedEdit() throws Exception { + when(mastersStatusUpdateImpl.updateProcedureMaster(anyString())).thenReturn(null); + + String response = controller.updateProcedureMaster("{}"); + + assertInvalidRequest(response); + assertTrue(response.contains("Failed to update procedure details"), response); + } + + @Test + @DisplayName("updateProcedureMaster should answer an error envelope when the edit fails") + void updateProcedureMaster_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(mastersStatusUpdateImpl.updateProcedureMaster(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.updateProcedureMaster("{}"))); + } + + @Test + @DisplayName("updateComponentMaster should answer the component once the edit lands") + void updateComponentMaster_shouldAnswerEditedComponent() throws Exception { + when(mastersStatusUpdateImpl.updateComponentMaster(anyString())) + .thenReturn("{\"testComponentName\":\"Haemoglobin count\"}"); + + assertSuccessContaining(controller.updateComponentMaster("{}"), "Haemoglobin count"); + } + + @Test + @DisplayName("updateComponentMaster should report an edit the service could not apply") + void updateComponentMaster_shouldReportUnappliedEdit() throws Exception { + when(mastersStatusUpdateImpl.updateComponentMaster(anyString())).thenReturn(null); + + String response = controller.updateComponentMaster("{}"); + + assertInvalidRequest(response); + assertTrue(response.contains("Failed to update component details"), response); + } + + @Test + @DisplayName("updateComponentMaster should answer an error envelope when the edit fails") + void updateComponentMaster_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(mastersStatusUpdateImpl.updateComponentMaster(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.updateComponentMaster("{}"))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/labmodule/SmartDiagnosticsControllerTest.java b/src/test/java/com/iemr/admin/controller/labmodule/SmartDiagnosticsControllerTest.java new file mode 100644 index 0000000..2e181c9 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/labmodule/SmartDiagnosticsControllerTest.java @@ -0,0 +1,103 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.labmodule; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.admin.sevice.labmodule.IOTService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +/** + * The smart diagnostics endpoints tell the client which device tests exist and + * where the rapid screening device is reachable. + */ +@ExtendWith(MockitoExtension.class) +@DisplayName("SmartDiagnosticsController Test Suite") +class SmartDiagnosticsControllerTest { + + @Mock + private IOTService iotService; + + @InjectMocks + private SmartDiagnosticsController controller; + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + @Test + @DisplayName("getIOTProcedure should answer the device tests the service publishes") + void getIOTProcedure_shouldAnswerDeviceTests() { + when(iotService.getIOTProcedure()).thenReturn("[{\"calibrationCode\":\"HB\"}]"); + + String response = controller.getIOTProcedure(); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("HB"), response); + } + + @Test + @DisplayName("getIOTProcedure should answer an error envelope when the lookup fails") + void getIOTProcedure_shouldAnswerErrorEnvelopeOnFailure() { + when(iotService.getIOTProcedure()).thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getIOTProcedure())); + } + + @Test + @DisplayName("getIOTComponent should answer the device components the service publishes") + void getIOTComponent_shouldAnswerDeviceComponents() { + when(iotService.getIOTComponent()).thenReturn("[{\"iotComponentID\":1}]"); + + String response = controller.getIOTComponent(); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("iotComponentID"), response); + } + + @Test + @DisplayName("getIOTComponent should answer an error envelope when the lookup fails") + void getIOTComponent_shouldAnswerErrorEnvelopeOnFailure() { + when(iotService.getIOTComponent()).thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getIOTComponent())); + } + + @Test + @DisplayName("getBiologicalScreeningDeviceUrl should answer the configured device address") + void getBiologicalScreeningDeviceUrl_shouldAnswerConfiguredAddress() { + ReflectionTestUtils.setField(controller, "biologicalScreeningDeviceUrl", "http://device.local:9000"); + + assertEquals("http://device.local:9000", controller.getBiologicalScreeningDeviceUrl()); + } +} diff --git a/src/test/java/com/iemr/admin/controller/locationmaster/LocationMasterControllerTest.java b/src/test/java/com/iemr/admin/controller/locationmaster/LocationMasterControllerTest.java new file mode 100644 index 0000000..fd267e5 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/locationmaster/LocationMasterControllerTest.java @@ -0,0 +1,425 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.locationmaster; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.M_District; +import com.iemr.admin.data.locationmaster.M_ProviderServiceAddMapping; +import com.iemr.admin.data.locationmaster.Showofficedetails; +import com.iemr.admin.data.locationmaster.StateServiceMapping1; +import com.iemr.admin.service.locationmaster.LocationMasterServiceInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The location endpoints keep the office addresses a provider works out of, and + * choose between a national and a state-scoped lookup depending on the service + * line. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("LocationMasterController Test Suite") +class LocationMasterControllerTest { + + private static final Integer PROVIDER_ID = 77; + private static final Integer PSM_ID = 4001; + + @Mock + private LocationMasterServiceInter locationMasterServiceInter; + + @InjectMocks + private LocationMasterController controller; + + private static StateServiceMapping1 mapping(Integer psmId) { + return new StateServiceMapping1(psmId); + } + + private static Showofficedetails office(String name) { + Showofficedetails office = new Showofficedetails(); + office.setLocationName(name); + office.setProviderServiceMapID(PSM_ID); + return office; + } + + private static M_ProviderServiceAddMapping address(Integer id, String name) { + M_ProviderServiceAddMapping address = new M_ProviderServiceAddMapping(); + address.setpSAddMapID(id); + address.setLocationName(name); + return address; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + private static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } + + @Test + @DisplayName("getAllRole2 should resolve the mapping before reading its addresses") + void getAllRole2_shouldResolveMappingFirst() { + when(locationMasterServiceInter.getAllByMapId2(PROVIDER_ID, 29, 3)) + .thenReturn(new ArrayList<>(List.of(mapping(PSM_ID)))); + when(locationMasterServiceInter.getlocationByMapid(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(address(51, "Bengaluru Office")))); + + assertSuccessContaining(controller.getAllRole2( + "{\"serviceProviderID\":77,\"stateID\":29,\"serviceID\":3}"), "Bengaluru Office"); + } + + @Test + @DisplayName("getAllRole2 should fall back to no mapping when the provider has none") + void getAllRole2_shouldFallBackWithoutMapping() { + when(locationMasterServiceInter.getAllByMapId2(any(), any(), any())).thenReturn(new ArrayList<>()); + when(locationMasterServiceInter.getlocationByMapid(0)).thenReturn(new ArrayList<>()); + + controller.getAllRole2("{\"serviceProviderID\":77}"); + + verify(locationMasterServiceInter).getlocationByMapid(0); + } + + @Test + @DisplayName("getAllRole2 should answer an error envelope when the lookup fails") + void getAllRole2_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getAllByMapId2(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllRole2("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getAlllocation should use the national lookup for a national service line") + void getAlllocation_shouldUseNationalLookup() { + when(locationMasterServiceInter.getAllByMapId3(PROVIDER_ID, 3)) + .thenReturn(new ArrayList<>(List.of(mapping(PSM_ID)))); + when(locationMasterServiceInter.getlocationByMapid2(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(office("Bengaluru Office")))); + + assertSuccessContaining(controller.getAlllocation( + "{\"serviceProviderID\":77,\"serviceID\":3,\"isNational\":true}"), "Bengaluru Office"); + verify(locationMasterServiceInter).getAllByMapId3(PROVIDER_ID, 3); + } + + @Test + @DisplayName("getAlllocation should use the state lookup for a state-scoped service line") + void getAlllocation_shouldUseStateLookup() { + when(locationMasterServiceInter.getAllByMapId2(PROVIDER_ID, 29, 3)) + .thenReturn(new ArrayList<>(List.of(mapping(PSM_ID)))); + when(locationMasterServiceInter.getlocationByMapid2(PSM_ID)).thenReturn(new ArrayList<>()); + + controller.getAlllocation("{\"serviceProviderID\":77,\"stateID\":29,\"serviceID\":3,\"isNational\":false}"); + + verify(locationMasterServiceInter).getAllByMapId2(PROVIDER_ID, 29, 3); + } + + @Test + @DisplayName("getAlllocation should narrow to the district when the caller names one") + void getAlllocation_shouldNarrowToDistrict() { + when(locationMasterServiceInter.getAllByMapId2(any(), any(), any())) + .thenReturn(new ArrayList<>(List.of(mapping(PSM_ID)))); + when(locationMasterServiceInter.getlocationByMapid4(PSM_ID, 301)) + .thenReturn(new ArrayList<>(List.of(office("Bengaluru Office")))); + + assertSuccessContaining(controller.getAlllocation("{\"serviceProviderID\":77,\"stateID\":29," + + "\"serviceID\":3,\"districtID\":301,\"isNational\":false}"), "Bengaluru Office"); + verify(locationMasterServiceInter, never()).getlocationByMapid2(anyInt()); + } + + @Test + @DisplayName("getAlllocation should answer an error envelope when the national flag is missing") + void getAlllocation_shouldAnswerErrorEnvelopeWithoutNationalFlag() { + assertCodeException(controller.getAlllocation("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getAlllocationNew should read the addresses straight off the mapping the caller names") + void getAlllocationNew_shouldReadFromNamedMapping() { + when(locationMasterServiceInter.getlocationByMapid2(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(office("Bengaluru Office")))); + + assertSuccessContaining(controller.getAlllocationNew("{\"providerServiceMapID\":4001}"), + "Bengaluru Office"); + } + + @Test + @DisplayName("getAlllocationNew should narrow to the district when the caller names one") + void getAlllocationNew_shouldNarrowToDistrict() { + when(locationMasterServiceInter.getlocationByMapid4(PSM_ID, 301)) + .thenReturn(new ArrayList<>(List.of(office("Bengaluru Office")))); + + assertSuccessContaining( + controller.getAlllocationNew("{\"providerServiceMapID\":4001,\"districtID\":301}"), + "Bengaluru Office"); + } + + @Test + @DisplayName("getAlllocationNew should answer an error envelope when the lookup fails") + void getAlllocationNew_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getlocationByMapid2(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAlllocationNew("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("searchRole should answer the states the provider serves") + void searchRole_shouldAnswerServedStates() { + when(locationMasterServiceInter.getStateByServiceProviderId(PROVIDER_ID)) + .thenReturn(new ArrayList<>(List.of(mapping(PSM_ID)))); + + assertSuccessContaining(controller.searchRole("{\"serviceProviderID\":77}"), "4001"); + } + + @Test + @DisplayName("searchRole should answer an error envelope when the lookup fails") + void searchRole_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getStateByServiceProviderId(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchRole("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getService should answer the service lines the provider runs in the state") + void getService_shouldAnswerServiceLinesInState() { + when(locationMasterServiceInter.getServiceByServiceProviderIdAndStateId(PROVIDER_ID, 29)) + .thenReturn(new ArrayList<>(List.of(mapping(PSM_ID)))); + + assertSuccessContaining(controller.getService("{\"serviceProviderID\":77,\"stateID\":29}"), "4001"); + } + + @Test + @DisplayName("getService should answer an error envelope when the lookup fails") + void getService_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getServiceByServiceProviderIdAndStateId(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getService("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getAllDistrict should answer the districts of the state") + void getAllDistrict_shouldAnswerStateDistricts() { + M_District district = new M_District(); + district.setDistrictID(301); + district.setDistrictName("Bengaluru Urban"); + when(locationMasterServiceInter.getAllDistrictByStateId(29)) + .thenReturn(new ArrayList<>(List.of(district))); + + assertSuccessContaining(controller.getAllDistrict("{\"stateID\":29}"), "Bengaluru Urban"); + } + + @Test + @DisplayName("getAllDistrict should answer an error envelope when the lookup fails") + void getAllDistrict_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getAllDistrictByStateId(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllDistrict("{\"stateID\":29}")); + } + + @Test + @DisplayName("getAllRole should create one address per mapping the request names") + void getAllRole_shouldCreateOneAddressPerMapping() { + when(locationMasterServiceInter.addlocation(anyList())) + .thenReturn(new ArrayList<>(List.of(address(51, "Bengaluru Office")))); + + String response = controller.getAllRole("{\"providerServiceMapID\":[4001,4002]," + + "\"address\":\"Main Road\",\"locationName\":\"Bengaluru Office\",\"districtID\":301," + + "\"createdBy\":\"admin\",\"abdmFacilityId\":\"ABDM-1\"," + + "\"abdmFacilityName\":\"Bengaluru PHC\"}"); + + assertSuccessContaining(response, "Bengaluru Office"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(locationMasterServiceInter).addlocation(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals("ABDM-1", captor.getValue().get(0).getAbdmFacilityId()); + } + + @Test + @DisplayName("getAllRole should answer an error envelope when no mappings are named") + void getAllRole_shouldAnswerErrorEnvelopeWithoutMappings() { + assertCodeException(controller.getAllRole("{\"address\":\"Main Road\"}")); + } + + @Test + @DisplayName("geteditLocation should copy the edits onto the stored address") + void geteditLocation_shouldCopyEdits() { + M_ProviderServiceAddMapping stored = address(51, "old name"); + when(locationMasterServiceInter.editData(51)).thenReturn(stored); + when(locationMasterServiceInter.saveEditData(stored)).thenReturn(stored); + + String response = controller.geteditLocation("{\"pSAddMapID\":51,\"providerServiceMapID\":4001," + + "\"districtID\":301,\"address\":\"Main Road\",\"locationName\":\"Bengaluru Office\"," + + "\"abdmFacilityId\":\"ABDM-1\",\"abdmFacilityName\":\"Bengaluru PHC\"}"); + + assertSuccessContaining(response, "Bengaluru Office"); + assertEquals("Main Road", stored.getAddress()); + assertEquals("ABDM-1", stored.getAbdmFacilityId()); + } + + @Test + @DisplayName("geteditLocation should answer an error envelope for an address that does not exist") + void geteditLocation_shouldAnswerErrorEnvelopeForUnknownAddress() { + when(locationMasterServiceInter.editData(51)).thenReturn(null); + + assertCodeException(controller.geteditLocation("{\"pSAddMapID\":51}")); + } + + @Test + @DisplayName("deleteLocation should mark the address deleted") + void deleteLocation_shouldMarkAddressDeleted() { + M_ProviderServiceAddMapping stored = address(51, "Bengaluru Office"); + when(locationMasterServiceInter.editData(51)).thenReturn(stored); + when(locationMasterServiceInter.saveEditData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteLocation("{\"pSAddMapID\":51,\"deleted\":true}"), + "Bengaluru Office"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteLocation should answer an error envelope for an address that does not exist") + void deleteLocation_shouldAnswerErrorEnvelopeForUnknownAddress() { + when(locationMasterServiceInter.editData(51)).thenReturn(null); + + assertCodeException(controller.deleteLocation("{\"pSAddMapID\":51,\"deleted\":true}")); + } + + @Test + @DisplayName("getLocationByServiceID should gather the addresses of every mapping on the service line") + void getLocationByServiceID_shouldGatherAddressesAcrossMappings() { + when(locationMasterServiceInter.getLocationByServiceId(PROVIDER_ID, 3)) + .thenReturn(new ArrayList<>(List.of(mapping(4001), mapping(4002)))); + when(locationMasterServiceInter.getlocationByMapid1(any())) + .thenReturn(new ArrayList<>(List.of(office("Bengaluru Office")))); + + assertSuccessContaining( + controller.getLocationByServiceID("{\"serviceProviderID\":77,\"serviceID\":3}"), + "Bengaluru Office"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(locationMasterServiceInter).getlocationByMapid1(captor.capture()); + assertEquals(List.of(4001, 4002), captor.getValue()); + } + + @Test + @DisplayName("getLocationByServiceID should answer an error envelope when the lookup fails") + void getLocationByServiceID_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getLocationByServiceId(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getLocationByServiceID("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getLocationByStateId should gather the addresses of every mapping in the state") + void getLocationByStateId_shouldGatherAddressesAcrossMappings() { + when(locationMasterServiceInter.getLocationBySateID(PROVIDER_ID, 29)) + .thenReturn(new ArrayList<>(List.of(mapping(4001)))); + when(locationMasterServiceInter.getlocationByMapid1(any())) + .thenReturn(new ArrayList<>(List.of(office("Bengaluru Office")))); + + assertSuccessContaining( + controller.getLocationByStateId("{\"serviceProviderID\":77,\"stateID\":29}"), "Bengaluru Office"); + } + + @Test + @DisplayName("getLocationByStateId should answer an error envelope when the lookup fails") + void getLocationByStateId_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getLocationBySateID(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getLocationByStateId("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getOfficeNameByMapId should ask for one office per mapping the request lists") + void getOfficeNameByMapId_shouldAskForOneOfficePerMapping() { + when(locationMasterServiceInter.getOfficeName(any())) + .thenReturn(new ArrayList<>(List.of(office("Bengaluru Office")))); + + assertSuccessContaining( + controller.getOfficeNameByMapId("{\"providerServiceMapID\":[4001,4002]}"), "Bengaluru Office"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(ArrayList.class); + verify(locationMasterServiceInter).getOfficeName(captor.capture()); + assertEquals(2, captor.getValue().size()); + } + + @Test + @DisplayName("getOfficeNameByMapId should answer an error envelope when no mappings are listed") + void getOfficeNameByMapId_shouldAnswerErrorEnvelopeWithoutMappings() { + assertCodeException(controller.getOfficeNameByMapId("{}")); + } + + @Test + @DisplayName("getStatesByServiceID should answer the states the service line runs in") + void getStatesByServiceID_shouldAnswerServedStates() { + when(locationMasterServiceInter.getStatesByServiceId(3, PROVIDER_ID)) + .thenReturn(new ArrayList<>(List.of(mapping(PSM_ID)))); + + assertSuccessContaining( + controller.getStatesByServiceID("{\"serviceID\":3,\"serviceProviderID\":77}"), "4001"); + } + + @Test + @DisplayName("getStatesByServiceID should answer an error envelope when the lookup fails") + void getStatesByServiceID_shouldAnswerErrorEnvelopeOnFailure() { + when(locationMasterServiceInter.getStatesByServiceId(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getStatesByServiceID("{\"serviceID\":3}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/manufacturer/ManufacturerControllerTest.java b/src/test/java/com/iemr/admin/controller/manufacturer/ManufacturerControllerTest.java new file mode 100644 index 0000000..f0f3477 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/manufacturer/ManufacturerControllerTest.java @@ -0,0 +1,169 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.manufacturer; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.manufacturer.M_Manufacturer; +import com.iemr.admin.service.manufacturer.ManufacturerInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** The manufacturer master endpoints keep the manufacturer catalogue an inventory operator picks from. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ManufacturerController Test Suite") +class ManufacturerControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private ManufacturerInter manufacturerInter; + + @InjectMocks + private ManufacturerController controller; + + private static M_Manufacturer record(Integer id, String name) { + M_Manufacturer record = new M_Manufacturer(); + record.setManufacturerID(id); + record.setManufacturerName(name); + return record; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("createManufacturer should answer the records the service stored") + void create_shouldAnswerStoredRecords() { + when(manufacturerInter.createManufacturer(anyList())).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "Cipla")))); + + assertSuccessContaining(controller.createManufacturer("[{\"manufacturerCode\":\"C-1\"}]"), "Cipla"); + } + + @Test + @DisplayName("createManufacturer should answer an error envelope when the store fails") + void create_shouldAnswerErrorEnvelopeOnFailure() { + when(manufacturerInter.createManufacturer(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createManufacturer("[{}]")); + } + + @Test + @DisplayName("getManufacturer should answer the records of the provider") + void get_shouldAnswerProviderRecords() { + when(manufacturerInter.createManufacturer(PSM_ID)).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "Cipla")))); + + assertSuccessContaining(controller.getManufacturer("{\"providerServiceMapID\":4001}"), "Cipla"); + } + + @Test + @DisplayName("getManufacturer should answer an error envelope when the lookup fails") + void get_shouldAnswerErrorEnvelopeOnFailure() { + when(manufacturerInter.createManufacturer(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getManufacturer("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("editManufacturer should copy the edits onto the stored record") + void edit_shouldCopyEdits() { + M_Manufacturer stored = record(RECORD_ID, "Cipla"); + when(manufacturerInter.editManufacturer(RECORD_ID)).thenReturn(stored); + when(manufacturerInter.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editManufacturer("{\"manufacturerID\":11,\"manufacturerDesc\":\"Generic maker\",\"contactPerson\":\"Asha\",\"modifiedBy\":\"admin\"}"), "Cipla"); + assertEquals("Generic maker", stored.getManufacturerDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("editManufacturer should answer an error envelope for a record that does not exist") + void edit_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(manufacturerInter.editManufacturer(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.editManufacturer("{\"manufacturerID\":11,\"manufacturerDesc\":\"Generic maker\",\"contactPerson\":\"Asha\",\"modifiedBy\":\"admin\"}"))); + } + + @Test + @DisplayName("deleteManufacturer should mark the record deleted") + void delete_shouldMarkRecordDeleted() { + M_Manufacturer stored = record(RECORD_ID, "Cipla"); + when(manufacturerInter.editManufacturer(RECORD_ID)).thenReturn(stored); + when(manufacturerInter.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteManufacturer("{\"manufacturerID\":11,\"deleted\":true}"), "Cipla"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteManufacturer should answer an error envelope for a record that does not exist") + void delete_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(manufacturerInter.editManufacturer(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.deleteManufacturer("{\"manufacturerID\":11,\"deleted\":true}"))); + } + + @Test + @DisplayName("checkManufacturerCode should report whether the code is already taken") + void check_shouldReportWhetherCodeIsTaken() { + when(manufacturerInter.checkManufacturerCode(any())).thenReturn(Boolean.TRUE); + + assertSuccessContaining(controller.checkManufacturerCode("{\"manufacturerCode\":\"C-1\"}"), "true"); + } + + @Test + @DisplayName("checkManufacturerCode should answer an error envelope when the check fails") + void check_shouldAnswerErrorEnvelopeOnFailure() { + when(manufacturerInter.checkManufacturerCode(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkManufacturerCode("{\"manufacturerCode\":\"C-1\"}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/nodalConfig/NodalConfigControllerTest.java b/src/test/java/com/iemr/admin/controller/nodalConfig/NodalConfigControllerTest.java new file mode 100644 index 0000000..e5129d3 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/nodalConfig/NodalConfigControllerTest.java @@ -0,0 +1,147 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.nodalConfig; + +import java.util.ArrayList; +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.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletRequest; + +import com.iemr.admin.model.emailconfig.NodalEmailRequest; +import com.iemr.admin.model.emailconfig.NodalEmailResponse; +import com.iemr.admin.model.emailconfig.CreateNodalEmailRequestModel; +import com.iemr.admin.model.emailconfig.UpdateNodalEmailRequest; +import com.iemr.admin.service.nodalemailconfig.NodalConfigService; +import com.iemr.admin.utils.mapper.OutputMapper; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The nodal config screen keeps the nodal officer mailboxes a complaint is + * escalated to. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("NodalConfigController Test Suite") +class NodalConfigControllerTest { + + @Mock + private NodalConfigService nodalConfigService; + + @InjectMocks + private NodalConfigController controller; + + private final MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + + @BeforeEach + @DisplayName("Prime the shared output builder the screens publish through") + void setUp() { + new OutputMapper(); + } + + private static NodalEmailResponse mailbox() { + NodalEmailResponse mailbox = new NodalEmailResponse(); + mailbox.setEmailID("nodal.bengaluru@example.org"); + return mailbox; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + @Test + @DisplayName("saveConfig should answer the mailboxes it recorded") + void save_shouldAnswerRecordedMailboxes() { + when(nodalConfigService.saveNodalEmailConfigs(anyList())).thenReturn(List.of(mailbox())); + + String response = controller.saveConfig(List.of(new CreateNodalEmailRequestModel()), servletRequest); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("nodal.bengaluru@example.org"), response); + } + + @Test + @DisplayName("saveConfig should report the failure when the mailbox cannot be recorded") + void save_shouldReportStorageFailure() { + when(nodalConfigService.saveNodalEmailConfigs(anyList())).thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.saveConfig(new ArrayList<>(), servletRequest))); + } + + @Test + @DisplayName("getEmailConfigs should answer the mailboxes matching what the caller narrowed by") + void get_shouldAnswerMatchingMailboxes() { + when(nodalConfigService.getAllNodalEmailConfigs(any(NodalEmailRequest.class))).thenReturn(List.of(mailbox())); + + String response = controller.getNodalEmailConfigs(new NodalEmailRequest(), servletRequest); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("nodal.bengaluru@example.org"), response); + } + + @Test + @DisplayName("getEmailConfigs should report the failure when the mailboxes cannot be answered") + void get_shouldReportLookupFailure() { + when(nodalConfigService.getAllNodalEmailConfigs(any(NodalEmailRequest.class))) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getNodalEmailConfigs(new NodalEmailRequest(), servletRequest))); + } + + @Test + @DisplayName("updateEmailConfig should answer the mailbox as it stands after the change") + void update_shouldAnswerChangedMailbox() { + when(nodalConfigService.updateNodalEmailConfigs(any(UpdateNodalEmailRequest.class))).thenReturn(mailbox()); + + String response = controller.updateNodalEmailConfig(new UpdateNodalEmailRequest(), servletRequest); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("nodal.bengaluru@example.org"), response); + } + + @Test + @DisplayName("updateEmailConfig should report the failure when the change cannot be recorded") + void update_shouldReportStorageFailure() { + when(nodalConfigService.updateNodalEmailConfigs(any(UpdateNodalEmailRequest.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.updateNodalEmailConfig(new UpdateNodalEmailRequest(), servletRequest))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/parkingPlace/ParkingPlaceControllerTest.java b/src/test/java/com/iemr/admin/controller/parkingPlace/ParkingPlaceControllerTest.java new file mode 100644 index 0000000..bfa0d24 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/parkingPlace/ParkingPlaceControllerTest.java @@ -0,0 +1,260 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.parkingPlace; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.parkingPlace.M_Parkingplace; +import com.iemr.admin.service.parkingPlace.ParkingPlaceServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The parking place screens create, retire and re-address the places a van is + * stationed at, and answer them back filtered by state, provider or zone. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ParkingPlaceController Test Suite") +class ParkingPlaceControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer PARKING_PLACE_ID = 31; + + @Mock + private ParkingPlaceServiceImpl parkingPlaceServiceImpl; + + @InjectMocks + private ParkingPlaceController controller; + + private static M_Parkingplace place() { + M_Parkingplace place = new M_Parkingplace(); + place.setParkingPlaceID(PARKING_PLACE_ID); + place.setParkingPlaceName("Hosur parking"); + place.setParkingPlaceDesc("Near the bus stand"); + place.setProviderServiceMapID(PSM_ID); + return place; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + private static void assertFailure(String response, int expectedCode) { + assertEquals(expectedCode, statusCodeOf(response), response); + } + + @Test + @DisplayName("saveParkingPlace should answer the parking places it created") + void saveParkingPlace_shouldAnswerCreatedPlaces() throws Exception { + when(parkingPlaceServiceImpl.saveParkingPlace(anyList())) + .thenReturn(new ArrayList<>(List.of(place()))); + + String response = controller.saveParkingPlace( + "{\"parkingPlaces\":[{\"parkingPlaceName\":\"Hosur parking\",\"providerServiceMapID\":4001}]}"); + + assertSuccessContaining(response, "Hosur parking"); + } + + @Test + @DisplayName("saveParkingPlace should report the failure when the parking place cannot be stored") + void saveParkingPlace_shouldReportStorageFailure() throws Exception { + when(parkingPlaceServiceImpl.saveParkingPlace(anyList())) + .thenThrow(new RuntimeException("duplicate parking place")); + + String response = controller.saveParkingPlace("{\"parkingPlaces\":[{\"parkingPlaceName\":\"Hosur\"}]}"); + + assertFailure(response, OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("getParkingPlaces should answer the parking places of the state and district asked for") + void getParkingPlaces_shouldAnswerPlacesOfStateAndDistrict() throws Exception { + when(parkingPlaceServiceImpl.getAvailableParkingPlaces(29, 301, 5)) + .thenReturn(new ArrayList<>(List.of(place()))); + + String response = controller + .getParkingPlaces("{\"stateID\":29,\"districtID\":301,\"serviceProviderID\":5}"); + + assertSuccessContaining(response, "Hosur parking"); + } + + @Test + @DisplayName("getParkingPlaces should report the failure when the lookup cannot be answered") + void getParkingPlaces_shouldReportLookupFailure() throws Exception { + when(parkingPlaceServiceImpl.getAvailableParkingPlaces(any(), any(), any())) + .thenThrow(new RuntimeException("query timed out")); + + assertFailure(controller.getParkingPlaces("{\"stateID\":29}"), OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("deleteParkingPlace should confirm the retirement it recorded") + void deleteParkingPlace_shouldConfirmRetirement() throws Exception { + when(parkingPlaceServiceImpl.updateParkingPlaceStatus(any(M_Parkingplace.class))).thenReturn(1); + + String response = controller + .deleteParkingPlace("{\"parkingPlaceID\":31,\"deleted\":true,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "status updated successfully"); + } + + @Test + @DisplayName("deleteParkingPlace should say so when no parking place was retired") + void deleteParkingPlace_shouldSaySoWhenNothingRetired() throws Exception { + when(parkingPlaceServiceImpl.updateParkingPlaceStatus(any(M_Parkingplace.class))).thenReturn(0); + + String response = controller.deleteParkingPlace("{\"parkingPlaceID\":-1,\"deleted\":true}"); + + assertSuccessContaining(response, "Failed to update the status"); + } + + @Test + @DisplayName("deleteParkingPlace should report the failure when the retirement cannot be recorded") + void deleteParkingPlace_shouldReportRetirementFailure() throws Exception { + when(parkingPlaceServiceImpl.updateParkingPlaceStatus(any(M_Parkingplace.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertFailure(controller.deleteParkingPlace("{\"parkingPlaceID\":31}"), OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("updateParkingPlaceDetails should answer the re-addressed parking place") + void updateParkingPlaceDetails_shouldAnswerReaddressedPlace() throws Exception { + M_Parkingplace stored = place(); + when(parkingPlaceServiceImpl.getParkingPlaceByID(PARKING_PLACE_ID)).thenReturn(stored); + when(parkingPlaceServiceImpl.updateParkingPlaceData(stored)).thenReturn(stored); + + String response = controller.updateParkingPlaceDetails( + "{\"parkingPlaceID\":31,\"parkingPlaceName\":\"Hosur parking\",\"areaHQAddress\":\"Hosur Road\"," + + "\"stateID\":29,\"districtID\":301,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "Hosur parking"); + assertEquals("Hosur Road", stored.getAreaHQAddress()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("updateParkingPlaceDetails should report the failure when the parking place is unknown") + void updateParkingPlaceDetails_shouldReportUnknownPlace() throws Exception { + when(parkingPlaceServiceImpl.getParkingPlaceByID(anyInt())).thenReturn(null); + + assertFailure(controller.updateParkingPlaceDetails("{\"parkingPlaceID\":-1}"), + OutputResponse.CODE_EXCEPTION); + } + + @Test + @DisplayName("getParkingPlacesProviderserviceMap should answer the parking places of the provider asked for") + void getParkingPlacesProviderserviceMap_shouldAnswerPlacesOfProvider() throws Exception { + when(parkingPlaceServiceImpl.getParkingPlaces(PSM_ID)).thenReturn(List.of(place())); + + assertSuccessContaining(controller.getParkingPlacesProviderserviceMap("{\"providerServiceMapID\":4001}"), + "Hosur parking"); + } + + @Test + @DisplayName("getParkingPlacesProviderserviceMap should report the failure when the lookup cannot be answered") + void getParkingPlacesProviderserviceMap_shouldReportLookupFailure() throws Exception { + when(parkingPlaceServiceImpl.getParkingPlaces(any())) + .thenThrow(new RuntimeException("connection reset")); + + assertFailure(controller.getParkingPlacesProviderserviceMap("{\"providerServiceMapID\":4001}"), + OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("getSubDistrictDetailsByParkingPlaceID should answer the taluks the parking place covers") + void getSubDistrictDetails_shouldAnswerCoveredTaluks() throws Exception { + when(parkingPlaceServiceImpl.getSubDistrict(PARKING_PLACE_ID)) + .thenReturn(List.of(new M_Parkingplace(PARKING_PLACE_ID, 3011, "Anekal"))); + + assertSuccessContaining(controller.getSubDistrictDetailsByParkingPlaceID("{\"parkingPlaceID\":31}"), + "Anekal"); + } + + @Test + @DisplayName("getSubDistrictDetailsByParkingPlaceID should report the failure when the lookup cannot be answered") + void getSubDistrictDetails_shouldReportLookupFailure() throws Exception { + when(parkingPlaceServiceImpl.getSubDistrict(any())).thenThrow(new RuntimeException("query timed out")); + + assertFailure(controller.getSubDistrictDetailsByParkingPlaceID("{\"parkingPlaceID\":31}"), + OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("getparkingPlacesbyzoneid should answer the parking places of the zone asked for") + void getparkingPlacesbyzoneid_shouldAnswerPlacesOfZone() throws Exception { + when(parkingPlaceServiceImpl.getAvailableParkingPlacesbyZoneID(9, PSM_ID)) + .thenReturn(new ArrayList<>(List.of(place()))); + + assertSuccessContaining( + controller.getparkingPlacesbyzoneid("{\"zoneID\":9,\"providerServiceMapID\":4001}"), + "Hosur parking"); + } + + @Test + @DisplayName("getparkingPlacesbyzoneid should report the failure when the lookup cannot be answered") + void getparkingPlacesbyzoneid_shouldReportLookupFailure() throws Exception { + when(parkingPlaceServiceImpl.getAvailableParkingPlacesbyZoneID(any(), any())) + .thenThrow(new RuntimeException("connection reset")); + + assertFailure(controller.getparkingPlacesbyzoneid("{\"zoneID\":9}"), OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("a malformed request body should be rejected rather than reach the service") + void malformedBody_shouldBeRejected() throws Exception { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(runMalformed()), "malformed JSON must be refused"); + } + + private String runMalformed() throws Exception { + try { + return controller.getParkingPlaces("{not json"); + } catch (Exception e) { + OutputResponse output = new OutputResponse(); + output.setError(e); + return output.toString(); + } + } +} diff --git a/src/test/java/com/iemr/admin/controller/parkingPlace/ParkingPlaceTalukMappingControllerTest.java b/src/test/java/com/iemr/admin/controller/parkingPlace/ParkingPlaceTalukMappingControllerTest.java new file mode 100644 index 0000000..30821f2 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/parkingPlace/ParkingPlaceTalukMappingControllerTest.java @@ -0,0 +1,261 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.parkingPlace; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.DistrictBlock; +import com.iemr.admin.data.parkingPlace.ParkingplaceTalukMapping; +import com.iemr.admin.data.parkingPlace.ParkingplaceTalukMappingTO; +import com.iemr.admin.service.parkingPlace.ParkingPlaceTalukMappingServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The taluk mapping screen records which taluks a parking place covers, and + * offers the district's remaining taluks as the candidates for a new mapping. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ParkingPlaceTalukMappingController Test Suite") +class ParkingPlaceTalukMappingControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer PARKING_PLACE_ID = 31; + private static final Integer MAP_ID = 7001; + private static final Integer DISTRICT_ID = 301; + + @Mock + private ParkingPlaceTalukMappingServiceImpl parkingPlaceTalukMappingServiceImpl; + + @InjectMocks + private ParkingPlaceTalukMappingController controller; + + private static ParkingplaceTalukMapping mapping() { + ParkingplaceTalukMapping mapping = new ParkingplaceTalukMapping(); + mapping.setPpSubDistrictMapID(MAP_ID); + mapping.setParkingPlaceID(PARKING_PLACE_ID); + mapping.setDistrictID(DISTRICT_ID); + mapping.setDistrictBlockID(3011); + mapping.setProviderServiceMapID(PSM_ID); + mapping.setCreatedBy("admin"); + return mapping; + } + + private static ParkingplaceTalukMappingTO published() { + ParkingplaceTalukMappingTO to = new ParkingplaceTalukMappingTO(); + to.setPpSubDistrictMapID(MAP_ID); + to.setDistrictBlockName("Anekal"); + return to; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + private static void assertFailure(String response, int expectedCode) { + assertEquals(expectedCode, statusCodeOf(response), response); + } + + @Test + @DisplayName("parkingPlacesTalukMapping should answer the mappings it created") + void create_shouldAnswerCreatedMappings() { + when(parkingPlaceTalukMappingServiceImpl.saveParkingPlaceTalukMapping(anyList())) + .thenReturn(new ArrayList<>(List.of(mapping()))); + + assertSuccessContaining(controller.parkingPlacesTalukMapping(List.of(mapping())), "7001"); + } + + @Test + @DisplayName("parkingPlacesTalukMapping should report the failure when the mapping cannot be stored") + void create_shouldReportStorageFailure() { + when(parkingPlaceTalukMappingServiceImpl.saveParkingPlaceTalukMapping(anyList())) + .thenThrow(new RuntimeException("taluk already mapped")); + + assertFailure(controller.parkingPlacesTalukMapping(List.of(mapping())), OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("updateparkingPlacesTalukMapping should answer the mapping it moved") + void update_shouldAnswerMovedMapping() { + ParkingplaceTalukMapping stored = mapping(); + when(parkingPlaceTalukMappingServiceImpl.findbyID(MAP_ID)).thenReturn(stored); + when(parkingPlaceTalukMappingServiceImpl.updateParkingPlaceTalukMapping(stored)).thenReturn(stored); + + assertSuccessContaining(controller.updateparkingPlacesTalukMapping(mapping()), "7001"); + assertEquals("admin", stored.getModifiedBy(), "the caller must be recorded as the one who moved it"); + } + + @Test + @DisplayName("updateparkingPlacesTalukMapping should leave the request alone when no mapping is named") + void update_shouldLeaveRequestAloneWithoutMapId() { + ParkingplaceTalukMapping request = mapping(); + request.setPpSubDistrictMapID(null); + + assertSuccessContaining(controller.updateparkingPlacesTalukMapping(request), "31"); + verify(parkingPlaceTalukMappingServiceImpl, never()).findbyID(anyInt()); + } + + @Test + @DisplayName("updateparkingPlacesTalukMapping should report the failure when the mapping is unknown") + void update_shouldReportUnknownMapping() { + when(parkingPlaceTalukMappingServiceImpl.findbyID(MAP_ID)).thenReturn(null); + + assertFailure(controller.updateparkingPlacesTalukMapping(mapping()), OutputResponse.CODE_EXCEPTION); + } + + @Test + @DisplayName("getparkingPlacesTalukMapping should answer the mapping asked for") + void getById_shouldAnswerNamedMapping() { + when(parkingPlaceTalukMappingServiceImpl.findbyID(MAP_ID)).thenReturn(mapping()); + + assertSuccessContaining(controller.getparkingPlacesTalukMapping(mapping()), "7001"); + } + + @Test + @DisplayName("getparkingPlacesTalukMapping should echo the request when no mapping is named") + void getById_shouldEchoRequestWithoutMapId() { + ParkingplaceTalukMapping request = mapping(); + request.setPpSubDistrictMapID(null); + + assertSuccessContaining(controller.getparkingPlacesTalukMapping(request), "31"); + verify(parkingPlaceTalukMappingServiceImpl, never()).findbyID(anyInt()); + } + + @Test + @DisplayName("getparkingPlacesTalukMapping should report the failure when the lookup cannot be answered") + void getById_shouldReportLookupFailure() { + when(parkingPlaceTalukMappingServiceImpl.findbyID(MAP_ID)) + .thenThrow(new RuntimeException("query timed out")); + + assertFailure(controller.getparkingPlacesTalukMapping(mapping()), OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("getallparkingPlacesTalukMapping should answer every taluk the parking place covers") + void getAll_shouldAnswerCoveredTaluks() { + when(parkingPlaceTalukMappingServiceImpl.findbyProviderservicemapid(any())) + .thenReturn(List.of(published())); + + assertSuccessContaining(controller.getallparkingPlacesTalukMapping(mapping()), "Anekal"); + } + + @Test + @DisplayName("getallparkingPlacesTalukMapping should report the failure when the lookup cannot be answered") + void getAll_shouldReportLookupFailure() { + when(parkingPlaceTalukMappingServiceImpl.findbyProviderservicemapid(any())) + .thenThrow(new RuntimeException("connection reset")); + + assertFailure(controller.getallparkingPlacesTalukMapping(mapping()), OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("getafilterparkingPlacesTalukMapping should narrow the mappings to the district asked for") + void getFiltered_shouldNarrowToDistrict() { + when(parkingPlaceTalukMappingServiceImpl.findbyParkingplaceAndDistrictID(any())) + .thenReturn(List.of(published())); + + assertSuccessContaining(controller.getafilterparkingPlacesTalukMapping(mapping()), "Anekal"); + } + + @Test + @DisplayName("getafilterparkingPlacesTalukMapping should report the failure when the lookup cannot be answered") + void getFiltered_shouldReportLookupFailure() { + when(parkingPlaceTalukMappingServiceImpl.findbyParkingplaceAndDistrictID(any())) + .thenThrow(new RuntimeException("query timed out")); + + assertFailure(controller.getafilterparkingPlacesTalukMapping(mapping()), OutputResponse.GENERIC_FAILURE); + } + + @Test + @DisplayName("activateparkingPlacesTalukMapping should answer the mapping whose status it changed") + void activate_shouldAnswerChangedMapping() { + ParkingplaceTalukMapping stored = mapping(); + ParkingplaceTalukMapping request = mapping(); + request.setDeleted(Boolean.TRUE); + when(parkingPlaceTalukMappingServiceImpl.findbyID(MAP_ID)).thenReturn(stored); + when(parkingPlaceTalukMappingServiceImpl.updateParkingPlaceTalukMapping(stored)).thenReturn(stored); + + assertSuccessContaining(controller.activateparkingPlacesTalukMapping(request), "7001"); + assertEquals(Boolean.TRUE, stored.getDeleted(), "the mapping must take the requested status"); + } + + @Test + @DisplayName("activateparkingPlacesTalukMapping should leave the request alone when no mapping is named") + void activate_shouldLeaveRequestAloneWithoutMapId() { + ParkingplaceTalukMapping request = mapping(); + request.setPpSubDistrictMapID(null); + + assertSuccessContaining(controller.activateparkingPlacesTalukMapping(request), "31"); + verify(parkingPlaceTalukMappingServiceImpl, never()).findbyID(anyInt()); + } + + @Test + @DisplayName("activateparkingPlacesTalukMapping should report the failure when the mapping is unknown") + void activate_shouldReportUnknownMapping() { + when(parkingPlaceTalukMappingServiceImpl.findbyID(MAP_ID)).thenReturn(null); + + assertFailure(controller.activateparkingPlacesTalukMapping(mapping()), OutputResponse.CODE_EXCEPTION); + } + + @Test + @DisplayName("getunmappedtaluk should offer the taluks that are still free") + void getunmappedtaluk_shouldOfferFreeTaluks() { + when(parkingPlaceTalukMappingServiceImpl.getunmappedtaluk(DISTRICT_ID, PSM_ID)) + .thenReturn(List.of(new DistrictBlock(3012, "Hoskote"))); + + assertSuccessContaining(controller.getunmappedtaluk(mapping()), "Hoskote"); + } + + @Test + @DisplayName("getunmappedtaluk should report the failure when the candidates cannot be worked out") + void getunmappedtaluk_shouldReportLookupFailure() { + when(parkingPlaceTalukMappingServiceImpl.getunmappedtaluk(any(), any())) + .thenThrow(new RuntimeException("query timed out")); + + assertFailure(controller.getunmappedtaluk(mapping()), OutputResponse.GENERIC_FAILURE); + } +} diff --git a/src/test/java/com/iemr/admin/controller/pharmacologicalcategory/PharmacologicalCategoryControllerTest.java b/src/test/java/com/iemr/admin/controller/pharmacologicalcategory/PharmacologicalCategoryControllerTest.java new file mode 100644 index 0000000..7ceb4be --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/pharmacologicalcategory/PharmacologicalCategoryControllerTest.java @@ -0,0 +1,169 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.pharmacologicalcategory; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.pharmacologicalcategory.M_Pharmacologicalcategory; +import com.iemr.admin.service.pharmacologicalcategory.PharmacologicalcategoryInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** The pharmacological category master endpoints keep the pharmacological category catalogue an inventory operator picks from. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("PharmacologicalCategoryController Test Suite") +class PharmacologicalCategoryControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private PharmacologicalcategoryInter pharmacologicalcategoryInter; + + @InjectMocks + private PharmacologicalCategoryController controller; + + private static M_Pharmacologicalcategory record(Integer id, String name) { + M_Pharmacologicalcategory record = new M_Pharmacologicalcategory(); + record.setPharmacologyCategoryID(id); + record.setPharmCategoryName(name); + return record; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("createPharmacologicalcategory should answer the records the service stored") + void create_shouldAnswerStoredRecords() { + when(pharmacologicalcategoryInter.createPharmacologicalcategory(anyList())).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "Analgesic")))); + + assertSuccessContaining(controller.createPharmacologicalcategory("[{\"pharmCategoryCode\":\"C-1\"}]"), "Analgesic"); + } + + @Test + @DisplayName("createPharmacologicalcategory should answer an error envelope when the store fails") + void create_shouldAnswerErrorEnvelopeOnFailure() { + when(pharmacologicalcategoryInter.createPharmacologicalcategory(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createPharmacologicalcategory("[{}]")); + } + + @Test + @DisplayName("getPharmacologicalcategory should answer the records of the provider") + void get_shouldAnswerProviderRecords() { + when(pharmacologicalcategoryInter.getPharmacologicalcategory(PSM_ID)).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "Analgesic")))); + + assertSuccessContaining(controller.getPharmacologicalcategory("{\"providerServiceMapID\":4001}"), "Analgesic"); + } + + @Test + @DisplayName("getPharmacologicalcategory should answer an error envelope when the lookup fails") + void get_shouldAnswerErrorEnvelopeOnFailure() { + when(pharmacologicalcategoryInter.getPharmacologicalcategory(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getPharmacologicalcategory("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("editPharmacologicalcategory should copy the edits onto the stored record") + void edit_shouldCopyEdits() { + M_Pharmacologicalcategory stored = record(RECORD_ID, "Analgesic"); + when(pharmacologicalcategoryInter.editPharmacologicalcategory(RECORD_ID)).thenReturn(stored); + when(pharmacologicalcategoryInter.saveEditedPharData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editPharmacologicalcategory("{\"pharmacologyCategoryID\":11,\"pharmCategoryDesc\":\"Pain relief\",\"modifiedBy\":\"admin\"}"), "Analgesic"); + assertEquals("Pain relief", stored.getPharmCategoryDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("editPharmacologicalcategory should answer an error envelope for a record that does not exist") + void edit_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(pharmacologicalcategoryInter.editPharmacologicalcategory(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.editPharmacologicalcategory("{\"pharmacologyCategoryID\":11,\"pharmCategoryDesc\":\"Pain relief\",\"modifiedBy\":\"admin\"}"))); + } + + @Test + @DisplayName("deletePharmacologicalcategory should mark the record deleted") + void delete_shouldMarkRecordDeleted() { + M_Pharmacologicalcategory stored = record(RECORD_ID, "Analgesic"); + when(pharmacologicalcategoryInter.editPharmacologicalcategory(RECORD_ID)).thenReturn(stored); + when(pharmacologicalcategoryInter.saveEditedPharData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deletePharmacologicalcategory("{\"pharmacologyCategoryID\":11,\"deleted\":true}"), "Analgesic"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deletePharmacologicalcategory should answer an error envelope for a record that does not exist") + void delete_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(pharmacologicalcategoryInter.editPharmacologicalcategory(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.deletePharmacologicalcategory("{\"pharmacologyCategoryID\":11,\"deleted\":true}"))); + } + + @Test + @DisplayName("checkPharmacologicalcategoryCode should report whether the code is already taken") + void check_shouldReportWhetherCodeIsTaken() { + when(pharmacologicalcategoryInter.checkPharmacologicalcategoryCode(any())).thenReturn(Boolean.TRUE); + + assertSuccessContaining(controller.checkPharmacologicalcategoryCode("{\"pharmCategoryCode\":\"C-1\"}"), "true"); + } + + @Test + @DisplayName("checkPharmacologicalcategoryCode should answer an error envelope when the check fails") + void check_shouldAnswerErrorEnvelopeOnFailure() { + when(pharmacologicalcategoryInter.checkPharmacologicalcategoryCode(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkPharmacologicalcategoryCode("{\"pharmCategoryCode\":\"C-1\"}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardCallTypeControllerTest.java b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardCallTypeControllerTest.java new file mode 100644 index 0000000..67077e1 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardCallTypeControllerTest.java @@ -0,0 +1,288 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.provideronboard; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import com.iemr.admin.data.provideronboard.M_Calltype; +import com.iemr.admin.data.provideronboard.M_Subservice; +import com.iemr.admin.data.provideronboard.M_SubservicemasterPA; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The call type and sub-service endpoints define the call taxonomy a provider's + * agents work with. + */ +@DisplayName("ProviderOnBoardController call type Test Suite") +class ProviderOnBoardCallTypeControllerTest extends ProviderOnBoardFixture { + + private static M_Calltype callType(Integer id, String name) { + M_Calltype callType = new M_Calltype(); + callType.setCallTypeID(id); + callType.setCallType(name); + return callType; + } + + private static M_Subservice subService(Integer id, String name) { + M_Subservice subService = new M_Subservice(); + subService.setSubServiceID(id); + subService.setSubServiceName(name); + return subService; + } + + @Test + @DisplayName("saveCallTypeData should flatten the nested request into one call type per description") + void saveCallTypeData_shouldFlattenNestedRequest() { + ArrayList stored = new ArrayList<>(List.of(callType(51, "Medical Advice"))); + when(calltypeinter.saveCallList(anyList())).thenReturn(stored); + + String request = "[{\"callGroupType\":\"Inbound\",\"createdBy\":\"admin\",\"callType1\":" + + "[{\"calltype\":\"Medical Advice\",\"providerServiceMapID\":4001," + + "\"callTypeDesc1\":[\"General\",\"Urgent\"],\"fitToBlock1\":[\"true\",\"false\"]," + + "\"fitForFollowup1\":[true,false],\"isInbound1\":[true,true]," + + "\"isOutbound1\":[false,false]}]}]"; + + assertSuccessContaining(controller.saveCallTypeData(request), "Medical Advice"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(calltypeinter).saveCallList(captor.capture()); + List saved = captor.getValue(); + assertEquals(2, saved.size(), "each description must become its own call type"); + assertEquals("Inbound", saved.get(0).getCallGroupType()); + assertEquals("General", saved.get(0).getCallTypeDesc()); + assertTrue(saved.get(0).getFitToBlock()); + assertEquals(4001, saved.get(0).getProviderServiceMapID()); + } + + @Test + @DisplayName("saveCallTypeData should answer an error envelope for a request it cannot read") + void saveCallTypeData_shouldAnswerErrorEnvelopeForUnreadableRequest() { + assertCodeException(controller.saveCallTypeData("[{\"callGroupType\":\"Inbound\"}]")); + } + + @Test + @DisplayName("createCalltypeData should answer the call types the service stored") + void createCalltypeData_shouldAnswerStoredCallTypes() { + ArrayList stored = new ArrayList<>(List.of(callType(51, "Medical Advice"))); + when(calltypeinter.createCalltype(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createCalltypeData("[{\"callType\":\"Medical Advice\"}]"), + "Medical Advice"); + } + + @Test + @DisplayName("createCalltypeData should answer an error envelope when the store fails") + void createCalltypeData_shouldAnswerErrorEnvelopeOnFailure() { + when(calltypeinter.createCalltype(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createCalltypeData("[{\"callType\":\"Medical Advice\"}]")); + } + + @Test + @DisplayName("getCallTypeData should answer the call types configured for the mapping") + void getCallTypeData_shouldAnswerConfiguredCallTypes() { + ArrayList stored = new ArrayList<>(List.of(callType(51, "Medical Advice"))); + when(calltypeinter.getCalltypeData(4001)).thenReturn(stored); + + assertSuccessContaining(controller.getCallTypeData("{\"providerServiceMapID\":4001}"), "Medical Advice"); + } + + @Test + @DisplayName("getCallTypeData should answer an error envelope when the lookup fails") + void getCallTypeData_shouldAnswerErrorEnvelopeOnFailure() { + when(calltypeinter.getCalltypeData(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getCallTypeData("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("updateCallTypeData should copy the edited fields onto the stored call type") + void updateCallTypeData_shouldCopyEditedFields() { + M_Calltype stored = callType(51, "old name"); + when(calltypeinter.updateCallType(51)).thenReturn(stored); + when(calltypeinter.saveupdatedData(stored)).thenReturn(stored); + + String response = controller.updateCallTypeData("{\"callTypeID\":51,\"callType\":\"Medical Advice\"," + + "\"callGroupType\":\"Inbound\",\"callTypeDesc\":\"General\",\"providerServiceMapID\":4001," + + "\"fitToBlock\":true,\"fitForFollowup\":false,\"isInbound\":true,\"isOutbound\":false," + + "\"processed\":\"N\",\"maxRedial\":3}"); + + assertSuccessContaining(response, "Medical Advice"); + assertEquals("Inbound", stored.getCallGroupType()); + assertEquals(3, stored.getMaxRedial()); + } + + @Test + @DisplayName("updateCallTypeData should answer an error envelope for a call type that does not exist") + void updateCallTypeData_shouldAnswerErrorEnvelopeForUnknownCallType() { + when(calltypeinter.updateCallType(51)).thenReturn(null); + + assertCodeException(controller.updateCallTypeData("{\"callTypeID\":51}")); + } + + @Test + @DisplayName("deleteCallType should mark the call type deleted and answer what was saved") + void deleteCallType_shouldMarkCallTypeDeleted() { + M_Calltype stored = callType(51, "Medical Advice"); + when(calltypeinter.updateCallType(51)).thenReturn(stored); + when(calltypeinter.saveupdatedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteCallType("{\"callTypeID\":51,\"deleted\":true}"), "Medical Advice"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteCallType should answer an error envelope for a call type that does not exist") + void deleteCallType_shouldAnswerErrorEnvelopeForUnknownCallType() { + when(calltypeinter.updateCallType(51)).thenReturn(null); + + assertCodeException(controller.deleteCallType("{\"callTypeID\":51,\"deleted\":true}")); + } + + @Test + @DisplayName("saveSubServiceData should flatten the nested request onto the provider mapping") + void saveSubServiceData_shouldFlattenNestedRequest() { + ArrayList stored = new ArrayList<>(List.of(subService(61, "Counselling"))); + when(subServiceInter.saveSubList(anyList())).thenReturn(stored); + + String request = "[{\"providerServiceMapID\":4001,\"createdBy\":\"admin\",\"subServiceDetails\":" + + "[{\"subServiceName\":\"Counselling\",\"subServiceDesc\":\"Mental health\"}]}]"; + + assertSuccessContaining(controller.saveSubServiceData(request), "Counselling"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(subServiceInter).saveSubList(captor.capture()); + assertEquals(1, captor.getValue().size()); + assertEquals(4001, captor.getValue().get(0).getProviderServiceMapID()); + assertEquals("admin", captor.getValue().get(0).getCreatedBy()); + } + + @Test + @DisplayName("saveSubServiceData should answer an error envelope for a request it cannot read") + void saveSubServiceData_shouldAnswerErrorEnvelopeForUnreadableRequest() { + assertCodeException(controller.saveSubServiceData("[{\"providerServiceMapID\":4001}]")); + } + + @Test + @DisplayName("FindSubSeriveNameByMapId should answer the master sub-services for the service") + void findSubSeriveNameByMapId_shouldAnswerMasterSubServices() { + M_SubservicemasterPA master = new M_SubservicemasterPA(); + master.setSubServiceMasterID(71); + master.setSubServiceName("Counselling"); + ArrayList stored = new ArrayList<>(List.of(master)); + when(subServiceInter.getServiceNameByServiceID(3)).thenReturn(stored); + + assertSuccessContaining(controller.FindSubSeriveNameByMapId("{\"serviceID\":3}"), "Counselling"); + } + + @Test + @DisplayName("FindSubSeriveNameByMapId should answer an error envelope when the lookup fails") + void findSubSeriveNameByMapId_shouldAnswerErrorEnvelopeOnFailure() { + when(subServiceInter.getServiceNameByServiceID(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.FindSubSeriveNameByMapId("{\"serviceID\":3}")); + } + + @Test + @DisplayName("getSubSeriveName should answer the sub-services configured for the mapping") + void getSubSeriveName_shouldAnswerConfiguredSubServices() { + ArrayList stored = new ArrayList<>(List.of(subService(61, "Counselling"))); + when(subServiceInter.getsubServiceName(4001)).thenReturn(stored); + + assertSuccessContaining(controller.getSubSeriveName("{\"providerServiceMapID\":4001}"), "Counselling"); + } + + @Test + @DisplayName("getSubSeriveName should answer an error envelope when the lookup fails") + void getSubSeriveName_shouldAnswerErrorEnvelopeOnFailure() { + when(subServiceInter.getsubServiceName(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getSubSeriveName("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("updateSubSerive should copy the edited fields onto the stored sub-service") + void updateSubSerive_shouldCopyEditedFields() { + M_Subservice stored = subService(61, "old name"); + when(subServiceInter.getsubServiceNameById(61)).thenReturn(stored); + when(subServiceInter.saveupdatedData(stored)).thenReturn(stored); + + String response = controller.updateSubSerive("{\"subServiceID\":61,\"subServiceName\":\"Counselling\"," + + "\"subServiceDesc\":\"Mental health\",\"providerServiceMapID\":4001,\"processed\":\"N\"}"); + + assertSuccessContaining(response, "Counselling"); + assertEquals("Mental health", stored.getSubServiceDesc()); + verify(subServiceInter).saveupdatedData(stored); + } + + @Test + @DisplayName("updateSubSerive should answer an error envelope for a sub-service that does not exist") + void updateSubSerive_shouldAnswerErrorEnvelopeForUnknownSubService() { + when(subServiceInter.getsubServiceNameById(61)).thenReturn(null); + + assertCodeException(controller.updateSubSerive("{\"subServiceID\":61}")); + } + + @Test + @DisplayName("deleteSubSerive should mark the sub-service deleted and answer what was saved") + void deleteSubSerive_shouldMarkSubServiceDeleted() { + M_Subservice stored = subService(61, "Counselling"); + when(subServiceInter.getsubServiceNameById(61)).thenReturn(stored); + when(subServiceInter.saveupdatedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteSubSerive("{\"subServiceID\":61,\"deleted\":true}"), "Counselling"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteSubSerive should answer an error envelope for a sub-service that does not exist") + void deleteSubSerive_shouldAnswerErrorEnvelopeForUnknownSubService() { + when(subServiceInter.getsubServiceNameById(61)).thenReturn(null); + + assertCodeException(controller.deleteSubSerive("{\"subServiceID\":61,\"deleted\":true}")); + } + + @Test + @DisplayName("saveupdatedData should be reached with the resolved record rather than the request") + void updateSubSerive_shouldSaveTheResolvedRecord() { + M_Subservice stored = subService(61, "old name"); + when(subServiceInter.getsubServiceNameById(61)).thenReturn(stored); + when(subServiceInter.saveupdatedData(any())).thenReturn(stored); + + controller.updateSubSerive("{\"subServiceID\":61,\"subServiceName\":\"Counselling\"}"); + + verify(subServiceInter).saveupdatedData(stored); + } +} diff --git a/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardCategoryControllerTest.java b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardCategoryControllerTest.java new file mode 100644 index 0000000..11e27da --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardCategoryControllerTest.java @@ -0,0 +1,688 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.provideronboard; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import com.iemr.admin.data.provideronboard.M_Category; +import com.iemr.admin.data.provideronboard.M_Feedbacknature; +import com.iemr.admin.data.provideronboard.M_Feedbacktype; +import com.iemr.admin.data.provideronboard.M_Severity; +import com.iemr.admin.data.provideronboard.M_Subcategory; +import com.iemr.admin.data.provideronboard.V_Showsubcategory; + +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.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The category, severity and feedback endpoints define how a provider's agents + * classify a call once it has been taken. + */ +@DisplayName("ProviderOnBoardController category Test Suite") +class ProviderOnBoardCategoryControllerTest extends ProviderOnBoardFixture { + + private static final String SUBCAT_REQUEST = "{\"categoryID\":81,\"createdBy\":\"admin\",\"subcatArray\":" + + "[{\"subCategoryName\":\"Fever\",\"subCategoryDesc\":\"High temperature\"," + + "\"subCatFilePath\":\"/fever\"}]}"; + + private static M_Category category(Integer id, String name) { + M_Category category = new M_Category(); + category.setCategoryID(id); + category.setCategoryName(name); + return category; + } + + private static M_Subcategory subCategory(Integer id, String name) { + M_Subcategory subCategory = new M_Subcategory(); + subCategory.setSubCategoryID(id); + subCategory.setSubCategoryName(name); + return subCategory; + } + + @Test + @DisplayName("saveCategory should attach the sub-categories to the category the service resolves") + void saveCategory_shouldAttachSubCategoriesToResolvedCategory() { + ArrayList stored = new ArrayList<>(List.of(subCategory(91, "Fever"))); + when(categoryInter.getCategoryId(any())).thenReturn(81); + when(categoryInter.saveSubCatData(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.saveCategory(SUBCAT_REQUEST), "Fever"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(categoryInter).saveSubCatData(captor.capture()); + assertEquals(81, captor.getValue().get(0).getCategoryID()); + assertEquals("admin", captor.getValue().get(0).getCreatedBy()); + } + + @Test + @DisplayName("saveCategory should answer an error envelope when no sub-categories are named") + void saveCategory_shouldAnswerErrorEnvelopeWithoutSubCategories() { + when(categoryInter.getCategoryId(any())).thenReturn(81); + + assertCodeException(controller.saveCategory("{\"categoryID\":81}")); + } + + @Test + @DisplayName("saveCategoryUseExist should attach the sub-categories to the category the caller names") + void saveCategoryUseExist_shouldAttachSubCategoriesToNamedCategory() { + ArrayList stored = new ArrayList<>(List.of(subCategory(91, "Fever"))); + when(categoryInter.saveSubCatData(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.saveCategoryUseExist(SUBCAT_REQUEST), "Fever"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(categoryInter).saveSubCatData(captor.capture()); + assertEquals(81, captor.getValue().get(0).getCategoryID()); + } + + @Test + @DisplayName("saveCategoryUseExist should answer an error envelope when no sub-categories are named") + void saveCategoryUseExist_shouldAnswerErrorEnvelopeWithoutSubCategories() { + assertCodeException(controller.saveCategoryUseExist("{\"categoryID\":81}")); + } + + @Test + @DisplayName("getCategoryBySubServiceID should answer the categories under the sub-service") + void getCategoryBySubServiceID_shouldAnswerCategories() { + V_Showsubcategory view = new V_Showsubcategory(); + view.setSubCategoryID(91); + view.setSubCategoryName("Fever"); + ArrayList stored = new ArrayList<>(List.of(view)); + when(categoryInter.getCategoryByMapIDAndSubServiceID(4001, 61)).thenReturn(stored); + + assertSuccessContaining( + controller.getCategoryBySubServiceID("{\"providerServiceMapID\":4001,\"subServiceID\":61}"), "Fever"); + } + + @Test + @DisplayName("getCategoryBySubServiceID should answer an error envelope when the lookup fails") + void getCategoryBySubServiceID_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.getCategoryByMapIDAndSubServiceID(anyInt(), anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure( + controller.getCategoryBySubServiceID("{\"providerServiceMapID\":4001,\"subServiceID\":61}")); + } + + @Test + @DisplayName("getsubCategory should answer the sub-categories of the category") + void getsubCategory_shouldAnswerSubCategories() { + ArrayList stored = new ArrayList<>(List.of(subCategory(91, "Fever"))); + when(categoryInter.getCategory(81)).thenReturn(stored); + + assertSuccessContaining(controller.getsubCategory("{\"categoryID\":81}"), "Fever"); + } + + @Test + @DisplayName("getsubCategory should answer an error envelope when the lookup fails") + void getsubCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.getCategory(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getsubCategory("{\"categoryID\":81}")); + } + + @Test + @DisplayName("getCategory should answer the categories under the sub-service and mapping") + void getCategory_shouldAnswerCategories() { + ArrayList stored = new ArrayList<>(List.of(category(81, "Medical"))); + when(categoryInter.getAllCategory(61, 4001)).thenReturn(stored); + + assertSuccessContaining(controller.getCategory("{\"subServiceID\":61,\"providerServiceMapID\":4001}"), + "Medical"); + } + + @Test + @DisplayName("getCategory should answer an error envelope when the lookup fails") + void getCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.getAllCategory(anyInt(), anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getCategory("{\"subServiceID\":61,\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("updateSubCategory should copy the edited fields onto the stored sub-category") + void updateSubCategory_shouldCopyEditedFields() { + M_Subcategory stored = subCategory(91, "old name"); + when(categoryInter.getSubCategory(91)).thenReturn(stored); + when(categoryInter.updateSubCatData(stored)).thenReturn(stored); + + String response = controller.updateSubCategory("{\"subCategoryID\":91,\"categoryID\":81," + + "\"subCategoryName\":\"Fever\",\"subCategoryDesc\":\"High temperature\"," + + "\"subCatFilePath\":\"/fever\"}"); + + assertSuccessContaining(response, "Fever"); + assertEquals("High temperature", stored.getSubCategoryDesc()); + assertEquals("/fever", stored.getSubCatFilePath()); + } + + @Test + @DisplayName("updateSubCategory should answer an error envelope for a sub-category that does not exist") + void updateSubCategory_shouldAnswerErrorEnvelopeForUnknownSubCategory() { + when(categoryInter.getSubCategory(91)).thenReturn(null); + + assertCodeException(controller.updateSubCategory("{\"subCategoryID\":91}")); + } + + @Test + @DisplayName("createCategory should answer the categories the service stored") + void createCategory_shouldAnswerStoredCategories() { + ArrayList stored = new ArrayList<>(List.of(category(81, "Medical"))); + when(categoryInter.createcat(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createCategory("[{\"categoryName\":\"Medical\"}]"), "Medical"); + } + + @Test + @DisplayName("createCategory should answer an error envelope when the store fails") + void createCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.createcat(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createCategory("[{\"categoryName\":\"Medical\"}]")); + } + + @Test + @DisplayName("deleteCategory1 should mark the category deleted and answer what was saved") + void deleteCategory1_shouldMarkCategoryDeleted() { + M_Category stored = category(81, "Medical"); + when(categoryInter.getcatdatabycatId(81)).thenReturn(stored); + when(categoryInter.deletedata(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteCategory1("{\"categoryID\":81,\"deleted\":true}"), "Medical"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteCategory1 should answer an error envelope for a category that does not exist") + void deleteCategory1_shouldAnswerErrorEnvelopeForUnknownCategory() { + when(categoryInter.getcatdatabycatId(81)).thenReturn(null); + + assertCodeException(controller.deleteCategory1("{\"categoryID\":81,\"deleted\":true}")); + } + + @Test + @DisplayName("createSubCategory should answer the sub-categories the service stored") + void createSubCategory_shouldAnswerStoredSubCategories() { + ArrayList stored = new ArrayList<>(List.of(subCategory(91, "Fever"))); + when(categoryInter.createSubCategory(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createSubCategory("[{\"subCategoryName\":\"Fever\"}]"), "Fever"); + } + + @Test + @DisplayName("createSubCategory should answer an error envelope when the store fails") + void createSubCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.createSubCategory(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createSubCategory("[{\"subCategoryName\":\"Fever\"}]")); + } + + @Test + @DisplayName("deleteSubCategory should mark the sub-category deleted and answer what was saved") + void deleteSubCategory_shouldMarkSubCategoryDeleted() { + M_Subcategory stored = subCategory(91, "Fever"); + when(categoryInter.getSubCategory(91)).thenReturn(stored); + when(categoryInter.updateSubCatData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteSubCategory("{\"subCategoryID\":91,\"deleted\":true}"), "Fever"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteSubCategory should answer an error envelope for a sub-category that does not exist") + void deleteSubCategory_shouldAnswerErrorEnvelopeForUnknownSubCategory() { + when(categoryInter.getSubCategory(91)).thenReturn(null); + + assertCodeException(controller.deleteSubCategory("{\"subCategoryID\":91,\"deleted\":true}")); + } + + @Test + @DisplayName("getSubCategory should answer the sub-category view the service resolves") + void getSubCategory_shouldAnswerSubCategoryView() { + V_Showsubcategory view = new V_Showsubcategory(); + view.setSubCategoryID(91); + view.setSubCategoryName("Fever"); + ArrayList stored = new ArrayList<>(List.of(view)); + when(categoryInter.getSubCategory1(91)).thenReturn(stored); + + assertSuccessContaining(controller.getSubCategory("{\"subCategoryID\":91}"), "Fever"); + } + + @Test + @DisplayName("getSubCategory should answer an error envelope when the lookup fails") + void getSubCategory_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.getSubCategory1(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getSubCategory("{\"subCategoryID\":91}")); + } + + @Test + @DisplayName("updateCategory should copy the edited fields onto the stored category") + void updateCategory_shouldCopyEditedFields() { + M_Category stored = category(81, "old name"); + when(categoryInter.getcatdatabycatId(81)).thenReturn(stored); + when(categoryInter.deletedata(stored)).thenReturn(stored); + + String response = controller.updateCategory("{\"categoryID\":81,\"categoryName\":\"Medical\"," + + "\"categoryDesc\":\"Clinical calls\",\"modifiedBy\":\"admin\",\"s104_CS_Type\":true}"); + + assertSuccessContaining(response, "Medical"); + assertEquals("Clinical calls", stored.getCategoryDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("updateCategory should answer an error envelope for a category that does not exist") + void updateCategory_shouldAnswerErrorEnvelopeForUnknownCategory() { + when(categoryInter.getcatdatabycatId(81)).thenReturn(null); + + assertCodeException(controller.updateCategory("{\"categoryID\":81}")); + } + + @Test + @DisplayName("mapCategorytoFeedbackNature should map every category the request names") + void mapCategorytoFeedbackNature_shouldMapEveryCategory() { + when(categoryInter.updateCategory(81, 21)).thenReturn(1); + when(categoryInter.updateCategory(82, 22)).thenReturn(1); + + String response = controller.mapCategorytoFeedbackNature( + "[{\"categoryID\":81,\"feedbackNatureID\":21},{\"categoryID\":82,\"feedbackNatureID\":22}]"); + + assertSuccessContaining(response, "inserted"); + verify(categoryInter).updateCategory(81, 21); + verify(categoryInter).updateCategory(82, 22); + } + + @Test + @DisplayName("mapCategorytoFeedbackNature should answer an error envelope when the mapping fails") + void mapCategorytoFeedbackNature_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.updateCategory(anyInt(), anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.mapCategorytoFeedbackNature("[{\"categoryID\":81,\"feedbackNatureID\":21}]")); + } + + @Test + @DisplayName("updateCategorytoFeedbackNature should clear the old category before mapping the new one") + void updateCategorytoFeedbackNature_shouldClearOldCategoryFirst() { + M_Category previous = category(80, "Previous"); + previous.setFeedbackNatureID(21); + M_Category current = category(81, "Medical"); + when(categoryInter.getcatdatabycatId(80)).thenReturn(previous); + when(categoryInter.getcatdatabycatId(81)).thenReturn(current); + when(categoryInter.deletedata(any())).thenReturn(current); + + String response = controller.updateCategorytoFeedbackNature("{\"oldCategoryID\":80,\"categoryID\":81," + + "\"feedbackNatureID\":22,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "Medical"); + assertNull(previous.getFeedbackNatureID(), "the old category must stop pointing at a feedback nature"); + assertEquals(22, current.getFeedbackNatureID()); + } + + @Test + @DisplayName("updateCategorytoFeedbackNature should answer an error envelope for an unknown old category") + void updateCategorytoFeedbackNature_shouldAnswerErrorEnvelopeForUnknownOldCategory() { + when(categoryInter.getcatdatabycatId(anyInt())).thenReturn(null); + + assertCodeException( + controller.updateCategorytoFeedbackNature("{\"oldCategoryID\":80,\"categoryID\":81}")); + } + + @Test + @DisplayName("getmapedCategorytoFeedbackNature should answer the categories mapped to the feedback nature") + void getmapedCategorytoFeedbackNature_shouldAnswerMappedCategories() { + ArrayList stored = new ArrayList<>(List.of(category(81, "Medical"))); + when(categoryInter.getAllCategorywithFeedbackNatureID(4001, 21)).thenReturn(stored); + + assertSuccessContaining(controller.getmapedCategorytoFeedbackNatureWithCatIDandFeedbackNatureID( + "{\"providerServiceMapID\":4001,\"feedbackNatureID\":21}"), "Medical"); + } + + @Test + @DisplayName("getmapedCategorytoFeedbackNature should answer an error envelope when the lookup fails") + void getmapedCategorytoFeedbackNature_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.getAllCategorywithFeedbackNatureID(anyInt(), anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getmapedCategorytoFeedbackNatureWithCatIDandFeedbackNatureID( + "{\"providerServiceMapID\":4001,\"feedbackNatureID\":21}")); + } + + @Test + @DisplayName("getunmappedCategoryforFeedbackNature should answer the categories still unmapped") + void getunmappedCategoryforFeedbackNature_shouldAnswerUnmappedCategories() { + ArrayList stored = new ArrayList<>(List.of(category(81, "Medical"))); + when(categoryInter.getUpmappedCategory(4001)).thenReturn(stored); + + assertSuccessContaining( + controller.getunmappedCategoryforFeedbackNature("{\"providerServiceMapID\":4001}"), "Medical"); + } + + @Test + @DisplayName("getunmappedCategoryforFeedbackNature should answer an error envelope when the lookup fails") + void getunmappedCategoryforFeedbackNature_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.getUpmappedCategory(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getunmappedCategoryforFeedbackNature("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("unmappCategoryforFeedbackNature should clear the feedback nature from the category") + void unmappCategoryforFeedbackNature_shouldClearFeedbackNature() { + M_Category stored = category(81, "Medical"); + stored.setFeedbackNatureID(21); + when(categoryInter.getcatdatabycatId(81)).thenReturn(stored); + when(categoryInter.deletedata(stored)).thenReturn(stored); + + assertSuccessContaining( + controller.unmappCategoryforFeedbackNature("{\"categoryID\":81,\"modifiedBy\":\"admin\"}"), "Medical"); + assertNull(stored.getFeedbackNatureID()); + } + + @Test + @DisplayName("unmappCategoryforFeedbackNature should answer an error envelope for an unknown category") + void unmappCategoryforFeedbackNature_shouldAnswerErrorEnvelopeForUnknownCategory() { + when(categoryInter.getcatdatabycatId(81)).thenReturn(null); + + assertCodeException(controller.unmappCategoryforFeedbackNature("{\"categoryID\":81}")); + } + + @Test + @DisplayName("getAllCategoryPsmMapid should answer every category under the mapping") + void getAllCategoryPsmMapid_shouldAnswerEveryCategory() { + ArrayList stored = new ArrayList<>(List.of(category(81, "Medical"))); + when(categoryInter.getAllCategory1(4001)).thenReturn(stored); + + assertSuccessContaining(controller.getAllCategoryPsmMapid("{\"providerServiceMapID\":4001}"), "Medical"); + } + + @Test + @DisplayName("getAllCategoryPsmMapid should answer an error envelope when the lookup fails") + void getAllCategoryPsmMapid_shouldAnswerErrorEnvelopeOnFailure() { + when(categoryInter.getAllCategory1(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllCategoryPsmMapid("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getServerity should answer the severities configured for the mapping") + void getServerity_shouldAnswerConfiguredSeverities() { + M_Severity severity = new M_Severity(); + severity.setSeverityID(31); + severity.setSeverityTypeName("Critical"); + ArrayList stored = new ArrayList<>(List.of(severity)); + when(m_ServerityInter.getServerity(4001)).thenReturn(stored); + + assertSuccessContaining(controller.getServerity("{\"providerServiceMapID\":4001}"), "Critical"); + } + + @Test + @DisplayName("getServerity should answer an error envelope when the lookup fails") + void getServerity_shouldAnswerErrorEnvelopeOnFailure() { + when(m_ServerityInter.getServerity(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getServerity("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("saveServerity should answer the severities the service stored") + void saveServerity_shouldAnswerStoredSeverities() { + M_Severity severity = new M_Severity(); + severity.setSeverityID(31); + severity.setSeverityTypeName("Critical"); + ArrayList stored = new ArrayList<>(List.of(severity)); + when(m_ServerityInter.saveServerity(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.saveServerity("[{\"severityTypeName\":\"Critical\"}]"), "Critical"); + } + + @Test + @DisplayName("saveServerity should answer an error envelope when the store fails") + void saveServerity_shouldAnswerErrorEnvelopeOnFailure() { + when(m_ServerityInter.saveServerity(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveServerity("[{\"severityTypeName\":\"Critical\"}]")); + } + + @Test + @DisplayName("deleteServerity should mark the severity deleted and answer what was saved") + void deleteServerity_shouldMarkSeverityDeleted() { + M_Severity stored = new M_Severity(); + stored.setSeverityID(31); + stored.setSeverityTypeName("Critical"); + when(m_ServerityInter.getDataByServId(31)).thenReturn(stored); + when(m_ServerityInter.deletedataser(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteServerity("{\"severityID\":31,\"deleted\":true}"), "Critical"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteServerity should answer an error envelope for a severity that does not exist") + void deleteServerity_shouldAnswerErrorEnvelopeForUnknownSeverity() { + when(m_ServerityInter.getDataByServId(31)).thenReturn(null); + + assertCodeException(controller.deleteServerity("{\"severityID\":31,\"deleted\":true}")); + } + + @Test + @DisplayName("editServerity should copy the edited fields onto the stored severity") + void editServerity_shouldCopyEditedFields() { + M_Severity stored = new M_Severity(); + stored.setSeverityID(31); + when(m_ServerityInter.getDataByServId(31)).thenReturn(stored); + when(m_ServerityInter.deletedataser(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editServerity("{\"severityID\":31,\"severityTypeName\":\"Critical\"," + + "\"severityDesc\":\"Needs escalation\"}"), "Critical"); + assertEquals("Needs escalation", stored.getSeverityDesc()); + } + + @Test + @DisplayName("editServerity should answer an error envelope for a severity that does not exist") + void editServerity_shouldAnswerErrorEnvelopeForUnknownSeverity() { + when(m_ServerityInter.getDataByServId(31)).thenReturn(null); + + assertCodeException(controller.editServerity("{\"severityID\":31}")); + } + + @Test + @DisplayName("getFeedbackType should answer the feedback types configured for the mapping") + void getFeedbackType_shouldAnswerConfiguredFeedbackTypes() { + M_Feedbacktype feedbackType = new M_Feedbacktype(); + feedbackType.setFeedbackTypeID(41); + feedbackType.setFeedbackTypeName("Complaint"); + ArrayList stored = new ArrayList<>(List.of(feedbackType)); + when(m_FeedbacktypeInter.getFeedbackt(4001)).thenReturn(stored); + + assertSuccessContaining(controller.getFeedbackType("{\"providerServiceMapID\":4001}"), "Complaint"); + } + + @Test + @DisplayName("getFeedbackType should answer an error envelope when the lookup fails") + void getFeedbackType_shouldAnswerErrorEnvelopeOnFailure() { + when(m_FeedbacktypeInter.getFeedbackt(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFeedbackType("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("saveFeedbackType should answer the feedback types the service stored") + void saveFeedbackType_shouldAnswerStoredFeedbackTypes() { + M_Feedbacktype feedbackType = new M_Feedbacktype(); + feedbackType.setFeedbackTypeID(41); + feedbackType.setFeedbackTypeName("Complaint"); + ArrayList stored = new ArrayList<>(List.of(feedbackType)); + when(m_FeedbacktypeInter.saveFeedbackType(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.saveFeedbackType("[{\"feedbackTypeName\":\"Complaint\"}]"), "Complaint"); + } + + @Test + @DisplayName("saveFeedbackType should answer an error envelope when the store fails") + void saveFeedbackType_shouldAnswerErrorEnvelopeOnFailure() { + when(m_FeedbacktypeInter.saveFeedbackType(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveFeedbackType("[{\"feedbackTypeName\":\"Complaint\"}]")); + } + + @Test + @DisplayName("editFeedbackType should copy the edited fields onto the stored feedback type") + void editFeedbackType_shouldCopyEditedFields() { + M_Feedbacktype stored = new M_Feedbacktype(); + stored.setFeedbackTypeID(41); + when(m_FeedbacktypeInter.getDataByServId(41)).thenReturn(stored); + when(m_FeedbacktypeInter.deletedataser(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editFeedbackType("{\"feedbackTypeID\":41," + + "\"feedbackTypeName\":\"Complaint\",\"feedbackDesc\":\"Service issue\"," + + "\"modifiedBy\":\"admin\"}"), "Complaint"); + assertEquals("Service issue", stored.getFeedbackDesc()); + } + + @Test + @DisplayName("editFeedbackType should answer an error envelope for a feedback type that does not exist") + void editFeedbackType_shouldAnswerErrorEnvelopeForUnknownFeedbackType() { + when(m_FeedbacktypeInter.getDataByServId(41)).thenReturn(null); + + assertCodeException(controller.editFeedbackType("{\"feedbackTypeID\":41}")); + } + + @Test + @DisplayName("deleteFeedbackType should mark the feedback type deleted") + void deleteFeedbackType_shouldMarkFeedbackTypeDeleted() { + M_Feedbacktype stored = new M_Feedbacktype(); + stored.setFeedbackTypeID(41); + stored.setFeedbackTypeName("Complaint"); + when(m_FeedbacktypeInter.getDataByServId(41)).thenReturn(stored); + when(m_FeedbacktypeInter.deletedataser(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteFeedbackType("{\"feedbackTypeID\":41,\"deleted\":true}"), + "Complaint"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteFeedbackType should answer an error envelope for a feedback type that does not exist") + void deleteFeedbackType_shouldAnswerErrorEnvelopeForUnknownFeedbackType() { + when(m_FeedbacktypeInter.getDataByServId(41)).thenReturn(null); + + assertCodeException(controller.deleteFeedbackType("{\"feedbackTypeID\":41,\"deleted\":true}")); + } + + @Test + @DisplayName("getFeedbackNatureType should answer the natures under the feedback type") + void getFeedbackNatureType_shouldAnswerNatures() { + M_Feedbacknature nature = new M_Feedbacknature(); + nature.setFeedbackNatureID(21); + nature.setFeedbackNature("Escalation"); + ArrayList stored = new ArrayList<>(List.of(nature)); + when(m_FeedbacknatureInteger.getFeedbackNatureType(41)).thenReturn(stored); + + assertSuccessContaining(controller.getFeedbackNatureType("{\"feedbackTypeID\":41}"), "Escalation"); + } + + @Test + @DisplayName("getFeedbackNatureType should answer an error envelope when the lookup fails") + void getFeedbackNatureType_shouldAnswerErrorEnvelopeOnFailure() { + when(m_FeedbacknatureInteger.getFeedbackNatureType(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFeedbackNatureType("{\"feedbackTypeID\":41}")); + } + + @Test + @DisplayName("createFeedbackNatureType should answer the natures the service stored") + void createFeedbackNatureType_shouldAnswerStoredNatures() { + M_Feedbacknature nature = new M_Feedbacknature(); + nature.setFeedbackNatureID(21); + nature.setFeedbackNature("Escalation"); + ArrayList stored = new ArrayList<>(List.of(nature)); + when(m_FeedbacknatureInteger.createFeedbackNatueType(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createFeedbackNatureType("[{\"feedbackNature\":\"Escalation\"}]"), + "Escalation"); + } + + @Test + @DisplayName("createFeedbackNatureType should answer an error envelope when the store fails") + void createFeedbackNatureType_shouldAnswerErrorEnvelopeOnFailure() { + when(m_FeedbacknatureInteger.createFeedbackNatueType(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createFeedbackNatureType("[{\"feedbackNature\":\"Escalation\"}]")); + } + + @Test + @DisplayName("deleteFeedbackNatureType should mark the nature deleted") + void deleteFeedbackNatureType_shouldMarkNatureDeleted() { + M_Feedbacknature stored = new M_Feedbacknature(); + stored.setFeedbackNatureID(21); + stored.setFeedbackNature("Escalation"); + when(m_FeedbacknatureInteger.editFeedbackNatureType(21)).thenReturn(stored); + when(m_FeedbacknatureInteger.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteFeedbackNatureType("{\"feedbackNatureID\":21,\"deleted\":true}"), + "Escalation"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteFeedbackNatureType should answer an error envelope for a nature that does not exist") + void deleteFeedbackNatureType_shouldAnswerErrorEnvelopeForUnknownNature() { + when(m_FeedbacknatureInteger.editFeedbackNatureType(21)).thenReturn(null); + + assertCodeException(controller.deleteFeedbackNatureType("{\"feedbackNatureID\":21,\"deleted\":true}")); + } + + @Test + @DisplayName("editFeedbackNatureType should copy the edited fields onto the stored nature") + void editFeedbackNatureType_shouldCopyEditedFields() { + M_Feedbacknature stored = new M_Feedbacknature(); + stored.setFeedbackNatureID(21); + when(m_FeedbacknatureInteger.editFeedbackNatureType(21)).thenReturn(stored); + when(m_FeedbacknatureInteger.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editFeedbackNatureType("{\"feedbackNatureID\":21," + + "\"feedbackNature\":\"Escalation\",\"feedbackNatureDesc\":\"Raise to supervisor\"," + + "\"modifiedBy\":\"admin\"}"), "Escalation"); + assertEquals("Raise to supervisor", stored.getFeedbackNatureDesc()); + } + + @Test + @DisplayName("editFeedbackNatureType should answer an error envelope for a nature that does not exist") + void editFeedbackNatureType_shouldAnswerErrorEnvelopeForUnknownNature() { + when(m_FeedbacknatureInteger.editFeedbackNatureType(21)).thenReturn(null); + + assertCodeException(controller.editFeedbackNatureType("{\"feedbackNatureID\":21}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardDrugControllerTest.java b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardDrugControllerTest.java new file mode 100644 index 0000000..d52347d --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardDrugControllerTest.java @@ -0,0 +1,304 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.provideronboard; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.data.provideronboard.M_104druggroup; +import com.iemr.admin.data.provideronboard.M_104drugmapping; +import com.iemr.admin.data.provideronboard.M_104drugmaster; +import com.iemr.admin.utils.exception.IEMRException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** The drug endpoints keep the 104 helpline's drug catalogue for a provider. */ +@DisplayName("ProviderOnBoardController drug Test Suite") +class ProviderOnBoardDrugControllerTest extends ProviderOnBoardFixture { + + private static M_104drugmaster drug(Integer id, String name) { + M_104drugmaster drug = new M_104drugmaster(); + drug.setDrugID(id); + drug.setDrugName(name); + return drug; + } + + private static M_104druggroup drugGroup(Integer id, String name) { + M_104druggroup group = new M_104druggroup(); + group.setDrugGroupID(id); + group.setDrugGroup(name); + return group; + } + + private static M_104drugmapping drugMapping(Integer id, String drugName) { + M_104drugmapping mapping = new M_104drugmapping(); + mapping.setDrugMapID(id); + mapping.setDrugName(drugName); + return mapping; + } + + @Test + @DisplayName("getDrugData should answer the drugs the catalogue holds") + void getDrugData_shouldAnswerCatalogueDrugs() throws IEMRException { + ArrayList stored = new ArrayList<>(List.of(drug(101, "Paracetamol"))); + when(drugMasterInter.getAllDrugData(101, (short) 77, Boolean.FALSE)).thenReturn(stored); + + assertSuccessContaining( + controller.getDrugData("{\"drugID\":101,\"serviceProviderID\":77,\"deleted\":false}"), + "Paracetamol"); + } + + @Test + @DisplayName("getDrugData should answer an error envelope when the catalogue cannot be read") + void getDrugData_shouldAnswerErrorEnvelopeOnFailure() throws IEMRException { + when(drugMasterInter.getAllDrugData(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getDrugData("{\"drugID\":101,\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getDrugGroups should answer the drug groups the catalogue holds") + void getDrugGroups_shouldAnswerCatalogueGroups() throws IEMRException { + ArrayList stored = new ArrayList<>(List.of(drugGroup(201, "Analgesics"))); + when(drugMasterInter.getAllDrugGroups(201, (short) 77, Boolean.FALSE)).thenReturn(stored); + + assertSuccessContaining( + controller.getDrugGroups("{\"drugGroupID\":201,\"serviceProviderID\":77,\"deleted\":false}"), + "Analgesics"); + } + + @Test + @DisplayName("getDrugGroups should answer an error envelope when the catalogue cannot be read") + void getDrugGroups_shouldAnswerErrorEnvelopeOnFailure() throws IEMRException { + when(drugMasterInter.getAllDrugGroups(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getDrugGroups("{\"drugGroupID\":201,\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getDrugGroupMappings should answer every mapping for the provider's service") + void getDrugGroupMappings_shouldAnswerMappings() throws IEMRException { + ArrayList stored = new ArrayList<>(List.of(drugMapping(301, "Paracetamol"))); + when(drugMasterInter.getAllDrugGroupMappings(null, 77, 3)).thenReturn(stored); + + assertSuccessContaining( + controller.getDrugGroupMappings("{\"serviceProviderID\":77,\"serviceID\":3}"), "Paracetamol"); + } + + @Test + @DisplayName("getDrugGroupMappings should answer an error envelope when the lookup fails") + void getDrugGroupMappings_shouldAnswerErrorEnvelopeOnFailure() throws IEMRException { + when(drugMasterInter.getAllDrugGroupMappings(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getDrugGroupMappings("{\"serviceProviderID\":77,\"serviceID\":3}")); + } + + @Test + @DisplayName("updateDrugGroup should copy the edited fields onto the stored group") + void updateDrugGroup_shouldCopyEditedFields() { + M_104druggroup stored = drugGroup(201, "old name"); + when(drugMasterInter.getDrugGroupById(201)).thenReturn(stored); + when(drugMasterInter.saveUpdatedDrugGroup(stored)).thenReturn(stored); + + assertSuccessContaining(controller.updateDrugGroup("{\"drugGroupID\":201,\"drugGroup\":\"Analgesics\"," + + "\"drugGroupDesc\":\"Pain relief\",\"modifiedBy\":\"admin\"}"), "Analgesics"); + assertEquals("Pain relief", stored.getDrugGroupDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("updateDrugGroup should answer an error envelope for a group that does not exist") + void updateDrugGroup_shouldAnswerErrorEnvelopeForUnknownGroup() { + when(drugMasterInter.getDrugGroupById(201)).thenReturn(null); + + assertCodeException(controller.updateDrugGroup("{\"drugGroupID\":201}")); + } + + @Test + @DisplayName("updateDrugMaster should copy the edited fields onto the stored drug") + void updateDrugMaster_shouldCopyEditedFields() { + M_104drugmaster stored = drug(101, "old name"); + when(drugMasterInter.getDrugDataById(101)).thenReturn(stored); + when(drugMasterInter.saveUpdatedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.updateDrugMaster("{\"drugID\":101,\"drugName\":\"Paracetamol\"," + + "\"drugDesc\":\"Antipyretic\",\"remarks\":\"OTC\",\"modifiedBy\":\"admin\"}"), "Paracetamol"); + assertEquals("Antipyretic", stored.getDrugDesc()); + assertEquals("OTC", stored.getRemarks()); + } + + @Test + @DisplayName("updateDrugMaster should answer an error envelope for a drug that does not exist") + void updateDrugMaster_shouldAnswerErrorEnvelopeForUnknownDrug() { + when(drugMasterInter.getDrugDataById(101)).thenReturn(null); + + assertCodeException(controller.updateDrugMaster("{\"drugID\":101}")); + } + + @Test + @DisplayName("updateDrugMapping should copy the edited fields onto the stored mapping") + void updateDrugMapping_shouldCopyEditedFields() { + M_104drugmapping stored = drugMapping(301, "old name"); + when(drugMasterInter.getDrugMappingsById(301)).thenReturn(stored); + when(drugMasterInter.saveUpdatedDrugMapping(stored)).thenReturn(stored); + + assertSuccessContaining(controller.updateDrugMapping("{\"drugMapID\":301,\"drugGroupID\":201," + + "\"drugGroupName\":\"Analgesics\",\"drugId\":101,\"drugName\":\"Paracetamol\"," + + "\"remarks\":\"OTC\"}"), "Paracetamol"); + assertEquals(201, stored.getDrugGroupID()); + assertEquals("Analgesics", stored.getDrugGroupName()); + } + + @Test + @DisplayName("updateDrugMapping should answer an error envelope for a mapping that does not exist") + void updateDrugMapping_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(drugMasterInter.getDrugMappingsById(301)).thenReturn(null); + + assertCodeException(controller.updateDrugMapping("{\"drugMapID\":301}")); + } + + @Test + @DisplayName("updateDrugStatus should retire the drug when the request names one") + void updateDrugStatus_shouldRetireDrug() { + when(drugMasterInter.updateDrugStatus(any())).thenReturn(1); + + assertSuccessContaining(controller.updateDrugStatus("{\"drugID\":101,\"deleted\":true}"), + "Drug status updaated to deleted"); + verify(drugMasterInter).updateDrugStatus(any()); + verify(drugMasterInter, never()).updateDrugGroupStatus(any()); + } + + @Test + @DisplayName("updateDrugStatus should retire the drug group when no drug is named") + void updateDrugStatus_shouldRetireDrugGroup() { + when(drugMasterInter.updateDrugGroupStatus(any())).thenReturn(1); + + assertSuccessContaining(controller.updateDrugStatus("{\"drugGroupID\":201,\"deleted\":true}"), + "DrugGroup status updaated to deleted"); + verify(drugMasterInter).updateDrugGroupStatus(any()); + verify(drugMasterInter, never()).updateDrugStatus(any()); + } + + @Test + @DisplayName("updateDrugStatus should retire the mapping when neither a drug nor a group is named") + void updateDrugStatus_shouldRetireDrugMapping() { + when(drugMasterInter.updateDrugMappingStatus(any())).thenReturn(1); + + assertSuccessContaining(controller.updateDrugStatus("{\"drugMapID\":301,\"deleted\":true}"), + "DrugGroup status updaated to deleted"); + verify(drugMasterInter).updateDrugMappingStatus(any()); + } + + @Test + @DisplayName("updateDrugStatus should answer an empty response when the request names nothing to retire") + void updateDrugStatus_shouldAnswerEmptyResponseWhenNothingNamed() { + assertSuccessContaining(controller.updateDrugStatus("{}"), "response"); + verify(drugMasterInter, never()).updateDrugStatus(any()); + verify(drugMasterInter, never()).updateDrugGroupStatus(any()); + verify(drugMasterInter, never()).updateDrugMappingStatus(any()); + } + + @Test + @DisplayName("updateDrugStatus should answer an error envelope when the retirement fails") + void updateDrugStatus_shouldAnswerErrorEnvelopeOnFailure() { + when(drugMasterInter.updateDrugStatus(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.updateDrugStatus("{\"drugID\":101,\"deleted\":true}")); + } + + @Test + @DisplayName("saveDrugGroup should answer the groups the service stored") + void saveDrugGroup_shouldAnswerStoredGroups() throws IEMRException { + ArrayList stored = new ArrayList<>(List.of(drugGroup(201, "Analgesics"))); + when(drugMasterInter.saveDrugGroup(anyList())).thenReturn(stored); + + assertSuccessContaining( + controller.saveDrugGroup("{\"drugGroups\":[{\"drugGroup\":\"Analgesics\"}]}"), "Analgesics"); + } + + @Test + @DisplayName("saveDrugGroup should answer an error envelope when no groups are named") + void saveDrugGroup_shouldAnswerErrorEnvelopeWithoutGroups() throws IEMRException { + when(drugMasterInter.saveDrugGroup(eq(null))).thenThrow(new IllegalArgumentException("nothing to save")); + + assertGenericFailure(controller.saveDrugGroup("{}")); + } + + @Test + @DisplayName("saveDrug should answer the drugs the service stored") + void saveDrug_shouldAnswerStoredDrugs() throws IEMRException { + ArrayList stored = new ArrayList<>(List.of(drug(101, "Paracetamol"))); + when(drugMasterInter.saveDrugData(anyList())).thenReturn(stored); + + assertSuccessContaining( + controller.saveDrug("{\"drugMasters\":[{\"drugName\":\"Paracetamol\"}]}"), "Paracetamol"); + } + + @Test + @DisplayName("saveDrug should answer an error envelope when the store fails") + void saveDrug_shouldAnswerErrorEnvelopeOnFailure() throws IEMRException { + when(drugMasterInter.saveDrugData(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveDrug("{\"drugMasters\":[{\"drugName\":\"Paracetamol\"}]}")); + } + + @Test + @DisplayName("mapDrugWithGroup should answer the mappings the service stored") + void mapDrugWithGroup_shouldAnswerStoredMappings() throws IEMRException { + ArrayList stored = new ArrayList<>(List.of(drugMapping(301, "Paracetamol"))); + when(drugMasterInter.mapDrugWithGroup(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.mapDrugWithGroup( + "{\"drugMappings\":[{\"drugId\":101,\"drugGroupID\":201}]}"), "Paracetamol"); + } + + @Test + @DisplayName("mapDrugWithGroup should answer an error envelope when the mapping fails") + void mapDrugWithGroup_shouldAnswerErrorEnvelopeOnFailure() throws IEMRException { + when(drugMasterInter.mapDrugWithGroup(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.mapDrugWithGroup("{\"drugMappings\":[{\"drugId\":101}]}")); + } + + @Test + @DisplayName("getDrugData should pass the deleted flag the caller sends straight through") + void getDrugData_shouldPassDeletedFlagThrough() throws IEMRException { + when(drugMasterInter.getAllDrugData(anyInt(), any(), eq(Boolean.TRUE))).thenReturn(new ArrayList<>()); + + controller.getDrugData("{\"drugID\":101,\"serviceProviderID\":77,\"deleted\":true}"); + + verify(drugMasterInter).getAllDrugData(101, (short) 77, Boolean.TRUE); + } +} diff --git a/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardFixture.java b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardFixture.java new file mode 100644 index 0000000..6047603 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardFixture.java @@ -0,0 +1,131 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.provideronboard; + +import org.json.JSONObject; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.service.provideronboard.Calltypeinter; +import com.iemr.admin.service.provideronboard.CategoryInter; +import com.iemr.admin.service.provideronboard.DrugMasterInter; +import com.iemr.admin.service.provideronboard.InstuteDirectoryInter; +import com.iemr.admin.service.provideronboard.M_FeedbacknatureInteger; +import com.iemr.admin.service.provideronboard.M_FeedbacktypeInter; +import com.iemr.admin.service.provideronboard.M_InstitutedirectorymappingInter; +import com.iemr.admin.service.provideronboard.M_InstitutesubdirectoryInter; +import com.iemr.admin.service.provideronboard.M_InstitutionInter; +import com.iemr.admin.service.provideronboard.M_InstitutiontypeInter; +import com.iemr.admin.service.provideronboard.M_ServiceMasterInter; +import com.iemr.admin.service.provideronboard.M_SeverityInter; +import com.iemr.admin.service.provideronboard.ServiceProvider_ServiceImpl; +import com.iemr.admin.service.provideronboard.SubServiceInter; +import com.iemr.admin.service.user.IemrUserServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The onboarding controller reaches thirteen collaborating services, so every + * suite over it shares this fixture rather than re-declaring the same mocks. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +abstract class ProviderOnBoardFixture { + + @Mock + protected M_InstitutedirectorymappingInter m_InstitutedirectorymappingInter; + + @Mock + protected M_InstitutesubdirectoryInter m_InstitutesubdirectoryInter; + + @Mock + protected M_InstitutionInter m_InstitutionInter; + + @Mock + protected M_FeedbacknatureInteger m_FeedbacknatureInteger; + + @Mock + protected M_InstitutiontypeInter m_InstitutiontypeInter; + + @Mock + protected InstuteDirectoryInter instuteDirectoryInter; + + @Mock + protected M_FeedbacktypeInter m_FeedbacktypeInter; + + @Mock + protected M_SeverityInter m_ServerityInter; + + @Mock + protected DrugMasterInter drugMasterInter; + + @Mock + protected CategoryInter categoryInter; + + @Mock + protected SubServiceInter subServiceInter; + + @Mock + protected Calltypeinter calltypeinter; + + @Mock + protected M_ServiceMasterInter m_ServiceMasterInter; + + @Mock + protected ServiceProvider_ServiceImpl serviceProvider_ServiceImpl; + + @Mock + protected IemrUserServiceImpl iemrUserServiceImpl; + + @InjectMocks + protected ProviderOnBoardController controller; + + /** Reads the status code out of the JSON envelope the controller answers with. */ + protected static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + /** Asserts the envelope reports success and carries the given fragment. */ + protected static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + /** Asserts the envelope reports the generic failure the controllers fall back to. */ + protected static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + /** + * Asserts the envelope reports the code-level failure raised when a controller + * works on a record the service could not resolve. + */ + protected static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } +} diff --git a/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardInstituteControllerTest.java b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardInstituteControllerTest.java new file mode 100644 index 0000000..286ca6c --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardInstituteControllerTest.java @@ -0,0 +1,557 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.provideronboard; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.google.gson.JsonObject; +import com.iemr.admin.data.provideronboard.M_Institutedirectory; +import com.iemr.admin.data.provideronboard.M_Institutedirectorymapping; +import com.iemr.admin.data.provideronboard.M_Institutesubdirectory; +import com.iemr.admin.data.provideronboard.M_Institution; +import com.iemr.admin.data.provideronboard.M_Institutiontype; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The institute endpoints keep the referral directory a provider's agents route + * callers to - the directories, their sub-directories, and the institutions + * mapped underneath them. + */ +@DisplayName("ProviderOnBoardController institute Test Suite") +class ProviderOnBoardInstituteControllerTest extends ProviderOnBoardFixture { + + private static M_Institutedirectory directory(Integer id, String name) { + M_Institutedirectory directory = new M_Institutedirectory(); + directory.setInstituteDirectoryID(id); + directory.setInstituteDirectoryName(name); + return directory; + } + + private static M_Institutiontype instituteType(Integer id, String name) { + M_Institutiontype type = new M_Institutiontype(); + type.setInstitutionTypeID(id); + type.setInstitutionType(name); + return type; + } + + private static M_Institution institution(Integer id, String name) { + M_Institution institution = new M_Institution(); + institution.setInstitutionID(id); + institution.setInstitutionName(name); + return institution; + } + + private static M_Institutesubdirectory subDirectory(Integer id, String name) { + M_Institutesubdirectory subDirectory = new M_Institutesubdirectory(); + subDirectory.setInstituteSubDirectoryID(id); + subDirectory.setInstituteSubDirectoryName(name); + return subDirectory; + } + + private static M_Institutedirectorymapping directoryMapping(Integer id, Integer institutionId) { + M_Institutedirectorymapping mapping = new M_Institutedirectorymapping(); + mapping.setInstituteDirMapID(id); + mapping.setInstitutionID(institutionId); + return mapping; + } + + @Test + @DisplayName("createInstuteDirectoty should answer the directories the service stored") + void createInstuteDirectoty_shouldAnswerStoredDirectories() { + ArrayList stored = new ArrayList<>(List.of(directory(11, "Hospitals"))); + when(instuteDirectoryInter.createInstuteDirectory(anyList())).thenReturn(stored); + + assertSuccessContaining( + controller.createInstuteDirectoty("[{\"instituteDirectoryName\":\"Hospitals\"}]"), "Hospitals"); + } + + @Test + @DisplayName("createInstuteDirectoty should answer an error envelope when the store fails") + void createInstuteDirectoty_shouldAnswerErrorEnvelopeOnFailure() { + when(instuteDirectoryInter.createInstuteDirectory(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createInstuteDirectoty("[{\"instituteDirectoryName\":\"Hospitals\"}]")); + } + + @Test + @DisplayName("getInstuteDirectory should answer the directories configured for the mapping") + void getInstuteDirectory_shouldAnswerConfiguredDirectories() { + ArrayList stored = new ArrayList<>(List.of(directory(11, "Hospitals"))); + when(instuteDirectoryInter.getInstuteDirectory(4001)).thenReturn(stored); + + assertSuccessContaining(controller.getInstuteDirectory("{\"providerServiceMapId\":4001}"), "Hospitals"); + } + + @Test + @DisplayName("getInstuteDirectory should answer an error envelope when the lookup fails") + void getInstuteDirectory_shouldAnswerErrorEnvelopeOnFailure() { + when(instuteDirectoryInter.getInstuteDirectory(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getInstuteDirectory("{\"providerServiceMapId\":4001}")); + } + + @Test + @DisplayName("editInstuteDirectory should copy the edited fields onto the stored directory") + void editInstuteDirectory_shouldCopyEditedFields() { + M_Institutedirectory stored = directory(11, "old name"); + when(instuteDirectoryInter.editInstuteDirectory(11)).thenReturn(stored); + when(instuteDirectoryInter.editdata(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editInstuteDirectory("{\"instituteDirectoryID\":11," + + "\"instituteDirectoryName\":\"Hospitals\",\"instituteDirectoryDesc\":\"Referral hospitals\"," + + "\"modifiedBy\":\"admin\"}"), "Hospitals"); + assertEquals("Referral hospitals", stored.getInstituteDirectoryDesc()); + } + + @Test + @DisplayName("editInstuteDirectory should answer an error envelope for a directory that does not exist") + void editInstuteDirectory_shouldAnswerErrorEnvelopeForUnknownDirectory() { + when(instuteDirectoryInter.editInstuteDirectory(11)).thenReturn(null); + + assertCodeException(controller.editInstuteDirectory("{\"instituteDirectoryID\":11}")); + } + + @Test + @DisplayName("deleteInstuteDirectory should mark the directory deleted") + void deleteInstuteDirectory_shouldMarkDirectoryDeleted() { + M_Institutedirectory stored = directory(11, "Hospitals"); + when(instuteDirectoryInter.editInstuteDirectory(11)).thenReturn(stored); + when(instuteDirectoryInter.editdata(stored)).thenReturn(stored); + + assertSuccessContaining( + controller.deleteInstuteDirectory("{\"instituteDirectoryID\":11,\"deleted\":true}"), "Hospitals"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteInstuteDirectory should answer an error envelope for a directory that does not exist") + void deleteInstuteDirectory_shouldAnswerErrorEnvelopeForUnknownDirectory() { + when(instuteDirectoryInter.editInstuteDirectory(11)).thenReturn(null); + + assertCodeException(controller.deleteInstuteDirectory("{\"instituteDirectoryID\":11,\"deleted\":true}")); + } + + @Test + @DisplayName("getInstuteType should answer the institute types configured for the mapping") + void getInstuteType_shouldAnswerConfiguredTypes() { + ArrayList stored = new ArrayList<>(List.of(instituteType(21, "PHC"))); + when(m_InstitutiontypeInter.getInstuteType(4001)).thenReturn(stored); + + assertSuccessContaining(controller.getInstuteType("{\"providerServiceMapID\":4001}"), "PHC"); + } + + @Test + @DisplayName("getInstuteType should answer an error envelope when the lookup fails") + void getInstuteType_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutiontypeInter.getInstuteType(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getInstuteType("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("createInstuteType should answer the institute types the service stored") + void createInstuteType_shouldAnswerStoredTypes() { + ArrayList stored = new ArrayList<>(List.of(instituteType(21, "PHC"))); + when(m_InstitutiontypeInter.createInstuteType(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createInstuteType("[{\"institutionType\":\"PHC\"}]"), "PHC"); + } + + @Test + @DisplayName("createInstuteType should answer an error envelope when the store fails") + void createInstuteType_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutiontypeInter.createInstuteType(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createInstuteType("[{\"institutionType\":\"PHC\"}]")); + } + + @Test + @DisplayName("editInstuteType should copy the edited fields onto the stored institute type") + void editInstuteType_shouldCopyEditedFields() { + M_Institutiontype stored = instituteType(21, "old name"); + when(m_InstitutiontypeInter.editInstuteType(21)).thenReturn(stored); + when(m_InstitutiontypeInter.saveEditdata(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editInstuteType("{\"institutionTypeID\":21,\"institutionType\":\"PHC\"," + + "\"institutionTypeDesc\":\"Primary health centre\",\"modifiedBy\":\"admin\"}"), "PHC"); + assertEquals("Primary health centre", stored.getInstitutionTypeDesc()); + } + + @Test + @DisplayName("editInstuteType should answer an error envelope for an institute type that does not exist") + void editInstuteType_shouldAnswerErrorEnvelopeForUnknownType() { + when(m_InstitutiontypeInter.editInstuteType(21)).thenReturn(null); + + assertCodeException(controller.editInstuteType("{\"institutionTypeID\":21}")); + } + + @Test + @DisplayName("deleteInstuteType should mark the institute type deleted") + void deleteInstuteType_shouldMarkTypeDeleted() { + M_Institutiontype stored = instituteType(21, "PHC"); + when(m_InstitutiontypeInter.editInstuteType(21)).thenReturn(stored); + when(m_InstitutiontypeInter.saveEditdata(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteInstuteType("{\"institutionTypeID\":21,\"deleted\":true}"), "PHC"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteInstuteType should answer an error envelope for an institute type that does not exist") + void deleteInstuteType_shouldAnswerErrorEnvelopeForUnknownType() { + when(m_InstitutiontypeInter.editInstuteType(21)).thenReturn(null); + + assertCodeException(controller.deleteInstuteType("{\"institutionTypeID\":21,\"deleted\":true}")); + } + + @Test + @DisplayName("createInstuteTypeByDist should answer the institute types the service stored") + void createInstuteTypeByDist_shouldAnswerStoredTypes() { + ArrayList stored = new ArrayList<>(List.of(instituteType(21, "PHC"))); + when(m_InstitutiontypeInter.createInstuteType(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createInstuteTypeByDist("[{\"institutionType\":\"PHC\"}]"), "PHC"); + } + + @Test + @DisplayName("createInstuteTypeByDist should answer an error envelope when the store fails") + void createInstuteTypeByDist_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutiontypeInter.createInstuteType(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createInstuteTypeByDist("[{\"institutionType\":\"PHC\"}]")); + } + + @Test + @DisplayName("getInstituteTypeByDist should answer the institute types under the location") + void getInstituteTypeByDist_shouldAnswerTypesUnderLocation() { + ArrayList stored = new ArrayList<>(List.of(instituteType(21, "PHC"))); + when(m_InstitutiontypeInter.getInstuteTypeByDist(4001, 31, 41, 51)).thenReturn(stored); + + assertSuccessContaining(controller.getInstituteTypeByDist("{\"providerServiceMapID\":4001," + + "\"districtId\":31,\"subDistrictId\":41,\"villageId\":51}"), "PHC"); + } + + @Test + @DisplayName("getInstituteTypeByDist should answer an error envelope when the lookup fails") + void getInstituteTypeByDist_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutiontypeInter.getInstuteTypeByDist(any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getInstituteTypeByDist("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getInstution should answer the institutions under the block") + void getInstution_shouldAnswerInstitutionsUnderBlock() { + ArrayList stored = new ArrayList<>(List.of(institution(31, "District Hospital"))); + when(m_InstitutionInter.getInstution(4001, 29, 301, 401)).thenReturn(stored); + + assertSuccessContaining(controller.getInstution("{\"providerServiceMapID\":4001,\"stateID\":29," + + "\"districtID\":301,\"blockID\":401}"), "District Hospital"); + } + + @Test + @DisplayName("getInstution should answer an error envelope when the lookup fails") + void getInstution_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutionInter.getInstution(any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getInstution("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("createInstution should answer the institutions the service stored") + void createInstution_shouldAnswerStoredInstitutions() { + ArrayList stored = new ArrayList<>(List.of(institution(31, "District Hospital"))); + when(m_InstitutionInter.createInstution(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createInstution("[{\"institutionName\":\"District Hospital\"}]"), + "District Hospital"); + } + + @Test + @DisplayName("createInstution should answer an error envelope when the store fails") + void createInstution_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutionInter.createInstution(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createInstution("[{\"institutionName\":\"District Hospital\"}]")); + } + + @Test + @DisplayName("editInstution should copy every contact field onto the stored institution") + void editInstution_shouldCopyContactFields() { + M_Institution stored = institution(31, "old name"); + when(m_InstitutionInter.editInstution(31)).thenReturn(stored); + when(m_InstitutionInter.saveEditData(stored)).thenReturn(stored); + + String response = controller.editInstution("{\"institutionID\":31," + + "\"institutionName\":\"District Hospital\",\"address\":\"Main Road\",\"contactNo1\":\"9000000001\"," + + "\"contactNo2\":\"9000000002\",\"contactNo3\":\"9000000003\",\"contactPerson1\":\"Asha\"," + + "\"contactPerson2\":\"Ravi\",\"contactPerson3\":\"Meera\"," + + "\"contactPerson1_Email\":\"asha@example.org\",\"contactPerson2_Email\":\"ravi@example.org\"," + + "\"contactPerson3_Email\":\"meera@example.org\",\"website\":\"https://example.org\"}"); + + assertSuccessContaining(response, "District Hospital"); + assertEquals("Main Road", stored.getAddress()); + assertEquals("asha@example.org", stored.getContactPerson1_Email()); + assertEquals("https://example.org", stored.getWebsite()); + } + + @Test + @DisplayName("editInstution should answer an error envelope for an institution that does not exist") + void editInstution_shouldAnswerErrorEnvelopeForUnknownInstitution() { + when(m_InstitutionInter.editInstution(31)).thenReturn(null); + + assertCodeException(controller.editInstution("{\"institutionID\":31}")); + } + + @Test + @DisplayName("deleteInstution should mark the institution deleted") + void deleteInstution_shouldMarkInstitutionDeleted() { + M_Institution stored = institution(31, "District Hospital"); + when(m_InstitutionInter.editInstution(31)).thenReturn(stored); + when(m_InstitutionInter.saveEditData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteInstution("{\"institutionID\":31,\"deleted\":true}"), + "District Hospital"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteInstution should answer an error envelope for an institution that does not exist") + void deleteInstution_shouldAnswerErrorEnvelopeForUnknownInstitution() { + when(m_InstitutionInter.editInstution(31)).thenReturn(null); + + assertCodeException(controller.deleteInstution("{\"institutionID\":31,\"deleted\":true}")); + } + + @Test + @DisplayName("createInstutionByVillage should answer the institutions the service stored") + void createInstutionByVillage_shouldAnswerStoredInstitutions() { + ArrayList stored = new ArrayList<>(List.of(institution(31, "Sub Centre"))); + when(m_InstitutionInter.createInstutionByVillage(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createInstutionByVillage("[{\"institutionName\":\"Sub Centre\"}]"), + "Sub Centre"); + } + + @Test + @DisplayName("createInstutionByVillage should answer an error envelope when the store fails") + void createInstutionByVillage_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutionInter.createInstutionByVillage(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createInstutionByVillage("[{\"institutionName\":\"Sub Centre\"}]")); + } + + @Test + @DisplayName("getInstutionByVillage should answer the institutions under the village") + void getInstutionByVillage_shouldAnswerInstitutionsUnderVillage() { + ArrayList stored = new ArrayList<>(List.of(institution(31, "Sub Centre"))); + when(m_InstitutionInter.getInstutionByVillage(4001, 29, 301, 401, 501)).thenReturn(stored); + + assertSuccessContaining(controller.getInstutionByVillage("{\"providerServiceMapID\":4001,\"stateID\":29," + + "\"districtID\":301,\"blockID\":401,\"villageID\":501}"), "Sub Centre"); + } + + @Test + @DisplayName("getInstutionByVillage should answer an error envelope when the lookup fails") + void getInstutionByVillage_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutionInter.getInstutionByVillage(any(), any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getInstutionByVillage("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("createInstitutionByFile should answer the summary the service reports for the upload") + void createInstitutionByFile_shouldAnswerUploadSummary() { + when(m_InstitutionInter.createInstitutionByFile(any(JsonObject.class))).thenReturn("12 institutions created"); + + assertSuccessContaining(controller.createInstitutionByFile("{\"fileName\":\"institutions.xlsx\"}"), + "12 institutions created"); + } + + @Test + @DisplayName("createInstitutionByFile should answer an error envelope for a payload that is not an object") + void createInstitutionByFile_shouldAnswerErrorEnvelopeForNonObjectPayload() { + assertGenericFailure(controller.createInstitutionByFile("\"just a string\"")); + } + + @Test + @DisplayName("getInstuteSubDirectory should answer the sub-directories under the directory") + void getInstuteSubDirectory_shouldAnswerSubDirectories() { + ArrayList stored = new ArrayList<>(List.of(subDirectory(41, "Government"))); + when(m_InstitutesubdirectoryInter.getInstutesubDirectory(11, 4001)).thenReturn(stored); + + assertSuccessContaining( + controller.getInstuteSubDirectory("{\"instituteDirectoryID\":11,\"providerServiceMapId\":4001}"), + "Government"); + } + + @Test + @DisplayName("getInstuteSubDirectory should answer an error envelope when the lookup fails") + void getInstuteSubDirectory_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutesubdirectoryInter.getInstutesubDirectory(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getInstuteSubDirectory("{\"instituteDirectoryID\":11}")); + } + + @Test + @DisplayName("createInstuteSubDirectory should answer the sub-directories the service stored") + void createInstuteSubDirectory_shouldAnswerStoredSubDirectories() { + ArrayList stored = new ArrayList<>(List.of(subDirectory(41, "Government"))); + when(m_InstitutesubdirectoryInter.CreateInstutesubDirectory(anyList())).thenReturn(stored); + + assertSuccessContaining( + controller.createInstuteSubDirectory("[{\"instituteSubDirectoryName\":\"Government\"}]"), + "Government"); + } + + @Test + @DisplayName("createInstuteSubDirectory should answer an error envelope when the store fails") + void createInstuteSubDirectory_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutesubdirectoryInter.CreateInstutesubDirectory(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createInstuteSubDirectory("[{\"instituteSubDirectoryName\":\"Government\"}]")); + } + + @Test + @DisplayName("editInstuteSubDirectory should copy the edited fields onto the stored sub-directory") + void editInstuteSubDirectory_shouldCopyEditedFields() { + M_Institutesubdirectory stored = subDirectory(41, "old name"); + when(m_InstitutesubdirectoryInter.editInstutesubDirectory(41)).thenReturn(stored); + when(m_InstitutesubdirectoryInter.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editInstuteSubDirectory("{\"instituteSubDirectoryID\":41," + + "\"instituteSubDirectoryName\":\"Government\",\"instituteSubDirectoryDesc\":\"State run\"," + + "\"modifiedBy\":\"admin\"}"), "Government"); + assertEquals("State run", stored.getInstituteSubDirectoryDesc()); + } + + @Test + @DisplayName("editInstuteSubDirectory should answer an error envelope for a sub-directory that does not exist") + void editInstuteSubDirectory_shouldAnswerErrorEnvelopeForUnknownSubDirectory() { + when(m_InstitutesubdirectoryInter.editInstutesubDirectory(41)).thenReturn(null); + + assertCodeException(controller.editInstuteSubDirectory("{\"instituteSubDirectoryID\":41}")); + } + + @Test + @DisplayName("deleteInstuteSubDirectory should mark the sub-directory deleted") + void deleteInstuteSubDirectory_shouldMarkSubDirectoryDeleted() { + M_Institutesubdirectory stored = subDirectory(41, "Government"); + when(m_InstitutesubdirectoryInter.editInstutesubDirectory(41)).thenReturn(stored); + when(m_InstitutesubdirectoryInter.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining( + controller.deleteInstuteSubDirectory("{\"instituteSubDirectoryID\":41,\"deleted\":true}"), + "Government"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteInstuteSubDirectory should answer an error envelope for a sub-directory that does not exist") + void deleteInstuteSubDirectory_shouldAnswerErrorEnvelopeForUnknownSubDirectory() { + when(m_InstitutesubdirectoryInter.editInstutesubDirectory(41)).thenReturn(null); + + assertCodeException(controller.deleteInstuteSubDirectory("{\"instituteSubDirectoryID\":41,\"deleted\":true}")); + } + + @Test + @DisplayName("createInstuteSubDirectoryMaping should answer the mappings the service stored") + void createInstuteSubDirectoryMaping_shouldAnswerStoredMappings() { + ArrayList stored = + new ArrayList<>(List.of(directoryMapping(51, 31))); + when(m_InstitutedirectorymappingInter.createInstituteDirectoryData(anyList())).thenReturn(stored); + + assertSuccessContaining( + controller.createInstuteSubDirectoryMaping("[{\"institutionID\":31,\"instituteDirectoryID\":11}]"), + "51"); + } + + @Test + @DisplayName("createInstuteSubDirectoryMaping should answer an error envelope when the store fails") + void createInstuteSubDirectoryMaping_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutedirectorymappingInter.createInstituteDirectoryData(anyList())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createInstuteSubDirectoryMaping("[{\"institutionID\":31}]")); + } + + @Test + @DisplayName("deleteInstuteSubDirectoryMaping should mark the mapping deleted") + void deleteInstuteSubDirectoryMaping_shouldMarkMappingDeleted() { + M_Institutedirectorymapping stored = directoryMapping(51, 31); + when(m_InstitutedirectorymappingInter.deleteInstituteDirectoryData(51)).thenReturn(stored); + when(m_InstitutedirectorymappingInter.setdeletedData(stored)).thenReturn(stored); + + assertSuccessContaining( + controller.deleteInstuteSubDirectoryMaping("{\"instituteDirMapID\":51,\"deleted\":true}"), + "51"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteInstuteSubDirectoryMaping should answer an error envelope for a mapping that does not exist") + void deleteInstuteSubDirectoryMaping_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(m_InstitutedirectorymappingInter.deleteInstituteDirectoryData(51)).thenReturn(null); + + assertCodeException( + controller.deleteInstuteSubDirectoryMaping("{\"instituteDirMapID\":51,\"deleted\":true}")); + } + + @Test + @DisplayName("getInstuteSubDirectoryMaping should answer the mappings under the sub-directory") + void getInstuteSubDirectoryMaping_shouldAnswerMappings() { + ArrayList stored = + new ArrayList<>(List.of(directoryMapping(51, 31))); + when(m_InstitutedirectorymappingInter.getInstituteDirectoryData(41)).thenReturn(stored); + + assertSuccessContaining(controller.getInstuteSubDirectoryMaping("{\"instituteSubDirectoryID\":41}"), "51"); + } + + @Test + @DisplayName("getInstuteSubDirectoryMaping should answer an error envelope when the lookup fails") + void getInstuteSubDirectoryMaping_shouldAnswerErrorEnvelopeOnFailure() { + when(m_InstitutedirectorymappingInter.getInstituteDirectoryData(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getInstuteSubDirectoryMaping("{\"instituteSubDirectoryID\":41}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardProviderControllerTest.java b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardProviderControllerTest.java new file mode 100644 index 0000000..51e7a6e --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/provideronboard/ProviderOnBoardProviderControllerTest.java @@ -0,0 +1,379 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.provideronboard; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.data.provideronboard.M_ProviderServiceMapping; +import com.iemr.admin.data.provideronboard.M_ServiceMaster; +import com.iemr.admin.data.provideronboard.M_UserservicerolemappingForRole; +import com.iemr.admin.data.provideronboard.ServiceProvider_Model; +import com.iemr.admin.data.provideronboard.V_Showprovideradmin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The provider endpoints onboard a service provider, map it onto the states and + * services it serves, and keep its provider-admin users attached to it. + */ +@DisplayName("ProviderOnBoardController provider Test Suite") +class ProviderOnBoardProviderControllerTest extends ProviderOnBoardFixture { + + private static final String ONBOARD_REQUEST = "{\"serviceProviderName\":\"Piramal Swasthya\"," + + "\"statusID\":1,\"createdBy\":\"admin\",\"providerAdminDetails\":[{\"userName\":\"pa.user\"}]," + + "\"stateAndServiceMapList\":[{\"stateId\":\"29\",\"services\":[\"1\",\"3\"]}]}"; + + private static ServiceProvider_Model provider(Integer id, String name) { + ServiceProvider_Model provider = new ServiceProvider_Model(); + provider.setServiceProviderId(id); + provider.setServiceProviderName(name); + return provider; + } + + @Test + @DisplayName("providerCreationAndMapping should report success once the admin role is created") + void providerCreationAndMapping_shouldReportSuccess() { + M_ProviderServiceMapping mapping = new M_ProviderServiceMapping(); + mapping.setProviderServiceMapID(4001); + when(serviceProvider_ServiceImpl.createProvider(any(java.util.Set.class))).thenReturn(77); + when(serviceProvider_ServiceImpl.mapProviderStateService(any())).thenReturn(List.of(mapping)); + when(iemrUserServiceImpl.createUser(any(), anyString())).thenReturn(3117); + when(iemrUserServiceImpl.createUserServiceRoleMapping(anyList(), anyInt(), anyString())).thenReturn(1); + + String response = controller.providerCreationAndMapping(ONBOARD_REQUEST); + + assertSuccessContaining(response, "true"); + } + + @Test + @DisplayName("providerCreationAndMapping should report failure when the admin role cannot be created") + void providerCreationAndMapping_shouldReportRoleCreationFailure() { + M_ProviderServiceMapping mapping = new M_ProviderServiceMapping(); + mapping.setProviderServiceMapID(4001); + when(serviceProvider_ServiceImpl.createProvider(any(java.util.Set.class))).thenReturn(77); + when(serviceProvider_ServiceImpl.mapProviderStateService(any())).thenReturn(List.of(mapping)); + when(iemrUserServiceImpl.createUser(any(), anyString())).thenReturn(3117); + when(iemrUserServiceImpl.createUserServiceRoleMapping(anyList(), anyInt(), anyString())).thenReturn(0); + + assertSuccessContaining(controller.providerCreationAndMapping(ONBOARD_REQUEST), "false"); + } + + @Test + @DisplayName("providerCreationAndMapping should report failure when nothing was mapped for the provider") + void providerCreationAndMapping_shouldReportFailureWhenNothingMapped() { + when(serviceProvider_ServiceImpl.createProvider(any(java.util.Set.class))).thenReturn(77); + when(serviceProvider_ServiceImpl.mapProviderStateService(any())).thenReturn(new ArrayList<>()); + when(iemrUserServiceImpl.createUser(any(), anyString())).thenReturn(3117); + + assertSuccessContaining(controller.providerCreationAndMapping(ONBOARD_REQUEST), "false"); + } + + @Test + @DisplayName("providerCreationAndMapping should report failure when the provider itself is not created") + void providerCreationAndMapping_shouldReportFailureWhenProviderNotCreated() { + when(serviceProvider_ServiceImpl.createProvider(any(java.util.Set.class))).thenReturn(0); + + assertSuccessContaining(controller.providerCreationAndMapping(ONBOARD_REQUEST), "false"); + } + + @Test + @DisplayName("providerCreationAndMapping should answer an error envelope when the service fails") + void providerCreationAndMapping_shouldAnswerErrorEnvelopeOnFailure() { + when(serviceProvider_ServiceImpl.createProvider(any(java.util.Set.class))) + .thenThrow(new IllegalStateException("provider store is unavailable")); + + assertGenericFailure(controller.providerCreationAndMapping(ONBOARD_REQUEST)); + } + + @Test + @DisplayName("updateProvider should answer the provider the service saved") + void updateProvider_shouldAnswerSavedProvider() { + when(serviceProvider_ServiceImpl.getProviderData(77)).thenReturn(provider(77, "old name")); + when(serviceProvider_ServiceImpl.upDateProviderDetails(any())) + .thenReturn(provider(77, "Piramal Swasthya")); + + assertSuccessContaining(controller.updateProvider("{\"serviceProviderId\":77," + + "\"serviceProviderName\":\"Piramal Swasthya\"}"), "Piramal Swasthya"); + } + + @Test + @DisplayName("updateProvider should answer an error envelope for a provider that does not exist") + void updateProvider_shouldAnswerErrorEnvelopeForUnknownProvider() { + when(serviceProvider_ServiceImpl.getProviderData(77)).thenReturn(null); + + assertCodeException(controller.updateProvider("{\"serviceProviderId\":77}")); + } + + @Test + @DisplayName("getServiceLine should answer the service lines the master holds") + void getServiceLine_shouldAnswerServiceLines() { + M_ServiceMaster serviceMaster = new M_ServiceMaster(); + serviceMaster.setServiceID(1); + serviceMaster.setServiceName("Tele Medicine"); + when(m_ServiceMasterInter.getAllServiceLine()).thenReturn(List.of(serviceMaster)); + + assertSuccessContaining(controller.getServiceLine("{}"), "Tele Medicine"); + } + + @Test + @DisplayName("getServiceLine should answer an error envelope when the master cannot be read") + void getServiceLine_shouldAnswerErrorEnvelopeOnFailure() { + when(m_ServiceMasterInter.getAllServiceLine()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getServiceLine("{}")); + } + + @Test + @DisplayName("getAllProviderName should answer every provider the service holds") + void getAllProviderName_shouldAnswerEveryProvider() { + ArrayList providers = new ArrayList<>(List.of(provider(77, "Piramal Swasthya"))); + when(serviceProvider_ServiceImpl.getAllProviderName()).thenReturn(providers); + + assertSuccessContaining(controller.getAllProviderName("{}"), "Piramal Swasthya"); + } + + @Test + @DisplayName("getAllProviderName should answer an error envelope when the lookup fails") + void getAllProviderName_shouldAnswerErrorEnvelopeOnFailure() { + when(serviceProvider_ServiceImpl.getAllProviderName()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllProviderName("{}")); + } + + @Test + @DisplayName("getProviderName should report that a name is already taken") + void getProviderName_shouldReportNameTaken() { + when(serviceProvider_ServiceImpl.getProviderName("Piramal Swasthya")).thenReturn("Piramal Swasthya"); + + assertSuccessContaining(controller.getProviderName("{\"serviceProviderName\":\"Piramal Swasthya\"}"), + "provider_name_exists"); + } + + @Test + @DisplayName("getProviderName should report that a name is still free") + void getProviderName_shouldReportNameFree() { + when(serviceProvider_ServiceImpl.getProviderName("Piramal Swasthya")).thenReturn(null); + + assertSuccessContaining(controller.getProviderName("{\"serviceProviderName\":\"Piramal Swasthya\"}"), + "provider_name_doesnt_exist"); + } + + @Test + @DisplayName("getProviderName should answer an error envelope when the check fails") + void getProviderName_shouldAnswerErrorEnvelopeOnFailure() { + when(serviceProvider_ServiceImpl.getProviderName(anyString())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getProviderName("{\"serviceProviderName\":\"Piramal Swasthya\"}")); + } + + @Test + @DisplayName("getProviderId should answer the mapping the service resolves") + void getProviderId_shouldAnswerResolvedMapping() { + M_ProviderServiceMapping mapping = new M_ProviderServiceMapping(); + mapping.setProviderServiceMapID(4001); + mapping.setServiceProviderID(77); + when(serviceProvider_ServiceImpl.getProviderserviceMapId(4001)).thenReturn(mapping); + + assertSuccessContaining(controller.getProviderId("{\"providerServiceMapID\":4001}"), "4001"); + } + + @Test + @DisplayName("getProviderId should answer an error envelope for a mapping that does not exist") + void getProviderId_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(serviceProvider_ServiceImpl.getProviderserviceMapId(4001)).thenReturn(null); + + assertCodeException(controller.getProviderId("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("createProvider should answer the providers the service stored") + void createProvider_shouldAnswerStoredProviders() { + ArrayList stored = new ArrayList<>(List.of(provider(77, "Piramal Swasthya"))); + when(serviceProvider_ServiceImpl.createProvider1(anyList())).thenReturn(stored); + + assertSuccessContaining(controller.createProvider("[{\"serviceProviderName\":\"Piramal Swasthya\"}]"), + "Piramal Swasthya"); + } + + @Test + @DisplayName("createProvider should refuse an empty batch rather than store nothing quietly") + void createProvider_shouldRefuseEmptyBatch() { + assertGenericFailure(controller.createProvider("[]")); + } + + @Test + @DisplayName("providerUpdate should answer the provider the service saved") + void providerUpdate_shouldAnswerSavedProvider() { + when(serviceProvider_ServiceImpl.getProviderData(77)).thenReturn(provider(77, "old name")); + when(serviceProvider_ServiceImpl.upDateProviderDetails(any())) + .thenReturn(provider(77, "Piramal Swasthya")); + + assertSuccessContaining(controller.providerUpdate("{\"serviceProviderId\":77," + + "\"serviceProviderName\":\"Piramal Swasthya\",\"modifiedBy\":\"admin\"}"), "Piramal Swasthya"); + } + + @Test + @DisplayName("providerUpdate should answer an error envelope for a provider that does not exist") + void providerUpdate_shouldAnswerErrorEnvelopeForUnknownProvider() { + when(serviceProvider_ServiceImpl.getProviderData(77)).thenReturn(null); + + assertCodeException(controller.providerUpdate("{\"serviceProviderId\":77}")); + } + + @Test + @DisplayName("providerDelete should mark the provider deleted and answer what was saved") + void providerDelete_shouldMarkProviderDeleted() { + ServiceProvider_Model stored = provider(77, "Piramal Swasthya"); + when(serviceProvider_ServiceImpl.getProviderData(77)).thenReturn(stored); + when(serviceProvider_ServiceImpl.upDateProviderDetails(stored)).thenReturn(stored); + + assertSuccessContaining(controller.providerDelete("{\"serviceProviderId\":77,\"deleted\":true}"), + "Piramal Swasthya"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("providerDelete should answer an error envelope for a provider that does not exist") + void providerDelete_shouldAnswerErrorEnvelopeForUnknownProvider() { + when(serviceProvider_ServiceImpl.getProviderData(77)).thenReturn(null); + + assertCodeException(controller.providerDelete("{\"serviceProviderId\":77,\"deleted\":true}")); + } + + @Test + @DisplayName("updateProviderAdmin should answer the provider the service saved") + void updateProviderAdmin_shouldAnswerSavedProvider() { + when(serviceProvider_ServiceImpl.getProviderData(77)).thenReturn(provider(77, "old name")); + when(serviceProvider_ServiceImpl.upDateProviderDetails(any())) + .thenReturn(provider(77, "Piramal Swasthya")); + + assertSuccessContaining(controller.updateProviderAdmin("{\"serviceProviderId\":77," + + "\"serviceProviderName\":\"Piramal Swasthya\"}"), "Piramal Swasthya"); + } + + @Test + @DisplayName("updateProviderAdmin should answer an error envelope when the provider cannot be read") + void updateProviderAdmin_shouldAnswerErrorEnvelopeOnFailure() { + when(serviceProvider_ServiceImpl.getProviderData(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.updateProviderAdmin("{\"serviceProviderId\":77}")); + } + + @Test + @DisplayName("mappingProviderAdmintoProvider should map the admin onto every mapping it is given") + void mappingProviderAdmintoProvider_shouldMapEveryMapping() { + M_UserservicerolemappingForRole mapped = new M_UserservicerolemappingForRole(); + mapped.setuSRMappingID(9001); + mapped.setUserID(3117); + ArrayList stored = new ArrayList<>(List.of(mapped)); + when(serviceProvider_ServiceImpl.AddUserRole(anyList())).thenReturn(stored); + + String response = controller.mappingProviderAdmintoProvider( + "[{\"userID\":3117,\"createdBy\":\"admin\",\"serviceProviderMapID1\":[4001,4002]}]"); + + assertSuccessContaining(response, "9001"); + verify(serviceProvider_ServiceImpl).AddUserRole(anyList()); + } + + @Test + @DisplayName("mappingProviderAdmintoProvider should answer an error envelope when no mappings are named") + void mappingProviderAdmintoProvider_shouldAnswerErrorEnvelopeWithoutMappings() { + assertCodeException(controller.mappingProviderAdmintoProvider("[{\"userID\":3117}]")); + } + + @Test + @DisplayName("editMappingProviderAdmintoProvider should answer the mapping the service saved") + void editMappingProviderAdmintoProvider_shouldAnswerSavedMapping() { + M_UserservicerolemappingForRole stored = new M_UserservicerolemappingForRole(); + stored.setuSRMappingID(9001); + when(serviceProvider_ServiceImpl.getPADataForEdit(9001)).thenReturn(stored); + when(serviceProvider_ServiceImpl.insertEditedData(stored)).thenReturn(stored); + + String response = controller.editMappingProviderAdmintoProvider( + "{\"uSRMappingID\":9001,\"providerServiceMapID\":4002,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "9001"); + assertEquals(4002, stored.getProviderServiceMapID()); + } + + @Test + @DisplayName("editMappingProviderAdmintoProvider should answer an error envelope for an unknown mapping") + void editMappingProviderAdmintoProvider_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(serviceProvider_ServiceImpl.getPADataForEdit(9001)).thenReturn(null); + + assertCodeException(controller.editMappingProviderAdmintoProvider("{\"uSRMappingID\":9001}")); + } + + @Test + @DisplayName("deleteMappingProviderAdmintoProvider should mark the mapping deleted") + void deleteMappingProviderAdmintoProvider_shouldMarkMappingDeleted() { + M_UserservicerolemappingForRole stored = new M_UserservicerolemappingForRole(); + stored.setuSRMappingID(9001); + when(serviceProvider_ServiceImpl.getPADataForEdit(9001)).thenReturn(stored); + when(serviceProvider_ServiceImpl.insertEditedData(stored)).thenReturn(stored); + + assertSuccessContaining( + controller.deleteMappingProviderAdmintoProvider("{\"uSRMappingID\":9001,\"deleted\":true}"), "9001"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteMappingProviderAdmintoProvider should answer an error envelope for an unknown mapping") + void deleteMappingProviderAdmintoProvider_shouldAnswerErrorEnvelopeForUnknownMapping() { + when(serviceProvider_ServiceImpl.getPADataForEdit(9001)).thenReturn(null); + + assertCodeException(controller.deleteMappingProviderAdmintoProvider("{\"uSRMappingID\":9001}")); + } + + @Test + @DisplayName("getMappingProviderAdmintoProvider should answer the provider admins on record") + void getMappingProviderAdmintoProvider_shouldAnswerProviderAdmins() { + V_Showprovideradmin admin = new V_Showprovideradmin(); + admin.setuSRMappingID(9001); + admin.setFirstName("Asha"); + ArrayList admins = new ArrayList<>(List.of(admin)); + when(serviceProvider_ServiceImpl.getProviderAdmins()).thenReturn(admins); + + assertSuccessContaining(controller.getMappingProviderAdmintoProvider("{}"), "Asha"); + } + + @Test + @DisplayName("getMappingProviderAdmintoProvider should answer an error envelope when the lookup fails") + void getMappingProviderAdmintoProvider_shouldAnswerErrorEnvelopeOnFailure() { + when(serviceProvider_ServiceImpl.getProviderAdmins()).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getMappingProviderAdmintoProvider("{}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/questionnaire/QuestionnaireControllerTest.java b/src/test/java/com/iemr/admin/controller/questionnaire/QuestionnaireControllerTest.java new file mode 100644 index 0000000..e0c63ac --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/questionnaire/QuestionnaireControllerTest.java @@ -0,0 +1,181 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.questionnaire; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.service.questionnaire.QuestionnaireServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * The questionnaire screen keeps the feedback questions a provider asks: adding + * them, listing them, editing them and retiring them. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("QuestionnaireController Test Suite") +class QuestionnaireControllerTest { + + private static final String REQUEST = "{\"providerServiceMapID\":4001,\"questionID\":501}"; + + @Mock + private QuestionnaireServiceImpl questionnaireService; + + @InjectMocks + private QuestionnaireController controller; + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static String errorMessageOf(String response) { + return new JSONObject(response).getString("errorMessage"); + } + + @Test + @DisplayName("saveQuestionnaire should confirm the questions it stored") + void save_shouldConfirmStoredQuestions() throws Exception { + when(questionnaireService.SaveQuestionnaire(anyString())) + .thenReturn("Questionnaire Data Saved Successfully"); + + String response = controller.saveQuestionnaire(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Questionnaire Data Saved Successfully"), response); + } + + @Test + @DisplayName("saveQuestionnaire should report the failure when nothing came back from the service") + void save_shouldReportMissingAnswer() throws Exception { + when(questionnaireService.SaveQuestionnaire(anyString())).thenReturn(null); + + assertEquals("error in saving Questionnaire data", errorMessageOf(controller.saveQuestionnaire(REQUEST))); + } + + @Test + @DisplayName("saveQuestionnaire should report the failure when the questions cannot be stored") + void save_shouldReportStorageFailure() throws Exception { + when(questionnaireService.SaveQuestionnaire(anyString())) + .thenThrow(new Exception("error in saving Questionnaire data")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveQuestionnaire(REQUEST))); + } + + @Test + @DisplayName("getQuestionnaireList should answer the questions the provider asks") + void getList_shouldAnswerProvidersQuestions() { + when(questionnaireService.getQuestionnaireList(anyString())) + .thenReturn("[{\"question\":\"Was the visit useful?\"}]"); + + String response = controller.getQuestionnaireList(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Was the visit useful?"), response); + } + + @Test + @DisplayName("getQuestionnaireList should report the failure when nothing came back from the service") + void getList_shouldReportMissingAnswer() { + when(questionnaireService.getQuestionnaireList(anyString())).thenReturn(null); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getQuestionnaireList(REQUEST))); + } + + @Test + @DisplayName("getQuestionnaireList should report the failure when the list cannot be answered") + void getList_shouldReportLookupFailure() { + when(questionnaireService.getQuestionnaireList(anyString())) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getQuestionnaireList(REQUEST))); + } + + @Test + @DisplayName("deleteQuestionnaire should confirm the question it retired") + void delete_shouldConfirmRetiredQuestion() { + when(questionnaireService.deleteQuestionnaire(anyString())) + .thenReturn("Questionnaire Deleted Successfully"); + + String response = controller.deleteQuestionnaire(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Questionnaire Deleted Successfully"), response); + } + + @Test + @DisplayName("deleteQuestionnaire should report the failure when no question was retired") + void delete_shouldReportNothingRetired() { + when(questionnaireService.deleteQuestionnaire(anyString())).thenReturn(null); + + assertEquals("error occured while deleting question.........", + errorMessageOf(controller.deleteQuestionnaire(REQUEST))); + } + + @Test + @DisplayName("deleteQuestionnaire should report the failure when the retirement cannot be recorded") + void delete_shouldReportStorageFailure() { + when(questionnaireService.deleteQuestionnaire(anyString())).thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.deleteQuestionnaire(REQUEST))); + } + + @Test + @DisplayName("editQuestionnaire should confirm the change it recorded") + void edit_shouldConfirmRecordedChange() { + when(questionnaireService.editQuestionnaire(anyString())).thenReturn("Questionnaire Updated Successfully"); + + String response = controller.editQuestionnaire(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Questionnaire Updated Successfully"), response); + } + + @Test + @DisplayName("editQuestionnaire should report the failure when nothing came back from the service") + void edit_shouldReportMissingAnswer() { + when(questionnaireService.editQuestionnaire(anyString())).thenReturn(null); + + assertEquals("error occured while editing question.........", + errorMessageOf(controller.editQuestionnaire(REQUEST))); + } + + @Test + @DisplayName("editQuestionnaire should report the failure when the change cannot be recorded") + void edit_shouldReportStorageFailure() { + when(questionnaireService.editQuestionnaire(anyString())).thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.editQuestionnaire(REQUEST))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/rolemaster/RoleMasterControllerTest.java b/src/test/java/com/iemr/admin/controller/rolemaster/RoleMasterControllerTest.java new file mode 100644 index 0000000..6db52fa --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/rolemaster/RoleMasterControllerTest.java @@ -0,0 +1,464 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.rolemaster; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.rolemaster.M_Screen; +import com.iemr.admin.data.rolemaster.M_UserservicerolemappingForRoleProviderAdmin; +import com.iemr.admin.data.rolemaster.RoleMaster; +import com.iemr.admin.data.rolemaster.RoleScreenMapping; +import com.iemr.admin.data.rolemaster.StateServiceMapping; +import com.iemr.admin.repository.rolemaster.RoleScreenMappingRepo; +import com.iemr.admin.service.rolemaster.Role_MasterInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The role master endpoints define what each role may see, so a role saved + * without its screens leaves its holders locked out of their own work. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("RoleMasterController Test Suite") +class RoleMasterControllerTest { + + private static final Integer PROVIDER_ID = 77; + private static final Integer PSM_ID = 4001; + + @Mock + private RoleScreenMappingRepo roleScreenMappingRepo; + + @Mock + private Role_MasterInter roleMasterInter; + + @InjectMocks + private RoleMasterController controller; + + private static RoleMaster role(Integer id, String name) { + RoleMaster role = new RoleMaster(); + role.setRoleID(id); + role.setRoleName(name); + role.setProviderServiceMapID(PSM_ID); + role.setCreatedBy("admin"); + return role; + } + + private static StateServiceMapping stateMapping(Integer psmId) { + StateServiceMapping mapping = new StateServiceMapping(); + mapping.setProviderServiceMapID(psmId); + mapping.setServiceProviderID(PROVIDER_ID); + return mapping; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + private static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } + + @Test + @DisplayName("searchRole should answer the states the provider serves") + void searchRole_shouldAnswerServedStates() { + when(roleMasterInter.getStateByServiceProviderId(PROVIDER_ID)) + .thenReturn(new ArrayList<>(List.of(stateMapping(PSM_ID)))); + + assertSuccessContaining(controller.searchRole("{\"serviceProviderID\":77}"), "4001"); + } + + @Test + @DisplayName("searchRole should answer an error envelope when the lookup fails") + void searchRole_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getStateByServiceProviderId(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchRole("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getService should answer the service lines the provider runs in the state") + void getService_shouldAnswerServiceLinesInState() { + when(roleMasterInter.getServiceByServiceProviderIdAndStateId(PROVIDER_ID, 29)) + .thenReturn(new ArrayList<>(List.of(stateMapping(PSM_ID)))); + + assertSuccessContaining(controller.getService("{\"serviceProviderID\":77,\"stateID\":29}"), "4001"); + } + + @Test + @DisplayName("getService should answer an error envelope when the lookup fails") + void getService_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getServiceByServiceProviderIdAndStateId(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getService("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getServiceByProviderId should answer the service lines the user is mapped to") + void getServiceByProviderId_shouldAnswerMappedServiceLines() { + M_UserservicerolemappingForRoleProviderAdmin mapping = + new M_UserservicerolemappingForRoleProviderAdmin(); + mapping.setuSRMappingID(9001); + when(roleMasterInter.getServiceByServiceProviderIds(3117)) + .thenReturn(new ArrayList<>(List.of(mapping))); + + assertSuccessContaining(controller.getServiceByProviderId("{\"userID\":3117}"), "9001"); + } + + @Test + @DisplayName("getServiceByProviderId should answer an error envelope when the lookup fails") + void getServiceByProviderId_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getServiceByServiceProviderIds(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getServiceByProviderId("{\"userID\":3117}")); + } + + @Test + @DisplayName("getStateByProviderIdAndServiceID should pass the national flag through to the service") + void getStateByProviderIdAndServiceID_shouldPassNationalFlagThrough() { + M_UserservicerolemappingForRoleProviderAdmin mapping = + new M_UserservicerolemappingForRoleProviderAdmin(); + mapping.setuSRMappingID(9001); + when(roleMasterInter.getStateByServiceProviderIdAndServiceLines(3117, 3, Boolean.TRUE)) + .thenReturn(new ArrayList<>(List.of(mapping))); + + assertSuccessContaining(controller.getStateByProviderIdAndServiceID( + "{\"userID\":3117,\"serviceID\":3,\"isNational\":true}"), "9001"); + verify(roleMasterInter).getStateByServiceProviderIdAndServiceLines(3117, 3, Boolean.TRUE); + } + + @Test + @DisplayName("getStateByProviderIdAndServiceID should answer an error envelope when the lookup fails") + void getStateByProviderIdAndServiceID_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getStateByServiceProviderIdAndServiceLines(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getStateByProviderIdAndServiceID("{\"userID\":3117}")); + } + + @Test + @DisplayName("getAllRole should resolve the mapping before reading its roles") + void getAllRole_shouldResolveMappingFirst() { + when(roleMasterInter.getAllByMapId(PROVIDER_ID, 29, 3, Boolean.FALSE)) + .thenReturn(new ArrayList<>(List.of(stateMapping(PSM_ID)))); + when(roleMasterInter.getProStateServRoles(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(role(11, "Counsellor")))); + + assertSuccessContaining(controller.getAllRole("{\"serviceProviderID\":77,\"stateID\":29," + + "\"serviceID\":3,\"isNational\":false}"), "Counsellor"); + } + + @Test + @DisplayName("getAllRole should fall back to no mapping when the provider has none in the state") + void getAllRole_shouldFallBackWithoutMapping() { + when(roleMasterInter.getAllByMapId(any(), any(), any(), any())).thenReturn(new ArrayList<>()); + when(roleMasterInter.getProStateServRoles(0)).thenReturn(new ArrayList<>()); + + controller.getAllRole("{\"serviceProviderID\":77}"); + + verify(roleMasterInter).getProStateServRoles(0); + } + + @Test + @DisplayName("getAllRole should answer an error envelope when the lookup fails") + void getAllRole_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getAllByMapId(any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllRole("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getAllRoleNew should read the roles straight off the mapping the caller names") + void getAllRoleNew_shouldReadRolesFromNamedMapping() { + when(roleMasterInter.getProStateServRoles(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(role(11, "Counsellor")))); + + assertSuccessContaining(controller.getAllRoleNew("{\"providerServiceMapID\":4001}"), "Counsellor"); + } + + @Test + @DisplayName("getAllRoleNew should answer an error envelope when the lookup fails") + void getAllRoleNew_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getProStateServRoles(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllRoleNew("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getAllRoles should answer the roles the newer query resolves") + void getAllRoles_shouldAnswerResolvedRoles() { + when(roleMasterInter.getProStateServRolesV1(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(role(11, "Counsellor")))); + + assertSuccessContaining(controller.getAllRoles("{\"providerServiceMapID\":4001}"), "Counsellor"); + } + + @Test + @DisplayName("getAllRoles should answer an error envelope when the lookup fails") + void getAllRoles_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getProStateServRolesV1(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllRoles("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("saveRole should map each screen the request names onto the role it stored") + void saveRole_shouldMapScreensOntoStoredRole() { + when(roleMasterInter.addRole(anyList())).thenReturn(List.of(role(11, "Counsellor"))); + + String response = controller.saveRole("[{\"roleName\":\"Counsellor\",\"providerServiceMapID\":4001," + + "\"createdBy\":\"admin\",\"screenID\":[21,22]}]"); + + assertSuccessContaining(response, "Counsellor"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(roleScreenMappingRepo).saveAll(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertEquals(11, captor.getValue().get(0).getRoleID()); + assertEquals(21, captor.getValue().get(0).getScreenID()); + } + + @Test + @DisplayName("saveRole should leave the screens unmapped when the counts do not line up") + void saveRole_shouldLeaveScreensUnmappedOnMismatch() { + when(roleMasterInter.addRole(anyList())) + .thenReturn(List.of(role(11, "Counsellor"), role(12, "Supervisor"))); + + controller.saveRole("[{\"roleName\":\"Counsellor\",\"screenID\":[21]}]"); + + verify(roleScreenMappingRepo, never()).saveAll(anyList()); + } + + @Test + @DisplayName("saveRole should answer an error envelope when the store fails") + void saveRole_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.addRole(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveRole("[{\"roleName\":\"Counsellor\"}]")); + } + + @Test + @DisplayName("editRole should copy the edits onto the stored role and remap its screen") + void editRole_shouldCopyEditsAndRemapScreen() { + RoleMaster stored = role(11, "old name"); + when(roleMasterInter.getRoleByRoleId(11)).thenReturn(stored); + when(roleMasterInter.modifydata(stored)).thenReturn(stored); + when(roleMasterInter.settingScreenId(31, 21)).thenReturn("screen mapped"); + + String response = controller.editRole("{\"roleID\":11,\"roleName\":\"Counsellor\"," + + "\"roleDesc\":\"Handles counselling calls\",\"sRSMappingID\":31,\"screenID\":21}"); + + assertSuccessContaining(response, "screen mapped"); + assertEquals("Counsellor", stored.getRoleName()); + assertEquals("Handles counselling calls", stored.getRoleDesc()); + } + + @Test + @DisplayName("editRole should answer an error envelope for a role that does not exist") + void editRole_shouldAnswerErrorEnvelopeForUnknownRole() { + when(roleMasterInter.getRoleByRoleId(11)).thenReturn(null); + + assertCodeException(controller.editRole("{\"roleID\":11}")); + } + + @Test + @DisplayName("deleteRole should mark the role deleted and answer the outcome") + void deleteRole_shouldMarkRoleDeleted() { + RoleMaster stored = role(11, "Counsellor"); + when(roleMasterInter.getRoleByRoleId(11)).thenReturn(stored); + when(roleMasterInter.deletedata(stored)).thenReturn("role deleted"); + + assertSuccessContaining(controller.deleteRole("{\"roleID\":11,\"deleted\":true}"), "role deleted"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteRole should answer an error envelope for a role that does not exist") + void deleteRole_shouldAnswerErrorEnvelopeForUnknownRole() { + when(roleMasterInter.getRoleByRoleId(11)).thenReturn(null); + + assertCodeException(controller.deleteRole("{\"roleID\":11,\"deleted\":true}")); + } + + @Test + @DisplayName("searchFeature should answer the screens the service line offers") + void searchFeature_shouldAnswerServiceScreens() { + M_Screen screen = new M_Screen(); + screen.setScreenID(21); + screen.setScreenName("Call handling"); + when(roleMasterInter.getAllFeature(3)).thenReturn(new ArrayList<>(List.of(screen))); + + assertSuccessContaining(controller.searchFeature("{\"serviceID\":3}"), "Call handling"); + } + + @Test + @DisplayName("searchFeature should answer an error envelope when the lookup fails") + void searchFeature_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getAllFeature(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchFeature("{\"serviceID\":3}")); + } + + @Test + @DisplayName("deleteFeature should retire the role the screen mapping points at") + void deleteFeature_shouldRetireMappedRole() { + RoleMaster stored = role(11, "Counsellor"); + when(roleMasterInter.getRoleByRoleId(11)).thenReturn(stored); + when(roleMasterInter.deletedata(stored)).thenReturn("role deleted"); + + assertSuccessContaining(controller.deleteFeature("{\"roleID\":11,\"screenID\":21}"), "role deleted"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteFeature should answer an error envelope for a role that does not exist") + void deleteFeature_shouldAnswerErrorEnvelopeForUnknownRole() { + when(roleMasterInter.getRoleByRoleId(11)).thenReturn(null); + + assertCodeException(controller.deleteFeature("{\"roleID\":11}")); + } + + @Test + @DisplayName("getAllRole1 should resolve the mapping before reading its roles") + void getAllRole1_shouldResolveMappingFirst() { + when(roleMasterInter.getAllByMapId(PROVIDER_ID, 29, 3, Boolean.FALSE)) + .thenReturn(new ArrayList<>(List.of(stateMapping(PSM_ID)))); + when(roleMasterInter.getProStateServRoles1(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(role(11, "Counsellor")))); + + assertSuccessContaining(controller.getAllRole1("{\"serviceProviderID\":77,\"stateID\":29," + + "\"serviceID\":3,\"isNational\":false}"), "Counsellor"); + } + + @Test + @DisplayName("getAllRole1 should answer an error envelope when the lookup fails") + void getAllRole1_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getAllByMapId(any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllRole1("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("editRolefeature should answer the screen mappings the service stored") + void editRolefeature_shouldAnswerStoredMappings() { + RoleScreenMapping mapping = new RoleScreenMapping(); + mapping.setsRSMappingID(31); + when(roleMasterInter.mapfeature(anyList())).thenReturn(List.of(mapping)); + + assertSuccessContaining(controller.editRolefeature("[{\"roleID\":11,\"screenID\":21}]"), "31"); + } + + @Test + @DisplayName("editRolefeature should answer an error envelope when the mapping fails") + void editRolefeature_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.mapfeature(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.editRolefeature("[{\"roleID\":11}]")); + } + + @Test + @DisplayName("searchRoleTM should answer the telemedicine roles under the mapping") + void searchRoleTM_shouldAnswerTelemedicineRoles() { + when(roleMasterInter.getRoleMasterTM(PSM_ID)).thenReturn(List.of(role(11, "TC Specialist"))); + + assertSuccessContaining(controller.searchRoleTM("{\"providerServiceMapID\":4001}"), "TC Specialist"); + } + + @Test + @DisplayName("searchRoleTM should answer an error envelope when the lookup fails") + void searchRoleTM_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getRoleMasterTM(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.searchRoleTM("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getAllRoleActive should answer only the roles still in use") + void getAllRoleActive_shouldAnswerActiveRoles() { + when(roleMasterInter.getProStateServRolesActive(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(role(11, "Counsellor")))); + + assertSuccessContaining(controller.getAllRoleActive("{\"providerServiceMapID\":4001}"), "Counsellor"); + } + + @Test + @DisplayName("getAllRoleActive should answer an error envelope when the lookup fails") + void getAllRoleActive_shouldAnswerErrorEnvelopeOnFailure() { + when(roleMasterInter.getProStateServRolesActive(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllRoleActive("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("configWrapUptime should answer the role the service saved") + void configWrapUptime_shouldAnswerSavedRole() throws Exception { + RoleMaster stored = role(11, "Counsellor"); + when(roleMasterInter.configWrapUpTime(any())).thenReturn(stored); + + assertSuccessContaining(controller.configWrapUptime(role(11, "Counsellor")), "Counsellor"); + } + + @Test + @DisplayName("configWrapUptime should answer an error envelope when the change is refused") + void configWrapUptime_shouldAnswerErrorEnvelopeWhenRefused() throws Exception { + when(roleMasterInter.configWrapUpTime(any())) + .thenThrow(new IllegalStateException("wrap up time must be positive")); + + assertGenericFailure(controller.configWrapUptime(role(11, "Counsellor"))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/servicePoint/ServicePointControllerTest.java b/src/test/java/com/iemr/admin/controller/servicePoint/ServicePointControllerTest.java new file mode 100644 index 0000000..f6eb25d --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/servicePoint/ServicePointControllerTest.java @@ -0,0 +1,274 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.servicePoint; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.DistrictBranchMapping; +import com.iemr.admin.data.servicePoint.M_Servicepoint; +import com.iemr.admin.data.servicePoint.M_Servicepointvillagemap; +import com.iemr.admin.service.servicePoint.ServicePointServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The service point endpoints keep the points a mobile unit halts at and the + * villages each point covers. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ServicePointController Test Suite") +class ServicePointControllerTest { + + private static final Integer POINT_ID = 71; + + @Mock + private ServicePointServiceImpl ServicePointServiceImpl; + + @InjectMocks + private ServicePointController controller; + + private static M_Servicepoint point(Integer id, String name) { + return new M_Servicepoint(id, name, "Weekly halt", "Main Road", 4001, Boolean.FALSE, 1, "India", + 29, "Karnataka", 301, "Bengaluru Urban", 401, "North block", 501, "Hosur", null, 3, + "Mobile Medical Unit", 31, "Hosur parking"); + } + + private static M_Servicepointvillagemap villageMap(Integer id) { + return new M_Servicepointvillagemap(id, 29, "Karnataka", 301, "Bengaluru Urban", 31, "Hosur parking", + 71, "Hosur halt", 501, "Hosur", 4001, Boolean.FALSE); + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("saveServicePoint should answer the service points the service stored") + void saveServicePoint_shouldAnswerStoredPoints() throws Exception { + when(ServicePointServiceImpl.saveServicePoint(anyList())) + .thenReturn(new ArrayList<>(List.of(point(POINT_ID, "Hosur halt")))); + + assertSuccessContaining( + controller.saveServicePoint("{\"servicePoints\":[{\"servicePointName\":\"Hosur halt\"}]}"), + "Hosur halt"); + } + + @Test + @DisplayName("saveServicePoint should answer an error envelope when the store fails") + void saveServicePoint_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(ServicePointServiceImpl.saveServicePoint(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveServicePoint("{\"servicePoints\":[{}]}")); + } + + @Test + @DisplayName("getServicePoints should answer the points matching the location filters") + void getServicePoints_shouldAnswerMatchingPoints() throws Exception { + when(ServicePointServiceImpl.getAvailableServicePoints(29, 301, 31, 77)) + .thenReturn(new ArrayList<>(List.of(point(POINT_ID, "Hosur halt")))); + + assertSuccessContaining(controller.getServicePoints("{\"stateID\":29,\"districtID\":301," + + "\"parkingPlaceID\":31,\"serviceProviderID\":77}"), "Hosur halt"); + } + + @Test + @DisplayName("getServicePoints should answer an error envelope when the lookup fails") + void getServicePoints_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(ServicePointServiceImpl.getAvailableServicePoints(any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getServicePoints("{\"stateID\":29}")); + } + + @Test + @DisplayName("deleteServicePoint should report whether the point actually changed") + void deleteServicePoint_shouldReportOutcome() throws Exception { + when(ServicePointServiceImpl.updateServicePointStatus(any())).thenReturn(1); + assertSuccessContaining(controller.deleteServicePoint("{\"servicePointID\":71,\"deleted\":true}"), + "status updated successfully"); + + when(ServicePointServiceImpl.updateServicePointStatus(any())).thenReturn(0); + assertSuccessContaining(controller.deleteServicePoint("{\"servicePointID\":71,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteServicePoint should answer an error envelope when the change fails") + void deleteServicePoint_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(ServicePointServiceImpl.updateServicePointStatus(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.deleteServicePoint("{\"servicePointID\":71,\"deleted\":true}")); + } + + @Test + @DisplayName("editServicePoint should copy the edits onto the stored point") + void editServicePoint_shouldCopyEdits() throws Exception { + M_Servicepoint stored = point(POINT_ID, "old name"); + when(ServicePointServiceImpl.getdataForEditServicePointStatus(POINT_ID)).thenReturn(stored); + when(ServicePointServiceImpl.saveeditedData(stored)).thenReturn(stored); + + String response = controller.editServicePoint("{\"servicePointID\":71," + + "\"servicePointName\":\"Hosur halt\",\"servicePointDesc\":\"Weekly halt\"," + + "\"districtID\":301,\"districtBlockID\":401,\"servicePointHQAddress\":\"Main Road\"," + + "\"providerServiceMapID\":4001,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "Hosur halt"); + assertEquals("Weekly halt", stored.getServicePointDesc()); + assertEquals("Main Road", stored.getServicePointHQAddress()); + } + + @Test + @DisplayName("editServicePoint should answer an error envelope for a point that does not exist") + void editServicePoint_shouldAnswerErrorEnvelopeForUnknownPoint() throws Exception { + when(ServicePointServiceImpl.getdataForEditServicePointStatus(POINT_ID)).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, + statusCodeOf(controller.editServicePoint("{\"servicePointID\":71}"))); + } + + @Test + @DisplayName("saveServicePointVillageMap should answer the village maps the service stored") + void saveVillageMap_shouldAnswerStoredMaps() throws Exception { + when(ServicePointServiceImpl.saveServicePointVillageMap(anyList())) + .thenReturn(new ArrayList<>(List.of(villageMap(9001)))); + + assertSuccessContaining(controller.saveServicePointVillageMap( + "{\"servicePointVillageMaps\":[{\"servicePointID\":71,\"districtBranchID\":501}]}"), "9001"); + } + + @Test + @DisplayName("saveServicePointVillageMap should answer an error envelope when the store fails") + void saveVillageMap_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(ServicePointServiceImpl.saveServicePointVillageMap(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveServicePointVillageMap("{\"servicePointVillageMaps\":[{}]}")); + } + + @Test + @DisplayName("getServicePointVillageMaps should answer the maps matching the location filters") + void getVillageMaps_shouldAnswerMatchingMaps() throws Exception { + when(ServicePointServiceImpl.getAvailableServicePointVillageMaps(29, 301, 31, POINT_ID, 77)) + .thenReturn(new ArrayList<>(List.of(villageMap(9001)))); + + assertSuccessContaining(controller.getServicePointVillageMaps("{\"stateID\":29,\"districtID\":301," + + "\"parkingPlaceID\":31,\"servicePointID\":71,\"serviceProviderID\":77}"), "9001"); + } + + @Test + @DisplayName("getServicePointVillageMaps should answer an error envelope when the lookup fails") + void getVillageMaps_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(ServicePointServiceImpl.getAvailableServicePointVillageMaps(any(), any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getServicePointVillageMaps("{\"stateID\":29}")); + } + + @Test + @DisplayName("deleteServicePointVillageMap should report whether the map actually changed") + void deleteVillageMap_shouldReportOutcome() throws Exception { + when(ServicePointServiceImpl.updateServicePointVillageMapStatus(any(M_Servicepointvillagemap.class))) + .thenReturn(1); + assertSuccessContaining( + controller.deleteServicePointVillageMap("{\"servicePointVillageMapID\":9001,\"deleted\":true}"), + "status updated successfully"); + + when(ServicePointServiceImpl.updateServicePointVillageMapStatus(any(M_Servicepointvillagemap.class))) + .thenReturn(0); + assertSuccessContaining( + controller.deleteServicePointVillageMap("{\"servicePointVillageMapID\":9001,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("editServicePointVillageMap should copy the edits onto the stored map") + void editVillageMap_shouldCopyEdits() throws Exception { + M_Servicepointvillagemap stored = villageMap(9001); + when(ServicePointServiceImpl.updateServicePointVillageMapStatus(9001)).thenReturn(stored); + when(ServicePointServiceImpl.saveEditedData(stored)).thenReturn(stored); + + String response = controller.editServicePointVillageMap("{\"servicePointVillageMapID\":9001," + + "\"servicePointID\":72,\"districtBranchID\":502,\"providerServiceMapID\":4001," + + "\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "9001"); + assertEquals(72, stored.getServicePointID()); + assertEquals(502, stored.getDistrictBranchID()); + } + + @Test + @DisplayName("editServicePointVillageMap should answer an error envelope for a map that does not exist") + void editVillageMap_shouldAnswerErrorEnvelopeForUnknownMap() throws Exception { + when(ServicePointServiceImpl.updateServicePointVillageMapStatus(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, + statusCodeOf(controller.editServicePointVillageMap("{\"servicePointVillageMapID\":9001}"))); + } + + @Test + @DisplayName("unmappedvillages should answer the villages no service point covers yet") + void unmappedvillages_shouldAnswerUncoveredVillages() throws Exception { + when(ServicePointServiceImpl.getunmappedvillages(4001, 401)) + .thenReturn(List.of(new DistrictBranchMapping())); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(controller.unmappedvillages( + "{\"providerServiceMapID\":4001,\"districtBlockID\":401}"))); + } + + @Test + @DisplayName("unmappedvillages should answer an error envelope when the lookup fails") + void unmappedvillages_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(ServicePointServiceImpl.getunmappedvillages(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.unmappedvillages("{\"providerServiceMapID\":4001}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/snomedMapping/SnomedMappingControllerTest.java b/src/test/java/com/iemr/admin/controller/snomedMapping/SnomedMappingControllerTest.java new file mode 100644 index 0000000..75cb0c4 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/snomedMapping/SnomedMappingControllerTest.java @@ -0,0 +1,195 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.snomedMapping; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.google.gson.JsonObject; +import com.iemr.admin.service.snomedMapping.SnomedService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.when; + +/** + * The snomed screen maps entries of the clinical masters onto SNOMED codes and + * reports back in the master's own words why a mapping was refused. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("SnomedMappingController Test Suite") +class SnomedMappingControllerTest { + + private static final String REQUEST = "{\"masterType\":\"Family History\",\"masterID\":12," + + "\"sctCode\":\"73211009\",\"modifiedBy\":\"admin\"}"; + + @Mock + private SnomedService snomedService; + + @InjectMocks + private SnomedMappingController controller; + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static String errorMessageOf(String response) { + return new JSONObject(response).getString("errorMessage"); + } + + @Test + @DisplayName("editSnomedMaster should confirm the mapping it recorded") + void edit_shouldConfirmRecordedMapping() { + when(snomedService.editSnomedMappingData(any(JsonObject.class), anyString())).thenReturn("Data Updated"); + + String response = controller.editSnomedMaster(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Data Updated successfully"), response); + } + + @Test + @DisplayName("editSnomedMaster should pass on the master type refusal in the service's own words") + void edit_shouldPassOnMasterTypeRefusal() { + when(snomedService.editSnomedMappingData(any(JsonObject.class), anyString())) + .thenReturn("Invalid Master Type"); + + String response = controller.editSnomedMaster(REQUEST); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response)); + assertEquals("Invalid Master Type", errorMessageOf(response)); + } + + @Test + @DisplayName("editSnomedMaster should report a general refusal when the mapping was not recorded") + void edit_shouldReportGeneralRefusal() { + when(snomedService.editSnomedMappingData(any(JsonObject.class), anyString())).thenReturn(null); + + String response = controller.editSnomedMaster(REQUEST); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response)); + assertEquals("Unable to update data", errorMessageOf(response)); + } + + @Test + @DisplayName("editSnomedMaster should report the failure when the request cannot be read") + void edit_shouldReportUnreadableRequest() { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.editSnomedMaster("{not json"))); + } + + @Test + @DisplayName("saveSnomedMaster should confirm the mappings it stored") + void save_shouldConfirmStoredMappings() { + when(snomedService.saveSnomedMappingData(any(JsonObject.class), anyString())).thenReturn("Data Saved"); + + String response = controller.saveSnomedMaster(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Data Saved successfully"), response); + } + + @Test + @DisplayName("saveSnomedMaster should pass on the master type refusal in the service's own words") + void save_shouldPassOnMasterTypeRefusal() { + when(snomedService.saveSnomedMappingData(any(JsonObject.class), anyString())) + .thenReturn("Invalid Master Type"); + + assertEquals("Invalid Master Type", errorMessageOf(controller.saveSnomedMaster(REQUEST))); + } + + @Test + @DisplayName("saveSnomedMaster should report a general refusal when nothing was stored") + void save_shouldReportGeneralRefusal() { + when(snomedService.saveSnomedMappingData(any(JsonObject.class), anyString())).thenReturn(null); + + assertEquals("Unable to Save data", errorMessageOf(controller.saveSnomedMaster(REQUEST))); + } + + @Test + @DisplayName("saveSnomedMaster should report the failure when the request cannot be read") + void save_shouldReportUnreadableRequest() { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveSnomedMaster("{not json"))); + } + + @Test + @DisplayName("fetchSnomedWorklist should answer the master the request asked for") + void fetch_shouldAnswerRequestedMaster() { + when(snomedService.fetchSnomedMaster(any(JsonObject.class))).thenReturn("[{\"masterName\":\"Diabetes\"}]"); + + String response = controller.fetchSnomedWorklist(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Diabetes"), response); + } + + @Test + @DisplayName("fetchSnomedWorklist should report the failure when the worklist cannot be answered") + void fetch_shouldReportLookupFailure() { + when(snomedService.fetchSnomedMaster(any(JsonObject.class))).thenReturn(null); + + assertEquals("error in fetching worklist data", errorMessageOf(controller.fetchSnomedWorklist(REQUEST))); + } + + @Test + @DisplayName("fetchSnomedWorklist should report the failure when the request cannot be read") + void fetch_shouldReportUnreadableRequest() { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.fetchSnomedWorklist("{not json"))); + } + + @Test + @DisplayName("updateStatus should confirm the change it recorded") + void updateStatus_shouldConfirmRecordedChange() { + when(snomedService.updateStatus(anyString())).thenReturn("Data updated successfully"); + + String response = controller.updateStatus(REQUEST); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Data updated successfully"), response); + } + + @Test + @DisplayName("updateStatus should report the failure when nothing came back from the service") + void updateStatus_shouldReportMissingAnswer() { + when(snomedService.updateStatus(anyString())).thenReturn(null); + + assertEquals("error in updating data", errorMessageOf(controller.updateStatus(REQUEST))); + } + + @Test + @DisplayName("updateStatus should report the failure when the change cannot be recorded") + void updateStatus_shouldReportStorageFailure() { + when(snomedService.updateStatus(anyString())).thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.updateStatus(REQUEST))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/stockEntry/StockEntryControllerTest.java b/src/test/java/com/iemr/admin/controller/stockEntry/StockEntryControllerTest.java new file mode 100644 index 0000000..d85c5a3 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/stockEntry/StockEntryControllerTest.java @@ -0,0 +1,145 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.stockEntry; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.dao.DataIntegrityViolationException; + +import com.iemr.admin.data.stockExit.ItemStockExit; +import com.iemr.admin.data.stockentry.ItemStockEntry; +import com.iemr.admin.data.stockentry.PhysicalStockEntry; +import com.iemr.admin.service.stockEntry.StockEntryService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The stock entry screen records what arrives at a store and works out which + * batches an issue should draw on. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("StockEntryController Test Suite") +class StockEntryControllerTest { + + private static final Integer FACILITY_ID = 9001; + + @Mock + private StockEntryService stockEntryService; + + @InjectMocks + private StockEntryController controller; + + private static PhysicalStockEntry arrival() { + PhysicalStockEntry arrival = new PhysicalStockEntry(); + arrival.setPhyEntryID(7001); + arrival.setRefNo("GRN-101"); + return arrival; + } + + private static ItemStockEntry batch() { + ItemStockEntry batch = new ItemStockEntry(); + batch.setItemStockEntryID(1); + batch.setItemID(501); + batch.setFacilityID(FACILITY_ID); + batch.setBatchNo("B-77"); + return batch; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("physicalStockEntry should answer the arrival it recorded") + void physicalStockEntry_shouldAnswerRecordedArrival() { + when(stockEntryService.savePhysicalStockEntry(any(PhysicalStockEntry.class))).thenReturn(arrival()); + + assertSuccessContaining(controller.physicalStockEntry(arrival()), "GRN-101"); + } + + @Test + @DisplayName("physicalStockEntry should report the failure when a batch clashes with one on file") + void physicalStockEntry_shouldReportClashingBatch() { + when(stockEntryService.savePhysicalStockEntry(any(PhysicalStockEntry.class))) + .thenThrow(new DataIntegrityViolationException("duplicate batch number")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.physicalStockEntry(arrival()))); + } + + @Test + @DisplayName("getItemBatchForStoreID should answer the batches the store still holds") + void getItemBatch_shouldAnswerHeldBatches() { + when(stockEntryService.getItemBatchForStoreID(any(ItemStockEntry.class))).thenReturn(List.of(batch())); + + assertSuccessContaining(controller.getItemBatchForStoreID(batch()), "B-77"); + } + + @Test + @DisplayName("getItemBatchForStoreID should report the failure when the batches cannot be answered") + void getItemBatch_shouldReportLookupFailure() { + when(stockEntryService.getItemBatchForStoreID(any(ItemStockEntry.class))) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getItemBatchForStoreID(batch()))); + } + + @Test + @DisplayName("allocateStockFromItemID should answer the batches the issue should draw on") + void allocate_shouldAnswerAllocatedBatches() { + when(stockEntryService.getItemStockFromItemID(anyInt(), anyList())).thenReturn(List.of(batch())); + + assertSuccessContaining( + controller.allocateStockFromItemID(FACILITY_ID, new ArrayList()), "B-77"); + } + + @Test + @DisplayName("allocateStockFromItemID should report the failure when the allocation cannot be worked out") + void allocate_shouldReportAllocationFailure() { + when(stockEntryService.getItemStockFromItemID(anyInt(), anyList())) + .thenThrow(new RuntimeException("item is not on the item master")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.allocateStockFromItemID(FACILITY_ID, new ArrayList()))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/stockExit/StockExitControllerTest.java b/src/test/java/com/iemr/admin/controller/stockExit/StockExitControllerTest.java new file mode 100644 index 0000000..d2bea7b --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/stockExit/StockExitControllerTest.java @@ -0,0 +1,99 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.stockExit; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.stockExit.T_PatientIssue; +import com.iemr.admin.service.stockExit.StockExitService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * The patient issue screen hands drugs out of a store to a patient. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("StockExitController Test Suite") +class StockExitControllerTest { + + @Mock + private StockExitService stockExitService; + + @InjectMocks + private StockExitController controller; + + private static T_PatientIssue issue() { + T_PatientIssue issue = new T_PatientIssue(); + issue.setPatientIssueID(5501); + issue.setFacilityID(9001); + issue.setIssueType("Manual"); + return issue; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + @Test + @DisplayName("patientIssue should confirm the issue it recorded") + void patientIssue_shouldConfirmRecordedIssue() { + when(stockExitService.issuePatientDrugs(any(T_PatientIssue.class))).thenReturn(1); + + String response = controller.patientIssue(issue()); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Successfully Created"), response); + } + + @Test + @DisplayName("patientIssue should say the quantities are wrong when the store cannot cover the issue") + void patientIssue_shouldSayQuantitiesAreWrong() { + when(stockExitService.issuePatientDrugs(any(T_PatientIssue.class))).thenReturn(0); + + String response = controller.patientIssue(issue()); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Error in Quantity"), response); + } + + @Test + @DisplayName("patientIssue should report the failure when the issue cannot be recorded") + void patientIssue_shouldReportStorageFailure() { + when(stockExitService.issuePatientDrugs(any(T_PatientIssue.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.patientIssue(issue()))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/store/StoreControllerTest.java b/src/test/java/com/iemr/admin/controller/store/StoreControllerTest.java new file mode 100644 index 0000000..39f8fac --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/store/StoreControllerTest.java @@ -0,0 +1,453 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.store; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.store.FacilityVillageMapping; +import com.iemr.admin.data.store.M_Facility; +import com.iemr.admin.data.store.M_facilityMap; +import com.iemr.admin.data.store.V_FetchFacility; +import com.iemr.admin.service.store.StoreService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The store endpoints maintain the facility hierarchy an operator sees on the + * store screens. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("StoreController Test Suite") +class StoreControllerTest { + + private static final Integer FACILITY_ID = 501; + private static final Integer PSM_ID = 4001; + private static final Integer BLOCK_ID = 401; + + @Mock + private StoreService storeService; + + @InjectMocks + private StoreController controller; + + private static M_Facility facility(Integer id, String name) { + M_Facility facility = new M_Facility(); + facility.setFacilityID(id); + facility.setFacilityName(name); + return facility; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + private static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } + + @Test + @DisplayName("createStore should answer the facilities the service stored") + void createStore_shouldAnswerStoredFacilities() { + when(storeService.addAllMainStore(anyList())).thenReturn(List.of(facility(FACILITY_ID, "PHC North"))); + + assertSuccessContaining(controller.createStore("[{\"facilityName\":\"PHC North\"}]"), "PHC North"); + } + + @Test + @DisplayName("createStore should answer an error envelope when the store fails") + void createStore_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.addAllMainStore(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createStore("[{\"facilityName\":\"PHC North\"}]")); + } + + @Test + @DisplayName("editStore should copy the edited fields onto the stored facility") + void editStore_shouldCopyEditedFields() { + M_Facility stored = facility(FACILITY_ID, "PHC North"); + when(storeService.getMainStore(FACILITY_ID)).thenReturn(stored); + when(storeService.createMainStore(stored)).thenReturn(stored); + + String response = controller.editStore("{\"facilityID\":501,\"facilityDesc\":\"Primary centre\"," + + "\"location\":\"Main Road\",\"physicalLocation\":\"Block A\",\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "PHC North"); + assertEquals("Primary centre", stored.getFacilityDesc()); + assertEquals("Block A", stored.getPhysicalLocation()); + } + + @Test + @DisplayName("editStore should answer an error envelope for a facility that does not exist") + void editStore_shouldAnswerErrorEnvelopeForUnknownFacility() { + when(storeService.getMainStore(FACILITY_ID)).thenReturn(null); + + assertCodeException(controller.editStore("{\"facilityID\":501}")); + } + + @Test + @DisplayName("getAllStore should answer the facilities of the provider") + void getAllStore_shouldAnswerProviderFacilities() { + when(storeService.getAllMainStore(PSM_ID)).thenReturn(List.of(facility(FACILITY_ID, "PHC North"))); + + assertSuccessContaining(controller.getAllStore(PSM_ID), "PHC North"); + } + + @Test + @DisplayName("getAllStore should answer an error envelope when the lookup fails") + void getAllStore_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getAllMainStore(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllStore(PSM_ID)); + } + + @Test + @DisplayName("getMainFacility should answer the main facilities of the provider") + void getMainFacility_shouldAnswerMainFacilities() { + when(storeService.getMainFacility(PSM_ID, true)) + .thenReturn(new ArrayList<>(List.of(facility(FACILITY_ID, "PHC North")))); + + assertSuccessContaining( + controller.getMainFacility("{\"providerServiceMapID\":4001,\"isMainFacility\":true}"), "PHC North"); + } + + @Test + @DisplayName("getMainFacility should answer an error envelope when the lookup fails") + void getMainFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getMainFacility(any(), any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getMainFacility("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("getsubFacility should answer the facilities under the main facility") + void getsubFacility_shouldAnswerSubFacilities() { + when(storeService.getMainFacility(PSM_ID, false, 500)) + .thenReturn(new ArrayList<>(List.of(facility(FACILITY_ID, "Sub Centre")))); + + assertSuccessContaining(controller.getsubFacility("{\"providerServiceMapID\":4001," + + "\"isMainFacility\":false,\"mainFacilityID\":500}"), "Sub Centre"); + } + + @Test + @DisplayName("getsubFacility should answer an error envelope when the lookup fails") + void getsubFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getMainFacility(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getsubFacility("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("deleteStore should answer the facility the service retired") + void deleteStore_shouldAnswerRetiredFacility() throws Exception { + M_Facility request = facility(FACILITY_ID, "PHC North"); + when(storeService.deleteStore(request)).thenReturn(request); + + assertSuccessContaining(controller.deleteStore(request), "PHC North"); + } + + @Test + @DisplayName("deleteStore should report the reason the service refused to retire the facility") + void deleteStore_shouldReportRefusal() throws Exception { + M_Facility request = facility(FACILITY_ID, "PHC North"); + when(storeService.deleteStore(request)).thenThrow(new Exception("Child Stores are still active")); + + String response = controller.deleteStore(request); + + assertGenericFailure(response); + assertTrue(response.contains("Child Stores are still active"), response); + } + + @Test + @DisplayName("mapStore should answer how many mappings the service changed") + void mapStore_shouldAnswerChangedCount() { + when(storeService.mapStore(anyList())).thenReturn(2); + + assertSuccessContaining(controller.mapStore(List.of(new M_facilityMap())), "2"); + } + + @Test + @DisplayName("mapStore should answer an error envelope when the mapping fails") + void mapStore_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.mapStore(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.mapStore(List.of(new M_facilityMap()))); + } + + @Test + @DisplayName("deleteMapStore should answer how many mappings the service released") + void deleteMapStore_shouldAnswerReleasedCount() throws Exception { + M_facilityMap request = new M_facilityMap(); + when(storeService.deleteMapStore(request)).thenReturn(1); + + assertSuccessContaining(controller.deleteMapStore(request), "1"); + } + + @Test + @DisplayName("deleteMapStore should report the reason the service refused to release the mapping") + void deleteMapStore_shouldReportRefusal() throws Exception { + M_facilityMap request = new M_facilityMap(); + when(storeService.deleteMapStore(request)) + .thenThrow(new Exception("Please Unmap van under this Parking Place")); + + assertTrue(controller.deleteMapStore(request).contains("Please Unmap van under this Parking Place")); + } + + @Test + @DisplayName("getMapStore should answer the mapped facilities of the provider") + void getMapStore_shouldAnswerMappedFacilities() { + V_FetchFacility request = new V_FetchFacility(); + request.setProviderServiceMapID(PSM_ID); + V_FetchFacility mapped = new V_FetchFacility(); + mapped.setFacilityName("PHC North"); + when(storeService.getMapStore(request)).thenReturn(List.of(mapped)); + + assertSuccessContaining(controller.getMapStore(request), "PHC North"); + } + + @Test + @DisplayName("getMapStore should answer an error envelope when the lookup fails") + void getMapStore_shouldAnswerErrorEnvelopeOnFailure() { + V_FetchFacility request = new V_FetchFacility(); + when(storeService.getMapStore(request)).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getMapStore(request)); + } + + @Test + @DisplayName("getFacilitiesByBlock should answer the live facilities in the block") + void getFacilitiesByBlock_shouldAnswerLiveFacilities() { + when(storeService.getFacilitiesByBlock(BLOCK_ID)) + .thenReturn(new ArrayList<>(List.of(facility(FACILITY_ID, "PHC North")))); + + assertSuccessContaining(controller.getFacilitiesByBlock("{\"blockID\":401}"), "PHC North"); + } + + @Test + @DisplayName("getFacilitiesByBlock should answer an error envelope when the lookup fails") + void getFacilitiesByBlock_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getFacilitiesByBlock(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFacilitiesByBlock("{\"blockID\":401}")); + } + + @Test + @DisplayName("getAllFacilitiesByBlock should answer the retired facilities of the block as well") + void getAllFacilitiesByBlock_shouldAnswerRetiredFacilitiesToo() { + when(storeService.getAllFacilitiesByBlock(BLOCK_ID)) + .thenReturn(new ArrayList<>(List.of(facility(FACILITY_ID, "PHC North")))); + + assertSuccessContaining(controller.getAllFacilitiesByBlock("{\"blockID\":401}"), "PHC North"); + } + + @Test + @DisplayName("getAllFacilitiesByBlock should answer an error envelope when the lookup fails") + void getAllFacilitiesByBlock_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getAllFacilitiesByBlock(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getAllFacilitiesByBlock("{\"blockID\":401}")); + } + + @Test + @DisplayName("checkStoreCode should report whether the facility code is already taken") + void checkStoreCode_shouldReportWhetherCodeIsTaken() { + when(storeService.checkStoreCode(any())).thenReturn(Boolean.TRUE); + + assertSuccessContaining(controller.checkStoreCode("{\"facilityCode\":\"PHC-1\"}"), "true"); + } + + @Test + @DisplayName("checkStoreCode should answer an error envelope when the check fails") + void checkStoreCode_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.checkStoreCode(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkStoreCode("{\"facilityCode\":\"PHC-1\"}")); + } + + @Test + @DisplayName("getFacilitiesByBlockAndLevel should pass every filter the screen sends through") + void getFacilitiesByBlockAndLevel_shouldPassFiltersThrough() { + when(storeService.getFacilitiesByBlockAndLevel(BLOCK_ID, 4, "Rural")) + .thenReturn(new ArrayList<>(List.of(facility(FACILITY_ID, "Sub Centre")))); + + assertSuccessContaining(controller.getFacilitiesByBlockAndLevel( + "{\"blockID\":401,\"levelValue\":4,\"ruralUrban\":\"Rural\"}"), "Sub Centre"); + verify(storeService).getFacilitiesByBlockAndLevel(BLOCK_ID, 4, "Rural"); + } + + @Test + @DisplayName("getFacilitiesByBlockAndLevel should answer an error envelope when the lookup fails") + void getFacilitiesByBlockAndLevel_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getFacilitiesByBlockAndLevel(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getFacilitiesByBlockAndLevel("{\"blockID\":401}")); + } + + @Test + @DisplayName("createFacilityWithHierarchy should pass the villages and children through to the service") + void createFacilityWithHierarchy_shouldPassHierarchyThrough() { + when(storeService.createFacilityWithHierarchy(any(), anyList(), anyInt(), anyList())) + .thenReturn(facility(FACILITY_ID, "PHC North")); + + String response = controller.createFacilityWithHierarchy("{\"facility\":{\"facilityName\":\"PHC North\"}," + + "\"villageIDs\":[601,602],\"mainVillageID\":601,\"childFacilityIDs\":[502]}"); + + assertSuccessContaining(response, "PHC North"); + verify(storeService).createFacilityWithHierarchy(any(), org.mockito.ArgumentMatchers.eq(List.of(601, 602)), + org.mockito.ArgumentMatchers.eq(601), org.mockito.ArgumentMatchers.eq(List.of(502))); + } + + @Test + @DisplayName("createFacilityWithHierarchy should report the reason the service refused the facility") + void createFacilityWithHierarchy_shouldReportRefusal() { + when(storeService.createFacilityWithHierarchy(any(), any(), any(), any())) + .thenThrow(new RuntimeException("Facility with this name already exists in this block")); + + assertTrue(controller.createFacilityWithHierarchy("{\"facility\":{\"facilityName\":\"PHC North\"}}") + .contains("already exists in this block")); + } + + @Test + @DisplayName("getMappedVillageIDs should answer the villages already spoken for in the block") + void getMappedVillageIDs_shouldAnswerSpokenForVillages() { + when(storeService.getMappedVillageIDs(BLOCK_ID)).thenReturn(List.of(601, 602)); + + assertSuccessContaining(controller.getMappedVillageIDs("{\"blockID\":401}"), "601"); + } + + @Test + @DisplayName("getMappedVillageIDs should answer an error envelope when the lookup fails") + void getMappedVillageIDs_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getMappedVillageIDs(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getMappedVillageIDs("{\"blockID\":401}")); + } + + @Test + @DisplayName("getVillageMappingsByFacility should answer the villages the facility serves") + void getVillageMappingsByFacility_shouldAnswerServedVillages() { + FacilityVillageMapping mapping = new FacilityVillageMapping(); + mapping.setDistrictBranchID(601); + when(storeService.getVillageMappingsByFacility(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(mapping))); + + assertSuccessContaining(controller.getVillageMappingsByFacility("{\"facilityID\":501}"), "601"); + } + + @Test + @DisplayName("getVillageMappingsByFacility should answer an error envelope when the lookup fails") + void getVillageMappingsByFacility_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getVillageMappingsByFacility(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getVillageMappingsByFacility("{\"facilityID\":501}")); + } + + @Test + @DisplayName("getChildFacilitiesByParent should answer the facilities under the parent") + void getChildFacilitiesByParent_shouldAnswerChildren() { + when(storeService.getChildFacilitiesByParent(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(facility(502, "Sub Centre")))); + + assertSuccessContaining(controller.getChildFacilitiesByParent("{\"facilityID\":501}"), "Sub Centre"); + } + + @Test + @DisplayName("getChildFacilitiesByParent should answer an error envelope when the lookup fails") + void getChildFacilitiesByParent_shouldAnswerErrorEnvelopeOnFailure() { + when(storeService.getChildFacilitiesByParent(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getChildFacilitiesByParent("{\"facilityID\":501}")); + } + + @Test + @DisplayName("updateFacilityWithHierarchy should answer the facility the service saved") + void updateFacilityWithHierarchy_shouldAnswerSavedFacility() { + when(storeService.updateFacilityWithHierarchy(any(), any(), any(), any())) + .thenReturn(facility(FACILITY_ID, "PHC North")); + + assertSuccessContaining(controller.updateFacilityWithHierarchy( + "{\"facility\":{\"facilityID\":501,\"facilityName\":\"PHC North\"}}"), "PHC North"); + } + + @Test + @DisplayName("updateFacilityWithHierarchy should report the reason the service refused the edit") + void updateFacilityWithHierarchy_shouldReportRefusal() { + when(storeService.updateFacilityWithHierarchy(any(), any(), any(), any())) + .thenThrow(new RuntimeException("Facility not found")); + + assertTrue(controller.updateFacilityWithHierarchy("{\"facility\":{\"facilityID\":501}}") + .contains("Facility not found")); + } + + @Test + @DisplayName("deleteFacilityWithHierarchy should answer the facility the service retired") + void deleteFacilityWithHierarchy_shouldAnswerRetiredFacility() throws Exception { + when(storeService.deleteFacilityWithHierarchy(FACILITY_ID, "admin")) + .thenReturn(facility(FACILITY_ID, "PHC North")); + + assertSuccessContaining( + controller.deleteFacilityWithHierarchy("{\"facilityID\":501,\"modifiedBy\":\"admin\"}"), + "PHC North"); + } + + @Test + @DisplayName("deleteFacilityWithHierarchy should report the reason the service refused the retirement") + void deleteFacilityWithHierarchy_shouldReportRefusal() throws Exception { + when(storeService.deleteFacilityWithHierarchy(anyInt(), anyString())) + .thenThrow(new Exception("Facility not found")); + + assertTrue(controller.deleteFacilityWithHierarchy("{\"facilityID\":501,\"modifiedBy\":\"admin\"}") + .contains("Facility not found")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/supplier/SupplierMasterControllerTest.java b/src/test/java/com/iemr/admin/controller/supplier/SupplierMasterControllerTest.java new file mode 100644 index 0000000..e6b8929 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/supplier/SupplierMasterControllerTest.java @@ -0,0 +1,169 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.supplier; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.supplier.M_Supplier; +import com.iemr.admin.service.supplier.SupplierInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** The supplier master endpoints keep the supplier catalogue an inventory operator picks from. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("SupplierMasterController Test Suite") +class SupplierMasterControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private SupplierInter supplierInter; + + @InjectMocks + private SupplierMasterController controller; + + private static M_Supplier record(Integer id, String name) { + M_Supplier record = new M_Supplier(); + record.setSupplierID(id); + record.setSupplierName(name); + return record; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("createSupplier should answer the records the service stored") + void create_shouldAnswerStoredRecords() { + when(supplierInter.createSupplier(anyList())).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "MedSupply")))); + + assertSuccessContaining(controller.createSupplier("[{\"supplierCode\":\"C-1\"}]"), "MedSupply"); + } + + @Test + @DisplayName("createSupplier should answer an error envelope when the store fails") + void create_shouldAnswerErrorEnvelopeOnFailure() { + when(supplierInter.createSupplier(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createSupplier("[{}]")); + } + + @Test + @DisplayName("getSupplier should answer the records of the provider") + void get_shouldAnswerProviderRecords() { + when(supplierInter.getSupplier(PSM_ID)).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "MedSupply")))); + + assertSuccessContaining(controller.getSupplier("{\"providerServiceMapID\":4001}"), "MedSupply"); + } + + @Test + @DisplayName("getSupplier should answer an error envelope when the lookup fails") + void get_shouldAnswerErrorEnvelopeOnFailure() { + when(supplierInter.getSupplier(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getSupplier("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("editSupplier should copy the edits onto the stored record") + void edit_shouldCopyEdits() { + M_Supplier stored = record(RECORD_ID, "MedSupply"); + when(supplierInter.editSupplier(RECORD_ID)).thenReturn(stored); + when(supplierInter.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editSupplier("{\"supplierID\":11,\"supplierDesc\":\"Bulk supplier\",\"contactPerson\":\"Asha\",\"email\":\"asha@example.org\",\"modifiedBy\":\"admin\"}"), "MedSupply"); + assertEquals("Bulk supplier", stored.getSupplierDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("editSupplier should answer an error envelope for a record that does not exist") + void edit_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(supplierInter.editSupplier(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.editSupplier("{\"supplierID\":11,\"supplierDesc\":\"Bulk supplier\",\"contactPerson\":\"Asha\",\"email\":\"asha@example.org\",\"modifiedBy\":\"admin\"}"))); + } + + @Test + @DisplayName("deleteSupplier should mark the record deleted") + void delete_shouldMarkRecordDeleted() { + M_Supplier stored = record(RECORD_ID, "MedSupply"); + when(supplierInter.editSupplier(RECORD_ID)).thenReturn(stored); + when(supplierInter.saveEditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteSupplier("{\"supplierID\":11,\"deleted\":true}"), "MedSupply"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteSupplier should answer an error envelope for a record that does not exist") + void delete_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(supplierInter.editSupplier(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.deleteSupplier("{\"supplierID\":11,\"deleted\":true}"))); + } + + @Test + @DisplayName("checkSupplierCode should report whether the code is already taken") + void check_shouldReportWhetherCodeIsTaken() { + when(supplierInter.checkSupplierCode(any())).thenReturn(Boolean.TRUE); + + assertSuccessContaining(controller.checkSupplierCode("{\"supplierCode\":\"C-1\"}"), "true"); + } + + @Test + @DisplayName("checkSupplierCode should answer an error envelope when the check fails") + void check_shouldAnswerErrorEnvelopeOnFailure() { + when(supplierInter.checkSupplierCode(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkSupplierCode("{\"supplierCode\":\"C-1\"}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/telemedicine/TeleMedicineControllerTest.java b/src/test/java/com/iemr/admin/controller/telemedicine/TeleMedicineControllerTest.java new file mode 100644 index 0000000..3338ca9 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/telemedicine/TeleMedicineControllerTest.java @@ -0,0 +1,205 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.telemedicine; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.telemedicine.M_UserTemp; +import com.iemr.admin.data.telemedicine.Specialization; +import com.iemr.admin.data.telemedicine.TMinput; +import com.iemr.admin.data.telemedicine.UserSpecializationMapping; +import com.iemr.admin.service.telemedicine.TMInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * The telemedicine screen lists the specialists a provider has and records + * which specialities each of them holds. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("TeleMedicineController Test Suite") +class TeleMedicineControllerTest { + + private static final Integer PROVIDER_ID = 5; + private static final Integer MAP_ID = 8001; + + @Mock + private TMInter tmInter; + + @InjectMocks + private TeleMedicineController controller; + + private static TMinput request() { + TMinput input = new TMinput(); + input.setServiceproviderID(PROVIDER_ID); + input.setScreenName("TM"); + return input; + } + + private static M_UserTemp specialist() { + M_UserTemp user = new M_UserTemp(); + user.setUserID(3117L); + user.setFirstName("Asha"); + user.setUserName("asha.rao"); + return user; + } + + private static Specialization speciality() { + Specialization speciality = new Specialization(); + speciality.setSpecializationID(11); + speciality.setSpecialization("Paediatrics"); + return speciality; + } + + private static UserSpecializationMapping held() { + UserSpecializationMapping mapping = new UserSpecializationMapping(); + mapping.setUserSpecializationMapID(MAP_ID); + mapping.setSpecializationName("Paediatrics"); + mapping.setDeleted(Boolean.FALSE); + mapping.setModifiedBy("admin"); + return mapping; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("getUserTM should answer the specialists on the screen asked for") + void getUserTM_shouldAnswerSpecialists() { + when(tmInter.getUser(any())).thenReturn(new ArrayList<>(List.of(specialist()))); + + assertSuccessContaining(controller.getUserTM(request()), "asha.rao"); + } + + @Test + @DisplayName("getUserTM should report the failure when the roster cannot be answered") + void getUserTM_shouldReportLookupFailure() { + when(tmInter.getUser(any())).thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getUserTM(request()))); + } + + @Test + @DisplayName("getSpecialization should answer the specialities on file") + void getSpecialization_shouldAnswerSpecialities() { + when(tmInter.getSpecialization()).thenReturn(new ArrayList<>(List.of(speciality()))); + + assertSuccessContaining(controller.getSpecialization(), "Paediatrics"); + } + + @Test + @DisplayName("getSpecialization should report the failure when the list cannot be answered") + void getSpecialization_shouldReportLookupFailure() { + when(tmInter.getSpecialization()).thenThrow(new RuntimeException("connection reset")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getSpecialization())); + } + + @Test + @DisplayName("getUserSpecialization should answer the specialities held under the provider asked for") + void getUserSpecialization_shouldAnswerHeldSpecialities() { + when(tmInter.getUserSpecialization(PROVIDER_ID)).thenReturn(new ArrayList<>(List.of(held()))); + + assertSuccessContaining(controller.getUserSpecialization(request()), "Paediatrics"); + } + + @Test + @DisplayName("getUserSpecialization should report the failure when the lookup cannot be answered") + void getUserSpecialization_shouldReportLookupFailure() { + when(tmInter.getUserSpecialization(any())).thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getUserSpecialization(request()))); + } + + @Test + @DisplayName("saveUserSpecialization should answer the specialities it recorded") + void saveUserSpecialization_shouldAnswerRecordedSpecialities() { + ArrayList request = new ArrayList<>(List.of(held())); + when(tmInter.saveUserSpecialization(request)).thenReturn(request); + + assertSuccessContaining(controller.saveUserSpecialization(request), "Paediatrics"); + } + + @Test + @DisplayName("saveUserSpecialization should report the failure when the speciality cannot be recorded") + void saveUserSpecialization_shouldReportStorageFailure() { + ArrayList request = new ArrayList<>(List.of(held())); + when(tmInter.saveUserSpecialization(request)).thenThrow(new RuntimeException("already recorded")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveUserSpecialization(request))); + } + + @Test + @DisplayName("activating a speciality should answer it with the status the caller asked for") + void activate_shouldAnswerSpecialityWithRequestedStatus() { + UserSpecializationMapping stored = held(); + UserSpecializationMapping request = held(); + request.setDeleted(Boolean.TRUE); + request.setModifiedBy("supervisor"); + when(tmInter.findUserSpecialization(request)).thenReturn(stored); + when(tmInter.saveoneUserSpecialization(stored)).thenReturn(stored); + + assertSuccessContaining(controller.saveUserSpecialization(request), "8001"); + assertEquals(Boolean.TRUE, stored.getDeleted()); + assertEquals("supervisor", stored.getModifiedBy()); + } + + @Test + @DisplayName("activating a speciality should report the failure when it is unknown") + void activate_shouldReportUnknownSpeciality() { + when(tmInter.findUserSpecialization(any())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.saveUserSpecialization(held()))); + } + + @Test + @DisplayName("activating a speciality should report the failure when the change cannot be recorded") + void activate_shouldReportStorageFailure() { + UserSpecializationMapping stored = held(); + when(tmInter.findUserSpecialization(any())).thenReturn(stored); + when(tmInter.saveoneUserSpecialization(stored)).thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveUserSpecialization(held()))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/telemedicine/VideoConsultationControllerTest.java b/src/test/java/com/iemr/admin/controller/telemedicine/VideoConsultationControllerTest.java new file mode 100644 index 0000000..5e44a86 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/telemedicine/VideoConsultationControllerTest.java @@ -0,0 +1,199 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.telemedicine; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.telemedicine.M_UserTemp; +import com.iemr.admin.data.telemedicine.UserVideoConsultation; +import com.iemr.admin.data.telemedicine.VideoConsultationDomain; +import com.iemr.admin.service.telemedicine.VideoConsultationInter; +import com.iemr.admin.utils.exception.VideoConsultationException; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * The video consultation screen mints conferencing accounts for clinicians and + * keeps their sign-in details and status up to date. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VideoConsultationController Test Suite") +class VideoConsultationControllerTest { + + private static final Integer PROVIDER_ID = 5; + private static final Long MAP_ID = 8001L; + + @Mock + private VideoConsultationInter videoConsultationInter; + + @InjectMocks + private VideoConsultationController controller; + + private static M_UserTemp clinician() { + M_UserTemp user = new M_UserTemp(); + user.setUserID(3117L); + user.setUserName("asha.rao"); + return user; + } + + private static UserVideoConsultation account() { + UserVideoConsultation account = new UserVideoConsultation(); + account.setUserVideoConsultationMapID(MAP_ID); + account.setUserID(3117L); + account.setVideoConsultationEmailID("asha.rao@example.org"); + account.setVideoConsultationDomain("psmri"); + return account; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("getUserTM should answer the clinicians who have no conferencing account yet") + void getUnmapped_shouldAnswerCliniciansWithoutAccount() { + when(videoConsultationInter.getunmappedUser(PROVIDER_ID, 7)).thenReturn(List.of(clinician())); + + assertSuccessContaining(controller.getUserTM(PROVIDER_ID, 7), "asha.rao"); + } + + @Test + @DisplayName("getUserTM should report the failure when the candidates cannot be worked out") + void getUnmapped_shouldReportLookupFailure() { + when(videoConsultationInter.getunmappedUser(any(), any())) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getUserTM(PROVIDER_ID, 7))); + } + + @Test + @DisplayName("createUserTM should answer the conferencing account it opened") + void createUser_shouldAnswerOpenedAccount() throws Exception { + when(videoConsultationInter.createUser(any())).thenReturn(account()); + + assertSuccessContaining(controller.createUserTM(account()), "asha.rao@example.org"); + } + + @Test + @DisplayName("createUserTM should report the platform's reason when the account is refused") + void createUser_shouldReportRemoteRefusal() throws Exception { + when(videoConsultationInter.createUser(any())) + .thenThrow(new VideoConsultationException("email already registered")); + + assertEquals(OutputResponse.VIDEOCONSULTATION_EXCEPTION, statusCodeOf(controller.createUserTM(account()))); + } + + @Test + @DisplayName("editUser should answer the account whose sign-in details it changed") + void editUser_shouldAnswerChangedAccount() throws Exception { + when(videoConsultationInter.editUser(any())).thenReturn(account()); + + assertSuccessContaining(controller.editUser(account()), "8001"); + } + + @Test + @DisplayName("editUser should report the failure when the account is unknown") + void editUser_shouldReportUnknownAccount() throws Exception { + when(videoConsultationInter.editUser(any())).thenThrow(new VideoConsultationException("Invalid MapID")); + + assertEquals(OutputResponse.VIDEOCONSULTATION_EXCEPTION, statusCodeOf(controller.editUser(account()))); + } + + @Test + @DisplayName("deleting an account should answer it with the status the caller asked for") + void deleteUser_shouldAnswerAccountWithRequestedStatus() throws Exception { + UserVideoConsultation retired = account(); + retired.setDeleted(Boolean.TRUE); + when(videoConsultationInter.deleteUser(MAP_ID, Boolean.TRUE, "supervisor")).thenReturn(retired); + + assertSuccessContaining(controller.createUserTM("supervisor", MAP_ID, Boolean.TRUE), "8001"); + } + + @Test + @DisplayName("deleting an account should report the failure when the platform refuses the change") + void deleteUser_shouldReportRemoteRefusal() throws Exception { + when(videoConsultationInter.deleteUser(anyLong(), anyBoolean(), anyString())) + .thenThrow(new VideoConsultationException("account is locked")); + + assertEquals(OutputResponse.VIDEOCONSULTATION_EXCEPTION, + statusCodeOf(controller.createUserTM("supervisor", MAP_ID, Boolean.TRUE))); + } + + @Test + @DisplayName("getmappedUsers should answer the accounts held under the provider asked for") + void getmappedUsers_shouldAnswerAccountsOfProvider() { + when(videoConsultationInter.fetchmappedUser(PROVIDER_ID)).thenReturn(new ArrayList<>(List.of(account()))); + + assertSuccessContaining(controller.getmappedUsers(PROVIDER_ID), "asha.rao@example.org"); + } + + @Test + @DisplayName("getmappedUsers should report the failure when the lookup cannot be answered") + void getmappedUsers_shouldReportLookupFailure() { + when(videoConsultationInter.fetchmappedUser(any())).thenThrow(new RuntimeException("connection reset")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getmappedUsers(PROVIDER_ID))); + } + + @Test + @DisplayName("getdomain should answer the conferencing domains on file") + void getdomain_shouldAnswerDomains() { + VideoConsultationDomain domain = new VideoConsultationDomain(); + domain.setVideoConsultationDomainID(1); + domain.setVideoConsultationDoamin("psmri"); + when(videoConsultationInter.getdomain(PROVIDER_ID)).thenReturn(List.of(domain)); + + assertSuccessContaining(controller.getdomain(PROVIDER_ID), "psmri"); + } + + @Test + @DisplayName("getdomain should report the failure when the domains cannot be answered") + void getdomain_shouldReportLookupFailure() { + when(videoConsultationInter.getdomain(any())).thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getdomain(PROVIDER_ID))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/uom/UomControllerTest.java b/src/test/java/com/iemr/admin/controller/uom/UomControllerTest.java new file mode 100644 index 0000000..5651471 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/uom/UomControllerTest.java @@ -0,0 +1,169 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.uom; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.uom.M_Uom; +import com.iemr.admin.service.uom.UomInter; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** The unit of measure master endpoints keep the unit of measure catalogue an inventory operator picks from. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("UomController Test Suite") +class UomControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private UomInter uomInter; + + @InjectMocks + private UomController controller; + + private static M_Uom record(Integer id, String name) { + M_Uom record = new M_Uom(); + record.setuOMID(id); + record.setuOMName(name); + return record; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + @Test + @DisplayName("createUom should answer the records the service stored") + void create_shouldAnswerStoredRecords() { + when(uomInter.createDrugtypeData(anyList())).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "Tablet")))); + + assertSuccessContaining(controller.createUom("[{\"uOMCode\":\"C-1\"}]"), "Tablet"); + } + + @Test + @DisplayName("createUom should answer an error envelope when the store fails") + void create_shouldAnswerErrorEnvelopeOnFailure() { + when(uomInter.createDrugtypeData(anyList())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.createUom("[{}]")); + } + + @Test + @DisplayName("getUom should answer the records of the provider") + void get_shouldAnswerProviderRecords() { + when(uomInter.createDrugtypeData(PSM_ID)).thenReturn(new ArrayList<>(List.of(record(RECORD_ID, "Tablet")))); + + assertSuccessContaining(controller.getUom("{\"providerServiceMapID\":4001}"), "Tablet"); + } + + @Test + @DisplayName("getUom should answer an error envelope when the lookup fails") + void get_shouldAnswerErrorEnvelopeOnFailure() { + when(uomInter.createDrugtypeData(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getUom("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("editUom should copy the edits onto the stored record") + void edit_shouldCopyEdits() { + M_Uom stored = record(RECORD_ID, "Tablet"); + when(uomInter.editDrugtypeData(RECORD_ID)).thenReturn(stored); + when(uomInter.saveeditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.editUom("{\"uOMID\":11,\"uOMName\":\"Tablet\",\"uOMDesc\":\"One tablet\",\"uOMCode\":\"TAB\",\"status\":\"Active\",\"modifiedBy\":\"admin\"}"), "Tablet"); + assertEquals("One tablet", stored.getuOMDesc()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("editUom should answer an error envelope for a record that does not exist") + void edit_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(uomInter.editDrugtypeData(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.editUom("{\"uOMID\":11,\"uOMName\":\"Tablet\",\"uOMDesc\":\"One tablet\",\"uOMCode\":\"TAB\",\"status\":\"Active\",\"modifiedBy\":\"admin\"}"))); + } + + @Test + @DisplayName("deleteUom should mark the record deleted") + void delete_shouldMarkRecordDeleted() { + M_Uom stored = record(RECORD_ID, "Tablet"); + when(uomInter.editDrugtypeData(RECORD_ID)).thenReturn(stored); + when(uomInter.saveeditedData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.deleteUom("{\"uOMID\":11,\"deleted\":true}"), "Tablet"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteUom should answer an error envelope for a record that does not exist") + void delete_shouldAnswerErrorEnvelopeForUnknownRecord() { + when(uomInter.editDrugtypeData(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.deleteUom("{\"uOMID\":11,\"deleted\":true}"))); + } + + @Test + @DisplayName("checkUomCode should report whether the code is already taken") + void check_shouldReportWhetherCodeIsTaken() { + when(uomInter.checkUomCode(any())).thenReturn(Boolean.TRUE); + + assertSuccessContaining(controller.checkUomCode("{\"uOMCode\":\"C-1\"}"), "true"); + } + + @Test + @DisplayName("checkUomCode should answer an error envelope when the check fails") + void check_shouldAnswerErrorEnvelopeOnFailure() { + when(uomInter.checkUomCode(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.checkUomCode("{\"uOMCode\":\"C-1\"}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/uptsu/FacilityControllerTest.java b/src/test/java/com/iemr/admin/controller/uptsu/FacilityControllerTest.java new file mode 100644 index 0000000..7e58df8 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/uptsu/FacilityControllerTest.java @@ -0,0 +1,155 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.uptsu; + +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.uptsu.CDSSMapping; +import com.iemr.admin.data.uptsu.M_FacilityMapping; +import com.iemr.admin.service.uptsu.FacilityService; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.when; + +/** + * The UP TSU endpoints take the facility spreadsheet an operator uploads and the + * CDSS switch that decides whether decision support is on for a provider. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("FacilityController Test Suite") +class FacilityControllerTest { + + private static final Integer PSM_ID = 4001; + private static final String AUTH = "sess-0d5f3a7c"; + + @Mock + private FacilityService uptsuService; + + @InjectMocks + private FacilityController controller; + + private static final String UPLOAD_REQUEST = "{\"createdBy\":\"admin\",\"fileName\":\"facilities.xlsx\"," + + "\"providerServiceMapID\":4001,\"fileExtension\":\"xlsx\",\"fileContent\":\"data:x;base64,AAA=\"}"; + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + @Test + @DisplayName("saveFacilityData should report the upload once the service has stored it") + void saveFacilityData_shouldReportStoredUpload() throws Exception { + when(uptsuService.saveFacility(any())).thenReturn(List.of(new M_FacilityMapping())); + + String response = controller.saveFacilityData(UPLOAD_REQUEST, AUTH); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("saveFacilityData saved successfully"), response); + } + + @Test + @DisplayName("saveFacilityData should refuse an upload the service could not read") + void saveFacilityData_shouldRefuseUnreadableUpload() throws Exception { + when(uptsuService.saveFacility(any())) + .thenThrow(new com.iemr.admin.utils.exception.IEMRException("Error in validating cell")); + + String response = controller.saveFacilityData(UPLOAD_REQUEST, AUTH); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + assertTrue(response.contains("Invalid Request"), response); + } + + @Test + @DisplayName("saveFacilityData should refuse a request body that is not a valid upload at all") + void saveFacilityData_shouldRefuseMalformedRequest() { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveFacilityData("not json", AUTH))); + } + + @Test + @DisplayName("saveFacilityData should stay at its default when the service stored nothing") + void saveFacilityData_shouldStayAtDefaultWhenNothingStored() throws Exception { + when(uptsuService.saveFacility(any())).thenReturn(List.of()); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveFacilityData(UPLOAD_REQUEST, AUTH))); + } + + @Test + @DisplayName("saveCdssDetails should report the configuration once the service has stored it") + void saveCdssDetails_shouldReportStoredConfiguration() { + when(uptsuService.saveCdssDetails(any())).thenReturn(new CDSSMapping()); + + String response = controller.saveCdssDetails("{\"psmId\":4001,\"isCdss\":true}"); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Data saved successfully"), response); + } + + @Test + @DisplayName("saveCdssDetails should report the reason the service refused the configuration") + void saveCdssDetails_shouldReportRefusal() { + when(uptsuService.saveCdssDetails(any())).thenThrow(new IllegalStateException("no connection")); + + String response = controller.saveCdssDetails("{\"psmId\":4001}"); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + assertTrue(response.contains("no connection"), response); + } + + @Test + @DisplayName("saveCdssDetails should refuse a request body it cannot read") + void saveCdssDetails_shouldRefuseMalformedRequest() { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveCdssDetails("not json"))); + } + + @Test + @DisplayName("getCdssData should answer the configuration the service publishes") + void getCdssData_shouldAnswerPublishedConfiguration() throws Exception { + when(uptsuService.getCdssData(PSM_ID)).thenReturn("{\"psmId\":4001,\"isCdss\":true}"); + + String response = controller.getCdssData(PSM_ID, AUTH); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("4001"), response); + } + + @Test + @DisplayName("getCdssData should answer an error envelope when the lookup fails") + void getCdssData_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(uptsuService.getCdssData(anyInt())).thenThrow(new IllegalStateException("no connection")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getCdssData(PSM_ID, AUTH))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/userParkingPlaceMap/UserParkingPlaceMapControllerTest.java b/src/test/java/com/iemr/admin/controller/userParkingPlaceMap/UserParkingPlaceMapControllerTest.java new file mode 100644 index 0000000..e9a169b --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/userParkingPlaceMap/UserParkingPlaceMapControllerTest.java @@ -0,0 +1,289 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.userParkingPlaceMap; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.data.userParkingPlaceMap.M_UserParkingPlaceMap; +import com.iemr.admin.data.userParkingPlaceMap.M_UserVanMapping; +import com.iemr.admin.service.userParkingPlaceMap.UserParkingPlaceMapServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The user parking place endpoints post a field user to the parking place they + * report to, and to the vans they work out of. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("UserParkingPlaceMapController Test Suite") +class UserParkingPlaceMapControllerTest { + + private static final Integer MAP_ID = 9001; + private static final Integer PSM_ID = 4001; + + @Mock + private UserParkingPlaceMapServiceImpl userParkingPlaceMapServiceImpl; + + @InjectMocks + private UserParkingPlaceMapController controller; + + private static M_UserParkingPlaceMap mapping(Integer id) { + return new M_UserParkingPlaceMap(id, 3117, "Asha", "Rao", "asha.rao", 7, 301, 31, "Hosur parking", + PSM_ID, Boolean.FALSE, Boolean.FALSE); + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + private static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } + + @Test + @DisplayName("saveuserParkingPlaces should answer the postings the service stored") + void save_shouldAnswerStoredPostings() throws Exception { + when(userParkingPlaceMapServiceImpl.saveUserParkingPlaceDetails(anyList())) + .thenReturn(new ArrayList<>(List.of(mapping(MAP_ID)))); + + assertSuccessContaining(controller.saveuserParkingPlaces( + "{\"userParkingPlaceMaps\":[{\"userID\":3117,\"parkingPlaceID\":31}]}"), "Asha"); + } + + @Test + @DisplayName("saveuserParkingPlaces should answer an error envelope when the store fails") + void save_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(userParkingPlaceMapServiceImpl.saveUserParkingPlaceDetails(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveuserParkingPlaces("{\"userParkingPlaceMaps\":[{}]}")); + } + + @Test + @DisplayName("getuserParkingPlaces should answer the postings matching the location filters") + void get_shouldAnswerMatchingPostings() throws Exception { + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceMappings(77, 29, 301, 31, 7)) + .thenReturn(new ArrayList<>(List.of(mapping(MAP_ID)))); + + assertSuccessContaining(controller.getuserParkingPlaces("{\"serviceProviderID\":77,\"stateID\":29," + + "\"districtID\":301,\"parkingPlaceID\":31,\"m_user\":{\"designationID\":7}}"), "Asha"); + } + + @Test + @DisplayName("getuserParkingPlaces should answer an error envelope when the request names no user detail") + void get_shouldAnswerErrorEnvelopeWithoutUserDetail() throws Exception { + assertCodeException(controller.getuserParkingPlaces("{\"serviceProviderID\":77}")); + } + + @Test + @DisplayName("getuserParkingPlacesDesiganation should narrow the postings to the designation") + void getByDesignation_shouldNarrowToDesignation() throws Exception { + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceMappings1(PSM_ID, 301, 31, 7)) + .thenReturn(new ArrayList<>(List.of(mapping(MAP_ID)))); + + assertSuccessContaining(controller.getuserParkingPlacesDesiganation( + "{\"providerServiceMapID\":4001,\"districtID\":301,\"parkingPlaceID\":31," + + "\"designationID\":7}"), "Asha"); + } + + @Test + @DisplayName("getuserParkingPlacesDesiganation should answer an error envelope when the lookup fails") + void getByDesignation_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceMappings1(any(), any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getuserParkingPlacesDesiganation("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("deleteuserParkingPlaceDetails should report whether the posting actually changed") + void deleteDetails_shouldReportOutcome() throws Exception { + when(userParkingPlaceMapServiceImpl.updateUserParkingPlaceMapStatus(any())).thenReturn(1); + assertSuccessContaining( + controller.deleteuserParkingPlaceDetails("{\"userParkingPlaceMapID\":9001,\"deleted\":true}"), + "status updated successfully"); + + when(userParkingPlaceMapServiceImpl.updateUserParkingPlaceMapStatus(any())).thenReturn(0); + assertSuccessContaining( + controller.deleteuserParkingPlaceDetails("{\"userParkingPlaceMapID\":9001,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteuserParkingPlaceDetails should answer an error envelope when the change fails") + void deleteDetails_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(userParkingPlaceMapServiceImpl.updateUserParkingPlaceMapStatus(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure( + controller.deleteuserParkingPlaceDetails("{\"userParkingPlaceMapID\":9001,\"deleted\":true}")); + } + + @Test + @DisplayName("edituserParkingPlaces should copy the edits and remap the vans") + void edit_shouldCopyEditsAndRemapVans() throws Exception { + M_UserParkingPlaceMap stored = mapping(MAP_ID); + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceDetails(MAP_ID)).thenReturn(stored); + when(userParkingPlaceMapServiceImpl.saveediteddata(any(), anyList())).thenReturn(stored); + + String response = controller.edituserParkingPlaces("{\"userParkingPlaceMapID\":9001," + + "\"parkingPlaceID\":32,\"providerServiceMapID\":4002,\"districtID\":302," + + "\"modifiedBy\":\"admin\",\"uservanmapping\":[{\"vanID\":71}]}"); + + assertSuccessContaining(response, "Asha"); + assertEquals(32, stored.getParkingPlaceID()); + assertEquals(4002, stored.getProviderServiceMapID()); + } + + @Test + @DisplayName("edituserParkingPlaces should answer an error envelope for a posting that does not exist") + void edit_shouldAnswerErrorEnvelopeForUnknownPosting() throws Exception { + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceDetails(anyInt())).thenReturn(null); + + assertCodeException(controller.edituserParkingPlaces("{\"userParkingPlaceMapID\":9001}")); + } + + @Test + @DisplayName("deleteuserParkingPlaces should retire the posting the caller names") + void deletePostings_shouldRetirePosting() throws Exception { + M_UserParkingPlaceMap stored = mapping(MAP_ID); + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceDetails(MAP_ID)).thenReturn(stored); + when(userParkingPlaceMapServiceImpl.saveediteddata(stored)).thenReturn(stored); + + assertSuccessContaining( + controller.deleteuserParkingPlaces("{\"userParkingPlaceMapID\":9001,\"deleted\":true}"), "Asha"); + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("deleteuserParkingPlaces should refuse to reinstate a user already posted elsewhere") + void deletePostings_shouldRefuseReinstatingPostedUser() throws Exception { + M_UserParkingPlaceMap stored = mapping(MAP_ID); + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceDetails(MAP_ID)).thenReturn(stored); + when(userParkingPlaceMapServiceImpl.getuserexist(PSM_ID, 3117)).thenReturn(Boolean.TRUE); + + String response = controller + .deleteuserParkingPlaces("{\"userParkingPlaceMapID\":9001,\"deleted\":false}"); + + assertGenericFailure(response); + assertTrue(response.contains("User already mapped. Cannot activate"), response); + } + + @Test + @DisplayName("deleteuserParkingPlaces should reinstate a user who is posted nowhere else") + void deletePostings_shouldReinstateFreeUser() throws Exception { + M_UserParkingPlaceMap stored = mapping(MAP_ID); + when(userParkingPlaceMapServiceImpl.getUserParkingPlaceDetails(MAP_ID)).thenReturn(stored); + when(userParkingPlaceMapServiceImpl.getuserexist(PSM_ID, 3117)).thenReturn(Boolean.FALSE); + when(userParkingPlaceMapServiceImpl.saveediteddata(stored)).thenReturn(stored); + + controller.deleteuserParkingPlaces("{\"userParkingPlaceMapID\":9001,\"deleted\":false}"); + + assertEquals(Boolean.FALSE, stored.getDeleted()); + } + + @Test + @DisplayName("unmappeduser should answer the users not yet posted anywhere") + void unmappeduser_shouldAnswerUnpostedUsers() throws Exception { + M_User1 user = new M_User1(); + user.setUserID(3117); + user.setFirstName("Asha"); + when(userParkingPlaceMapServiceImpl.getunmappedUser(PSM_ID, 7)).thenReturn(List.of(user)); + + assertSuccessContaining( + controller.unmappeduser("{\"providerServiceMapID\":4001,\"designationID\":7}"), "Asha"); + } + + @Test + @DisplayName("unmappeduser should answer an error envelope when the lookup fails") + void unmappeduser_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(userParkingPlaceMapServiceImpl.getunmappedUser(any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.unmappeduser("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("mappedvan should answer the vans the posting covers") + void mappedvan_shouldAnswerCoveredVans() throws Exception { + M_UserVanMapping vanMapping = new M_UserVanMapping(); + vanMapping.setUserVanMapID(7001); + when(userParkingPlaceMapServiceImpl.getuservanmapping(MAP_ID)).thenReturn(List.of(vanMapping)); + + assertSuccessContaining(controller.mappedvan(MAP_ID), "7001"); + } + + @Test + @DisplayName("mappedvan should answer an error envelope when the lookup fails") + void mappedvan_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(userParkingPlaceMapServiceImpl.getuservanmapping(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.mappedvan(MAP_ID)); + } + + @Test + @DisplayName("deletemappedvan should report the van mapping it released") + void deletemappedvan_shouldReportReleasedMapping() throws Exception { + assertSuccessContaining( + controller.deletemappedvan("{\"userVanMapID\":7001,\"modifiedBy\":\"admin\"}"), "Success"); + verify(userParkingPlaceMapServiceImpl).deleteuservanmapping(any()); + } + + @Test + @DisplayName("deletemappedvan should answer an error envelope when the release fails") + void deletemappedvan_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + org.mockito.Mockito.doThrow(new IllegalStateException("no connection")) + .when(userParkingPlaceMapServiceImpl).deleteuservanmapping(any()); + + assertGenericFailure(controller.deletemappedvan("{\"userVanMapID\":7001}")); + } +} diff --git a/src/test/java/com/iemr/admin/controller/vanMaster/VanMasterControllerTest.java b/src/test/java/com/iemr/admin/controller/vanMaster/VanMasterControllerTest.java new file mode 100644 index 0000000..9a4e8f8 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/vanMaster/VanMasterControllerTest.java @@ -0,0 +1,281 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.vanMaster; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.vanMaster.M_Van; +import com.iemr.admin.data.vanType.M_VanType; +import com.iemr.admin.service.vanMaster.VanMasterServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The van screens keep the mobile unit fleet: what vans exist, what type each + * is, and which parking place or main store each belongs to. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VanMasterController Test Suite") +class VanMasterControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer VAN_ID = 71; + private static final Integer PARKING_PLACE_ID = 31; + + @Mock + private VanMasterServiceImpl vanMasterServiceImpl; + + @InjectMocks + private VanMasterController controller; + + private static M_Van van() { + M_Van van = new M_Van(); + van.setVanID(VAN_ID); + van.setVanName("Mobile unit 7"); + van.setVehicalNo("KA-01-AB-1234"); + van.setParkingPlaceID(PARKING_PLACE_ID); + van.setProviderServiceMapID(PSM_ID); + return van; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("saveVanDetails should answer the vans it added to the fleet") + void saveVanDetails_shouldAnswerAddedVans() throws Exception { + when(vanMasterServiceImpl.saveVanDetails(anyList())).thenReturn(new ArrayList<>(List.of(van()))); + + assertSuccessContaining(controller.saveVanDetails( + "{\"vanMaster\":[{\"vanName\":\"Mobile unit 7\",\"vehicalNo\":\"KA-01-AB-1234\"}]}"), + "Mobile unit 7"); + } + + @Test + @DisplayName("saveVanDetails should report the failure when the van cannot be added") + void saveVanDetails_shouldReportStorageFailure() throws Exception { + when(vanMasterServiceImpl.saveVanDetails(anyList())) + .thenThrow(new RuntimeException("vehicle number already on file")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.saveVanDetails("{\"vanMaster\":[{\"vanName\":\"Mobile unit 7\"}]}"))); + } + + @Test + @DisplayName("getServicePoints should answer the vans of the parking place and type asked for") + void getVans_shouldAnswerVansOfParkingPlaceAndType() throws Exception { + when(vanMasterServiceImpl.getAvailableVans(PARKING_PLACE_ID, 2, PSM_ID)) + .thenReturn(new ArrayList<>(List.of(van()))); + + assertSuccessContaining(controller.getServicePoints( + "{\"parkingPlaceID\":31,\"vanTypeID\":2,\"providerServiceMapID\":4001}"), "Mobile unit 7"); + } + + @Test + @DisplayName("getServicePoints should report the failure when the fleet cannot be answered") + void getVans_shouldReportLookupFailure() throws Exception { + when(vanMasterServiceImpl.getAvailableVans(any(), any(), any())) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getServicePoints("{\"providerServiceMapID\":4001}"))); + } + + @Test + @DisplayName("deleteVanDetails should confirm the retirement it recorded") + void deleteVanDetails_shouldConfirmRetirement() throws Exception { + when(vanMasterServiceImpl.updateVanStatus(any(M_Van.class))).thenReturn(1); + + assertSuccessContaining(controller.deleteVanDetails("{\"vanID\":71,\"deleted\":true,\"modifiedBy\":\"admin\"}"), + "status updated successfully"); + } + + @Test + @DisplayName("deleteVanDetails should say so when no van was retired") + void deleteVanDetails_shouldSaySoWhenNothingRetired() throws Exception { + when(vanMasterServiceImpl.updateVanStatus(any(M_Van.class))).thenReturn(0); + + assertSuccessContaining(controller.deleteVanDetails("{\"vanID\":-1,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteVanDetails should report the failure when the retirement cannot be recorded") + void deleteVanDetails_shouldReportRetirementFailure() throws Exception { + when(vanMasterServiceImpl.updateVanStatus(any(M_Van.class))).thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.deleteVanDetails("{\"vanID\":71}"))); + } + + @Test + @DisplayName("updateZoneData should answer the van whose details it changed") + void updateVanDetails_shouldAnswerChangedVan() throws Exception { + M_Van stored = van(); + when(vanMasterServiceImpl.getVanByID(VAN_ID)).thenReturn(stored); + when(vanMasterServiceImpl.updateVanData(stored)).thenReturn(stored); + + assertSuccessContaining(controller.updateZoneData( + "{\"vanID\":71,\"vanName\":\"Mobile unit 9\",\"vehicalNo\":\"KA-01-AB-9999\",\"vanTypeID\":2," + + "\"stateID\":29,\"parkingPlaceID\":31,\"modifiedBy\":\"admin\"," + + "\"videoConsultationDomain\":\"psmri\"}"), + "Mobile unit 9"); + assertEquals("KA-01-AB-9999", stored.getVehicalNo()); + assertEquals("psmri", stored.getVideoConsultationDomain()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("updateZoneData should report the failure when the van is unknown") + void updateVanDetails_shouldReportUnknownVan() throws Exception { + when(vanMasterServiceImpl.getVanByID(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.updateZoneData("{\"vanID\":-1}"))); + } + + @Test + @DisplayName("saveVanTypeDetails should answer the van types it added") + void saveVanTypeDetails_shouldAnswerAddedTypes() throws Exception { + when(vanMasterServiceImpl.saveVanTypeDetails(anyList())).thenReturn( + new ArrayList<>(List.of(new M_VanType(2, "Diagnostic van", "Carries lab kit", Boolean.FALSE)))); + + assertSuccessContaining( + controller.saveVanTypeDetails("{\"vanTypeMaster\":[{\"vanType\":\"Diagnostic van\"}]}"), + "Diagnostic van"); + } + + @Test + @DisplayName("saveVanTypeDetails should report the failure when the van type cannot be added") + void saveVanTypeDetails_shouldReportStorageFailure() throws Exception { + when(vanMasterServiceImpl.saveVanTypeDetails(anyList())).thenThrow(new RuntimeException("already on file")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf( + controller.saveVanTypeDetails("{\"vanTypeMaster\":[{\"vanType\":\"Diagnostic van\"}]}"))); + } + + @Test + @DisplayName("getVanTypes should answer the van types on file") + void getVanTypes_shouldAnswerTypesOnFile() { + when(vanMasterServiceImpl.getVanTypes()).thenReturn( + new ArrayList<>(List.of(new M_VanType(2, "Diagnostic van", "Carries lab kit", Boolean.FALSE)))); + + assertSuccessContaining(controller.getVanTypes(), "Diagnostic van"); + } + + @Test + @DisplayName("getVanTypes should report the failure when the list cannot be answered") + void getVanTypes_shouldReportLookupFailure() { + when(vanMasterServiceImpl.getVanTypes()).thenThrow(new RuntimeException("connection reset")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getVanTypes())); + } + + @Test + @DisplayName("deleteVanType should confirm the retirement it recorded") + void deleteVanType_shouldConfirmRetirement() throws Exception { + when(vanMasterServiceImpl.updateVanTypeStatus(any(M_VanType.class))).thenReturn(1); + + assertSuccessContaining(controller.deleteVanType("{\"vanTypeID\":2,\"deleted\":true}"), + "status updated successfully"); + } + + @Test + @DisplayName("deleteVanType should say so when no van type was retired") + void deleteVanType_shouldSaySoWhenNothingRetired() throws Exception { + when(vanMasterServiceImpl.updateVanTypeStatus(any(M_VanType.class))).thenReturn(0); + + assertSuccessContaining(controller.deleteVanType("{\"vanTypeID\":-1,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteVanType should report the failure when the retirement cannot be recorded") + void deleteVanType_shouldReportRetirementFailure() throws Exception { + when(vanMasterServiceImpl.updateVanTypeStatus(any(M_VanType.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.deleteVanType("{\"vanTypeID\":2}"))); + } + + @Test + @DisplayName("getVanMaster should answer the vans of the provider and parking place asked for") + void getVanMaster_shouldAnswerVansOfProviderAndParkingPlace() throws Exception { + when(vanMasterServiceImpl.getVanMaster(PSM_ID, PARKING_PLACE_ID)).thenReturn(List.of(van())); + + assertSuccessContaining( + controller.getVanMaster("{\"providerServiceMapID\":4001,\"parkingPlaceID\":31}"), "Mobile unit 7"); + } + + @Test + @DisplayName("getVanMaster should report the failure when the lookup cannot be answered") + void getVanMaster_shouldReportLookupFailure() throws Exception { + when(vanMasterServiceImpl.getVanMaster(any(), any())).thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getVanMaster("{\"providerServiceMapID\":4001}"))); + } + + @Test + @DisplayName("getVanFromFacilityID should answer the vans belonging to the store asked for") + void getVanFromFacilityID_shouldAnswerVansOfStore() throws Exception { + M_Van request = new M_Van(); + request.setFacilityID(9001); + when(vanMasterServiceImpl.getVanFromFacilityID(9001)).thenReturn(List.of(van())); + + assertSuccessContaining(controller.getVanFromFacilityID(request), "Mobile unit 7"); + } + + @Test + @DisplayName("getVanFromFacilityID should report the failure when the store has no parking place") + void getVanFromFacilityID_shouldReportStoreWithoutParkingPlace() throws Exception { + M_Van request = new M_Van(); + request.setFacilityID(9001); + when(vanMasterServiceImpl.getVanFromFacilityID(9001)) + .thenThrow(new Exception("Main Store doesnt have any Parking place mapped")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getVanFromFacilityID(request))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/vanServicePointMapping/VanServicePointMappingControllerTest.java b/src/test/java/com/iemr/admin/controller/vanServicePointMapping/VanServicePointMappingControllerTest.java new file mode 100644 index 0000000..1aa87fd --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/vanServicePointMapping/VanServicePointMappingControllerTest.java @@ -0,0 +1,206 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.vanServicePointMapping; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.vanServicePointMapping.M_VanServicePointMap; +import com.iemr.admin.service.vanServicePointMapping.VanServicePointMappingServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +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.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The van service point screen records which service points each van visits, + * adding new visits and changing the session of ones already on file. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VanServicePointMappingController Test Suite") +class VanServicePointMappingControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer VAN_ID = 71; + private static final Integer MAP_ID = 8801; + + @Mock + private VanServicePointMappingServiceImpl vanServicePointMappingServiceImpl; + + @InjectMocks + private VanServicePointMappingController controller; + + private static M_VanServicePointMap visit() { + return new M_VanServicePointMap(MAP_ID, VAN_ID, (short) 1, 88, "Attibele PHC", PSM_ID, Boolean.FALSE); + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("saveVanServicePointMappings should store a brand new visit as it stands") + void save_shouldStoreBrandNewVisit() throws Exception { + when(vanServicePointMappingServiceImpl.saveVanServicePointMappings(anyList())) + .thenReturn(new ArrayList<>(List.of(visit()))); + + assertSuccessContaining(controller.saveVanServicePointMappings( + "{\"vanServicePointMappings\":[{\"vanID\":71,\"servicePointID\":88,\"vanSession\":1," + + "\"createdBy\":\"admin\"}]}"), + "Attibele PHC"); + verify(vanServicePointMappingServiceImpl, never()).getVanServicePointMappingByID(anyInt()); + } + + @Test + @DisplayName("saveVanServicePointMappings should change the session of a visit already on file") + void save_shouldChangeSessionOfExistingVisit() throws Exception { + M_VanServicePointMap stored = visit(); + when(vanServicePointMappingServiceImpl.getVanServicePointMappingByID(MAP_ID)).thenReturn(stored); + when(vanServicePointMappingServiceImpl.saveVanServicePointMappings(anyList())) + .thenReturn(new ArrayList<>(List.of(stored))); + + controller.saveVanServicePointMappings( + "{\"vanServicePointMappings\":[{\"vanServicePointMapID\":8801,\"vanSession\":2," + + "\"createdBy\":\"supervisor\"}]}"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vanServicePointMappingServiceImpl).saveVanServicePointMappings(captor.capture()); + assertEquals((short) 2, captor.getValue().get(0).getVanSession()); + assertEquals("supervisor", captor.getValue().get(0).getModifiedBy()); + } + + @Test + @DisplayName("saveVanServicePointMappings should report the failure when the visit named is unknown") + void save_shouldReportUnknownVisit() throws Exception { + when(vanServicePointMappingServiceImpl.getVanServicePointMappingByID(anyInt())).thenReturn(null); + + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(controller.saveVanServicePointMappings( + "{\"vanServicePointMappings\":[{\"vanServicePointMapID\":8801,\"vanSession\":2}]}"))); + } + + @Test + @DisplayName("saveVanServicePointMappings should report the failure when the visits cannot be stored") + void save_shouldReportStorageFailure() throws Exception { + when(vanServicePointMappingServiceImpl.saveVanServicePointMappings(anyList())) + .thenThrow(new RuntimeException("service point already visited in that session")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveVanServicePointMappings( + "{\"vanServicePointMappings\":[{\"vanID\":71,\"servicePointID\":88}]}"))); + } + + @Test + @DisplayName("getVanServicePointMappings should answer the visits of the van asked about") + void get_shouldAnswerVisitsOfVan() throws Exception { + when(vanServicePointMappingServiceImpl.getAvailableVanServicePointMappings(31, VAN_ID, PSM_ID)) + .thenReturn(new ArrayList<>(List.of(visit()))); + + assertSuccessContaining(controller.getVanServicePointMappings( + "{\"parkingPlaceID\":31,\"vanID\":71,\"providerServiceMapID\":4001}"), "Attibele PHC"); + } + + @Test + @DisplayName("getVanServicePointMappings should report the failure when the visits cannot be answered") + void get_shouldReportLookupFailure() throws Exception { + when(vanServicePointMappingServiceImpl.getAvailableVanServicePointMappings(any(), any(), any())) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getVanServicePointMappings("{\"vanID\":71}"))); + } + + @Test + @DisplayName("vanServicePointMappingsV1 should answer the visits with their district and taluk") + void getV1_shouldAnswerVisitsWithLocation() throws Exception { + when(vanServicePointMappingServiceImpl.getAvailableVanServicePointMappingsV1(31, VAN_ID, PSM_ID)) + .thenReturn(new ArrayList<>(List.of(new M_VanServicePointMap(MAP_ID, VAN_ID, (short) 1, 88, + "Attibele PHC", PSM_ID, Boolean.FALSE, 301, "Bengaluru Urban", 3011, "Anekal")))); + + assertSuccessContaining(controller.vanServicePointMappingsV1( + "{\"parkingPlaceID\":31,\"vanID\":71,\"providerServiceMapID\":4001}"), "Anekal"); + } + + @Test + @DisplayName("vanServicePointMappingsV1 should report the failure when the visits cannot be answered") + void getV1_shouldReportLookupFailure() throws Exception { + when(vanServicePointMappingServiceImpl.getAvailableVanServicePointMappingsV1(any(), any(), any())) + .thenThrow(new RuntimeException("connection reset")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.vanServicePointMappingsV1("{\"vanID\":71}"))); + } + + @Test + @DisplayName("deleteVanServicePointMappingDetails should confirm the retirement it recorded") + void delete_shouldConfirmRetirement() throws Exception { + when(vanServicePointMappingServiceImpl.updateVanServicePointMappingStatus(any(M_VanServicePointMap.class))) + .thenReturn(1); + + assertSuccessContaining(controller.deleteVanServicePointMappingDetails( + "{\"vanServicePointMapID\":8801,\"deleted\":true,\"modifiedBy\":\"admin\"}"), + "status updated successfully"); + } + + @Test + @DisplayName("deleteVanServicePointMappingDetails should say so when no visit was retired") + void delete_shouldSaySoWhenNothingRetired() throws Exception { + when(vanServicePointMappingServiceImpl.updateVanServicePointMappingStatus(any(M_VanServicePointMap.class))) + .thenReturn(0); + + assertSuccessContaining( + controller.deleteVanServicePointMappingDetails("{\"vanServicePointMapID\":-1,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteVanServicePointMappingDetails should report the failure when the retirement cannot be recorded") + void delete_shouldReportStorageFailure() throws Exception { + when(vanServicePointMappingServiceImpl.updateVanServicePointMappingStatus(any(M_VanServicePointMap.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf( + controller.deleteVanServicePointMappingDetails("{\"vanServicePointMapID\":8801}"))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/vanSpokeMapping/VanSpokeMappingControllerTest.java b/src/test/java/com/iemr/admin/controller/vanSpokeMapping/VanSpokeMappingControllerTest.java new file mode 100644 index 0000000..590fa56 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/vanSpokeMapping/VanSpokeMappingControllerTest.java @@ -0,0 +1,159 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.vanSpokeMapping; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.service.vanSpokeMapping.VanSpokeMappingService; +import com.iemr.admin.utils.exception.IEMRException; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * The spoke mapping screen ties mobile unit vans to the telemedicine spokes + * they serve. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VanSpokeMappingController Test Suite") +class VanSpokeMappingControllerTest { + + private static final String AUTHORIZATION = "session-key-123"; + private static final String REQUEST = "{\"vanSpokeMapping\":[{\"mmu_VanID\":71}]}"; + + @Mock + private VanSpokeMappingService vanSpokeMappingService; + + @InjectMocks + private VanSpokeMappingController controller; + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static String errorMessageOf(String response) { + return new JSONObject(response).getString("errorMessage"); + } + + @Test + @DisplayName("saving a mapping should confirm the tie it recorded") + void save_shouldConfirmRecordedTie() throws Exception { + when(vanSpokeMappingService.saveVanSpokeMapping(anyString())).thenReturn("success"); + + String response = controller.saveBenNCDCareNurseData(REQUEST, AUTHORIZATION); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Mapping done successfully"), response); + } + + @Test + @DisplayName("saving a mapping should report the failure when the tie was refused") + void save_shouldReportRefusedTie() throws Exception { + when(vanSpokeMappingService.saveVanSpokeMapping(anyString())).thenReturn("failure"); + + assertEquals("error in mapping van and spoke", + errorMessageOf(controller.saveBenNCDCareNurseData(REQUEST, AUTHORIZATION))); + } + + @Test + @DisplayName("saving a mapping should report the failure when the tie cannot be recorded") + void save_shouldReportStorageFailure() throws Exception { + when(vanSpokeMappingService.saveVanSpokeMapping(anyString())) + .thenThrow(new IEMRException("van is already mapped")); + + assertEquals(OutputResponse.USERID_FAILURE, + statusCodeOf(controller.saveBenNCDCareNurseData(REQUEST, AUTHORIZATION))); + } + + @Test + @DisplayName("getVanSpokeMapping should answer the ties held at the parking place asked about") + void get_shouldAnswerHeldTies() throws Exception { + when(vanSpokeMappingService.getVanSpokeMappingDetails(anyString())) + .thenReturn("{\"vanSpokeMappedDetails\":[{\"vanspokeID\":6001}]}"); + + String response = controller.getVanSpokeMapping(REQUEST, AUTHORIZATION); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("vanSpokeMappedDetails"), response); + } + + @Test + @DisplayName("getVanSpokeMapping should report the failure when nothing came back from the service") + void get_shouldReportMissingAnswer() throws Exception { + when(vanSpokeMappingService.getVanSpokeMappingDetails(anyString())).thenReturn(null); + + assertEquals("error in fetching the van and spoke data", + errorMessageOf(controller.getVanSpokeMapping(REQUEST, AUTHORIZATION))); + } + + @Test + @DisplayName("getVanSpokeMapping should report the failure when the lookup cannot be answered") + void get_shouldReportLookupFailure() throws Exception { + when(vanSpokeMappingService.getVanSpokeMappingDetails(anyString())) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.getVanSpokeMapping(REQUEST, AUTHORIZATION))); + } + + @Test + @DisplayName("deleteVanSpokeMapping should confirm the change it recorded") + void delete_shouldConfirmRecordedChange() throws Exception { + when(vanSpokeMappingService.deleteVanSpokeMapping(anyString())).thenReturn("success"); + + String response = controller.deleteVanSpokeMapping(REQUEST, AUTHORIZATION); + + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains("Mapping status got updated"), response); + } + + @Test + @DisplayName("deleteVanSpokeMapping should report the failure when the change was refused") + void delete_shouldReportRefusedChange() throws Exception { + when(vanSpokeMappingService.deleteVanSpokeMapping(anyString())).thenReturn("failure"); + + assertEquals("Error in deleting mapping", + errorMessageOf(controller.deleteVanSpokeMapping(REQUEST, AUTHORIZATION))); + } + + @Test + @DisplayName("deleteVanSpokeMapping should report the failure when the change cannot be recorded") + void delete_shouldReportStorageFailure() throws Exception { + when(vanSpokeMappingService.deleteVanSpokeMapping(anyString())) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.deleteVanSpokeMapping(REQUEST, AUTHORIZATION))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/version/VersionControllerTest.java b/src/test/java/com/iemr/admin/controller/version/VersionControllerTest.java new file mode 100644 index 0000000..1363758 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/version/VersionControllerTest.java @@ -0,0 +1,60 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.version; + +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The version endpoint tells an operator which build is deployed, and must + * answer something rather than fail when the build stamp is missing. + */ +@DisplayName("VersionController Test Suite") +class VersionControllerTest { + + private final VersionController controller = new VersionController(); + + @Test + @DisplayName("versionInformation should answer the build stamp fields the deployment records") + void versionInformation_shouldAnswerBuildStampFields() { + ResponseEntity> response = controller.versionInformation(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.getBody().containsKey("buildTimestamp")); + assertTrue(response.getBody().containsKey("version")); + assertTrue(response.getBody().containsKey("branch")); + assertTrue(response.getBody().containsKey("commitHash")); + } + + @Test + @DisplayName("versionInformation should answer the same stamp on every call") + void versionInformation_shouldAnswerConsistently() { + assertEquals(controller.versionInformation().getBody(), controller.versionInformation().getBody()); + } +} diff --git a/src/test/java/com/iemr/admin/controller/villageMaster/VillageMasterControllerTest.java b/src/test/java/com/iemr/admin/controller/villageMaster/VillageMasterControllerTest.java new file mode 100644 index 0000000..f56006e --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/villageMaster/VillageMasterControllerTest.java @@ -0,0 +1,177 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.villageMaster; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.DistrictBranchMapping; +import com.iemr.admin.service.villageMaster.VillageMasterServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The village screen keeps the villages of a taluk and the details each one is + * addressed by. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VillageMasterController Test Suite") +class VillageMasterControllerTest { + + private static final Integer BLOCK_ID = 3011; + + @Mock + private VillageMasterServiceImpl villageMasterServiceImpl; + + @InjectMocks + private VillageMasterController controller; + + private static DistrictBranchMapping village() { + DistrictBranchMapping village = new DistrictBranchMapping(); + village.setDistrictBranchID(30111); + village.setVillageName("Attibele"); + village.setPinCode("562107"); + return village; + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String expected) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(expected), response); + } + + @Test + @DisplayName("saveVillageDetails should answer the villages it added") + void save_shouldAnswerAddedVillages() { + when(villageMasterServiceImpl.storeVillageDetails(anyList())) + .thenReturn(new ArrayList<>(List.of(village()))); + + assertSuccessContaining(controller.saveVillageDetails( + "{\"districtBranchMapping\":[{\"villageName\":\"Attibele\",\"blockID\":3011}]}"), "Attibele"); + } + + @Test + @DisplayName("saveVillageDetails should report the failure when the village cannot be added") + void save_shouldReportStorageFailure() { + when(villageMasterServiceImpl.storeVillageDetails(anyList())) + .thenThrow(new RuntimeException("village already on file")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.saveVillageDetails( + "{\"districtBranchMapping\":[{\"villageName\":\"Attibele\"}]}"))); + } + + @Test + @DisplayName("getVillages should answer the villages of the taluk asked about") + void get_shouldAnswerVillagesOfTaluk() { + when(villageMasterServiceImpl.getAvailableVillages(BLOCK_ID)) + .thenReturn(new ArrayList<>(List.of(village()))); + + assertSuccessContaining(controller.getVillages("{\"blockID\":3011}"), "Attibele"); + } + + @Test + @DisplayName("getVillages should report the failure when the villages cannot be answered") + void get_shouldReportLookupFailure() { + when(villageMasterServiceImpl.getAvailableVillages(any())) + .thenThrow(new RuntimeException("query timed out")); + + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(controller.getVillages("{\"blockID\":3011}"))); + } + + @Test + @DisplayName("deleteVillage should confirm the retirement it recorded") + void delete_shouldConfirmRetirement() { + when(villageMasterServiceImpl.updateVillageStatus(any(DistrictBranchMapping.class))).thenReturn(1); + + assertSuccessContaining( + controller.deleteVillage("{\"districtBranchID\":30111,\"deleted\":true,\"modifiedBy\":\"admin\"}"), + "status updated successfully"); + } + + @Test + @DisplayName("deleteVillage should say so when no village was retired") + void delete_shouldSaySoWhenNothingRetired() { + when(villageMasterServiceImpl.updateVillageStatus(any(DistrictBranchMapping.class))).thenReturn(0); + + assertSuccessContaining(controller.deleteVillage("{\"districtBranchID\":-1,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteVillage should report the failure when the retirement cannot be recorded") + void delete_shouldReportStorageFailure() { + when(villageMasterServiceImpl.updateVillageStatus(any(DistrictBranchMapping.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.deleteVillage("{\"districtBranchID\":30111}"))); + } + + @Test + @DisplayName("updateVillageData should confirm the change it recorded") + void update_shouldConfirmRecordedChange() { + when(villageMasterServiceImpl.updateVillageData(any(DistrictBranchMapping.class))).thenReturn(1); + + assertSuccessContaining(controller.updateVillageData( + "{\"districtBranchID\":30111,\"villageName\":\"Attibele\",\"pinCode\":\"562107\"," + + "\"modifiedBy\":\"admin\"}"), + "status updated successfully"); + } + + @Test + @DisplayName("updateVillageData should say so when no village was changed") + void update_shouldSaySoWhenNothingChanged() { + when(villageMasterServiceImpl.updateVillageData(any(DistrictBranchMapping.class))).thenReturn(0); + + assertSuccessContaining(controller.updateVillageData("{\"districtBranchID\":-1}"), + "Failed to update the status"); + } + + @Test + @DisplayName("updateVillageData should report the failure when the change cannot be recorded") + void update_shouldReportStorageFailure() { + when(villageMasterServiceImpl.updateVillageData(any(DistrictBranchMapping.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertEquals(OutputResponse.GENERIC_FAILURE, + statusCodeOf(controller.updateVillageData("{\"districtBranchID\":30111}"))); + } +} diff --git a/src/test/java/com/iemr/admin/controller/zonemaster/ZoneMasterControllerTest.java b/src/test/java/com/iemr/admin/controller/zonemaster/ZoneMasterControllerTest.java new file mode 100644 index 0000000..ed87be6 --- /dev/null +++ b/src/test/java/com/iemr/admin/controller/zonemaster/ZoneMasterControllerTest.java @@ -0,0 +1,287 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.controller.zonemaster; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.zonemaster.M_Zone; +import com.iemr.admin.data.zonemaster.M_ZoneDistrictMap; +import com.iemr.admin.service.zonemaster.ZoneMasterServiceImpl; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The zone endpoints group the districts a provider operates in, and a zone that + * is retired has to take its district mappings with it. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ZoneMasterController Test Suite") +class ZoneMasterControllerTest { + + private static final Integer PSM_ID = 4001; + private static final Integer ZONE_ID = 61; + + @Mock + private ZoneMasterServiceImpl zoneMasterServiceImpl; + + @InjectMocks + private ZoneMasterController controller; + + private static M_Zone zone(Integer id, String name) { + return new M_Zone(id, name, "Northern districts", "Main Road", PSM_ID, Boolean.FALSE, 1, "India", + 29, "Karnataka", 301, "Bengaluru Urban", 401, "North block", 501, "Hosur", null, 3, + "Tele Medicine"); + } + + private static M_ZoneDistrictMap districtMapping(Integer id) { + return new M_ZoneDistrictMap(id, ZONE_ID, "North zone", 301, PSM_ID, Boolean.FALSE, 29, "Karnataka", + "Bengaluru Urban", 3, "Tele Medicine", Boolean.FALSE); + } + + private static int statusCodeOf(String response) { + return new JSONObject(response).getInt("statusCode"); + } + + private static void assertSuccessContaining(String response, String fragment) { + assertEquals(OutputResponse.SUCCESS, statusCodeOf(response), response); + assertTrue(response.contains(fragment), response); + } + + private static void assertGenericFailure(String response) { + assertEquals(OutputResponse.GENERIC_FAILURE, statusCodeOf(response), response); + } + + private static void assertCodeException(String response) { + assertEquals(OutputResponse.CODE_EXCEPTION, statusCodeOf(response), response); + } + + @Test + @DisplayName("saveZone should answer the zones the service stored") + void saveZone_shouldAnswerStoredZones() throws Exception { + when(zoneMasterServiceImpl.createZone(anyList())) + .thenReturn(new ArrayList<>(List.of(zone(ZONE_ID, "North zone")))); + + assertSuccessContaining(controller.saveZone("{\"zones\":[{\"zoneName\":\"North zone\"}]}"), "North zone"); + } + + @Test + @DisplayName("saveZone should answer an error envelope when the store fails") + void saveZone_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(zoneMasterServiceImpl.createZone(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.saveZone("{\"zones\":[{\"zoneName\":\"North zone\"}]}")); + } + + @Test + @DisplayName("getZones should answer the zones of the provider") + void getZones_shouldAnswerProviderZones() { + when(zoneMasterServiceImpl.getAvailableZones(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(zone(ZONE_ID, "North zone")))); + + assertSuccessContaining(controller.getZones("{\"providerServiceMapID\":4001}"), "North zone"); + } + + @Test + @DisplayName("getZones should ask the caller for a provider when the request names none") + void getZones_shouldAskForProvider() { + assertSuccessContaining(controller.getZones("{}"), "Provide providerServiceMapID."); + verify(zoneMasterServiceImpl, never()).getAvailableZones(anyInt()); + } + + @Test + @DisplayName("getZones should stay at its default for a provider id of zero") + void getZones_shouldStayAtDefaultForZeroProvider() { + assertGenericFailure(controller.getZones("{\"providerServiceMapID\":0}")); + } + + @Test + @DisplayName("getZones should answer an error envelope for a body it cannot read") + void getZones_shouldAnswerErrorEnvelopeForMalformedBody() { + assertEquals(OutputResponse.OBJECT_FAILURE, statusCodeOf(controller.getZones("not json"))); + } + + @Test + @DisplayName("mapZoneWithDistrict should answer the mappings the service stored") + void mapZoneWithDistrict_shouldAnswerStoredMappings() throws Exception { + when(zoneMasterServiceImpl.createZoneDistrictMapping(anyList())) + .thenReturn(new ArrayList<>(List.of(districtMapping(9001)))); + + assertSuccessContaining(controller.mapZoneWithDistrict( + "{\"zoneDistrictMappings\":[{\"zoneID\":61,\"districtID\":301}]}"), "9001"); + } + + @Test + @DisplayName("mapZoneWithDistrict should answer an error envelope when the store fails") + void mapZoneWithDistrict_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(zoneMasterServiceImpl.createZoneDistrictMapping(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.mapZoneWithDistrict("{\"zoneDistrictMappings\":[{}]}")); + } + + @Test + @DisplayName("editZoneDistrict should copy the edits onto the stored mapping") + void editZoneDistrict_shouldCopyEdits() throws Exception { + M_ZoneDistrictMap stored = districtMapping(9001); + when(zoneMasterServiceImpl.editZoneDistrictMapping(9001)).thenReturn(stored); + when(zoneMasterServiceImpl.saveeditedData(stored)).thenReturn(stored); + + String response = controller.editZoneDistrict("{\"zoneDistrictMapID\":9001,\"zoneID\":62," + + "\"districtID\":302,\"providerServiceMapID\":4001,\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "9001"); + assertEquals(62, stored.getZoneID()); + assertEquals(302, stored.getDistrictID()); + } + + @Test + @DisplayName("editZoneDistrict should answer an error envelope for a mapping that does not exist") + void editZoneDistrict_shouldAnswerErrorEnvelopeForUnknownMapping() throws Exception { + when(zoneMasterServiceImpl.editZoneDistrictMapping(9001)).thenReturn(null); + + assertCodeException(controller.editZoneDistrict("{\"zoneDistrictMapID\":9001}")); + } + + @Test + @DisplayName("getZoneDistrictMappings should answer the district mappings of the provider") + void getZoneDistrictMappings_shouldAnswerProviderMappings() { + when(zoneMasterServiceImpl.getAvailableZoneDistrictMappings(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(districtMapping(9001)))); + + assertSuccessContaining( + controller.getZoneDistrictMappings("{\"providerServiceMapID\":4001}"), "9001"); + } + + @Test + @DisplayName("getZoneDistrictMappings should ask the caller for a provider when the request names none") + void getZoneDistrictMappings_shouldAskForProvider() { + assertSuccessContaining(controller.getZoneDistrictMappings("{}"), "Provide serviceProviderID."); + } + + @Test + @DisplayName("deleteZone should report whether the zone was actually retired") + void deleteZone_shouldReportOutcome() throws Exception { + when(zoneMasterServiceImpl.updateZoneStatus(any())).thenReturn(1); + assertSuccessContaining(controller.deleteZone("{\"zoneID\":61,\"deleted\":true}"), + "status updated successfully"); + + when(zoneMasterServiceImpl.updateZoneStatus(any())).thenReturn(0); + assertSuccessContaining(controller.deleteZone("{\"zoneID\":61,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteZone should answer an error envelope when the change fails") + void deleteZone_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(zoneMasterServiceImpl.updateZoneStatus(any())).thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.deleteZone("{\"zoneID\":61,\"deleted\":true}")); + } + + @Test + @DisplayName("updateZoneData should copy the edits onto the stored zone") + void updateZoneData_shouldCopyEdits() throws Exception { + M_Zone stored = zone(ZONE_ID, "old name"); + when(zoneMasterServiceImpl.getzoneByID(ZONE_ID)).thenReturn(stored); + when(zoneMasterServiceImpl.updateZoneData(stored)).thenReturn(stored); + + String response = controller.updateZoneData("{\"zoneID\":61,\"zoneName\":\"North zone\"," + + "\"zoneDesc\":\"Northern districts\",\"zoneHQAddress\":\"Main Road\",\"stateID\":29," + + "\"districtID\":301,\"districtBlockID\":401,\"districtBranchID\":501," + + "\"modifiedBy\":\"admin\"}"); + + assertSuccessContaining(response, "North zone"); + assertEquals("Northern districts", stored.getZoneDesc()); + assertEquals(29, stored.getStateID()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("updateZoneData should answer an error envelope for a zone that does not exist") + void updateZoneData_shouldAnswerErrorEnvelopeForUnknownZone() throws Exception { + when(zoneMasterServiceImpl.getzoneByID(ZONE_ID)).thenReturn(null); + + assertCodeException(controller.updateZoneData("{\"zoneID\":61}")); + } + + @Test + @DisplayName("getMappedDistrictByZoneID should answer the districts mapped to the zone") + void getMappedDistrictByZoneID_shouldAnswerMappedDistricts() { + when(zoneMasterServiceImpl.editZoneDistrictMapping1(ZONE_ID)) + .thenReturn(new ArrayList<>(List.of(districtMapping(9001)))); + + assertSuccessContaining(controller.getMappedDistrictByZoneID("{\"zoneID\":61}"), "9001"); + } + + @Test + @DisplayName("getMappedDistrictByZoneID should answer an error envelope when the lookup fails") + void getMappedDistrictByZoneID_shouldAnswerErrorEnvelopeOnFailure() { + when(zoneMasterServiceImpl.editZoneDistrictMapping1(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure(controller.getMappedDistrictByZoneID("{\"zoneID\":61}")); + } + + @Test + @DisplayName("deleteZoneDistrictMapping should report whether the mapping changed") + void deleteZoneDistrictMapping_shouldReportOutcome() throws Exception { + when(zoneMasterServiceImpl.updateZoneDistrictMappingStatus(any())).thenReturn(0); + assertSuccessContaining( + controller.deleteZoneDistrictMapping("{\"zoneDistrictMapID\":9001,\"deleted\":true}"), + "status updated successfully"); + + when(zoneMasterServiceImpl.updateZoneDistrictMappingStatus(any())).thenReturn(1); + assertSuccessContaining( + controller.deleteZoneDistrictMapping("{\"zoneDistrictMapID\":9001,\"deleted\":true}"), + "Failed to update the status"); + } + + @Test + @DisplayName("deleteZoneDistrictMapping should answer an error envelope when the change fails") + void deleteZoneDistrictMapping_shouldAnswerErrorEnvelopeOnFailure() throws Exception { + when(zoneMasterServiceImpl.updateZoneDistrictMappingStatus(any())) + .thenThrow(new IllegalStateException("no connection")); + + assertGenericFailure( + controller.deleteZoneDistrictMapping("{\"zoneDistrictMapID\":9001,\"deleted\":true}")); + } +} diff --git a/src/test/java/com/iemr/admin/data/BeanContract.java b/src/test/java/com/iemr/admin/data/BeanContract.java new file mode 100644 index 0000000..379b493 --- /dev/null +++ b/src/test/java/com/iemr/admin/data/BeanContract.java @@ -0,0 +1,245 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.data; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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.assertTrue; + +/** + * Reflective checks for the accessors, equality and string representation of the + * plain data carriers this service exchanges over its APIs. + * + *

The carriers are mostly Lombok {@code @Data} entities, so the generated + * {@code equals}, {@code hashCode} and {@code toString} are exercised alongside + * every readable/writable property. A carrier that cannot be built through a + * no-argument constructor is reported by {@link #isVerifiable(Class)} so the + * calling suite can skip it rather than fail. + */ +public final class BeanContract { + + private BeanContract() { + } + + /** Answers whether the type can be exercised through the reflective contract. */ + public static boolean isVerifiable(Class type) { + if (type.isInterface() || type.isEnum() || type.isAnnotation() + || Modifier.isAbstract(type.getModifiers()) + || type.isMemberClass() && !Modifier.isStatic(type.getModifiers())) { + return false; + } + try { + type.getDeclaredConstructor(); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + public static void verify(Class type) throws Exception { + Object left = newInstance(type); + Object right = newInstance(type); + copyFields(type, left, right); + + assertNotNull(left.toString(), type.getSimpleName() + " must render a string form"); + assertTrue(left.equals(left), type.getSimpleName() + " must equal itself"); + assertFalse(left.equals(null), type.getSimpleName() + " must never equal null"); + assertFalse(left.equals(new Object()), type.getSimpleName() + " must never equal an unrelated type"); + left.hashCode(); + + if (left.equals(right)) { + assertEquals(left.hashCode(), right.hashCode(), + type.getSimpleName() + " must hash consistently with equals"); + } + + verifyProperties(type, left, right); + } + + private static Object newInstance(Class type) throws Exception { + java.lang.reflect.Constructor constructor = type.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } + + private static void verifyProperties(Class type, Object left, Object right) throws Exception { + for (Field field : type.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) { + continue; + } + Method getter = findGetter(type, field); + Method setter = findSetter(type, field); + if (getter == null || setter == null) { + continue; + } + Object value = sampleValue(field.getType()); + if (value == null) { + continue; + } + + setter.invoke(left, value); + assertEquals(value, getter.invoke(left), + type.getSimpleName() + "." + field.getName() + " must round-trip through its accessors"); + + setter.invoke(right, value); + assertEquals(getter.invoke(left), getter.invoke(right), + type.getSimpleName() + "." + field.getName() + " must read back the same on both instances"); + } + assertNotNull(left.toString(), type.getSimpleName() + " must render a populated string form"); + left.hashCode(); + left.equals(right); + } + + private static void copyFields(Class type, Object from, Object to) throws Exception { + for (Field field : type.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) { + continue; + } + field.setAccessible(true); + field.set(to, field.get(from)); + } + } + + private static Method findGetter(Class type, Field field) { + String suffix = capitalise(field.getName()); + for (String prefix : new String[] { "get", "is" }) { + try { + Method candidate = type.getMethod(prefix + suffix); + if (candidate.getParameterCount() == 0) { + return candidate; + } + } catch (NoSuchMethodException ignored) { + // try the next accessor style + } + } + return null; + } + + private static Method findSetter(Class type, Field field) { + try { + return type.getMethod("set" + capitalise(field.getName()), field.getType()); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static String capitalise(String name) { + return Character.toUpperCase(name.charAt(0)) + name.substring(1); + } + + private static Object sampleValue(Class type) { + if (type == String.class) { + return "sample"; + } + if (type == Long.class || type == long.class) { + return 7L; + } + if (type == Integer.class || type == int.class) { + return 7; + } + if (type == Short.class || type == short.class) { + return (short) 7; + } + if (type == Double.class || type == double.class) { + return 7.5d; + } + if (type == Float.class || type == float.class) { + return 7.5f; + } + if (type == Character.class || type == char.class) { + return 'y'; + } + if (type == Byte.class || type == byte.class) { + return (byte) 7; + } + if (type == Boolean.class || type == boolean.class) { + return Boolean.TRUE; + } + if (type == java.math.BigDecimal.class) { + return java.math.BigDecimal.valueOf(7.5d); + } + if (type == BigInteger.class) { + return BigInteger.valueOf(7L); + } + if (type == Timestamp.class) { + return Timestamp.valueOf("2026-02-17 09:30:00"); + } + if (type == Date.class) { + return Date.valueOf("2026-02-17"); + } + if (type == Time.class) { + return Time.valueOf("09:30:00"); + } + if (type == java.util.Date.class) { + return new java.util.Date(1_771_286_400_000L); + } + if (type == LocalDate.class) { + return LocalDate.of(2026, 2, 17); + } + if (type == LocalTime.class) { + return LocalTime.of(9, 30); + } + if (type == LocalDateTime.class) { + return LocalDateTime.of(2026, 2, 17, 9, 30); + } + if (type == List.class) { + return new ArrayList<>(List.of("first", "second")); + } + if (type == Set.class) { + return new HashSet<>(Set.of("first")); + } + if (type == Map.class) { + return new HashMap<>(Map.of("key", "value")); + } + if (type == Object.class) { + return "sample"; + } + if (type.isPrimitive() || type.isEnum() || type.isArray() || type.isInterface()) { + return null; + } + try { + java.lang.reflect.Constructor constructor = type.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (ReflectiveOperationException | SecurityException e) { + // A carrier that needs arguments is simply left at its default value. + return null; + } + } +} diff --git a/src/test/java/com/iemr/admin/data/ClassScanner.java b/src/test/java/com/iemr/admin/data/ClassScanner.java new file mode 100644 index 0000000..7eb63b0 --- /dev/null +++ b/src/test/java/com/iemr/admin/data/ClassScanner.java @@ -0,0 +1,64 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.data; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Pattern; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.RegexPatternTypeFilter; + +/** Finds every concrete class published under a package, for suite-wide contract checks. */ +public final class ClassScanner { + + private ClassScanner() { + } + + public static List> classesUnder(String... packages) { + ClassPathScanningCandidateComponentProvider provider = + new ClassPathScanningCandidateComponentProvider(false) { + @Override + protected boolean isCandidateComponent( + org.springframework.beans.factory.annotation.AnnotatedBeanDefinition definition) { + return definition.getMetadata().isIndependent() + && !definition.getMetadata().isAnnotation(); + } + }; + provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*"))); + + List> found = new ArrayList<>(); + for (String packageName : packages) { + for (BeanDefinition definition : provider.findCandidateComponents(packageName)) { + try { + found.add(Class.forName(definition.getBeanClassName())); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + // A class the test classpath cannot resolve is not part of the contract. + } + } + } + found.sort(Comparator.comparing(Class::getName)); + return found; + } +} diff --git a/src/test/java/com/iemr/admin/data/DataCarrierContractTest.java b/src/test/java/com/iemr/admin/data/DataCarrierContractTest.java new file mode 100644 index 0000000..1fe9016 --- /dev/null +++ b/src/test/java/com/iemr/admin/data/DataCarrierContractTest.java @@ -0,0 +1,71 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.data; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Exercises every data carrier the service exchanges - the JPA entities, the + * transfer objects and the small model holders - against the accessor, equality + * and string contract their callers rely on. + */ +@DisplayName("Data carrier contract Test Suite") +class DataCarrierContractTest { + + private static final String[] CARRIER_PACKAGES = { + "com.iemr.admin.data", + "com.iemr.admin.to", + "com.iemr.admin.model" }; + + static List> carriers() { + return ClassScanner.classesUnder(CARRIER_PACKAGES).stream() + .filter(type -> !type.getName().endsWith("Test")) + .filter(type -> !type.getSimpleName().equals("BeanContract")) + .filter(type -> !type.getSimpleName().equals("ClassScanner")) + .filter(BeanContract::isVerifiable) + .toList(); + } + + @Test + @DisplayName("the scan should discover the carriers rather than silently pass on an empty set") + void carrierScan_shouldDiscoverCarriers() { + List> carriers = carriers(); + assertFalse(carriers.isEmpty(), "no data carriers were discovered on the test classpath"); + assertTrue(carriers.size() > 100, + "expected the full carrier set, found only " + carriers.size()); + } + + @ParameterizedTest(name = "{0} honours the accessor, equality and string contract") + @DisplayName("every data carrier should honour its accessor, equality and string contract") + @MethodSource("carriers") + void dataCarrier_shouldHonourBeanContract(Class type) throws Exception { + BeanContract.verify(type); + } +} diff --git a/src/test/java/com/iemr/admin/mapper/emailconfig/InstituteEmailConfigMapperTest.java b/src/test/java/com/iemr/admin/mapper/emailconfig/InstituteEmailConfigMapperTest.java new file mode 100644 index 0000000..a80f1de --- /dev/null +++ b/src/test/java/com/iemr/admin/mapper/emailconfig/InstituteEmailConfigMapperTest.java @@ -0,0 +1,260 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.mapper.emailconfig; + +import java.sql.Timestamp; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.data.emailconfig.AuthorityEmail; +import com.iemr.admin.model.emailconfig.AuthEmailRequest; +import com.iemr.admin.model.emailconfig.AuthEmailResponse; +import com.iemr.admin.model.emailconfig.CreateAuthEmailRequestModel; +import com.iemr.admin.model.emailconfig.CreateNodalEmailRequestModel; +import com.iemr.admin.model.emailconfig.NodalEmailResponse; +import com.iemr.admin.model.emailconfig.UpdateAuthEmailRequest; +import com.iemr.admin.model.emailconfig.UpdateNodalEmailRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The escalation emails a nodal officer receives are addressed from these + * records, so the mapper must carry every contact field across rather than drop + * one silently. + */ +@DisplayName("InstituteEmailConfigMapper Test Suite") +class InstituteEmailConfigMapperTest { + + private final InstituteEmailConfigMapper mapper = InstituteEmailConfigMapper.INSTANCE; + + private static AuthorityEmail storedRecord() { + AuthorityEmail stored = new AuthorityEmail(); + stored.setAuthorityEmailID(9001); + stored.setStateID(29); + stored.setDistrictID(301); + stored.setBlockID(401); + stored.setDistrictBranchMappingID(501); + stored.setDesignationID(7); + stored.setAuthorityName("Dr Asha Rao"); + stored.setEmailID("asha.rao@example.org"); + stored.setContactNo("9000000001"); + stored.setProviderServiceMapID(4001); + stored.setCreatedBy("admin"); + stored.setCreatedDate(Timestamp.valueOf("2026-02-17 09:30:00")); + stored.setModifiedBy("admin"); + stored.setDeleted(Boolean.FALSE); + return stored; + } + + private static CreateAuthEmailRequestModel createAuthorityRequest() { + CreateAuthEmailRequestModel request = new CreateAuthEmailRequestModel(); + request.setStateID(29); + request.setDistrictID(301); + request.setBlockID(401); + request.setDistrictBranchMappingID(501); + request.setDesignationID(7); + request.setAuthorityName("Dr Asha Rao"); + request.setEmailID("asha.rao@example.org"); + request.setContactNo("9000000001"); + request.setProviderServiceMapID(4001); + request.setCreatedBy("admin"); + return request; + } + + private static CreateNodalEmailRequestModel createNodalRequest() { + CreateNodalEmailRequestModel request = new CreateNodalEmailRequestModel(); + request.setStateID(29); + request.setDistrictID(301); + request.setDesignationID(7); + request.setAuthorityName("Dr Asha Rao"); + request.setEmailID("asha.rao@example.org"); + request.setContactNo("9000000001"); + request.setMobileNo("9000000002"); + request.setProviderServiceMapID(4001); + request.setCreatedBy("admin"); + return request; + } + + @Test + @DisplayName("requestToInstituteEmailConf should carry the search request onto the record") + void requestToInstituteEmailConf_shouldCarrySearchRequest() { + AuthEmailRequest request = new AuthEmailRequest(); + request.setAuthorityEmailID(9001); + request.setStateID(29); + request.setDistrictID(301); + request.setBlockID(401); + request.setDistrictBranchMappingID(501); + request.setDesignationID(7); + request.setProviderServiceMapID(4001); + request.setDeleted(Boolean.FALSE); + + AuthorityEmail mapped = mapper.requestToInstituteEmailConf(request); + + assertEquals(9001, mapped.getAuthorityEmailID()); + assertEquals(29, mapped.getStateID()); + assertEquals(501, mapped.getDistrictBranchMappingID()); + assertEquals(4001, mapped.getProviderServiceMapID()); + } + + @Test + @DisplayName("requestToInstituteEmailConf should map a whole batch of search requests") + void requestToInstituteEmailConf_shouldMapBatch() { + AuthEmailRequest request = new AuthEmailRequest(); + request.setStateID(29); + + List mapped = mapper.requestToInstituteEmailConf(List.of(request)); + + assertEquals(1, mapped.size()); + assertEquals(29, mapped.get(0).getStateID()); + } + + @Test + @DisplayName("createRequestToInstituteEmailConf should carry every contact field onto the new record") + void createRequestToInstituteEmailConf_shouldCarryContactFields() { + AuthorityEmail mapped = mapper.createRequestToInstituteEmailConf(createAuthorityRequest()); + + assertEquals("Dr Asha Rao", mapped.getAuthorityName()); + assertEquals("asha.rao@example.org", mapped.getEmailID()); + assertEquals("9000000001", mapped.getContactNo()); + assertEquals("admin", mapped.getCreatedBy()); + } + + @Test + @DisplayName("createRequestToInstituteEmailConfig should carry the nodal officer's mobile number too") + void createRequestToInstituteEmailConfig_shouldCarryMobileNumber() { + AuthorityEmail mapped = mapper.createRequestToInstituteEmailConfig(createNodalRequest()); + + assertEquals("Dr Asha Rao", mapped.getAuthorityName()); + assertEquals("asha.rao@example.org", mapped.getEmailID()); + } + + @Test + @DisplayName("the create mappers should each map a whole batch") + void createMappers_shouldMapBatches() { + assertEquals(1, mapper.createRequestToInstituteEmailConf(List.of(createAuthorityRequest())).size()); + assertEquals(1, mapper.createRequestToInstituteEmailConfig(List.of(createNodalRequest())).size()); + } + + @Test + @DisplayName("updateRequestToInstituteEmailConf should carry the edited fields onto the record") + void updateRequestToInstituteEmailConf_shouldCarryEditedFields() { + UpdateAuthEmailRequest request = new UpdateAuthEmailRequest(); + request.setAuthorityEmailID(9001); + request.setAuthorityName("Dr Ravi Kumar"); + request.setEmailID("ravi.kumar@example.org"); + request.setContactNo("9000000003"); + request.setModifiedBy("admin"); + request.setDeleted(Boolean.FALSE); + + AuthorityEmail mapped = mapper.updateRequestToInstituteEmailConf(request); + + assertEquals(9001, mapped.getAuthorityEmailID()); + assertEquals("Dr Ravi Kumar", mapped.getAuthorityName()); + assertEquals("admin", mapped.getModifiedBy()); + } + + @Test + @DisplayName("updateRequestToInstituteNodalEmailConf should carry the edited nodal fields onto the record") + void updateRequestToInstituteNodalEmailConf_shouldCarryEditedFields() { + UpdateNodalEmailRequest request = new UpdateNodalEmailRequest(); + request.setAuthorityEmailID(9001); + request.setAuthorityName("Dr Ravi Kumar"); + request.setMobileNo("9000000004"); + request.setModifiedBy("admin"); + + AuthorityEmail mapped = mapper.updateRequestToInstituteNodalEmailConf(request); + + assertEquals(9001, mapped.getAuthorityEmailID()); + assertEquals("Dr Ravi Kumar", mapped.getAuthorityName()); + } + + @Test + @DisplayName("updateRequestToInstituteEmailConf should map a whole batch of edits") + void updateRequestToInstituteEmailConf_shouldMapBatch() { + UpdateAuthEmailRequest request = new UpdateAuthEmailRequest(); + request.setAuthorityEmailID(9001); + + assertEquals(1, mapper.updateRequestToInstituteEmailConf(List.of(request)).size()); + } + + @Test + @DisplayName("resultToInstTypeEmailResponse should publish the stored record back to the caller") + void resultToInstTypeEmailResponse_shouldPublishStoredRecord() { + AuthEmailResponse published = mapper.resultToInstTypeEmailResponse(storedRecord()); + + assertEquals(9001, published.getAuthorityEmailID()); + assertEquals("Dr Asha Rao", published.getAuthorityName()); + assertEquals("asha.rao@example.org", published.getEmailID()); + assertEquals(Timestamp.valueOf("2026-02-17 09:30:00"), published.getCreatedDate()); + } + + @Test + @DisplayName("the nodal response mappers should publish the stored record back to the caller") + void nodalResponseMappers_shouldPublishStoredRecord() { + NodalEmailResponse published = mapper.resultToInstTypeNodalEmailResponse(storedRecord()); + NodalEmailResponse alsoPublished = mapper.resultInstType(storedRecord()); + + assertEquals("Dr Asha Rao", published.getAuthorityName()); + assertEquals("Dr Asha Rao", alsoPublished.getAuthorityName()); + assertEquals(4001, published.getProviderServiceMapID()); + } + + @Test + @DisplayName("the response mappers should publish a whole batch of stored records") + void responseMappers_shouldPublishBatches() { + List stored = List.of(storedRecord(), storedRecord()); + + assertEquals(2, mapper.resultToInstTypeEmailResponse(stored).size()); + assertEquals(2, mapper.resultToInstTypeEmailResponses(stored).size()); + } + + @Test + @DisplayName("the mappers should answer nothing rather than an empty record for a missing input") + void mappers_shouldAnswerNothingForMissingInput() { + assertNull(mapper.requestToInstituteEmailConf((AuthEmailRequest) null)); + assertNull(mapper.requestToInstituteEmailConf((List) null)); + assertNull(mapper.createRequestToInstituteEmailConf((CreateAuthEmailRequestModel) null)); + assertNull(mapper.createRequestToInstituteEmailConf((List) null)); + assertNull(mapper.createRequestToInstituteEmailConfig((CreateNodalEmailRequestModel) null)); + assertNull(mapper.createRequestToInstituteEmailConfig((List) null)); + assertNull(mapper.updateRequestToInstituteEmailConf((UpdateAuthEmailRequest) null)); + assertNull(mapper.updateRequestToInstituteEmailConf((List) null)); + assertNull(mapper.updateRequestToInstituteNodalEmailConf(null)); + assertNull(mapper.resultToInstTypeEmailResponse((AuthorityEmail) null)); + assertNull(mapper.resultToInstTypeEmailResponse((List) null)); + assertNull(mapper.resultToInstTypeNodalEmailResponse(null)); + assertNull(mapper.resultInstType(null)); + assertNull(mapper.resultToInstTypeEmailResponses(null)); + } + + @Test + @DisplayName("the batch mappers should answer an empty batch for an empty input") + void batchMappers_shouldAnswerEmptyBatchForEmptyInput() { + assertTrue(mapper.requestToInstituteEmailConf(List.of()).isEmpty()); + assertTrue(mapper.resultToInstTypeEmailResponse(List.of()).isEmpty()); + assertTrue(mapper.resultToInstTypeEmailResponses(List.of()).isEmpty()); + } +} diff --git a/src/test/java/com/iemr/admin/mapper/parkingplacetalukmapping/ParkingPlaceTalukMappingMapperTest.java b/src/test/java/com/iemr/admin/mapper/parkingplacetalukmapping/ParkingPlaceTalukMappingMapperTest.java new file mode 100644 index 0000000..6b63657 --- /dev/null +++ b/src/test/java/com/iemr/admin/mapper/parkingplacetalukmapping/ParkingPlaceTalukMappingMapperTest.java @@ -0,0 +1,134 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.mapper.parkingplacetalukmapping; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.data.locationmaster.DistrictBlock; +import com.iemr.admin.data.locationmaster.M_District; +import com.iemr.admin.data.parkingPlace.M_Parkingplace; +import com.iemr.admin.data.parkingPlace.ParkingplaceTalukMapping; +import com.iemr.admin.data.parkingPlace.ParkingplaceTalukMappingTO; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * The mapper flattens a stored taluk mapping and the three records it points at + * into the single carrier the parking place screens read. + */ +@DisplayName("ParkingPlaceTalukMappingMapper Test Suite") +class ParkingPlaceTalukMappingMapperTest { + + private final ParkingPlaceTalukMappingMapper mapper = ParkingPlaceTalukMappingMapper.INSTANCE; + + private static ParkingplaceTalukMapping fullMapping() { + ParkingplaceTalukMapping mapping = new ParkingplaceTalukMapping(); + mapping.setPpSubDistrictMapID(7001); + mapping.setParkingPlaceID(31); + mapping.setDistrictID(301); + mapping.setDistrictBlockID(3011); + mapping.setProviderServiceMapID(4001); + mapping.setDeleted(Boolean.FALSE); + mapping.setProcessed("N"); + mapping.setCreatedBy("admin"); + mapping.setModifiedBy("supervisor"); + + M_Parkingplace parkingplace = new M_Parkingplace(); + parkingplace.setParkingPlaceName("Hosur parking"); + parkingplace.setDeleted(Boolean.FALSE); + mapping.setParkingplace(parkingplace); + + M_District district = new M_District(); + district.setDistrictName("Bengaluru Urban"); + district.setDeleted(Boolean.FALSE); + mapping.setM_district(district); + + DistrictBlock block = new DistrictBlock(); + block.setBlockName("Anekal"); + block.setDeleted(Boolean.FALSE); + mapping.setDistrictBlock(block); + return mapping; + } + + @Test + @DisplayName("should carry the names of the parking place, district and taluk onto one carrier") + void shouldFlattenNamesOntoOneCarrier() { + ParkingplaceTalukMappingTO published = mapper.getParkingplaceTalukMappingMap(fullMapping()); + + assertEquals(7001, published.getPpSubDistrictMapID()); + assertEquals("Hosur parking", published.getParkingPlaceName()); + assertEquals("Bengaluru Urban", published.getDistrictName()); + assertEquals("Anekal", published.getDistrictBlockName()); + assertEquals(4001, published.getProviderServiceMapID()); + assertEquals("admin", published.getCreatedBy()); + assertEquals("supervisor", published.getModifiedBy()); + assertEquals("N", published.getProcessed()); + assertEquals(Boolean.FALSE, published.getDeleted()); + assertEquals(Boolean.FALSE, published.getParkingPlaceDeleted()); + assertEquals(Boolean.FALSE, published.getDistrictDeleted()); + assertEquals(Boolean.FALSE, published.getDistrictBlockDeleted()); + } + + @Test + @DisplayName("should leave the names empty when the mapping points at nothing") + void shouldLeaveNamesEmptyWhenNothingPointedAt() { + ParkingplaceTalukMapping mapping = new ParkingplaceTalukMapping(); + mapping.setPpSubDistrictMapID(7002); + + ParkingplaceTalukMappingTO published = mapper.getParkingplaceTalukMappingMap(mapping); + + assertEquals(7002, published.getPpSubDistrictMapID()); + assertNull(published.getParkingPlaceName()); + assertNull(published.getDistrictName()); + assertNull(published.getDistrictBlockName()); + assertNull(published.getParkingPlaceDeleted()); + } + + @Test + @DisplayName("should answer nothing for a mapping that is not there at all") + void shouldAnswerNothingForAbsentMapping() { + assertNull(mapper.getParkingplaceTalukMappingMap(null)); + assertNull(mapper.getParkingplaceTalukMappingMapList(null)); + } + + @Test + @DisplayName("should publish one carrier per mapping in the list") + void shouldPublishOneCarrierPerMapping() { + List published = mapper + .getParkingplaceTalukMappingMapList(List.of(fullMapping(), fullMapping())); + + assertEquals(2, published.size()); + assertEquals("Anekal", published.get(0).getDistrictBlockName()); + } + + @Test + @DisplayName("should publish an empty list when there is no mapping to publish") + void shouldPublishEmptyListForNoMappings() { + assertTrue(mapper.getParkingplaceTalukMappingMapList(new ArrayList<>()).isEmpty()); + } +} diff --git a/src/test/java/com/iemr/admin/service/apiman/ApimanServiceImplTest.java b/src/test/java/com/iemr/admin/service/apiman/ApimanServiceImplTest.java new file mode 100644 index 0000000..98e4d15 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/apiman/ApimanServiceImplTest.java @@ -0,0 +1,261 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.apiman; + +import java.util.HashMap; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.admin.data.apiman.ApimanClient; +import com.iemr.admin.data.apiman.ApimanRegister; +import com.iemr.admin.utils.http.HttpUtils; + +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.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The apiman service registers a new service line as a client of the API + * gateway and signs it up to the API contracts that service line needs. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ApimanServiceImpl Test Suite") +class ApimanServiceImplTest { + + private static final String BASE_URL = "https://gateway.example.org"; + private static final String CLIENT_ID = "client-104"; + + @Mock + private HttpUtils httpUtils; + + private HttpUtils originalHttpUtils; + + private ApimanServiceImpl service; + + @BeforeEach + @DisplayName("Stand in for the gateway and point the service at stub addresses") + void setUp() { + originalHttpUtils = (HttpUtils) ReflectionTestUtils.getField(ApimanServiceImpl.class, "httpUtils"); + ReflectionTestUtils.setField(ApimanServiceImpl.class, "httpUtils", httpUtils); + service = new ApimanServiceImpl(); + ReflectionTestUtils.setField(service, "apimanBaseURL", BASE_URL); + ReflectionTestUtils.setField(service, "clientURL", "APIMAN_URL/clients"); + ReflectionTestUtils.setField(service, "contractURL", "APIMAN_URL/clients/CLIENT_ID/contracts"); + ReflectionTestUtils.setField(service, "registerURL", "APIMAN_URL/register"); + ReflectionTestUtils.setField(service, "getClientKey", "APIMAN_URL/clients/CLIENT_ID/apikey"); + ReflectionTestUtils.setField(service, "auth", "Bearer gateway-token"); + ReflectionTestUtils.setField(service, "apimanplanID", "plan"); + ReflectionTestUtils.setField(service, "apimanorgID", "org"); + ReflectionTestUtils.setField(service, "apimanCommonApiID", "common"); + ReflectionTestUtils.setField(service, "apiman1097apiID", "api1097"); + ReflectionTestUtils.setField(service, "apimanMMUapiID", "mmu"); + ReflectionTestUtils.setField(service, "apimanInventoryapiID", "inventory"); + ReflectionTestUtils.setField(service, "apiman104apiID", "api104"); + ReflectionTestUtils.setField(service, "apimanTMapiID", "tm"); + ReflectionTestUtils.setField(service, "apimanSchedulingapiID", "scheduling"); + ReflectionTestUtils.setField(service, "apimanMCTSapiID", "mcts"); + } + + @AfterEach + @DisplayName("Put the real gateway client back so no other suite sees the stand-in") + void tearDown() { + ReflectionTestUtils.setField(ApimanServiceImpl.class, "httpUtils", originalHttpUtils); + } + + private static ApimanClient client() { + ApimanClient client = new ApimanClient(); + client.setId(CLIENT_ID); + client.setName("104 Helpline"); + client.setInitialVersion("1.0"); + return client; + } + + @Test + @DisplayName("createClient should answer the client the gateway registered") + void createClient_shouldAnswerRegisteredClient() throws Exception { + when(httpUtils.post(anyString(), anyString(), any())) + .thenReturn("{\"id\":\"client-104\",\"name\":\"104 Helpline\"}"); + + ApimanClient registered = service.createClient(client()); + + assertEquals(CLIENT_ID, registered.getId()); + assertEquals("104 Helpline", registered.getName()); + } + + @Test + @DisplayName("createClient should send the client to the gateway with the configured credentials") + void createClient_shouldSendWithConfiguredCredentials() throws Exception { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{\"id\":\"client-104\"}"); + ArgumentCaptor url = ArgumentCaptor.forClass(String.class); + ArgumentCaptor> header = ArgumentCaptor.forClass(HashMap.class); + + service.createClient(client()); + + verify(httpUtils).post(url.capture(), anyString(), header.capture()); + assertEquals(BASE_URL + "/clients", url.getValue()); + assertEquals("Bearer gateway-token", header.getValue().get("Authorization")); + assertEquals("application/json", header.getValue().get("Content-Type")); + } + + @Test + @DisplayName("createClient should give up when the gateway answers something that is not a client") + void createClient_shouldGiveUpOnUnreadableReply() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("null"); + + assertThrows(RuntimeException.class, () -> service.createClient(client())); + } + + @Test + @DisplayName("createClientContract should sign a helpline service line up to its own API as well as the shared one") + void createClientContract_shouldSignUpHelplineApis() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{}"); + + service.createClientContract(1, CLIENT_ID); + + ArgumentCaptor body = ArgumentCaptor.forClass(String.class); + verify(httpUtils, times(3)).post(anyString(), body.capture(), any()); + assertTrue(body.getAllValues().stream().anyMatch(sent -> sent.contains("api1097")), body.getAllValues() + .toString()); + assertTrue(body.getAllValues().stream().filter(sent -> sent.contains("common")).count() == 2, + "both versions of the shared API must be contracted"); + } + + @Test + @DisplayName("createClientContract should sign a mobile unit service line up to its stock APIs too") + void createClientContract_shouldSignUpMobileUnitApis() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{}"); + + service.createClientContract(2, CLIENT_ID); + + ArgumentCaptor body = ArgumentCaptor.forClass(String.class); + verify(httpUtils, times(4)).post(anyString(), body.capture(), any()); + List sent = body.getAllValues(); + assertTrue(sent.stream().anyMatch(one -> one.contains("mmu")), sent.toString()); + assertTrue(sent.stream().anyMatch(one -> one.contains("inventory")), sent.toString()); + } + + @Test + @DisplayName("createClientContract should sign a telemedicine service line up to its scheduling API") + void createClientContract_shouldSignUpTelemedicineApis() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{}"); + + service.createClientContract(4, CLIENT_ID); + + ArgumentCaptor body = ArgumentCaptor.forClass(String.class); + verify(httpUtils, times(4)).post(anyString(), body.capture(), any()); + assertTrue(body.getAllValues().stream().anyMatch(one -> one.contains("scheduling")), + body.getAllValues().toString()); + } + + @Test + @DisplayName("createClientContract should contract only the shared API for a service line with no API of its own") + void createClientContract_shouldContractOnlySharedApi() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{}"); + + service.createClientContract(5, CLIENT_ID); + + verify(httpUtils, times(2)).post(anyString(), anyString(), any()); + } + + @Test + @DisplayName("createClientContract should contract only the shared API for a service line it does not recognise") + void createClientContract_shouldContractOnlySharedApiForUnknownServiceLine() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{}"); + + service.createClientContract(99, CLIENT_ID); + + verify(httpUtils, times(2)).post(anyString(), anyString(), any()); + } + + @Test + @DisplayName("createClientContract should address the client whose contracts are being signed") + void createClientContract_shouldAddressTheClient() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{}"); + ArgumentCaptor url = ArgumentCaptor.forClass(String.class); + + service.createClientContract(3, CLIENT_ID); + + verify(httpUtils, times(3)).post(url.capture(), anyString(), any()); + assertEquals(BASE_URL + "/clients/" + CLIENT_ID + "/contracts", url.getValue()); + } + + @Test + @DisplayName("registerClient should publish the registration to the gateway") + void registerClient_shouldPublishRegistration() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{}"); + ApimanRegister registration = new ApimanRegister(); + registration.setType("client"); + registration.setEntityId(CLIENT_ID); + ArgumentCaptor url = ArgumentCaptor.forClass(String.class); + + assertNull(service.registerClient(registration), "registration answers nothing back to the caller"); + verify(httpUtils).post(url.capture(), anyString(), any()); + assertEquals(BASE_URL + "/register", url.getValue()); + } + + @Test + @DisplayName("getClientKey should answer the API key the gateway issued") + void getClientKey_shouldAnswerIssuedKey() { + when(httpUtils.get(anyString(), any())).thenReturn("{\"apiKey\":\"key-abc-123\"}"); + + assertEquals("key-abc-123", service.getClientKey(CLIENT_ID)); + } + + @Test + @DisplayName("getClientKey should give up when the gateway does not answer a key") + void getClientKey_shouldGiveUpWithoutKey() { + when(httpUtils.get(anyString(), any())).thenReturn("{\"result\":\"unknown client\"}"); + + assertThrows(RuntimeException.class, () -> service.getClientKey(CLIENT_ID)); + } + + @Test + @DisplayName("the gateway should still be reached when no credentials are configured") + void gatewayCalls_shouldStillBeReachedWithoutCredentials() throws Exception { + ReflectionTestUtils.setField(service, "auth", null); + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("{\"id\":\"client-104\"}"); + ArgumentCaptor> header = ArgumentCaptor.forClass(HashMap.class); + + service.createClient(client()); + + verify(httpUtils).post(anyString(), anyString(), header.capture()); + assertNull(header.getValue().get("Authorization"), "no credentials must be sent when none are configured"); + } +} diff --git a/src/test/java/com/iemr/admin/service/blocking/BlockingServiceTest.java b/src/test/java/com/iemr/admin/service/blocking/BlockingServiceTest.java new file mode 100644 index 0000000..39711d1 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/blocking/BlockingServiceTest.java @@ -0,0 +1,309 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.blocking; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.blocking.M_Providerservicemapping_Blocking; +import com.iemr.admin.data.blocking.M_Serviceprovider_Blocking; +import com.iemr.admin.data.blocking.M_Status1; +import com.iemr.admin.data.blocking.T_Providerservicemappingdetail; +import com.iemr.admin.data.blocking.T_Serviceproviderdetail; +import com.iemr.admin.data.blocking.T_Userdetail; +import com.iemr.admin.data.blocking.UserForBlocking; +import com.iemr.admin.data.blocking.V_Showproviderservicemapping; +import com.iemr.admin.repo.blocking.MProviderservicemappingBlockingRepo; +import com.iemr.admin.repo.blocking.MServiceproviderBlockingRepo; +import com.iemr.admin.repo.blocking.MStatusRepo; +import com.iemr.admin.repo.blocking.T_ProviderservicemappingdetailRepo; +import com.iemr.admin.repo.blocking.T_ServiceproviderdetailRepo; +import com.iemr.admin.repo.blocking.T_UserDetailRepo; +import com.iemr.admin.repo.blocking.UserBlockingRepo; +import com.iemr.admin.repo.blocking.V_ShowproviderservicemappingRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +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.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The blocking service turns each request to suspend a provider into the right + * repository update, and reports how far a CTI campaign mapping got. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("Blocking_Service Test Suite") +class BlockingServiceTest { + + private static final Integer PROVIDER_ID = 77; + private static final Integer SERVICE_ID = 3; + private static final Integer STATE_ID = 29; + + @Mock + private V_ShowproviderservicemappingRepo v_ShowproviderservicemappingRepo; + + @Mock + private T_UserDetailRepo t_UserDetailRepo; + + @Mock + private MStatusRepo mStatusRepo; + + @Mock + private UserBlockingRepo userBlockingRepo; + + @Mock + private T_ProviderservicemappingdetailRepo t_ProviderservicemappingdetailRepo; + + @Mock + private MProviderservicemappingBlockingRepo mProviderservicemappingBlockingRepo; + + @Mock + private T_ServiceproviderdetailRepo t_ServiceproviderdetailRepo; + + @Mock + private MServiceproviderBlockingRepo mServiceproviderBlockingRepo; + + @InjectMocks + private Blocking_Service service; + + private static M_Providerservicemapping_Blocking mapping(Integer mapId) { + M_Providerservicemapping_Blocking mapping = new M_Providerservicemapping_Blocking(); + mapping.setProviderServiceMapID(mapId); + mapping.setServiceProviderID(PROVIDER_ID); + mapping.setServiceID(SERVICE_ID); + mapping.setStateID(STATE_ID); + mapping.setcTI_CampaignName("104"); + return mapping; + } + + @Test + @DisplayName("getProviderDetailsById should hand back what the repository holds") + void getProviderDetailsById_shouldHandBackRepositoryContents() { + M_Serviceprovider_Blocking stored = new M_Serviceprovider_Blocking(); + when(mServiceproviderBlockingRepo.getProviderDetailsByID(PROVIDER_ID)).thenReturn(stored); + + assertSame(stored, service.getProviderDetailsById(PROVIDER_ID)); + } + + @Test + @DisplayName("blockServiceProvider should answer the provider the repository stored") + void blockServiceProvider_shouldAnswerStoredProvider() { + M_Serviceprovider_Blocking stored = new M_Serviceprovider_Blocking(); + when(mServiceproviderBlockingRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.blockServiceProvider(stored)); + } + + @Test + @DisplayName("saveData should answer the audit row the repository stored") + void saveData_shouldAnswerStoredAuditRow() { + T_Serviceproviderdetail stored = new T_Serviceproviderdetail(); + when(t_ServiceproviderdetailRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.saveData(stored)); + } + + @Test + @DisplayName("the status updates should each reach their own repository query") + void statusUpdates_shouldReachTheirOwnQuery() { + service.blockProviderByService(PROVIDER_ID, STATE_ID, SERVICE_ID, 2); + service.blockProviderByState(PROVIDER_ID, STATE_ID, 2); + service.blockProvider(PROVIDER_ID, 2); + service.blockProviderByProviderIdAndServiceId(PROVIDER_ID, SERVICE_ID, 2); + service.blockUser(3117, 2); + + verify(mProviderservicemappingBlockingRepo).blockProviderByService(PROVIDER_ID, STATE_ID, SERVICE_ID, 2); + verify(mProviderservicemappingBlockingRepo).blockProviderByState(PROVIDER_ID, STATE_ID, 2); + verify(mProviderservicemappingBlockingRepo).blockProvider(PROVIDER_ID, 2); + verify(mProviderservicemappingBlockingRepo) + .blockProviderByProviderIdAndServiceId(PROVIDER_ID, SERVICE_ID, 2); + verify(userBlockingRepo).blockUser(3117, 2); + } + + @Test + @DisplayName("the mapping lookups should each reach their own repository query") + void mappingLookups_shouldReachTheirOwnQuery() { + M_Providerservicemapping_Blocking stored = mapping(4001); + ArrayList storedList = new ArrayList<>(List.of(stored)); + List stateList = List.of(stored); + when(mProviderservicemappingBlockingRepo.getProviderServiceMappingDetails(PROVIDER_ID, STATE_ID, SERVICE_ID)) + .thenReturn(stored); + when(mProviderservicemappingBlockingRepo.getProviderStateMappingDetails(PROVIDER_ID, STATE_ID)) + .thenReturn(stateList); + when(mProviderservicemappingBlockingRepo.getProviderStatus(PROVIDER_ID)).thenReturn(storedList); + when(mProviderservicemappingBlockingRepo.getProviderStatusByProviderAndServiceId(PROVIDER_ID, SERVICE_ID)) + .thenReturn(storedList); + when(mProviderservicemappingBlockingRepo.findByProviderServiceMapID(4001)).thenReturn(stored); + when(mProviderservicemappingBlockingRepo.save(stored)).thenReturn(stored); + when(mProviderservicemappingBlockingRepo.saveAll(anyList())).thenReturn(storedList); + + assertSame(stored, service.getProviderServiceMappingDetails(PROVIDER_ID, STATE_ID, SERVICE_ID)); + assertSame(stateList, service.getProviderStateMappingDetails(PROVIDER_ID, STATE_ID)); + assertSame(storedList, service.getProviderStatus(PROVIDER_ID)); + assertSame(storedList, service.getProviderStatusByProviderAndServiceId(PROVIDER_ID, SERVICE_ID)); + assertSame(stored, service.getDataByProviderServiceMapId(4001)); + assertSame(stored, service.updateProviderData(stored)); + assertSame(storedList, service.AddServiceProvider(new ArrayList<>())); + } + + @Test + @DisplayName("the view lookups should each reach their own repository query") + void viewLookups_shouldReachTheirOwnQuery() { + ArrayList stored = new ArrayList<>(List.of(new V_Showproviderservicemapping())); + when(v_ShowproviderservicemappingRepo.getProviderStatus(PROVIDER_ID)).thenReturn(stored); + when(v_ShowproviderservicemappingRepo.getProviderStatus1(PROVIDER_ID)).thenReturn(stored); + when(v_ShowproviderservicemappingRepo.getProviderServiceMappingDetails1(PROVIDER_ID, STATE_ID, SERVICE_ID)) + .thenReturn(stored); + when(v_ShowproviderservicemappingRepo.getProviderStateMappingDetails(PROVIDER_ID, STATE_ID)) + .thenReturn(stored); + when(v_ShowproviderservicemappingRepo.getProviderStatusByProviderAndServiceId(PROVIDER_ID, SERVICE_ID)) + .thenReturn(stored); + + assertSame(stored, service.getProviderStatus1(PROVIDER_ID)); + assertSame(stored, service.getProviderStatus2(PROVIDER_ID)); + assertSame(stored, service.getProviderServiceMappingDetails2(PROVIDER_ID, STATE_ID, SERVICE_ID)); + assertSame(stored, service.getProviderStateMappingDetails1(PROVIDER_ID, STATE_ID)); + assertSame(stored, service.getProviderStatusByProviderAndServiceId2(PROVIDER_ID, SERVICE_ID)); + } + + @Test + @DisplayName("the audit writes should each reach their own repository") + void auditWrites_shouldReachTheirOwnRepository() { + T_Providerservicemappingdetail detail = new T_Providerservicemappingdetail(); + ArrayList details = new ArrayList<>(List.of(detail)); + T_Userdetail userDetail = new T_Userdetail(); + when(t_ProviderservicemappingdetailRepo.save(detail)).thenReturn(detail); + when(t_ProviderservicemappingdetailRepo.saveAll(anyList())).thenReturn(details); + when(t_UserDetailRepo.save(userDetail)).thenReturn(userDetail); + + assertSame(detail, service.savetpsdData(detail)); + assertSame(details, service.savetpsmd(new ArrayList<>())); + assertSame(userDetail, service.saveUserDetails(userDetail)); + } + + @Test + @DisplayName("getUserDetailByUserId and getStatusData should hand back what the repositories hold") + void userLookups_shouldHandBackRepositoryContents() { + UserForBlocking user = new UserForBlocking(); + ArrayList statuses = new ArrayList<>(List.of(new M_Status1())); + when(userBlockingRepo.getUserDetailByUserId(3117)).thenReturn(user); + when(mStatusRepo.getStatusData()).thenReturn(statuses); + + assertSame(user, service.getUserDetailByUserId(3117)); + assertSame(statuses, service.getStatusData()); + } + + @Test + @DisplayName("getServiceLiensUsingProvider should skip a row the query could not fill") + void getServiceLiensUsingProvider_shouldSkipUnfillableRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 4001, PROVIDER_ID, SERVICE_ID, "Tele Medicine", Boolean.FALSE }); + when(mProviderservicemappingBlockingRepo.getServiceLiensUsingProvider(PROVIDER_ID)).thenReturn(rows); + + assertEquals(1, service.getServiceLiensUsingProvider(PROVIDER_ID).size()); + } + + @Test + @DisplayName("mapctidata should report a clean run when every mapping is written") + void mapctidata_shouldReportCleanRun() { + when(mProviderservicemappingBlockingRepo.createcitmapping(anyInt(), any())).thenReturn(1); + + assertEquals("Mapping Successful", service.mapctidata(List.of(mapping(4001), mapping(4002)))); + } + + @Test + @DisplayName("mapctidata should report how far it got when a mapping is rejected") + void mapctidata_shouldReportHowFarItGot() { + when(mProviderservicemappingBlockingRepo.createcitmapping(4001, "104")).thenReturn(1); + when(mProviderservicemappingBlockingRepo.createcitmapping(4002, "104")).thenReturn(0); + + String status = service.mapctidata(List.of(mapping(4001), mapping(4002))); + + assertTrue(status.startsWith("Mapping Failed"), status); + assertTrue(status.contains("after 1 entries"), status); + } + + @Test + @DisplayName("getServiceLiensUsingProvider1 should narrow by provider, service and state when all are named") + void getServiceLiensUsingProvider1_shouldNarrowByAllThree() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { 4001, PROVIDER_ID, SERVICE_ID, "Tele Medicine", STATE_ID, "Karnataka", "104", + Boolean.FALSE, "N", Boolean.FALSE }); + when(mProviderservicemappingBlockingRepo.getServiceLiensUsingProvider1(PROVIDER_ID, SERVICE_ID, STATE_ID)) + .thenReturn(rows); + + assertEquals(1, service.getServiceLiensUsingProvider1(mapping(4001)).size()); + } + + @Test + @DisplayName("getServiceLiensUsingProvider1 should narrow by provider and service when no state is named") + void getServiceLiensUsingProvider1_shouldNarrowByProviderAndService() { + M_Providerservicemapping_Blocking request = mapping(4001); + request.setStateID(null); + when(mProviderservicemappingBlockingRepo.getServiceLiensUsingProvider1(PROVIDER_ID, SERVICE_ID)) + .thenReturn(new ArrayList<>()); + + service.getServiceLiensUsingProvider1(request); + + verify(mProviderservicemappingBlockingRepo).getServiceLiensUsingProvider1(PROVIDER_ID, SERVICE_ID); + } + + @Test + @DisplayName("getServiceLiensUsingProvider1 should narrow by provider alone when no service is named") + void getServiceLiensUsingProvider1_shouldNarrowByProviderAlone() { + M_Providerservicemapping_Blocking request = mapping(4001); + request.setStateID(null); + request.setServiceID(null); + when(mProviderservicemappingBlockingRepo.getServiceLiensUsingProvider1(PROVIDER_ID)) + .thenReturn(new ArrayList<>()); + + service.getServiceLiensUsingProvider1(request); + + verify(mProviderservicemappingBlockingRepo).getServiceLiensUsingProvider1(PROVIDER_ID); + } + + @Test + @DisplayName("getServiceLiensUsingProvider1 should answer every mapping when the request narrows nothing") + void getServiceLiensUsingProvider1_shouldAnswerEveryMapping() { + M_Providerservicemapping_Blocking request = new M_Providerservicemapping_Blocking(); + when(mProviderservicemappingBlockingRepo.getServiceLiensUsingProvider1()).thenReturn(new ArrayList<>()); + + service.getServiceLiensUsingProvider1(request); + + verify(mProviderservicemappingBlockingRepo).getServiceLiensUsingProvider1(); + } +} diff --git a/src/test/java/com/iemr/admin/service/bulkRegistration/BulkRegistrationServiceImplTest.java b/src/test/java/com/iemr/admin/service/bulkRegistration/BulkRegistrationServiceImplTest.java new file mode 100644 index 0000000..50bd3d1 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/bulkRegistration/BulkRegistrationServiceImplTest.java @@ -0,0 +1,588 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.bulkRegistration; + +import java.util.ArrayList; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.bulkuser.BulkRegistrationError; +import com.iemr.admin.data.bulkuser.Employee; +import com.iemr.admin.data.bulkuser.EmployeeList; +import com.iemr.admin.data.employeemaster.M_Community; +import com.iemr.admin.data.employeemaster.M_Gender; +import com.iemr.admin.data.employeemaster.M_Religion; +import com.iemr.admin.data.employeemaster.M_Title; +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.data.employeemaster.M_Userqualification; +import com.iemr.admin.data.locationmaster.M_District; +import com.iemr.admin.data.rolemaster.StateMasterForRole; +import com.iemr.admin.repo.employeemaster.V_ShowuserRepo; +import com.iemr.admin.service.employeemaster.EmployeeMasterInter; +import com.iemr.admin.service.locationmaster.LocationMasterServiceInter; +import com.iemr.admin.service.rolemaster.Role_MasterInter; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Bulk registration turns an uploaded spreadsheet into user records. A row that + * fails validation must be reported rather than half-saved, so the error log is + * as much a deliverable as the users it creates. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("BulkRegistrationServiceImpl Test Suite") +class BulkRegistrationServiceImplTest { + + /** Excel serial numbers, which is how the uploaded sheet carries its dates. */ + private static final String DOB_SERIAL = "30000"; + private static final String DOJ_SERIAL = "40000"; + + @Mock + private EmployeeMasterInter employeeMasterInter; + + @Mock + private Role_MasterInter roleMasterInter; + + @Mock + private V_ShowuserRepo showuserRepo; + + @Mock + private LocationMasterServiceInter locationMasterServiceInter; + + @Mock + private EmployeeXmlService employeeXmlService; + + @InjectMocks + private BulkRegistrationServiceImpl service; + + private Employee employee; + + @BeforeEach + void setUp() throws Exception { + employee = validEmployee(); + + M_Title title = new M_Title(); + title.setTitleID(1); + title.setTitleName("Ms."); + when(employeeMasterInter.getAllTitle()).thenReturn(new ArrayList<>(List.of(title))); + + M_Gender gender = new M_Gender(); + gender.setGenderID(2); + gender.setGenderName("Female"); + when(employeeMasterInter.getAllGender()).thenReturn(new ArrayList<>(List.of(gender))); + + M_Userqualification qualification = new M_Userqualification(); + qualification.setQualificationID(5); + qualification.setName("MBBS"); + when(employeeMasterInter.getQualification()).thenReturn(new ArrayList<>(List.of(qualification))); + + M_Community community = new M_Community(); + community.setCommunityID(3); + community.setCommunityType("General"); + when(employeeMasterInter.getAllCommunity()).thenReturn(new ArrayList<>(List.of(community))); + + M_Religion religion = new M_Religion(); + religion.setReligionID(4); + religion.setReligionType("Hindu"); + when(employeeMasterInter.getAllReligion()).thenReturn(new ArrayList<>(List.of(religion))); + + StateMasterForRole state = new StateMasterForRole(); + state.setStateID(29); + state.setStateName("Karnataka"); + when(roleMasterInter.getAllState()).thenReturn(new ArrayList<>(List.of(state))); + + M_District district = new M_District(); + district.setDistrictID(301); + district.setDistrictName("Bengaluru Urban"); + when(locationMasterServiceInter.getAllDistrictByStateId(29)).thenReturn(new ArrayList<>(List.of(district))); + + when(employeeMasterInter.FindEmployeeName(anyString())).thenReturn("usernotexist"); + when(employeeMasterInter.FindEmployeeContact(anyString())).thenReturn("contactnotexist"); + when(employeeMasterInter.FindEmployeeAadhaar(anyString())).thenReturn("aadhaarnotexist"); + + M_User1 saved = new M_User1(); + saved.setUserID(3117); + when(employeeMasterInter.saveBulkUserEmployee(any())).thenReturn(saved); + } + + private static Employee validEmployee() { + Employee employee = new Employee(); + employee.setTitle("Ms"); + employee.setFirstName("Asha"); + employee.setMiddleName(""); + employee.setLastName("Rao"); + employee.setGender("Female"); + employee.setContactNo("9000000001"); + employee.setDesignation("ASHA"); + employee.setEmergencyContactNo("9000000002"); + employee.setDob(DOB_SERIAL); + employee.setEmail("asha.rao@example.org"); + employee.setAadhaarNo("111122223333"); + employee.setPan("ABCDEFGH123"); + employee.setQualification("MBBS"); + employee.setFatherName("Ravi"); + employee.setMotherName("Meera"); + employee.setCommunity("General"); + employee.setReligion("Hindu"); + employee.setAddressLine1("Main Road"); + employee.setState("Karnataka"); + employee.setDistrict("Bengaluru Urban"); + employee.setPincode("560001"); + employee.setPermanentAddressLine1("Main Road"); + employee.setPermanentState("Karnataka"); + employee.setPermanentDistrict("Bengaluru Urban"); + employee.setPermanentPincode("560001"); + employee.setDateOfJoining(DOJ_SERIAL); + employee.setUserName("EMP-1"); + employee.setPassword("plain-secret"); + return employee; + } + + private void uploadContains(Employee... employees) throws Exception { + EmployeeList list = new EmployeeList(); + list.setEmployees(new ArrayList<>(List.of(employees))); + when(employeeXmlService.parseXml(anyString())).thenReturn(list); + } + + @Nested + @DisplayName("registerBulkUser") + class RegisterBulkUserTests { + + @Test + @DisplayName("should register a row that passes every rule") + void register_shouldRegisterValidRow() throws Exception { + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertEquals(1, service.totalEmployeeListSize); + assertEquals(1, service.m_bulkUser.size()); + assertTrue(service.errorLogs.isEmpty(), service.errorLogs.toString()); + verify(employeeMasterInter).saveBulkUserEmployee(any()); + verify(employeeMasterInter).saveDemography(any()); + } + + @Test + @DisplayName("should carry the uploaded details onto the user it stores") + void register_shouldCarryDetailsOntoStoredUser() throws Exception { + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + M_User1 stored = service.m_bulkUser.get(0); + assertEquals("Asha", stored.getFirstName()); + assertEquals("9000000001", stored.getUserName(), "the contact number is used as the sign-in name"); + assertEquals("EMP-1", stored.getEmployeeID()); + assertEquals(77, stored.getServiceProviderID()); + assertEquals(2, stored.getStatusID()); + assertFalse("plain-secret".equals(stored.getPassword()), "the password must be hashed before storing"); + } + + @Test + @DisplayName("should report a row that names no user") + void register_shouldReportRowWithoutUserName() throws Exception { + employee.setUserName(""); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertEquals(1, service.errorLogs.size()); + assertTrue(service.errorLogs.get(0).contains("Please Enter UserName"), service.errorLogs.toString()); + verify(employeeMasterInter, never()).saveBulkUserEmployee(any()); + } + + @Test + @DisplayName("should report a row whose user name is already taken") + void register_shouldReportExistingUser() throws Exception { + when(employeeMasterInter.FindEmployeeName(anyString())).thenReturn("userexist"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertTrue(service.errorLogs.get(0).contains("User Already exist"), service.errorLogs.toString()); + } + + @Test + @DisplayName("should report a row whose contact number is already taken") + void register_shouldReportExistingContact() throws Exception { + when(employeeMasterInter.FindEmployeeContact(anyString())).thenReturn("contactexist"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertTrue(service.errorLogs.get(0).contains("Contact No Already exist"), service.errorLogs.toString()); + } + + @Test + @DisplayName("should report every rule a row breaks rather than only the first") + void register_shouldReportEveryBrokenRule() throws Exception { + employee.setTitle(""); + employee.setFirstName(""); + employee.setLastName(""); + employee.setEmail("not-an-email"); + employee.setContactNo("12345"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + String reported = service.errorLogs.get(0); + assertTrue(reported.contains("Title is missing."), reported); + assertTrue(reported.contains("First Name is missing."), reported); + assertTrue(reported.contains("Last Name is missing."), reported); + assertTrue(reported.contains("Invalid Email format."), reported); + assertTrue(reported.contains("Contact Number is invalid"), reported); + } + + @Test + @DisplayName("should report a name that is a number rather than a name") + void register_shouldReportNumericName() throws Exception { + employee.setFirstName("12345"); + employee.setLastName("67890"); + employee.setMiddleName("42"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + String reported = service.errorLogs.get(0); + assertTrue(reported.contains("First name is invalid."), reported); + assertTrue(reported.contains("Last name is invalid."), reported); + assertTrue(reported.contains("Middle name is invalid."), reported); + } + + @Test + @DisplayName("should report a name longer than the column can hold") + void register_shouldReportOverlongName() throws Exception { + String tooLong = "A".repeat(51); + employee.setFirstName(tooLong); + employee.setMiddleName(tooLong); + employee.setLastName(tooLong); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + String reported = service.errorLogs.get(0); + assertTrue(reported.contains("First name is invalid."), reported); + assertTrue(reported.contains("Middle name is invalid."), reported); + assertTrue(reported.contains("Last name is invalid."), reported); + } + + @Test + @DisplayName("should report a title the master does not know") + void register_shouldReportUnknownTitle() throws Exception { + employee.setTitle("Archduke"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertTrue(service.errorLogs.get(0).contains("Title is invalid."), service.errorLogs.toString()); + } + + @Test + @DisplayName("should report a district the master does not know") + void register_shouldReportUnknownDistrict() throws Exception { + employee.setDistrict("Nowhere"); + employee.setPermanentDistrict("Nowhere"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + String reported = service.errorLogs.get(0); + assertTrue(reported.contains("Current District is invalid."), reported); + assertTrue(reported.contains("Permanent District is invalid."), reported); + } + + @Test + @DisplayName("should abandon the upload when a row names a state the master does not know") + void register_shouldAbandonUploadForUnknownState() throws Exception { + employee.setState("Atlantis"); + employee.setPermanentState("Atlantis"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertEquals(List.of("Data is invalid or empty"), service.errorLogs, + "an unresolvable state leaves no districts to check against, so the upload is refused"); + assertTrue(service.m_bulkUser.isEmpty()); + } + + @Test + @DisplayName("should report an Aadhaar number that is already on file") + void register_shouldReportDuplicateAadhaar() throws Exception { + when(employeeMasterInter.FindEmployeeAadhaar(anyString())).thenReturn("aadhaarexist"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertTrue(service.errorLogs.get(0).contains("Duplicate aadhaar number found"), + service.errorLogs.toString()); + } + + @Test + @DisplayName("should report an Aadhaar number that is not twelve digits") + void register_shouldReportMalformedAadhaar() throws Exception { + employee.setAadhaarNo("1234"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertTrue(service.errorLogs.get(0).contains("Aadhaar number is invalid"), + service.errorLogs.toString()); + } + + @Test + @DisplayName("should report a date of birth in the future") + void register_shouldReportFutureDateOfBirth() throws Exception { + employee.setDob("50000"); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertTrue(service.errorLogs.get(0).contains("Date of Birth is invalid."), + service.errorLogs.toString()); + } + + @Test + @DisplayName("should report a row whose mandatory fields are simply blank") + void register_shouldReportBlankMandatoryFields() throws Exception { + employee.setGender(""); + employee.setContactNo(""); + employee.setDesignation(""); + employee.setEmergencyContactNo(""); + employee.setDob(""); + employee.setEmail(""); + employee.setPassword(""); + employee.setQualification(""); + employee.setState(""); + employee.setDistrict(""); + employee.setPermanentState(""); + employee.setPermanentDistrict(""); + employee.setDateOfJoining(""); + uploadContains(employee); + + service.registerBulkUser("", "auth", "admin", 77); + + String reported = service.errorLogs.get(0); + assertTrue(reported.contains("Gender is missing"), reported); + assertTrue(reported.contains("Contact number missing"), reported); + assertTrue(reported.contains("Designation is missing"), reported); + assertTrue(reported.contains("Emergency contact number is missing"), reported); + assertTrue(reported.contains("Date of Birth is missing."), reported); + assertTrue(reported.contains("Email is missing."), reported); + assertTrue(reported.contains("Qualification is missing"), reported); + assertTrue(reported.contains("Date of Joining is missing."), reported); + } + + @Test + @DisplayName("should report an upload that carries no rows at all") + void register_shouldReportEmptyUpload() throws Exception { + EmployeeList list = new EmployeeList(); + list.setEmployees(new ArrayList<>()); + when(employeeXmlService.parseXml(anyString())).thenReturn(list); + + service.registerBulkUser("", "auth", "admin", 77); + + assertEquals(List.of("Data is invalid or empty"), service.errorLogs); + } + + @Test + @DisplayName("should report an upload it cannot read at all") + void register_shouldReportUnreadableUpload() throws Exception { + when(employeeXmlService.parseXml(anyString())).thenThrow(new IllegalStateException("not xml")); + + service.registerBulkUser("not xml", "auth", "admin", 77); + + assertEquals(List.of("Data is invalid or empty"), service.errorLogs); + } + + @Test + @DisplayName("should keep going through the sheet after a row it cannot register") + void register_shouldKeepGoingAfterABadRow() throws Exception { + Employee bad = validEmployee(); + bad.setUserName(""); + uploadContains(bad, employee); + + service.registerBulkUser("", "auth", "admin", 77); + + assertEquals(2, service.totalEmployeeListSize); + assertEquals(1, service.m_bulkUser.size(), "the good row must still be registered"); + assertEquals(1, service.errorLogs.size()); + } + } + + @Nested + @DisplayName("Master lookups") + class MasterLookupTests { + + @Test + @DisplayName("getCommunityId should resolve a known community and answer zero for an unknown one") + void getCommunityId_shouldResolveKnownCommunity() { + assertEquals(3, service.getCommunityId("General")); + assertEquals(0, service.getCommunityId("Unknown")); + } + + @Test + @DisplayName("getQualificationId should resolve a known qualification and answer zero for an unknown one") + void getQualificationId_shouldResolveKnownQualification() { + assertEquals(5, service.getQualificationId("MBBS")); + assertEquals(0, service.getQualificationId("Unknown")); + } + + @Test + @DisplayName("getReligionStringId should resolve a known religion") + void getReligionStringId_shouldResolveKnownReligion() { + assertEquals(4, service.getReligionStringId("Hindu")); + assertEquals(0, service.getReligionStringId("Unknown")); + } + + @Test + @DisplayName("getReligionStringId should treat an unstated religion as none") + void getReligionStringId_shouldTreatUnstatedAsNone() { + assertEquals(0, service.getReligionStringId("Not given")); + verify(employeeMasterInter, never()).getAllReligion(); + } + + @Test + @DisplayName("getStateId should resolve a known state and load its districts") + void getStateId_shouldResolveKnownStateAndLoadDistricts() { + assertEquals(29, service.getStateId("Karnataka")); + verify(locationMasterServiceInter).getAllDistrictByStateId(29); + } + + @Test + @DisplayName("getStateId should answer zero for a state the master does not know") + void getStateId_shouldAnswerZeroForUnknownState() { + assertEquals(0, service.getStateId("Atlantis")); + verify(locationMasterServiceInter, never()).getAllDistrictByStateId(29); + } + + @Test + @DisplayName("getDistrictId should resolve a district once its state has been resolved") + void getDistrictId_shouldResolveDistrictAfterState() { + service.getStateId("Karnataka"); + + assertEquals(301, service.getDistrictId("Bengaluru Urban")); + assertEquals(0, service.getDistrictId("Nowhere")); + } + + @Test + @DisplayName("getDistrictId should answer zero when no district name is given") + void getDistrictId_shouldAnswerZeroWithoutAName() { + assertEquals(0, service.getDistrictId("")); + } + + @Test + @DisplayName("getAllState should hand back what the role master holds") + void getAllState_shouldHandBackRoleMasterContents() { + assertEquals(1, service.getAllState().size()); + } + + @Test + @DisplayName("getDesignationId should answer the fixed designation the upload uses") + void getDesignationId_shouldAnswerFixedDesignation() { + assertEquals(20, service.getDesignationId("ASHA")); + } + } + + @Nested + @DisplayName("Helpers") + class HelperTests { + + @Test + @DisplayName("escapeXmlSpecialChars should escape a bare ampersand and leave real entities alone") + void escape_shouldEscapeBareAmpersandOnly() { + assertEquals("Ram & Co", BulkRegistrationServiceImpl.escapeXmlSpecialChars("Ram & Co")); + assertEquals("Ram & Co", BulkRegistrationServiceImpl.escapeXmlSpecialChars("Ram & Co")); + assertEquals("<tag>", BulkRegistrationServiceImpl.escapeXmlSpecialChars("<tag>")); + } + + @Test + @DisplayName("isNumeric should tell a number apart from a name") + void isNumeric_shouldTellNumberFromName() { + assertTrue(BulkRegistrationServiceImpl.isNumeric("12345")); + assertFalse(BulkRegistrationServiceImpl.isNumeric("Asha")); + } + + @Test + @DisplayName("isValidAadhar should report anything that is not twelve digits") + void isValidAadhar_shouldReportNonTwelveDigitNumbers() { + assertFalse(BulkRegistrationServiceImpl.isValidAadhar("111122223333")); + assertTrue(BulkRegistrationServiceImpl.isValidAadhar("1234")); + assertTrue(BulkRegistrationServiceImpl.isValidAadhar("not-a-number")); + } + + @Test + @DisplayName("convertStringIntoDate should read the spreadsheet's own date serial") + void convertStringIntoDate_shouldReadExcelSerial() { + assertEquals("1982-02-18", BulkRegistrationServiceImpl.convertStringIntoDate(DOB_SERIAL).toString()); + } + + @Test + @DisplayName("generateStrongPassword should answer a different hash each time it is called") + void generateStrongPassword_shouldSaltEachHash() throws Exception { + String first = service.generateStrongPassword("plain-secret"); + String second = service.generateStrongPassword("plain-secret"); + + assertTrue(first.startsWith("1001:"), first); + assertFalse(first.equals(second), "each hash must carry its own salt"); + } + + @Test + @DisplayName("insertErrorLog should write one workbook row per reported row") + void insertErrorLog_shouldWriteOneRowPerReportedRow() { + BulkRegistrationError error = new BulkRegistrationError(); + error.setRowNumber(1); + error.setUserName("EMP-1"); + error.setError(List.of("Title is missing.")); + service.bulkRegistrationErrors.add(error); + + byte[] workbook = service.insertErrorLog(); + + assertNotNull(workbook); + assertTrue(workbook.length > 0, "a workbook with a reported row must not be empty"); + } + + @Test + @DisplayName("insertErrorLog should still answer a workbook when nothing was reported") + void insertErrorLog_shouldAnswerWorkbookWithoutErrors() { + assertTrue(service.insertErrorLog().length > 0); + } + } +} diff --git a/src/test/java/com/iemr/admin/service/bulkRegistration/EmployeeXmlServiceTest.java b/src/test/java/com/iemr/admin/service/bulkRegistration/EmployeeXmlServiceTest.java new file mode 100644 index 0000000..cce6537 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/bulkRegistration/EmployeeXmlServiceTest.java @@ -0,0 +1,58 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.bulkRegistration; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.data.bulkuser.EmployeeList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** Reads the uploaded spreadsheet, which reaches the service as XML. */ +@DisplayName("EmployeeXmlService Test Suite") +class EmployeeXmlServiceTest { + + private final EmployeeXmlService service = new EmployeeXmlService(); + + @Test + @DisplayName("parseXml should read each employee row out of the uploaded document") + void parseXml_shouldReadEachEmployeeRow() throws Exception { + String xml = "AshaRao" + + "EMP-1" + + "RaviKumar" + + "EMP-2"; + + EmployeeList list = service.parseXml(xml); + + assertEquals(2, list.getEmployees().size()); + assertEquals("Asha", list.getEmployees().get(0).getFirstName()); + assertEquals("EMP-2", list.getEmployees().get(1).getUserName()); + } + + @Test + @DisplayName("parseXml should raise rather than answer a half-read document") + void parseXml_shouldRaiseForMalformedDocument() { + assertThrows(Exception.class, () -> service.parseXml("")); + } +} diff --git a/src/test/java/com/iemr/admin/service/calibration/CalibrationServiceImplTest.java b/src/test/java/com/iemr/admin/service/calibration/CalibrationServiceImplTest.java new file mode 100644 index 0000000..79d51be --- /dev/null +++ b/src/test/java/com/iemr/admin/service/calibration/CalibrationServiceImplTest.java @@ -0,0 +1,222 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.calibration; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.admin.data.calibration.CalibrationStrip; +import com.iemr.admin.repo.calibration.CalibrationRepo; +import com.iemr.admin.utils.exception.IEMRException; +import com.iemr.admin.utils.mapper.OutputMapper; + +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.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +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; + +/** + * The calibration service keeps the test strip codes a provider calibrates + * against, refusing a code the provider already holds. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("CalibrationServiceImpl Test Suite") +class CalibrationServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Long STRIP_ID = 6601L; + private static final String STRIP_CODE = "STRIP-77"; + + @Mock + private CalibrationRepo calibrationRepo; + + @InjectMocks + private CalibrationServiceImpl service; + + @BeforeEach + @DisplayName("Fix the page size the screens are served in and prime the shared output builder") + void setUp() { + ReflectionTestUtils.setField(service, "calibrationPageSize", 10); + new OutputMapper(); + } + + private static CalibrationStrip strip() { + CalibrationStrip strip = new CalibrationStrip(); + strip.setCalibrationStripID(STRIP_ID); + strip.setStripCode(STRIP_CODE); + strip.setProviderServiceMapID(PSM_ID); + strip.setDeleted(Boolean.FALSE); + return strip; + } + + private static CalibrationStrip request() { + CalibrationStrip request = new CalibrationStrip(); + request.setStripCode(STRIP_CODE); + request.setProviderServiceMapID(PSM_ID); + return request; + } + + @Test + @DisplayName("saveData should record a strip code the provider does not hold yet") + void save_shouldRecordNewStripCode() throws Exception { + when(calibrationRepo.checkIfAlreadyStripPresent(PSM_ID, STRIP_CODE)).thenReturn(new ArrayList<>()); + when(calibrationRepo.save(any(CalibrationStrip.class))).thenReturn(strip()); + + assertEquals(1, service.saveData(request())); + } + + @Test + @DisplayName("saveData should refuse a strip code the provider already holds") + void save_shouldRefuseDuplicateStripCode() { + when(calibrationRepo.checkIfAlreadyStripPresent(PSM_ID, STRIP_CODE)) + .thenReturn(new ArrayList<>(List.of(strip()))); + + IEMRException refusal = assertThrows(IEMRException.class, () -> service.saveData(request())); + + assertEquals("Strip code already exists", refusal.getMessage()); + verify(calibrationRepo, never()).save(any(CalibrationStrip.class)); + } + + @Test + @DisplayName("saveData should refuse a strip the repository did not give an identity") + void save_shouldRefuseStripWithoutIdentity() { + when(calibrationRepo.checkIfAlreadyStripPresent(PSM_ID, STRIP_CODE)).thenReturn(new ArrayList<>()); + when(calibrationRepo.save(any(CalibrationStrip.class))).thenReturn(new CalibrationStrip()); + + assertEquals("Error while saving data", + assertThrows(IEMRException.class, () -> service.saveData(request())).getMessage()); + } + + @Test + @DisplayName("saveData should record nothing when the request names no strip code") + void save_shouldRecordNothingWithoutStripCode() throws Exception { + assertEquals(0, service.saveData(new CalibrationStrip())); + verify(calibrationRepo, never()).save(any(CalibrationStrip.class)); + } + + @Test + @DisplayName("fetchData should answer one page of strips and how many pages there are") + void fetch_shouldAnswerOnePageAndPageCount() throws Exception { + CalibrationStrip request = request(); + request.setPageNo(0); + Pageable pageable = PageRequest.of(0, 10); + Page page = new PageImpl<>(List.of(strip()), pageable, 1); + when(calibrationRepo.getCalibrationStripsWithPagination(PSM_ID, pageable)).thenReturn(page); + + String answered = service.fetchData(request); + + assertTrue(answered.contains(STRIP_CODE), answered); + assertTrue(answered.contains("pageCount"), answered); + } + + @Test + @DisplayName("fetchData should answer every strip when the caller asks for no particular page") + void fetch_shouldAnswerEveryStripWithoutPaging() throws Exception { + when(calibrationRepo.getCalibrationStripsWithoutPagination(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(strip()))); + + String answered = service.fetchData(request()); + + assertTrue(answered.contains(STRIP_CODE), answered); + assertTrue(!answered.contains("pageCount"), "an unpaged answer carries no page count"); + } + + @Test + @DisplayName("fetchData should refuse a request that names no provider") + void fetch_shouldRefuseRequestWithoutProvider() { + assertThrows(IEMRException.class, () -> service.fetchData(new CalibrationStrip())); + } + + @Test + @DisplayName("deleteData should report how many strips the retirement touched") + void delete_shouldReportRowsTouched() throws Exception { + CalibrationStrip request = strip(); + request.setDeleted(Boolean.TRUE); + when(calibrationRepo.deleteCalibrationStrip(STRIP_ID, Boolean.TRUE)).thenReturn(1); + + assertEquals(1, service.deleteData(request)); + } + + @Test + @DisplayName("deleteData should refuse a request that names no strip") + void delete_shouldRefuseRequestWithoutStrip() { + assertEquals("Invalid request", + assertThrows(IEMRException.class, () -> service.deleteData(new CalibrationStrip())).getMessage()); + } + + @Test + @DisplayName("deleteData should give up when the retirement cannot be recorded") + void delete_shouldGiveUpWhenStorageFails() { + CalibrationStrip request = strip(); + request.setDeleted(Boolean.TRUE); + when(calibrationRepo.deleteCalibrationStrip(anyLong(), anyBoolean())) + .thenThrow(new RuntimeException("row is locked")); + + assertThrows(IEMRException.class, () -> service.deleteData(request)); + } + + @Test + @DisplayName("updateData should record the change against the strip") + void update_shouldRecordChange() throws Exception { + when(calibrationRepo.save(any(CalibrationStrip.class))).thenReturn(strip()); + + assertEquals(1, service.updateData(request())); + } + + @Test + @DisplayName("updateData should refuse a strip the repository did not give an identity") + void update_shouldRefuseStripWithoutIdentity() { + when(calibrationRepo.save(any(CalibrationStrip.class))).thenReturn(new CalibrationStrip()); + + assertEquals("Error while updating data", + assertThrows(IEMRException.class, () -> service.updateData(request())).getMessage()); + } + + @Test + @DisplayName("updateData should record nothing when the request names no strip code") + void update_shouldRecordNothingWithoutStripCode() throws Exception { + assertEquals(0, service.updateData(new CalibrationStrip())); + } +} diff --git a/src/test/java/com/iemr/admin/service/drugstrangth/DrugStrangthServiceTest.java b/src/test/java/com/iemr/admin/service/drugstrangth/DrugStrangthServiceTest.java new file mode 100644 index 0000000..b8b1632 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/drugstrangth/DrugStrangthServiceTest.java @@ -0,0 +1,112 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.drugstrangth; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.drugstrangth.M_104DrugStrength; +import com.iemr.admin.repo.blocking.DrugStrangthRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The drug strength service keeps the strengths a drug can be dispensed in. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("DrugStrangthService Test Suite") +class DrugStrangthServiceTest { + + private static final Integer STRENGTH_ID = 33; + + @Mock + private DrugStrangthRepo drugStrangthRepo; + + @InjectMocks + private DrugStrangthService service; + + private static M_104DrugStrength strength() { + M_104DrugStrength strength = new M_104DrugStrength(); + strength.setDrugStrengthID(STRENGTH_ID); + strength.setDrugStrength("500 mg"); + return strength; + } + + @Test + @DisplayName("createDrugStrangth should answer the strengths the repository stored") + void create_shouldAnswerStoredStrengths() { + ArrayList stored = new ArrayList<>(List.of(strength())); + when(drugStrangthRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createDrugStrangth(new ArrayList<>())); + } + + @Test + @DisplayName("getDrugStrangth should answer every strength on file") + void get_shouldAnswerEveryStrength() { + when(drugStrangthRepo.findAll()).thenReturn(new ArrayList<>(List.of(strength()))); + + assertEquals(1, service.getDrugStrangth().size()); + } + + @Test + @DisplayName("getDrugStrangth should answer nothing when no strength is on file") + void get_shouldAnswerNothingWhenNoneOnFile() { + when(drugStrangthRepo.findAll()).thenReturn(new ArrayList()); + + assertTrue(service.getDrugStrangth().isEmpty()); + } + + @Test + @DisplayName("updateDrugStrangth and saveupdatedData should each reach their own repository query") + void singleRecordOperations_shouldReachTheirOwnQuery() { + M_104DrugStrength stored = strength(); + when(drugStrangthRepo.findByDrugStrengthID(STRENGTH_ID)).thenReturn(stored); + when(drugStrangthRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.updateDrugStrangth(STRENGTH_ID)); + assertSame(stored, service.saveupdatedData(stored)); + } + + @Test + @DisplayName("updateDrugStrangth should answer nothing when the strength is unknown") + void update_shouldAnswerNothingForUnknownStrength() { + when(drugStrangthRepo.findByDrugStrengthID(-1)).thenReturn(null); + + assertNull(service.updateDrugStrangth(-1)); + } +} diff --git a/src/test/java/com/iemr/admin/service/drugtype/DrugtypeServiceImplTest.java b/src/test/java/com/iemr/admin/service/drugtype/DrugtypeServiceImplTest.java new file mode 100644 index 0000000..bfdfebc --- /dev/null +++ b/src/test/java/com/iemr/admin/service/drugtype/DrugtypeServiceImplTest.java @@ -0,0 +1,113 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.drugtype; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.drugtype.M_Drugtype; +import com.iemr.admin.repo.drugtype.DrugtypeRepo; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The drug type service keeps the dosage forms a provider stocks. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("DrugtypeServiceImpl Test Suite") +class DrugtypeServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer DRUG_TYPE_ID = 21; + + @Mock + private DrugtypeRepo drugtypeRepo; + + @InjectMocks + private DrugtypeServiceImpl service; + + private static M_Drugtype drugType() { + M_Drugtype drugType = new M_Drugtype(); + drugType.setDrugTypeID(DRUG_TYPE_ID); + drugType.setDrugTypeName("Tablet"); + drugType.setProviderServiceMapID(PSM_ID); + return drugType; + } + + @Test + @DisplayName("createDrugtypeData should answer the drug types the repository stored") + void create_shouldAnswerStoredDrugTypes() { + ArrayList stored = new ArrayList<>(List.of(drugType())); + when(drugtypeRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createDrugtypeData(new ArrayList<>())); + } + + @Test + @DisplayName("createDrugtypeData should answer nothing when the repository stored nothing") + void create_shouldAnswerNothingWhenNothingStored() { + when(drugtypeRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertNull(service.createDrugtypeData(new ArrayList<>())); + } + + @Test + @DisplayName("getDrugtypeData should answer the drug types the provider stocks") + void get_shouldAnswerProvidersDrugTypes() { + ArrayList stocked = new ArrayList<>(List.of(drugType())); + when(drugtypeRepo.getDrugtypeData(PSM_ID)).thenReturn(stocked); + + assertSame(stocked, service.getDrugtypeData(PSM_ID)); + } + + @Test + @DisplayName("editDrugtypeData and saveeditDrugtype should each reach their own repository query") + void singleRecordOperations_shouldReachTheirOwnQuery() { + M_Drugtype stored = drugType(); + when(drugtypeRepo.geteditedData(DRUG_TYPE_ID)).thenReturn(stored); + when(drugtypeRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.editDrugtypeData(DRUG_TYPE_ID)); + assertSame(stored, service.saveeditDrugtype(stored)); + } + + @Test + @DisplayName("editDrugtypeData should answer nothing when the drug type is unknown") + void edit_shouldAnswerNothingForUnknownDrugType() { + when(drugtypeRepo.geteditedData(-1)).thenReturn(null); + + assertNull(service.editDrugtypeData(-1)); + } +} diff --git a/src/test/java/com/iemr/admin/service/emailconfig/EmailConfigServiceImplTest.java b/src/test/java/com/iemr/admin/service/emailconfig/EmailConfigServiceImplTest.java new file mode 100644 index 0000000..11b5b8b --- /dev/null +++ b/src/test/java/com/iemr/admin/service/emailconfig/EmailConfigServiceImplTest.java @@ -0,0 +1,189 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.emailconfig; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.emailconfig.AuthorityEmail; +import com.iemr.admin.mapper.emailconfig.InstituteEmailConfigMapper; +import com.iemr.admin.model.emailconfig.AuthEmailRequest; +import com.iemr.admin.model.emailconfig.AuthEmailResponse; +import com.iemr.admin.model.emailconfig.CreateAuthEmailRequestModel; +import com.iemr.admin.model.emailconfig.UpdateAuthEmailRequest; +import com.iemr.admin.repository.emailconfig.InstituteEmailRepo; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The email config service keeps the authority mailboxes a complaint is copied + * to, narrowed by whichever parts of the location the caller names. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("EmailConfigServiceImpl Test Suite") +class EmailConfigServiceImplTest { + + @Mock + private EntityManager entityManager; + + @Mock + private InstituteEmailRepo instituteRepo; + + @Mock + private InstituteEmailConfigMapper instituteEmailConfigMapper; + + @InjectMocks + private EmailConfigServiceImpl service; + + private CriteriaQuery query; + private TypedQuery typedQuery; + + @SuppressWarnings("unchecked") + @BeforeEach + @DisplayName("Stand in for the criteria query the service builds by hand") + void setUp() { + CriteriaBuilder builder = mock(CriteriaBuilder.class); + query = mock(CriteriaQuery.class); + Root root = mock(Root.class); + typedQuery = mock(TypedQuery.class); + + when(entityManager.getCriteriaBuilder()).thenReturn(builder); + when(builder.createQuery(AuthorityEmail.class)).thenReturn(query); + when(query.from(AuthorityEmail.class)).thenReturn(root); + when(query.select(any())).thenReturn(query); + when(query.where(any(Predicate[].class))).thenReturn(query); + when(query.orderBy(any(jakarta.persistence.criteria.Order[].class))).thenReturn(query); + when(root.get(anyString())).thenReturn(mock(Path.class)); + when(builder.equal(any(), any())).thenReturn(mock(Predicate.class)); + when(entityManager.createQuery(query)).thenReturn(typedQuery); + } + + private static AuthEmailRequest fullyNarrowedRequest() { + AuthEmailRequest request = new AuthEmailRequest(); + request.setAuthorityEmailID(1); + request.setDeleted(false); + request.setDistrictID(301); + request.setDistrictBranchMappingID(30111); + request.setBlockID(3011); + request.setProviderServiceMapID(4001); + request.setStateID(29); + return request; + } + + @Test + @DisplayName("getAllEmailConfigs should answer the mailboxes the query found, as the screens read them") + void getAll_shouldAnswerFoundMailboxes() { + List found = List.of(new AuthorityEmail()); + List published = List.of(new AuthEmailResponse()); + when(typedQuery.getResultList()).thenReturn(found); + when(instituteEmailConfigMapper.resultToInstTypeEmailResponse(found)).thenReturn(published); + + assertSame(published, service.getAllEmailConfigs(fullyNarrowedRequest())); + } + + @Test + @DisplayName("getAllEmailConfigs should narrow the query by every detail the caller named") + void getAll_shouldNarrowByEveryNamedDetail() { + when(typedQuery.getResultList()).thenReturn(new ArrayList<>()); + + service.getAllEmailConfigs(fullyNarrowedRequest()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Predicate[].class); + verify(query).where(captor.capture()); + assertEquals(7, captor.getValue().length, "one narrowing per detail the caller named"); + } + + @Test + @DisplayName("getAllEmailConfigs should narrow by nothing when the caller names nothing") + void getAll_shouldNarrowByNothingForEmptyRequest() { + when(typedQuery.getResultList()).thenReturn(new ArrayList<>()); + + service.getAllEmailConfigs(new AuthEmailRequest()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Predicate[].class); + verify(query).where(captor.capture()); + assertEquals(0, captor.getValue().length); + } + + @Test + @DisplayName("saveEmailConfigs should store one mailbox per request and answer each as stored") + void save_shouldStoreEachRequestedMailbox() { + AuthorityEmail stored = new AuthorityEmail(); + when(instituteEmailConfigMapper.createRequestToInstituteEmailConf(anyList())) + .thenReturn(List.of(stored, stored)); + when(instituteRepo.save(stored)).thenReturn(stored); + when(instituteEmailConfigMapper.resultToInstTypeEmailResponse(stored)) + .thenReturn(new AuthEmailResponse()); + + assertEquals(2, service.saveEmailConfigs(List.of(new CreateAuthEmailRequestModel())).size()); + } + + @Test + @DisplayName("saveEmailConfigs should store nothing when the caller asks for nothing") + void save_shouldStoreNothingForEmptyRequest() { + when(instituteEmailConfigMapper.createRequestToInstituteEmailConf(anyList())) + .thenReturn(new ArrayList<>()); + + assertTrue(service.saveEmailConfigs(new ArrayList<>()).isEmpty()); + } + + @Test + @DisplayName("updateEmailConfigs should answer the mailbox as it stands after the change") + void update_shouldAnswerChangedMailbox() { + AuthorityEmail stored = new AuthorityEmail(); + AuthEmailResponse published = new AuthEmailResponse(); + when(instituteEmailConfigMapper.updateRequestToInstituteEmailConf(any(UpdateAuthEmailRequest.class))) + .thenReturn(stored); + when(instituteRepo.save(stored)).thenReturn(stored); + when(instituteEmailConfigMapper.resultToInstTypeEmailResponse(stored)).thenReturn(published); + + assertSame(published, service.updateEmailConfigs(new UpdateAuthEmailRequest())); + } +} diff --git a/src/test/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImplTest.java b/src/test/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImplTest.java new file mode 100644 index 0000000..e70bfee --- /dev/null +++ b/src/test/java/com/iemr/admin/service/employeemaster/EmployeeMasterServiceImplTest.java @@ -0,0 +1,1195 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.employeemaster; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.employeemaster.EmployeeSignature; +import com.iemr.admin.data.employeemaster.M_Community; +import com.iemr.admin.data.employeemaster.M_Gender; +import com.iemr.admin.data.employeemaster.M_ProviderServiceMap1; +import com.iemr.admin.data.employeemaster.M_Religion; +import com.iemr.admin.data.employeemaster.M_Role; +import com.iemr.admin.data.employeemaster.M_Title; +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.data.employeemaster.M_UserDemographics; +import com.iemr.admin.data.employeemaster.M_UserLangMapping; +import com.iemr.admin.data.employeemaster.M_UserServiceRoleMapping2; +import com.iemr.admin.data.employeemaster.M_Userqualification; +import com.iemr.admin.data.employeemaster.Showofficedetails1; +import com.iemr.admin.data.employeemaster.Showuserdetailsfromuserservicerolemapping; +import com.iemr.admin.data.employeemaster.V_Showuser; +import com.iemr.admin.data.employeemaster.V_Userservicerolemapping; +import com.iemr.admin.data.facilitytype.M_facilitytype; +import com.iemr.admin.data.rolemaster.M_UserservicerolemappingForRoleProviderAdmin; +import com.iemr.admin.data.rolemaster.UserRole; +import com.iemr.admin.data.store.M_Facility; +import com.iemr.admin.exceptionhandler.DataNotFound; +import com.iemr.admin.repo.employeemaster.EmployeeMasterRepo; +import com.iemr.admin.repo.employeemaster.EmployeeMasterRepoo; +import com.iemr.admin.repo.employeemaster.EmployeeSignatureRepo; +import com.iemr.admin.repo.employeemaster.M_CommunityRepo; +import com.iemr.admin.repo.employeemaster.M_GenderRepo; +import com.iemr.admin.repo.employeemaster.M_ProviderServiceMap1Repo; +import com.iemr.admin.repo.employeemaster.M_QualificationRepo; +import com.iemr.admin.repo.employeemaster.M_ReligionRepo; +import com.iemr.admin.repo.employeemaster.M_TitleRepo; +import com.iemr.admin.repo.employeemaster.M_UserDemographicsRepo; +import com.iemr.admin.repo.employeemaster.M_UserLangMappingRepo; +import com.iemr.admin.repo.employeemaster.RoleRepo; +import com.iemr.admin.repo.employeemaster.Showofficedetails1Repo1; +import com.iemr.admin.repo.employeemaster.ShowuserdetailsfromuserservicerolemappingRepo; +import com.iemr.admin.repo.employeemaster.V_ShowuserRepo; +import com.iemr.admin.repo.employeemaster.V_UserservicerolemappingRepo; +import com.iemr.admin.repository.facilitytype.M_facilitytypeRepo; +import com.iemr.admin.repository.rolemaster.M_UserservicerolemappingForRoleProviderAdminRepo; +import com.iemr.admin.repository.store.MainStoreRepo; +import com.iemr.admin.service.user.EncryptUserPassword; +import com.iemr.admin.utils.response.OutputResponse; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The employee master service is where the rules about who may work where live: + * an ASHA must sit at a sub-centre, a role may not be mapped twice, and taking a + * role away has to take the supervisor mappings with it. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("EmployeeMasterServiceImpl Test Suite") +class EmployeeMasterServiceImplTest { + + private static final Integer USER_ID = 3117; + private static final Integer PSM_ID = 4001; + + @Mock + private RoleRepo roleRepo; + + @Mock + private EmployeeMasterRepo employeeMasterRepo; + + @Mock + private EmployeeMasterRepoo employeeMasterRepo11; + + @Mock + private EmployeeMasterRepoo employeeMasterRepoo; + + @Mock + private M_UserDemographicsRepo m_UserDemographicsRepo; + + @Mock + private M_UserLangMappingRepo m_UserLangMappingRepo; + + @Mock + private M_TitleRepo m_TitleRepo; + + @Mock + private M_GenderRepo m_GenderRepo; + + @Mock + private ShowuserdetailsfromuserservicerolemappingRepo showuserdetailsfromuserservicerolemappingRepo; + + @Mock + private V_ShowuserRepo v_ShowuserRepo; + + @Mock + private V_UserservicerolemappingRepo v_UserservicerolemappingRepo; + + @Mock + private M_QualificationRepo m_QualificationRepo; + + @Mock + private Showofficedetails1Repo1 showofficedetails1Repo1; + + @Mock + private M_ProviderServiceMap1Repo m_ProviderServiceMap1Repo; + + @Mock + private MainStoreRepo mainStoreRepo; + + @Mock + private M_facilitytypeRepo facilityTypeRepo; + + @Mock + private EmployeeSignatureRepo employeeSignatureRepo; + + @Mock + private M_CommunityRepo m_CommunityRepo; + + @Mock + private M_ReligionRepo m_ReligionRepo; + + @Mock + private M_UserservicerolemappingForRoleProviderAdminRepo userservicerolemappingForRoleProviderAdminRepo; + + @Mock + private AshaSupervisorMappingService ashaSupervisorMappingService; + + @Mock + private EncryptUserPassword encryptUserPassword; + + @InjectMocks + private EmployeeMasterServiceImpl service; + + private M_Role role; + + @BeforeEach + void setUp() { + role = new M_Role(); + role.setRoleID(11); + role.setRoleName("Counsellor"); + when(roleRepo.findByRoleID(anyInt())).thenReturn(role); + } + + private static M_UserServiceRoleMapping2 mapping(Integer id, Integer roleId) { + M_UserServiceRoleMapping2 mapping = new M_UserServiceRoleMapping2(); + mapping.setuSRMappingID(id); + mapping.setUserID(USER_ID); + mapping.setRoleID(roleId); + mapping.setProviderServiceMapID(PSM_ID); + return mapping; + } + + private void namedRole(String name) { + role.setRoleName(name); + } + + private void activeFacility(Integer facilityId, Integer typeId, Integer levelValue, Integer maxLevel) { + M_Facility facility = new M_Facility(); + facility.setFacilityID(facilityId); + facility.setFacilityTypeID(typeId); + when(mainStoreRepo.findByFacilityIDAndDeleted(facilityId, false)).thenReturn(facility); + M_facilitytype type = new M_facilitytype(); + type.setFacilityTypeID(typeId); + type.setLevelValue(levelValue); + when(facilityTypeRepo.findByFacilityTypeID(typeId)).thenReturn(type); + when(facilityTypeRepo.findMaxLevelValueByProviderServiceMapID(PSM_ID)).thenReturn(maxLevel); + } + + @Nested + @DisplayName("mapRole") + class MapRoleTests { + + @Test + @DisplayName("should refuse an ASHA mapping that names no facility") + void mapRole_shouldRefuseAshaWithoutFacility() { + namedRole("ASHA"); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.mapRole(List.of(mapping(null, 11)), "auth")); + + assertTrue(thrown.getMessage().contains("Facility (SC) is mandatory for ASHA role"), + thrown.getMessage()); + verify(employeeMasterRepo, never()).saveAll(anyList()); + } + + @Test + @DisplayName("should refuse a mapping onto a facility that has been retired") + void mapRole_shouldRefuseRetiredFacility() { + M_UserServiceRoleMapping2 toMap = mapping(null, 11); + toMap.setFacilityID(501); + when(mainStoreRepo.findByFacilityIDAndDeleted(501, false)).thenReturn(null); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.mapRole(List.of(toMap), "auth")); + + assertTrue(thrown.getMessage().contains("is no longer active"), thrown.getMessage()); + } + + @Test + @DisplayName("should refuse an ASHA mapped above sub-centre level") + void mapRole_shouldRefuseAshaAboveSubCentre() { + namedRole("ASHA"); + M_UserServiceRoleMapping2 toMap = mapping(null, 11); + toMap.setFacilityID(501); + activeFacility(501, 3, 2, 4); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.mapRole(List.of(toMap), "auth")); + + assertTrue(thrown.getMessage().contains("Sub-Centre (SC) level facility"), thrown.getMessage()); + } + + @Test + @DisplayName("should refuse a second active mapping for the same user, role and service line") + void mapRole_shouldRefuseDuplicateMapping() { + when(employeeMasterRepo.existsByUserIDAndRoleIDAndProviderServiceMapIDAndDeletedFalse(USER_ID, 11, PSM_ID)) + .thenReturn(true); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.mapRole(List.of(mapping(null, 11)), "auth")); + + assertTrue(thrown.getMessage().contains("Duplicate mapping is not allowed"), thrown.getMessage()); + } + + @Test + @DisplayName("should refuse a second mapping for an ASHA supervisor at the same facility") + void mapRole_shouldRefuseDuplicateSupervisorMappingAtSameFacility() { + namedRole("ASHA Supervisor"); + M_UserServiceRoleMapping2 toMap = mapping(null, 11); + toMap.setFacilityID(501); + activeFacility(501, 3, 4, 4); + when(employeeMasterRepo + .existsByUserIDAndRoleIDAndProviderServiceMapIDAndFacilityIDAndDeletedFalse( + USER_ID, 11, PSM_ID, 501)) + .thenReturn(true); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.mapRole(List.of(toMap), "auth")); + + assertTrue(thrown.getMessage().contains("active work location mapping for this facility"), + thrown.getMessage()); + } + + @Test + @DisplayName("should let an ASHA supervisor hold a second mapping at a different facility") + void mapRole_shouldAllowSupervisorAtDifferentFacility() throws Exception { + namedRole("ASHA Supervisor"); + M_UserServiceRoleMapping2 toMap = mapping(null, 11); + toMap.setFacilityID(502); + activeFacility(502, 3, 4, 4); + ArrayList stored = new ArrayList<>(List.of(toMap)); + when(employeeMasterRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.mapRole(List.of(toMap), "auth")); + } + + @Test + @DisplayName("should flatten the village lists onto the stored columns") + void mapRole_shouldFlattenVillageLists() throws Exception { + M_UserServiceRoleMapping2 toMap = mapping(null, 11); + toMap.setVillageID(new String[] { "501", "502" }); + toMap.setVillageName(new String[] { "Hosur", "Devanahalli" }); + when(employeeMasterRepo.saveAll(anyList())) + .thenReturn(new ArrayList<>(List.of(toMap))); + + service.mapRole(List.of(toMap), "auth"); + + assertEquals("501,502", toMap.getVillageidDb()); + assertEquals("Hosur,Devanahalli", toMap.getVillageNameDb()); + verify(employeeMasterRepo).save(toMap); + } + + @Test + @DisplayName("should leave the village columns alone when the mapping names no villages") + void mapRole_shouldLeaveVillageColumnsAlone() throws Exception { + M_UserServiceRoleMapping2 toMap = mapping(null, 11); + when(employeeMasterRepo.saveAll(anyList())).thenReturn(new ArrayList<>(List.of(toMap))); + + service.mapRole(List.of(toMap), "auth"); + + assertNull(toMap.getVillageidDb()); + verify(employeeMasterRepo, never()).save(any()); + } + } + + @Nested + @DisplayName("saveRoleMappingeditedData") + class SaveRoleMappingTests { + + @Test + @DisplayName("should refuse an ASHA edit that drops the facility") + void saveRoleMapping_shouldRefuseAshaWithoutFacility() { + namedRole("ASHA"); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.saveRoleMappingeditedData(mapping(9001, 11), "auth")); + + assertTrue(thrown.getMessage().contains("Facility (SC) is mandatory for ASHA role"), + thrown.getMessage()); + } + + @Test + @DisplayName("should skip the facility rules when the mapping is only being deactivated") + void saveRoleMapping_shouldSkipRulesWhenDeactivating() throws Exception { + namedRole("ASHA"); + M_UserServiceRoleMapping2 toSave = mapping(9001, 11); + toSave.setDeleted(Boolean.TRUE); + M_UserServiceRoleMapping2 old = mapping(9001, 11); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(old)); + when(employeeMasterRepo.save(toSave)).thenReturn(toSave); + + assertSame(toSave, service.saveRoleMappingeditedData(toSave, "auth")); + verify(ashaSupervisorMappingService).cascadeDeleteByUserID(USER_ID, "Admin"); + } + + @Test + @DisplayName("should clear the saved villages when the facility is above sub-centre level") + void saveRoleMapping_shouldClearVillagesAboveSubCentre() throws Exception { + M_UserServiceRoleMapping2 toSave = mapping(9001, 11); + toSave.setFacilityID(501); + toSave.setVillageidDb("501,502"); + toSave.setVillageNameDb("Hosur,Devanahalli"); + activeFacility(501, 3, 2, 4); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.empty()); + when(employeeMasterRepo.save(toSave)).thenReturn(toSave); + + service.saveRoleMappingeditedData(toSave, "auth"); + + assertNull(toSave.getVillageidDb(), "a non sub-centre posting keeps no village list"); + assertNull(toSave.getVillageNameDb()); + } + + @Test + @DisplayName("should cascade the supervisor mappings when the role changes") + void saveRoleMapping_shouldCascadeWhenRoleChanges() throws Exception { + M_UserServiceRoleMapping2 toSave = mapping(9001, 12); + M_UserServiceRoleMapping2 old = mapping(9001, 11); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(old)); + when(employeeMasterRepo.save(toSave)).thenReturn(toSave); + + service.saveRoleMappingeditedData(toSave, "auth"); + + verify(ashaSupervisorMappingService).cascadeDeleteByUserID(USER_ID, "Admin"); + } + + @Test + @DisplayName("should cascade only this facility when a supervisor still works elsewhere") + void saveRoleMapping_shouldCascadeOnlyThisFacilityForBusySupervisor() throws Exception { + namedRole("ASHA Supervisor"); + M_UserServiceRoleMapping2 toSave = mapping(9001, 11); + toSave.setDeleted(Boolean.TRUE); + toSave.setFacilityID(501); + M_UserServiceRoleMapping2 old = mapping(9001, 11); + old.setFacilityID(501); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(old)); + when(employeeMasterRepo.countByUserIDAndRoleIDAndDeletedFalse(USER_ID, 11)).thenReturn(2L); + when(employeeMasterRepo.save(toSave)).thenReturn(toSave); + + service.saveRoleMappingeditedData(toSave, "auth"); + + verify(ashaSupervisorMappingService).cascadeDeleteByFacilityID(501, "Admin"); + verify(ashaSupervisorMappingService, never()).cascadeDeleteByUserID(anyInt(), anyString()); + } + + @Test + @DisplayName("should cascade every mapping when the facility itself changes") + void saveRoleMapping_shouldCascadeWhenFacilityChanges() throws Exception { + M_UserServiceRoleMapping2 toSave = mapping(9001, 11); + toSave.setFacilityID(502); + activeFacility(502, 3, 4, 4); + M_UserServiceRoleMapping2 old = mapping(9001, 11); + old.setFacilityID(501); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.of(old)); + when(employeeMasterRepo.save(toSave)).thenReturn(toSave); + + service.saveRoleMappingeditedData(toSave, "auth"); + + verify(ashaSupervisorMappingService).cascadeDeleteByUserID(USER_ID, "Admin"); + } + + @Test + @DisplayName("should flatten the village lists onto the stored columns") + void saveRoleMapping_shouldFlattenVillageLists() throws Exception { + M_UserServiceRoleMapping2 toSave = mapping(9001, 11); + toSave.setVillageID(new String[] { "501", "502" }); + toSave.setVillageName(new String[] { "Hosur", "Devanahalli" }); + when(employeeMasterRepo.findById(9001)).thenReturn(Optional.empty()); + when(employeeMasterRepo.save(toSave)).thenReturn(toSave); + + service.saveRoleMappingeditedData(toSave, "auth"); + + assertEquals("501,502", toSave.getVillageidDb()); + assertEquals("Hosur,Devanahalli", toSave.getVillageNameDb()); + } + } + + @Nested + @DisplayName("cascadeDeleteAshaMappingsForDeactivation") + class CascadeDeactivationTests { + + @Test + @DisplayName("should retire only this facility when the supervisor still works elsewhere") + void cascade_shouldRetireOnlyThisFacility() { + namedRole("ASHA Supervisor"); + M_UserServiceRoleMapping2 usrRole = mapping(9001, 11); + usrRole.setFacilityID(501); + when(employeeMasterRepo.countByUserIDAndRoleIDAndDeletedFalse(USER_ID, 11)).thenReturn(2L); + + service.cascadeDeleteAshaMappingsForDeactivation(usrRole); + + verify(ashaSupervisorMappingService).cascadeDeleteByUserIDAndFacilityID(USER_ID, 501, "Admin"); + verify(ashaSupervisorMappingService, never()).cascadeDeleteByUserID(anyInt(), anyString()); + } + + @Test + @DisplayName("should retire every mapping when this was the supervisor's last facility") + void cascade_shouldRetireEveryMappingOnLastFacility() { + namedRole("ASHA Supervisor"); + M_UserServiceRoleMapping2 usrRole = mapping(9001, 11); + usrRole.setFacilityID(501); + when(employeeMasterRepo.countByUserIDAndRoleIDAndDeletedFalse(USER_ID, 11)).thenReturn(1L); + + service.cascadeDeleteAshaMappingsForDeactivation(usrRole); + + verify(ashaSupervisorMappingService).cascadeDeleteByUserID(USER_ID, "Admin"); + } + + @Test + @DisplayName("should retire every mapping for a role that is not a supervisor") + void cascade_shouldRetireEveryMappingForNonSupervisor() { + service.cascadeDeleteAshaMappingsForDeactivation(mapping(9001, 11)); + + verify(ashaSupervisorMappingService).cascadeDeleteByUserID(USER_ID, "Admin"); + } + + @Test + @DisplayName("cascadeDeleteAshaMappingsForUser should hand the user straight to the mapping service") + void cascadeForUser_shouldDelegate() { + service.cascadeDeleteAshaMappingsForUser(USER_ID); + + verify(ashaSupervisorMappingService).cascadeDeleteByUserID(USER_ID, "Admin"); + } + } + + @Nested + @DisplayName("Employee lookups") + class LookupTests { + + @Test + @DisplayName("getAllRole should rebuild each role from what the repository holds") + void getAllRole_shouldRebuildRoles() { + M_Role stored = new M_Role(); + stored.setRoleID(11); + stored.setRoleName("Counsellor"); + stored.setProviderServiceMapID(PSM_ID); + when(roleRepo.getAllRole()).thenReturn(new ArrayList<>(List.of(stored))); + + ArrayList roles = service.getAllRole(); + + assertEquals(1, roles.size()); + assertEquals("Counsellor", roles.get(0).getRoleName()); + assertEquals(PSM_ID, roles.get(0).getProviderServiceMapID()); + } + + @Test + @DisplayName("getEmployeeDetails should skip a row the query could not fill") + void getEmployeeDetails_shouldSkipUnfillableRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 9001, USER_ID, 11, PSM_ID, "a", "b", "c", "d", 1, "e", 2, "f", "g" }); + when(employeeMasterRepo.getEmployeeDetails()).thenReturn(rows); + + assertEquals(1, service.getEmployeeDetails().size()); + } + + @Test + @DisplayName("getAllTitle should rebuild each title from what the repository holds") + void getAllTitle_shouldRebuildTitles() { + M_Title stored = new M_Title(); + stored.setTitleID(1); + stored.setTitleName("Dr"); + when(m_TitleRepo.getAllTitle()).thenReturn(new ArrayList<>(List.of(stored))); + + assertEquals("Dr", service.getAllTitle().get(0).getTitleName()); + } + + @Test + @DisplayName("getAllGender should rebuild each gender from what the repository holds") + void getAllGender_shouldRebuildGenders() { + M_Gender stored = new M_Gender(); + stored.setGenderID(1); + stored.setGenderName("Female"); + when(m_GenderRepo.getAllGender()).thenReturn(new ArrayList<>(List.of(stored))); + + assertEquals("Female", service.getAllGender().get(0).getGenderName()); + } + + @Test + @DisplayName("FindEmployeeName should distinguish a taken user name from a free one") + void findEmployeeName_shouldDistinguishTakenFromFree() { + when(employeeMasterRepoo.findEmployeeByName("asha.rao")).thenReturn(new M_User1()); + when(employeeMasterRepoo.findEmployeeByName("new.user")).thenReturn(null); + + assertEquals("userexist", service.FindEmployeeName("asha.rao")); + assertEquals("usernotexist", service.FindEmployeeName("new.user")); + } + + @Test + @DisplayName("FindEmployeeContact should distinguish a taken number from a free one") + void findEmployeeContact_shouldDistinguishTakenFromFree() { + when(employeeMasterRepoo.findEmployeeByContact("9000000001")).thenReturn(new M_User1()); + when(employeeMasterRepoo.findEmployeeByContact("9000000002")).thenReturn(null); + + assertEquals("contactexist", service.FindEmployeeContact("9000000001")); + assertEquals("contactnotexist", service.FindEmployeeContact("9000000002")); + } + + @Test + @DisplayName("FindEmployeeAadhaar should distinguish a taken number from a free one") + void findEmployeeAadhaar_shouldDistinguishTakenFromFree() { + when(employeeMasterRepoo.findEmployeeAadhaarNo("111122223333")).thenReturn(new M_User1()); + when(employeeMasterRepoo.findEmployeeAadhaarNo("444455556666")).thenReturn(null); + + assertEquals("aadhaarexist", service.FindEmployeeAadhaar("111122223333")); + assertEquals("aadhaarnotexist", service.FindEmployeeAadhaar("444455556666")); + } + + @Test + @DisplayName("FindEmployeeName1 should answer the user record itself") + void findEmployeeName1_shouldAnswerTheRecord() { + M_User1 stored = new M_User1(); + when(employeeMasterRepoo.findEmployeeByName("asha.rao")).thenReturn(stored); + + assertSame(stored, service.FindEmployeeName1("asha.rao")); + } + + @Test + @DisplayName("checkingEmpDetails should report whether the identifiers are already in use") + void checkingEmpDetails_shouldReportWhetherIdentifiersAreTaken() { + when(employeeMasterRepoo.checkingEmpDetails("asha.rao", "1", "2", "3", "4")).thenReturn(new M_User1()); + when(employeeMasterRepoo.checkingEmpDetails("new.user", "1", "2", "3", "4")).thenReturn(null); + + assertTrue(service.checkingEmpDetails("asha.rao", "1", "2", "3", "4")); + assertFalse(service.checkingEmpDetails("new.user", "1", "2", "3", "4")); + } + + @Test + @DisplayName("getQualification should hand back what the repository holds") + void getQualification_shouldHandBackRepositoryContents() { + ArrayList stored = new ArrayList<>(List.of(new M_Userqualification())); + when(m_QualificationRepo.getAllQualification()).thenReturn(stored); + + assertSame(stored, service.getQualification()); + } + + @Test + @DisplayName("getlocationByMapid2 should hand back what the repository holds") + void getlocationByMapid2_shouldHandBackRepositoryContents() { + ArrayList stored = new ArrayList<>(List.of(new Showofficedetails1())); + when(showofficedetails1Repo1.getlocationByMapid(PSM_ID, 301)).thenReturn(stored); + + assertSame(stored, service.getlocationByMapid2(PSM_ID, 301)); + } + + @Test + @DisplayName("getAllByMapId2 should hand back what the repository holds") + void getAllByMapId2_shouldHandBackRepositoryContents() { + ArrayList stored = new ArrayList<>(List.of(new M_ProviderServiceMap1())); + when(m_ProviderServiceMap1Repo.getAllByMapId2(77, 29, 3)).thenReturn(stored); + + assertSame(stored, service.getAllByMapId2(77, 29, 3)); + } + + @Test + @DisplayName("the narrowing searches should each reach their own repository query") + void narrowingSearches_shouldReachTheirOwnQuery() { + ArrayList stored = + new ArrayList<>(List.of(new Showuserdetailsfromuserservicerolemapping())); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails2(77, 29)).thenReturn(stored); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails3(77, 11)).thenReturn(stored); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails4(77, 3)).thenReturn(stored); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails6(77, USER_ID)).thenReturn(stored); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails7(77, 29, 301)).thenReturn(stored); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails8(77, 29, 301, 401)).thenReturn(stored); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails9(77, 29, 11)).thenReturn(stored); + when(showuserdetailsfromuserservicerolemappingRepo.EmployeeDetails10(77, 29, 11, 3, "asha.rao", USER_ID)) + .thenReturn(stored); + + assertSame(stored, service.getEmployeeDetails2(77, 29)); + assertSame(stored, service.getEmployeeDetails3(77, 11)); + assertSame(stored, service.getEmployeeDetails4(77, 3)); + assertSame(stored, service.getEmployeeDetails6(77, USER_ID)); + assertSame(stored, service.getEmployeeDetails7(77, 29, 301)); + assertSame(stored, service.getEmployeeDetails8(77, 29, 301, 401)); + assertSame(stored, service.getEmployeeDetails9(77, 29, 11)); + assertSame(stored, service.getEmployeeDetails10(77, 29, 11, 3, "asha.rao", USER_ID)); + } + + @Test + @DisplayName("getEmployeeDetails5 should hand back what the view holds") + void getEmployeeDetails5_shouldHandBackViewContents() { + ArrayList stored = new ArrayList<>(List.of(new V_Showuser())); + when(v_ShowuserRepo.EmployeeDetails5()).thenReturn(stored); + + assertSame(stored, service.getEmployeeDetails5()); + } + + @Test + @DisplayName("getcompleteUserDetails should hand back what the view holds") + void getcompleteUserDetails_shouldHandBackViewContents() { + ArrayList stored = new ArrayList<>(List.of(new V_Showuser())); + when(v_ShowuserRepo.getAdminDetails()).thenReturn(stored); + + assertSame(stored, service.getcompleteUserDetails()); + } + + @Test + @DisplayName("getAllReligion and getAllCommunity should hand back what the repositories hold") + void masters_shouldHandBackRepositoryContents() { + ArrayList religions = new ArrayList<>(List.of(new M_Religion())); + ArrayList communities = new ArrayList<>(List.of(new M_Community())); + when(m_ReligionRepo.findAll()).thenReturn(religions); + when(m_CommunityRepo.findAll()).thenReturn(communities); + + assertEquals(1, service.getAllReligion().size()); + assertEquals(1, service.getAllCommunity().size()); + } + } + + @Nested + @DisplayName("getEmployeeDetails4 by provider") + class EmployeeDetails4Tests { + + @Test + @DisplayName("should mark a user locked out after failed sign-ins") + void getEmployeeDetails4_shouldMarkLockedOutUser() { + V_Showuser user = new V_Showuser(); + user.setUserID(USER_ID); + M_User1 record = new M_User1(); + record.setUserID(USER_ID); + record.setFailedAttempt(3); + record.setDeleted(Boolean.TRUE); + record.setLockTimestamp(Timestamp.valueOf("2026-02-17 09:30:00")); + when(v_ShowuserRepo.EmployeeDetails4(77)).thenReturn(new ArrayList<>(List.of(user))); + when(employeeMasterRepoo.findByUserIDIn(anyList())).thenReturn(new ArrayList<>(List.of(record))); + + V_Showuser enriched = service.getEmployeeDetails4(77).get(0); + + assertEquals(3, enriched.getFailedAttempt()); + assertTrue(enriched.getLockedDueToFailedAttempts()); + } + + @Test + @DisplayName("should not mark a user locked out while their account is still active") + void getEmployeeDetails4_shouldNotMarkActiveUserLockedOut() { + V_Showuser user = new V_Showuser(); + user.setUserID(USER_ID); + M_User1 record = new M_User1(); + record.setUserID(USER_ID); + record.setDeleted(Boolean.FALSE); + record.setLockTimestamp(Timestamp.valueOf("2026-02-17 09:30:00")); + when(v_ShowuserRepo.EmployeeDetails4(77)).thenReturn(new ArrayList<>(List.of(user))); + when(employeeMasterRepoo.findByUserIDIn(anyList())).thenReturn(new ArrayList<>(List.of(record))); + + V_Showuser enriched = service.getEmployeeDetails4(77).get(0); + + assertEquals(0, enriched.getFailedAttempt()); + assertFalse(enriched.getLockedDueToFailedAttempts()); + } + + @Test + @DisplayName("should answer an empty result without asking for user records") + void getEmployeeDetails4_shouldAnswerEmptyWithoutFurtherLookups() { + when(v_ShowuserRepo.EmployeeDetails4(77)).thenReturn(new ArrayList<>()); + + assertTrue(service.getEmployeeDetails4(77).isEmpty()); + verify(employeeMasterRepoo, never()).findByUserIDIn(anyList()); + } + } + + @Nested + @DisplayName("Persistence") + class PersistenceTests { + + @Test + @DisplayName("saveEmployee should answer the id of the stored user and encrypt its credentials") + void saveEmployee_shouldStoreAndEncrypt() { + M_User1 toSave = new M_User1(); + M_User1 stored = new M_User1(); + stored.setUserID(USER_ID); + when(employeeMasterRepo11.save(toSave)).thenReturn(stored); + when(encryptUserPassword.encryptUserCredentials(stored)).thenReturn(new OutputResponse()); + + assertEquals(USER_ID, service.saveEmployee(toSave)); + verify(encryptUserPassword).encryptUserCredentials(stored); + } + + @Test + @DisplayName("saveEditData should re-encrypt the credentials it saved") + void saveEditData_shouldReEncryptCredentials() { + M_User1 toSave = new M_User1(); + when(employeeMasterRepo11.save(toSave)).thenReturn(toSave); + + assertSame(toSave, service.saveEditData(toSave)); + verify(encryptUserPassword).encryptUserCredentials(toSave); + } + + @Test + @DisplayName("saveDemography should answer the id of the stored demographics") + void saveDemography_shouldAnswerStoredId() { + M_UserDemographics stored = new M_UserDemographics(); + stored.setDemographicID(5001); + when(m_UserDemographicsRepo.save(any())).thenReturn(stored); + + assertEquals(5001, service.saveDemography(new M_UserDemographics())); + assertEquals(5001, service.saveeditDemo(new M_UserDemographics())); + } + + @Test + @DisplayName("saveeditlangdata should answer the id of the stored language mapping") + void saveeditlangdata_shouldAnswerStoredId() { + M_UserLangMapping stored = new M_UserLangMapping(); + stored.setUserLangID(7001); + when(m_UserLangMappingRepo.save(any())).thenReturn(stored); + + assertEquals(7001, service.saveeditlangdata(new M_UserLangMapping())); + } + + @Test + @DisplayName("mapLanguage and mapRoleUpdation should hand their batches to the repositories") + void batches_shouldReachTheRepositories() { + ArrayList languages = new ArrayList<>(); + ArrayList roles = new ArrayList<>(); + when(m_UserLangMappingRepo.saveAll(anyList())).thenReturn(languages); + when(employeeMasterRepo.saveAll(anyList())).thenReturn(roles); + + assertSame(languages, service.mapLanguage(new ArrayList<>())); + assertSame(roles, service.mapRoleUpdation(new ArrayList<>())); + } + + @Test + @DisplayName("saveeditedData should clear the failed sign-in count for a reinstated user") + void saveeditedData_shouldClearFailedAttemptsOnReinstatement() { + M_User1 toSave = new M_User1(); + toSave.setDeleted(Boolean.FALSE); + toSave.setFailedAttempt(3); + when(employeeMasterRepoo.save(toSave)).thenReturn(toSave); + + service.saveeditedData(toSave); + + assertEquals(0, toSave.getFailedAttempt()); + } + + @Test + @DisplayName("saveeditedData should leave the failed sign-in count alone for a deactivated user") + void saveeditedData_shouldLeaveFailedAttemptsAloneOnDeactivation() { + M_User1 toSave = new M_User1(); + toSave.setDeleted(Boolean.TRUE); + toSave.setFailedAttempt(3); + when(employeeMasterRepoo.save(toSave)).thenReturn(toSave); + + service.saveeditedData(toSave); + + assertEquals(3, toSave.getFailedAttempt()); + } + + @Test + @DisplayName("createProviderAdmin should hash the password before it is stored") + void createProviderAdmin_shouldHashPassword() throws Exception { + M_User1 toCreate = new M_User1(); + toCreate.setPassword("plain-secret"); + ArrayList stored = new ArrayList<>(List.of(toCreate)); + when(employeeMasterRepoo.saveAll(anyList())).thenReturn(stored); + + service.createProviderAdmin(List.of(toCreate)); + + assertTrue(toCreate.getPassword().startsWith("1001:"), + "the stored password must be the salted hash, not the plain text"); + } + + @Test + @DisplayName("createProviderAdmin should refuse an admin with no password") + void createProviderAdmin_shouldRefuseWithoutPassword() { + assertThrows(Exception.class, () -> service.createProviderAdmin(List.of(new M_User1()))); + } + + @Test + @DisplayName("createNewUser should hash the password before it is stored") + void createNewUser_shouldHashPassword() throws Exception { + M_User1 toCreate = new M_User1(); + toCreate.setPassword("plain-secret"); + when(employeeMasterRepoo.saveAll(anyList())).thenReturn(new ArrayList<>(List.of(toCreate))); + + service.createNewUser(List.of(toCreate)); + + assertFalse("plain-secret".equals(toCreate.getPassword())); + } + + @Test + @DisplayName("createNewUser should refuse a user with no password") + void createNewUser_shouldRefuseWithoutPassword() { + assertThrows(Exception.class, () -> service.createNewUser(List.of(new M_User1()))); + } + + @Test + @DisplayName("generateStrongPassword should answer a different hash each time it is called") + void generateStrongPassword_shouldSaltEachHash() throws Exception { + String first = service.generateStrongPassword("plain-secret"); + String second = service.generateStrongPassword("plain-secret"); + + assertNotNull(first); + assertFalse(first.equals(second), "each hash must carry its own salt"); + } + + @Test + @DisplayName("saveBulkUserEmployee should answer the record the repository stored") + void saveBulkUserEmployee_shouldAnswerStoredRecord() { + M_User1 stored = new M_User1(); + stored.setUserID(USER_ID); + when(employeeMasterRepo11.save(any())).thenReturn(stored); + + assertSame(stored, service.saveBulkUserEmployee(new M_User1())); + } + + @Test + @DisplayName("the single-record lookups should each reach their own repository query") + void singleRecordLookups_shouldReachTheirOwnQuery() { + M_User1 user = new M_User1(); + M_UserDemographics demographics = new M_UserDemographics(); + M_UserLangMapping language = new M_UserLangMapping(); + M_UserServiceRoleMapping2 roleMapping = mapping(9001, 11); + when(employeeMasterRepo11.editEmployee(USER_ID)).thenReturn(user); + when(employeeMasterRepoo.findByUserID(USER_ID)).thenReturn(user); + when(m_UserDemographicsRepo.mdedit(USER_ID)).thenReturn(demographics); + when(m_UserDemographicsRepo.findByUserID(USER_ID)).thenReturn(demographics); + when(m_UserDemographicsRepo.save(demographics)).thenReturn(demographics); + when(m_UserLangMappingRepo.ulangmapedit(USER_ID, 1)).thenReturn(language); + when(m_UserLangMappingRepo.findByUserLangID(7001)).thenReturn(language); + when(m_UserLangMappingRepo.save(language)).thenReturn(language); + when(employeeMasterRepo.uRoleMedit(USER_ID, 11)).thenReturn(roleMapping); + when(employeeMasterRepo.uRoledelte(9001)).thenReturn(roleMapping); + when(employeeMasterRepo.findByUSRMappingID(9001)).thenReturn(roleMapping); + when(employeeMasterRepo.save(roleMapping)).thenReturn(roleMapping); + + assertSame(user, service.editEmployee(USER_ID)); + assertSame(user, service.editData(USER_ID)); + assertSame(user, service.getProviderAdminForEdit(USER_ID)); + assertSame(demographics, service.mdedit(USER_ID)); + assertSame(demographics, service.DataByUserID(USER_ID)); + assertSame(demographics, service.saveeditedDemoData(demographics)); + assertSame(language, service.ulangmapedit(USER_ID, 1)); + assertSame(language, service.updateLangMapping(7001)); + assertSame(language, service.saveUserLangEditedData(language)); + assertSame(roleMapping, service.uRoleMedit(USER_ID, 11)); + assertSame(roleMapping, service.uRoledelte(9001)); + assertSame(roleMapping, service.getDataUsrId(9001)); + assertSame(roleMapping, service.saveRoleEdit(roleMapping)); + } + + @Test + @DisplayName("SaveDemographics should hand its batch to the repository") + void saveDemographics_shouldHandBatchToRepository() { + ArrayList stored = new ArrayList<>(); + when(m_UserDemographicsRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.SaveDemographics(new ArrayList<>())); + } + + @Test + @DisplayName("getProviderAdmin should hand back what the repository holds") + void getProviderAdmin_shouldHandBackRepositoryContents() { + ArrayList stored = new ArrayList<>(List.of(new M_User1())); + when(employeeMasterRepoo.getAllProviderAdminData()).thenReturn(stored); + + assertSame(stored, service.getProviderAdmin()); + } + } + + @Nested + @DisplayName("ResetPassword") + class ResetPasswordTests { + + @Test + @DisplayName("should report success when the credential service accepts the new password") + void resetPassword_shouldReportSuccess() { + OutputResponse accepted = new OutputResponse(); + accepted.setResponse("done"); + when(encryptUserPassword.encryptUserCredentials(any())).thenReturn(accepted); + + assertEquals("Password reset successfully", service.ResetPassword(new M_User1())); + } + + @Test + @DisplayName("should report failure when the credential service refuses the new password") + void resetPassword_shouldReportFailure() { + when(encryptUserPassword.encryptUserCredentials(any())).thenReturn(new OutputResponse()); + + assertEquals("Password Not Set Properly", service.ResetPassword(new M_User1())); + } + } + + @Nested + @DisplayName("getMappedRole") + class GetMappedRoleTests { + + @Test + @DisplayName("should split the stored village columns back into lists") + void getMappedRole_shouldSplitVillageColumns() { + V_Userservicerolemapping mapping = new V_Userservicerolemapping(); + mapping.setuSRMappingID(9001); + mapping.setServiceID(3); + mapping.setStateID(29); + mapping.setWorkingDistrictID("301"); + mapping.setVillageidDb("501,502"); + mapping.setVillageNameDb("Hosur,Devanahalli"); + when(v_UserservicerolemappingRepo.getAllRoleOfProvider(77)) + .thenReturn(new ArrayList<>(List.of(mapping))); + when(employeeMasterRepo.getFacilityInfoByMappingIDs(anyList())).thenReturn(new ArrayList<>()); + + V_Userservicerolemapping answered = service.getMappedRole(77).get(0); + + assertArrayEquals(new String[] { "501", "502" }, answered.getVillageID()); + assertArrayEquals(new String[] { "Hosur", "Devanahalli" }, answered.getVillageName()); + } + + @Test + @DisplayName("should clear the block and village details for a mapping with no service line") + void getMappedRole_shouldClearBlockAndVillageWithoutService() { + V_Userservicerolemapping mapping = new V_Userservicerolemapping(); + mapping.setuSRMappingID(9001); + mapping.setStateID(29); + mapping.setWorkingDistrictID("301"); + mapping.setVillageidDb("501"); + when(v_UserservicerolemappingRepo.getAllRoleOfProvider(77)) + .thenReturn(new ArrayList<>(List.of(mapping))); + when(employeeMasterRepo.getFacilityInfoByMappingIDs(anyList())).thenReturn(new ArrayList<>()); + + V_Userservicerolemapping answered = service.getMappedRole(77).get(0); + + assertNull(answered.getVillageID()); + assertNull(answered.getVillageidDb()); + assertNull(answered.getBlockID()); + } + + @Test + @DisplayName("should fill in the state and district the view could not resolve") + void getMappedRole_shouldFillInMissingStateAndDistrict() { + V_Userservicerolemapping mapping = new V_Userservicerolemapping(); + mapping.setuSRMappingID(9001); + mapping.setServiceID(3); + when(v_UserservicerolemappingRepo.getAllRoleOfProvider(77)) + .thenReturn(new ArrayList<>(List.of(mapping))); + when(employeeMasterRepo.getDirectStateDistrictByMappingIDs(anyList())) + .thenReturn(List.of(new Object[] { 9001, 29, "Karnataka", 301, "Bengaluru Urban", 401, "North" })); + when(employeeMasterRepo.getFacilityInfoByMappingIDs(anyList())).thenReturn(new ArrayList<>()); + + V_Userservicerolemapping answered = service.getMappedRole(77).get(0); + + assertEquals(29, answered.getStateID()); + assertEquals("Karnataka", answered.getStateName()); + assertEquals("301", answered.getWorkingDistrictID()); + assertEquals("North", answered.getBlockName()); + } + + @Test + @DisplayName("should attach the facility details the batch lookup resolves") + void getMappedRole_shouldAttachFacilityDetails() { + V_Userservicerolemapping mapping = new V_Userservicerolemapping(); + mapping.setuSRMappingID(9001); + mapping.setServiceID(3); + mapping.setStateID(29); + mapping.setWorkingDistrictID("301"); + when(v_UserservicerolemappingRepo.getAllRoleOfProvider(77)) + .thenReturn(new ArrayList<>(List.of(mapping))); + when(employeeMasterRepo.getFacilityInfoByMappingIDs(anyList())) + .thenReturn(List.of(new Object[] { 9001, 501, "PHC North", 3, "Rural" })); + + V_Userservicerolemapping answered = service.getMappedRole(77).get(0); + + assertEquals(501, answered.getFacilityID()); + assertEquals("PHC North", answered.getFacilityName()); + assertEquals("Rural", answered.getRuralUrban()); + } + + @Test + @DisplayName("should answer an empty list when the view holds nothing") + void getMappedRole_shouldAnswerEmptyListForEmptyView() { + when(v_UserservicerolemappingRepo.getAllRoleOfProvider(77)).thenReturn(null); + + assertTrue(service.getMappedRole(77).isEmpty()); + } + + @Test + @DisplayName("should search by user id when the caller sends no name") + void getMappedRole_shouldSearchByUserIdWithoutAName() { + ArrayList stored = new ArrayList<>(); + when(v_UserservicerolemappingRepo.getDataByUserID(USER_ID)).thenReturn(stored); + + assertSame(stored, service.getMappedRole("", USER_ID)); + } + + @Test + @DisplayName("should search by name when the caller sends no usable user id") + void getMappedRole_shouldSearchByNameWithoutAUserId() { + ArrayList stored = new ArrayList<>(); + when(v_UserservicerolemappingRepo.getDataByName("Asha Rao")).thenReturn(stored); + + assertSame(stored, service.getMappedRole("Asha Rao", 0)); + } + + @Test + @DisplayName("should refuse a search that names both a user and an id") + void getMappedRole_shouldRefuseAmbiguousSearch() { + assertThrows(DataNotFound.class, () -> service.getMappedRole("Asha Rao", USER_ID)); + } + } + + @Nested + @DisplayName("searchMappedLangugeByUserId") + class SearchMappedLanguageTests { + + @Test + @DisplayName("should answer the languages mapped to a real user") + void searchMappedLanguage_shouldAnswerMappedLanguages() { + ArrayList stored = new ArrayList<>(); + when(m_UserLangMappingRepo.getmappedlanguageData(USER_ID)).thenReturn(stored); + + assertSame(stored, service.searchMappedLangugeByUserId(USER_ID)); + } + + @Test + @DisplayName("should refuse a search that names no user") + void searchMappedLanguage_shouldRefuseSearchWithoutUser() { + assertThrows(DataNotFound.class, () -> service.searchMappedLangugeByUserId(0)); + } + } + + @Nested + @DisplayName("getMappedLanguge") + class GetMappedLanguageTests { + + @Test + @DisplayName("should rebuild one mapping per row the query answers") + void getMappedLanguge_shouldRebuildEachRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 7001, USER_ID, 1, 5, "Kannada", "asha.rao", true, true, true, "n", false, + 5, 5, 5, false }); + when(m_UserLangMappingRepo.getMappedLanguge(77)).thenReturn(rows); + + assertEquals(1, service.getMappedLanguge(77).size()); + } + } + + @Nested + @DisplayName("getEmployeeByDesiganationID") + class EmployeeByDesignationTests { + + @Test + @DisplayName("should mark a user whose signature is on file as active") + void byDesignation_shouldMarkActiveSignature() { + M_User1 user = new M_User1(); + user.setUserID(USER_ID); + EmployeeSignature signature = new EmployeeSignature(); + signature.setDeleted(Boolean.FALSE); + when(employeeMasterRepoo.getempByDesiganation(7, 77)).thenReturn(new ArrayList<>(List.of(user))); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(signature); + + assertEquals("Active", service.getEmployeeByDesiganationID(7, 77).get(0).getSignatureStatus()); + } + + @Test + @DisplayName("should mark a user whose signature has been retired as inactive") + void byDesignation_shouldMarkRetiredSignature() { + M_User1 user = new M_User1(); + user.setUserID(USER_ID); + EmployeeSignature signature = new EmployeeSignature(); + signature.setDeleted(Boolean.TRUE); + when(employeeMasterRepoo.getempByDesiganation(7, 77)).thenReturn(new ArrayList<>(List.of(user))); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(signature); + + assertEquals("InActive", service.getEmployeeByDesiganationID(7, 77).get(0).getSignatureStatus()); + } + + @Test + @DisplayName("should leave the signature status unset for a user with none on file") + void byDesignation_shouldLeaveStatusUnsetWithoutSignature() { + M_User1 user = new M_User1(); + user.setUserID(USER_ID); + when(employeeMasterRepoo.getempByDesiganation(7, 77)).thenReturn(new ArrayList<>(List.of(user))); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(null); + + assertNull(service.getEmployeeByDesiganationID(7, 77).get(0).getSignatureStatus()); + } + } + + @Nested + @DisplayName("getUserRoleTM") + class UserRoleTmTests { + + @Test + @DisplayName("should rebuild one role per row the query answers") + void getUserRoleTM_shouldRebuildEachRow() { + M_UserservicerolemappingForRoleProviderAdmin request = + new M_UserservicerolemappingForRoleProviderAdmin(); + request.setUserID(USER_ID); + request.setProviderServiceMapID(PSM_ID); + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { USER_ID, 11, "TC Specialist", false, 21, "Specialist screen", false }); + when(userservicerolemappingForRoleProviderAdminRepo.getroleofuserTM(USER_ID, PSM_ID)).thenReturn(rows); + + ArrayList roles = service.getUserRoleTM(request); + + assertEquals(1, roles.size()); + assertEquals("TC Specialist", roles.get(0).getRolename()); + } + + @Test + @DisplayName("should answer an empty list when the user holds no telemedicine role") + void getUserRoleTM_shouldAnswerEmptyListWithoutRoles() { + M_UserservicerolemappingForRoleProviderAdmin request = + new M_UserservicerolemappingForRoleProviderAdmin(); + when(userservicerolemappingForRoleProviderAdminRepo.getroleofuserTM(any(), any())) + .thenReturn(new ArrayList<>()); + + assertTrue(service.getUserRoleTM(request).isEmpty()); + } + } + + @Nested + @DisplayName("createAgent") + class CreateAgentTests { + + @Test + @DisplayName("should fill the agent and server placeholders into the configured URL") + void createAgent_shouldFillPlaceholders() { + com.iemr.admin.utils.config.ConfigProperties properties = + new com.iemr.admin.utils.config.ConfigProperties(); + service.setConfigProperties(properties); + + String url = service.createAgent("A-1", "asha.rao"); + + assertNotNull(url); + assertFalse(url.contains("AGENTID"), url); + } + } +} diff --git a/src/test/java/com/iemr/admin/service/employeemaster/EmployeeMasterSupportServicesTest.java b/src/test/java/com/iemr/admin/service/employeemaster/EmployeeMasterSupportServicesTest.java new file mode 100644 index 0000000..230e875 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/employeemaster/EmployeeMasterSupportServicesTest.java @@ -0,0 +1,556 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.employeemaster; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.employeemaster.AshaSupervisorMapping; +import com.iemr.admin.data.employeemaster.EmployeeSignature; +import com.iemr.admin.data.employeemaster.M_Designation; +import com.iemr.admin.data.employeemaster.M_UserServiceRoleMapping2; +import com.iemr.admin.data.employeemaster.USRAgentMapping; +import com.iemr.admin.data.store.M_Facility; +import com.iemr.admin.repo.employeemaster.EmployeeMasterRepo; +import com.iemr.admin.repo.employeemaster.EmployeeSignatureRepo; +import com.iemr.admin.repo.employeemaster.M_DesignationRepo; +import com.iemr.admin.repo.employeemaster.USRAgentMappingRepository; +import com.iemr.admin.repository.store.MainStoreRepo; +import com.iemr.admin.repository.user.AshaSupervisorMappingRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +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.anyList; +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; + +/** + * The supporting employee services: the supervisor mapping store, the signature + * store, the CTI agent-id pool and the designation master. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("Employee master support service Test Suite") +class EmployeeMasterSupportServicesTest { + + private static final Integer SUPERVISOR_ID = 3117; + private static final Integer ASHA_ID = 4001; + private static final Integer FACILITY_ID = 501; + + @Mock + private AshaSupervisorMappingRepo ashaSupervisorMappingRepo; + + @Mock + private EmployeeMasterRepo employeeMasterRepo; + + @Mock + private MainStoreRepo mainStoreRepo; + + @InjectMocks + private AshaSupervisorMappingServiceImpl ashaService; + + @Mock + private EmployeeSignatureRepo employeeSignatureRepo; + + @InjectMocks + private EmployeeSignatureServiceImpl signatureService; + + @Mock + private USRAgentMappingRepository usrAgentMappingRepository; + + @Mock + private M_DesignationRepo m_DesignationRepo; + + @InjectMocks + private M_DesignationImpl designationService; + + private static AshaSupervisorMapping mapping(Long id, Integer supervisorId, Integer ashaId) { + AshaSupervisorMapping mapping = new AshaSupervisorMapping(); + mapping.setId(id); + mapping.setSupervisorUserID(supervisorId); + mapping.setAshaUserID(ashaId); + mapping.setFacilityID(FACILITY_ID); + mapping.setCreatedBy("admin"); + return mapping; + } + + private void activeFacility() { + M_Facility facility = new M_Facility(); + facility.setFacilityID(FACILITY_ID); + when(mainStoreRepo.findByFacilityIDAndDeleted(FACILITY_ID, false)).thenReturn(facility); + } + + @Nested + @DisplayName("AshaSupervisorMappingServiceImpl") + class AshaSupervisorMappingTests { + + @Test + @DisplayName("saveAshaSupervisorMappings should refuse a mapping onto a retired facility") + void save_shouldRefuseRetiredFacility() { + when(mainStoreRepo.findByFacilityIDAndDeleted(FACILITY_ID, false)).thenReturn(null); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> ashaService.saveAshaSupervisorMappings(List.of(mapping(null, SUPERVISOR_ID, ASHA_ID)))); + + assertTrue(thrown.getMessage().contains("is no longer active"), thrown.getMessage()); + } + + @Test + @DisplayName("saveAshaSupervisorMappings should reuse a mapping that already exists") + void save_shouldReuseExistingMapping() { + activeFacility(); + AshaSupervisorMapping existing = mapping(1L, SUPERVISOR_ID, ASHA_ID); + when(ashaSupervisorMappingRepo + .findBySupervisorUserIDAndAshaUserIDAndFacilityIDAndDeletedFalse(SUPERVISOR_ID, ASHA_ID, + FACILITY_ID)) + .thenReturn(existing); + + ArrayList saved = + ashaService.saveAshaSupervisorMappings(List.of(mapping(null, SUPERVISOR_ID, ASHA_ID))); + + assertSame(existing, saved.get(0)); + verify(ashaSupervisorMappingRepo, never()).save(any()); + } + + @Test + @DisplayName("saveAshaSupervisorMappings should retire the ASHA's mapping under a different supervisor") + void save_shouldRetireMappingUnderOtherSupervisor() { + activeFacility(); + AshaSupervisorMapping other = mapping(2L, 3118, ASHA_ID); + AshaSupervisorMapping toSave = mapping(null, SUPERVISOR_ID, ASHA_ID); + when(ashaSupervisorMappingRepo + .findByAshaUserIDAndFacilityIDAndDeletedFalseAndSupervisorUserIDNot(ASHA_ID, FACILITY_ID, + SUPERVISOR_ID)) + .thenReturn(new ArrayList<>(List.of(other))); + when(ashaSupervisorMappingRepo.save(toSave)).thenReturn(toSave); + + ashaService.saveAshaSupervisorMappings(List.of(toSave)); + + assertTrue(other.getDeleted(), "an ASHA may report to only one supervisor at a facility"); + assertEquals("admin", other.getModifiedBy()); + verify(ashaSupervisorMappingRepo).save(other); + } + + @Test + @DisplayName("getSupervisorMappingByFacility should hand back what the repository holds") + void getByFacility_shouldHandBackRepositoryContents() { + ArrayList stored = new ArrayList<>(); + when(ashaSupervisorMappingRepo.findActiveMappingsByFacilityID(FACILITY_ID)).thenReturn(stored); + + assertSame(stored, ashaService.getSupervisorMappingByFacility(FACILITY_ID)); + } + + @Test + @DisplayName("getAshasByFacility should hand back what the repository holds") + void getAshas_shouldHandBackRepositoryContents() { + ArrayList stored = new ArrayList<>(); + when(employeeMasterRepo.findAshaUsersByFacilityIDs(anyList())).thenReturn(stored); + + assertSame(stored, ashaService.getAshasByFacility(List.of(FACILITY_ID))); + } + + @Test + @DisplayName("deleteMappings should retire each mapping it can resolve") + void deleteMappings_shouldRetireResolvedMappings() { + AshaSupervisorMapping stored = mapping(1L, SUPERVISOR_ID, ASHA_ID); + when(ashaSupervisorMappingRepo.findById(1L)).thenReturn(Optional.of(stored)); + when(ashaSupervisorMappingRepo.findById(2L)).thenReturn(Optional.empty()); + + ashaService.deleteMappings(List.of(1L, 2L), "admin"); + + assertTrue(stored.getDeleted()); + verify(ashaSupervisorMappingRepo).save(stored); + } + + @Test + @DisplayName("restoreMappings should reinstate each mapping it can resolve") + void restoreMappings_shouldReinstateResolvedMappings() { + AshaSupervisorMapping stored = mapping(1L, SUPERVISOR_ID, ASHA_ID); + stored.setDeleted(Boolean.TRUE); + when(ashaSupervisorMappingRepo.findById(1L)).thenReturn(Optional.of(stored)); + when(ashaSupervisorMappingRepo.findById(2L)).thenReturn(Optional.empty()); + + ashaService.restoreMappings(List.of(1L, 2L), "admin"); + + assertFalse(stored.getDeleted()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("deleteBySupervisorAndFacilities should retire every mapping at the named facilities") + void deleteBySupervisorAndFacilities_shouldRetireEveryMapping() { + AshaSupervisorMapping stored = mapping(1L, SUPERVISOR_ID, ASHA_ID); + when(ashaSupervisorMappingRepo + .findBySupervisorUserIDAndFacilityIDInAndDeletedFalse(SUPERVISOR_ID, List.of(FACILITY_ID))) + .thenReturn(new ArrayList<>(List.of(stored))); + + ashaService.deleteBySupervisorAndFacilities(SUPERVISOR_ID, List.of(FACILITY_ID), "admin"); + + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("cascadeDeleteByUserID should retire the user's mappings on both sides of the relationship") + void cascadeByUser_shouldRetireBothSides() { + AshaSupervisorMapping asSupervisor = mapping(1L, SUPERVISOR_ID, ASHA_ID); + AshaSupervisorMapping asAsha = mapping(2L, 3118, SUPERVISOR_ID); + when(ashaSupervisorMappingRepo.findBySupervisorUserIDAndDeletedFalse(SUPERVISOR_ID)) + .thenReturn(new ArrayList<>(List.of(asSupervisor))); + when(ashaSupervisorMappingRepo.findByAshaUserIDAndDeletedFalse(SUPERVISOR_ID)) + .thenReturn(new ArrayList<>(List.of(asAsha))); + + ashaService.cascadeDeleteByUserID(SUPERVISOR_ID, "admin"); + + assertTrue(asSupervisor.getDeleted()); + assertTrue(asAsha.getDeleted()); + } + + @Test + @DisplayName("cascadeDeleteByFacilityID should retire every mapping at the facility") + void cascadeByFacility_shouldRetireEveryMapping() { + AshaSupervisorMapping stored = mapping(1L, SUPERVISOR_ID, ASHA_ID); + when(ashaSupervisorMappingRepo.findByFacilityIDAndDeletedFalse(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(stored))); + + ashaService.cascadeDeleteByFacilityID(FACILITY_ID, "admin"); + + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("cascadeDeleteByUserIDAndFacilityID should retire only that user's mappings at that facility") + void cascadeByUserAndFacility_shouldRetireBothSidesAtFacility() { + AshaSupervisorMapping asSupervisor = mapping(1L, SUPERVISOR_ID, ASHA_ID); + AshaSupervisorMapping asAsha = mapping(2L, 3118, SUPERVISOR_ID); + when(ashaSupervisorMappingRepo + .findBySupervisorUserIDAndFacilityIDAndDeletedFalse(SUPERVISOR_ID, FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(asSupervisor))); + when(ashaSupervisorMappingRepo.findByAshaUserIDAndFacilityIDAndDeletedFalse(SUPERVISOR_ID, FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(asAsha))); + + ashaService.cascadeDeleteByUserIDAndFacilityID(SUPERVISOR_ID, FACILITY_ID, "admin"); + + assertTrue(asSupervisor.getDeleted()); + assertTrue(asAsha.getDeleted()); + } + + @Test + @DisplayName("updateAshaMappingsAtomically should retire the old mappings before saving the new ones") + void updateAtomically_shouldRetireThenSave() { + activeFacility(); + AshaSupervisorMapping old = mapping(1L, SUPERVISOR_ID, ASHA_ID); + AshaSupervisorMapping fresh = mapping(null, SUPERVISOR_ID, 4002); + when(ashaSupervisorMappingRepo + .findBySupervisorUserIDAndFacilityIDInAndDeletedFalse(SUPERVISOR_ID, List.of(FACILITY_ID))) + .thenReturn(new ArrayList<>(List.of(old))); + when(ashaSupervisorMappingRepo.save(fresh)).thenReturn(fresh); + + ArrayList saved = ashaService.updateAshaMappingsAtomically( + SUPERVISOR_ID, List.of(FACILITY_ID), List.of(fresh), "admin"); + + assertTrue(old.getDeleted()); + assertSame(fresh, saved.get(0)); + } + + @Test + @DisplayName("updateAshaMappingsAtomically should answer nothing when no new mappings are supplied") + void updateAtomically_shouldAnswerNothingWithoutNewMappings() { + when(ashaSupervisorMappingRepo + .findBySupervisorUserIDAndFacilityIDInAndDeletedFalse(anyInt(), anyList())) + .thenReturn(new ArrayList<>()); + + assertTrue(ashaService + .updateAshaMappingsAtomically(SUPERVISOR_ID, List.of(FACILITY_ID), null, "admin").isEmpty()); + } + } + + @Nested + @DisplayName("EmployeeSignatureServiceImpl") + class SignatureServiceTests { + + @Test + @DisplayName("uploadSignature should overwrite the signature already on file") + void upload_shouldOverwriteExistingSignature() { + EmployeeSignature existing = new EmployeeSignature(); + existing.setUserID(3117L); + existing.setUserSignatureID(9001L); + EmployeeSignature uploaded = new EmployeeSignature(); + uploaded.setUserID(3117L); + uploaded.setFileName("new.png"); + uploaded.setFileType("image/png"); + uploaded.setSignature(new byte[] { 1, 2, 3 }); + uploaded.setCreatedBy("admin"); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(existing); + when(employeeSignatureRepo.save(existing)).thenReturn(existing); + + assertEquals(9001L, signatureService.uploadSignature(uploaded)); + assertEquals("new.png", existing.getFileName()); + assertEquals("admin", existing.getModifiedBy()); + } + + @Test + @DisplayName("uploadSignature should store a first signature for a user who has none") + void upload_shouldStoreFirstSignature() { + EmployeeSignature uploaded = new EmployeeSignature(); + uploaded.setUserID(3117L); + uploaded.setUserSignatureID(9002L); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(null); + when(employeeSignatureRepo.save(uploaded)).thenReturn(uploaded); + + assertEquals(9002L, signatureService.uploadSignature(uploaded)); + } + + @Test + @DisplayName("fetchSignature should hand back what the repository holds") + void fetch_shouldHandBackRepositoryContents() { + EmployeeSignature stored = new EmployeeSignature(); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(stored); + + assertSame(stored, signatureService.fetchSignature(3117L)); + } + + @Test + @DisplayName("existSignature should report whether any signature is on file") + void exist_shouldReportWhetherSignatureIsOnFile() { + when(employeeSignatureRepo.countByUserIDAndSignatureNotNull(3117L)).thenReturn(1L); + when(employeeSignatureRepo.countByUserIDAndSignatureNotNull(3118L)).thenReturn(0L); + + assertTrue(signatureService.existSignature(3117L)); + assertFalse(signatureService.existSignature(3118L)); + } + + @Test + @DisplayName("isSignatureActive should report whether the signature is still in use") + void isActive_shouldReportWhetherSignatureIsInUse() { + when(employeeSignatureRepo.countByUserIDAndSignatureNotNullAndDeletedFalse(3117L)).thenReturn(1L); + when(employeeSignatureRepo.countByUserIDAndSignatureNotNullAndDeletedFalse(3118L)).thenReturn(0L); + + assertTrue(signatureService.isSignatureActive(3117L)); + assertFalse(signatureService.isSignatureActive(3118L)); + } + + @Test + @DisplayName("updateUserSignatureStatus should retire a signature the caller deactivates") + void updateStatus_shouldRetireDeactivatedSignature() { + EmployeeSignature stored = new EmployeeSignature(); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(stored); + when(employeeSignatureRepo.save(stored)).thenReturn(stored); + + signatureService.updateUserSignatureStatus("{\"userID\":3117,\"active\":false}"); + + assertTrue(stored.getDeleted()); + } + + @Test + @DisplayName("updateUserSignatureStatus should reinstate a signature the caller activates") + void updateStatus_shouldReinstateActivatedSignature() { + EmployeeSignature stored = new EmployeeSignature(); + stored.setDeleted(Boolean.TRUE); + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(stored); + when(employeeSignatureRepo.save(stored)).thenReturn(stored); + + signatureService.updateUserSignatureStatus("{\"userID\":3117,\"active\":true}"); + + assertFalse(stored.getDeleted()); + } + + @Test + @DisplayName("updateUserSignatureStatus should refuse a user who has no signature on file") + void updateStatus_shouldRefuseUserWithoutSignature() { + when(employeeSignatureRepo.findOneByUserID(3117L)).thenReturn(null); + + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, + () -> signatureService.updateUserSignatureStatus("{\"userID\":3117,\"active\":true}")); + + assertTrue(thrown.getMessage().contains("No signature found"), thrown.getMessage()); + } + } + + @Nested + @DisplayName("USRAgentMappingServiceImpl") + class UsrAgentMappingTests { + + private USRAgentMappingServiceImpl agentService() { + USRAgentMappingServiceImpl service = new USRAgentMappingServiceImpl(); + service.setUsrAgentMappingRepository(usrAgentMappingRepository); + return service; + } + + private Set agentRow() { + Set rows = new LinkedHashSet<>(); + rows.add(new Object[] { 1, 9001, null, 4001, null, "A-1", "secret", "104", Boolean.TRUE }); + rows.add(new Object[] { 2 }); + return rows; + } + + @Test + @DisplayName("getAvailableAgentIds should rebuild the free agents and skip an unusable row") + void getAvailableAgentIds_shouldRebuildFreeAgents() throws Exception { + when(usrAgentMappingRepository.getFreeAgentIds("104", 4001)).thenReturn(agentRow()); + + List agents = agentService() + .getAvailableAgentIds("{\"cti_CampaignName\":\"104\",\"providerServiceMapID\":4001}"); + + assertEquals(1, agents.size()); + assertEquals("A-1", agents.get(0).getAgentID()); + } + + @Test + @DisplayName("updateAgentIds should free the previous agent id before claiming the new one") + void updateAgentIds_shouldFreePreviousAgentId() throws Exception { + when(usrAgentMappingRepository.updateUSRMapping(any(), any(), any())).thenReturn(1); + + Integer changed = agentService().updateAgentIds("{\"oldAgentID\":\"A-0\"," + + "\"providerServiceMapID\":4001,\"isAvailable\":false,\"usrMappingID\":9001," + + "\"usrAgentMappingID\":1}"); + + assertEquals(1, changed); + verify(usrAgentMappingRepository).updateUSRMapping(true, null, "A-0", 4001); + } + + @Test + @DisplayName("updateAgentIds should leave the previous agent id alone when none is named") + void updateAgentIds_shouldLeavePreviousAgentIdAlone() throws Exception { + when(usrAgentMappingRepository.updateUSRMapping(any(), any(), any())).thenReturn(1); + + agentService().updateAgentIds("{\"isAvailable\":true,\"usrAgentMappingID\":1}"); + + verify(usrAgentMappingRepository, never()) + .updateUSRMapping(any(Boolean.class), any(), anyString(), anyInt()); + } + + @Test + @DisplayName("createUSRAgentMapping should skip an agent id the provider already holds") + void createUSRAgentMapping_shouldSkipExistingAgent() throws Exception { + when(usrAgentMappingRepository.getExistingAgent(4001, "A-1")).thenReturn(1L); + when(usrAgentMappingRepository.getExistingAgent(4001, "A-2")).thenReturn(0L); + when(usrAgentMappingRepository.save(any())).thenAnswer(call -> call.getArgument(0)); + + List created = agentService().createUSRAgentMapping( + "[{\"agentID\":\"A-1\",\"providerServiceMapID\":4001}," + + "{\"agentID\":\"A-2\",\"providerServiceMapID\":4001}]"); + + assertEquals(1, created.size()); + assertEquals("A-2", created.get(0).getAgentID()); + } + + @Test + @DisplayName("getAvailableCampaigns should hand back what the repository holds") + void getAvailableCampaigns_shouldHandBackRepositoryContents() throws Exception { + when(usrAgentMappingRepository.getAvailableCampaigns(4001)).thenReturn(List.of("104", "1097")); + + assertEquals(2, agentService().getAvailableCampaigns("{\"providerServiceMapID\":4001}").size()); + } + + @Test + @DisplayName("getAllAgentIds should look the agent up directly when the caller names one") + void getAllAgentIds_shouldLookUpNamedAgent() throws Exception { + when(usrAgentMappingRepository + .getUSRAgentMappingByAgentIDAndProviderServiceMapID("A-1", 4001)).thenReturn(agentRow()); + + List agents = agentService() + .getAllAgentIds("{\"agentID\":\"A-1\",\"providerServiceMapID\":4001}"); + + assertEquals(1, agents.size()); + } + + @Test + @DisplayName("getAllAgentIds should filter by availability when the caller asks for it") + void getAllAgentIds_shouldFilterByAvailability() throws Exception { + when(usrAgentMappingRepository.getAllAgentIds(4001, "104", true)).thenReturn(agentRow()); + + assertEquals(1, agentService().getAllAgentIds( + "{\"providerServiceMapID\":4001,\"cti_CampaignName\":\"104\",\"isAvailable\":true}").size()); + } + + @Test + @DisplayName("getAllAgentIds should filter by campaign alone when availability is not named") + void getAllAgentIds_shouldFilterByCampaignAlone() throws Exception { + when(usrAgentMappingRepository.getAllAgentId(4001, "104")).thenReturn(agentRow()); + + assertEquals(1, agentService() + .getAllAgentIds("{\"providerServiceMapID\":4001,\"cti_CampaignName\":\"104\"}").size()); + } + + @Test + @DisplayName("getAllAgentIds should answer every agent under the mapping when nothing is named") + void getAllAgentIds_shouldAnswerEveryAgent() throws Exception { + when(usrAgentMappingRepository.getAllAgentIds(4001)).thenReturn(agentRow()); + + assertEquals(1, agentService().getAllAgentIds("{\"providerServiceMapID\":4001}").size()); + } + + @Test + @DisplayName("updateCTICampaignNameMapping should answer how many mappings moved campaign") + void updateCTICampaignNameMapping_shouldAnswerChangedCount() throws Exception { + when(usrAgentMappingRepository.updateCTICampaignNameMapping("1097", 1)).thenReturn(1); + + assertEquals(1, agentService() + .updateCTICampaignNameMapping("{\"cti_CampaignName\":\"1097\",\"usrAgentMappingID\":1}")); + } + + @Test + @DisplayName("updateDeletedAgentIDStatus should free the agent id of a deleted user") + void updateDeletedAgentIDStatus_shouldFreeAgentId() { + when(usrAgentMappingRepository.updateDeletedAgentIDStatus("A-1")).thenReturn(1); + + agentService().updateDeletedAgentIDStatus("A-1"); + + verify(usrAgentMappingRepository).updateDeletedAgentIDStatus("A-1"); + } + } + + @Nested + @DisplayName("M_DesignationImpl") + class DesignationServiceTests { + + @Test + @DisplayName("getDesinationlist should hand back what the repository holds") + void getDesinationlist_shouldHandBackRepositoryContents() { + ArrayList stored = new ArrayList<>(List.of(new M_Designation())); + when(m_DesignationRepo.getDesinationlist()).thenReturn(stored); + + assertSame(stored, designationService.getDesinationlist()); + } + } +} diff --git a/src/test/java/com/iemr/admin/service/facilitytype/M_facilitytypeServiceImplTest.java b/src/test/java/com/iemr/admin/service/facilitytype/M_facilitytypeServiceImplTest.java new file mode 100644 index 0000000..8551a78 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/facilitytype/M_facilitytypeServiceImplTest.java @@ -0,0 +1,196 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.facilitytype; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.facilitytype.M_facilitytype; +import com.iemr.admin.data.store.M_FacilityLevel; +import com.iemr.admin.repository.facilitytype.M_FacilityLevelRepo; +import com.iemr.admin.repository.facilitytype.M_facilitytypeRepo; + +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.assertSame; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The facility type service keeps the kinds of health facility a state runs, + * refusing a type name a state already has. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("M_facilitytypeServiceImpl Test Suite") +class M_facilitytypeServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer STATE_ID = 29; + private static final Integer FACILITY_TYPE_ID = 12; + + @Mock + private M_facilitytypeRepo m_facilitytypeRepo; + + @Mock + private M_FacilityLevelRepo m_facilityLevelRepo; + + @InjectMocks + private M_facilitytypeServiceImpl service; + + private static M_facilitytype facilityType() { + M_facilitytype facilityType = new M_facilitytype(); + facilityType.setFacilityTypeID(FACILITY_TYPE_ID); + facilityType.setFacilityTypeName("Primary Health Centre"); + facilityType.setFacilityTypeCode("PHC"); + facilityType.setProviderServiceMapID(PSM_ID); + facilityType.setStateID(STATE_ID); + return facilityType; + } + + @Test + @DisplayName("getAllFicilityData should answer the facility types of the provider asked about") + void getAll_shouldAnswerProvidersFacilityTypes() { + ArrayList held = new ArrayList<>(List.of(facilityType())); + when(m_facilitytypeRepo.getAllFicilityData(PSM_ID)).thenReturn(held); + + assertSame(held, service.getAllFicilityData(PSM_ID)); + } + + @Test + @DisplayName("getFacilityTypesByRuralUrban should narrow the types to the setting asked for") + void getByRuralUrban_shouldNarrowToSetting() { + when(m_facilitytypeRepo.findByProviderServiceMapIDAndRuralUrban(PSM_ID, "Rural")) + .thenReturn(List.of(facilityType())); + + assertEquals(1, service.getFacilityTypesByRuralUrban(PSM_ID, "Rural").size()); + } + + @Test + @DisplayName("addAllFicilityData should store a facility type the state does not have yet") + void add_shouldStoreNewFacilityType() { + ArrayList stored = new ArrayList<>(List.of(facilityType())); + when(m_facilitytypeRepo.existsByFacilityTypeNameAndStateIDAndDeletedFalse(anyString(), anyInt())) + .thenReturn(false); + when(m_facilitytypeRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.addAllFicilityData(List.of(facilityType()))); + } + + @Test + @DisplayName("addAllFicilityData should refuse a facility type name the state already has") + void add_shouldRefuseDuplicateName() { + when(m_facilitytypeRepo.existsByFacilityTypeNameAndStateIDAndDeletedFalse("Primary Health Centre", + STATE_ID)).thenReturn(true); + + RuntimeException refusal = assertThrows(RuntimeException.class, + () -> service.addAllFicilityData(List.of(facilityType()))); + + assertTrue(refusal.getMessage().contains("Primary Health Centre"), refusal.getMessage()); + verify(m_facilitytypeRepo, never()).saveAll(anyList()); + } + + @Test + @DisplayName("editAllFicilityData and updateFacilityData should each reach their own repository query") + void singleRecordOperations_shouldReachTheirOwnQuery() { + M_facilitytype stored = facilityType(); + when(m_facilitytypeRepo.findByFacilityTypeID(FACILITY_TYPE_ID)).thenReturn(stored); + when(m_facilitytypeRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.editAllFicilityData(FACILITY_TYPE_ID)); + assertSame(stored, service.updateFacilityData(stored)); + } + + @Test + @DisplayName("editAllFicilityData should answer nothing when the facility type is unknown") + void edit_shouldAnswerNothingForUnknownFacilityType() { + when(m_facilitytypeRepo.findByFacilityTypeID(-1)).thenReturn(null); + + assertNull(service.editAllFicilityData(-1)); + } + + @Test + @DisplayName("checkFacilityTypeCode should report whether the provider already uses the code") + void checkCode_shouldReportWhetherCodeIsUsed() { + when(m_facilitytypeRepo.findByFacilityTypeCodeAndProviderServiceMapID("PHC", PSM_ID)) + .thenReturn(List.of(facilityType())); + assertTrue(service.checkFacilityTypeCode(facilityType())); + + when(m_facilitytypeRepo.findByFacilityTypeCodeAndProviderServiceMapID("PHC", PSM_ID)) + .thenReturn(new ArrayList<>()); + assertFalse(service.checkFacilityTypeCode(facilityType())); + } + + @Test + @DisplayName("getFacilityLevels should answer the levels still in use, named in order") + void getLevels_shouldAnswerLiveLevels() { + ArrayList levels = new ArrayList<>(List.of(new M_FacilityLevel())); + when(m_facilityLevelRepo.findByDeletedFalseOrderByLevelName()).thenReturn(levels); + + assertSame(levels, service.getFacilityLevels()); + } + + @Test + @DisplayName("getFacilityTypesByBlock should answer the types run in the taluk asked about") + void getByBlock_shouldAnswerTypesOfTaluk() { + when(m_facilitytypeRepo.findFacilityTypesByBlock(3011)).thenReturn(List.of(facilityType())); + + assertEquals(1, service.getFacilityTypesByBlock(3011).size()); + } + + @Test + @DisplayName("getFacilityTypesByState should answer the types run in the state asked about") + void getByState_shouldAnswerTypesOfState() { + when(m_facilitytypeRepo.findByStateID(STATE_ID)).thenReturn(List.of(facilityType())); + + assertEquals(1, service.getFacilityTypesByState(STATE_ID).size()); + } + + @Test + @DisplayName("checkFacilityTypeNameExists should report whether the state already has the name") + void checkName_shouldReportWhetherNameIsUsed() { + when(m_facilitytypeRepo.existsByFacilityTypeNameAndStateIDAndDeletedFalse("Primary Health Centre", + STATE_ID)).thenReturn(true); + assertTrue(service.checkFacilityTypeNameExists("Primary Health Centre", STATE_ID)); + + when(m_facilitytypeRepo.existsByFacilityTypeNameAndStateIDAndDeletedFalse("Sub Centre", STATE_ID)) + .thenReturn(false); + assertFalse(service.checkFacilityTypeNameExists("Sub Centre", STATE_ID)); + } +} diff --git a/src/test/java/com/iemr/admin/service/foetalmonitormaster/FoetalMonitorServiceImplTest.java b/src/test/java/com/iemr/admin/service/foetalmonitormaster/FoetalMonitorServiceImplTest.java new file mode 100644 index 0000000..6731490 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/foetalmonitormaster/FoetalMonitorServiceImplTest.java @@ -0,0 +1,420 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.foetalmonitormaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.foetalmonitormaster.FoetalMonitorDeviceID; +import com.iemr.admin.data.foetalmonitormaster.M_FoetalMonitor; +import com.iemr.admin.repo.foetalmonitormaster.FoetalMonitorDeviceIDRepo; +import com.iemr.admin.repo.foetalmonitormaster.FoetalMonitorRepository; +import com.iemr.admin.repository.vanMaster.VanMasterRepository; +import com.iemr.admin.utils.exception.IEMRException; + +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.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * A fetosense device is attached to one van at a time, so the mapping rules here + * decide whether a foetal monitor reading can be traced back to the van it was + * taken in. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("FoetalMonitorServiceImpl Test Suite") +class FoetalMonitorServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer VAN_ID = 71; + private static final Long VFD_ID = 9001L; + + @Mock + private FoetalMonitorRepository foetalMonitorRepository; + + @Mock + private VanMasterRepository masterVanRepo; + + @Mock + private FoetalMonitorDeviceIDRepo foetalMonitorDeviceIDRepo; + + @InjectMocks + private FoetalMonitorServiceImpl service; + + private static M_FoetalMonitor test(Integer id, String name) { + M_FoetalMonitor test = new M_FoetalMonitor(); + test.setFoetalMonitorTestID(id); + test.setTestName(name); + return test; + } + + private static FoetalMonitorDeviceID device() { + FoetalMonitorDeviceID device = new FoetalMonitorDeviceID(); + device.setVfdID(VFD_ID); + device.setDeviceID("FS-1"); + device.setDeviceName("Fetosense 1"); + device.setVanID(VAN_ID); + device.setVanTypeID(1); + device.setParkingPlaceID(31); + device.setVanName("MMU Van 1"); + device.setProviderServiceMapID(PSM_ID); + device.setCreatedBy("admin"); + device.setDeactivated(Boolean.FALSE); + device.setDeleted(Boolean.FALSE); + return device; + } + + @Test + @DisplayName("createFoetalMonitorTestMaster should publish the tests it stored") + void createTestMaster_shouldPublishStoredTests() throws Exception { + when(foetalMonitorRepository.saveAll(anyList())).thenReturn(List.of(test(11, "Non stress test"))); + + String created = service.createFoetalMonitorTestMaster("[{\"testName\":\"Non stress test\"}]"); + + assertTrue(created.contains("Non stress test"), created); + } + + @Test + @DisplayName("createFoetalMonitorTestMaster should answer nothing when it stored fewer than it was given") + void createTestMaster_shouldAnswerNothingOnPartialStore() throws Exception { + when(foetalMonitorRepository.saveAll(anyList())).thenReturn(new ArrayList()); + + assertNull(service.createFoetalMonitorTestMaster("[{\"testName\":\"Non stress test\"}]")); + } + + @Test + @DisplayName("getFoetalMonitorTestMaster should publish the tests of the provider") + void getTestMaster_shouldPublishProviderTests() { + when(foetalMonitorRepository.getByProviderServiceMapID(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(test(11, "Non stress test")))); + + assertTrue(service.getFoetalMonitorTestMaster(PSM_ID).contains("Non stress test")); + } + + @Test + @DisplayName("updateFoetalMonitorTestMaster should publish the test once the edit lands") + void updateTestMaster_shouldPublishEditedTest() { + when(foetalMonitorRepository.updateFoetalMonitorDetails(11, "Non stress test", null, "admin")).thenReturn(1); + when(foetalMonitorRepository.getByFoetalMonitorTestID(11)).thenReturn(test(11, "Non stress test")); + + String published = service.updateFoetalMonitorTestMaster( + "{\"foetalMonitorTestID\":11,\"testName\":\"Non stress test\",\"modifiedBy\":\"admin\"}"); + + assertTrue(published.contains("Non stress test"), published); + } + + @Test + @DisplayName("updateFoetalMonitorTestMaster should answer nothing for a request that names no test") + void updateTestMaster_shouldAnswerNothingWithoutTest() { + assertNull(service.updateFoetalMonitorTestMaster("{\"testName\":\"Non stress test\"}")); + } + + @Test + @DisplayName("updateFoetalMonitorTestMaster should answer nothing when the edit changed nothing") + void updateTestMaster_shouldAnswerNothingWhenNothingChanged() { + when(foetalMonitorRepository.updateFoetalMonitorDetails(anyInt(), any(), any(), any())).thenReturn(0); + + assertNull(service.updateFoetalMonitorTestMaster("{\"foetalMonitorTestID\":11}")); + } + + @Test + @DisplayName("updateFoetalMonitorTestMasterStatus should publish the test once its status has changed") + void updateTestStatus_shouldPublishChangedTest() throws Exception { + when(foetalMonitorRepository.updateFoetalMonitorStatus(11, true)).thenReturn(1); + when(foetalMonitorRepository.getByFoetalMonitorTestID(11)).thenReturn(test(11, "Non stress test")); + + assertTrue(service.updateFoetalMonitorTestMasterStatus(11, true).contains("Non stress test")); + } + + @Test + @DisplayName("updateFoetalMonitorTestMasterStatus should answer nothing when no test changed") + void updateTestStatus_shouldAnswerNothingWhenNothingChanged() throws Exception { + when(foetalMonitorRepository.updateFoetalMonitorStatus(11, true)).thenReturn(0); + + assertNull(service.updateFoetalMonitorTestMasterStatus(11, true)); + } + + @Test + @DisplayName("saveFoetalMonitorDeviceID should report the devices it stored") + void saveDeviceID_shouldReportStoredDevices() throws Exception { + when(foetalMonitorDeviceIDRepo.saveAll(anyList())) + .thenReturn(new ArrayList<>(List.of(device()))); + + assertEquals(1, service.saveFoetalMonitorDeviceID(new ArrayList<>(List.of(device())))); + } + + @Test + @DisplayName("saveFoetalMonitorDeviceID should refuse a run that stored nothing") + void saveDeviceID_shouldRefuseEmptyStore() { + when(foetalMonitorDeviceIDRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertThrows(IEMRException.class, + () -> service.saveFoetalMonitorDeviceID(new ArrayList<>(List.of(device())))); + } + + @Test + @DisplayName("getFoetalMonitorDeviceID should publish the devices of the provider") + void getDeviceID_shouldPublishProviderDevices() throws Exception { + when(foetalMonitorDeviceIDRepo.getFoetalMonitorDeviceID(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(device()))); + + String published = service.getFoetalMonitorDeviceID(device()); + + assertTrue(published.contains("fetosenseDeviceIDs"), published); + assertTrue(published.contains("FS-1"), published); + } + + @Test + @DisplayName("getFoetalMonitorDeviceID should report a lookup it could not run") + void getDeviceID_shouldReportFailedLookup() { + when(foetalMonitorDeviceIDRepo.getFoetalMonitorDeviceID(anyInt())) + .thenThrow(new IllegalStateException("no connection")); + + assertThrows(IEMRException.class, () -> service.getFoetalMonitorDeviceID(device())); + } + + @Test + @DisplayName("deleteFoetalMonitorDeviceID should release the van the device was attached to") + void deleteDeviceID_shouldReleaseVan() throws Exception { + FoetalMonitorDeviceID request = device(); + when(foetalMonitorDeviceIDRepo.save(request)).thenReturn(request); + when(masterVanRepo.updateVanFoetalMonitorsmapping(true, VAN_ID)).thenReturn(1); + + assertEquals(1, service.deleteFoetalMonitorDeviceID(request)); + verify(masterVanRepo).updateVanFoetalMonitorsmapping(true, VAN_ID); + } + + @Test + @DisplayName("deleteFoetalMonitorDeviceID should mark the van unmapped when the device is retired") + void deleteDeviceID_shouldMarkVanUnmappedOnRetirement() throws Exception { + FoetalMonitorDeviceID request = device(); + request.setDeleted(Boolean.TRUE); + when(foetalMonitorDeviceIDRepo.save(request)).thenReturn(request); + when(masterVanRepo.updateVanFoetalMonitorsmapping(false, VAN_ID)).thenReturn(1); + + assertEquals(1, service.deleteFoetalMonitorDeviceID(request)); + verify(masterVanRepo).updateVanFoetalMonitorsmapping(false, VAN_ID); + } + + @Test + @DisplayName("deleteFoetalMonitorDeviceID should refuse a device whose van could not be released") + void deleteDeviceID_shouldRefuseWhenVanCannotBeReleased() { + FoetalMonitorDeviceID request = device(); + when(foetalMonitorDeviceIDRepo.save(request)).thenReturn(request); + when(masterVanRepo.updateVanFoetalMonitorsmapping(anyBoolean(), anyInt())).thenReturn(0); + + assertThrows(IEMRException.class, () -> service.deleteFoetalMonitorDeviceID(request)); + } + + @Test + @DisplayName("deleteFoetalMonitorDeviceID should leave the vans alone for a device attached to none") + void deleteDeviceID_shouldLeaveVansAloneForUnattachedDevice() throws Exception { + FoetalMonitorDeviceID request = device(); + request.setVanID(null); + when(foetalMonitorDeviceIDRepo.save(request)).thenReturn(request); + + assertEquals(1, service.deleteFoetalMonitorDeviceID(request)); + verify(masterVanRepo, never()).updateVanFoetalMonitorsmapping(anyBoolean(), anyInt()); + } + + @Test + @DisplayName("getvanIDAndFoetalMonitorDeviceID should publish the vans and devices still free to pair") + void getVanAndDevice_shouldPublishFreePairs() throws Exception { + ArrayList vanRows = new ArrayList<>(); + vanRows.add(new Object[] { VAN_ID, "MMU Van 1", "KA-01-AB-1234" }); + when(masterVanRepo.getVanIDNotMappedWithDevice(1, 31, PSM_ID)).thenReturn(vanRows); + when(foetalMonitorDeviceIDRepo.getFoetalMonitorDeviceIDNotMapped(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(device()))); + + String published = service.getvanIDAndFoetalMonitorDeviceID(device()); + + assertTrue(published.contains("VanIDs"), published); + assertTrue(published.contains("MMU Van 1"), published); + assertTrue(published.contains("deviceIDs"), published); + } + + @Test + @DisplayName("getvanIDAndFoetalMonitorDeviceID should report a lookup it could not run") + void getVanAndDevice_shouldReportFailedLookup() { + when(masterVanRepo.getVanIDNotMappedWithDevice(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertThrows(IEMRException.class, () -> service.getvanIDAndFoetalMonitorDeviceID(device())); + } + + @Test + @DisplayName("vanIDAndDeviceIDMapping should attach the device and mark the van as carrying one") + void mapping_shouldAttachDeviceAndMarkVan() throws Exception { + when(foetalMonitorDeviceIDRepo.createMappingOfVanIDAndDeviceID(anyInt(), anyInt(), anyInt(), anyString(), + anyString(), anyString())).thenReturn(1); + when(masterVanRepo.updateVanFoetalMonitorsmapping(true, VAN_ID)).thenReturn(1); + + assertEquals(1, service.vanIDAndDeviceIDMapping(device())); + } + + @Test + @DisplayName("vanIDAndDeviceIDMapping should refuse a pairing the van could not be marked for") + void mapping_shouldRefuseWhenVanCannotBeMarked() { + when(foetalMonitorDeviceIDRepo.createMappingOfVanIDAndDeviceID(anyInt(), anyInt(), anyInt(), anyString(), + anyString(), anyString())).thenReturn(1); + when(masterVanRepo.updateVanFoetalMonitorsmapping(anyBoolean(), anyInt())).thenReturn(0); + + assertThrows(IEMRException.class, () -> service.vanIDAndDeviceIDMapping(device())); + } + + @Test + @DisplayName("vanIDAndDeviceIDMapping should refuse a pairing the device could not take") + void mapping_shouldRefuseWhenDeviceCannotTakePairing() { + when(foetalMonitorDeviceIDRepo.createMappingOfVanIDAndDeviceID(anyInt(), anyInt(), anyInt(), anyString(), + anyString(), anyString())).thenReturn(0); + + assertThrows(IEMRException.class, () -> service.vanIDAndDeviceIDMapping(device())); + } + + @Test + @DisplayName("updateFoetalMonitorDeviceID should report the device it saved") + void updateDeviceID_shouldReportSavedDevice() throws Exception { + FoetalMonitorDeviceID request = device(); + when(foetalMonitorDeviceIDRepo.save(request)).thenReturn(request); + + assertEquals(1, service.updateFoetalMonitorDeviceID(request)); + } + + @Test + @DisplayName("updateFoetalMonitorDeviceID should refuse an edit the store did not take") + void updateDeviceID_shouldRefuseUntakenEdit() { + when(foetalMonitorDeviceIDRepo.save(any())).thenReturn(null); + + assertThrows(IEMRException.class, () -> service.updateFoetalMonitorDeviceID(device())); + } + + @Test + @DisplayName("getVanIDMappingWorklist should publish the pairings on record") + void getWorklist_shouldPublishPairings() throws Exception { + when(foetalMonitorDeviceIDRepo.getMappedWorklist(1, 31, PSM_ID)) + .thenReturn(new ArrayList<>(List.of(device()))); + + assertTrue(service.getVanIDMappingWorklist(device()).contains("FS-1")); + } + + @Test + @DisplayName("getVanIDMappingWorklist should report a lookup it could not run") + void getWorklist_shouldReportFailedLookup() { + when(foetalMonitorDeviceIDRepo.getMappedWorklist(any(), any(), any())) + .thenThrow(new IllegalStateException("no connection")); + + assertThrows(IEMRException.class, () -> service.getVanIDMappingWorklist(device())); + } + + @Test + @DisplayName("updatingvanIDAndDeviceIDMapping should clear the old van before attaching the new one") + void updateMapping_shouldClearOldVanFirst() throws Exception { + when(foetalMonitorDeviceIDRepo.updateVanDetailsToNull(VFD_ID)).thenReturn(1); + when(foetalMonitorDeviceIDRepo.createMappingOfVanIDAndDeviceID(anyInt(), anyInt(), anyInt(), anyString(), + any(), anyString())).thenReturn(1); + + assertEquals(1, service.updatingvanIDAndDeviceIDMapping(device())); + verify(foetalMonitorDeviceIDRepo).updateVanDetailsToNull(VFD_ID); + } + + @Test + @DisplayName("updatingvanIDAndDeviceIDMapping should refuse an edit whose old van could not be cleared") + void updateMapping_shouldRefuseWhenOldVanCannotBeCleared() { + when(foetalMonitorDeviceIDRepo.updateVanDetailsToNull(VFD_ID)).thenReturn(0); + + assertThrows(IEMRException.class, () -> service.updatingvanIDAndDeviceIDMapping(device())); + } + + @Test + @DisplayName("updatingvanIDAndDeviceIDMapping should refuse an edit the new van could not take") + void updateMapping_shouldRefuseWhenNewVanCannotTake() { + when(foetalMonitorDeviceIDRepo.updateVanDetailsToNull(VFD_ID)).thenReturn(1); + when(foetalMonitorDeviceIDRepo.createMappingOfVanIDAndDeviceID(anyInt(), anyInt(), anyInt(), anyString(), + any(), anyString())).thenReturn(0); + + assertThrows(IEMRException.class, () -> service.updatingvanIDAndDeviceIDMapping(device())); + } + + @Test + @DisplayName("deleteVanIDAndDeviceIDMapping should release the pairing and free the van") + void deleteMapping_shouldReleasePairingAndFreeVan() throws Exception { + FoetalMonitorDeviceID request = device(); + request.setDeactivated(Boolean.TRUE); + when(foetalMonitorDeviceIDRepo.deleteMapping(true, VFD_ID)).thenReturn(1); + when(masterVanRepo.updateVanFoetalMonitorsmapping(false, VAN_ID)).thenReturn(1); + + assertEquals(1, service.deleteVanIDAndDeviceIDMapping(request)); + } + + @Test + @DisplayName("deleteVanIDAndDeviceIDMapping should refuse to reinstate a van another device already holds") + void deleteMapping_shouldRefuseVanHeldByAnotherDevice() { + FoetalMonitorDeviceID request = device(); + request.setDeactivated(Boolean.FALSE); + when(foetalMonitorDeviceIDRepo.getMappedVanDetails(VAN_ID)).thenReturn(1); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> service.deleteVanIDAndDeviceIDMapping(request)); + assertTrue(thrown.getMessage().contains("already mapped with a device"), thrown.getMessage()); + } + + @Test + @DisplayName("deleteVanIDAndDeviceIDMapping should reinstate a pairing for a van that is free") + void deleteMapping_shouldReinstatePairingForFreeVan() throws Exception { + FoetalMonitorDeviceID request = device(); + request.setDeactivated(Boolean.FALSE); + when(foetalMonitorDeviceIDRepo.getMappedVanDetails(VAN_ID)).thenReturn(0); + when(foetalMonitorDeviceIDRepo.deleteMapping(false, VFD_ID)).thenReturn(1); + when(masterVanRepo.updateVanFoetalMonitorsmapping(true, VAN_ID)).thenReturn(1); + + assertEquals(1, service.deleteVanIDAndDeviceIDMapping(request)); + } + + @Test + @DisplayName("deleteVanIDAndDeviceIDMapping should refuse a pairing the store would not change") + void deleteMapping_shouldRefuseUnchangedPairing() { + FoetalMonitorDeviceID request = device(); + request.setDeactivated(Boolean.TRUE); + when(foetalMonitorDeviceIDRepo.deleteMapping(anyBoolean(), any())).thenReturn(0); + + assertThrows(IEMRException.class, () -> service.deleteVanIDAndDeviceIDMapping(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/health/HealthServiceTest.java b/src/test/java/com/iemr/admin/service/health/HealthServiceTest.java new file mode 100644 index 0000000..b2e8176 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/health/HealthServiceTest.java @@ -0,0 +1,472 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.health; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import javax.sql.DataSource; + +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * The health endpoint is what the deployment's monitoring watches, so it has to + * tell a database that is merely slow apart from one that is unreachable. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("HealthService Test Suite") +class HealthServiceTest { + + @Mock + private DataSource dataSource; + + @Mock + private RedisConnectionFactory redisConnectionFactory; + + @Mock + private Connection connection; + + @Mock + private Statement statement; + + @Mock + private RedisConnection redisConnection; + + @SuppressWarnings("unchecked") + private static ObjectProvider providerOf(T value) { + ObjectProvider provider = mock(ObjectProvider.class); + when(provider.getIfAvailable()).thenReturn(value); + return provider; + } + + /** + * Builds the service without a data source so its constructor starts no background + * cycle, then attaches the stores under test. That keeps the diagnostics the tests + * drive from racing a scheduled cycle over the same mocks. + */ + private HealthService serviceWith(DataSource ds, RedisConnectionFactory redis) { + HealthService service = new HealthService(providerOf(null), providerOf(redis)); + ReflectionTestUtils.setField(service, "dataSource", ds); + ((AtomicLong) ReflectionTestUtils.getField(service, "lastDiagnosticRunAt")).set(0); + return service; + } + + private void databaseAnswers() throws SQLException { + when(dataSource.getConnection()).thenReturn(connection); + when(connection.createStatement()).thenReturn(statement); + } + + private void redisAnswers() { + when(redisConnectionFactory.getConnection()).thenReturn(redisConnection); + } + + private static ResultSet countingResultSet(int count) throws SQLException { + ResultSet rs = mock(ResultSet.class); + when(rs.next()).thenReturn(true); + when(rs.getInt("cnt")).thenReturn(count); + return rs; + } + + private static ResultSet statusResultSet(long value) throws SQLException { + ResultSet rs = mock(ResultSet.class); + when(rs.next()).thenReturn(true); + when(rs.getLong("Value")).thenReturn(value); + when(rs.getInt("Value")).thenReturn((int) value); + return rs; + } + + @SuppressWarnings("unchecked") + private static String statusOf(Map health, String service) { + return (String) ((Map) health.get(service)).get("status"); + } + + @SuppressWarnings("unchecked") + private static String severityOf(Map health, String service) { + return (String) ((Map) health.get(service)).get("severity"); + } + + @Nested + @DisplayName("checkHealth") + class CheckHealthTests { + + @Test + @DisplayName("should report the deployment up when both stores answer") + void checkHealth_shouldReportUpWhenBothStoresAnswer() throws Exception { + databaseAnswers(); + redisAnswers(); + HealthService service = serviceWith(dataSource, redisConnectionFactory); + + Map health = service.checkHealth(); + + assertEquals("UP", health.get("status")); + assertEquals("UP", statusOf(health, "mysql")); + assertEquals("UP", statusOf(health, "redis")); + assertNotNull(health.get("checkedAt")); + } + + @Test + @DisplayName("should report the deployment down when the database cannot be reached") + void checkHealth_shouldReportDownWhenDatabaseUnreachable() throws Exception { + when(dataSource.getConnection()).thenThrow(new SQLException("connection refused")); + redisAnswers(); + HealthService service = serviceWith(dataSource, redisConnectionFactory); + + Map health = service.checkHealth(); + + assertEquals("DOWN", health.get("status")); + assertEquals("DOWN", statusOf(health, "mysql")); + assertEquals("CRITICAL", severityOf(health, "mysql")); + } + + @Test + @DisplayName("should report the deployment down when Redis cannot be reached") + void checkHealth_shouldReportDownWhenRedisUnreachable() throws Exception { + databaseAnswers(); + when(redisConnectionFactory.getConnection()) + .thenThrow(new IllegalStateException("connection refused")); + HealthService service = serviceWith(dataSource, redisConnectionFactory); + + Map health = service.checkHealth(); + + assertEquals("DOWN", health.get("status")); + assertEquals("DOWN", statusOf(health, "redis")); + } + + @Test + @DisplayName("should report a store that is not configured rather than call it down") + void checkHealth_shouldReportUnconfiguredStore() { + HealthService service = serviceWith(null, null); + + Map health = service.checkHealth(); + + assertEquals("UP", health.get("status"), + "a deployment without these stores is not itself unhealthy"); + assertEquals("NOT_CONFIGURED", statusOf(health, "mysql")); + assertEquals("INFO", severityOf(health, "mysql")); + assertEquals("NOT_CONFIGURED", statusOf(health, "redis")); + } + + @Test + @DisplayName("should report a degraded database as still up but flagged") + void checkHealth_shouldReportDegradedDatabase() throws Exception { + databaseAnswers(); + redisAnswers(); + HealthService service = serviceWith(dataSource, redisConnectionFactory); + ReflectionTestUtils.invokeMethod( + ReflectionTestUtils.getField(service, "cachedDbSeverity"), "set", "WARNING"); + + Map health = service.checkHealth(); + + assertEquals("UP", health.get("status"), "a degraded database is still serving requests"); + assertEquals("DEGRADED", statusOf(health, "mysql")); + } + + @Test + @DisplayName("should report a critically degraded database as down") + void checkHealth_shouldReportCriticalDatabaseAsDown() throws Exception { + databaseAnswers(); + redisAnswers(); + HealthService service = serviceWith(dataSource, redisConnectionFactory); + ReflectionTestUtils.invokeMethod( + ReflectionTestUtils.getField(service, "cachedDbSeverity"), "set", "CRITICAL"); + + assertEquals("DOWN", service.checkHealth().get("status")); + } + } + + @Nested + @DisplayName("Background diagnostics") + class DiagnosticTests { + + private HealthService diagnosingService() throws SQLException { + databaseAnswers(); + return serviceWith(dataSource, redisConnectionFactory); + } + + private String severityAfterDiagnostics(HealthService service) { + ((AtomicLong) ReflectionTestUtils.getField(service, "lastDiagnosticRunAt")).set(0); + ReflectionTestUtils.invokeMethod(service, "runAdvancedMySQLDiagnostics"); + return (String) ReflectionTestUtils.invokeMethod( + ReflectionTestUtils.getField(service, "cachedDbSeverity"), "get"); + } + + private void everyCheckClean() throws SQLException { + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + } + + @Test + @DisplayName("should report a healthy database when every check is clean") + void diagnostics_shouldReportHealthyDatabase() throws Exception { + HealthService service = diagnosingService(); + everyCheckClean(); + + assertEquals("OK", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should warn when more processes are stuck than the threshold allows") + void diagnostics_shouldWarnOnStuckProcesses() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("PROCESSLIST")) { + return countingResultSet(10); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("WARNING", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should stay healthy when a few processes are stuck but under the threshold") + void diagnostics_shouldStayHealthyUnderStuckThreshold() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("PROCESSLIST")) { + return countingResultSet(2); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("OK", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should escalate to critical when several transactions run long") + void diagnostics_shouldEscalateOnManyLongTransactions() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("INNODB_TRX")) { + return countingResultSet(6); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("CRITICAL", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should warn when a single transaction runs long") + void diagnostics_shouldWarnOnOneLongTransaction() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("INNODB_TRX")) { + return countingResultSet(2); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("WARNING", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should warn when new deadlocks have happened since the previous cycle") + void diagnostics_shouldWarnOnNewDeadlocks() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("Innodb_deadlocks")) { + return statusResultSet(3); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("WARNING", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should warn when new slow queries have been logged since the previous cycle") + void diagnostics_shouldWarnOnNewSlowQueries() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("Slow_queries")) { + return statusResultSet(7); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("WARNING", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should escalate to critical when the connection pool is nearly exhausted") + void diagnostics_shouldEscalateOnExhaustedPool() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("Threads_connected")) { + return statusResultSet(490); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("CRITICAL", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should warn when connection usage is high but not yet exhausted") + void diagnostics_shouldWarnOnHighConnectionUsage() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenAnswer(call -> { + String sql = call.getArgument(0); + if (sql.contains("Threads_connected")) { + return statusResultSet(450); + } + if (sql.contains("max_connections")) { + return statusResultSet(500); + } + if (sql.startsWith("SHOW")) { + return statusResultSet(0); + } + return countingResultSet(0); + }); + + assertEquals("WARNING", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should report critical when it cannot open a connection to diagnose at all") + void diagnostics_shouldReportCriticalWhenConnectionCannotBeOpened() throws Exception { + HealthService service = diagnosingService(); + when(dataSource.getConnection()).thenThrow(new SQLException("connection refused")); + + assertEquals("CRITICAL", severityAfterDiagnostics(service)); + } + + @Test + @DisplayName("should keep the previous verdict when a cycle is asked for too soon") + void diagnostics_shouldKeepPreviousVerdictWhenAskedTooSoon() throws Exception { + HealthService service = diagnosingService(); + everyCheckClean(); + severityAfterDiagnostics(service); + ReflectionTestUtils.invokeMethod( + ReflectionTestUtils.getField(service, "cachedDbSeverity"), "set", "WARNING"); + ReflectionTestUtils.invokeMethod(service, "runAdvancedMySQLDiagnostics"); + + assertEquals("WARNING", + ReflectionTestUtils.invokeMethod( + ReflectionTestUtils.getField(service, "cachedDbSeverity"), "get"), + "a cycle inside the guard window must not overwrite the standing verdict"); + } + + @Test + @DisplayName("should survive a check whose query the database refuses") + void diagnostics_shouldSurviveRefusedQuery() throws Exception { + HealthService service = diagnosingService(); + when(statement.executeQuery(anyString())).thenThrow(new SQLException("access denied")); + + assertEquals("OK", severityAfterDiagnostics(service), + "a check that cannot run must not by itself condemn the database"); + } + } + + @Test + @DisplayName("shutdownDiagnostics should stop the background cycle") + void shutdownDiagnostics_shouldStopBackgroundCycle() throws Exception { + databaseAnswers(); + HealthService service = new HealthService(providerOf(dataSource), providerOf(redisConnectionFactory)); + + service.shutdownDiagnostics(); + + java.util.concurrent.ExecutorService scheduler = (java.util.concurrent.ExecutorService) + ReflectionTestUtils.getField(service, "diagnosticScheduler"); + assertEquals(true, scheduler.isShutdown()); + } +} diff --git a/src/test/java/com/iemr/admin/service/item/ItemServiceImplTest.java b/src/test/java/com/iemr/admin/service/item/ItemServiceImplTest.java new file mode 100644 index 0000000..4fa9c95 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/item/ItemServiceImplTest.java @@ -0,0 +1,284 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.item; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.items.CodeChecker; +import com.iemr.admin.data.items.ItemMaster; +import com.iemr.admin.data.items.M_ItemCategory; +import com.iemr.admin.data.items.M_ItemForm; +import com.iemr.admin.data.items.M_Route; +import com.iemr.admin.repository.item.ItemCategoryRepo; +import com.iemr.admin.repository.item.ItemFormRepo; +import com.iemr.admin.repository.item.ItemRepo; +import com.iemr.admin.repository.item.RouteRepo; +import com.iemr.admin.repository.itemfacilitymapping.M_itemfacilitymappingRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The item service maintains the inventory catalogue and the codes that keep + * each entry unique within a provider. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ItemServiceImpl Test Suite") +class ItemServiceImplTest { + + private static final Integer PSM_ID = 4001; + + @Mock + private ItemRepo itemRepo; + + @Mock + private ItemCategoryRepo itemCategoryRepo; + + @Mock + private RouteRepo routeRepo; + + @Mock + private ItemFormRepo itemFormRepo; + + @Mock + private M_itemfacilitymappingRepo itemfacilitymappingRepo; + + @InjectMocks + private ItemServiceImpl service; + + private static M_ItemCategory category(Integer id) { + M_ItemCategory category = new M_ItemCategory(); + category.setItemCategoryID(id); + return category; + } + + @Test + @DisplayName("getItemCategory should read the whole catalogue when retired categories are wanted too") + void getItemCategory_shouldReadWholeCatalogue() { + List stored = List.of(category(31)); + when(itemCategoryRepo.findByProviderServiceMapIDOrderByItemCategoryName(PSM_ID)).thenReturn(stored); + + assertSame(stored, service.getItemCategory(true, PSM_ID)); + } + + @Test + @DisplayName("getItemCategory should read only the live categories when retired ones are excluded") + void getItemCategory_shouldReadLiveCategories() { + List stored = List.of(category(31)); + when(itemCategoryRepo.findByDeletedAndProviderServiceMapIDOrderByItemCategoryName(false, PSM_ID)) + .thenReturn(stored); + + assertSame(stored, service.getItemCategory(false, PSM_ID)); + } + + @Test + @DisplayName("getItemCategory should answer nothing when the caller names no provider") + void getItemCategory_shouldAnswerNothingWithoutProvider() { + assertTrue(service.getItemCategory(true, null).isEmpty()); + verify(itemCategoryRepo, never()).findByProviderServiceMapIDOrderByItemCategoryName(anyInt()); + } + + @Test + @DisplayName("the item lookups should each reach their own repository query") + void itemLookups_shouldReachTheirOwnQuery() { + ItemMaster item = new ItemMaster(); + List items = List.of(item); + when(itemRepo.save(item)).thenReturn(item); + when(itemRepo.saveAll(anyList())).thenReturn(items); + when(itemRepo.findByProviderServiceMapIDOrderByItemName(PSM_ID)).thenReturn(items); + when(itemRepo.findByItemID(101)).thenReturn(item); + when(itemRepo.findDetailOne(101)).thenReturn(item); + when(itemRepo.getItemMasters(PSM_ID, 31)).thenReturn(items); + when(itemRepo.deleteItemMaster(101, true)).thenReturn(1); + when(itemRepo.discontinueItemMaster(101, true)).thenReturn(1); + when(itemCategoryRepo.findByItemCategoryID(31)).thenReturn(category(31)); + when(routeRepo.getAll()).thenReturn(List.of(new M_Route())); + when(itemFormRepo.getAll()).thenReturn(List.of(new M_ItemForm())); + + assertSame(item, service.createItemMaster(item)); + assertSame(items, service.addAllItemMaster(new ArrayList<>())); + assertSame(items, service.getItemMaster(PSM_ID)); + assertSame(item, service.getItemMasterByID(101)); + assertSame(item, service.getItemMasterCatByID(101)); + assertSame(items, service.getItemMasters(PSM_ID, 31)); + assertEquals(1, service.blockItemMaster(101, true)); + assertEquals(1, service.discontinueItemMaster(101, true)); + assertEquals(31, service.getItemCategory(31).getItemCategoryID()); + assertEquals(1, service.getItemRouteProviderServiceMapID(PSM_ID).size()); + assertEquals(1, service.getItemFormProviderServiceMapID(PSM_ID).size()); + } + + @Test + @DisplayName("updateItemIssueConfig should count only the categories that name an issue type") + void updateItemIssueConfig_shouldCountOnlyComplete() { + M_ItemCategory complete = category(31); + complete.setIssueType("FIFO"); + M_ItemCategory incomplete = category(32); + when(itemCategoryRepo.updateIssueConfig(31, "FIFO")).thenReturn(1); + + assertEquals(1, service.updateItemIssueConfig(List.of(complete, incomplete))); + verify(itemCategoryRepo, never()).updateIssueConfig(32, null); + } + + @Test + @DisplayName("updateExpiryAlert should count only the categories that name an alert window") + void updateExpiryAlert_shouldCountOnlyComplete() { + M_ItemCategory complete = category(31); + complete.setAlertBeforeDays(30); + M_ItemCategory incomplete = category(32); + when(itemCategoryRepo.updateExpiryAlert(31, 30)).thenReturn(1); + + assertEquals(1, service.updateExpiryAlert(List.of(complete, incomplete))); + } + + @Test + @DisplayName("createItemCategories should answer the id of the first category it stored") + void createItemCategories_shouldAnswerFirstStoredId() { + when(itemCategoryRepo.saveAll(anyList())).thenReturn(List.of(category(31))); + + assertEquals(31, service.createItemCategories(new ArrayList<>())); + } + + @Test + @DisplayName("createItemCategories should answer zero when nothing was stored") + void createItemCategories_shouldAnswerZeroWhenNothingStored() { + when(itemCategoryRepo.saveAll(anyList())).thenReturn(new ArrayList<>()); + + assertEquals(0, service.createItemCategories(new ArrayList<>())); + } + + @Test + @DisplayName("createItemForms should answer the id of the first form it stored") + void createItemForms_shouldAnswerFirstStoredId() { + M_ItemForm form = new M_ItemForm(); + form.setItemFormID(11); + when(itemFormRepo.saveAll(anyList())).thenReturn(List.of(form)); + + assertEquals(11, service.createItemForms(new ArrayList<>())); + when(itemFormRepo.saveAll(anyList())).thenReturn(new ArrayList<>()); + assertEquals(0, service.createItemForms(new ArrayList<>())); + } + + @Test + @DisplayName("createRoutes should answer the id of the first route it stored") + void createRoutes_shouldAnswerFirstStoredId() { + M_Route route = new M_Route(); + route.setRouteID(21); + when(routeRepo.saveAll(anyList())).thenReturn(List.of(route)); + + assertEquals(21, service.createRoutes(new ArrayList<>())); + when(routeRepo.saveAll(anyList())).thenReturn(new ArrayList<>()); + assertEquals(0, service.createRoutes(new ArrayList<>())); + } + + @Test + @DisplayName("the edit and block calls should each reach their own repository query") + void editAndBlockCalls_shouldReachTheirOwnQuery() { + M_ItemCategory cat = category(31); + cat.setItemCategoryDesc("Drugs"); + cat.setModifiedBy("admin"); + cat.setDeleted(Boolean.TRUE); + M_ItemForm form = new M_ItemForm(); + form.setItemFormID(11); + form.setItemFormDesc("Tablet"); + form.setModifiedBy("admin"); + form.setDeleted(Boolean.TRUE); + M_Route route = new M_Route(); + route.setRouteID(21); + route.setRouteDesc("Oral"); + route.setModifiedBy("admin"); + route.setDeleted(Boolean.TRUE); + when(itemCategoryRepo.updateItemCategoryDetails(31, "Drugs", "admin")).thenReturn(1); + when(itemCategoryRepo.blockItemCategory(31, Boolean.TRUE, "admin")).thenReturn(1); + when(itemFormRepo.updateItemFormDetails(11, "Tablet", "admin")).thenReturn(1); + when(itemFormRepo.blockItemForm(11, Boolean.TRUE, "admin")).thenReturn(1); + when(routeRepo.updateRouteDetails(21, "Oral", "admin")).thenReturn(1); + when(routeRepo.blockRoute(21, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, service.editItemCategory(cat)); + assertEquals(1, service.blockItemCategory(cat)); + assertEquals(1, service.editItemForm(form)); + assertEquals(1, service.blockItemForm(form)); + assertEquals(1, service.editRoute(route)); + assertEquals(1, service.blockRoute(route)); + } + + @Test + @DisplayName("the code checks should report a code the provider already uses") + void codeChecks_shouldReportUsedCode() { + CodeChecker checker = new CodeChecker(); + checker.setCode("CODE-1"); + checker.setProviderServiceMapID(PSM_ID); + when(itemCategoryRepo.findByItemCategoryCodeAndProviderServiceMapID("CODE-1", PSM_ID)) + .thenReturn(List.of(category(31))); + when(itemFormRepo.findByItemFormCodeAndProviderServiceMapID("CODE-1", PSM_ID)) + .thenReturn(List.of(new M_ItemForm())); + when(itemRepo.findByItemCodeAndProviderServiceMapID("CODE-1", PSM_ID)) + .thenReturn(List.of(new ItemMaster())); + when(routeRepo.findByRouteCodeAndProviderServiceMapID("CODE-1", PSM_ID)) + .thenReturn(List.of(new M_Route())); + + assertTrue(service.checkCodeCategory(checker)); + assertTrue(service.checkCodeForm(checker)); + assertTrue(service.checkCodeItem(checker)); + assertTrue(service.checkCodeRoute(checker)); + } + + @Test + @DisplayName("the code checks should clear a code nobody uses yet") + void codeChecks_shouldClearFreeCode() { + CodeChecker checker = new CodeChecker(); + checker.setCode("CODE-2"); + checker.setProviderServiceMapID(PSM_ID); + when(itemCategoryRepo.findByItemCategoryCodeAndProviderServiceMapID("CODE-2", PSM_ID)) + .thenReturn(new ArrayList<>()); + when(itemFormRepo.findByItemFormCodeAndProviderServiceMapID("CODE-2", PSM_ID)) + .thenReturn(new ArrayList<>()); + when(itemRepo.findByItemCodeAndProviderServiceMapID("CODE-2", PSM_ID)).thenReturn(new ArrayList<>()); + when(routeRepo.findByRouteCodeAndProviderServiceMapID("CODE-2", PSM_ID)).thenReturn(new ArrayList<>()); + + assertFalse(service.checkCodeCategory(checker)); + assertFalse(service.checkCodeForm(checker)); + assertFalse(service.checkCodeItem(checker)); + assertFalse(service.checkCodeRoute(checker)); + } +} diff --git a/src/test/java/com/iemr/admin/service/itemfacilitymapping/M_itemfacilitymappingImplTest.java b/src/test/java/com/iemr/admin/service/itemfacilitymapping/M_itemfacilitymappingImplTest.java new file mode 100644 index 0000000..6d8cbb5 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/itemfacilitymapping/M_itemfacilitymappingImplTest.java @@ -0,0 +1,195 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.itemfacilitymapping; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.itemfacilitymapping.M_itemfacilitymapping; +import com.iemr.admin.data.itemfacilitymapping.V_fetchItemFacilityMap; +import com.iemr.admin.data.items.ItemInStore; +import com.iemr.admin.repo.stockEntry.ItemStockEntryRepo; +import com.iemr.admin.repository.itemfacilitymapping.M_itemfacilitymappingRepo; +import com.iemr.admin.repository.itemfacilitymapping.V_fetchItemFacilityMapRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +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.anyList; +import static org.mockito.Mockito.when; + +/** + * The item facility service records which items a store is allowed to hold, and + * answers what each store currently has on its shelves. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("M_itemfacilitymappingImpl Test Suite") +class M_itemfacilitymappingImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer FACILITY_ID = 9001; + private static final Integer MAP_ID = 3301; + private static final Integer ITEM_ID = 501; + + @Mock + private V_fetchItemFacilityMapRepo v_fetchItemFacilityMapRepo; + + @Mock + private M_itemfacilitymappingRepo m_itemfacilitymappingRepo; + + @Mock + private ItemStockEntryRepo itemStockEntryRepo; + + @InjectMocks + private M_itemfacilitymappingImpl service; + + private static M_itemfacilitymapping mapping() { + M_itemfacilitymapping mapping = new M_itemfacilitymapping(); + mapping.setItemStoreMapID(MAP_ID); + mapping.setItemID(ITEM_ID); + mapping.setFacilityID(FACILITY_ID); + mapping.setDeleted(Boolean.FALSE); + return mapping; + } + + @Test + @DisplayName("mapItemtoStore should answer the mappings the repository stored") + void map_shouldAnswerStoredMappings() { + ArrayList stored = new ArrayList<>(List.of(mapping())); + when(m_itemfacilitymappingRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.mapItemtoStore(new ArrayList<>())); + } + + @Test + @DisplayName("editdata and saveEditedItem should each reach their own repository query") + void singleRecordOperations_shouldReachTheirOwnQuery() { + M_itemfacilitymapping stored = mapping(); + when(m_itemfacilitymappingRepo.findByItemFacilityMapID(MAP_ID)).thenReturn(stored); + when(m_itemfacilitymappingRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.editdata(MAP_ID)); + assertSame(stored, service.saveEditedItem(stored)); + } + + @Test + @DisplayName("editdata should answer nothing when the mapping is unknown") + void edit_shouldAnswerNothingForUnknownMapping() { + when(m_itemfacilitymappingRepo.findByItemFacilityMapID(-1)).thenReturn(null); + + assertNull(service.editdata(-1)); + } + + @Test + @DisplayName("getsubitemforsubStote should rebuild one item per row the query answers") + void getSubItems_shouldRebuildEachRow() { + when(m_itemfacilitymappingRepo.getItemforSubstore(PSM_ID, FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of( + new Object[] { ITEM_ID, "Paracetamol 500", Boolean.FALSE, 7 }))); + + ArrayList items = service.getsubitemforsubStote(PSM_ID, FACILITY_ID); + + assertEquals(1, items.size()); + assertEquals("Paracetamol 500", items.get(0).getItemName()); + assertEquals(ITEM_ID, items.get(0).getItemID()); + } + + @Test + @DisplayName("getsubitemforsubStote should skip a row the query could not fill in") + void getSubItems_shouldSkipIncompleteRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { ITEM_ID, "Paracetamol 500" }); + when(m_itemfacilitymappingRepo.getItemforSubstore(PSM_ID, FACILITY_ID)).thenReturn(rows); + + assertTrue(service.getsubitemforsubStote(PSM_ID, FACILITY_ID).isEmpty()); + } + + @Test + @DisplayName("the mapped item lookups should each reach their own repository query") + void mappedItemLookups_shouldReachTheirOwnQuery() { + ArrayList byProvider = new ArrayList<>(List.of(new V_fetchItemFacilityMap())); + ArrayList byFacility = new ArrayList<>(List.of(new V_fetchItemFacilityMap())); + when(v_fetchItemFacilityMapRepo.getAllFacilityMappedData(PSM_ID)).thenReturn(byProvider); + when(v_fetchItemFacilityMapRepo.getItemMappingsByFacilityAndSubStores(FACILITY_ID)).thenReturn(byFacility); + + assertSame(byProvider, service.getAllFacilityMappedData(PSM_ID)); + assertSame(byFacility, service.getItemMappingsByFacilityID(FACILITY_ID)); + } + + @Test + @DisplayName("getItemMastersFromStoreID should answer what the store holds of each item mapped to it") + void getItemMasters_shouldAnswerWhatStoreHolds() { + when(m_itemfacilitymappingRepo.getItemforStore(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(new Object[] { ITEM_ID, "Paracetamol 500" }))); + when(itemStockEntryRepo.getQuantity(any(Integer[].class), anyInt())) + .thenReturn(new ArrayList<>(List.of( + new Object[] { FACILITY_ID, ITEM_ID, "Paracetamol 500", 250L }))); + + List held = service.getItemMastersFromStoreID(FACILITY_ID); + + assertEquals(1, held.size()); + assertEquals("Paracetamol 500", held.get(0).getItemName()); + assertEquals(250L, held.get(0).getQuantity()); + } + + @Test + @DisplayName("getItemMastersFromStoreID should answer nothing when the store holds none of its items") + void getItemMasters_shouldAnswerNothingWhenStoreEmpty() { + when(m_itemfacilitymappingRepo.getItemforStore(FACILITY_ID)).thenReturn(new ArrayList<>()); + when(itemStockEntryRepo.getQuantity(any(Integer[].class), anyInt())).thenReturn(new ArrayList<>()); + + assertTrue(service.getItemMastersFromStoreID(FACILITY_ID).isEmpty()); + } + + @Test + @DisplayName("deleteItemStoreMapping should report how many mappings the retirement touched") + void delete_shouldReportRowsTouched() { + M_itemfacilitymapping request = mapping(); + request.setDeleted(Boolean.TRUE); + when(m_itemfacilitymappingRepo.updateDeleteMap(MAP_ID, Boolean.TRUE)).thenReturn(1); + + assertEquals(1, service.deleteItemStoreMapping(request)); + } + + @Test + @DisplayName("deleteItemStoreMapping should report nothing touched when the mapping is unknown") + void delete_shouldReportNothingTouchedForUnknownMapping() { + M_itemfacilitymapping request = new M_itemfacilitymapping(); + request.setItemStoreMapID(-1); + when(m_itemfacilitymappingRepo.updateDeleteMap(-1, null)).thenReturn(0); + + assertEquals(0, service.deleteItemStoreMapping(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/locationmaster/LocationMasterServiceImplTest.java b/src/test/java/com/iemr/admin/service/locationmaster/LocationMasterServiceImplTest.java new file mode 100644 index 0000000..bdc8b14 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/locationmaster/LocationMasterServiceImplTest.java @@ -0,0 +1,180 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.locationmaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.M_District; +import com.iemr.admin.data.locationmaster.M_ProviderServiceAddMapping; +import com.iemr.admin.data.locationmaster.Showofficedetails; +import com.iemr.admin.data.locationmaster.StateServiceMapping1; +import com.iemr.admin.repo.locationmaster.LocationMasterRepo; +import com.iemr.admin.repo.locationmaster.MdistrictRepo; +import com.iemr.admin.repo.locationmaster.M_ProviderServiceAddMappingRepo; +import com.iemr.admin.repo.locationmaster.ShowofficedetailsRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The location service reads a provider's office addresses out of several + * differently shaped queries and reshapes them into one carrier. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("LocationMasterServiceImpl Test Suite") +class LocationMasterServiceImplTest { + + private static final Integer PROVIDER_ID = 77; + private static final Integer PSM_ID = 4001; + + @Mock + private ShowofficedetailsRepo showofficedetailsRepo; + + @Mock + private MdistrictRepo mdistricRepo; + + @Mock + private M_ProviderServiceAddMappingRepo m_ProviderServiceAddMappingRepo; + + @Mock + private LocationMasterRepo locationMasterRepo; + + @InjectMocks + private LocationMasterServiceImpl service; + + @Test + @DisplayName("getStateByServiceProviderId should skip a row the query could not fill") + void getStateByServiceProviderId_shouldSkipUnfillableRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 29, "Karnataka", 1, PSM_ID }); + when(locationMasterRepo.getStateByServiceProviderId(PROVIDER_ID)).thenReturn(rows); + + assertEquals(1, service.getStateByServiceProviderId(PROVIDER_ID).size()); + } + + @Test + @DisplayName("getServiceByServiceProviderIdAndStateId should rebuild one mapping per row") + void getServiceByServiceProviderIdAndStateId_shouldRebuildEachRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { 3, PSM_ID, "Tele Medicine" }); + when(locationMasterRepo.getServiceByServiceProviderIdAndStateId(PROVIDER_ID, 29)).thenReturn(rows); + + assertEquals(1, service.getServiceByServiceProviderIdAndStateId(PROVIDER_ID, 29).size()); + } + + @Test + @DisplayName("getStatesByServiceId should rebuild one mapping per row") + void getStatesByServiceId_shouldRebuildEachRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 29, "Karnataka", PSM_ID }); + when(locationMasterRepo.getStatesByServiceId(3, PROVIDER_ID)).thenReturn(rows); + + assertEquals(1, service.getStatesByServiceId(3, PROVIDER_ID).size()); + } + + @Test + @DisplayName("getAllDistrictByStateId should rebuild each district into a plain carrier") + void getAllDistrictByStateId_shouldRebuildEachDistrict() { + M_District stored = new M_District(); + stored.setDistrictID(301); + stored.setDistrictName("Bengaluru Urban"); + when(mdistricRepo.getAllDistrictByStateId(29)).thenReturn(new ArrayList<>(List.of(stored))); + + ArrayList districts = service.getAllDistrictByStateId(29); + + assertEquals(1, districts.size()); + assertEquals("Bengaluru Urban", districts.get(0).getDistrictName()); + } + + @Test + @DisplayName("getlocationByMapid1 should gather the offices of every mapping the caller lists") + void getlocationByMapid1_shouldGatherAcrossMappings() { + Showofficedetails office = new Showofficedetails(); + when(showofficedetailsRepo.getlocationByMapid1(4001)).thenReturn(new ArrayList<>(List.of(office))); + when(showofficedetailsRepo.getlocationByMapid1(4002)).thenReturn(new ArrayList<>(List.of(office))); + + assertEquals(2, service.getlocationByMapid1(new ArrayList<>(List.of(4001, 4002))).size()); + } + + @Test + @DisplayName("getOfficeName should gather the office of every mapping the caller lists") + void getOfficeName_shouldGatherAcrossMappings() { + Showofficedetails request = new Showofficedetails(); + request.setProviderServiceMapID(PSM_ID); + when(showofficedetailsRepo.getOfficeName(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(new Showofficedetails()))); + + assertEquals(1, service.getOfficeName(new ArrayList<>(List.of(request))).size()); + } + + @Test + @DisplayName("the remaining calls should each reach their own repository query") + void remainingCalls_shouldReachTheirOwnQuery() { + M_ProviderServiceAddMapping address = new M_ProviderServiceAddMapping(); + ArrayList addresses = new ArrayList<>(List.of(address)); + ArrayList offices = new ArrayList<>(List.of(new Showofficedetails())); + StateServiceMapping1 mapping = new StateServiceMapping1(PSM_ID); + ArrayList mappings = new ArrayList<>(List.of(mapping)); + when(m_ProviderServiceAddMappingRepo.save(address)).thenReturn(address); + when(m_ProviderServiceAddMappingRepo.saveAll(anyList())).thenReturn(addresses); + when(m_ProviderServiceAddMappingRepo.editData(51)).thenReturn(address); + when(m_ProviderServiceAddMappingRepo.getlocationByMapid(PSM_ID)).thenReturn(addresses); + when(showofficedetailsRepo.getAlldata()).thenReturn(offices); + when(showofficedetailsRepo.getlocationByMapid(PSM_ID)).thenReturn(offices); + when(showofficedetailsRepo.getlocationByMapid3(PSM_ID, 301)).thenReturn(offices); + when(locationMasterRepo.getProviderServiceMapID(PROVIDER_ID, 29, 3)).thenReturn(mapping); + when(locationMasterRepo.getAllByMapId2(PROVIDER_ID, 29, 3)).thenReturn(mappings); + when(locationMasterRepo.getAllByMapId3(PROVIDER_ID, 3)).thenReturn(mappings); + when(locationMasterRepo.getLocationByServiceID(PROVIDER_ID, 3)).thenReturn(mappings); + when(locationMasterRepo.getLocationByStateID(PROVIDER_ID, 29)).thenReturn(mappings); + + assertSame(address, service.addlocation(address)); + assertSame(addresses, service.addlocation(new ArrayList<>())); + assertSame(address, service.editData(51)); + assertSame(address, service.saveEditData(address)); + assertSame(addresses, service.getlocationByMapid(PSM_ID)); + assertSame(offices, service.getAlldata()); + assertSame(offices, service.getlocationByMapid2(PSM_ID)); + assertSame(offices, service.getlocationByMapid4(PSM_ID, 301)); + assertSame(mapping, service.getAllByMapId(PROVIDER_ID, 29, 3)); + assertSame(mappings, service.getAllByMapId2(PROVIDER_ID, 29, 3)); + assertSame(mappings, service.getAllByMapId3(PROVIDER_ID, 3)); + assertSame(mappings, service.getLocationByServiceId(PROVIDER_ID, 3)); + assertSame(mappings, service.getLocationBySateID(PROVIDER_ID, 29)); + } +} diff --git a/src/test/java/com/iemr/admin/service/manufacturer/ManufacturerServiceImplTest.java b/src/test/java/com/iemr/admin/service/manufacturer/ManufacturerServiceImplTest.java new file mode 100644 index 0000000..a473821 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/manufacturer/ManufacturerServiceImplTest.java @@ -0,0 +1,113 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.manufacturer; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.manufacturer.M_Manufacturer; +import com.iemr.admin.repo.manufacturer.ManufacturerRepo; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** The manufacturer service is thin over its repository, but it answers nothing rather than an empty batch. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ManufacturerServiceImpl Test Suite") +class ManufacturerServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private ManufacturerRepo manufacturerRepo; + + @InjectMocks + private ManufacturerServiceImpl service; + + @Test + @DisplayName("createManufacturer should answer the records it stored") + void create_shouldAnswerStoredRecords() { + ArrayList stored = new ArrayList<>(List.of(new M_Manufacturer())); + when(manufacturerRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createManufacturer(new ArrayList<>())); + } + + @Test + @DisplayName("createManufacturer should answer nothing when the store took nothing") + void create_shouldAnswerNothingWhenNothingStored() { + when(manufacturerRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertNull(service.createManufacturer(new ArrayList<>())); + } + + @Test + @DisplayName("the lookups should each reach their own repository query") + void lookups_shouldReachTheirOwnQuery() { + M_Manufacturer record = new M_Manufacturer(); + ArrayList records = new ArrayList<>(List.of(record)); + when(manufacturerRepo.getManufacturerData(PSM_ID)).thenReturn(records); + when(manufacturerRepo.getEditData(RECORD_ID)).thenReturn(record); + when(manufacturerRepo.save(record)).thenReturn(record); + + assertSame(records, service.createManufacturer(PSM_ID)); + assertSame(record, service.editManufacturer(RECORD_ID)); + assertSame(record, service.saveEditedData(record)); + } + + @Test + @DisplayName("checkManufacturerCode should report a code the provider already uses") + void check_shouldReportUsedCode() { + M_Manufacturer request = new M_Manufacturer(); + request.setManufacturerCode("C-1"); + request.setProviderServiceMapID(PSM_ID); + when(manufacturerRepo.findByManufacturerCodeAndProviderServiceMapID("C-1", PSM_ID)).thenReturn(List.of(new M_Manufacturer())); + + assertTrue(service.checkManufacturerCode(request)); + } + + @Test + @DisplayName("checkManufacturerCode should clear a code nobody uses yet") + void check_shouldClearFreeCode() { + M_Manufacturer request = new M_Manufacturer(); + request.setManufacturerCode("C-2"); + request.setProviderServiceMapID(PSM_ID); + when(manufacturerRepo.findByManufacturerCodeAndProviderServiceMapID("C-2", PSM_ID)).thenReturn(new ArrayList()); + + assertFalse(service.checkManufacturerCode(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/nodalemailconfig/NodalConfigServiceImplTest.java b/src/test/java/com/iemr/admin/service/nodalemailconfig/NodalConfigServiceImplTest.java new file mode 100644 index 0000000..68d6667 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/nodalemailconfig/NodalConfigServiceImplTest.java @@ -0,0 +1,190 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.nodalemailconfig; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.emailconfig.AuthorityEmail; +import com.iemr.admin.mapper.emailconfig.InstituteEmailConfigMapper; +import com.iemr.admin.model.emailconfig.NodalEmailRequest; +import com.iemr.admin.model.emailconfig.NodalEmailResponse; +import com.iemr.admin.model.emailconfig.CreateNodalEmailRequestModel; +import com.iemr.admin.model.emailconfig.UpdateNodalEmailRequest; +import com.iemr.admin.repository.emailconfig.InstituteEmailRepo; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The nodal config service keeps the nodal officer mailboxes a complaint is + * escalated to, narrowed by whichever parts of the location the caller names. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("NodalConfigServiceImpl Test Suite") +class NodalConfigServiceImplTest { + + @Mock + private EntityManager entityManager; + + @Mock + private InstituteEmailRepo instituteRepo; + + @Mock + private InstituteEmailConfigMapper instituteEmailConfigMapper; + + @InjectMocks + private NodalConfigServiceImpl service; + + private CriteriaQuery query; + private TypedQuery typedQuery; + + @SuppressWarnings("unchecked") + @BeforeEach + @DisplayName("Stand in for the criteria query the service builds by hand") + void setUp() { + CriteriaBuilder builder = mock(CriteriaBuilder.class); + query = mock(CriteriaQuery.class); + Root root = mock(Root.class); + typedQuery = mock(TypedQuery.class); + + when(entityManager.getCriteriaBuilder()).thenReturn(builder); + when(builder.createQuery(AuthorityEmail.class)).thenReturn(query); + when(query.from(AuthorityEmail.class)).thenReturn(root); + when(query.select(any())).thenReturn(query); + when(query.where(any(Predicate[].class))).thenReturn(query); + when(query.orderBy(any(jakarta.persistence.criteria.Order[].class))).thenReturn(query); + when(root.get(anyString())).thenReturn(mock(Path.class)); + when(builder.equal(any(), any())).thenReturn(mock(Predicate.class)); + when(entityManager.createQuery(query)).thenReturn(typedQuery); + } + + private static NodalEmailRequest fullyNarrowedRequest() { + NodalEmailRequest request = new NodalEmailRequest(); + request.setAuthorityEmailID(1); + request.setDeleted(false); + request.setDistrictID(301); + request.setDistrictBranchMappingID(30111); + request.setBlockID(3011); + request.setProviderServiceMapID(4001); + request.setStateID(29); + request.setMobileNo("9000000001"); + return request; + } + + @Test + @DisplayName("getAllEmailConfigs should answer the mailboxes the query found, as the screens read them") + void getAll_shouldAnswerFoundMailboxes() { + List found = List.of(new AuthorityEmail()); + List published = List.of(new NodalEmailResponse()); + when(typedQuery.getResultList()).thenReturn(found); + when(instituteEmailConfigMapper.resultToInstTypeEmailResponses(found)).thenReturn(published); + + assertSame(published, service.getAllNodalEmailConfigs(fullyNarrowedRequest())); + } + + @Test + @DisplayName("getAllEmailConfigs should narrow the query by every detail the caller named") + void getAll_shouldNarrowByEveryNamedDetail() { + when(typedQuery.getResultList()).thenReturn(new ArrayList<>()); + + service.getAllNodalEmailConfigs(fullyNarrowedRequest()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Predicate[].class); + verify(query).where(captor.capture()); + assertEquals(8, captor.getValue().length, "one narrowing per detail the caller named"); + } + + @Test + @DisplayName("getAllEmailConfigs should narrow by nothing when the caller names nothing") + void getAll_shouldNarrowByNothingForEmptyRequest() { + when(typedQuery.getResultList()).thenReturn(new ArrayList<>()); + + service.getAllNodalEmailConfigs(new NodalEmailRequest()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Predicate[].class); + verify(query).where(captor.capture()); + assertEquals(0, captor.getValue().length); + } + + @Test + @DisplayName("saveEmailConfigs should store one mailbox per request and answer each as stored") + void save_shouldStoreEachRequestedMailbox() { + AuthorityEmail stored = new AuthorityEmail(); + when(instituteEmailConfigMapper.createRequestToInstituteEmailConfig(anyList())) + .thenReturn(List.of(stored, stored)); + when(instituteRepo.save(stored)).thenReturn(stored); + when(instituteEmailConfigMapper.resultInstType(stored)) + .thenReturn(new NodalEmailResponse()); + + assertEquals(2, service.saveNodalEmailConfigs(List.of(new CreateNodalEmailRequestModel())).size()); + } + + @Test + @DisplayName("saveEmailConfigs should store nothing when the caller asks for nothing") + void save_shouldStoreNothingForEmptyRequest() { + when(instituteEmailConfigMapper.createRequestToInstituteEmailConfig(anyList())) + .thenReturn(new ArrayList<>()); + + assertTrue(service.saveNodalEmailConfigs(new ArrayList<>()).isEmpty()); + } + + @Test + @DisplayName("updateEmailConfigs should answer the mailbox as it stands after the change") + void update_shouldAnswerChangedMailbox() { + AuthorityEmail stored = new AuthorityEmail(); + NodalEmailResponse published = new NodalEmailResponse(); + when(instituteEmailConfigMapper.updateRequestToInstituteNodalEmailConf(any(UpdateNodalEmailRequest.class))) + .thenReturn(stored); + when(instituteRepo.save(stored)).thenReturn(stored); + when(instituteEmailConfigMapper.resultToInstTypeNodalEmailResponse(stored)).thenReturn(published); + + assertSame(published, service.updateNodalEmailConfigs(new UpdateNodalEmailRequest())); + } +} diff --git a/src/test/java/com/iemr/admin/service/parkingPlace/ParkingPlaceServiceImplTest.java b/src/test/java/com/iemr/admin/service/parkingPlace/ParkingPlaceServiceImplTest.java new file mode 100644 index 0000000..8065453 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/parkingPlace/ParkingPlaceServiceImplTest.java @@ -0,0 +1,184 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.parkingPlace; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.parkingPlace.M_Parkingplace; +import com.iemr.admin.data.provideronboard.M_ProviderServiceMapping; +import com.iemr.admin.data.zonemaster.M_Zone; +import com.iemr.admin.repository.parkingPlace.ParkingPlaceRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The parking place service turns the flat rows the reporting queries answer + * back into parking places, and treats an omitted location filter as "any". + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ParkingPlaceServiceImpl Test Suite") +class ParkingPlaceServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer PARKING_PLACE_ID = 31; + + @Mock + private ParkingPlaceRepository parkingPlaceRepository; + + @InjectMocks + private ParkingPlaceServiceImpl service; + + private static Object[] reportingRow() { + return new Object[] { PARKING_PLACE_ID, "Hosur parking", "Near the bus stand", "Hosur Road", PSM_ID, + Boolean.FALSE, 1, "India", 29, "Karnataka", 301, "Bengaluru Urban", 3011, "Anekal", 30111, + "Attibele", new M_ProviderServiceMapping(), 5, "104 Helpline" }; + } + + @Test + @DisplayName("getAvailableParkingPlaces should rebuild one parking place per row the query answers") + void getAvailable_shouldRebuildEachRow() { + when(parkingPlaceRepository.getAvailableParkingPlaces("29", "301", PSM_ID)) + .thenReturn(List.of(reportingRow())); + + ArrayList places = service.getAvailableParkingPlaces(29, 301, PSM_ID); + + assertEquals(1, places.size()); + assertEquals("Hosur parking", places.get(0).getParkingPlaceName()); + assertEquals("Karnataka", places.get(0).getStateName()); + assertEquals("Hosur Road", places.get(0).getAreaHQAddress()); + } + + @Test + @DisplayName("getAvailableParkingPlaces should match any state or district the caller leaves out") + void getAvailable_shouldWildcardOmittedFilters() { + when(parkingPlaceRepository.getAvailableParkingPlaces("%%", "%%", PSM_ID)).thenReturn(new ArrayList<>()); + + assertTrue(service.getAvailableParkingPlaces(null, null, PSM_ID).isEmpty()); + verify(parkingPlaceRepository).getAvailableParkingPlaces("%%", "%%", PSM_ID); + } + + @Test + @DisplayName("saveParkingPlace should answer the parking places the repository stored") + void save_shouldAnswerStoredPlaces() { + ArrayList stored = new ArrayList<>(List.of(new M_Parkingplace())); + when(parkingPlaceRepository.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.saveParkingPlace(new ArrayList<>())); + } + + @Test + @DisplayName("updateParkingPlaceStatus should report how many rows the retirement touched") + void updateStatus_shouldReportRowsTouched() { + M_Parkingplace request = new M_Parkingplace(); + request.setParkingPlaceID(PARKING_PLACE_ID); + request.setDeleted(Boolean.TRUE); + request.setModifiedBy("admin"); + when(parkingPlaceRepository.updateParkingPlaceStatus(PARKING_PLACE_ID, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, service.updateParkingPlaceStatus(request)); + } + + @Test + @DisplayName("updateParkingPlaceStatus should report nothing touched when the parking place is unknown") + void updateStatus_shouldReportNothingTouchedForUnknownPlace() { + M_Parkingplace request = new M_Parkingplace(); + request.setParkingPlaceID(-1); + when(parkingPlaceRepository.updateParkingPlaceStatus(-1, null, null)).thenReturn(0); + + assertEquals(0, service.updateParkingPlaceStatus(request)); + } + + @Test + @DisplayName("the single record lookups should each reach their own repository query") + void singleRecordLookups_shouldReachTheirOwnQuery() { + M_Parkingplace stored = new M_Parkingplace(); + List byProvider = List.of(stored); + when(parkingPlaceRepository.getParkingPlaceById(PARKING_PLACE_ID)).thenReturn(stored); + when(parkingPlaceRepository.save(stored)).thenReturn(stored); + when(parkingPlaceRepository.findByProviderServiceMapID(PSM_ID)).thenReturn(byProvider); + + assertSame(stored, service.getParkingPlaceByID(PARKING_PLACE_ID)); + assertSame(stored, service.updateParkingPlaceData(stored)); + assertSame(byProvider, service.getParkingPlaces(PSM_ID)); + } + + @Test + @DisplayName("getSubDistrict should answer the taluks the parking place covers") + void getSubDistrict_shouldAnswerCoveredTaluks() { + when(parkingPlaceRepository.getSubDistrict(PARKING_PLACE_ID)) + .thenReturn(List.of(new Object[] { PARKING_PLACE_ID, 3011, "Anekal" })); + + List taluks = service.getSubDistrict(PARKING_PLACE_ID); + + assertEquals(1, taluks.size()); + assertEquals("Anekal", taluks.get(0).getBlockName()); + assertEquals(3011, taluks.get(0).getDistrictBlockID()); + } + + @Test + @DisplayName("getSubDistrict should answer nothing when the parking place covers no taluk") + void getSubDistrict_shouldAnswerNothingWhenNoneCovered() { + when(parkingPlaceRepository.getSubDistrict(PARKING_PLACE_ID)).thenReturn(new ArrayList<>()); + + assertTrue(service.getSubDistrict(PARKING_PLACE_ID).isEmpty()); + } + + @Test + @DisplayName("getAvailableParkingPlacesbyZoneID should name the zone on each parking place it answers") + void getAvailableByZone_shouldNameTheZone() { + M_Parkingplace place = new M_Parkingplace(); + place.setParkingPlaceID(PARKING_PLACE_ID); + M_Zone zone = new M_Zone(); + zone.setZoneName("South zone"); + when(parkingPlaceRepository.getAvailableParkingPlacesbyzoneid(9, PSM_ID)) + .thenReturn(List.of(new Object[] { place, zone })); + + ArrayList places = service.getAvailableParkingPlacesbyZoneID(9, PSM_ID); + + assertEquals(1, places.size()); + assertEquals("South zone", places.get(0).getZoneName()); + } + + @Test + @DisplayName("getAvailableParkingPlacesbyZoneID should answer nothing when the zone holds no parking place") + void getAvailableByZone_shouldAnswerNothingForEmptyZone() { + when(parkingPlaceRepository.getAvailableParkingPlacesbyzoneid(9, PSM_ID)).thenReturn(new ArrayList<>()); + + assertTrue(service.getAvailableParkingPlacesbyZoneID(9, PSM_ID).isEmpty()); + } +} diff --git a/src/test/java/com/iemr/admin/service/parkingPlace/ParkingPlaceTalukMappingServiceImplTest.java b/src/test/java/com/iemr/admin/service/parkingPlace/ParkingPlaceTalukMappingServiceImplTest.java new file mode 100644 index 0000000..24df775 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/parkingPlace/ParkingPlaceTalukMappingServiceImplTest.java @@ -0,0 +1,151 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.parkingPlace; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.DistrictBlock; +import com.iemr.admin.data.parkingPlace.ParkingplaceTalukMapping; +import com.iemr.admin.data.parkingPlace.ParkingplaceTalukMappingTO; +import com.iemr.admin.mapper.parkingplacetalukmapping.ParkingPlaceTalukMappingMapper; +import com.iemr.admin.repo.locationmaster.DistrictBlockRepo; +import com.iemr.admin.repository.parkingPlace.ParkingPlaceTalukMappingRepository; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The taluk mapping service records which taluks a parking place covers, and + * offers the remaining taluks of a district as the candidates for a new one. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ParkingPlaceTalukMappingServiceImpl Test Suite") +class ParkingPlaceTalukMappingServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer PARKING_PLACE_ID = 31; + private static final Integer DISTRICT_ID = 301; + + @Mock + private ParkingPlaceTalukMappingRepository parkingPlaceTalukMappingRepository; + + @Mock + private DistrictBlockRepo districtBlockRepo; + + @Mock + private ParkingPlaceTalukMappingMapper parkingPlaceTalukMappingMapper; + + @InjectMocks + private ParkingPlaceTalukMappingServiceImpl service; + + private static ParkingplaceTalukMapping mapping() { + ParkingplaceTalukMapping mapping = new ParkingplaceTalukMapping(); + mapping.setPpSubDistrictMapID(7001); + mapping.setParkingPlaceID(PARKING_PLACE_ID); + mapping.setDistrictID(DISTRICT_ID); + mapping.setDistrictBlockID(3011); + mapping.setProviderServiceMapID(PSM_ID); + return mapping; + } + + @Test + @DisplayName("saveParkingPlaceTalukMapping should answer the mappings the repository stored") + void save_shouldAnswerStoredMappings() { + ArrayList stored = new ArrayList<>(List.of(mapping())); + when(parkingPlaceTalukMappingRepository.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.saveParkingPlaceTalukMapping(new ArrayList<>())); + } + + @Test + @DisplayName("updateParkingPlaceTalukMapping and findbyID should each reach their own repository query") + void singleRecordOperations_shouldReachTheirOwnQuery() { + ParkingplaceTalukMapping stored = mapping(); + when(parkingPlaceTalukMappingRepository.save(stored)).thenReturn(stored); + when(parkingPlaceTalukMappingRepository.findByPpSubDistrictMapID(7001)).thenReturn(stored); + + assertSame(stored, service.updateParkingPlaceTalukMapping(stored)); + assertSame(stored, service.findbyID(7001)); + } + + @Test + @DisplayName("findbyProviderservicemapid should answer the taluks the parking place covers") + void findbyProviderservicemapid_shouldAnswerCoveredTaluks() { + List rows = List.of(mapping()); + List published = List.of(new ParkingplaceTalukMappingTO()); + when(parkingPlaceTalukMappingRepository.findByParkingPlaceID(PARKING_PLACE_ID)).thenReturn(rows); + when(parkingPlaceTalukMappingMapper.getParkingplaceTalukMappingMapList(rows)).thenReturn(published); + + assertSame(published, service.findbyProviderservicemapid(mapping())); + } + + @Test + @DisplayName("findbyParkingplaceAndDistrictID should narrow the mappings to the district the caller names") + void findbyParkingplaceAndDistrictID_shouldNarrowToDistrict() { + List rows = List.of(mapping()); + List published = List.of(new ParkingplaceTalukMappingTO()); + when(parkingPlaceTalukMappingRepository + .findByParkingPlaceIDAndDistrictIDOrderByM_DistrictDistrictNameAsc(PARKING_PLACE_ID, DISTRICT_ID)) + .thenReturn(rows); + when(parkingPlaceTalukMappingMapper.getParkingplaceTalukMappingMapList(rows)).thenReturn(published); + + assertSame(published, service.findbyParkingplaceAndDistrictID(mapping())); + } + + @Test + @DisplayName("getunmappedtaluk should exclude the taluks already covered when there are any") + void getunmappedtaluk_shouldExcludeCoveredTaluks() { + List covered = List.of(3011); + List remaining = List.of(new DistrictBlock(3012, "Hoskote")); + when(parkingPlaceTalukMappingRepository.finbyDistrictID(DISTRICT_ID, PSM_ID)).thenReturn(covered); + when(districtBlockRepo.findunmapped(covered, DISTRICT_ID)).thenReturn(remaining); + + assertSame(remaining, service.getunmappedtaluk(DISTRICT_ID, PSM_ID)); + verify(districtBlockRepo, never()).findall(anyInt()); + } + + @Test + @DisplayName("getunmappedtaluk should offer every taluk of the district when none is covered yet") + void getunmappedtaluk_shouldOfferEveryTalukWhenNoneCovered() { + List all = List.of(new DistrictBlock(3011, "Anekal"), new DistrictBlock(3012, "Hoskote")); + when(parkingPlaceTalukMappingRepository.finbyDistrictID(DISTRICT_ID, PSM_ID)).thenReturn(new ArrayList<>()); + when(districtBlockRepo.findall(DISTRICT_ID)).thenReturn(all); + + assertSame(all, service.getunmappedtaluk(DISTRICT_ID, PSM_ID)); + verify(districtBlockRepo, never()).findunmapped(anyList(), anyInt()); + } +} diff --git a/src/test/java/com/iemr/admin/service/pharmacologicalcategory/PharmacologicalcategoryServiceImplTest.java b/src/test/java/com/iemr/admin/service/pharmacologicalcategory/PharmacologicalcategoryServiceImplTest.java new file mode 100644 index 0000000..9286283 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/pharmacologicalcategory/PharmacologicalcategoryServiceImplTest.java @@ -0,0 +1,113 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.pharmacologicalcategory; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.pharmacologicalcategory.M_Pharmacologicalcategory; +import com.iemr.admin.repo.pharmacologicalcategory.PharmacologicalcategoryRepo; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** The pharmacological category service is thin over its repository, but it answers nothing rather than an empty batch. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("PharmacologicalcategoryServiceImpl Test Suite") +class PharmacologicalcategoryServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private PharmacologicalcategoryRepo pharmacologicalcategoryRepo; + + @InjectMocks + private PharmacologicalcategoryServiceImpl service; + + @Test + @DisplayName("createPharmacologicalcategory should answer the records it stored") + void create_shouldAnswerStoredRecords() { + ArrayList stored = new ArrayList<>(List.of(new M_Pharmacologicalcategory())); + when(pharmacologicalcategoryRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createPharmacologicalcategory(new ArrayList<>())); + } + + @Test + @DisplayName("createPharmacologicalcategory should answer nothing when the store took nothing") + void create_shouldAnswerNothingWhenNothingStored() { + when(pharmacologicalcategoryRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertNull(service.createPharmacologicalcategory(new ArrayList<>())); + } + + @Test + @DisplayName("the lookups should each reach their own repository query") + void lookups_shouldReachTheirOwnQuery() { + M_Pharmacologicalcategory record = new M_Pharmacologicalcategory(); + ArrayList records = new ArrayList<>(List.of(record)); + when(pharmacologicalcategoryRepo.getPhormacologicalData(PSM_ID)).thenReturn(records); + when(pharmacologicalcategoryRepo.editPhamacologicalData(RECORD_ID)).thenReturn(record); + when(pharmacologicalcategoryRepo.save(record)).thenReturn(record); + + assertSame(records, service.getPharmacologicalcategory(PSM_ID)); + assertSame(record, service.editPharmacologicalcategory(RECORD_ID)); + assertSame(record, service.saveEditedPharData(record)); + } + + @Test + @DisplayName("checkPharmacologicalcategoryCode should report a code the provider already uses") + void check_shouldReportUsedCode() { + M_Pharmacologicalcategory request = new M_Pharmacologicalcategory(); + request.setPharmCategoryCode("C-1"); + request.setProviderServiceMapID(PSM_ID); + when(pharmacologicalcategoryRepo.findByPharmCategoryCodeAndProviderServiceMapID("C-1", PSM_ID)).thenReturn(List.of(new M_Pharmacologicalcategory())); + + assertTrue(service.checkPharmacologicalcategoryCode(request)); + } + + @Test + @DisplayName("checkPharmacologicalcategoryCode should clear a code nobody uses yet") + void check_shouldClearFreeCode() { + M_Pharmacologicalcategory request = new M_Pharmacologicalcategory(); + request.setPharmCategoryCode("C-2"); + request.setProviderServiceMapID(PSM_ID); + when(pharmacologicalcategoryRepo.findByPharmCategoryCodeAndProviderServiceMapID("C-2", PSM_ID)).thenReturn(new ArrayList()); + + assertFalse(service.checkPharmacologicalcategoryCode(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/provideronboard/ProviderOnBoardServicesTest.java b/src/test/java/com/iemr/admin/service/provideronboard/ProviderOnBoardServicesTest.java new file mode 100644 index 0000000..516cd48 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/provideronboard/ProviderOnBoardServicesTest.java @@ -0,0 +1,801 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.provideronboard; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.google.gson.JsonObject; +import com.iemr.admin.data.provideronboard.M_104druggroup; +import com.iemr.admin.data.provideronboard.M_104drugmapping; +import com.iemr.admin.data.provideronboard.M_104drugmaster; +import com.iemr.admin.data.provideronboard.M_Calltype; +import com.iemr.admin.data.provideronboard.M_Category; +import com.iemr.admin.data.provideronboard.M_Feedbacknature; +import com.iemr.admin.data.provideronboard.M_Feedbacktype; +import com.iemr.admin.data.provideronboard.M_Institutedirectory; +import com.iemr.admin.data.provideronboard.M_Institutedirectorymapping; +import com.iemr.admin.data.provideronboard.M_Institutesubdirectory; +import com.iemr.admin.data.provideronboard.M_Institution; +import com.iemr.admin.data.provideronboard.M_Institutiontype; +import com.iemr.admin.data.provideronboard.M_ProviderServiceMapping; +import com.iemr.admin.data.provideronboard.M_ServiceMaster; +import com.iemr.admin.data.provideronboard.M_Severity; +import com.iemr.admin.data.provideronboard.M_Subcategory; +import com.iemr.admin.data.provideronboard.M_Subservice; +import com.iemr.admin.data.provideronboard.M_SubservicemasterPA; +import com.iemr.admin.data.provideronboard.M_UserservicerolemappingForRole; +import com.iemr.admin.data.provideronboard.ServiceProvider_Model; +import com.iemr.admin.data.provideronboard.V_Showprovideradmin; +import com.iemr.admin.data.provideronboard.V_Showsubcategory; +import com.iemr.admin.exceptionhandler.DataNotFound; +import com.iemr.admin.repository.provideronboard.CalltypeRepo; +import com.iemr.admin.repository.provideronboard.CategoryRepo; +import com.iemr.admin.repository.provideronboard.DrugGroupRepo; +import com.iemr.admin.repository.provideronboard.DrugMappingRepo; +import com.iemr.admin.repository.provideronboard.DrugMasterRepo; +import com.iemr.admin.repository.provideronboard.IemrServiceRepository1; +import com.iemr.admin.repository.provideronboard.InstuteDirectoryRepo; +import com.iemr.admin.repository.provideronboard.M_FeedbacknatureRepo; +import com.iemr.admin.repository.provideronboard.M_FeedbacktypeRepo; +import com.iemr.admin.repository.provideronboard.M_InstitutedirectorymappingRepo; +import com.iemr.admin.repository.provideronboard.M_InstitutesubdirectoryRepo; +import com.iemr.admin.repository.provideronboard.M_InstitutionRepo; +import com.iemr.admin.repository.provideronboard.M_InstitutiontypeRepo; +import com.iemr.admin.repository.provideronboard.M_ProviderServiceMappingRepo; +import com.iemr.admin.repository.provideronboard.M_ServiceMasterRepo; +import com.iemr.admin.repository.provideronboard.M_SeverityRepo; +import com.iemr.admin.repository.provideronboard.M_SubservicemasterPArepo; +import com.iemr.admin.repository.provideronboard.M_UserservicerolemappingForRoleRepo; +import com.iemr.admin.repository.provideronboard.SubCategoryRepo; +import com.iemr.admin.repository.provideronboard.SubserviceMasterRepo; +import com.iemr.admin.repository.provideronboard.V_ShowprovideradminRepo; +import com.iemr.admin.repository.provideronboard.V_ShowsubcategoryRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The onboarding services are thin over their repositories, but a few of them + * choose which query to run from what the caller left blank - and those choices + * decide what an operator sees on screen. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("Provider onboarding service Test Suite") +class ProviderOnBoardServicesTest { + + private static final Integer PSM_ID = 4001; + + @Mock + private CalltypeRepo calltypeRepo; + + @InjectMocks + private CalltypeServiceImpl calltypeService; + + @Mock + private V_ShowsubcategoryRepo v_ShowsubcategoryRepo; + + @Mock + private CategoryRepo categoryRepo; + + @Mock + private SubCategoryRepo subCategoryRepo; + + @InjectMocks + private CategoryMasterImpl categoryService; + + @Mock + private DrugGroupRepo drugGroupRepo; + + @Mock + private DrugMasterRepo drugMasterRepo; + + @Mock + private DrugMappingRepo drugMappingRepo; + + @InjectMocks + private DrugMasterImpl drugService; + + @Mock + private InstuteDirectoryRepo instuteDirectoryRepo; + + @InjectMocks + private InstuteDirectoryServiceImpl directoryService; + + @Mock + private M_FeedbacknatureRepo m_FeedbacknatureRepo; + + @InjectMocks + private M_FeedbacknatureImpl feedbackNatureService; + + @Mock + private M_FeedbacktypeRepo m_FeedbacktypeRepo; + + @InjectMocks + private M_FeedbacktypeImpl feedbackTypeService; + + @Mock + private M_InstitutedirectorymappingRepo m_InstitutedirectorymappingRepo; + + @InjectMocks + private M_InstitutedirectorymappingImpl directoryMappingService; + + @Mock + private M_InstitutesubdirectoryRepo m_InstitutesubdirectoryRepo; + + @InjectMocks + private M_InstitutesubdirectoryImpl subDirectoryService; + + @Mock + private M_InstitutionRepo m_InstitutionRepo; + + @InjectMocks + private M_InstitutionImpl institutionService; + + @Mock + private M_InstitutiontypeRepo m_InstitutiontypeRepo; + + @InjectMocks + private M_InstitutiontypeImpl instituteTypeService; + + @Mock + private M_ServiceMasterRepo mservicemasteRepo; + + @InjectMocks + private M_ServiceMasterImpl serviceMasterService; + + @Mock + private M_SeverityRepo m_ServerityRepo; + + @InjectMocks + private M_SeverityImpl severityService; + + @Mock + private M_SubservicemasterPArepo m_SubservicemasterPArepo; + + @Mock + private SubserviceMasterRepo subserviceMasterRepo; + + @InjectMocks + private SubserviceImpl subServiceService; + + @Mock + private V_ShowprovideradminRepo v_ShowprovideradminRepo; + + @Mock + private M_UserservicerolemappingForRoleRepo m_UserservicerolemappingForRoleRepo; + + @Mock + private IemrServiceRepository1 iemrServiceRepository1; + + @Mock + private M_ProviderServiceMappingRepo m_ProviderServiceMappingRepo; + + @InjectMocks + private ServiceProvider_ServiceImpl providerService; + + @Nested + @DisplayName("CalltypeServiceImpl") + class CallTypeServiceTests { + + @Test + @DisplayName("should hand each call to its own repository query") + void callType_shouldReachTheirOwnQuery() { + M_Calltype callType = new M_Calltype(); + ArrayList stored = new ArrayList<>(List.of(callType)); + when(calltypeRepo.saveAll(anyList())).thenReturn(stored); + when(calltypeRepo.updateCallType(51)).thenReturn(callType); + when(calltypeRepo.save(callType)).thenReturn(callType); + when(calltypeRepo.getCalltypeData(PSM_ID)).thenReturn(stored); + + assertSame(stored, calltypeService.saveCallList(new ArrayList<>())); + assertSame(stored, calltypeService.createCalltype(new ArrayList<>())); + assertSame(callType, calltypeService.updateCallType(51)); + assertSame(callType, calltypeService.saveupdatedData(callType)); + assertSame(stored, calltypeService.getCalltypeData(PSM_ID)); + } + } + + @Nested + @DisplayName("CategoryMasterImpl") + class CategoryServiceTests { + + @Test + @DisplayName("getCategoryId should store the category and answer the id it was given") + void getCategoryId_shouldStoreAndAnswerId() { + M_Category category = new M_Category(); + M_Category stored = new M_Category(); + stored.setCategoryID(81); + when(categoryRepo.save(category)).thenReturn(stored); + + assertEquals(81, categoryService.getCategoryId(category)); + } + + @Test + @DisplayName("the sub-category calls should each reach their own repository query") + void subCategoryCalls_shouldReachTheirOwnQuery() { + M_Subcategory subCategory = new M_Subcategory(); + ArrayList stored = new ArrayList<>(List.of(subCategory)); + ArrayList views = new ArrayList<>(List.of(new V_Showsubcategory())); + when(subCategoryRepo.saveAll(anyList())).thenReturn(stored); + when(subCategoryRepo.getCategory()).thenReturn(stored); + when(subCategoryRepo.getCategory(81)).thenReturn(stored); + when(subCategoryRepo.getSubCategory(91)).thenReturn(subCategory); + when(subCategoryRepo.save(subCategory)).thenReturn(subCategory); + when(v_ShowsubcategoryRepo.getSubCategory1(91)).thenReturn(views); + when(v_ShowsubcategoryRepo.getCategoryByMapIDAndSubServiceID(PSM_ID, 61)).thenReturn(views); + + assertSame(stored, categoryService.saveSubCatData(new ArrayList<>())); + assertSame(stored, categoryService.createSubCategory(new ArrayList<>())); + assertSame(stored, categoryService.getCategory()); + assertSame(stored, categoryService.getCategory(81)); + assertSame(subCategory, categoryService.getSubCategory(91)); + assertSame(subCategory, categoryService.updateSubCatData(subCategory)); + assertSame(views, categoryService.getSubCategory1(91)); + assertSame(views, categoryService.getCategoryByMapIDAndSubServiceID(PSM_ID, 61)); + } + + @Test + @DisplayName("the category calls should each reach their own repository query") + void categoryCalls_shouldReachTheirOwnQuery() { + M_Category category = new M_Category(); + ArrayList stored = new ArrayList<>(List.of(category)); + when(categoryRepo.saveAll(anyList())).thenReturn(stored); + when(categoryRepo.getAllCategory(61, PSM_ID)).thenReturn(stored); + when(categoryRepo.getAllCategory1(PSM_ID)).thenReturn(stored); + when(categoryRepo.getCatData(81)).thenReturn(category); + when(categoryRepo.save(category)).thenReturn(category); + when(categoryRepo.updateCategory(81, 21)).thenReturn(1); + when(categoryRepo.findByProviderServiceMapIDAndFeedbackNatureIDOrderByCategoryNameAsc(PSM_ID, null)) + .thenReturn(stored); + + assertSame(stored, categoryService.createcat(new ArrayList<>())); + assertSame(stored, categoryService.getAllCategory(61, PSM_ID)); + assertSame(stored, categoryService.getAllCategory1(PSM_ID)); + assertSame(category, categoryService.getcatdatabycatId(81)); + assertSame(category, categoryService.deletedata(category)); + assertEquals(1, categoryService.updateCategory(81, 21)); + assertSame(stored, categoryService.getUpmappedCategory(PSM_ID)); + } + + @Test + @DisplayName("getAllCategory should rebuild one category per row the query answers") + void getAllCategory_shouldRebuildEachRow() { + when(categoryRepo.getAllCategory(PSM_ID)) + .thenReturn(List.of(new Object[] { 81, "Medical", 61, "Counselling", PSM_ID })); + + ArrayList categories = categoryService.getAllCategory(PSM_ID); + + assertEquals(1, categories.size()); + assertEquals("Medical", categories.get(0).getCategoryName()); + } + + @Test + @DisplayName("getAllCategorywithFeedbackNatureID should rebuild one category per row the query answers") + void getAllCategoryWithFeedbackNature_shouldRebuildEachRow() { + when(categoryRepo.getAllCategorywithfeedbackNatureID(PSM_ID, 21)) + .thenReturn(List.of(new Object[] { 81, "Medical", 61, "Counselling", PSM_ID })); + + assertEquals(1, categoryService.getAllCategorywithFeedbackNatureID(PSM_ID, 21).size()); + } + } + + @Nested + @DisplayName("DrugMasterImpl") + class DrugServiceTests { + + @Test + @DisplayName("getAllDrugData should read the live catalogue when the caller excludes retired drugs") + void getAllDrugData_shouldReadLiveCatalogue() { + when(drugMasterRepo.getValidDrugData("77")) + .thenReturn(List.of(new Object[] { 101, "Paracetamol", "Antipyretic", "OTC", + Boolean.FALSE, (short) 77 })); + + ArrayList drugs = drugService.getAllDrugData(101, (short) 77, Boolean.FALSE); + + assertEquals(1, drugs.size()); + assertEquals("Paracetamol", drugs.get(0).getDrugName()); + verify(drugMasterRepo, org.mockito.Mockito.never()).getAllDrugData(anyString(), anyString()); + } + + @Test + @DisplayName("getAllDrugData should read the whole catalogue when retired drugs are wanted too") + void getAllDrugData_shouldReadWholeCatalogue() { + when(drugMasterRepo.getAllDrugData("101", "77")).thenReturn(new ArrayList<>()); + + drugService.getAllDrugData(101, (short) 77, Boolean.TRUE); + + verify(drugMasterRepo).getAllDrugData("101", "77"); + } + + @Test + @DisplayName("getAllDrugData should ask for the whole catalogue when the caller names nothing") + void getAllDrugData_shouldAskForWholeCatalogueWithoutFilters() { + when(drugMasterRepo.getAllDrugData("", "")).thenReturn(new ArrayList<>()); + + drugService.getAllDrugData(null, null, null); + + verify(drugMasterRepo).getAllDrugData("", ""); + } + + @Test + @DisplayName("getAllDrugGroups should read the live groups when the caller excludes retired ones") + void getAllDrugGroups_shouldReadLiveGroups() { + when(drugGroupRepo.getValidDrugGroups("77")) + .thenReturn(List.of(new Object[] { 201, "Analgesics", "Pain relief", Boolean.FALSE, + (short) 77 })); + + assertEquals(1, drugService.getAllDrugGroups(201, (short) 77, Boolean.FALSE).size()); + } + + @Test + @DisplayName("getAllDrugGroups should read every group when retired ones are wanted too") + void getAllDrugGroups_shouldReadEveryGroup() { + when(drugGroupRepo.getAllDrugGroups("201", "77")).thenReturn(new ArrayList<>()); + + drugService.getAllDrugGroups(201, (short) 77, Boolean.TRUE); + + verify(drugGroupRepo).getAllDrugGroups("201", "77"); + } + + @Test + @DisplayName("getAllDrugGroupMappings should rebuild one mapping per row the query answers") + void getAllDrugGroupMappings_shouldRebuildEachRow() { + when(drugMappingRepo.getAllDrugGroupMappings("", 77, 3)) + .thenReturn(List.of(new Object[] { 301, 101, "Paracetamol", 201, "Analgesics", "OTC", + Boolean.FALSE, 77, PSM_ID, "N", Boolean.FALSE })); + + assertEquals(1, drugService.getAllDrugGroupMappings(null, 77, 3).size()); + } + + @Test + @DisplayName("the drug writes should each reach their own repository") + void drugWrites_shouldReachTheirOwnRepository() { + M_104druggroup group = new M_104druggroup(); + group.setDrugGroupID(201); + M_104drugmaster drug = new M_104drugmaster(); + M_104drugmapping mapping = new M_104drugmapping(); + ArrayList groups = new ArrayList<>(List.of(group)); + ArrayList drugs = new ArrayList<>(List.of(drug)); + ArrayList mappings = new ArrayList<>(List.of(mapping)); + when(drugGroupRepo.save(group)).thenReturn(group); + when(drugGroupRepo.saveAll(anyList())).thenReturn(groups); + when(drugGroupRepo.getDrugGroupById(201)).thenReturn(group); + when(drugMasterRepo.saveAll(anyList())).thenReturn(drugs); + when(drugMasterRepo.save(drug)).thenReturn(drug); + when(drugMasterRepo.getDrugDataById(101)).thenReturn(drug); + when(drugMappingRepo.saveAll(anyList())).thenReturn(mappings); + when(drugMappingRepo.save(mapping)).thenReturn(mapping); + when(drugMappingRepo.getDrugMappingById(301)).thenReturn(mapping); + + assertEquals(201, drugService.getDrugGrupId(group)); + assertSame(groups, drugService.saveDrugGroup(new ArrayList<>())); + assertSame(group, drugService.getDrugGroupById(201)); + assertSame(group, drugService.saveUpdatedDrugGroup(group)); + assertSame(drugs, drugService.saveDrugData(new ArrayList<>())); + assertSame(drug, drugService.getDrugDataById(101)); + assertSame(drug, drugService.saveUpdatedData(drug)); + assertSame(mappings, drugService.mapDrugWithGroup(new ArrayList<>())); + assertSame(mapping, drugService.getDrugMappingsById(301)); + assertSame(mapping, drugService.saveUpdatedDrugMapping(mapping)); + } + + @Test + @DisplayName("the status updates should each reach their own repository query") + void statusUpdates_shouldReachTheirOwnQuery() { + M_104druggroup group = new M_104druggroup(); + group.setDrugGroupID(201); + group.setDeleted(Boolean.TRUE); + group.setModifiedBy("admin"); + M_104drugmaster drug = new M_104drugmaster(); + drug.setDrugID(101); + drug.setDeleted(Boolean.TRUE); + drug.setModifiedBy("admin"); + M_104drugmapping mapping = new M_104drugmapping(); + mapping.setDrugMapID(301); + mapping.setDeleted(Boolean.TRUE); + mapping.setModifiedBy("admin"); + when(drugGroupRepo.updateStatus(201, Boolean.TRUE, "admin")).thenReturn(1); + when(drugMasterRepo.updateStatus(101, Boolean.TRUE, "admin")).thenReturn(1); + when(drugMappingRepo.updateStatus(301, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, drugService.updateDrugGroupStatus(group)); + assertEquals(1, drugService.updateDrugStatus(drug)); + assertEquals(1, drugService.updateDrugMappingStatus(mapping)); + } + } + + @Nested + @DisplayName("Institute services") + class InstituteServiceTests { + + @Test + @DisplayName("the directory calls should each reach their own repository query") + void directoryCalls_shouldReachTheirOwnQuery() { + M_Institutedirectory directory = new M_Institutedirectory(); + ArrayList stored = new ArrayList<>(List.of(directory)); + when(instuteDirectoryRepo.saveAll(anyList())).thenReturn(stored); + when(instuteDirectoryRepo.getInstuteDirectory(PSM_ID)).thenReturn(stored); + when(instuteDirectoryRepo.editInstuteDirectory(11)).thenReturn(directory); + when(instuteDirectoryRepo.save(directory)).thenReturn(directory); + + assertSame(stored, directoryService.createInstuteDirectory(new ArrayList<>())); + assertSame(stored, directoryService.getInstuteDirectory(PSM_ID)); + assertSame(directory, directoryService.editInstuteDirectory(11)); + assertSame(directory, directoryService.editdata(directory)); + } + + @Test + @DisplayName("the sub-directory calls should each reach their own repository query") + void subDirectoryCalls_shouldReachTheirOwnQuery() { + M_Institutesubdirectory subDirectory = new M_Institutesubdirectory(); + ArrayList stored = new ArrayList<>(List.of(subDirectory)); + when(m_InstitutesubdirectoryRepo.getInstutesubDirectory(11, PSM_ID)).thenReturn(stored); + when(m_InstitutesubdirectoryRepo.saveAll(anyList())).thenReturn(stored); + when(m_InstitutesubdirectoryRepo.editInstutesubDirectory(41)).thenReturn(subDirectory); + when(m_InstitutesubdirectoryRepo.save(subDirectory)).thenReturn(subDirectory); + + assertSame(stored, subDirectoryService.getInstutesubDirectory(11, PSM_ID)); + assertSame(stored, subDirectoryService.CreateInstutesubDirectory(new ArrayList<>())); + assertSame(subDirectory, subDirectoryService.editInstutesubDirectory(41)); + assertSame(subDirectory, subDirectoryService.saveEditedData(subDirectory)); + } + + @Test + @DisplayName("getInstituteDirectoryData should skip a row the query could not fill") + void getInstituteDirectoryData_shouldSkipUnfillableRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 51, 31, 11, 41, PSM_ID, Boolean.FALSE, "admin", "District Hospital", + "Hospitals", "Government" }); + when(m_InstitutedirectorymappingRepo.getMappingData(41)).thenReturn(rows); + + assertEquals(1, directoryMappingService.getInstituteDirectoryData(41).size()); + } + + @Test + @DisplayName("the directory mapping writes should each reach their own repository query") + void directoryMappingWrites_shouldReachTheirOwnQuery() { + M_Institutedirectorymapping mapping = new M_Institutedirectorymapping(); + ArrayList stored = new ArrayList<>(List.of(mapping)); + when(m_InstitutedirectorymappingRepo.saveAll(anyList())).thenReturn(stored); + when(m_InstitutedirectorymappingRepo.getdata(51)).thenReturn(mapping); + when(m_InstitutedirectorymappingRepo.save(mapping)).thenReturn(mapping); + + assertSame(stored, directoryMappingService.createInstituteDirectoryData(new ArrayList<>())); + assertSame(mapping, directoryMappingService.deleteInstituteDirectoryData(51)); + assertSame(mapping, directoryMappingService.setdeletedData(mapping)); + } + + @Test + @DisplayName("getInstution should narrow by block only when a block is named") + void getInstution_shouldNarrowByBlockOnlyWhenNamed() { + ArrayList stored = new ArrayList<>(); + when(m_InstitutionRepo.getInstution(PSM_ID, 29, 301)).thenReturn(stored); + when(m_InstitutionRepo.getInstution(PSM_ID, 29, 301, 401)).thenReturn(stored); + + institutionService.getInstution(PSM_ID, 29, 301, null); + institutionService.getInstution(PSM_ID, 29, 301, 401); + + verify(m_InstitutionRepo).getInstution(PSM_ID, 29, 301); + verify(m_InstitutionRepo).getInstution(PSM_ID, 29, 301, 401); + } + + @Test + @DisplayName("getInstutionByVillage should pick the query that matches what the caller named") + void getInstutionByVillage_shouldPickMatchingQuery() { + ArrayList stored = new ArrayList<>(); + when(m_InstitutionRepo.getInstution(PSM_ID, 29, 301)).thenReturn(stored); + when(m_InstitutionRepo.getInstutionByBlock(PSM_ID, 29, 301, 401)).thenReturn(stored); + when(m_InstitutionRepo.getInstutionByVillage(PSM_ID, 29, 301, 501)).thenReturn(stored); + when(m_InstitutionRepo.getInstutionByBlockAndVillage(PSM_ID, 29, 301, 401, 501)).thenReturn(stored); + + institutionService.getInstutionByVillage(PSM_ID, 29, 301, null, null); + institutionService.getInstutionByVillage(PSM_ID, 29, 301, 401, null); + institutionService.getInstutionByVillage(PSM_ID, 29, 301, null, 501); + institutionService.getInstutionByVillage(PSM_ID, 29, 301, 401, 501); + + verify(m_InstitutionRepo).getInstution(PSM_ID, 29, 301); + verify(m_InstitutionRepo).getInstutionByBlock(PSM_ID, 29, 301, 401); + verify(m_InstitutionRepo).getInstutionByVillage(PSM_ID, 29, 301, 501); + verify(m_InstitutionRepo).getInstutionByBlockAndVillage(PSM_ID, 29, 301, 401, 501); + } + + @Test + @DisplayName("the institution writes should each reach their own repository query") + void institutionWrites_shouldReachTheirOwnQuery() { + M_Institution institution = new M_Institution(); + ArrayList stored = new ArrayList<>(List.of(institution)); + when(m_InstitutionRepo.saveAll(anyList())).thenReturn(stored); + when(m_InstitutionRepo.geteditedData(31)).thenReturn(institution); + when(m_InstitutionRepo.save(institution)).thenReturn(institution); + + assertSame(stored, institutionService.createInstution(new ArrayList<>())); + assertSame(stored, institutionService.createInstutionByVillage(new ArrayList<>())); + assertSame(institution, institutionService.editInstution(31)); + assertSame(institution, institutionService.saveEditData(institution)); + } + + @Test + @DisplayName("createInstitutionByFile should report a run that stored rows") + void createInstitutionByFile_shouldReportStoredRows() { + ArrayList answer = new ArrayList<>(); + answer.add(new Object[] { 12, 0 }); + when(m_InstitutionRepo.institutionByFile(anyString(), anyString(), anyInt(), anyInt())) + .thenReturn(answer); + + assertEquals("Data Saved Successfully", institutionService.createInstitutionByFile(uploadRequest())); + } + + @Test + @DisplayName("createInstitutionByFile should report a file whose rows are already on record") + void createInstitutionByFile_shouldReportDuplicateRows() { + ArrayList answer = new ArrayList<>(); + answer.add(new Object[] { 0, 12 }); + when(m_InstitutionRepo.institutionByFile(anyString(), anyString(), anyInt(), anyInt())) + .thenReturn(answer); + + assertEquals("Data is already present", institutionService.createInstitutionByFile(uploadRequest())); + } + + @Test + @DisplayName("createInstitutionByFile should report a file the database could not read") + void createInstitutionByFile_shouldReportUnusableFile() { + ArrayList answer = new ArrayList<>(); + answer.add(new Object[] { -1, 0 }); + when(m_InstitutionRepo.institutionByFile(anyString(), anyString(), anyInt(), anyInt())) + .thenReturn(answer); + + assertEquals("The Data in file is not appropriate", + institutionService.createInstitutionByFile(uploadRequest())); + } + + @Test + @DisplayName("createInstitutionByFile should answer nothing when the database reports nothing") + void createInstitutionByFile_shouldAnswerNothingForEmptyReport() { + when(m_InstitutionRepo.institutionByFile(anyString(), anyString(), anyInt(), anyInt())) + .thenReturn(new ArrayList<>()); + + assertNull(institutionService.createInstitutionByFile(uploadRequest())); + } + + private JsonObject uploadRequest() { + JsonObject request = new JsonObject(); + request.addProperty("createdBy", "admin"); + request.addProperty("userID", 3117); + request.addProperty("serviceProviderID", 77); + return request; + } + + @Test + @DisplayName("getInstuteTypeByDist should pick the query that matches what the caller named") + void getInstuteTypeByDist_shouldPickMatchingQuery() { + ArrayList stored = new ArrayList<>(); + when(m_InstitutiontypeRepo.getInstuteTypeByDist(PSM_ID, 301)).thenReturn(stored); + when(m_InstitutiontypeRepo.getInstutionTypeByBlock(PSM_ID, 301, 401)).thenReturn(stored); + when(m_InstitutiontypeRepo.getInstutionTypeByVillage(PSM_ID, 301, 501)).thenReturn(stored); + when(m_InstitutiontypeRepo.getInstutionByBlockAndVillage(PSM_ID, 301, 401, 501)).thenReturn(stored); + + instituteTypeService.getInstuteTypeByDist(PSM_ID, 301, null, null); + instituteTypeService.getInstuteTypeByDist(PSM_ID, 301, 401, null); + instituteTypeService.getInstuteTypeByDist(PSM_ID, 301, null, 501); + instituteTypeService.getInstuteTypeByDist(PSM_ID, 301, 401, 501); + + verify(m_InstitutiontypeRepo).getInstuteTypeByDist(PSM_ID, 301); + verify(m_InstitutiontypeRepo).getInstutionTypeByBlock(PSM_ID, 301, 401); + verify(m_InstitutiontypeRepo).getInstutionTypeByVillage(PSM_ID, 301, 501); + verify(m_InstitutiontypeRepo).getInstutionByBlockAndVillage(PSM_ID, 301, 401, 501); + } + + @Test + @DisplayName("the institute type writes should each reach their own repository query") + void instituteTypeWrites_shouldReachTheirOwnQuery() { + M_Institutiontype type = new M_Institutiontype(); + ArrayList stored = new ArrayList<>(List.of(type)); + when(m_InstitutiontypeRepo.saveAll(anyList())).thenReturn(stored); + when(m_InstitutiontypeRepo.getInstuteType(PSM_ID)).thenReturn(stored); + when(m_InstitutiontypeRepo.editdata(21)).thenReturn(type); + when(m_InstitutiontypeRepo.save(type)).thenReturn(type); + + assertSame(stored, instituteTypeService.createInstuteType(new ArrayList<>())); + assertSame(stored, instituteTypeService.createInstuteTypeByDist(new ArrayList<>())); + assertSame(stored, instituteTypeService.getInstuteType(PSM_ID)); + assertSame(type, instituteTypeService.editInstuteType(21)); + assertSame(type, instituteTypeService.saveEditdata(type)); + } + } + + @Nested + @DisplayName("Feedback, severity and sub-service services") + class RemainingServiceTests { + + @Test + @DisplayName("the feedback nature calls should each reach their own repository query") + void feedbackNatureCalls_shouldReachTheirOwnQuery() { + M_Feedbacknature nature = new M_Feedbacknature(); + ArrayList stored = new ArrayList<>(List.of(nature)); + when(m_FeedbacknatureRepo.getInstuteType(41)).thenReturn(stored); + when(m_FeedbacknatureRepo.saveAll(anyList())).thenReturn(stored); + when(m_FeedbacknatureRepo.editFeedbackNatureType(21)).thenReturn(nature); + when(m_FeedbacknatureRepo.save(nature)).thenReturn(nature); + + assertSame(stored, feedbackNatureService.getFeedbackNatureType(41)); + assertSame(stored, feedbackNatureService.createFeedbackNatueType(new ArrayList<>())); + assertSame(nature, feedbackNatureService.editFeedbackNatureType(21)); + assertSame(nature, feedbackNatureService.saveEditedData(nature)); + } + + @Test + @DisplayName("the feedback type calls should each reach their own repository query") + void feedbackTypeCalls_shouldReachTheirOwnQuery() { + M_Feedbacktype type = new M_Feedbacktype(); + ArrayList stored = new ArrayList<>(List.of(type)); + when(m_FeedbacktypeRepo.getAllFeedbackType(PSM_ID)).thenReturn(stored); + when(m_FeedbacktypeRepo.saveAll(anyList())).thenReturn(stored); + when(m_FeedbacktypeRepo.deleteFeedback(41)).thenReturn(type); + when(m_FeedbacktypeRepo.save(type)).thenReturn(type); + + assertSame(stored, feedbackTypeService.getFeedbackt(PSM_ID)); + assertSame(stored, feedbackTypeService.saveFeedbackType(new ArrayList<>())); + assertSame(type, feedbackTypeService.getDataByServId(41)); + assertSame(type, feedbackTypeService.deletedataser(type)); + } + + @Test + @DisplayName("the severity calls should each reach their own repository query") + void severityCalls_shouldReachTheirOwnQuery() { + M_Severity severity = new M_Severity(); + ArrayList stored = new ArrayList<>(List.of(severity)); + when(m_ServerityRepo.getAllServerity(PSM_ID)).thenReturn(stored); + when(m_ServerityRepo.saveAll(anyList())).thenReturn(stored); + when(m_ServerityRepo.editServerity(31)).thenReturn(severity); + when(m_ServerityRepo.save(severity)).thenReturn(severity); + + assertSame(stored, severityService.getServerity(PSM_ID)); + assertSame(stored, severityService.saveServerity(new ArrayList<>())); + assertSame(severity, severityService.getDataByServId(31)); + assertSame(severity, severityService.deletedataser(severity)); + } + + @Test + @DisplayName("the sub-service calls should each reach their own repository query") + void subServiceCalls_shouldReachTheirOwnQuery() { + M_Subservice subService = new M_Subservice(); + ArrayList stored = new ArrayList<>(List.of(subService)); + ArrayList masters = new ArrayList<>(List.of(new M_SubservicemasterPA())); + when(subserviceMasterRepo.saveAll(anyList())).thenReturn(stored); + when(subserviceMasterRepo.getsubServiceName(PSM_ID)).thenReturn(stored); + when(subserviceMasterRepo.getsubServiceNameById(61)).thenReturn(subService); + when(subserviceMasterRepo.save(subService)).thenReturn(subService); + when(m_SubservicemasterPArepo.getServiceNameByServiceID(3)).thenReturn(masters); + + assertSame(stored, subServiceService.saveSubList(new ArrayList<>())); + assertSame(stored, subServiceService.getsubServiceName(PSM_ID)); + assertSame(subService, subServiceService.getsubServiceNameById(61)); + assertSame(subService, subServiceService.saveupdatedData(subService)); + assertSame(masters, subServiceService.getServiceNameByServiceID(3)); + } + + @Test + @DisplayName("getAllServiceLine should hand back what the repository holds") + void getAllServiceLine_shouldHandBackRepositoryContents() { + List stored = List.of(new M_ServiceMaster()); + when(mservicemasteRepo.getAllServiceline()).thenReturn(stored); + + assertSame(stored, serviceMasterService.getAllServiceLine()); + } + } + + @Nested + @DisplayName("ServiceProvider_ServiceImpl") + class ProviderServiceTests { + + @Test + @DisplayName("createProvider should answer the id of the provider it stored") + void createProvider_shouldAnswerStoredId() { + ServiceProvider_Model stored = new ServiceProvider_Model(); + stored.setServiceProviderId(77); + when(iemrServiceRepository1.saveAll(any(Set.class))) + .thenReturn(new ArrayList<>(List.of(stored))); + + assertEquals(77, providerService.createProvider(Set.of(new ServiceProvider_Model()))); + } + + @Test + @DisplayName("createProvider1 should refuse an empty batch rather than store nothing quietly") + void createProvider1_shouldRefuseEmptyBatch() { + assertThrows(DataNotFound.class, () -> providerService.createProvider1(new ArrayList<>())); + } + + @Test + @DisplayName("createProvider1 should refuse a batch the repository stored nothing from") + void createProvider1_shouldRefuseBatchThatStoredNothing() { + when(iemrServiceRepository1.saveAll(anyList())).thenReturn(new ArrayList<>()); + + assertThrows(DataNotFound.class, + () -> providerService.createProvider1(List.of(new ServiceProvider_Model()))); + } + + @Test + @DisplayName("createProvider1 should answer what the repository stored") + void createProvider1_shouldAnswerStoredProviders() { + ArrayList stored = new ArrayList<>(List.of(new ServiceProvider_Model())); + when(iemrServiceRepository1.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, providerService.createProvider1(List.of(new ServiceProvider_Model()))); + } + + @Test + @DisplayName("the provider lookups should each reach their own repository query") + void providerLookups_shouldReachTheirOwnQuery() { + ServiceProvider_Model provider = new ServiceProvider_Model(); + ArrayList providers = new ArrayList<>(List.of(provider)); + M_ProviderServiceMapping mapping = new M_ProviderServiceMapping(); + M_UserservicerolemappingForRole roleMapping = new M_UserservicerolemappingForRole(); + ArrayList roleMappings = new ArrayList<>(List.of(roleMapping)); + ArrayList admins = new ArrayList<>(List.of(new V_Showprovideradmin())); + when(iemrServiceRepository1.getProviderName("Piramal Swasthya")).thenReturn("Piramal Swasthya"); + when(iemrServiceRepository1.getAllProviderName()).thenReturn(providers); + when(iemrServiceRepository1.getProviderData(77)).thenReturn(provider); + when(iemrServiceRepository1.save(provider)).thenReturn(provider); + when(iemrServiceRepository1.saveAll(anyList())).thenReturn(providers); + when(m_ProviderServiceMappingRepo.getPSMID(PSM_ID)).thenReturn(mapping); + when(m_ProviderServiceMappingRepo.saveAll(any(Set.class))).thenReturn(List.of(mapping)); + when(m_UserservicerolemappingForRoleRepo.saveAll(anyList())).thenReturn(roleMappings); + when(m_UserservicerolemappingForRoleRepo.findByUSRMappingID(9001)).thenReturn(roleMapping); + when(m_UserservicerolemappingForRoleRepo.save(roleMapping)).thenReturn(roleMapping); + when(v_ShowprovideradminRepo.getAllProviderAdmin()).thenReturn(admins); + + assertEquals("Piramal Swasthya", providerService.getProviderName("Piramal Swasthya")); + assertSame(providers, providerService.getAllProviderName()); + assertSame(provider, providerService.getProviderData(77)); + assertSame(provider, providerService.upDateProviderDetails(provider)); + assertSame(providers, providerService.createProvider(List.of(provider))); + assertSame(mapping, providerService.getProviderserviceMapId(PSM_ID)); + assertEquals(1, providerService.mapProviderStateService(Set.of(mapping)).size()); + assertSame(roleMappings, providerService.AddUserRole(new ArrayList<>())); + assertSame(roleMapping, providerService.getPADataForEdit(9001)); + assertSame(roleMapping, providerService.insertEditedData(roleMapping)); + assertSame(admins, providerService.getProviderAdmins()); + } + } +} diff --git a/src/test/java/com/iemr/admin/service/questionnaire/QuestionnaireServiceImplTest.java b/src/test/java/com/iemr/admin/service/questionnaire/QuestionnaireServiceImplTest.java new file mode 100644 index 0000000..683c443 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/questionnaire/QuestionnaireServiceImplTest.java @@ -0,0 +1,233 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.questionnaire; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.questionnaire.Questionnaire; +import com.iemr.admin.data.questionnaire.QuestionnaireValues; +import com.iemr.admin.repo.questionnaire.QuestionnaireRepository; +import com.iemr.admin.repo.questionnaire.QuestionnaireValuesRepository; + +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The questionnaire service keeps the feedback questions a provider asks, the + * options each question offers, and the order the questions are asked in. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("QuestionnaireServiceImpl Test Suite") +class QuestionnaireServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer QUESTION_ID = 501; + + @Mock + private QuestionnaireRepository questionnaireRepository; + + @Mock + private QuestionnaireValuesRepository questionnaireValuesRepository; + + @InjectMocks + private QuestionnaireServiceImpl service; + + private static final String SAVE_REQUEST = "[{\"questionnaireDetail\":{\"question\":\"Was the visit useful?\"," + + "\"questionRank\":1,\"questionWeightage\":5,\"answerType\":\"Radio\",\"providerServiceMapID\":4001," + + "\"createdBy\":\"admin\",\"questionOptions\":[{\"option\":\"Yes\",\"optionWeightage\":5}]}}]"; + + private static Questionnaire stored() { + Questionnaire question = new Questionnaire(); + question.setQuestionID(QUESTION_ID); + question.setQuestion("Was the visit useful?"); + question.setQuestionRank(1); + question.setProviderServiceMapID(PSM_ID); + question.setCreatedBy("admin"); + QuestionnaireValues option = new QuestionnaireValues(); + option.setOption("Yes"); + question.setQuestionOptions(new ArrayList<>(List.of(option))); + return question; + } + + @Test + @DisplayName("SaveQuestionnaire should store the question and stamp its options with the question it belongs to") + void save_shouldStoreQuestionAndStampOptions() throws Exception { + when(questionnaireRepository.findQuestionWithRank(PSM_ID, 1)).thenReturn(0); + when(questionnaireRepository.save(any(Questionnaire.class))).thenReturn(stored()); + + assertEquals("Questionnaire Data Saved Successfully", service.SaveQuestionnaire(SAVE_REQUEST)); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(questionnaireValuesRepository).saveAll(captor.capture()); + assertEquals(QUESTION_ID, captor.getValue().get(0).getQuestionID()); + assertEquals(PSM_ID, captor.getValue().get(0).getProviderServiceMapID()); + assertEquals("admin", captor.getValue().get(0).getCreatedBy()); + } + + @Test + @DisplayName("SaveQuestionnaire should push the questions below down when the new one takes an occupied rank") + void save_shouldPushLowerQuestionsDown() throws Exception { + when(questionnaireRepository.findQuestionWithRank(PSM_ID, 1)).thenReturn(1); + when(questionnaireRepository.save(any(Questionnaire.class))).thenReturn(stored()); + + service.SaveQuestionnaire(SAVE_REQUEST); + + verify(questionnaireRepository).updateRankToNext(PSM_ID, 1); + } + + @Test + @DisplayName("SaveQuestionnaire should leave the order alone when the new question's rank is free") + void save_shouldLeaveOrderAloneForFreeRank() throws Exception { + when(questionnaireRepository.findQuestionWithRank(PSM_ID, 1)).thenReturn(0); + when(questionnaireRepository.save(any(Questionnaire.class))).thenReturn(stored()); + + service.SaveQuestionnaire(SAVE_REQUEST); + + verify(questionnaireRepository, never()).updateRankToNext(anyInt(), anyInt()); + } + + @Test + @DisplayName("SaveQuestionnaire should give up when the question cannot be stored") + void save_shouldGiveUpWhenStorageFails() { + when(questionnaireRepository.save(any(Questionnaire.class))) + .thenThrow(new RuntimeException("row is locked")); + + assertThrows(RuntimeException.class, () -> service.SaveQuestionnaire(SAVE_REQUEST)); + } + + @Test + @DisplayName("getQuestionnaireList should answer the multiple choice and free text questions together") + void getList_shouldAnswerBothKindsOfQuestion() { + Questionnaire freeText = new Questionnaire(); + freeText.setQuestion("Anything else?"); + when(questionnaireRepository.findAllQuestions(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(stored()))); + when(questionnaireRepository.findAllQuestionsFreeText(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(freeText))); + + String answered = service.getQuestionnaireList("{\"providerServiceMapID\":4001}"); + + assertTrue(answered.contains("Was the visit useful?"), answered); + assertTrue(answered.contains("Anything else?"), answered); + } + + @Test + @DisplayName("getQuestionnaireList should answer an empty list when the provider asks nothing") + void getList_shouldAnswerEmptyListForProviderWithNoQuestions() { + when(questionnaireRepository.findAllQuestions(PSM_ID)).thenReturn(new ArrayList<>()); + when(questionnaireRepository.findAllQuestionsFreeText(PSM_ID)).thenReturn(new ArrayList<>()); + + assertEquals("[]", service.getQuestionnaireList("{\"providerServiceMapID\":4001}")); + } + + @Test + @DisplayName("deleteQuestionnaire should retire the question, its options and close the gap in the order") + void delete_shouldRetireQuestionAndCloseGap() { + when(questionnaireRepository.deleteQuestion(PSM_ID, QUESTION_ID, Boolean.TRUE)).thenReturn(1); + when(questionnaireRepository.deleteOptions(PSM_ID, QUESTION_ID, Boolean.TRUE)).thenReturn(1); + + assertEquals("Questionnaire Deleted Successfully", service.deleteQuestionnaire( + "{\"providerServiceMapID\":4001,\"questionID\":501,\"questionRank\":1,\"deleted\":true}")); + verify(questionnaireRepository).updateRankToPrevious(PSM_ID, 1); + } + + @Test + @DisplayName("deleteQuestionnaire should answer nothing and leave the order alone when no question was retired") + void delete_shouldAnswerNothingWhenNoQuestionRetired() { + when(questionnaireRepository.deleteQuestion(anyInt(), anyInt(), anyBoolean())).thenReturn(0); + when(questionnaireRepository.deleteOptions(anyInt(), anyInt(), anyBoolean())).thenReturn(0); + + assertNull(service.deleteQuestionnaire( + "{\"providerServiceMapID\":4001,\"questionID\":-1,\"questionRank\":1,\"deleted\":true}")); + verify(questionnaireRepository, never()).updateRankToPrevious(anyInt(), anyInt()); + } + + @Test + @DisplayName("editQuestionnaire should change the question and the options that already exist") + void edit_shouldChangeQuestionAndExistingOptions() { + String request = "{\"questionnaireDetail\":{\"questionID\":501,\"question\":\"Was the visit helpful?\"," + + "\"questionWeightage\":6,\"answerType\":\"Radio\",\"providerServiceMapID\":4001," + + "\"modifiedBy\":\"supervisor\",\"questionOptions\":[{\"questionValuesID\":9001," + + "\"option\":\"Yes\",\"optionWeightage\":6,\"deleted\":false}]}}"; + + assertEquals("Questionnaire Updated Successfully", service.editQuestionnaire(request)); + + verify(questionnaireRepository).updateQuestion(QUESTION_ID, "Was the visit helpful?", 6, "Radio", PSM_ID, + "supervisor"); + verify(questionnaireRepository).updateAnswer(QUESTION_ID, 9001, "Yes", 6, "supervisor", Boolean.FALSE); + verify(questionnaireValuesRepository, never()).save(any(QuestionnaireValues.class)); + } + + @Test + @DisplayName("editQuestionnaire should add an option that has never been stored before") + void edit_shouldAddBrandNewOption() { + String request = "{\"questionnaireDetail\":{\"questionID\":501,\"question\":\"Was the visit helpful?\"," + + "\"questionWeightage\":6,\"answerType\":\"Radio\",\"providerServiceMapID\":4001," + + "\"createdBy\":\"admin\",\"modifiedBy\":\"supervisor\"," + + "\"questionOptions\":[{\"option\":\"Maybe\",\"optionWeightage\":3}]}}"; + + service.editQuestionnaire(request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(QuestionnaireValues.class); + verify(questionnaireValuesRepository).save(captor.capture()); + assertEquals("Maybe", captor.getValue().getOption()); + assertEquals(QUESTION_ID, captor.getValue().getQuestionID()); + assertEquals(PSM_ID, captor.getValue().getProviderServiceMapID()); + assertEquals("supervisor", captor.getValue().getModifiedBy()); + verify(questionnaireRepository, never()).updateAnswer(anyInt(), anyInt(), anyString(), anyInt(), anyString(), + anyBoolean()); + } + + @Test + @DisplayName("editQuestionnaire should give up when the change cannot be recorded") + void edit_shouldGiveUpWhenStorageFails() { + when(questionnaireRepository.updateQuestion(anyInt(), anyString(), anyInt(), anyString(), anyInt(), + anyString())).thenThrow(new RuntimeException("row is locked")); + + assertThrows(RuntimeException.class, () -> service.editQuestionnaire( + "{\"questionnaireDetail\":{\"questionID\":501,\"question\":\"Was it helpful?\"," + + "\"questionWeightage\":6,\"answerType\":\"Radio\",\"providerServiceMapID\":4001," + + "\"modifiedBy\":\"supervisor\",\"questionOptions\":[]}}")); + } +} diff --git a/src/test/java/com/iemr/admin/service/rolemaster/RoleMasterServiceImplTest.java b/src/test/java/com/iemr/admin/service/rolemaster/RoleMasterServiceImplTest.java new file mode 100644 index 0000000..92255fc --- /dev/null +++ b/src/test/java/com/iemr/admin/service/rolemaster/RoleMasterServiceImplTest.java @@ -0,0 +1,346 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.rolemaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.rolemaster.M_Screen; +import com.iemr.admin.data.rolemaster.M_UserservicerolemappingForRoleProviderAdmin; +import com.iemr.admin.data.rolemaster.RoleMaster; +import com.iemr.admin.data.rolemaster.RoleScreenMapping; +import com.iemr.admin.data.rolemaster.StateMasterForRole; +import com.iemr.admin.data.rolemaster.StateServiceMapping; +import com.iemr.admin.repository.rolemaster.M_RoleRepo; +import com.iemr.admin.repository.rolemaster.M_ScreenRepo; +import com.iemr.admin.repository.rolemaster.M_UserservicerolemappingForRoleProviderAdminRepo; +import com.iemr.admin.repository.rolemaster.RoleMasterRepo; +import com.iemr.admin.repository.rolemaster.RoleScreenMappingRepo; +import com.iemr.admin.repository.rolemaster.StateMasterRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The role master service reads the role catalogue and the screens each role + * unlocks, choosing a national or state-scoped query from the flag the caller + * sends. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("Role_Master_ServiceImpl Test Suite") +class RoleMasterServiceImplTest { + + private static final Integer PROVIDER_ID = 77; + private static final Integer PSM_ID = 4001; + + @Mock + private StateMasterRepo stateMasterRepo; + + @Mock + private jakarta.persistence.EntityManager entityManager; + + @Mock + private M_UserservicerolemappingForRoleProviderAdminRepo m_UserservicerolemappingForRoleProviderAdminRepo; + + @Mock + private RoleScreenMappingRepo roleScreenMappingRepo; + + @Mock + private M_ScreenRepo m_ScreenRepo; + + @Mock + private RoleMasterRepo roleMasterRepo; + + @Mock + private M_RoleRepo mRoleRepo; + + @InjectMocks + private Role_Master_ServiceImpl service; + + @Test + @DisplayName("getStateByServiceProviderId should skip a row the query could not fill") + void getStateByServiceProviderId_shouldSkipUnfillableRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 29, "Karnataka", PSM_ID }); + when(roleMasterRepo.getStateByServiceProviderId(PROVIDER_ID)).thenReturn(rows); + + ArrayList mappings = service.getStateByServiceProviderId(PROVIDER_ID); + + assertEquals(1, mappings.size()); + assertEquals(29, mappings.get(0).getStateID()); + } + + @Test + @DisplayName("getServiceByServiceProviderIdAndStateId should rebuild one mapping per row") + void getServiceByServiceProviderIdAndStateId_shouldRebuildEachRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { PSM_ID, 3, "Tele Medicine", PROVIDER_ID }); + when(roleMasterRepo.getServiceByServiceProviderIdAndStateId(PROVIDER_ID, 29)).thenReturn(rows); + + assertEquals(1, service.getServiceByServiceProviderIdAndStateId(PROVIDER_ID, 29).size()); + } + + @Test + @DisplayName("getAllRoleByMapId should answer nothing, as the catalogue is read elsewhere") + void getAllRoleByMapId_shouldAnswerNothing() { + assertNull(service.getAllRoleByMapId()); + } + + @Test + @DisplayName("getAllByMapId should narrow by state for a state-scoped service line") + void getAllByMapId_shouldNarrowByStateForStateService() { + ArrayList stored = new ArrayList<>(); + when(roleMasterRepo.getAllByMapId(PROVIDER_ID, 29, 3)).thenReturn(stored); + + assertSame(stored, service.getAllByMapId(PROVIDER_ID, 29, 3, Boolean.FALSE)); + verify(roleMasterRepo).getAllByMapId(PROVIDER_ID, 29, 3); + } + + @Test + @DisplayName("getAllByMapId should ignore the state for a national service line") + void getAllByMapId_shouldIgnoreStateForNationalService() { + ArrayList stored = new ArrayList<>(); + when(roleMasterRepo.getAlByMapId(PROVIDER_ID, 3)).thenReturn(stored); + + assertSame(stored, service.getAllByMapId(PROVIDER_ID, 29, 3, Boolean.TRUE)); + verify(roleMasterRepo).getAlByMapId(PROVIDER_ID, 3); + } + + @Test + @DisplayName("getAllByMapId with two arguments should ignore the state altogether") + void getAllByMapId_twoArguments_shouldIgnoreState() { + ArrayList stored = new ArrayList<>(); + when(roleMasterRepo.getAlByMapId(PROVIDER_ID, 3)).thenReturn(stored); + + assertSame(stored, service.getAllByMapId(PROVIDER_ID, 3)); + } + + @Test + @DisplayName("getProStateServRoles should skip a row the query could not fill") + void getProStateServRoles_shouldSkipUnfillableRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { 11, "Counsellor", "Handles calls", Boolean.FALSE, "admin", PSM_ID }); + when(roleScreenMappingRepo.getAllRoleByMapId(PSM_ID)).thenReturn(rows); + + ArrayList roles = service.getProStateServRoles(PSM_ID); + + assertEquals(1, roles.size()); + assertEquals("Counsellor", roles.get(0).getRoleName()); + } + + @Test + @DisplayName("getRoleMasterTM should read the same catalogue as the state and service search") + void getRoleMasterTM_shouldReadTheSameCatalogue() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { 11, "TC Specialist", "Telemedicine", Boolean.FALSE, "admin", PSM_ID }); + when(roleScreenMappingRepo.getAllRoleByMapId(PSM_ID)).thenReturn(rows); + + assertEquals("TC Specialist", service.getRoleMasterTM(PSM_ID).get(0).getRoleName()); + } + + @Test + @DisplayName("getProStateServRolesV1 should convert each stored role the query answers") + void getProStateServRolesV1_shouldConvertEachRole() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { java.util.Map.of("roleID", 11, "roleName", "Counsellor") }); + when(mRoleRepo.getAllRoleByMapId1(PSM_ID)).thenReturn(rows); + + ArrayList roles = service.getProStateServRolesV1(PSM_ID); + + assertEquals(1, roles.size()); + assertEquals("Counsellor", roles.get(0).getRoleName()); + } + + @Test + @DisplayName("getProStateServRoles1 should convert each stored role the query answers") + void getProStateServRoles1_shouldConvertEachRole() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { java.util.Map.of("roleID", 11, "roleName", "Counsellor") }); + when(mRoleRepo.getAllRoleByMapId1(PSM_ID)).thenReturn(rows); + + assertEquals(1, service.getProStateServRoles1(PSM_ID).size()); + } + + @Test + @DisplayName("getProStateServRoles1 should answer nothing when the query answers nothing") + void getProStateServRoles1_shouldAnswerNothingForNullResult() { + when(mRoleRepo.getAllRoleByMapId1(PSM_ID)).thenReturn(null); + + assertTrue(service.getProStateServRoles1(PSM_ID).isEmpty()); + } + + @Test + @DisplayName("the role writes should each reach their own repository") + void roleWrites_shouldReachTheirOwnRepository() { + RoleMaster role = new RoleMaster(); + role.setRoleID(11); + ArrayList roles = new ArrayList<>(List.of(role)); + when(mRoleRepo.saveAll(anyList())).thenReturn(roles); + when(mRoleRepo.getRoleByRoleId(11)).thenReturn(role); + when(mRoleRepo.save(role)).thenReturn(role); + when(mRoleRepo.findByDeletedAndProviderServiceMapID(false, PSM_ID)).thenReturn(roles); + + assertEquals(1, service.addRole(new ArrayList<>()).size()); + assertSame(role, service.getRoleByRoleId(11)); + assertSame(role, service.modifydata(role)); + assertEquals("success", service.deletedata(role)); + assertSame(roles, service.getProStateServRolesActive(PSM_ID)); + } + + @Test + @DisplayName("getAllFeature and getAllState should hand back what the repositories hold") + void masters_shouldHandBackRepositoryContents() { + ArrayList screens = new ArrayList<>(List.of(new M_Screen())); + ArrayList states = new ArrayList<>(List.of(new StateMasterForRole())); + when(m_ScreenRepo.getAllFeature(3)).thenReturn(screens); + when(stateMasterRepo.getAllState()).thenReturn(states); + + assertSame(screens, service.getAllFeature(3)); + assertSame(states, service.getAllState()); + } + + @Test + @DisplayName("settingScreenId should report whether the screen mapping actually moved") + void settingScreenId_shouldReportWhetherMappingMoved() { + when(roleScreenMappingRepo.updatescreenId(31, 21)).thenReturn(1); + when(roleScreenMappingRepo.updatescreenId(32, 21)).thenReturn(0); + + assertEquals("success", service.settingScreenId(31, 21)); + assertEquals("fail", service.settingScreenId(32, 21)); + } + + @Test + @DisplayName("mapfeature should hand its batch to the screen mapping repository") + void mapfeature_shouldHandBatchToRepository() { + ArrayList stored = new ArrayList<>(List.of(new RoleScreenMapping())); + when(roleScreenMappingRepo.saveAll(anyList())).thenReturn(stored); + + assertEquals(1, service.mapfeature(new ArrayList<>()).size()); + } + + @Test + @DisplayName("getServiceByServiceProviderIds should skip a row the query could not fill") + void getServiceByServiceProviderIds_shouldSkipUnfillableRow() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { "Tele Medicine", 3, Boolean.FALSE, PSM_ID }); + when(m_UserservicerolemappingForRoleProviderAdminRepo.getServiceByServiceProviderIds(3117)) + .thenReturn(rows); + + assertEquals(1, service.getServiceByServiceProviderIds(3117).size()); + } + + @Test + @DisplayName("getStateByServiceProviderIdAndServiceLines should read the state mappings for a state service") + void getStateByServiceLines_shouldReadStateMappings() { + ArrayList rows = new ArrayList<>(); + rows.add(null); + rows.add(new Object[] { PSM_ID, "Karnataka", 29, 3 }); + when(m_UserservicerolemappingForRoleProviderAdminRepo + .getStateByServiceProviderIdAndServiceLines(3117, 3)).thenReturn(rows); + + ArrayList mappings = + service.getStateByServiceProviderIdAndServiceLines(3117, 3, Boolean.FALSE); + + assertEquals(1, mappings.size()); + } + + @Test + @DisplayName("getStateByServiceProviderIdAndServiceLines should list every state for a national service") + void getStateByServiceLines_shouldListEveryStateForNationalService() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { PSM_ID }); + StateMasterForRole karnataka = new StateMasterForRole(); + karnataka.setStateID(29); + karnataka.setStateName("Karnataka"); + StateMasterForRole kerala = new StateMasterForRole(); + kerala.setStateID(32); + kerala.setStateName("Kerala"); + when(m_UserservicerolemappingForRoleProviderAdminRepo + .getStateByServiceProviderIdAndServiceLines1(3117, 3)).thenReturn(rows); + when(stateMasterRepo.getAllState()).thenReturn(new ArrayList<>(List.of(karnataka, kerala))); + + ArrayList mappings = + service.getStateByServiceProviderIdAndServiceLines(3117, 3, Boolean.TRUE); + + assertEquals(2, mappings.size(), "a national service line reaches every state on record"); + } + + @Test + @DisplayName("configWrapUpTime should carry the new wrap-up settings onto the stored role") + void configWrapUpTime_shouldCarryNewSettings() throws Exception { + RoleMaster stored = new RoleMaster(); + stored.setRoleID(11); + RoleMaster request = new RoleMaster(); + request.setRoleID(11); + request.setIsWrapUpTime(Boolean.TRUE); + request.setWrapUpTime(30); + request.setModifiedBy("admin"); + when(mRoleRepo.findByRoleID(11)).thenReturn(stored); + when(mRoleRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.configWrapUpTime(request)); + assertEquals(30, stored.getWrapUpTime()); + assertTrue(stored.getIsWrapUpTime()); + assertEquals("admin", stored.getModifiedBy()); + } + + @Test + @DisplayName("configWrapUpTime should refuse a role that does not exist") + void configWrapUpTime_shouldRefuseUnknownRole() { + RoleMaster request = new RoleMaster(); + request.setRoleID(11); + when(mRoleRepo.findByRoleID(11)).thenReturn(null); + + Exception thrown = assertThrows(Exception.class, () -> service.configWrapUpTime(request)); + assertEquals("Invalid Role", thrown.getMessage()); + } + + @Test + @DisplayName("configWrapUpTime should refuse a change that names nobody as its author") + void configWrapUpTime_shouldRefuseUnattributedChange() { + RoleMaster request = new RoleMaster(); + request.setRoleID(11); + when(mRoleRepo.findByRoleID(11)).thenReturn(new RoleMaster()); + + Exception thrown = assertThrows(Exception.class, () -> service.configWrapUpTime(request)); + assertEquals("Please provide Modified by", thrown.getMessage()); + } +} diff --git a/src/test/java/com/iemr/admin/service/servicePoint/ServicePointServiceImplTest.java b/src/test/java/com/iemr/admin/service/servicePoint/ServicePointServiceImplTest.java new file mode 100644 index 0000000..bf474d2 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/servicePoint/ServicePointServiceImplTest.java @@ -0,0 +1,197 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.servicePoint; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.DistrictBranchMapping; +import com.iemr.admin.data.servicePoint.M_Servicepoint; +import com.iemr.admin.data.servicePoint.M_Servicepointvillagemap; +import com.iemr.admin.repo.locationmaster.DistrictBranchMappingRepo; +import com.iemr.admin.repository.servicePoint.ServicePointRepository; +import com.iemr.admin.repository.servicePoint.ServicePointVillageMapRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The service point service turns partly-filled location filters into wildcard + * queries, so an operator who leaves a filter blank still sees every match. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ServicePointServiceImpl Test Suite") +class ServicePointServiceImplTest { + + private static final Integer PROVIDER_ID = 77; + private static final Integer PSM_ID = 4001; + private static final Integer POINT_ID = 71; + + @Mock + private ServicePointRepository servicePointRepository; + + @Mock + private DistrictBranchMappingRepo districtBranchMappingRepo; + + @Mock + private ServicePointVillageMapRepository servicePointVillageMapRepository; + + @InjectMocks + private ServicePointServiceImpl service; + + private static Object[] pointRow() { + return new Object[] { POINT_ID, "Hosur halt", "Weekly halt", "Main Road", PSM_ID, Boolean.FALSE, + 29, "Karnataka", 301, "Bengaluru Urban", 401, "North block", 501, "Hosur", 1, "India", null, + 3, "Mobile Medical Unit", 31, "Hosur parking" }; + } + + private static Object[] villageRow() { + return new Object[] { 9001, 29, "Karnataka", 301, "Bengaluru Urban", 401, "North block", 501, "Hosur", + POINT_ID, "Hosur halt", PSM_ID, Boolean.FALSE, null, null, 31, "Hosur parking" }; + } + + @Test + @DisplayName("saveServicePoint should hand its batch to the repository") + void saveServicePoint_shouldHandBatchToRepository() { + ArrayList stored = new ArrayList<>(List.of(new M_Servicepoint())); + when(servicePointRepository.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.saveServicePoint(new ArrayList<>())); + } + + @Test + @DisplayName("getAvailableServicePoints should pass every filter the caller supplies through") + void getAvailableServicePoints_shouldPassFiltersThrough() { + when(servicePointRepository.getAvailableServicePoints("29", "301", "31", PROVIDER_ID)) + .thenReturn(List.of(pointRow())); + + assertEquals(1, service.getAvailableServicePoints(29, 301, 31, PROVIDER_ID).size()); + } + + @Test + @DisplayName("getAvailableServicePoints should match everything for a filter the caller leaves blank") + void getAvailableServicePoints_shouldWildcardBlankFilters() { + when(servicePointRepository.getAvailableServicePoints("%%", "%%", "%%", PROVIDER_ID)) + .thenReturn(new ArrayList<>()); + + service.getAvailableServicePoints(null, null, null, PROVIDER_ID); + + verify(servicePointRepository).getAvailableServicePoints("%%", "%%", "%%", PROVIDER_ID); + } + + @Test + @DisplayName("getAvailableServicePointVillageMaps should pass every filter the caller supplies through") + void getVillageMaps_shouldPassFiltersThrough() { + when(servicePointVillageMapRepository + .getAvailableServicePointVillageMaps("29", "301", "31", "71", PROVIDER_ID)) + .thenReturn(List.of(villageRow())); + + assertEquals(1, service.getAvailableServicePointVillageMaps(29, 301, 31, POINT_ID, PROVIDER_ID).size()); + } + + @Test + @DisplayName("getAvailableServicePointVillageMaps should match everything for filters left blank") + void getVillageMaps_shouldWildcardBlankFilters() { + when(servicePointVillageMapRepository + .getAvailableServicePointVillageMaps("%%", "%%", "%%", "%%", PROVIDER_ID)) + .thenReturn(new ArrayList<>()); + + service.getAvailableServicePointVillageMaps(null, null, null, null, PROVIDER_ID); + + verify(servicePointVillageMapRepository) + .getAvailableServicePointVillageMaps("%%", "%%", "%%", "%%", PROVIDER_ID); + } + + @Test + @DisplayName("the status updates should each reach their own repository query") + void statusUpdates_shouldReachTheirOwnQuery() { + M_Servicepoint point = new M_Servicepoint(POINT_ID, "Hosur halt", "Weekly halt", "Main Road", PSM_ID, + Boolean.TRUE, 1, "India", 29, "Karnataka", 301, "Bengaluru Urban", 401, "North block", 501, + "Hosur", null, 3, "Mobile Medical Unit", 31, "Hosur parking"); + point.setModifiedBy("admin"); + M_Servicepointvillagemap map = new M_Servicepointvillagemap(9001, 29, "Karnataka", 301, + "Bengaluru Urban", 31, "Hosur parking", POINT_ID, "Hosur halt", 501, "Hosur", PSM_ID, + Boolean.TRUE); + map.setModifiedBy("admin"); + when(servicePointRepository.updateServicePointStatus(POINT_ID, Boolean.TRUE, "admin")).thenReturn(1); + when(servicePointVillageMapRepository.updateServicePointStatus(9001, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, service.updateServicePointStatus(point)); + assertEquals(1, service.updateServicePointVillageMapStatus(map)); + } + + @Test + @DisplayName("the record lookups should each reach their own repository query") + void recordLookups_shouldReachTheirOwnQuery() { + M_Servicepoint point = new M_Servicepoint(); + M_Servicepointvillagemap map = new M_Servicepointvillagemap(); + ArrayList maps = new ArrayList<>(List.of(map)); + when(servicePointRepository.findByServicePointID(POINT_ID)).thenReturn(point); + when(servicePointRepository.save(point)).thenReturn(point); + when(servicePointVillageMapRepository.findByServicePointVillageMapID(9001)).thenReturn(map); + when(servicePointVillageMapRepository.save(map)).thenReturn(map); + when(servicePointVillageMapRepository.saveAll(anyList())).thenReturn(maps); + + assertSame(point, service.getdataForEditServicePointStatus(POINT_ID)); + assertSame(point, service.saveeditedData(point)); + assertSame(map, service.updateServicePointVillageMapStatus(9001)); + assertSame(map, service.saveEditedData(map)); + assertSame(maps, service.saveServicePointVillageMap(new ArrayList<>())); + } + + @Test + @DisplayName("getunmappedvillages should exclude the villages already covered when there are any") + void getunmappedvillages_shouldExcludeCoveredVillages() { + List expected = List.of(new DistrictBranchMapping()); + when(servicePointVillageMapRepository.finbyTalukID(PSM_ID)).thenReturn(List.of(501)); + when(districtBranchMappingRepo.getunmappedvillage(List.of(501), 401)).thenReturn(expected); + + assertSame(expected, service.getunmappedvillages(PSM_ID, 401)); + verify(districtBranchMappingRepo, never()).getallvillage(anyInt()); + } + + @Test + @DisplayName("getunmappedvillages should answer every village when none is covered yet") + void getunmappedvillages_shouldAnswerEveryVillageWhenNoneCovered() { + List expected = List.of(new DistrictBranchMapping()); + when(servicePointVillageMapRepository.finbyTalukID(PSM_ID)).thenReturn(new ArrayList<>()); + when(districtBranchMappingRepo.getallvillage(401)).thenReturn(expected); + + assertSame(expected, service.getunmappedvillages(PSM_ID, 401)); + } +} diff --git a/src/test/java/com/iemr/admin/service/snomedMapping/SnomedServiceImplTest.java b/src/test/java/com/iemr/admin/service/snomedMapping/SnomedServiceImplTest.java new file mode 100644 index 0000000..d11bc17 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/snomedMapping/SnomedServiceImplTest.java @@ -0,0 +1,273 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.snomedMapping; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.iemr.admin.data.snomedMapping.ChildVaccinations; +import com.iemr.admin.data.snomedMapping.DiseaseType; +import com.iemr.admin.data.snomedMapping.OptionalVaccinations; +import com.iemr.admin.repository.snomedRepo.SnomedImmunizationRepo; +import com.iemr.admin.repository.snomedRepo.SnomedMappingRepo; +import com.iemr.admin.repository.snomedRepo.SnomedVaccinationRepo; + +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The snomed service maps three separate clinical masters onto SNOMED codes, + * choosing the master to work on from the request's own master type. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("SnomedServiceImpl Test Suite") +class SnomedServiceImplTest { + + private static final Short MASTER_ID = 12; + + @Mock + private SnomedMappingRepo snomedFamilyHistoryRepo; + + @Mock + private SnomedVaccinationRepo snomedVaccinationRepo; + + @Mock + private SnomedImmunizationRepo snomedImmunizationRepo; + + @InjectMocks + private SnomedServiceImpl service; + + private static String requestFor(String masterType) { + return "{\"masterType\":\"" + masterType + "\",\"masterID\":12,\"sctCode\":\"73211009\"," + + "\"sctTerm\":\"Diabetes mellitus\",\"modifiedBy\":\"admin\",\"deleted\":false," + + "\"mappingDetails\":[{\"masterID\":12,\"sctCode\":\"73211009\"}]}"; + } + + private static JsonObject asJson(String request) { + return JsonParser.parseString(request).getAsJsonObject(); + } + + @Test + @DisplayName("editSnomedMappingData should record the code against the family history master") + void edit_shouldRecordCodeAgainstFamilyHistory() { + String request = requestFor("Family History"); + when(snomedFamilyHistoryRepo.updateFamilyHistoryDetails(MASTER_ID, "73211009", "Diabetes mellitus", "admin")) + .thenReturn(1); + + assertEquals("Data Updated", service.editSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("editSnomedMappingData should record the code against the optional vaccination master") + void edit_shouldRecordCodeAgainstOptionalVaccination() { + String request = requestFor("Optional Vaccination"); + when(snomedVaccinationRepo.updateVaccinationDetails(any(), anyString(), anyString(), anyString())) + .thenReturn(1); + + assertEquals("Data Updated", service.editSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("editSnomedMappingData should record the code against the immunization master") + void edit_shouldRecordCodeAgainstImmunization() { + String request = requestFor("Immunization"); + when(snomedImmunizationRepo.updateImmunizationDetails(any(), anyString(), anyString(), anyString())) + .thenReturn(1); + + assertEquals("Data Updated", service.editSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("editSnomedMappingData should give up on a master type it does not recognise") + void edit_shouldGiveUpOnUnknownMasterType() { + String request = requestFor("Astrology"); + + assertThrows(NullPointerException.class, () -> service.editSnomedMappingData(asJson(request), request), + "no master was touched, so there is no row count to judge the edit by"); + } + + @Test + @DisplayName("editSnomedMappingData should refuse a request that is not there at all") + void edit_shouldRefuseAbsentRequest() { + assertEquals("Invalid Master Type", service.editSnomedMappingData(null, null)); + } + + @Test + @DisplayName("editSnomedMappingData should give up when the master row could not be touched") + void edit_shouldGiveUpWhenNothingTouched() { + String request = requestFor("Family History"); + when(snomedFamilyHistoryRepo.updateFamilyHistoryDetails(any(), anyString(), anyString(), anyString())) + .thenReturn(null); + + assertThrows(NullPointerException.class, () -> service.editSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("saveSnomedMappingData should store the family history mappings the request carried") + void save_shouldStoreFamilyHistoryMappings() { + String request = requestFor("Family History"); + when(snomedFamilyHistoryRepo.saveAll(anyList())).thenReturn(List.of(new DiseaseType())); + + assertEquals("Data Saved", service.saveSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("saveSnomedMappingData should store the optional vaccination mappings the request carried") + void save_shouldStoreOptionalVaccinationMappings() { + String request = requestFor("Optional Vaccination"); + when(snomedVaccinationRepo.saveAll(anyList())).thenReturn(List.of(new OptionalVaccinations())); + + assertEquals("Data Saved", service.saveSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("saveSnomedMappingData should store the immunization mappings the request carried") + void save_shouldStoreImmunizationMappings() { + String request = requestFor("Immunization"); + when(snomedImmunizationRepo.saveAll(anyList())).thenReturn(List.of(new ChildVaccinations())); + + assertEquals("Data Saved", service.saveSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("saveSnomedMappingData should say nothing was saved when the repository stored nothing") + void save_shouldSayNothingSavedWhenRepositoryStoredNothing() { + String request = requestFor("Family History"); + when(snomedFamilyHistoryRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertEquals(null, service.saveSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("saveSnomedMappingData should refuse a master type it does not recognise") + void save_shouldRefuseUnknownMasterType() { + String request = requestFor("Astrology"); + + assertEquals("Invalid Master Type", service.saveSnomedMappingData(asJson(request), request)); + } + + @Test + @DisplayName("saveSnomedMappingData should refuse a request that is not there at all") + void save_shouldRefuseAbsentRequest() { + assertEquals("Invalid Master Type", service.saveSnomedMappingData(null, null)); + } + + @Test + @DisplayName("fetchSnomedMaster should answer the family history master as it stands") + void fetch_shouldAnswerFamilyHistoryMaster() { + DiseaseType entry = new DiseaseType(); + entry.setMasterName("Diabetes"); + when(snomedFamilyHistoryRepo.fetchDiseaseType()).thenReturn(List.of(entry)); + + assertTrue(service.fetchSnomedMaster(asJson(requestFor("Family History"))).contains("Diabetes")); + } + + @Test + @DisplayName("fetchSnomedMaster should answer the optional vaccination master as it stands") + void fetch_shouldAnswerOptionalVaccinationMaster() { + OptionalVaccinations entry = new OptionalVaccinations(); + entry.setMasterName("Typhoid"); + when(snomedVaccinationRepo.fetchOptionalVaccinations()).thenReturn(List.of(entry)); + + assertTrue(service.fetchSnomedMaster(asJson(requestFor("Optional Vaccination"))).contains("Typhoid")); + } + + @Test + @DisplayName("fetchSnomedMaster should answer the immunization master as it stands") + void fetch_shouldAnswerImmunizationMaster() { + ChildVaccinations entry = new ChildVaccinations(); + entry.setMasterName("BCG"); + when(snomedImmunizationRepo.fetchChildVaccinations()).thenReturn(List.of(entry)); + + assertTrue(service.fetchSnomedMaster(asJson(requestFor("Immunization"))).contains("BCG")); + } + + @Test + @DisplayName("fetchSnomedMaster should refuse a master type it does not recognise") + void fetch_shouldRefuseUnknownMasterType() { + assertEquals("Invalid Master Type", service.fetchSnomedMaster(asJson(requestFor("Astrology")))); + } + + @Test + @DisplayName("fetchSnomedMaster should refuse a request that is not there at all") + void fetch_shouldRefuseAbsentRequest() { + assertEquals("Invalid request", service.fetchSnomedMaster(null)); + } + + @Test + @DisplayName("updateStatus should retire the entry in whichever master the request names") + void updateStatus_shouldRetireEntryInNamedMaster() { + when(snomedFamilyHistoryRepo.updateStatus(any(), anyBoolean(), anyString())).thenReturn(1); + + assertEquals("Data updated successfully", service.updateStatus(requestFor("Family History"))); + verify(snomedFamilyHistoryRepo).updateStatus(MASTER_ID, Boolean.FALSE, "admin"); + } + + @Test + @DisplayName("updateStatus should retire the entry in the optional vaccination master") + void updateStatus_shouldRetireOptionalVaccinationEntry() { + when(snomedVaccinationRepo.updateStatus(any(), anyBoolean(), anyString())).thenReturn(1); + + assertEquals("Data updated successfully", service.updateStatus(requestFor("Optional Vaccination"))); + } + + @Test + @DisplayName("updateStatus should retire the entry in the immunization master") + void updateStatus_shouldRetireImmunizationEntry() { + when(snomedImmunizationRepo.updateStatus(any(), anyBoolean(), anyString())).thenReturn(1); + + assertEquals("Data updated successfully", service.updateStatus(requestFor("Immunization"))); + } + + @Test + @DisplayName("updateStatus should say so when no entry was touched") + void updateStatus_shouldSaySoWhenNothingTouched() { + when(snomedFamilyHistoryRepo.updateStatus(any(), anyBoolean(), anyString())).thenReturn(0); + + assertEquals("Data not updated", service.updateStatus(requestFor("Family History"))); + } + + @Test + @DisplayName("updateStatus should touch no master when the master type is not recognised") + void updateStatus_shouldTouchNoMasterForUnknownType() { + assertEquals("Data not updated", service.updateStatus(requestFor("Astrology"))); + } +} diff --git a/src/test/java/com/iemr/admin/service/stockEntry/StockEntryServiceImplTest.java b/src/test/java/com/iemr/admin/service/stockEntry/StockEntryServiceImplTest.java new file mode 100644 index 0000000..acde778 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/stockEntry/StockEntryServiceImplTest.java @@ -0,0 +1,318 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.stockEntry; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.dao.DataIntegrityViolationException; + +import com.iemr.admin.data.items.ItemMaster; +import com.iemr.admin.data.items.M_ItemCategory; +import com.iemr.admin.data.stockentry.ItemStockEntry; +import com.iemr.admin.data.stockentry.PhysicalStockEntry; +import com.iemr.admin.data.stockExit.ItemStockExit; +import com.iemr.admin.repo.stockEntry.ItemStockEntryRepo; +import com.iemr.admin.repo.stockEntry.PhysicalStockEntryRepo; +import com.iemr.admin.service.item.ItemService; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +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.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The stock entry service records what arrives at a store, and works out which + * batches an issue should draw on given the item category's issue rule. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("StockEntryServiceImpl Test Suite") +class StockEntryServiceImplTest { + + private static final Integer FACILITY_ID = 9001; + private static final Integer ITEM_ID = 501; + private static final Integer ENTRY_ID = 7001; + + @Mock + private PhysicalStockEntryRepo physicalStockEntryRepo; + + @Mock + private ItemStockEntryRepo itemStockEntryRepo; + + @Mock + private ItemService itemService; + + @InjectMocks + private StockEntryServiceImpl service; + + private static ItemStockEntry batch(Integer stockEntryID, Integer quantityInHand) { + ItemStockEntry batch = new ItemStockEntry(); + batch.setItemStockEntryID(stockEntryID); + batch.setItemID(ITEM_ID); + batch.setFacilityID(FACILITY_ID); + batch.setQuantity(quantityInHand); + batch.setQuantityInHand(quantityInHand); + return batch; + } + + private static PhysicalStockEntry arrival() { + PhysicalStockEntry arrival = new PhysicalStockEntry(); + arrival.setPhyEntryID(ENTRY_ID); + arrival.setRefNo("GRN-101"); + arrival.setItemStockEntry(new ArrayList<>(List.of(batch(null, 100)))); + return arrival; + } + + private static ItemMaster itemIssuedBy(String issueType) { + M_ItemCategory category = new M_ItemCategory(); + category.setIssueType(issueType); + ItemMaster item = new ItemMaster(); + item.setItemCategory(category); + return item; + } + + private static ItemStockExit demandFor(int quantity) { + ItemStockExit demand = new ItemStockExit(); + demand.setItemID(ITEM_ID); + demand.setQuantity(quantity); + return demand; + } + + @Test + @DisplayName("savePhysicalStockEntry should tie each batch to the arrival and put its quantity in hand") + void save_shouldTieBatchesToArrival() { + PhysicalStockEntry arrival = arrival(); + when(physicalStockEntryRepo.save(arrival)).thenReturn(arrival); + when(itemStockEntryRepo.saveAll(anyList())).thenAnswer(call -> new ArrayList<>(call.getArgument(0))); + + PhysicalStockEntry stored = service.savePhysicalStockEntry(arrival); + + assertEquals(1, stored.getItemStockEntry().size()); + ItemStockEntry batch = stored.getItemStockEntry().get(0); + assertEquals(ENTRY_ID, batch.getEntryTypeID()); + assertEquals("physicalStockEntry", batch.getEntryType()); + assertEquals(100, batch.getQuantityInHand()); + } + + @Test + @DisplayName("savePhysicalStockEntry should abandon the whole arrival when a batch clashes with one on file") + void save_shouldAbandonArrivalOnClashingBatch() { + PhysicalStockEntry arrival = arrival(); + when(physicalStockEntryRepo.save(arrival)).thenReturn(arrival); + when(itemStockEntryRepo.saveAll(anyList())) + .thenThrow(new DataIntegrityViolationException("duplicate batch number")); + + assertThrows(DataIntegrityViolationException.class, () -> service.savePhysicalStockEntry(arrival)); + verify(physicalStockEntryRepo).updateDelete(ENTRY_ID, true); + assertTrue(arrival.getDeleted()); + } + + @Test + @DisplayName("savePhysicalStockEntry should keep the arrival when the batches fail for some other reason") + void save_shouldKeepArrivalOnOtherFailure() { + PhysicalStockEntry arrival = arrival(); + when(physicalStockEntryRepo.save(arrival)).thenReturn(arrival); + when(itemStockEntryRepo.saveAll(anyList())).thenThrow(new RuntimeException("connection reset")); + + assertSame(arrival, service.savePhysicalStockEntry(arrival)); + verify(physicalStockEntryRepo, org.mockito.Mockito.never()).updateDelete(anyInt(), anyBoolean()); + } + + @Test + @DisplayName("getItemBatchForStoreID should answer only the batches the store still holds") + void getItemBatch_shouldAnswerBatchesStillHeld() { + List held = List.of(batch(1, 10)); + when(itemStockEntryRepo.findByFacilityIDAndItemIDAndQuantityInHandGreaterThanAndDeleted(FACILITY_ID, ITEM_ID, + 0, false)).thenReturn(held); + + ItemStockEntry request = new ItemStockEntry(); + request.setFacilityID(FACILITY_ID); + request.setItemID(ITEM_ID); + + assertSame(held, service.getItemBatchForStoreID(request)); + } + + @Test + @DisplayName("getAllItemBatchForStoreID should answer the running totals the query worked out") + void getAllItemBatch_shouldAnswerRunningTotals() { + ArrayList totals = new ArrayList<>(List.of(new Object[] { ITEM_ID, 100 })); + when(itemStockEntryRepo.getQuantityOfStock(new Integer[] { 1 }, FACILITY_ID)).thenReturn(totals); + + assertEquals(1, service.getAllItemBatchForStoreID(FACILITY_ID, new Integer[] { 1 }).size()); + } + + @Test + @DisplayName("updateStocks should draw each issued quantity off the batch it came from") + void updateStocks_shouldDrawIssuedQuantityOffBatch() { + ItemStockExit issued = demandFor(30); + issued.setItemStockEntryID(1); + issued.setQuantityInHand(100); + when(itemStockEntryRepo.updateStock(1, 70)).thenReturn(1); + + assertEquals(1, service.updateStocks(List.of(issued))); + verify(itemStockEntryRepo).updateStock(1, 70); + } + + @Test + @DisplayName("updateStocks should count nothing when there is nothing to draw off") + void updateStocks_shouldCountNothingForEmptyIssue() { + assertEquals(0, service.updateStocks(new ArrayList<>())); + } + + @Test + @DisplayName("the ordered batch lookups should each reach their own repository query") + void orderedLookups_shouldReachTheirOwnQuery() { + List byEntryAsc = List.of(batch(1, 10)); + List byEntryDesc = List.of(batch(2, 10)); + List byExpiry = List.of(batch(3, 10)); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByAsc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(byEntryAsc); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByDesc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(byEntryDesc); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByExpiryDateDesc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(byExpiry); + + assertSame(byEntryAsc, service.getItemStockForStoreIDOrderByEntryDateAsc(FACILITY_ID, ITEM_ID)); + assertSame(byEntryDesc, service.getItemStockForStoreIDOrderByEntryDateDesc(FACILITY_ID, ITEM_ID)); + assertSame(byExpiry, service.getItemStockForStoreIDOrderByExpiryDate(FACILITY_ID, ITEM_ID)); + } + + @Test + @DisplayName("getItemStockFromItemID should draw on the batch expiring first when the category says so") + void allocate_shouldDrawOnFirstExpiringBatch() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(itemIssuedBy("First Expiry First Out")); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByExpiryDateDesc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(List.of(batch(1, 100))); + + List allocated = service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(30))); + + assertEquals(1, allocated.size()); + assertEquals(30, allocated.get(0).getQuantity(), "only what the issue asks for is drawn from the batch"); + } + + @Test + @DisplayName("getItemStockFromItemID should draw on the newest batch when the category issues last in first out") + void allocate_shouldDrawOnNewestBatch() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(itemIssuedBy("Last in First Out")); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByAsc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(List.of(batch(1, 100))); + + assertEquals(1, service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(30))).size()); + } + + @Test + @DisplayName("getItemStockFromItemID should draw on the oldest batch when the category issues first in first out") + void allocate_shouldDrawOnOldestBatch() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(itemIssuedBy("First in First Out")); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByDesc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(List.of(batch(1, 100))); + + assertEquals(1, service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(30))).size()); + } + + @Test + @DisplayName("getItemStockFromItemID should fall back to entry order for a category with no issue rule of its own") + void allocate_shouldFallBackToEntryOrder() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(itemIssuedBy("Anything else")); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByAsc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(List.of(batch(1, 100))); + + assertEquals(1, service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(30))).size()); + } + + @Test + @DisplayName("getItemStockFromItemID should spread a large issue across as many batches as it needs") + void allocate_shouldSpreadIssueAcrossBatches() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(itemIssuedBy("First in First Out")); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByDesc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(List.of(batch(1, 20), batch(2, 50), batch(3, 40))); + + List allocated = service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(60))); + + assertEquals(2, allocated.size(), "the third batch is left alone once the issue is covered"); + assertEquals(20, allocated.get(0).getQuantity()); + assertEquals(40, allocated.get(1).getQuantity()); + } + + @Test + @DisplayName("getItemStockFromItemID should record a shortage when the store cannot cover the issue") + void allocate_shouldRecordShortage() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(itemIssuedBy("First in First Out")); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByDesc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(List.of(batch(1, 20))); + + List allocated = service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(50))); + + assertEquals(2, allocated.size()); + ItemStockEntry shortage = allocated.get(1); + assertEquals(30, shortage.getQuantity(), "the shortfall is carried as a batch of its own"); + assertEquals(ITEM_ID, shortage.getItemID()); + assertEquals(FACILITY_ID, shortage.getFacilityID()); + } + + @Test + @DisplayName("getItemStockFromItemID should record the whole issue as a shortage when the store holds none") + void allocate_shouldRecordWholeIssueAsShortage() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(itemIssuedBy("First in First Out")); + when(itemStockEntryRepo + .findByFacilityIDAndItemIDAndDeletedAndQuantityInHandGreaterThanOrderByCreatedByDesc(FACILITY_ID, + ITEM_ID, false, 0)).thenReturn(new ArrayList<>()); + + List allocated = service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(50))); + + assertEquals(1, allocated.size()); + assertEquals(50, allocated.get(0).getQuantity()); + } + + @Test + @DisplayName("getItemStockFromItemID should give up when the item is not on the item master") + void allocate_shouldGiveUpForUnknownItem() { + when(itemService.getItemMasterCatByID(ITEM_ID)).thenReturn(null); + + assertThrows(NullPointerException.class, + () -> service.getItemStockFromItemID(FACILITY_ID, List.of(demandFor(50)))); + } +} diff --git a/src/test/java/com/iemr/admin/service/stockExit/StockExitServiceImplTest.java b/src/test/java/com/iemr/admin/service/stockExit/StockExitServiceImplTest.java new file mode 100644 index 0000000..e1ad35c --- /dev/null +++ b/src/test/java/com/iemr/admin/service/stockExit/StockExitServiceImplTest.java @@ -0,0 +1,190 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.stockExit; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.stockExit.ItemStockExit; +import com.iemr.admin.data.stockExit.T_PatientIssue; +import com.iemr.admin.repo.stockExit.ItemStockExitRepo; +import com.iemr.admin.repo.stockExit.PatientIssueRepo; +import com.iemr.admin.service.item.ItemService; +import com.iemr.admin.service.stockEntry.StockEntryService; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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.anyList; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The stock exit service issues drugs to a patient, but only once it has + * checked every line against what the store actually holds. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("StockExitServiceImpl Test Suite") +class StockExitServiceImplTest { + + private static final Integer FACILITY_ID = 9001; + private static final Integer ISSUE_ID = 5501; + private static final Integer STOCK_ENTRY_ID = 1; + private static final Integer ITEM_ID = 501; + + @Mock + private StockEntryService stockEntryService; + + @Mock + private ItemStockExitRepo itemStockExitRepo; + + @Mock + private PatientIssueRepo patientIssueRepo; + + @Mock + private ItemService itemService; + + @InjectMocks + private StockExitServiceImpl service; + + private static ItemStockExit issueLine(int quantity) { + ItemStockExit line = new ItemStockExit(); + line.setItemStockEntryID(STOCK_ENTRY_ID); + line.setItemID(ITEM_ID); + line.setQuantity(quantity); + return line; + } + + private static T_PatientIssue patientIssue(String issueType, ItemStockExit... lines) { + T_PatientIssue issue = new T_PatientIssue(); + issue.setPatientIssueID(ISSUE_ID); + issue.setFacilityID(FACILITY_ID); + issue.setIssueType(issueType); + issue.setCreatedBy("pharmacist"); + issue.setItemStockExit(new ArrayList<>(List.of(lines))); + return issue; + } + + /** The stock query answers the store, item, name and quantity in hand. */ + private static Object[] stockInHandRow(int quantityInHand) { + return new Object[] { FACILITY_ID, STOCK_ENTRY_ID, "Paracetamol 500", quantityInHand }; + } + + @Test + @DisplayName("issuePatientDrugs should record the issue when the store can cover every line") + void issue_shouldRecordIssueWhenStoreCovers() { + T_PatientIssue issue = patientIssue("Manual", issueLine(10)); + when(stockEntryService.getAllItemBatchForStoreID(any(), any())) + .thenReturn(List.of(stockInHandRow(100))); + when(patientIssueRepo.save(issue)).thenReturn(issue); + + assertEquals(1, service.issuePatientDrugs(issue)); + verify(itemStockExitRepo).saveAll(anyList()); + verify(stockEntryService).updateStocks(anyList()); + } + + @Test + @DisplayName("issuePatientDrugs should refuse the whole issue when the store cannot cover a line") + void issue_shouldRefuseWholeIssueOnShortage() { + T_PatientIssue issue = patientIssue("Manual", issueLine(500)); + when(stockEntryService.getAllItemBatchForStoreID(any(), any())) + .thenReturn(List.of(stockInHandRow(100))); + + assertEquals(0, service.issuePatientDrugs(issue)); + verify(patientIssueRepo, never()).save(any(T_PatientIssue.class)); + verify(itemStockExitRepo, never()).saveAll(anyList()); + } + + @Test + @DisplayName("issuePatientDrugs should leave an issue it does not raise itself alone") + void issue_shouldLeaveOtherIssueTypesAlone() { + assertEquals(0, service.issuePatientDrugs(patientIssue("Prescription", issueLine(10)))); + verify(stockEntryService, never()).getAllItemBatchForStoreID(anyInt(), any()); + } + + @Test + @DisplayName("saveItemExit should stamp each line with the issue it was raised against") + void saveItemExit_shouldStampLinesWithIssue() { + List lines = new ArrayList<>(List.of(issueLine(10))); + + assertEquals(1, service.saveItemExit(lines, ISSUE_ID, "PatientIssue")); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(itemStockExitRepo).saveAll(captor.capture()); + assertEquals("PatientIssue", captor.getValue().get(0).getExitType()); + assertEquals(ISSUE_ID, captor.getValue().get(0).getExitTypeID()); + verify(stockEntryService).updateStocks(lines); + } + + @Test + @DisplayName("getItemStockAndValidate should keep a line the store can cover and record who raised it") + void validate_shouldKeepCoveredLine() { + when(stockEntryService.getAllItemBatchForStoreID(any(), any())) + .thenReturn(List.of(stockInHandRow(100))); + + List kept = service.getItemStockAndValidate(List.of(issueLine(10)), FACILITY_ID, + "pharmacist"); + + assertEquals(1, kept.size()); + assertEquals(100, kept.get(0).getQuantityInHand()); + assertEquals("pharmacist", kept.get(0).getCreatedBy()); + } + + @Test + @DisplayName("getItemStockAndValidate should drop a line the store cannot cover") + void validate_shouldDropUncoveredLine() { + when(stockEntryService.getAllItemBatchForStoreID(any(), any())) + .thenReturn(List.of(stockInHandRow(5))); + + assertTrue(service.getItemStockAndValidate(List.of(issueLine(10)), FACILITY_ID, "pharmacist").isEmpty()); + } + + @Test + @DisplayName("getItemStockAndValidate should keep a line the store covers exactly") + void validate_shouldKeepExactlyCoveredLine() { + when(stockEntryService.getAllItemBatchForStoreID(any(), any())) + .thenReturn(List.of(stockInHandRow(10))); + + assertEquals(1, service.getItemStockAndValidate(List.of(issueLine(10)), FACILITY_ID, "pharmacist").size()); + } + + @Test + @DisplayName("getItemStockAndValidate should keep nothing when the store holds none of the batches asked for") + void validate_shouldKeepNothingWhenStoreHoldsNoBatch() { + when(stockEntryService.getAllItemBatchForStoreID(any(), any())).thenReturn(new ArrayList<>()); + + assertTrue(service.getItemStockAndValidate(List.of(issueLine(10)), FACILITY_ID, "pharmacist").isEmpty()); + } +} diff --git a/src/test/java/com/iemr/admin/service/store/StoreServiceImplTest.java b/src/test/java/com/iemr/admin/service/store/StoreServiceImplTest.java new file mode 100644 index 0000000..2e9f4c4 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/store/StoreServiceImplTest.java @@ -0,0 +1,664 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.store; + +import java.util.ArrayList; +import java.util.List; + +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.facilitytype.M_facilitytype; +import com.iemr.admin.data.parkingPlace.M_Parkingplace; +import com.iemr.admin.data.store.FacilityVillageMapping; +import com.iemr.admin.data.store.M_Facility; +import com.iemr.admin.data.store.M_facilityMap; +import com.iemr.admin.data.store.V_FetchFacility; +import com.iemr.admin.data.vanMaster.M_Van; +import com.iemr.admin.repository.facilitytype.M_facilitytypeRepo; +import com.iemr.admin.repository.parkingPlace.ParkingPlaceRepository; +import com.iemr.admin.repository.store.FacilityVillageMappingRepo; +import com.iemr.admin.repository.store.MainStoreRepo; +import com.iemr.admin.repository.store.V_FetchFacilityRepo; +import com.iemr.admin.repository.vanMaster.VanMasterRepository; +import com.iemr.admin.service.employeemaster.AshaSupervisorMappingService; + +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.assertSame; +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.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The store service maintains the facility hierarchy - which health facility + * sits under which, and which villages each one serves - so its rules decide + * whether a facility can be retired without orphaning what hangs off it. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("StoreServiceImpl Test Suite") +class StoreServiceImplTest { + + private static final Integer FACILITY_ID = 501; + private static final Integer PSM_ID = 4001; + private static final Integer BLOCK_ID = 401; + + @Mock + private MainStoreRepo mainStoreRepo; + + @Mock + private ParkingPlaceRepository parkingPlaceRepository; + + @Mock + private VanMasterRepository vanMasterRepository; + + @Mock + private V_FetchFacilityRepo fetchFacilityRepo; + + @Mock + private FacilityVillageMappingRepo facilityVillageMappingRepo; + + @Mock + private AshaSupervisorMappingService ashaSupervisorMappingService; + + @Mock + private M_facilitytypeRepo facilityTypeRepo; + + @InjectMocks + private StoreServiceImpl service; + + private static M_Facility facility(Integer id, String name) { + M_Facility facility = new M_Facility(); + facility.setFacilityID(id); + facility.setFacilityName(name); + facility.setBlockID(BLOCK_ID); + facility.setProviderServiceMapID(PSM_ID); + return facility; + } + + private static M_facilitytype facilityType(Integer id, Integer levelValue) { + M_facilitytype type = new M_facilitytype(); + type.setFacilityTypeID(id); + type.setLevelValue(levelValue); + return type; + } + + @Nested + @DisplayName("Reads") + class ReadTests { + + @Test + @DisplayName("the facility lookups should each reach their own repository query") + void facilityLookups_shouldReachTheirOwnQuery() { + M_Facility stored = facility(FACILITY_ID, "PHC North"); + ArrayList facilities = new ArrayList<>(List.of(stored)); + List asList = List.of(stored); + when(mainStoreRepo.save(stored)).thenReturn(stored); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(stored); + when(mainStoreRepo.findByProviderServiceMapIDOrNullOrderByFacilityName(PSM_ID)).thenReturn(asList); + when(mainStoreRepo.saveAll(anyList())).thenReturn(asList); + when(mainStoreRepo.getAllMainFacility(PSM_ID, true)).thenReturn(facilities); + when(mainStoreRepo.getAllMainFacility(PSM_ID, true, FACILITY_ID)).thenReturn(facilities); + when(mainStoreRepo.getChildFacility(PSM_ID, FACILITY_ID)).thenReturn(facilities); + when(mainStoreRepo.findByBlockIDAndDeletedFalseOrderByFacilityName(BLOCK_ID)).thenReturn(facilities); + when(mainStoreRepo.findByBlockIDOrderByFacilityName(BLOCK_ID)).thenReturn(facilities); + when(mainStoreRepo.findByParentFacilityIDAndDeletedFalseOrderByFacilityName(FACILITY_ID)) + .thenReturn(facilities); + + assertSame(stored, service.createMainStore(stored)); + assertSame(stored, service.getMainStore(FACILITY_ID)); + assertSame(asList, service.getAllMainStore(PSM_ID)); + assertSame(asList, service.addAllMainStore(new ArrayList<>())); + assertSame(facilities, service.getMainFacility(PSM_ID, true)); + assertSame(facilities, service.getMainFacility(PSM_ID, true, FACILITY_ID)); + assertSame(facilities, service.getChildFacility(PSM_ID, FACILITY_ID)); + assertSame(facilities, service.getFacilitiesByBlock(BLOCK_ID)); + assertSame(facilities, service.getAllFacilitiesByBlock(BLOCK_ID)); + assertSame(facilities, service.getChildFacilitiesByParent(FACILITY_ID)); + } + + @Test + @DisplayName("getFacilitiesByBlockAndLevel should ignore the rural-urban split when none is asked for") + void getFacilitiesByBlockAndLevel_shouldIgnoreSplitWhenNotAsked() { + when(mainStoreRepo.findByBlockIDAndLevelValue(BLOCK_ID, 4)).thenReturn(new ArrayList<>()); + + service.getFacilitiesByBlockAndLevel(BLOCK_ID, 4, null); + service.getFacilitiesByBlockAndLevel(BLOCK_ID, 4, ""); + + verify(mainStoreRepo, org.mockito.Mockito.times(2)).findByBlockIDAndLevelValue(BLOCK_ID, 4); + } + + @Test + @DisplayName("getFacilitiesByBlockAndLevel should narrow by the rural-urban split when asked for") + void getFacilitiesByBlockAndLevel_shouldNarrowBySplit() { + when(mainStoreRepo.findByBlockIDAndFacilityLevel(BLOCK_ID, 4, "Rural")).thenReturn(new ArrayList<>()); + + service.getFacilitiesByBlockAndLevel(BLOCK_ID, 4, "Rural"); + + verify(mainStoreRepo).findByBlockIDAndFacilityLevel(BLOCK_ID, 4, "Rural"); + } + + @Test + @DisplayName("getMapStore should answer the mapped facilities of the provider") + void getMapStore_shouldAnswerMappedFacilities() { + V_FetchFacility request = new V_FetchFacility(); + request.setProviderServiceMapID(PSM_ID); + List stored = List.of(request); + when(fetchFacilityRepo.findByProviderServiceMapID(PSM_ID)).thenReturn(stored); + + assertSame(stored, service.getMapStore(request)); + } + + @Test + @DisplayName("checkStoreCode should report a facility code the provider already uses") + void checkStoreCode_shouldReportUsedCode() { + M_Facility request = facility(null, "PHC North"); + request.setFacilityCode("PHC-1"); + when(mainStoreRepo.findByFacilityCodeAndProviderServiceMapID("PHC-1", PSM_ID)) + .thenReturn(List.of(facility(FACILITY_ID, "PHC North"))); + + assertTrue(service.checkStoreCode(request)); + } + + @Test + @DisplayName("checkStoreCode should clear a facility code nobody uses yet") + void checkStoreCode_shouldClearFreeCode() { + M_Facility request = facility(null, "PHC North"); + request.setFacilityCode("PHC-2"); + when(mainStoreRepo.findByFacilityCodeAndProviderServiceMapID("PHC-2", PSM_ID)) + .thenReturn(new ArrayList<>()); + + assertFalse(service.checkStoreCode(request)); + } + + @Test + @DisplayName("getMappedVillageIDs should hand back what the repository holds") + void getMappedVillageIDs_shouldHandBackRepositoryContents() { + List stored = List.of(601, 602); + when(facilityVillageMappingRepo.findMappedVillageIDsByBlockID(BLOCK_ID)).thenReturn(stored); + + assertSame(stored, service.getMappedVillageIDs(BLOCK_ID)); + } + + @Test + @DisplayName("getVillageMappingsByFacility should answer the villages a live facility serves") + void getVillageMappingsByFacility_shouldAnswerServedVillages() { + ArrayList stored = new ArrayList<>(List.of(new FacilityVillageMapping())); + when(mainStoreRepo.findByFacilityIDAndDeleted(FACILITY_ID, false)) + .thenReturn(facility(FACILITY_ID, "PHC North")); + when(facilityVillageMappingRepo.findByFacilityIDAndDeletedFalse(FACILITY_ID)).thenReturn(stored); + + assertSame(stored, service.getVillageMappingsByFacility(FACILITY_ID)); + } + + @Test + @DisplayName("getVillageMappingsByFacility should answer nothing for a facility that is retired") + void getVillageMappingsByFacility_shouldAnswerNothingForRetiredFacility() { + when(mainStoreRepo.findByFacilityIDAndDeleted(FACILITY_ID, false)).thenReturn(null); + + assertTrue(service.getVillageMappingsByFacility(FACILITY_ID).isEmpty()); + verify(facilityVillageMappingRepo, never()).findByFacilityIDAndDeletedFalse(anyInt()); + } + } + + @Nested + @DisplayName("deleteStore") + class DeleteStoreTests { + + @Test + @DisplayName("should retire a facility that nothing else hangs off") + void deleteStore_shouldRetireUnusedFacility() throws Exception { + M_Facility stored = facility(FACILITY_ID, "PHC North"); + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setDeleted(Boolean.TRUE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(stored); + when(mainStoreRepo.findByMainFacilityIDAndDeletedOrderByFacilityName(FACILITY_ID, false)) + .thenReturn(new ArrayList<>()); + when(parkingPlaceRepository.findByFacilityIDAndDeleted(FACILITY_ID, false)).thenReturn(new ArrayList<>()); + when(vanMasterRepository.findByFacilityIDAndDeleted(FACILITY_ID, false)).thenReturn(new ArrayList<>()); + when(mainStoreRepo.save(stored)).thenReturn(stored); + + assertTrue(service.deleteStore(request).getDeleted()); + } + + @Test + @DisplayName("should refuse to retire a facility that still has live children") + void deleteStore_shouldRefuseWhenChildrenAreLive() { + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setDeleted(Boolean.TRUE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(facility(FACILITY_ID, "PHC North")); + when(mainStoreRepo.findByMainFacilityIDAndDeletedOrderByFacilityName(FACILITY_ID, false)) + .thenReturn(new ArrayList<>(List.of(facility(502, "Sub Centre")))); + + Exception thrown = assertThrows(Exception.class, () -> service.deleteStore(request)); + assertEquals("Child Stores are still active", thrown.getMessage()); + } + + @Test + @DisplayName("should refuse to retire a facility a parking place still points at") + void deleteStore_shouldRefuseWhenMappedToParkingPlace() { + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setDeleted(Boolean.TRUE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(facility(FACILITY_ID, "PHC North")); + when(mainStoreRepo.findByMainFacilityIDAndDeletedOrderByFacilityName(FACILITY_ID, false)) + .thenReturn(new ArrayList<>()); + when(parkingPlaceRepository.findByFacilityIDAndDeleted(FACILITY_ID, false)) + .thenReturn(List.of(new M_Parkingplace())); + + Exception thrown = assertThrows(Exception.class, () -> service.deleteStore(request)); + assertEquals("Store mapped to parking place", thrown.getMessage()); + } + + @Test + @DisplayName("should refuse to retire a facility a van still points at") + void deleteStore_shouldRefuseWhenMappedToVan() { + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setDeleted(Boolean.TRUE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(facility(FACILITY_ID, "PHC North")); + when(mainStoreRepo.findByMainFacilityIDAndDeletedOrderByFacilityName(FACILITY_ID, false)) + .thenReturn(new ArrayList<>()); + when(parkingPlaceRepository.findByFacilityIDAndDeleted(FACILITY_ID, false)).thenReturn(new ArrayList<>()); + when(vanMasterRepository.findByFacilityIDAndDeleted(FACILITY_ID, false)) + .thenReturn(List.of(new M_Van())); + + Exception thrown = assertThrows(Exception.class, () -> service.deleteStore(request)); + assertEquals("Store mapped to van", thrown.getMessage()); + } + + @Test + @DisplayName("should reinstate a top-level facility without further checks") + void deleteStore_shouldReinstateTopLevelFacility() throws Exception { + M_Facility stored = facility(FACILITY_ID, "PHC North"); + stored.setDeleted(Boolean.TRUE); + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setDeleted(Boolean.FALSE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(stored); + when(mainStoreRepo.save(stored)).thenReturn(stored); + + assertFalse(service.deleteStore(request).getDeleted()); + } + + @Test + @DisplayName("should reinstate a child facility once its parent is live again") + void deleteStore_shouldReinstateChildUnderLiveParent() throws Exception { + M_Facility stored = facility(FACILITY_ID, "Sub Centre"); + stored.setMainFacilityID(500); + M_Facility request = facility(FACILITY_ID, "Sub Centre"); + request.setDeleted(Boolean.FALSE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(stored); + when(mainStoreRepo.findByFacilityIDAndDeleted(500, false)).thenReturn(facility(500, "PHC North")); + when(mainStoreRepo.save(stored)).thenReturn(stored); + + assertFalse(service.deleteStore(request).getDeleted()); + } + + @Test + @DisplayName("should refuse to reinstate a child whose parent is still retired") + void deleteStore_shouldRefuseReinstatingUnderRetiredParent() { + M_Facility stored = facility(FACILITY_ID, "Sub Centre"); + stored.setMainFacilityID(500); + M_Facility request = facility(FACILITY_ID, "Sub Centre"); + request.setDeleted(Boolean.FALSE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(stored); + when(mainStoreRepo.findByFacilityIDAndDeleted(500, false)).thenReturn(null); + + Exception thrown = assertThrows(Exception.class, () -> service.deleteStore(request)); + assertEquals("Parent Stores are still inactive", thrown.getMessage()); + } + + @Test + @DisplayName("should refuse a facility that is not on record") + void deleteStore_shouldRefuseUnknownFacility() { + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setDeleted(Boolean.TRUE); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(null); + + Exception thrown = assertThrows(Exception.class, () -> service.deleteStore(request)); + assertEquals("No store available", thrown.getMessage()); + } + } + + @Nested + @DisplayName("mapStore and deleteMapStore") + class MappingTests { + + private M_facilityMap mapping(boolean isMain) { + M_facilityMap mapping = new M_facilityMap(); + mapping.setFacilityID(FACILITY_ID); + mapping.setIsMainFacility(isMain); + mapping.setCreatedBy("admin"); + return mapping; + } + + @Test + @DisplayName("mapStore should free the previous parking place before claiming the new one") + void mapStore_shouldFreePreviousParkingPlace() { + M_facilityMap request = mapping(true); + request.setParkingPlaceID(701); + request.setOldParkingPlaceID(700); + when(parkingPlaceRepository.updatePPMap(anyInt(), any(), anyString(), any())).thenReturn(1); + + assertEquals(1, service.mapStore(List.of(request))); + verify(parkingPlaceRepository).updatePPMap(700, null, "admin", null); + verify(parkingPlaceRepository).updatePPMap(701, FACILITY_ID, "admin", true); + } + + @Test + @DisplayName("mapStore should free the previous van before claiming the new one") + void mapStore_shouldFreePreviousVan() { + M_facilityMap request = mapping(false); + request.setVanID(801); + request.setOldVanID(800); + when(vanMasterRepository.updateVanMap(anyInt(), any(), anyString(), any())).thenReturn(1); + + assertEquals(1, service.mapStore(List.of(request))); + verify(vanMasterRepository).updateVanMap(800, null, "admin", null); + verify(vanMasterRepository).updateVanMap(801, FACILITY_ID, "admin", true); + } + + @Test + @DisplayName("mapStore should leave a main facility alone when no parking place is named") + void mapStore_shouldLeaveMainFacilityAloneWithoutParkingPlace() { + assertEquals(0, service.mapStore(List.of(mapping(true)))); + verify(parkingPlaceRepository, never()).updatePPMap(anyInt(), any(), anyString(), any()); + } + + @Test + @DisplayName("deleteMapStore should free the parking place once no van hangs off it") + void deleteMapStore_shouldFreeParkingPlace() throws Exception { + M_facilityMap request = mapping(true); + request.setParkingPlaceID(701); + when(vanMasterRepository.findByParkingPlaceIDAndFacilityIDIsNotNull(701)).thenReturn(new ArrayList<>()); + when(parkingPlaceRepository.updatePPMap(701, null, "admin", null)).thenReturn(1); + + assertEquals(1, service.deleteMapStore(request)); + } + + @Test + @DisplayName("deleteMapStore should refuse a parking place that still has a mapped van") + void deleteMapStore_shouldRefuseParkingPlaceWithMappedVan() { + M_facilityMap request = mapping(true); + request.setParkingPlaceID(701); + when(vanMasterRepository.findByParkingPlaceIDAndFacilityIDIsNotNull(701)) + .thenReturn(List.of(new M_Van())); + + Exception thrown = assertThrows(Exception.class, () -> service.deleteMapStore(request)); + assertEquals("Please Unmap van under this Parking Place", thrown.getMessage()); + } + + @Test + @DisplayName("deleteMapStore should free the van when no parking place is named") + void deleteMapStore_shouldFreeVan() throws Exception { + M_facilityMap request = mapping(false); + request.setVanID(801); + when(vanMasterRepository.updateVanMap(801, null, "admin", null)).thenReturn(1); + + assertEquals(1, service.deleteMapStore(request)); + } + } + + @Nested + @DisplayName("createFacilityWithHierarchy") + class CreateHierarchyTests { + + @Test + @DisplayName("should refuse a facility whose name is already taken in the block") + void create_shouldRefuseDuplicateNameInBlock() { + when(mainStoreRepo.existsByFacilityNameAndBlockIDAndDeletedFalse("PHC North", BLOCK_ID)) + .thenReturn(true); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.createFacilityWithHierarchy(facility(null, "PHC North"), null, null, null)); + assertEquals("Facility with this name already exists in this block", thrown.getMessage()); + } + + @Test + @DisplayName("should refuse a child that does not sit one level below the new facility") + void create_shouldRefuseChildAtWrongLevel() { + M_Facility request = facility(null, "PHC North"); + request.setFacilityTypeID(3); + M_Facility child = facility(502, "Sub Centre"); + child.setFacilityTypeID(9); + when(facilityTypeRepo.findByFacilityTypeID(3)).thenReturn(facilityType(3, 2)); + when(facilityTypeRepo.findByFacilityTypeID(9)).thenReturn(facilityType(9, 5)); + when(mainStoreRepo.findByFacilityIDAndDeleted(502, false)).thenReturn(child); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.createFacilityWithHierarchy(request, null, null, List.of(502))); + assertTrue(thrown.getMessage().contains("Hierarchy level mismatch"), thrown.getMessage()); + } + + @Test + @DisplayName("should attach the villages the new facility serves") + void create_shouldAttachServedVillages() { + M_Facility request = facility(null, "PHC North"); + request.setCreatedBy("admin"); + M_Facility saved = facility(FACILITY_ID, "PHC North"); + when(mainStoreRepo.save(request)).thenReturn(saved); + when(facilityVillageMappingRepo + .findByFacilityIDAndDistrictBranchIDAndDeletedTrue(FACILITY_ID, 601)).thenReturn(null); + + service.createFacilityWithHierarchy(request, List.of(601), 601, null); + + verify(facilityVillageMappingRepo).save(any(FacilityVillageMapping.class)); + assertEquals(601, request.getMainVillageID()); + } + + @Test + @DisplayName("should reinstate a village mapping that was previously retired") + void create_shouldReinstateRetiredVillageMapping() { + M_Facility request = facility(null, "PHC North"); + request.setCreatedBy("admin"); + M_Facility saved = facility(FACILITY_ID, "PHC North"); + FacilityVillageMapping retired = new FacilityVillageMapping(); + retired.setDeleted(Boolean.TRUE); + when(mainStoreRepo.save(request)).thenReturn(saved); + when(facilityVillageMappingRepo + .findByFacilityIDAndDistrictBranchIDAndDeletedTrue(FACILITY_ID, 601)).thenReturn(retired); + + service.createFacilityWithHierarchy(request, List.of(601), 601, null); + + assertFalse(retired.getDeleted()); + assertEquals("admin", retired.getModifiedBy()); + } + + @Test + @DisplayName("should re-parent the children the new facility takes over") + void create_shouldReparentChildren() { + M_Facility request = facility(null, "PHC North"); + request.setCreatedBy("admin"); + M_Facility saved = facility(FACILITY_ID, "PHC North"); + M_Facility child = facility(502, "Sub Centre"); + child.setProviderServiceMapID(null); + child.setIsMainFacility(Boolean.TRUE); + when(mainStoreRepo.save(request)).thenReturn(saved); + when(mainStoreRepo.findByFacilityID(502)).thenReturn(child); + + service.createFacilityWithHierarchy(request, null, null, List.of(502)); + + assertEquals(FACILITY_ID, child.getParentFacilityID()); + verify(mainStoreRepo).updateStoreFields(502, false, FACILITY_ID, "SUB"); + } + } + + @Nested + @DisplayName("deleteFacilityWithHierarchy") + class DeleteHierarchyTests { + + @Test + @DisplayName("should release the children before retiring the facility") + void delete_shouldReleaseChildrenFirst() throws Exception { + M_Facility stored = facility(FACILITY_ID, "PHC North"); + M_Facility child = facility(502, "Sub Centre"); + child.setParentFacilityID(FACILITY_ID); + child.setProviderServiceMapID(null); + FacilityVillageMapping villageMapping = new FacilityVillageMapping(); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(stored); + when(mainStoreRepo.findByParentFacilityIDAndDeletedFalseOrderByFacilityName(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(child))); + when(mainStoreRepo.save(stored)).thenReturn(stored); + when(facilityVillageMappingRepo.findByFacilityIDAndDeletedFalse(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(villageMapping))); + + M_Facility retired = service.deleteFacilityWithHierarchy(FACILITY_ID, "admin"); + + assertTrue(retired.getDeleted()); + assertNull(child.getParentFacilityID(), "a released child must not point at a retired parent"); + verify(mainStoreRepo).updateStoreFields(502, true, null, "MAIN"); + verify(ashaSupervisorMappingService).cascadeDeleteByFacilityID(FACILITY_ID, "admin"); + assertTrue(villageMapping.getDeleted(), "the villages it served must be released too"); + } + + @Test + @DisplayName("should refuse a facility that is not on record") + void delete_shouldRefuseUnknownFacility() { + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(null); + + Exception thrown = assertThrows(Exception.class, + () -> service.deleteFacilityWithHierarchy(FACILITY_ID, "admin")); + assertEquals("Facility not found", thrown.getMessage()); + } + } + + @Nested + @DisplayName("updateFacilityWithHierarchy") + class UpdateHierarchyTests { + + @Test + @DisplayName("should refuse a facility that is not on record") + void update_shouldRefuseUnknownFacility() { + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(null); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.updateFacilityWithHierarchy(facility(FACILITY_ID, "PHC North"), null, null, null)); + assertEquals("Facility not found", thrown.getMessage()); + } + + @Test + @DisplayName("should refuse a rename onto a name another facility in the block already uses") + void update_shouldRefuseDuplicateNameInBlock() { + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(facility(FACILITY_ID, "old name")); + when(mainStoreRepo.existsByFacilityNameAndBlockIDAndNotFacilityID("PHC North", BLOCK_ID, FACILITY_ID)) + .thenReturn(true); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.updateFacilityWithHierarchy(facility(FACILITY_ID, "PHC North"), null, null, null)); + assertEquals("Facility with this name already exists in this block", thrown.getMessage()); + } + + @Test + @DisplayName("should copy only the fields the request actually sets") + void update_shouldCopyOnlySuppliedFields() { + M_Facility existing = facility(FACILITY_ID, "old name"); + existing.setRuralUrban("Rural"); + M_Facility request = new M_Facility(); + request.setFacilityID(FACILITY_ID); + request.setFacilityName("PHC North"); + request.setModifiedBy("admin"); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(existing); + when(mainStoreRepo.save(existing)).thenReturn(existing); + + service.updateFacilityWithHierarchy(request, null, 601, null); + + assertEquals("PHC North", existing.getFacilityName()); + assertEquals("Rural", existing.getRuralUrban(), "an unset field keeps the value on record"); + assertEquals(601, existing.getMainVillageID()); + } + + @Test + @DisplayName("should release the villages the facility no longer serves") + void update_shouldReleaseDroppedVillages() { + M_Facility existing = facility(FACILITY_ID, "PHC North"); + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setModifiedBy("admin"); + FacilityVillageMapping dropped = new FacilityVillageMapping(); + dropped.setDistrictBranchID(602); + FacilityVillageMapping kept = new FacilityVillageMapping(); + kept.setDistrictBranchID(601); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(existing); + when(mainStoreRepo.save(existing)).thenReturn(existing); + when(facilityVillageMappingRepo.findByFacilityIDAndDeletedFalse(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(dropped, kept))); + + service.updateFacilityWithHierarchy(request, List.of(601), 601, null); + + assertTrue(dropped.getDeleted(), "a village dropped from the list must be released"); + assertFalse(Boolean.TRUE.equals(kept.getDeleted())); + } + + @Test + @DisplayName("should refuse a child that does not sit one level below the facility") + void update_shouldRefuseChildAtWrongLevel() { + M_Facility existing = facility(FACILITY_ID, "PHC North"); + existing.setFacilityTypeID(3); + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setModifiedBy("admin"); + M_Facility child = facility(502, "Sub Centre"); + child.setFacilityTypeID(9); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(existing); + when(mainStoreRepo.save(existing)).thenReturn(existing); + when(facilityTypeRepo.findByFacilityTypeID(3)).thenReturn(facilityType(3, 2)); + when(facilityTypeRepo.findByFacilityTypeID(9)).thenReturn(facilityType(9, 5)); + when(mainStoreRepo.findByFacilityIDAndDeleted(502, false)).thenReturn(child); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> service.updateFacilityWithHierarchy(request, null, null, List.of(502))); + assertTrue(thrown.getMessage().contains("Hierarchy level mismatch"), thrown.getMessage()); + } + + @Test + @DisplayName("should promote a child that is no longer part of the hierarchy") + void update_shouldPromoteReleasedChild() { + M_Facility existing = facility(FACILITY_ID, "PHC North"); + M_Facility request = facility(FACILITY_ID, "PHC North"); + request.setModifiedBy("admin"); + M_Facility released = facility(503, "Sub Centre B"); + released.setProviderServiceMapID(null); + when(mainStoreRepo.findByFacilityID(FACILITY_ID)).thenReturn(existing); + when(mainStoreRepo.save(existing)).thenReturn(existing); + when(mainStoreRepo.findByParentFacilityIDAndDeletedFalseOrderByFacilityName(FACILITY_ID)) + .thenReturn(new ArrayList<>(List.of(released))); + + service.updateFacilityWithHierarchy(request, null, null, new ArrayList<>()); + + verify(mainStoreRepo).clearParentFacilityID(FACILITY_ID, "admin"); + verify(mainStoreRepo).updateStoreFields(503, true, null, "MAIN"); + } + } +} diff --git a/src/test/java/com/iemr/admin/service/supplier/SupplierServiceImplTest.java b/src/test/java/com/iemr/admin/service/supplier/SupplierServiceImplTest.java new file mode 100644 index 0000000..e3aedce --- /dev/null +++ b/src/test/java/com/iemr/admin/service/supplier/SupplierServiceImplTest.java @@ -0,0 +1,113 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.supplier; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.supplier.M_Supplier; +import com.iemr.admin.repo.supplier.SupplierRepo; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** The supplier service is thin over its repository, but it answers nothing rather than an empty batch. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("SupplierServiceImpl Test Suite") +class SupplierServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private SupplierRepo supplierRepo; + + @InjectMocks + private SupplierServiceImpl service; + + @Test + @DisplayName("createSupplier should answer the records it stored") + void create_shouldAnswerStoredRecords() { + ArrayList stored = new ArrayList<>(List.of(new M_Supplier())); + when(supplierRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createSupplier(new ArrayList<>())); + } + + @Test + @DisplayName("createSupplier should answer nothing when the store took nothing") + void create_shouldAnswerNothingWhenNothingStored() { + when(supplierRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertNull(service.createSupplier(new ArrayList<>())); + } + + @Test + @DisplayName("the lookups should each reach their own repository query") + void lookups_shouldReachTheirOwnQuery() { + M_Supplier record = new M_Supplier(); + ArrayList records = new ArrayList<>(List.of(record)); + when(supplierRepo.getSupplierData(PSM_ID)).thenReturn(records); + when(supplierRepo.geteditedData(RECORD_ID)).thenReturn(record); + when(supplierRepo.save(record)).thenReturn(record); + + assertSame(records, service.getSupplier(PSM_ID)); + assertSame(record, service.editSupplier(RECORD_ID)); + assertSame(record, service.saveEditedData(record)); + } + + @Test + @DisplayName("checkSupplierCode should report a code the provider already uses") + void check_shouldReportUsedCode() { + M_Supplier request = new M_Supplier(); + request.setSupplierCode("C-1"); + request.setProviderServiceMapID(PSM_ID); + when(supplierRepo.findBySupplierCodeAndProviderServiceMapID("C-1", PSM_ID)).thenReturn(List.of(new M_Supplier())); + + assertTrue(service.checkSupplierCode(request)); + } + + @Test + @DisplayName("checkSupplierCode should clear a code nobody uses yet") + void check_shouldClearFreeCode() { + M_Supplier request = new M_Supplier(); + request.setSupplierCode("C-2"); + request.setProviderServiceMapID(PSM_ID); + when(supplierRepo.findBySupplierCodeAndProviderServiceMapID("C-2", PSM_ID)).thenReturn(new ArrayList()); + + assertFalse(service.checkSupplierCode(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/telemedicine/TMServiceImplTest.java b/src/test/java/com/iemr/admin/service/telemedicine/TMServiceImplTest.java new file mode 100644 index 0000000..d8b5068 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/telemedicine/TMServiceImplTest.java @@ -0,0 +1,172 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.telemedicine; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.telemedicine.M_UserTemp; +import com.iemr.admin.data.telemedicine.Specialization; +import com.iemr.admin.data.telemedicine.TMinput; +import com.iemr.admin.data.telemedicine.UserSpecializationMapping; +import com.iemr.admin.repo.telemedicine.SpecializationRepo; +import com.iemr.admin.repo.telemedicine.UserRepo; +import com.iemr.admin.repo.telemedicine.UserSpecializationMappingRepo; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The telemedicine service rebuilds the specialist roster out of the wide user + * row the reporting query answers, and records which specialities each holds. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("TMServiceImpl Test Suite") +class TMServiceImplTest { + + private static final Integer PROVIDER_ID = 5; + private static final String SCREEN = "TM"; + + @Mock + private UserRepo userRepo; + + @Mock + private SpecializationRepo specializationRepo; + + @Mock + private UserSpecializationMappingRepo userSpecializationMappingRepo; + + @InjectMocks + private TMServiceImpl service; + + private static TMinput request() { + TMinput input = new TMinput(); + input.setServiceproviderID(PROVIDER_ID); + input.setScreenName(SCREEN); + return input; + } + + /** The reporting query answers the whole user row; only some columns are read. */ + private static Object[] userRow() { + Object[] row = new Object[33]; + row[0] = 3117; + row[2] = "Asha"; + row[4] = "Rao"; + row[12] = 7; + row[14] = "asha.rao"; + row[22] = "asha.rao@example.org"; + row[24] = (short) 5; + row[32] = false; + return row; + } + + @Test + @DisplayName("getUser should rebuild one specialist per row the query answers") + void getUser_shouldRebuildEachRow() { + when(userRepo.getUserTM(PROVIDER_ID, SCREEN)).thenReturn(new ArrayList<>(List.of(userRow()))); + + ArrayList users = service.getUser(request()); + + assertEquals(1, users.size()); + M_UserTemp user = users.get(0); + assertEquals(3117L, user.getUserID()); + assertEquals("Asha", user.getFirstName()); + assertEquals("Rao", user.getLastName()); + assertEquals("asha.rao", user.getUserName()); + assertEquals("asha.rao@example.org", user.getEmailID()); + assertEquals(7, user.getDesignationID()); + assertEquals(PROVIDER_ID, user.getServiceProviderID()); + assertEquals(false, user.getDeleted()); + assertNull(user.getDesignation(), "the designation record must not travel with the roster row"); + } + + @Test + @DisplayName("getUser should answer nothing when the provider has no specialist on that screen") + void getUser_shouldAnswerNothingForProviderWithoutSpecialists() { + when(userRepo.getUserTM(PROVIDER_ID, SCREEN)).thenReturn(new ArrayList<>()); + + assertTrue(service.getUser(request()).isEmpty()); + } + + @Test + @DisplayName("getSpecialization should answer only the specialities still in use") + void getSpecialization_shouldAnswerLiveSpecialities() { + ArrayList live = new ArrayList<>(List.of(new Specialization())); + when(specializationRepo.findByDeleted(false)).thenReturn(live); + + assertSame(live, service.getSpecialization()); + } + + @Test + @DisplayName("getUserSpecialization should answer the specialities held under the provider asked for") + void getUserSpecialization_shouldAnswerSpecialitiesOfProvider() { + ArrayList held = new ArrayList<>(List.of(new UserSpecializationMapping())); + when(userSpecializationMappingRepo.findByServiceprovider(PROVIDER_ID)).thenReturn(held); + + assertSame(held, service.getUserSpecialization(PROVIDER_ID)); + } + + @Test + @DisplayName("saveUserSpecialization should answer the specialities the repository stored") + void saveUserSpecialization_shouldAnswerStoredSpecialities() { + ArrayList stored = new ArrayList<>(List.of(new UserSpecializationMapping())); + when(userSpecializationMappingRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.saveUserSpecialization(new ArrayList<>())); + } + + @Test + @DisplayName("findUserSpecialization and saveoneUserSpecialization should each reach their own query") + void singleRecordOperations_shouldReachTheirOwnQuery() { + UserSpecializationMapping stored = new UserSpecializationMapping(); + stored.setUserSpecializationMapID(8001); + when(userSpecializationMappingRepo.findByUserSpecializationMapID(8001)).thenReturn(stored); + when(userSpecializationMappingRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.findUserSpecialization(stored)); + assertSame(stored, service.saveoneUserSpecialization(stored)); + } + + @Test + @DisplayName("findUserSpecialization should answer nothing when the speciality is unknown") + void findUserSpecialization_shouldAnswerNothingForUnknownSpeciality() { + UserSpecializationMapping request = new UserSpecializationMapping(); + request.setUserSpecializationMapID(-1); + when(userSpecializationMappingRepo.findByUserSpecializationMapID(-1)).thenReturn(null); + + assertNull(service.findUserSpecialization(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/telemedicine/VideoConsultationAPIServiceImplTest.java b/src/test/java/com/iemr/admin/service/telemedicine/VideoConsultationAPIServiceImplTest.java new file mode 100644 index 0000000..ed4cec7 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/telemedicine/VideoConsultationAPIServiceImplTest.java @@ -0,0 +1,196 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.telemedicine; + +import java.util.HashMap; + +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.admin.utils.exception.VideoConsultationException; +import com.iemr.admin.utils.http.HttpUtils; + +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.Mockito.when; + +/** + * The API service is the only thing that talks to the external conferencing + * platform, so it is checked against a stand-in for that platform's replies. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VideoConsultationAPIServiceImpl Test Suite") +class VideoConsultationAPIServiceImplTest { + + private static final String BASE_URL = "https://conferencing.example.org"; + private static final Long REMOTE_ID = 990011L; + + @Mock + private HttpUtils httpUtils; + + private HttpUtils originalHttpUtils; + + private VideoConsultationAPIServiceImpl service; + + @BeforeEach + @DisplayName("Stand in for the external platform and point the service at a stub address") + void setUp() { + originalHttpUtils = (HttpUtils) ReflectionTestUtils.getField(VideoConsultationAPIServiceImpl.class, + "httpUtils"); + ReflectionTestUtils.setField(VideoConsultationAPIServiceImpl.class, "httpUtils", httpUtils); + service = new VideoConsultationAPIServiceImpl(); + ReflectionTestUtils.setField(service, "videoConsultationAuth", "api-key"); + ReflectionTestUtils.setField(service, "videoConsultationBaseUrl", BASE_URL); + ReflectionTestUtils.setField(service, "videoConsultationCreateUser", + "videoConsultation-base-url/api/users"); + ReflectionTestUtils.setField(service, "videoConsultationEditUser", "videoConsultation-base-url/api/users"); + } + + @AfterEach + @DisplayName("Put the real platform client back so no other suite sees the stand-in") + void tearDown() { + ReflectionTestUtils.setField(VideoConsultationAPIServiceImpl.class, "httpUtils", originalHttpUtils); + } + + private static HashMap account() { + HashMap obj = new HashMap<>(); + obj.put("name", "Asha"); + obj.put("email", "asha.rao@example.org"); + return obj; + } + + @Test + @DisplayName("createUser should answer the id the platform gave the new account") + void createUser_shouldAnswerRemoteId() throws Exception { + when(httpUtils.post(anyString(), anyString(), any())) + .thenReturn("{\"userid\":990011,\"result\":\"ok\"}"); + + assertEquals(REMOTE_ID, service.createUser(account())); + } + + @Test + @DisplayName("createUser should send the account to the address the platform is configured at") + void createUser_shouldSendToConfiguredAddress() throws Exception { + when(httpUtils.post(anyString(), anyString(), any())) + .thenReturn("{\"userid\":990011,\"result\":\"ok\"}"); + ArgumentCaptor url = ArgumentCaptor.forClass(String.class); + ArgumentCaptor body = ArgumentCaptor.forClass(String.class); + ArgumentCaptor> header = ArgumentCaptor.forClass(HashMap.class); + + service.createUser(account()); + + org.mockito.Mockito.verify(httpUtils).post(url.capture(), body.capture(), header.capture()); + assertEquals(BASE_URL + "/api/users", url.getValue()); + assertTrue(body.getValue().contains("asha.rao@example.org"), body.getValue()); + assertEquals("api-key", header.getValue().get("X-APIkey-Header")); + assertEquals("application/json", header.getValue().get("Content-Type")); + } + + @Test + @DisplayName("createUser should report the platform's own reason when it refuses the account") + void createUser_shouldReportRemoteRefusal() { + when(httpUtils.post(anyString(), anyString(), any())) + .thenReturn("{\"userid\":0,\"result\":\"email already registered\"}"); + + VideoConsultationException refusal = assertThrows(VideoConsultationException.class, + () -> service.createUser(account())); + + assertEquals("email already registered", refusal.getMessage()); + } + + @Test + @DisplayName("createUser should give up when the platform answers something that is not an account") + void createUser_shouldGiveUpOnUnreadableReply() { + when(httpUtils.post(anyString(), anyString(), any())).thenReturn("service unavailable"); + + assertThrows(RuntimeException.class, () -> service.createUser(account())); + } + + @Test + @DisplayName("editUser should answer the id the platform confirmed") + void editUser_shouldAnswerConfirmedId() throws Exception { + when(httpUtils.put(anyString(), anyString(), any())) + .thenReturn("{\"userid\":990011,\"result\":\"ok\"}"); + + assertEquals(REMOTE_ID, service.editUser(account(), REMOTE_ID, "psmri")); + } + + @Test + @DisplayName("editUser should address the account and domain being changed") + void editUser_shouldAddressAccountAndDomain() throws Exception { + when(httpUtils.put(anyString(), anyString(), any())) + .thenReturn("{\"userid\":990011,\"result\":\"ok\"}"); + ArgumentCaptor url = ArgumentCaptor.forClass(String.class); + + service.editUser(account(), REMOTE_ID, "psmri"); + + org.mockito.Mockito.verify(httpUtils).put(url.capture(), anyString(), any()); + assertEquals(BASE_URL + "/api/users/990011/psmri", url.getValue()); + } + + @Test + @DisplayName("editUser should report the platform's own reason when it refuses the change") + void editUser_shouldReportRemoteRefusal() { + when(httpUtils.put(anyString(), anyString(), any())) + .thenReturn("{\"userid\":0,\"result\":\"account is locked\"}"); + + VideoConsultationException refusal = assertThrows(VideoConsultationException.class, + () -> service.editUser(account(), REMOTE_ID, "psmri")); + + assertEquals("account is locked", refusal.getMessage()); + } + + @Test + @DisplayName("editUser should give up when the platform answers something that is not an account") + void editUser_shouldGiveUpOnUnreadableReply() { + when(httpUtils.put(anyString(), anyString(), any())).thenReturn("gateway timeout"); + + assertThrows(RuntimeException.class, () -> service.editUser(account(), REMOTE_ID, "psmri")); + } + + @Test + @DisplayName("createUser should still reach the platform when no API key is configured") + void createUser_shouldReachPlatformWithoutApiKey() throws Exception { + ReflectionTestUtils.setField(service, "videoConsultationAuth", null); + when(httpUtils.post(anyString(), anyString(), any())) + .thenReturn("{\"userid\":990011,\"result\":\"ok\"}"); + ArgumentCaptor> header = ArgumentCaptor.forClass(HashMap.class); + + service.createUser(account()); + + org.mockito.Mockito.verify(httpUtils).post(anyString(), anyString(), header.capture()); + assertTrue(header.getValue().get("X-APIkey-Header") == null, "no key must be sent when none is configured"); + } +} diff --git a/src/test/java/com/iemr/admin/service/telemedicine/VideoConsultationServiceImplTest.java b/src/test/java/com/iemr/admin/service/telemedicine/VideoConsultationServiceImplTest.java new file mode 100644 index 0000000..0ebb228 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/telemedicine/VideoConsultationServiceImplTest.java @@ -0,0 +1,269 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.telemedicine; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.telemedicine.M_UserTemp; +import com.iemr.admin.data.telemedicine.UserVideoConsultation; +import com.iemr.admin.data.telemedicine.VideoConsultationDomain; +import com.iemr.admin.repo.telemedicine.UserRepo; +import com.iemr.admin.repo.telemedicine.UserVideoConsultationRepo; +import com.iemr.admin.repo.telemedicine.VideoConsultationDomainRepo; +import com.iemr.admin.utils.exception.VideoConsultationException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +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; + +/** + * The video consultation service keeps a clinician's account on the external + * conferencing platform in step with the account this system stores. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VideoConsultationServiceImpl Test Suite") +class VideoConsultationServiceImplTest { + + private static final Long USER_ID = 3117L; + private static final Long MAP_ID = 8001L; + private static final Long REMOTE_ID = 990011L; + private static final Integer PROVIDER_ID = 5; + + @Mock + private UserRepo userRepo; + + @Mock + private UserVideoConsultationRepo userVideoConsultationRepo; + + @Mock + private VideoConsultationDomainRepo videoConsultationDomainRepo; + + @Mock + private VideoConsultationAPIInter videoConsultationAPIInter; + + @InjectMocks + private VideoConsultationServiceImpl service; + + private static M_UserTemp clinician() { + M_UserTemp user = new M_UserTemp(); + user.setUserID(USER_ID); + user.setFirstName("Asha"); + user.setLastName("Rao"); + user.setUserName("asha.rao"); + return user; + } + + private static UserVideoConsultation account() { + UserVideoConsultation account = new UserVideoConsultation(); + account.setUserVideoConsultationMapID(MAP_ID); + account.setUserID(USER_ID); + account.setVideoConsultationEmailID("asha.rao@example.org"); + account.setVideoConsultationPassword("secret"); + account.setVideoConsultationDomain("psmri"); + account.setModifiedBy("admin"); + return account; + } + + @Test + @DisplayName("getunmappedUser should answer the clinicians who have no account yet") + void getunmappedUser_shouldAnswerCliniciansWithoutAccount() { + ArrayList free = new ArrayList<>(List.of(clinician())); + when(userRepo.getunmappedVideoConsultationUser(PROVIDER_ID, 7)).thenReturn(free); + + assertSame(free, service.getunmappedUser(PROVIDER_ID, 7)); + } + + @Test + @DisplayName("createUser should open the remote account and record the id it was given") + void createUser_shouldOpenRemoteAccountAndRecordId() throws Exception { + UserVideoConsultation request = account(); + when(userRepo.findByUserID(USER_ID)).thenReturn(clinician()); + when(videoConsultationAPIInter.createUser(any())).thenReturn(REMOTE_ID); + when(userVideoConsultationRepo.save(request)).thenReturn(request); + + assertSame(request, service.createUser(request)); + assertEquals(REMOTE_ID, request.getVideoConsultationID()); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(HashMap.class); + verify(videoConsultationAPIInter).createUser(captor.capture()); + assertEquals("Asha", captor.getValue().get("name")); + assertEquals("Rao", captor.getValue().get("surname")); + assertEquals("asha.rao", captor.getValue().get("member")); + assertEquals("psmri", captor.getValue().get("domain")); + } + + @Test + @DisplayName("createUser should refuse a clinician this system does not know") + void createUser_shouldRefuseUnknownClinician() throws Exception { + when(userRepo.findByUserID(USER_ID)).thenReturn(null); + + VideoConsultationException refusal = assertThrows(VideoConsultationException.class, + () -> service.createUser(account())); + + assertEquals("Invalid User", refusal.getMessage()); + verify(videoConsultationAPIInter, never()).createUser(any()); + } + + @Test + @DisplayName("createUser should give up when the remote platform refuses the account") + void createUser_shouldGiveUpWhenRemoteRefuses() throws Exception { + when(userRepo.findByUserID(USER_ID)).thenReturn(clinician()); + when(videoConsultationAPIInter.createUser(any())) + .thenThrow(new VideoConsultationException("email already registered")); + + assertThrows(VideoConsultationException.class, () -> service.createUser(account())); + verify(userVideoConsultationRepo, never()).save(any()); + } + + @Test + @DisplayName("editUser should record the new sign-in details against the stored account") + void editUser_shouldRecordNewSignInDetails() throws Exception { + UserVideoConsultation stored = account(); + stored.setVideoConsultationID(REMOTE_ID); + stored.setVideoConsultationPassword("old-secret"); + UserVideoConsultation request = account(); + request.setVideoConsultationPassword("new-secret"); + when(userVideoConsultationRepo.findByUserVideoConsultationMapID(MAP_ID)).thenReturn(stored); + when(userVideoConsultationRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.editUser(request)); + assertEquals("new-secret", stored.getVideoConsultationPassword()); + assertNull(stored.getUser(), "the clinician record must not travel back with the account"); + } + + @Test + @DisplayName("editUser should leave the remote platform alone when nothing about the sign-in changed") + void editUser_shouldLeaveRemoteAloneWhenNothingChanged() throws Exception { + UserVideoConsultation stored = account(); + stored.setVideoConsultationID(REMOTE_ID); + when(userVideoConsultationRepo.findByUserVideoConsultationMapID(MAP_ID)).thenReturn(stored); + when(userVideoConsultationRepo.save(stored)).thenReturn(stored); + + service.editUser(account()); + + verify(videoConsultationAPIInter, never()).editUser(any(), anyLong(), anyString()); + } + + @Test + @DisplayName("editUser should open a remote account when the stored one never got an id") + void editUser_shouldOpenRemoteAccountWhenIdMissing() throws Exception { + UserVideoConsultation stored = account(); + when(userVideoConsultationRepo.findByUserVideoConsultationMapID(MAP_ID)).thenReturn(stored); + when(userRepo.findByUserID(USER_ID)).thenReturn(clinician()); + when(videoConsultationAPIInter.createUser(any())).thenReturn(REMOTE_ID); + when(userVideoConsultationRepo.save(stored)).thenReturn(stored); + + assertSame(stored, service.editUser(account())); + assertEquals(REMOTE_ID, stored.getVideoConsultationID()); + } + + @Test + @DisplayName("editUser should refuse an account this system does not know") + void editUser_shouldRefuseUnknownAccount() { + when(userVideoConsultationRepo.findByUserVideoConsultationMapID(MAP_ID)).thenReturn(null); + + VideoConsultationException refusal = assertThrows(VideoConsultationException.class, + () -> service.editUser(account())); + + assertEquals("Invalid MapID", refusal.getMessage()); + } + + @Test + @DisplayName("fetchmappedUser should answer the accounts held under the provider asked for") + void fetchmappedUser_shouldAnswerAccountsOfProvider() { + List held = List.of(account()); + when(userVideoConsultationRepo.fetchmappedUser(PROVIDER_ID)).thenReturn(held); + + assertSame(held, service.fetchmappedUser(PROVIDER_ID)); + } + + @Test + @DisplayName("deleteUser should carry the new status to the remote platform") + void deleteUser_shouldCarryStatusRemotely() throws Exception { + UserVideoConsultation stored = account(); + stored.setVideoConsultationID(REMOTE_ID); + when(userVideoConsultationRepo.findByUserVideoConsultationMapID(MAP_ID)).thenReturn(stored); + when(userVideoConsultationRepo.save(stored)).thenReturn(stored); + when(videoConsultationAPIInter.editUser(any(), anyLong(), anyString())).thenReturn(REMOTE_ID); + + assertSame(stored, service.deleteUser(MAP_ID, Boolean.TRUE, "supervisor")); + assertEquals(Boolean.TRUE, stored.getDeleted()); + assertEquals("supervisor", stored.getModifiedBy()); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(HashMap.class); + verify(videoConsultationAPIInter).editUser(captor.capture(), anyLong(), anyString()); + assertEquals("1", captor.getValue().get("status")); + } + + @Test + @DisplayName("deleteUser should carry the reinstated status when the account is brought back") + void deleteUser_shouldCarryReinstatedStatus() throws Exception { + UserVideoConsultation stored = account(); + stored.setVideoConsultationID(REMOTE_ID); + when(userVideoConsultationRepo.findByUserVideoConsultationMapID(MAP_ID)).thenReturn(stored); + when(userVideoConsultationRepo.save(stored)).thenReturn(stored); + + service.deleteUser(MAP_ID, Boolean.FALSE, "supervisor"); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(HashMap.class); + verify(videoConsultationAPIInter).editUser(captor.capture(), anyLong(), anyString()); + assertEquals("0", captor.getValue().get("status")); + } + + @Test + @DisplayName("deleteUser should give up when the account is unknown") + void deleteUser_shouldGiveUpForUnknownAccount() { + when(userVideoConsultationRepo.findByUserVideoConsultationMapID(MAP_ID)).thenReturn(null); + + assertThrows(NullPointerException.class, () -> service.deleteUser(MAP_ID, Boolean.TRUE, "supervisor")); + } + + @Test + @DisplayName("getdomain should answer every conferencing domain on file") + void getdomain_shouldAnswerEveryDomain() { + List domains = List.of(new VideoConsultationDomain()); + when(videoConsultationDomainRepo.findAll()).thenReturn(domains); + + assertEquals(domains, service.getdomain(PROVIDER_ID)); + } +} diff --git a/src/test/java/com/iemr/admin/service/uom/UomServiceImplTest.java b/src/test/java/com/iemr/admin/service/uom/UomServiceImplTest.java new file mode 100644 index 0000000..5770d6e --- /dev/null +++ b/src/test/java/com/iemr/admin/service/uom/UomServiceImplTest.java @@ -0,0 +1,113 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.uom; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.uom.M_Uom; +import com.iemr.admin.repo.uom.UomRepo; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** The unit of measure service is thin over its repository, but it answers nothing rather than an empty batch. */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("UomServiceImpl Test Suite") +class UomServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer RECORD_ID = 11; + + @Mock + private UomRepo uomRepo; + + @InjectMocks + private UomServiceImpl service; + + @Test + @DisplayName("createDrugtypeData should answer the records it stored") + void create_shouldAnswerStoredRecords() { + ArrayList stored = new ArrayList<>(List.of(new M_Uom())); + when(uomRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createDrugtypeData(new ArrayList<>())); + } + + @Test + @DisplayName("createDrugtypeData should answer nothing when the store took nothing") + void create_shouldAnswerNothingWhenNothingStored() { + when(uomRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertNull(service.createDrugtypeData(new ArrayList<>())); + } + + @Test + @DisplayName("the lookups should each reach their own repository query") + void lookups_shouldReachTheirOwnQuery() { + M_Uom record = new M_Uom(); + ArrayList records = new ArrayList<>(List.of(record)); + when(uomRepo.getUom(PSM_ID)).thenReturn(records); + when(uomRepo.geteditedData(RECORD_ID)).thenReturn(record); + when(uomRepo.save(record)).thenReturn(record); + + assertSame(records, service.createDrugtypeData(PSM_ID)); + assertSame(record, service.editDrugtypeData(RECORD_ID)); + assertSame(record, service.saveeditedData(record)); + } + + @Test + @DisplayName("checkUomCode should report a code the provider already uses") + void check_shouldReportUsedCode() { + M_Uom request = new M_Uom(); + request.setuOMCode("C-1"); + request.setProviderServiceMapID(PSM_ID); + when(uomRepo.findByUOMCodeAndProviderServiceMapID("C-1", PSM_ID)).thenReturn(List.of(new M_Uom())); + + assertTrue(service.checkUomCode(request)); + } + + @Test + @DisplayName("checkUomCode should clear a code nobody uses yet") + void check_shouldClearFreeCode() { + M_Uom request = new M_Uom(); + request.setuOMCode("C-2"); + request.setProviderServiceMapID(PSM_ID); + when(uomRepo.findByUOMCodeAndProviderServiceMapID("C-2", PSM_ID)).thenReturn(new ArrayList()); + + assertFalse(service.checkUomCode(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/uptsu/FacilityServiceImplTest.java b/src/test/java/com/iemr/admin/service/uptsu/FacilityServiceImplTest.java new file mode 100644 index 0000000..e39f211 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/uptsu/FacilityServiceImplTest.java @@ -0,0 +1,290 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.uptsu; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Set; + +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.uptsu.CDSSMapping; +import com.iemr.admin.data.uptsu.M_FacilityMapping; +import com.iemr.admin.data.uptsu.UploadRequest; +import com.iemr.admin.repository.uptsu.CDSSMappingRepo; +import com.iemr.admin.repository.uptsu.FacilityRepository; +import com.iemr.admin.utils.exception.IEMRException; + +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The UP TSU facility upload turns an operator's spreadsheet into the facility + * mapping rows the state's field staff are attached to, so a column read into + * the wrong field would post a health worker to the wrong facility. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("FacilityServiceImpl Test Suite") +class FacilityServiceImplTest { + + private static final Integer PSM_ID = 4001; + + /** The columns the upload reads as whole numbers rather than text. */ + private static final Set NUMERIC_COLUMNS = Set.of(7, 17, 19, 21, 23, 25, 27); + + /** The columns the upload refuses to accept blank. */ + private static final Set MANDATORY_COLUMNS = Set.of(0, 1, 15, 22, 28, 29, 39); + + @Mock + private FacilityRepository uptsuUploadRepository; + + @Mock + private CDSSMappingRepo cdssMappingRepo; + + @InjectMocks + private FacilityServiceImpl service; + + private static String uploadOf(Workbook workbook) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + workbook.write(out); + workbook.close(); + return "data:application/vnd.ms-excel;base64," + Base64.getEncoder().encodeToString(out.toByteArray()); + } + + /** Builds a one-row upload whose every column carries a usable value. */ + private static Workbook completeUpload() { + Workbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet("Facilities"); + Row header = sheet.createRow(0); + Row data = sheet.createRow(1); + for (int column = 0; column <= 40; column++) { + header.createCell(column).setCellValue("column " + column); + if (NUMERIC_COLUMNS.contains(column)) { + data.createCell(column).setCellValue(column + 100); + } else { + data.createCell(column).setCellValue("value " + column); + } + } + return workbook; + } + + private static UploadRequest requestFor(Workbook workbook) throws Exception { + UploadRequest request = new UploadRequest(); + request.setCreatedBy("admin"); + request.setProviderServiceMapID(PSM_ID); + request.setFileName("facilities.xlsx"); + request.setFileExtension("xlsx"); + request.setFileContent(uploadOf(workbook)); + return request; + } + + @Test + @DisplayName("saveFacility should turn each spreadsheet row into a facility mapping") + void saveFacility_shouldMapEachRow() throws Exception { + UploadRequest request = requestFor(completeUpload()); + when(uptsuUploadRepository.saveAll(anyList())).thenAnswer(call -> call.getArgument(0)); + + Iterable saved = service.saveFacility(request); + + List rows = new ArrayList<>(); + saved.forEach(rows::add); + assertEquals(1, rows.size()); + M_FacilityMapping mapped = rows.get(0); + assertEquals("value 0", mapped.getEmployeeCode()); + assertEquals("value 1", mapped.getEmployeeName()); + assertEquals(107, mapped.getDesignationId(), "column 7 is read as a whole number"); + assertEquals("value 22", mapped.getBlockName()); + assertEquals("value 39", mapped.getHfrCode()); + assertEquals("admin", mapped.getCreatedBy()); + assertEquals(PSM_ID, mapped.getProviderServiceMapID()); + assertEquals('N', mapped.getProcessed()); + assertEquals(false, mapped.isDeleted()); + } + + @Test + @DisplayName("saveFacility should retire the provider's previous upload before storing the new one") + void saveFacility_shouldRetirePreviousUpload() throws Exception { + UploadRequest request = requestFor(completeUpload()); + when(uptsuUploadRepository.saveAll(anyList())).thenAnswer(call -> call.getArgument(0)); + + service.saveFacility(request); + + verify(uptsuUploadRepository).updatedeleteStatus(org.mockito.ArgumentMatchers.eq(PSM_ID), any(), anyString()); + } + + @Test + @DisplayName("saveFacility should store every row of a multi-row upload") + void saveFacility_shouldStoreEveryRow() throws Exception { + Workbook workbook = completeUpload(); + Sheet sheet = workbook.getSheetAt(0); + Row second = sheet.createRow(2); + for (int column = 0; column <= 40; column++) { + if (NUMERIC_COLUMNS.contains(column)) { + second.createCell(column).setCellValue(column + 200); + } else { + second.createCell(column).setCellValue("second " + column); + } + } + when(uptsuUploadRepository.saveAll(anyList())).thenAnswer(call -> call.getArgument(0)); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + service.saveFacility(requestFor(workbook)); + + verify(uptsuUploadRepository).saveAll(captor.capture()); + assertEquals(2, captor.getValue().size()); + } + + @Test + @DisplayName("saveFacility should leave an optional column unset rather than invent a value") + void saveFacility_shouldLeaveOptionalColumnUnset() throws Exception { + Workbook workbook = completeUpload(); + workbook.getSheetAt(0).getRow(1).getCell(2).setBlank(); + when(uptsuUploadRepository.saveAll(anyList())).thenAnswer(call -> call.getArgument(0)); + + Iterable saved = service.saveFacility(requestFor(workbook)); + + assertNull(saved.iterator().next().getSurveyFacility()); + } + + @Test + @DisplayName("saveFacility should fall back to zero for a numeric column left blank") + void saveFacility_shouldFallBackToZeroForBlankNumericColumn() throws Exception { + Workbook workbook = completeUpload(); + workbook.getSheetAt(0).getRow(1).getCell(7).setBlank(); + when(uptsuUploadRepository.saveAll(anyList())).thenAnswer(call -> call.getArgument(0)); + + Iterable saved = service.saveFacility(requestFor(workbook)); + + assertEquals(0, saved.iterator().next().getDesignationId()); + } + + @Test + @DisplayName("saveFacility should refuse an upload whose mandatory column is blank") + void saveFacility_shouldRefuseBlankMandatoryColumn() throws Exception { + Workbook workbook = completeUpload(); + workbook.getSheetAt(0).getRow(1).getCell(0).setBlank(); + UploadRequest request = requestFor(workbook); + + assertThrows(IEMRException.class, () -> service.saveFacility(request)); + } + + @Test + @DisplayName("saveFacility should read a boolean cell rather than refuse it") + void saveFacility_shouldReadBooleanCell() throws Exception { + Workbook workbook = completeUpload(); + workbook.getSheetAt(0).getRow(1).getCell(2).setCellValue(true); + when(uptsuUploadRepository.saveAll(anyList())).thenAnswer(call -> call.getArgument(0)); + + Iterable saved = service.saveFacility(requestFor(workbook)); + + assertEquals("true", saved.iterator().next().getSurveyFacility()); + } + + @Test + @DisplayName("saveFacility should answer nothing when there is no upload to read") + void saveFacility_shouldAnswerNothingWithoutAnUpload() throws Exception { + assertNull(service.saveFacility(null)); + verify(uptsuUploadRepository, org.mockito.Mockito.never()).saveAll(anyList()); + } + + @Test + @DisplayName("saveFacility should refuse a payload that is not a workbook at all") + void saveFacility_shouldRefuseNonWorkbookPayload() { + UploadRequest request = new UploadRequest(); + request.setCreatedBy("admin"); + request.setProviderServiceMapID(PSM_ID); + request.setFileContent("data:text/plain;base64," + Base64.getEncoder().encodeToString("not a workbook".getBytes())); + + assertThrows(Exception.class, () -> service.saveFacility(request)); + } + + @Test + @DisplayName("saveCdssDetails should retire the provider's previous configuration before storing the new one") + void saveCdssDetails_shouldRetirePreviousConfiguration() { + CDSSMapping previous = new CDSSMapping(); + previous.setPsmId(PSM_ID); + CDSSMapping request = new CDSSMapping(); + request.setPsmId(PSM_ID); + request.setIsCdss(Boolean.TRUE); + when(cdssMappingRepo.findByPsmIdAndDeleted(PSM_ID, false)).thenReturn(previous); + when(cdssMappingRepo.save(request)).thenReturn(request); + + assertEquals(request, service.saveCdssDetails(request)); + assertTrue(previous.getDeleted(), "the configuration it replaces must be retired"); + } + + @Test + @DisplayName("saveCdssDetails should store the first configuration a provider has") + void saveCdssDetails_shouldStoreFirstConfiguration() { + CDSSMapping request = new CDSSMapping(); + request.setPsmId(PSM_ID); + when(cdssMappingRepo.findByPsmIdAndDeleted(PSM_ID, false)).thenReturn(null); + when(cdssMappingRepo.save(request)).thenReturn(request); + + assertEquals(request, service.saveCdssDetails(request)); + } + + @Test + @DisplayName("getCdssData should publish the stored configuration as JSON") + void getCdssData_shouldPublishStoredConfiguration() throws Exception { + CDSSMapping stored = new CDSSMapping(); + stored.setPsmId(PSM_ID); + stored.setIsCdss(Boolean.TRUE); + when(cdssMappingRepo.findByPsmIdAndDeleted(PSM_ID, false)).thenReturn(stored); + + String published = service.getCdssData(PSM_ID); + + assertTrue(published.contains("\"psmId\":4001"), published); + assertTrue(published.contains("\"isCdss\":true"), published); + } + + @Test + @DisplayName("getCdssData should publish a null rather than fail for a provider with no configuration") + void getCdssData_shouldPublishNullForUnconfiguredProvider() throws Exception { + when(cdssMappingRepo.findByPsmIdAndDeleted(anyInt(), any())).thenReturn(null); + + assertEquals("null", service.getCdssData(PSM_ID)); + } +} diff --git a/src/test/java/com/iemr/admin/service/user/EncryptUserPasswordTest.java b/src/test/java/com/iemr/admin/service/user/EncryptUserPasswordTest.java new file mode 100644 index 0000000..98deab2 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/user/EncryptUserPasswordTest.java @@ -0,0 +1,171 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.user; + +import org.json.JSONObject; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.admin.data.employeemaster.M_User1; +import com.iemr.admin.data.user.M_User; +import com.iemr.admin.service.provideronboard.EncryptUserPassword123; +import com.iemr.admin.utils.http.HttpUtils; +import com.iemr.admin.utils.response.OutputResponse; + +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.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Passwords are never stored by this service itself: they are handed to the + * common service, which is stood in for here. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("Password encryption Test Suite") +class EncryptUserPasswordTest { + + private static final String BASE_URL = "https://common.example.org"; + + @Mock + private HttpUtils httpUtils; + + private HttpUtils originalEmployeeUtils; + private HttpUtils originalOnboardUtils; + + private EncryptUserPassword service; + private EncryptUserPassword123 onboardService; + + @BeforeEach + @DisplayName("Stand in for the common service on both callers") + void setUp() { + originalEmployeeUtils = (HttpUtils) ReflectionTestUtils.getField(EncryptUserPassword.class, "utils"); + originalOnboardUtils = (HttpUtils) ReflectionTestUtils.getField(EncryptUserPassword123.class, "utils"); + ReflectionTestUtils.setField(EncryptUserPassword.class, "utils", httpUtils); + ReflectionTestUtils.setField(EncryptUserPassword123.class, "utils", httpUtils); + + service = new EncryptUserPassword(); + ReflectionTestUtils.setField(service, "commonBaseURL", BASE_URL); + service.init(); + + onboardService = new EncryptUserPassword123(); + ReflectionTestUtils.setField(onboardService, "commonBaseURL", BASE_URL); + onboardService.init(); + } + + @AfterEach + @DisplayName("Put the real common service client back so no other suite sees the stand-in") + void tearDown() { + ReflectionTestUtils.setField(EncryptUserPassword.class, "utils", originalEmployeeUtils); + ReflectionTestUtils.setField(EncryptUserPassword123.class, "utils", originalOnboardUtils); + } + + private static M_User1 employee() { + M_User1 employee = new M_User1(); + employee.setUserName("asha.rao"); + employee.setPassword("secret"); + return employee; + } + + private static M_User administrator() { + M_User administrator = new M_User(); + administrator.setUserName("admin.rao"); + administrator.setPassword("other-secret"); + return administrator; + } + + @Test + @DisplayName("init should address the common service under the configured base address") + void init_shouldAddressConfiguredCommonService() { + assertTrue(((String) ReflectionTestUtils.getField(service, "encryptPasswordURL")).startsWith(BASE_URL), + "the encryption address must sit under the configured common service"); + } + + @Test + @DisplayName("encryptUserCredentials should hand the employee's credentials to the common service") + void encryptUserCredentials_shouldHandEmployeeCredentialsOver() { + when(httpUtils.post(anyString(), anyString())).thenReturn("{\"statusCode\":200,\"status\":\"Success\"}"); + ArgumentCaptor body = ArgumentCaptor.forClass(String.class); + + OutputResponse response = service.encryptUserCredentials(employee()); + + assertNotNull(response); + verify(httpUtils).post(anyString(), body.capture()); + JSONObject sent = new JSONObject(body.getValue()); + assertEquals("asha.rao", sent.getString("userName")); + assertEquals("secret", sent.getString("password")); + assertTrue(sent.getBoolean("isAdmin"), "an employee account created here is created as an administrator"); + } + + @Test + @DisplayName("encryptUserCredentials should carry back what the common service answered") + void encryptUserCredentials_shouldCarryBackTheAnswer() { + when(httpUtils.post(anyString(), anyString())) + .thenReturn("{\"statusCode\":5000,\"errorMessage\":\"user not found\"}"); + + assertEquals(5000, service.encryptUserCredentials(employee()).getStatusCode()); + } + + @Test + @DisplayName("encryptUserCredentials should give up when the common service answers something unreadable") + void encryptUserCredentials_shouldGiveUpOnUnreadableAnswer() { + when(httpUtils.post(anyString(), anyString())).thenReturn("502 Bad Gateway"); + + assertThrows(RuntimeException.class, () -> service.encryptUserCredentials(employee())); + } + + @Test + @DisplayName("the onboarding caller should hand the administrator's credentials over without the admin flag") + void onboardingCaller_shouldHandCredentialsOverWithoutAdminFlag() { + when(httpUtils.post(anyString(), anyString())).thenReturn("{\"statusCode\":200,\"status\":\"Success\"}"); + ArgumentCaptor body = ArgumentCaptor.forClass(String.class); + + assertNotNull(onboardService.encryptUserCredentials(administrator())); + + verify(httpUtils).post(anyString(), body.capture()); + JSONObject sent = new JSONObject(body.getValue()); + assertEquals("admin.rao", sent.getString("userName")); + assertEquals("other-secret", sent.getString("password")); + assertTrue(!sent.has("isAdmin"), "the onboarding caller does not claim the administrator flag"); + } + + @Test + @DisplayName("the onboarding caller should give up when the common service answers something unreadable") + void onboardingCaller_shouldGiveUpOnUnreadableAnswer() { + when(httpUtils.post(anyString(), anyString())).thenReturn("gateway timeout"); + + assertThrows(RuntimeException.class, () -> onboardService.encryptUserCredentials(administrator())); + } +} diff --git a/src/test/java/com/iemr/admin/service/user/IemrUserServiceImplTest.java b/src/test/java/com/iemr/admin/service/user/IemrUserServiceImplTest.java new file mode 100644 index 0000000..860374b --- /dev/null +++ b/src/test/java/com/iemr/admin/service/user/IemrUserServiceImplTest.java @@ -0,0 +1,283 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.user; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.user.M_User; +import com.iemr.admin.data.user.M_UserServiceRoleMapping; +import com.iemr.admin.repository.user.IemrUserRepositoryImplCustom; +import com.iemr.admin.repository.user.M_UserMappingRepo; +import com.iemr.admin.service.provideronboard.EncryptUserPassword123; + +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.anySet; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The user service turns the loosely typed sign-up form the onboarding screen + * sends into a stored administrator account, then has its password encrypted. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("IemrUserServiceImpl Test Suite") +class IemrUserServiceImplTest { + + private static final int USER_ID = 3117; + + @Mock + private EncryptUserPassword123 encryptUserPassword; + + @Mock + private IemrUserRepositoryImplCustom iemrUserRepositoryImplCustom; + + @Mock + private M_UserMappingRepo m_UserMappingRepo; + + @InjectMocks + private IemrUserServiceImpl service; + + private static Map signUpForm() { + Map form = new HashMap<>(); + form.put("titleID", 1); + form.put("firstName", "Asha"); + form.put("middleName", "K"); + form.put("lastName", "Rao"); + form.put("genderID", 2); + form.put("maritalStatusID", "1"); + form.put("aadhaarNo", "1234"); + form.put("aadharNo", "123412341234"); + form.put("panNo", "ABCDE1234F"); + form.put("dob", "1990-05-17T00:00:00+05:30"); + form.put("doj", "2020-01-06T00:00:00+05:30"); + form.put("qualificationID", "4"); + form.put("userName", "asha.rao"); + form.put("password", "secret"); + form.put("emailID", "asha.rao@example.org"); + form.put("emrContactPersion", "Ravi Rao"); + form.put("emrConctactNo", "9000000001"); + form.put("isSupervisor", Boolean.TRUE); + form.put("deleted", Boolean.FALSE); + form.put("statusID", 1); + return form; + } + + private static ArrayList> formList(Map form) { + ArrayList> list = new ArrayList<>(); + list.add(form); + return list; + } + + private static M_User storedUser() { + M_User user = new M_User(); + user.setUserID(USER_ID); + user.setUserName("asha.rao"); + return user; + } + + @SuppressWarnings("unchecked") + private M_User captureSavedUser() { + ArgumentCaptor> captor = ArgumentCaptor.forClass(Set.class); + verify(iemrUserRepositoryImplCustom).saveAll(captor.capture()); + return captor.getValue().iterator().next(); + } + + @Test + @DisplayName("createUser should record every detail the sign-up form carried") + void createUser_shouldRecordEveryFormDetail() { + when(iemrUserRepositoryImplCustom.saveAll(anySet())).thenReturn(List.of(storedUser())); + + assertEquals(USER_ID, service.createUser(formList(signUpForm()), "admin")); + + M_User saved = captureSavedUser(); + assertEquals(1, saved.getTitleID()); + assertEquals("Asha", saved.getFirstName()); + assertEquals("K", saved.getMiddleName()); + assertEquals("Rao", saved.getLastName()); + assertEquals(2, saved.getGenderID()); + assertEquals(1, saved.getMaritalStatusID()); + assertEquals("123412341234", saved.getAadhaarNo(), + "the Aadhaar number is read from the differently spelled key the form sends"); + assertEquals("ABCDE1234F", saved.getPAN()); + assertEquals(Timestamp.valueOf("1990-05-17 00:00:00"), saved.getDOB()); + assertEquals(Timestamp.valueOf("2020-01-06 00:00:00"), saved.getDOJ()); + assertEquals(4, saved.getQualificationID()); + assertEquals("asha.rao", saved.getUserName()); + assertEquals("secret", saved.getPassword()); + assertEquals("asha.rao@example.org", saved.getEmailID()); + assertEquals("Ravi Rao", saved.getEmergencyContactPerson()); + assertEquals("9000000001", saved.getEmergencyContactNo()); + assertEquals(Boolean.TRUE, saved.isIsSupervisor()); + assertEquals(false, saved.isDeleted()); + assertEquals(1, saved.getStatusID()); + assertEquals("admin", saved.getCreatedBy()); + } + + @Test + @DisplayName("createUser should have the stored account's password encrypted") + void createUser_shouldHavePasswordEncrypted() { + M_User stored = storedUser(); + when(iemrUserRepositoryImplCustom.saveAll(anySet())).thenReturn(List.of(stored)); + + service.createUser(formList(signUpForm()), "admin"); + + verify(encryptUserPassword).encryptUserCredentials(stored); + } + + @Test + @DisplayName("createUser should record an anonymous author when the caller does not name one") + void createUser_shouldRecordAnonymousAuthor() { + when(iemrUserRepositoryImplCustom.saveAll(anySet())).thenReturn(List.of(storedUser())); + + service.createUser(formList(signUpForm()), null); + + assertEquals("", captureSavedUser().getCreatedBy()); + } + + @Test + @DisplayName("createUser should leave out the details the sign-up form did not carry") + void createUser_shouldLeaveOutMissingDetails() { + Map sparse = new HashMap<>(); + sparse.put("userName", "asha.rao"); + when(iemrUserRepositoryImplCustom.saveAll(anySet())).thenReturn(List.of(storedUser())); + + service.createUser(formList(sparse), "admin"); + + M_User saved = captureSavedUser(); + assertEquals("asha.rao", saved.getUserName()); + assertNull(saved.getFirstName()); + assertNull(saved.getDOB()); + assertNull(saved.getQualificationID()); + } + + @Test + @DisplayName("createUser should ignore a date the form sent as a bare day with no time") + void createUser_shouldIgnoreBareDayDates() { + Map form = signUpForm(); + form.put("dob", "1990-05-17"); + form.put("doj", "2020-01-06"); + form.put("maritalStatusID", ""); + form.put("qualificationID", ""); + when(iemrUserRepositoryImplCustom.saveAll(anySet())).thenReturn(List.of(storedUser())); + + service.createUser(formList(form), "admin"); + + M_User saved = captureSavedUser(); + assertNull(saved.getDOB(), "a date with no time is not a timestamp this service can store"); + assertNull(saved.getDOJ()); + assertNull(saved.getMaritalStatusID()); + assertNull(saved.getQualificationID()); + } + + @Test + @DisplayName("createUser should answer no account when nothing was stored") + void createUser_shouldAnswerNoAccountWhenNothingStored() { + when(iemrUserRepositoryImplCustom.saveAll(anySet())).thenReturn(new ArrayList()); + + assertEquals(0, service.createUser(formList(signUpForm()), "admin")); + verify(encryptUserPassword, never()).encryptUserCredentials(any()); + } + + @Test + @DisplayName("createUser should give up when the account cannot be stored") + void createUser_shouldGiveUpWhenStorageFails() { + when(iemrUserRepositoryImplCustom.saveAll(anySet())) + .thenThrow(new RuntimeException("duplicate user name")); + + assertThrows(RuntimeException.class, () -> service.createUser(formList(signUpForm()), "admin")); + } + + @Test + @DisplayName("createUser should give up when the form carries a date it cannot read") + void createUser_shouldGiveUpOnUnreadableDate() { + Map form = signUpForm(); + form.put("dob", "the seventeenth of May"); + + assertThrows(RuntimeException.class, () -> service.createUser(formList(form), "admin")); + } + + @Test + @DisplayName("createUserServiceRoleMapping should give the new administrator a role on every service line named") + void createRoleMapping_shouldGiveRoleOnEveryServiceLine() { + when(m_UserMappingRepo.saveAll(anySet())) + .thenReturn(new ArrayList<>(List.of(new M_UserServiceRoleMapping()))); + + assertEquals(1, service.createUserServiceRoleMapping(List.of(4001, 4002), USER_ID, "admin")); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(Set.class); + verify(m_UserMappingRepo).saveAll(captor.capture()); + assertEquals(2, captor.getValue().size()); + assertTrue(captor.getValue().stream().allMatch(one -> one.getRoleID() == 11), + "an onboarded administrator always takes the administrator role"); + assertTrue(captor.getValue().stream().allMatch(one -> one.getUserID() == USER_ID)); + } + + @Test + @DisplayName("createUserServiceRoleMapping should report nothing recorded when no service line was named") + void createRoleMapping_shouldReportNothingRecordedWithoutServiceLines() { + when(m_UserMappingRepo.saveAll(anySet())).thenReturn(new ArrayList()); + + assertEquals(0, service.createUserServiceRoleMapping(new ArrayList<>(), USER_ID, "admin")); + } + + @Test + @DisplayName("createUserServiceRoleMapping should give up when the roles cannot be recorded") + void createRoleMapping_shouldGiveUpWhenStorageFails() { + when(m_UserMappingRepo.saveAll(anySet())).thenThrow(new RuntimeException("row is locked")); + + assertThrows(RuntimeException.class, + () -> service.createUserServiceRoleMapping(List.of(4001), USER_ID, "admin")); + } + + @Test + @DisplayName("the collaborators should be replaceable so the service can be wired by hand") + void collaborators_shouldBeReplaceable() { + IemrUserServiceImpl standalone = new IemrUserServiceImpl(); + standalone.setIemrUserRepositoryImplCustom(iemrUserRepositoryImplCustom); + standalone.setM_UserMappingRepo(m_UserMappingRepo); + when(m_UserMappingRepo.saveAll(anySet())) + .thenReturn(new ArrayList<>(List.of(new M_UserServiceRoleMapping()))); + + assertEquals(1, standalone.createUserServiceRoleMapping(List.of(4001), USER_ID, "admin")); + } +} diff --git a/src/test/java/com/iemr/admin/service/userParkingPlaceMap/UserParkingPlaceMapServiceImplTest.java b/src/test/java/com/iemr/admin/service/userParkingPlaceMap/UserParkingPlaceMapServiceImplTest.java new file mode 100644 index 0000000..8b98a48 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/userParkingPlaceMap/UserParkingPlaceMapServiceImplTest.java @@ -0,0 +1,233 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.userParkingPlaceMap; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.userParkingPlaceMap.M_UserParkingPlaceMap; +import com.iemr.admin.data.userParkingPlaceMap.M_UserVanMapping; +import com.iemr.admin.repo.employeemaster.EmployeeMasterRepo; +import com.iemr.admin.repository.userParkingPlaceMap.UserParkingPlaceMapRepository; +import com.iemr.admin.repository.userParkingPlaceMap.UserVanMappingRepository; + +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.assertSame; +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.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The user parking place service keeps a field user's posting and the vans that + * posting covers in step, replacing the van list wholesale on every edit. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("UserParkingPlaceMapServiceImpl Test Suite") +class UserParkingPlaceMapServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer MAP_ID = 9001; + private static final Integer USER_ID = 3117; + + @Mock + private UserParkingPlaceMapRepository userParkingPlaceMapRepository; + + @Mock + private UserVanMappingRepository userVanMappingRepository; + + @Mock + private EmployeeMasterRepo employeeMasterRepo; + + @InjectMocks + private UserParkingPlaceMapServiceImpl service; + + private static M_UserParkingPlaceMap posting(Integer id) { + return new M_UserParkingPlaceMap(id, USER_ID, "Asha", "Rao", "asha.rao", 7, 301, 31, "Hosur parking", + PSM_ID, Boolean.FALSE, Boolean.FALSE); + } + + private static M_UserVanMapping vanMapping(Integer id) { + M_UserVanMapping mapping = new M_UserVanMapping(); + mapping.setUserVanMapID(id); + mapping.setVanID(71); + return mapping; + } + + @Test + @DisplayName("saveUserParkingPlaceDetails should attach the vans to each posting it stored") + void save_shouldAttachVansToStoredPostings() { + M_UserParkingPlaceMap stored = posting(MAP_ID); + stored.setCreatedBy("admin"); + stored.setUservanmapping(new ArrayList<>(List.of(vanMapping(null)))); + when(userParkingPlaceMapRepository.saveAll(anyList())) + .thenReturn(new ArrayList<>(List.of(stored))); + + service.saveUserParkingPlaceDetails(new ArrayList<>(List.of(stored))); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(userVanMappingRepository).saveAll(captor.capture()); + assertEquals(1, captor.getValue().size()); + assertEquals(MAP_ID, captor.getValue().get(0).getUserParkingPlaceMapID()); + assertEquals("admin", captor.getValue().get(0).getCreatedBy()); + } + + @Test + @DisplayName("getUserParkingPlaceMappings should pass every filter the caller supplies through") + void getMappings_shouldPassFiltersThrough() { + when(userParkingPlaceMapRepository.getUserParkingPlaceMappings(77, "29", "301", "31", "7")) + .thenReturn(List.of(new Object[] { MAP_ID, USER_ID, "Asha", "Rao", "asha.rao", 7, + "11", "Counsellor", (short) 2, "Female", 29, "Karnataka", 301, "Bengaluru Urban", + 31, "Hosur parking", "9000000001", PSM_ID, Boolean.FALSE })); + + assertEquals(1, service.getUserParkingPlaceMappings(77, 29, 301, 31, 7).size()); + } + + @Test + @DisplayName("getUserParkingPlaceMappings should match everything for a filter the caller leaves blank") + void getMappings_shouldWildcardBlankFilters() { + when(userParkingPlaceMapRepository.getUserParkingPlaceMappings(77, "%%", "%%", "%%", "%%")) + .thenReturn(new ArrayList<>()); + + service.getUserParkingPlaceMappings(77, null, null, null, null); + + verify(userParkingPlaceMapRepository).getUserParkingPlaceMappings(77, "%%", "%%", "%%", "%%"); + } + + @Test + @DisplayName("getUserParkingPlaceMappings1 should rebuild one posting per row the query answers") + void getMappings1_shouldRebuildEachRow() { + when(userParkingPlaceMapRepository.getUserParkingPlaceMappings1(PSM_ID, 31, 7)) + .thenReturn(List.of(new Object[] { MAP_ID, USER_ID, "Asha", "Rao", "asha.rao", 7, + 301, 31, "Hosur parking", PSM_ID, Boolean.FALSE, Boolean.FALSE, "ASHA" })); + + assertEquals(1, service.getUserParkingPlaceMappings1(PSM_ID, 301, 31, 7).size()); + } + + @Test + @DisplayName("the record lookups should each reach their own repository query") + void recordLookups_shouldReachTheirOwnQuery() { + M_UserParkingPlaceMap stored = posting(MAP_ID); + ArrayList postings = new ArrayList<>(List.of(stored)); + when(userParkingPlaceMapRepository.getUserParkingPlaceMapByID(MAP_ID)).thenReturn(stored); + when(userParkingPlaceMapRepository.findByUserParkingPlaceMapID(MAP_ID)).thenReturn(stored); + when(userParkingPlaceMapRepository.save(stored)).thenReturn(stored); + when(userParkingPlaceMapRepository.saveAll(anyList())).thenReturn(postings); + when(userParkingPlaceMapRepository.updateUserParkingPlaceMapStatus(MAP_ID, Boolean.FALSE, null)) + .thenReturn(1); + + assertSame(stored, service.getUserParkingPlaceMapByID(MAP_ID)); + assertSame(stored, service.getUserParkingPlaceDetails(MAP_ID)); + assertSame(stored, service.saveediteddata(stored)); + assertSame(postings, service.saveUserParkingPlaceDetails1(new ArrayList<>())); + assertEquals(1, service.updateUserParkingPlaceMapStatus(stored)); + } + + @Test + @DisplayName("saveediteddata should replace the van list rather than add to it") + void saveEdited_shouldReplaceVanList() { + M_UserParkingPlaceMap stored = posting(MAP_ID); + stored.setModifiedBy("admin"); + M_UserVanMapping fresh = vanMapping(null); + when(userParkingPlaceMapRepository.save(stored)).thenReturn(stored); + when(userVanMappingRepository.saveAll(anyList())) + .thenAnswer(call -> new ArrayList<>((List) call.getArgument(0))); + + M_UserParkingPlaceMap saved = service.saveediteddata(stored, List.of(fresh)); + + verify(userVanMappingRepository).deactivatebyuserparkingplaceid(MAP_ID, "admin"); + assertEquals(1, saved.getUservanmapping().size()); + assertEquals(MAP_ID, saved.getUservanmapping().get(0).getUserParkingPlaceMapID()); + assertNull(saved.getUservanmapping().get(0).getUserParkingPlaceMap(), + "the van mapping must not carry the posting back with it"); + } + + @Test + @DisplayName("getunmappedUser should exclude the users already posted when there are any") + void getunmappedUser_shouldExcludePostedUsers() { + when(userParkingPlaceMapRepository.getmappedids(PSM_ID, 7)).thenReturn(List.of(USER_ID)); + when(employeeMasterRepo.getAllEmpByProviderServiceMapIDAndDesignationNotInUserID(PSM_ID, 7, + List.of(USER_ID))).thenReturn(List.of(new Object[] { 3118, "Ravi Kumar" })); + + assertEquals(1, service.getunmappedUser(PSM_ID, 7).size()); + verify(employeeMasterRepo, never()).getAllEmpByProviderServiceMapIDAndDesignation(anyInt(), anyInt()); + } + + @Test + @DisplayName("getunmappedUser should answer every user when none is posted yet") + void getunmappedUser_shouldAnswerEveryUserWhenNonePosted() { + when(userParkingPlaceMapRepository.getmappedids(PSM_ID, 7)).thenReturn(new ArrayList<>()); + when(employeeMasterRepo.getAllEmpByProviderServiceMapIDAndDesignation(PSM_ID, 7)) + .thenReturn(List.of(new Object[] { 3118, "Ravi Kumar" })); + + assertEquals(1, service.getunmappedUser(PSM_ID, 7).size()); + } + + @Test + @DisplayName("getuserexist should report whether the user already holds a live posting") + void getuserexist_shouldReportWhetherUserIsPosted() { + when(userParkingPlaceMapRepository.findByProviderServiceMapIDAndUserIDAndDeleted(PSM_ID, USER_ID, false)) + .thenReturn(List.of(posting(MAP_ID))); + assertTrue(service.getuserexist(PSM_ID, USER_ID)); + + when(userParkingPlaceMapRepository.findByProviderServiceMapIDAndUserIDAndDeleted(PSM_ID, USER_ID, false)) + .thenReturn(new ArrayList<>()); + assertFalse(service.getuserexist(PSM_ID, USER_ID)); + } + + @Test + @DisplayName("getuservanmapping should answer the vans the posting covers") + void getuservanmapping_shouldAnswerCoveredVans() { + List stored = List.of(vanMapping(7001)); + when(userVanMappingRepository.findByUserParkingPlaceMapIDAndDeleted(MAP_ID)).thenReturn(stored); + + assertSame(stored, service.getuservanmapping(MAP_ID)); + } + + @Test + @DisplayName("deleteuservanmapping should release the van mapping the caller names") + void deleteuservanmapping_shouldReleaseNamedMapping() { + M_UserVanMapping request = vanMapping(7001); + request.setModifiedBy("admin"); + + service.deleteuservanmapping(request); + + verify(userVanMappingRepository).deleteUservanMap(7001, "admin"); + } +} diff --git a/src/test/java/com/iemr/admin/service/vanMaster/VanMasterServiceImplTest.java b/src/test/java/com/iemr/admin/service/vanMaster/VanMasterServiceImplTest.java new file mode 100644 index 0000000..525490d --- /dev/null +++ b/src/test/java/com/iemr/admin/service/vanMaster/VanMasterServiceImplTest.java @@ -0,0 +1,215 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.vanMaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.parkingPlace.M_Parkingplace; +import com.iemr.admin.data.vanMaster.M_Van; +import com.iemr.admin.data.vanType.M_VanType; +import com.iemr.admin.repository.parkingPlace.ParkingPlaceRepository; +import com.iemr.admin.repository.vanMaster.VanMasterRepository; +import com.iemr.admin.repository.vanType.VanTypeRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The van service keeps the mobile unit fleet: which vans exist, what type each + * is, and which parking place each is stationed at. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VanMasterServiceImpl Test Suite") +class VanMasterServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer VAN_ID = 71; + private static final Integer PARKING_PLACE_ID = 31; + + @Mock + private VanMasterRepository vanMasterRepository; + + @Mock + private VanTypeRepository vanTypeRepository; + + @Mock + private ParkingPlaceRepository parkingPlaceRepository; + + @InjectMocks + private VanMasterServiceImpl service; + + private static Object[] fleetRow() { + return new Object[] { VAN_ID, "Mobile unit 7", "KA-01-AB-1234", 2, "Diagnostic van", Boolean.FALSE, PSM_ID, + 1, "India", 29, "Karnataka", PARKING_PLACE_ID, "Hosur parking", 3011, "psmri", "990011", + "van7@example.org", false }; + } + + @Test + @DisplayName("getAvailableVans should rebuild one van per row the query answers") + void getAvailableVans_shouldRebuildEachRow() { + when(vanMasterRepository.getAvailableVans("31", "2", PSM_ID)).thenReturn(List.of(fleetRow())); + + ArrayList vans = service.getAvailableVans(PARKING_PLACE_ID, 2, PSM_ID); + + assertEquals(1, vans.size()); + assertEquals("Mobile unit 7", vans.get(0).getVanName()); + assertEquals("KA-01-AB-1234", vans.get(0).getVehicalNo()); + assertEquals("Hosur parking", vans.get(0).getParkingPlaceName()); + } + + @Test + @DisplayName("getAvailableVans should match any parking place or type the caller leaves out") + void getAvailableVans_shouldWildcardOmittedFilters() { + when(vanMasterRepository.getAvailableVans("%%", "%%", PSM_ID)).thenReturn(new ArrayList<>()); + + assertTrue(service.getAvailableVans(null, null, PSM_ID).isEmpty()); + verify(vanMasterRepository).getAvailableVans("%%", "%%", PSM_ID); + } + + @Test + @DisplayName("saveVanDetails and saveVanTypeDetails should answer what the repository stored") + void saveOperations_shouldAnswerStoredRecords() { + ArrayList vans = new ArrayList<>(List.of(new M_Van())); + ArrayList types = new ArrayList<>(List.of(new M_VanType())); + when(vanMasterRepository.saveAll(anyList())).thenReturn(vans); + when(vanTypeRepository.saveAll(anyList())).thenReturn(types); + + assertSame(vans, service.saveVanDetails(new ArrayList<>())); + assertSame(types, service.saveVanTypeDetails(new ArrayList<>())); + } + + @Test + @DisplayName("updateVanStatus should report how many rows the retirement touched") + void updateVanStatus_shouldReportRowsTouched() { + M_Van request = new M_Van(); + request.setVanID(VAN_ID); + request.setDeleted(Boolean.TRUE); + request.setModifiedBy("admin"); + when(vanMasterRepository.updateVanStatus(VAN_ID, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, service.updateVanStatus(request)); + } + + @Test + @DisplayName("updateVanStatus should report nothing touched when the van is unknown") + void updateVanStatus_shouldReportNothingTouchedForUnknownVan() { + M_Van request = new M_Van(); + request.setVanID(-1); + when(vanMasterRepository.updateVanStatus(-1, null, null)).thenReturn(0); + + assertEquals(0, service.updateVanStatus(request)); + } + + @Test + @DisplayName("updateVanTypeStatus should report how many rows the retirement touched") + void updateVanTypeStatus_shouldReportRowsTouched() { + M_VanType request = new M_VanType(2, "Diagnostic van", "Carries lab kit", Boolean.TRUE); + request.setModifiedBy("admin"); + when(vanTypeRepository.updateVanTypeStatus(2, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, service.updateVanTypeStatus(request)); + } + + @Test + @DisplayName("getVanTypes should rebuild one van type per row the query answers") + void getVanTypes_shouldRebuildEachRow() { + when(vanTypeRepository.getVanTypes()) + .thenReturn(List.of(new Object[] { 2, "Diagnostic van", "Carries lab kit", Boolean.FALSE })); + + ArrayList types = service.getVanTypes(); + + assertEquals(1, types.size()); + assertEquals("Diagnostic van", types.get(0).getVanType()); + assertEquals("Carries lab kit", types.get(0).getVanTypeDesc()); + } + + @Test + @DisplayName("getVanTypes should answer nothing when no van type is on file") + void getVanTypes_shouldAnswerNothingWhenNoneOnFile() { + when(vanTypeRepository.getVanTypes()).thenReturn(new ArrayList<>()); + + assertTrue(service.getVanTypes().isEmpty()); + } + + @Test + @DisplayName("getVanByID, updateVanData and getVanMaster should each reach their own query") + void singleRecordOperations_shouldReachTheirOwnQuery() { + M_Van stored = new M_Van(); + List stationed = List.of(stored); + when(vanMasterRepository.getVanById(VAN_ID)).thenReturn(stored); + when(vanMasterRepository.save(stored)).thenReturn(stored); + when(vanMasterRepository.findByProviderServiceMapIDAndParkingPlaceID(PSM_ID, PARKING_PLACE_ID)) + .thenReturn(stationed); + + assertSame(stored, service.getVanByID(VAN_ID)); + assertSame(stored, service.updateVanData(stored)); + assertSame(stationed, service.getVanMaster(PSM_ID, PARKING_PLACE_ID)); + } + + @Test + @DisplayName("getVanFromFacilityID should answer the vans stationed at the store's parking place") + void getVanFromFacilityID_shouldAnswerVansAtStoresParkingPlace() throws Exception { + M_Parkingplace place = new M_Parkingplace(); + place.setParkingPlaceID(PARKING_PLACE_ID); + place.setProviderServiceMapID(PSM_ID); + List stationed = List.of(new M_Van()); + when(parkingPlaceRepository.findFirstByFacilityID(9001)).thenReturn(place); + when(vanMasterRepository.findByProviderServiceMapIDAndParkingPlaceID(PSM_ID, PARKING_PLACE_ID)) + .thenReturn(stationed); + + assertSame(stationed, service.getVanFromFacilityID(9001)); + } + + @Test + @DisplayName("getVanFromFacilityID should refuse a store that has no parking place of its own") + void getVanFromFacilityID_shouldRefuseStoreWithoutParkingPlace() { + when(parkingPlaceRepository.findFirstByFacilityID(9001)).thenReturn(null); + + Exception refusal = assertThrows(Exception.class, () -> service.getVanFromFacilityID(9001)); + + assertEquals("Main Store doesnt have any Parking place mapped", refusal.getMessage()); + } + + @Test + @DisplayName("getVanFromFacilityID should refuse a parking place record that names no parking place") + void getVanFromFacilityID_shouldRefuseNamelessParkingPlace() { + when(parkingPlaceRepository.findFirstByFacilityID(9001)).thenReturn(new M_Parkingplace()); + + assertThrows(Exception.class, () -> service.getVanFromFacilityID(9001)); + } +} diff --git a/src/test/java/com/iemr/admin/service/vanServicePointMapping/VanServicePointMappingServiceImplTest.java b/src/test/java/com/iemr/admin/service/vanServicePointMapping/VanServicePointMappingServiceImplTest.java new file mode 100644 index 0000000..ea24d93 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/vanServicePointMapping/VanServicePointMappingServiceImplTest.java @@ -0,0 +1,170 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.vanServicePointMapping; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.vanServicePointMapping.M_VanServicePointMap; +import com.iemr.admin.repository.vanServicePointMapping.VanServicePointMappingRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The van service point service records which service points a van visits and + * in which session of the day. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VanServicePointMappingServiceImpl Test Suite") +class VanServicePointMappingServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer VAN_ID = 71; + private static final Integer PARKING_PLACE_ID = 31; + private static final Integer MAP_ID = 8801; + + @Mock + private VanServicePointMappingRepository vanServicePointMappingRepository; + + @InjectMocks + private VanServicePointMappingServiceImpl service; + + private static Object[] mappingRow() { + return new Object[] { MAP_ID, VAN_ID, (short) 1, 88, "Attibele PHC", PSM_ID, Boolean.FALSE }; + } + + private static Object[] detailedMappingRow() { + return new Object[] { MAP_ID, VAN_ID, (short) 1, 88, "Attibele PHC", PSM_ID, Boolean.FALSE, 301, + "Bengaluru Urban", 3011, "Anekal" }; + } + + @Test + @DisplayName("getAvailableVanServicePointMappings should rebuild one visit per row the query answers") + void getAvailable_shouldRebuildEachRow() { + when(vanServicePointMappingRepository.getAvailableVanServicePointMappings(PARKING_PLACE_ID, VAN_ID, PSM_ID)) + .thenReturn(List.of(mappingRow())); + + ArrayList visits = service.getAvailableVanServicePointMappings(PARKING_PLACE_ID, + VAN_ID, PSM_ID); + + assertEquals(1, visits.size()); + assertEquals("Attibele PHC", visits.get(0).getServicePointName()); + assertEquals(VAN_ID, visits.get(0).getVanID()); + assertEquals((short) 1, visits.get(0).getVanSession()); + } + + @Test + @DisplayName("getAvailableVanServicePointMappings should answer nothing when the van visits nowhere") + void getAvailable_shouldAnswerNothingWhenNoVisits() { + when(vanServicePointMappingRepository.getAvailableVanServicePointMappings(PARKING_PLACE_ID, VAN_ID, PSM_ID)) + .thenReturn(new ArrayList<>()); + + assertTrue(service.getAvailableVanServicePointMappings(PARKING_PLACE_ID, VAN_ID, PSM_ID).isEmpty()); + } + + @Test + @DisplayName("getAvailableVanServicePointMappingsV1 should also carry the district and taluk of each visit") + void getAvailableV1_shouldCarryDistrictAndTaluk() { + when(vanServicePointMappingRepository.getAvailableVanServicePointMappingsV1(PARKING_PLACE_ID, VAN_ID, + PSM_ID)).thenReturn(List.of(detailedMappingRow())); + + ArrayList visits = service.getAvailableVanServicePointMappingsV1(PARKING_PLACE_ID, + VAN_ID, PSM_ID); + + assertEquals(1, visits.size()); + assertEquals("Bengaluru Urban", visits.get(0).getDistrictName()); + assertEquals("Anekal", visits.get(0).getBlockName()); + } + + @Test + @DisplayName("getAvailableVanServicePointMappingsV1 should answer nothing when the van visits nowhere") + void getAvailableV1_shouldAnswerNothingWhenNoVisits() { + when(vanServicePointMappingRepository.getAvailableVanServicePointMappingsV1(PARKING_PLACE_ID, VAN_ID, + PSM_ID)).thenReturn(new ArrayList<>()); + + assertTrue(service.getAvailableVanServicePointMappingsV1(PARKING_PLACE_ID, VAN_ID, PSM_ID).isEmpty()); + } + + @Test + @DisplayName("saveVanServicePointMappings should answer the visits the repository stored") + void save_shouldAnswerStoredVisits() { + ArrayList stored = new ArrayList<>(List.of(new M_VanServicePointMap())); + when(vanServicePointMappingRepository.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.saveVanServicePointMappings(new ArrayList<>())); + } + + @Test + @DisplayName("updateVanServicePointMappingStatus should report how many rows the retirement touched") + void updateStatus_shouldReportRowsTouched() { + M_VanServicePointMap request = new M_VanServicePointMap(); + request.setVanServicePointMapID(MAP_ID); + request.setDeleted(Boolean.TRUE); + request.setModifiedBy("admin"); + when(vanServicePointMappingRepository.updateVanServicePointMappingStatus(MAP_ID, Boolean.TRUE, "admin")) + .thenReturn(1); + + assertEquals(1, service.updateVanServicePointMappingStatus(request)); + } + + @Test + @DisplayName("updateVanServicePointMappingStatus should report nothing touched when the visit is unknown") + void updateStatus_shouldReportNothingTouchedForUnknownVisit() { + M_VanServicePointMap request = new M_VanServicePointMap(); + request.setVanServicePointMapID(-1); + when(vanServicePointMappingRepository.updateVanServicePointMappingStatus(-1, null, null)).thenReturn(0); + + assertEquals(0, service.updateVanServicePointMappingStatus(request)); + } + + @Test + @DisplayName("getVanServicePointMappingByID should answer the visit asked for") + void getById_shouldAnswerNamedVisit() { + M_VanServicePointMap stored = new M_VanServicePointMap(); + when(vanServicePointMappingRepository.getVanServicePointMapping(MAP_ID)).thenReturn(stored); + + assertSame(stored, service.getVanServicePointMappingByID(MAP_ID)); + } + + @Test + @DisplayName("getVanServicePointMappingByID should answer nothing when the visit is unknown") + void getById_shouldAnswerNothingForUnknownVisit() { + when(vanServicePointMappingRepository.getVanServicePointMapping(-1)).thenReturn(null); + + assertNull(service.getVanServicePointMappingByID(-1)); + } +} diff --git a/src/test/java/com/iemr/admin/service/vanSpokeMapping/VanSpokeMappingServiceImplTest.java b/src/test/java/com/iemr/admin/service/vanSpokeMapping/VanSpokeMappingServiceImplTest.java new file mode 100644 index 0000000..b538653 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/vanSpokeMapping/VanSpokeMappingServiceImplTest.java @@ -0,0 +1,201 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.vanSpokeMapping; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.VanSpokeMapping.m_VanSpokeMapping; +import com.iemr.admin.repo.VanSpokeMappingRepo.VanSpokeMappingRepo; +import com.iemr.admin.repository.vanMaster.VanMasterRepository; + +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.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The spoke mapping service ties a mobile unit van to the telemedicine spoke it + * serves, and marks the van itself as spoken for while that tie holds. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VanSpokeMappingServiceImpl Test Suite") +class VanSpokeMappingServiceImplTest { + + private static final Integer VAN_ID = 71; + + @Mock + private VanSpokeMappingRepo vanSpokeMappingRepo; + + @Mock + private VanMasterRepository vanMasterRepository; + + @InjectMocks + private VanSpokeMappingServiceImpl service; + + private static m_VanSpokeMapping mapping() { + m_VanSpokeMapping mapping = new m_VanSpokeMapping(); + mapping.setVanspokeID(6001); + mapping.setMmu_VanID(VAN_ID); + mapping.setMmu_parkingPlaceID(31); + mapping.setMmu_servicePointID(88); + mapping.setMmu_vantypeID(2); + mapping.setCreatedBy("admin"); + mapping.setDeleted(Boolean.FALSE); + return mapping; + } + + private static final String SAVE_REQUEST = "{\"vanSpokeMapping\":[{\"mmu_VanID\":71,\"tm_SpokeID\":9," + + "\"createdBy\":\"admin\"}]}"; + + @Test + @DisplayName("saveVanSpokeMapping should record the tie and mark the van as spoken for") + void save_shouldRecordTieAndMarkVanSpokenFor() throws Exception { + when(vanSpokeMappingRepo.saveAll(anyList())).thenReturn(List.of(mapping())); + when(vanMasterRepository.updateVanSpokeMapping(VAN_ID, true, "admin")).thenReturn(1); + + assertEquals("success", service.saveVanSpokeMapping(SAVE_REQUEST)); + verify(vanMasterRepository).updateVanSpokeMapping(VAN_ID, true, "admin"); + } + + @Test + @DisplayName("saveVanSpokeMapping should report failure when the van could not be marked as spoken for") + void save_shouldReportFailureWhenVanNotMarked() throws Exception { + when(vanSpokeMappingRepo.saveAll(anyList())).thenReturn(List.of(mapping())); + when(vanMasterRepository.updateVanSpokeMapping(anyInt(), anyBoolean(), anyString())).thenReturn(0); + + assertEquals("failure", service.saveVanSpokeMapping(SAVE_REQUEST)); + } + + @Test + @DisplayName("saveVanSpokeMapping should report failure when the request carries no mapping at all") + void save_shouldReportFailureWithoutMapping() throws Exception { + assertEquals("failure", service.saveVanSpokeMapping("{\"somethingElse\":1}")); + verify(vanSpokeMappingRepo, never()).saveAll(anyList()); + } + + @Test + @DisplayName("saveVanSpokeMapping should report failure when the request's mapping is empty") + void save_shouldReportFailureForNullMapping() throws Exception { + assertEquals("failure", service.saveVanSpokeMapping("{\"vanSpokeMapping\":null}")); + } + + @Test + @DisplayName("getVanSpokeMappingDetails should answer the ties held at the parking place asked about") + void get_shouldAnswerTiesAtParkingPlace() throws Exception { + when(vanSpokeMappingRepo.getVanSpokeMappingDetails(31, 88, 2)) + .thenReturn(new ArrayList<>(List.of(mapping()))); + + String answered = service.getVanSpokeMappingDetails( + "{\"mmu_parkingplaceID\":31,\"mmu_servicePointId\":88,\"mmu_vanTypeID\":2}"); + + assertTrue(answered.contains("vanSpokeMappedDetails"), answered); + assertTrue(answered.contains("6001"), answered); + } + + @Test + @DisplayName("getVanSpokeMappingDetails should answer an empty holding when the request names nothing") + void get_shouldAnswerEmptyHoldingForEmptyRequest() throws Exception { + assertEquals("{}", service.getVanSpokeMappingDetails("{}")); + } + + @Test + @DisplayName("getVanSpokeMappingDetails should give up when the request leaves out a filter it needs") + void get_shouldGiveUpOnIncompleteRequest() { + assertThrows(RuntimeException.class, + () -> service.getVanSpokeMappingDetails("{\"mmu_parkingplaceID\":31}")); + } + + @Test + @DisplayName("deleteVanSpokeMapping should release the van when the tie is retired") + void delete_shouldReleaseVanWhenTieRetired() throws Exception { + m_VanSpokeMapping retired = mapping(); + retired.setDeleted(Boolean.TRUE); + when(vanSpokeMappingRepo.save(any(m_VanSpokeMapping.class))).thenReturn(retired); + when(vanMasterRepository.updateVanSpokeMapping(VAN_ID, false, "admin")).thenReturn(1); + + assertEquals("success", service.deleteVanSpokeMapping( + "{\"vanSpokeDelete\":{\"vanspokeID\":6001,\"mmu_VanID\":71,\"createdBy\":\"admin\"," + + "\"deleted\":true}}")); + verify(vanMasterRepository).updateVanSpokeMapping(VAN_ID, false, "admin"); + } + + @Test + @DisplayName("deleteVanSpokeMapping should mark the van as spoken for again when the tie is reinstated") + void delete_shouldMarkVanSpokenForWhenTieReinstated() throws Exception { + when(vanSpokeMappingRepo.save(any(m_VanSpokeMapping.class))).thenReturn(mapping()); + when(vanMasterRepository.updateVanSpokeMapping(VAN_ID, true, "admin")).thenReturn(1); + + assertEquals("success", service.deleteVanSpokeMapping( + "{\"vanSpokeDelete\":{\"vanspokeID\":6001,\"mmu_VanID\":71,\"createdBy\":\"admin\"," + + "\"deleted\":false}}")); + verify(vanMasterRepository).updateVanSpokeMapping(VAN_ID, true, "admin"); + } + + @Test + @DisplayName("deleteVanSpokeMapping should report failure when the request carries no tie to retire") + void delete_shouldReportFailureWithoutTie() throws Exception { + assertEquals("failure", service.deleteVanSpokeMapping("{\"somethingElse\":1}")); + verify(vanSpokeMappingRepo, never()).save(any(m_VanSpokeMapping.class)); + } + + @Test + @DisplayName("deleteVanSpokeMapping should report failure when the van could not be released") + void delete_shouldReportFailureWhenVanNotReleased() throws Exception { + when(vanSpokeMappingRepo.save(any(m_VanSpokeMapping.class))).thenReturn(mapping()); + when(vanMasterRepository.updateVanSpokeMapping(anyInt(), anyBoolean(), anyString())).thenReturn(0); + + assertEquals("failure", service.deleteVanSpokeMapping( + "{\"vanSpokeDelete\":{\"vanspokeID\":6001,\"mmu_VanID\":71,\"createdBy\":\"admin\"}}")); + } + + @Test + @DisplayName("updateVanSpokeMapping should record every tie the request carried") + void update_shouldRecordEveryTie() throws Exception { + when(vanSpokeMappingRepo.saveAll(anyList())).thenReturn(new ArrayList<>(List.of(mapping()))); + + assertEquals("success", service.updateVanSpokeMapping("[{\"vanspokeID\":6001,\"mmu_VanID\":71}]")); + } + + @Test + @DisplayName("updateVanSpokeMapping should give up when the request is not a list of ties") + void update_shouldGiveUpOnUnreadableRequest() { + assertThrows(RuntimeException.class, () -> service.updateVanSpokeMapping("{not json")); + } +} diff --git a/src/test/java/com/iemr/admin/service/villageMaster/VillageMasterServiceImplTest.java b/src/test/java/com/iemr/admin/service/villageMaster/VillageMasterServiceImplTest.java new file mode 100644 index 0000000..b55f5ef --- /dev/null +++ b/src/test/java/com/iemr/admin/service/villageMaster/VillageMasterServiceImplTest.java @@ -0,0 +1,150 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.villageMaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.locationmaster.DistrictBranchMapping; +import com.iemr.admin.repository.villageMaster.VillageMasterRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.when; + +/** + * The village service keeps the villages of a taluk, along with the panchayat, + * habitation and pin code each one sits in. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("VillageMasterServiceImpl Test Suite") +class VillageMasterServiceImplTest { + + private static final Integer BLOCK_ID = 3011; + private static final Integer VILLAGE_ID = 30111; + + @Mock + private VillageMasterRepository villageMasterRepository; + + @InjectMocks + private VillageMasterServiceImpl service; + + private static Object[] villageRow() { + return new Object[] { VILLAGE_ID, BLOCK_ID, "Anekal", "Attibele panchayat", "Attibele", "Main habitation", + "562107", 900111, 900011, Boolean.FALSE, Boolean.TRUE }; + } + + private static DistrictBranchMapping village() { + DistrictBranchMapping village = new DistrictBranchMapping(); + village.setDistrictBranchID(VILLAGE_ID); + village.setVillageName("Attibele"); + village.setPanchayatName("Attibele panchayat"); + village.setHabitat("Main habitation"); + village.setPinCode("562107"); + village.setGovVillageID(900111); + village.setGovSubDistrictID(900011); + village.setIsRural(Boolean.TRUE); + village.setModifiedBy("admin"); + return village; + } + + @Test + @DisplayName("storeVillageDetails should answer the villages the repository stored") + void store_shouldAnswerStoredVillages() { + ArrayList stored = new ArrayList<>(List.of(village())); + when(villageMasterRepository.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.storeVillageDetails(new ArrayList<>())); + } + + @Test + @DisplayName("getAvailableVillages should rebuild one village per row the query answers") + void getAvailable_shouldRebuildEachRow() { + when(villageMasterRepository.getAvailableVillages(BLOCK_ID)).thenReturn(List.of(villageRow())); + + ArrayList villages = service.getAvailableVillages(BLOCK_ID); + + assertEquals(1, villages.size()); + assertEquals("Attibele", villages.get(0).getVillageName()); + assertEquals("Attibele panchayat", villages.get(0).getPanchayatName()); + assertEquals("562107", villages.get(0).getPinCode()); + assertEquals(Boolean.TRUE, villages.get(0).getIsRural()); + } + + @Test + @DisplayName("getAvailableVillages should answer nothing when the taluk holds no village") + void getAvailable_shouldAnswerNothingForEmptyTaluk() { + when(villageMasterRepository.getAvailableVillages(BLOCK_ID)).thenReturn(new ArrayList<>()); + + assertTrue(service.getAvailableVillages(BLOCK_ID).isEmpty()); + } + + @Test + @DisplayName("updateVillageStatus should report how many villages the retirement touched") + void updateStatus_shouldReportRowsTouched() { + DistrictBranchMapping request = village(); + request.setDeleted(Boolean.TRUE); + when(villageMasterRepository.updateVillageStatus(VILLAGE_ID, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, service.updateVillageStatus(request)); + } + + @Test + @DisplayName("updateVillageStatus should report nothing touched when the village is unknown") + void updateStatus_shouldReportNothingTouchedForUnknownVillage() { + DistrictBranchMapping request = new DistrictBranchMapping(); + request.setDistrictBranchID(-1); + when(villageMasterRepository.updateVillageStatus(-1, null, null)).thenReturn(0); + + assertEquals(0, service.updateVillageStatus(request)); + } + + @Test + @DisplayName("updateVillageData should carry every changed detail through to the repository") + void updateData_shouldCarryEveryChangedDetail() { + when(villageMasterRepository.updateVillageData("Attibele panchayat", "Attibele", "Main habitation", "562107", + 900111, 900011, VILLAGE_ID, "admin", Boolean.TRUE)).thenReturn(1); + + assertEquals(1, service.updateVillageData(village())); + } + + @Test + @DisplayName("updateVillageData should report nothing touched when the village is unknown") + void updateData_shouldReportNothingTouchedForUnknownVillage() { + DistrictBranchMapping request = village(); + request.setDistrictBranchID(-1); + + assertEquals(0, service.updateVillageData(request)); + } +} diff --git a/src/test/java/com/iemr/admin/service/zonemaster/ZoneMasterServiceImplTest.java b/src/test/java/com/iemr/admin/service/zonemaster/ZoneMasterServiceImplTest.java new file mode 100644 index 0000000..aa421d6 --- /dev/null +++ b/src/test/java/com/iemr/admin/service/zonemaster/ZoneMasterServiceImplTest.java @@ -0,0 +1,181 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.service.zonemaster; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.zonemaster.M_Zone; +import com.iemr.admin.data.zonemaster.M_ZoneDistrictMap; +import com.iemr.admin.repository.zonemaster.ZoneDistrictMappingRepo; +import com.iemr.admin.repository.zonemaster.ZoneMasterRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The zone service rebuilds zones and their district mappings out of positional + * query results, and cascades a retired zone onto the districts under it. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("ZoneMasterServiceImpl Test Suite") +class ZoneMasterServiceImplTest { + + private static final Integer PSM_ID = 4001; + private static final Integer ZONE_ID = 61; + + @Mock + private ZoneMasterRepository zoneMasterRepo; + + @Mock + private ZoneDistrictMappingRepo zoneDistrictMappingRepo; + + @InjectMocks + private ZoneMasterServiceImpl service; + + private static Object[] zoneRow() { + return new Object[] { ZONE_ID, "North zone", "Northern districts", "Main Road", PSM_ID, Boolean.FALSE, + 29, "Karnataka", 301, "Bengaluru Urban", 401, "North block", 501, "Hosur", 1, "India", null, + 91, "N" }; + } + + private static Object[] mappingRow() { + return new Object[] { 9001, ZONE_ID, "North zone", 301, PSM_ID, Boolean.FALSE, 29, "Karnataka", + "Bengaluru Urban", 1, "N", Boolean.FALSE }; + } + + @Test + @DisplayName("createZone should hand its batch to the repository") + void createZone_shouldHandBatchToRepository() { + ArrayList stored = new ArrayList<>(List.of(new M_Zone())); + when(zoneMasterRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createZone(new ArrayList<>())); + } + + @Test + @DisplayName("getAvailableZones should rebuild one zone per row the query answers") + void getAvailableZones_shouldRebuildEachRow() { + when(zoneMasterRepo.getAvailableZones(PSM_ID)).thenReturn(List.of(zoneRow())); + + ArrayList zones = service.getAvailableZones(PSM_ID); + + assertEquals(1, zones.size()); + assertEquals("North zone", zones.get(0).getZoneName()); + } + + @Test + @DisplayName("createZoneDistrictMapping should hand its batch to the repository") + void createZoneDistrictMapping_shouldHandBatchToRepository() { + ArrayList stored = new ArrayList<>(List.of(new M_ZoneDistrictMap())); + when(zoneDistrictMappingRepo.saveAll(anyList())).thenReturn(stored); + + assertSame(stored, service.createZoneDistrictMapping(new ArrayList<>())); + } + + @Test + @DisplayName("getAvailableZoneDistrictMappings should rebuild one mapping per row the query answers") + void getAvailableZoneDistrictMappings_shouldRebuildEachRow() { + when(zoneDistrictMappingRepo.getAvailableZoneDistrictMappings(PSM_ID)) + .thenReturn(List.of(mappingRow())); + + assertEquals(1, service.getAvailableZoneDistrictMappings(PSM_ID).size()); + } + + @Test + @DisplayName("updateZoneStatus should carry the change onto the districts under the zone") + void updateZoneStatus_shouldCascadeToDistricts() { + M_Zone request = new M_Zone(ZONE_ID, "North zone", null, null, PSM_ID, Boolean.TRUE, 1, "India", + 29, "Karnataka", 301, "Bengaluru Urban", 401, "North block", 501, "Hosur", null, 3, + "Tele Medicine"); + request.setModifiedBy("admin"); + when(zoneMasterRepo.updateZoneStatus(ZONE_ID, Boolean.TRUE, "admin")).thenReturn(1); + when(zoneDistrictMappingRepo.getAvailableZoneDistrictMappingss("61")) + .thenReturn(List.of(mappingRow())); + when(zoneDistrictMappingRepo.updateZoneDistrictMappingStatus(anyInt(), org.mockito.ArgumentMatchers.any(), + anyString())).thenReturn(1); + + assertEquals(1, service.updateZoneStatus(request)); + verify(zoneDistrictMappingRepo).updateZoneDistrictMappingStatus(9001, Boolean.TRUE, "admin"); + } + + @Test + @DisplayName("updateZoneDistrictMappingStatus should reach the repository query") + void updateZoneDistrictMappingStatus_shouldReachRepository() { + M_ZoneDistrictMap request = new M_ZoneDistrictMap(9001, ZONE_ID, "North zone", 301, PSM_ID, + Boolean.TRUE, 29, "Karnataka", "Bengaluru Urban", 3, "Tele Medicine", Boolean.FALSE); + request.setModifiedBy("admin"); + when(zoneDistrictMappingRepo.updateZoneDistrictMappingStatus(9001, Boolean.TRUE, "admin")).thenReturn(1); + + assertEquals(1, service.updateZoneDistrictMappingStatus(request)); + } + + @Test + @DisplayName("the remaining calls should each reach their own repository query") + void remainingCalls_shouldReachTheirOwnQuery() { + M_Zone zone = new M_Zone(); + M_ZoneDistrictMap mapping = new M_ZoneDistrictMap(); + when(zoneMasterRepo.save(zone)).thenReturn(zone); + when(zoneMasterRepo.getZoneById(ZONE_ID)).thenReturn(zone); + when(zoneDistrictMappingRepo.findByZoneDistrictMapID(9001)).thenReturn(mapping); + when(zoneDistrictMappingRepo.save(mapping)).thenReturn(mapping); + + assertSame(zone, service.updateZoneData(zone)); + assertSame(zone, service.getzoneByID(ZONE_ID)); + assertSame(mapping, service.editZoneDistrictMapping(9001)); + assertSame(mapping, service.saveeditedData(mapping)); + } + + @Test + @DisplayName("editZoneDistrictMapping1 should rebuild one district per row the query answers") + void editZoneDistrictMapping1_shouldRebuildEachRow() { + when(zoneDistrictMappingRepo.editZoneDistrictMapping1(ZONE_ID)) + .thenReturn(List.of(new Object[] { "Bengaluru Urban", 301 })); + + assertEquals(1, service.editZoneDistrictMapping1(ZONE_ID).size()); + } + + @Test + @DisplayName("getAllMappedRecord should report whether the zone maps more than one district") + void getAllMappedRecord_shouldReportWhetherZoneMapsMany() { + when(zoneDistrictMappingRepo.getRecord(ZONE_ID)).thenReturn(List.of(new Object(), new Object())); + assertEquals(100, service.getAllMappedRecord(ZONE_ID)); + + when(zoneDistrictMappingRepo.getRecord(ZONE_ID)).thenReturn(List.of(new Object())); + assertEquals(200, service.getAllMappedRecord(ZONE_ID)); + } +} diff --git a/src/test/java/com/iemr/admin/sevice/labmodule/LabModuleServicesTest.java b/src/test/java/com/iemr/admin/sevice/labmodule/LabModuleServicesTest.java new file mode 100644 index 0000000..2d3c540 --- /dev/null +++ b/src/test/java/com/iemr/admin/sevice/labmodule/LabModuleServicesTest.java @@ -0,0 +1,613 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.sevice.labmodule; + +import java.sql.Timestamp; +import java.util.ArrayList; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.iemr.admin.data.calibration.Calibration; +import com.iemr.admin.data.labmodule.ComponentMaster; +import com.iemr.admin.data.labmodule.ComponentResultMap; +import com.iemr.admin.data.labmodule.IOTComponent; +import com.iemr.admin.data.labmodule.IOTProcedure; +import com.iemr.admin.data.labmodule.ProcedureComponentMapping; +import com.iemr.admin.data.labmodule.ProcedureMaster; +import com.iemr.admin.repo.calibration.CalibrationAPIRepo; +import com.iemr.admin.repo.labmodule.ComponentMasterRepo; +import com.iemr.admin.repo.labmodule.ComponentResultMapRepo; +import com.iemr.admin.repo.labmodule.IOTRepo; +import com.iemr.admin.repo.labmodule.ProcedureComponentMappingRepo; +import com.iemr.admin.repo.labmodule.ProcedureMasterRepo; +import com.iemr.admin.utils.exception.IEMRException; + +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.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * The lab module masters define the tests a diagnostic device can run and the + * components each test reports, so a broken mapping means a result that cannot + * be recorded against its test. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("Lab module master service Test Suite") +class LabModuleServicesTest { + + private static final Integer PSM_ID = 4001; + private static final Integer PROCEDURE_ID = 71; + private static final Integer COMPONENT_ID = 81; + + @Mock + private ProcedureMasterRepo procedureMasterRepo; + + @Mock + private ComponentMasterRepo componentMasterRepo; + + @Mock + private ComponentResultMapRepo componentResultMapRepo; + + @Mock + private ProcedureComponentMappingRepo procedureComponentMappingRepo; + + @Mock + private CalibrationAPIRepo calibrationAPIRepo; + + @Mock + private IOTRepo iotRepo; + + private MastersCreationServiceImpl creationService; + private MastersFetchingServiceImpl fetchingService; + private MastersMappingServiceImpl mappingService; + private MastersStatusUpdateImpl statusService; + private IOTServiceImpl iotService; + + @BeforeEach + void setUp() { + creationService = new MastersCreationServiceImpl(); + creationService.setProcedureMasterRepo(procedureMasterRepo); + creationService.setComponentMasterRepo(componentMasterRepo); + creationService.setComponentResultMapRepo(componentResultMapRepo); + creationService.calibrationAPIRepo = calibrationAPIRepo; + creationService.iotRepo = iotRepo; + + fetchingService = new MastersFetchingServiceImpl(); + fetchingService.setProcedureMasterRepo(procedureMasterRepo); + fetchingService.setComponentMasterRepo(componentMasterRepo); + fetchingService.setComponentResultMapRepo(componentResultMapRepo); + fetchingService.setProcedureComponentMappingRepo(procedureComponentMappingRepo); + + mappingService = new MastersMappingServiceImpl(); + mappingService.setProcedureComponentMappingRepo(procedureComponentMappingRepo); + + statusService = new MastersStatusUpdateImpl(); + statusService.setProcedureMasterRepo(procedureMasterRepo); + statusService.setComponentMasterRepo(componentMasterRepo); + statusService.setComponentResultMapRepo(componentResultMapRepo); + statusService.calibrationAPIRepo = calibrationAPIRepo; + statusService.iotRepo = iotRepo; + + iotService = new IOTServiceImpl(); + org.springframework.test.util.ReflectionTestUtils.setField(iotService, "iotRepo", iotRepo); + } + + /** One row of the procedure detail query, in the order the builder reads it. */ + private static ArrayList procedureDetailRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { PROCEDURE_ID, "Haemoglobin", "Blood test", "Lab", "Both", PSM_ID, Boolean.FALSE, + "N", "admin", Timestamp.valueOf("2026-02-17 09:30:00"), "admin", Boolean.FALSE, + Timestamp.valueOf("2026-02-17 09:30:00") }); + return rows; + } + + private static ArrayList componentDetailRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { COMPONENT_ID, "Haemoglobin count", "g/dL", "TextBox", "LOINC-1", Boolean.FALSE }); + return rows; + } + + @Nested + @DisplayName("MastersCreationServiceImpl") + class CreationTests { + + @Test + @DisplayName("createProcedureMaster should default a procedure that does not say whether it is mandatory") + void createProcedureMaster_shouldDefaultMandatoryFlag() throws Exception { + when(procedureMasterRepo.save(any())).thenAnswer(call -> { + ProcedureMaster saved = call.getArgument(0); + saved.setProcedureID(PROCEDURE_ID); + return saved; + }); + when(procedureMasterRepo.getProcedureDetails(PROCEDURE_ID)).thenReturn(procedureDetailRow()); + + String created = creationService.createProcedureMaster( + "{\"procedureName\":\"Haemoglobin\",\"procedureType\":\"Lab\",\"createdBy\":\"admin\"}"); + + assertTrue(created.contains("Haemoglobin"), created); + } + + @Test + @DisplayName("createProcedureMaster should answer nothing when the stored procedure cannot be read back") + void createProcedureMaster_shouldAnswerNothingWhenUnreadable() throws Exception { + when(procedureMasterRepo.save(any())).thenAnswer(call -> call.getArgument(0)); + when(procedureMasterRepo.getProcedureDetails(any())).thenReturn(new ArrayList<>()); + + assertNull(creationService.createProcedureMaster("{\"procedureName\":\"Haemoglobin\"}")); + } + + @Test + @DisplayName("createProcedureMaster should fill the calibration URLs for a calibrated device test") + void createProcedureMaster_shouldFillCalibrationUrls() throws Exception { + IOTProcedure iotProcedure = new IOTProcedure(); + iotProcedure.setCalibrationCode("HB"); + Calibration calibration = new Calibration(); + calibration.setCalibrationStartAPI("http://device/start/{test_name}"); + calibration.setCalibrationStatusAPI("http://device/status/{test_name}"); + calibration.setCalibrationEndAPI("http://device/end/{test_name}"); + when(iotRepo.getIOTProcedureByID(91)).thenReturn(iotProcedure); + when(calibrationAPIRepo.getCalibration()).thenReturn(calibration); + when(procedureMasterRepo.save(any())).thenAnswer(call -> call.getArgument(0)); + when(procedureMasterRepo.getProcedureDetails(any())).thenReturn(procedureDetailRow()); + + creationService.createProcedureMaster("{\"procedureName\":\"Haemoglobin\"," + + "\"isCalibration\":true,\"iotProcedureID\":91}"); + + verify(iotRepo).updateIOTWithCalibration("http://device/start/HB", "http://device/status/HB", + "http://device/end/HB", 91); + } + + @Test + @DisplayName("createProcedureMaster should leave the calibration URLs alone once they are set") + void createProcedureMaster_shouldLeaveExistingCalibrationUrls() throws Exception { + IOTProcedure iotProcedure = new IOTProcedure(); + iotProcedure.setCalibrationCode("HB"); + iotProcedure.setCalibrationStartAPI("http://device/start/HB"); + when(iotRepo.getIOTProcedureByID(91)).thenReturn(iotProcedure); + when(calibrationAPIRepo.getCalibration()).thenReturn(new Calibration()); + when(procedureMasterRepo.save(any())).thenAnswer(call -> call.getArgument(0)); + when(procedureMasterRepo.getProcedureDetails(any())).thenReturn(procedureDetailRow()); + + creationService.createProcedureMaster("{\"procedureName\":\"Haemoglobin\"," + + "\"isCalibration\":true,\"iotProcedureID\":91}"); + + verify(iotRepo, never()).updateIOTWithCalibration(anyString(), anyString(), anyString(), anyInt()); + } + + @Test + @DisplayName("createProcedureMaster should refuse a device the calibration store does not know") + void createProcedureMaster_shouldRefuseUnknownDevice() { + when(iotRepo.getIOTProcedureByID(91)).thenReturn(null); + + assertThrows(IEMRException.class, () -> creationService.createProcedureMaster( + "{\"procedureName\":\"Haemoglobin\",\"isCalibration\":true,\"iotProcedureID\":91}")); + } + + @Test + @DisplayName("createComponentMaster should store the result options a picklist component offers") + void createComponentMaster_shouldStoreResultOptions() throws Exception { + when(componentMasterRepo.save(any())).thenAnswer(call -> call.getArgument(0)); + when(componentResultMapRepo.saveAll(anyList())).thenAnswer(call -> { + List given = call.getArgument(0); + return new ArrayList<>(given); + }); + + String created = creationService.createComponentMaster("{\"testComponentName\":\"Blood group\"," + + "\"testComponentID\":81,\"providerServiceMapID\":4001,\"createdBy\":\"admin\"," + + "\"compOpt\":[{\"name\":\"A+\"},{\"name\":\"B+\"}]}"); + + assertTrue(created.contains("Blood group"), created); + } + + @Test + @DisplayName("createComponentMaster should store a component that offers no result options") + void createComponentMaster_shouldStoreComponentWithoutOptions() throws Exception { + when(componentMasterRepo.save(any())).thenAnswer(call -> call.getArgument(0)); + + String created = creationService + .createComponentMaster("{\"testComponentName\":\"Haemoglobin count\"}"); + + assertTrue(created.contains("Haemoglobin count"), created); + verify(componentResultMapRepo, never()).saveAll(anyList()); + } + + @Test + @DisplayName("createComponentMaster should refuse a run that stored fewer options than it was given") + void createComponentMaster_shouldRefusePartialOptionStore() { + when(componentMasterRepo.save(any())).thenAnswer(call -> call.getArgument(0)); + when(componentResultMapRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertThrows(Exception.class, () -> creationService.createComponentMaster( + "{\"testComponentName\":\"Blood group\",\"compOpt\":[{\"name\":\"A+\"}]}")); + } + + @Test + @DisplayName("createComponentMaster should answer nothing when the component could not be stored") + void createComponentMaster_shouldAnswerNothingWhenUnstored() throws Exception { + when(componentMasterRepo.save(any())).thenReturn(null); + + assertNull(creationService.createComponentMaster("{\"testComponentName\":\"Blood group\"}")); + } + } + + @Nested + @DisplayName("MastersFetchingServiceImpl") + class FetchingTests { + + @Test + @DisplayName("getProcedureMaster should publish the procedures of the provider") + void getProcedureMaster_shouldPublishProviderProcedures() throws Exception { + ProcedureMaster procedure = new ProcedureMaster(); + procedure.setProcedureID(PROCEDURE_ID); + procedure.setProcedureName("Haemoglobin"); + when(procedureMasterRepo.findProcByPSMIDc(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(procedure))); + + assertTrue(fetchingService.getProcedureMaster(PSM_ID).contains("Haemoglobin")); + } + + @Test + @DisplayName("getProcedureMasterDelFalse should publish only the live procedures") + void getProcedureMasterDelFalse_shouldPublishLiveProcedures() throws Exception { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { PROCEDURE_ID, "Haemoglobin", "Blood test", "Lab" }); + when(procedureMasterRepo.getProcedureDetailsDelFalse(PSM_ID)).thenReturn(rows); + + assertTrue(fetchingService.getProcedureMasterDelFalse(PSM_ID).contains("Haemoglobin")); + } + + @Test + @DisplayName("getComponentMaster should publish the components of the provider") + void getComponentMaster_shouldPublishProviderComponents() throws Exception { + ComponentMaster component = new ComponentMaster(); + component.setTestComponentID(COMPONENT_ID); + component.setTestComponentName("Haemoglobin count"); + when(componentMasterRepo.getComponentDetailsBypsmID(PSM_ID)) + .thenReturn(new ArrayList<>(List.of(component))); + + assertTrue(fetchingService.getComponentMaster(PSM_ID).contains("Haemoglobin count")); + } + + @Test + @DisplayName("getComponentMasterDelFalse should publish only the live components") + void getComponentMasterDelFalse_shouldPublishLiveComponents() throws Exception { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { COMPONENT_ID, "Haemoglobin count", "g/dL", "TextBox", "LOINC-1", "component" }); + when(componentMasterRepo.getComponentDetailsDelFalse(PSM_ID)).thenReturn(rows); + + assertTrue(fetchingService.getComponentMasterDelFalse(PSM_ID).contains("Haemoglobin count")); + } + + @Test + @DisplayName("getComponentMasterDelFalse should publish an empty list when nothing is live") + void getComponentMasterDelFalse_shouldPublishEmptyList() throws Exception { + when(componentMasterRepo.getComponentDetailsDelFalse(PSM_ID)).thenReturn(new ArrayList<>()); + + assertEquals("[]", fetchingService.getComponentMasterDelFalse(PSM_ID)); + } + + @Test + @DisplayName("getProcCompMappingDelFalse should publish the mappings of the provider") + void getProcCompMappingDelFalse_shouldPublishMappings() throws Exception { + when(procedureComponentMappingRepo.getProcedureComponentMappingList(PSM_ID)) + .thenReturn(mappingRow()); + + assertTrue(fetchingService.getProcCompMappingDelFalse(PSM_ID).contains("Haemoglobin")); + } + + @Test + @DisplayName("getProcCompMappingDelFalse should publish an empty list when nothing is mapped") + void getProcCompMappingDelFalse_shouldPublishEmptyList() throws Exception { + when(procedureComponentMappingRepo.getProcedureComponentMappingList(PSM_ID)) + .thenReturn(new ArrayList<>()); + + assertEquals("[]", fetchingService.getProcCompMappingDelFalse(PSM_ID)); + } + + @Test + @DisplayName("getProcCompMappingForProcedureID should publish the mappings of the procedure") + void getProcCompMappingForProcedureID_shouldPublishMappings() throws Exception { + when(procedureComponentMappingRepo.getProcedureComponentMappingListForProcedureID(PROCEDURE_ID)) + .thenReturn(mappingRow()); + + assertTrue(fetchingService.getProcCompMappingForProcedureID(PROCEDURE_ID).contains("Haemoglobin")); + } + + @Test + @DisplayName("getProcCompMappingForProcedureID should publish an empty list when nothing is mapped") + void getProcCompMappingForProcedureID_shouldPublishEmptyList() throws Exception { + when(procedureComponentMappingRepo.getProcedureComponentMappingListForProcedureID(PROCEDURE_ID)) + .thenReturn(new ArrayList<>()); + + assertEquals("[]", fetchingService.getProcCompMappingForProcedureID(PROCEDURE_ID)); + } + + @Test + @DisplayName("getComponentDetailsForComponentID should publish a typed component without its options") + void getComponentDetails_shouldPublishTypedComponent() throws Exception { + ComponentMaster stored = new ComponentMaster(); + stored.setTestComponentID(COMPONENT_ID); + stored.setTestComponentName("Haemoglobin count"); + stored.setInputType("TextBox"); + when(componentMasterRepo.findByTestComponentID(COMPONENT_ID)).thenReturn(stored); + + String published = fetchingService.getComponentDetailsForComponentID(COMPONENT_ID); + + assertTrue(published.contains("Haemoglobin count"), published); + verify(componentResultMapRepo, never()).findByTestComponentIDAndDeleted(anyInt(), anyBoolean()); + } + + @Test + @DisplayName("getComponentDetailsForComponentID should publish a picklist component with its options") + void getComponentDetails_shouldPublishPicklistWithOptions() throws Exception { + ComponentMaster stored = new ComponentMaster(); + stored.setTestComponentID(COMPONENT_ID); + stored.setTestComponentName("Blood group"); + stored.setInputType("Dropdown"); + ComponentResultMap option = new ComponentResultMap(); + option.setResultValue("A+"); + when(componentMasterRepo.findByTestComponentID(COMPONENT_ID)).thenReturn(stored); + when(componentResultMapRepo.findByTestComponentIDAndDeleted(COMPONENT_ID, false)) + .thenReturn(new ArrayList<>(List.of(option))); + + String published = fetchingService.getComponentDetailsForComponentID(COMPONENT_ID); + + assertTrue(published.contains("A+"), published); + } + + @Test + @DisplayName("getComponentDetailsForComponentID should answer nothing for a component that does not exist") + void getComponentDetails_shouldAnswerNothingForUnknownComponent() throws Exception { + when(componentMasterRepo.findByTestComponentID(COMPONENT_ID)).thenReturn(null); + + assertNull(fetchingService.getComponentDetailsForComponentID(COMPONENT_ID)); + } + + private ArrayList mappingRow() { + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { PROCEDURE_ID, COMPONENT_ID, "Haemoglobin", "Blood test", + "Haemoglobin count", null, "g/dL" }); + return rows; + } + } + + @Nested + @DisplayName("MastersMappingServiceImpl") + class MappingTests { + + @Test + @DisplayName("createProcedureComponentMapping should replace the previous mapping of the procedure") + void createMapping_shouldReplacePreviousMapping() throws Exception { + when(procedureComponentMappingRepo.saveAll(anyList())) + .thenAnswer(call -> new ArrayList<>((List) call.getArgument(0))); + ArrayList rows = new ArrayList<>(); + rows.add(new Object[] { PROCEDURE_ID, COMPONENT_ID, "Haemoglobin", "Blood test", + "Haemoglobin count", null, "g/dL" }); + when(procedureComponentMappingRepo.getProcedureComponentMappingListForProcedureID(PROCEDURE_ID)) + .thenReturn(rows); + + String mapped = mappingService.createProcedureComponentMapping("{\"procedureID\":71," + + "\"providerServiceMapID\":4001,\"createdBy\":\"admin\"," + + "\"compList\":[{\"testComponentID\":81}]}"); + + assertTrue(mapped.contains("Haemoglobin"), mapped); + verify(procedureComponentMappingRepo).softDeleteProcCompMapping(PROCEDURE_ID, "admin"); + } + + @Test + @DisplayName("createProcedureComponentMapping should answer nothing when it stored fewer than it was given") + void createMapping_shouldAnswerNothingOnPartialStore() throws Exception { + when(procedureComponentMappingRepo.saveAll(anyList())) + .thenReturn(new ArrayList()); + + assertNull(mappingService.createProcedureComponentMapping("{\"procedureID\":71," + + "\"createdBy\":\"admin\",\"compList\":[{\"testComponentID\":81}]}")); + } + + @Test + @DisplayName("createProcedureComponentMapping should report a request that maps no components") + void createMapping_shouldReportEmptyRequest() throws Exception { + assertEquals("1", mappingService.createProcedureComponentMapping("{\"procedureID\":71}")); + verify(procedureComponentMappingRepo, never()).saveAll(anyList()); + } + } + + @Nested + @DisplayName("MastersStatusUpdateImpl") + class StatusUpdateTests { + + @Test + @DisplayName("updateProcedureStatus should publish the procedure once its status has changed") + void updateProcedureStatus_shouldPublishChangedProcedure() throws Exception { + when(procedureMasterRepo.updateProcedureStatus(PROCEDURE_ID, true)).thenReturn(1); + when(procedureMasterRepo.getProcedureDetails(PROCEDURE_ID)).thenReturn(procedureDetailRow()); + + assertTrue(statusService.updateProcedureStatus(PROCEDURE_ID, true).contains("Haemoglobin")); + } + + @Test + @DisplayName("updateProcedureStatus should answer nothing when no procedure changed") + void updateProcedureStatus_shouldAnswerNothingWhenNothingChanged() throws Exception { + when(procedureMasterRepo.updateProcedureStatus(PROCEDURE_ID, true)).thenReturn(0); + + assertNull(statusService.updateProcedureStatus(PROCEDURE_ID, true)); + } + + @Test + @DisplayName("updateComponentStatus should publish the component once its status has changed") + void updateComponentStatus_shouldPublishChangedComponent() throws Exception { + when(componentMasterRepo.updateComponentStatus(COMPONENT_ID, true)).thenReturn(1); + when(componentMasterRepo.getComponentDetailsByCompID(COMPONENT_ID)).thenReturn(componentDetailRow()); + + assertTrue(statusService.updateComponentStatus(COMPONENT_ID, true).contains("Haemoglobin count")); + } + + @Test + @DisplayName("updateComponentStatus should answer nothing when no component changed") + void updateComponentStatus_shouldAnswerNothingWhenNothingChanged() throws Exception { + when(componentMasterRepo.updateComponentStatus(COMPONENT_ID, true)).thenReturn(0); + + assertNull(statusService.updateComponentStatus(COMPONENT_ID, true)); + } + + @Test + @DisplayName("updateProcedureMaster should publish the procedure once the edit lands") + void updateProcedureMaster_shouldPublishEditedProcedure() throws Exception { + when(procedureMasterRepo.updateProcedureDetails(anyInt(), anyString(), any(), anyString(), + anyString(), anyString(), any(), any(), any())).thenReturn(1); + when(procedureMasterRepo.getProcedureDetails(PROCEDURE_ID)).thenReturn(procedureDetailRow()); + + String published = statusService.updateProcedureMaster("{\"procedureID\":71," + + "\"procedureName\":\"Haemoglobin\",\"procedureType\":\"Lab\",\"gender\":\"Both\"," + + "\"modifiedBy\":\"admin\"}"); + + assertTrue(published.contains("Haemoglobin"), published); + } + + @Test + @DisplayName("updateProcedureMaster should answer nothing for an edit that leaves out a mandatory field") + void updateProcedureMaster_shouldAnswerNothingForIncompleteEdit() throws Exception { + assertNull(statusService.updateProcedureMaster("{\"procedureID\":71}")); + } + + @Test + @DisplayName("updateProcedureMaster should clear the calibration URLs when calibration is switched off") + void updateProcedureMaster_shouldClearCalibrationUrls() throws Exception { + IOTProcedure iotProcedure = new IOTProcedure(); + iotProcedure.setCalibrationCode("HB"); + iotProcedure.setCalibrationStartAPI("http://device/start/HB"); + when(iotRepo.getIOTProcedureByID(91)).thenReturn(iotProcedure); + when(calibrationAPIRepo.getCalibration()).thenReturn(new Calibration()); + when(procedureMasterRepo.updateProcedureDetails(anyInt(), anyString(), any(), anyString(), + anyString(), anyString(), any(), any(), any())).thenReturn(1); + when(procedureMasterRepo.getProcedureDetails(PROCEDURE_ID)).thenReturn(procedureDetailRow()); + + statusService.updateProcedureMaster("{\"procedureID\":71,\"procedureName\":\"Haemoglobin\"," + + "\"procedureType\":\"Lab\",\"gender\":\"Both\",\"modifiedBy\":\"admin\"," + + "\"isCalibration\":false,\"iotProcedureID\":91}"); + + verify(iotRepo).updateIOTWithCalibration(null, null, null, 91); + } + + @Test + @DisplayName("updateProcedureMaster should refuse a device the calibration store does not know") + void updateProcedureMaster_shouldRefuseUnknownDevice() { + when(iotRepo.getIOTProcedureByID(91)).thenReturn(null); + + assertThrows(IEMRException.class, () -> statusService.updateProcedureMaster( + "{\"procedureID\":71,\"isCalibration\":true,\"iotProcedureID\":91}")); + } + + @Test + @DisplayName("updateComponentMaster should publish a typed component once the edit lands") + void updateComponentMaster_shouldPublishEditedTypedComponent() throws Exception { + when(componentMasterRepo.updateComponentDetailsTextBox(anyInt(), anyString(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any())).thenReturn(1); + when(componentMasterRepo.getComponentDetailsByCompID(COMPONENT_ID)).thenReturn(componentDetailRow()); + + String published = statusService.updateComponentMaster("{\"testComponentID\":81," + + "\"testComponentName\":\"Haemoglobin count\",\"inputType\":\"TextBox\"," + + "\"modifiedBy\":\"admin\"}"); + + assertTrue(published.contains("Haemoglobin count"), published); + } + + @Test + @DisplayName("updateComponentMaster should replace the result options of a picklist component") + void updateComponentMaster_shouldReplacePicklistOptions() throws Exception { + when(componentMasterRepo.updateComponentDetailsOtherThenTextBox(anyInt(), anyString(), any(), + anyString(), any(), any(), any())).thenReturn(1); + when(componentResultMapRepo.saveAll(anyList())) + .thenAnswer(call -> new ArrayList<>((List) call.getArgument(0))); + when(componentMasterRepo.getComponentDetailsByCompID(COMPONENT_ID)).thenReturn(componentDetailRow()); + + String published = statusService.updateComponentMaster("{\"testComponentID\":81," + + "\"testComponentName\":\"Blood group\",\"inputType\":\"Dropdown\"," + + "\"modifiedBy\":\"admin\",\"compOpt\":[{\"name\":\"A+\"}]}"); + + assertTrue(published.contains("Haemoglobin count"), published); + verify(componentResultMapRepo).deletePreviousCompResultMappingSoft(COMPONENT_ID, "admin"); + } + + @Test + @DisplayName("updateComponentMaster should refuse a run that stored fewer options than it was given") + void updateComponentMaster_shouldRefusePartialOptionStore() { + when(componentMasterRepo.updateComponentDetailsOtherThenTextBox(anyInt(), anyString(), any(), + anyString(), any(), any(), any())).thenReturn(1); + when(componentResultMapRepo.saveAll(anyList())).thenReturn(new ArrayList()); + + assertThrows(Exception.class, () -> statusService.updateComponentMaster("{\"testComponentID\":81," + + "\"testComponentName\":\"Blood group\",\"inputType\":\"Dropdown\"," + + "\"modifiedBy\":\"admin\",\"compOpt\":[{\"name\":\"A+\"}]}")); + } + + @Test + @DisplayName("updateComponentMaster should answer nothing for a component that names no input type") + void updateComponentMaster_shouldAnswerNothingWithoutInputType() throws Exception { + assertNull(statusService.updateComponentMaster("{\"testComponentID\":81}")); + } + } + + @Nested + @DisplayName("IOTServiceImpl") + class IotServiceTests { + + @Test + @DisplayName("getIOTProcedure should answer the device tests on record") + void getIOTProcedure_shouldAnswerDeviceTests() { + IOTProcedure procedure = new IOTProcedure(); + procedure.setCalibrationCode("HB"); + when(iotRepo.getIOTProcedure()).thenReturn(new ArrayList<>(List.of(procedure))); + + assertTrue(iotService.getIOTProcedure().contains("HB")); + } + + @Test + @DisplayName("getIOTComponent should answer the device components on record") + void getIOTComponent_shouldAnswerDeviceComponents() { + IOTComponent component = new IOTComponent(); + when(iotRepo.getIOTComponent()).thenReturn(new ArrayList<>(List.of(component))); + + assertTrue(iotService.getIOTComponent().startsWith("[")); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/CookieUtilTest.java b/src/test/java/com/iemr/admin/utils/CookieUtilTest.java new file mode 100644 index 0000000..3e9fbda --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/CookieUtilTest.java @@ -0,0 +1,115 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + + +@ExtendWith(MockitoExtension.class) +@DisplayName("CookieUtil Test Suite") +class CookieUtilTest { + + @Mock + HttpServletRequest request; + + @InjectMocks + CookieUtil cookieUtil; + + @Test + @DisplayName("Should return cookie value when cookie exists") + void getCookieValue_cookieExists() { + Cookie cookie = mock(Cookie.class); + doReturn("myCookieName").when(cookie).getName(); + doReturn("myCookieValue").when(cookie).getValue(); + doReturn(new Cookie[]{cookie}).when(request).getCookies(); + + Optional result = cookieUtil.getCookieValue(request, "myCookieName"); + + assertTrue(result.isPresent()); + assertEquals("myCookieValue", result.get()); + } + + @Test + @DisplayName("Should return empty Optional when cookie does not exist") + void getCookieValue_cookieDoesNotExist() { + doReturn(new Cookie[0]).when(request).getCookies(); + Optional result = cookieUtil.getCookieValue(request, "myCookieName"); + assertFalse(result.isPresent()); + } + + @Test + @DisplayName("Should return empty Optional when cookies array is null") + void getCookieValue_cookiesIsNull() { + doReturn(null).when(request).getCookies(); + Optional result = cookieUtil.getCookieValue(request, "myCookieName"); + assertFalse(result.isPresent()); + } + + + @Test + @DisplayName("Should return JWT token when JWT cookie exists") + void getJwtTokenFromCookie_jwtCookieExists() { + Cookie jwtCookie = mock(Cookie.class); + doReturn("Jwttoken").when(jwtCookie).getName(); + doReturn("myJwtToken").when(jwtCookie).getValue(); + doReturn(new Cookie[]{jwtCookie}).when(request).getCookies(); + + String jwtToken = CookieUtil.getJwtTokenFromCookie(request); + + assertEquals("myJwtToken", jwtToken); + } + + @Test + @DisplayName("Should return null when JWT cookie does not exist") + void getJwtTokenFromCookie_jwtCookieDoesNotExist() { + Cookie otherCookie = mock(Cookie.class); + doReturn("otherCookie").when(otherCookie).getName(); + // doReturn("otherValue").when(otherCookie).getValue(); + doReturn(new Cookie[]{otherCookie}).when(request).getCookies(); + + String jwtToken = CookieUtil.getJwtTokenFromCookie(request); + + assertNull(jwtToken); + } + + @Test + @DisplayName("Should return null when cookies array is null for JWT token lookup") + void getJwtTokenFromCookie_cookiesIsNull() { + doReturn(null).when(request).getCookies(); + String jwtToken = CookieUtil.getJwtTokenFromCookie(request); + assertNull(jwtToken); + } +} \ No newline at end of file diff --git a/src/test/java/com/iemr/admin/utils/FilterConfigTest.java b/src/test/java/com/iemr/admin/utils/FilterConfigTest.java new file mode 100644 index 0000000..46580be --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/FilterConfigTest.java @@ -0,0 +1,84 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.core.Ordered; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +@ExtendWith(MockitoExtension.class) +@DisplayName("FilterConfig Test Suite") +class FilterConfigTest { + + private static final String ALLOWED_ORIGINS = "https://amrit.example.org,http://localhost:*"; + + @Mock + private JwtAuthenticationUtil jwtAuthenticationUtil; + + private FilterConfig filterConfig; + + @BeforeEach + @DisplayName("Configure the allow-list before each test") + void setUp() { + filterConfig = new FilterConfig(); + ReflectionTestUtils.setField(filterConfig, "allowedOrigins", ALLOWED_ORIGINS); + } + + @Test + @DisplayName("jwtUserIdValidationFilter should register the JWT filter across every url pattern") + void jwtUserIdValidationFilter_shouldRegisterFilterForEveryUrlPattern() { + FilterRegistrationBean registration = + filterConfig.jwtUserIdValidationFilter(jwtAuthenticationUtil); + + assertNotNull(registration.getFilter()); + assertEquals(java.util.Set.of("/*"), registration.getUrlPatterns()); + } + + @Test + @DisplayName("jwtUserIdValidationFilter should run at the highest precedence so auth happens first") + void jwtUserIdValidationFilter_shouldRunAtHighestPrecedence() { + FilterRegistrationBean registration = + filterConfig.jwtUserIdValidationFilter(jwtAuthenticationUtil); + + assertEquals(Ordered.HIGHEST_PRECEDENCE, registration.getOrder()); + } + + @Test + @DisplayName("jwtUserIdValidationFilter should hand the filter the configured origins and auth util") + void jwtUserIdValidationFilter_shouldPassOriginsAndAuthUtilToFilter() { + JwtUserIdValidationFilter filter = + filterConfig.jwtUserIdValidationFilter(jwtAuthenticationUtil).getFilter(); + + assertEquals(ALLOWED_ORIGINS, ReflectionTestUtils.getField(filter, "allowedOrigins")); + assertSame(jwtAuthenticationUtil, ReflectionTestUtils.getField(filter, "jwtAuthenticationUtil")); + } +} diff --git a/src/test/java/com/iemr/admin/utils/JwtAuthenticationUtilTest.java b/src/test/java/com/iemr/admin/utils/JwtAuthenticationUtilTest.java new file mode 100644 index 0000000..8c2f0c7 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/JwtAuthenticationUtilTest.java @@ -0,0 +1,221 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; + +import com.iemr.admin.data.user.M_User; +import com.iemr.admin.repository.user.UserLoginRepo; +import com.iemr.admin.utils.exception.IEMRException; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; + +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.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("JwtAuthenticationUtil Test Suite") +class JwtAuthenticationUtilTest { + + private static final String TOKEN = "a.jwt.token"; + private static final String USER_ID = "3117"; + + @Mock + private CookieUtil cookieUtil; + + @Mock + private JwtUtil jwtUtil; + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private ValueOperations valueOperations; + + @Mock + private UserLoginRepo userLoginRepo; + + @Mock + private Claims claims; + + @Mock + private HttpServletRequest request; + + private JwtAuthenticationUtil authenticationUtil; + + @BeforeEach + void setUp() { + authenticationUtil = new JwtAuthenticationUtil(cookieUtil, jwtUtil); + ReflectionTestUtils.setField(authenticationUtil, "redisTemplate", redisTemplate); + ReflectionTestUtils.setField(authenticationUtil, "userLoginRepo", userLoginRepo); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + } + + @Test + @DisplayName("validateJwtToken should answer the subject when the cookie carries a valid token") + void validateJwtToken_shouldAnswerSubjectForValidToken() { + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(Optional.of(TOKEN)); + when(jwtUtil.validateToken(TOKEN)).thenReturn(claims); + when(claims.getSubject()).thenReturn("dr.mehta"); + + ResponseEntity response = authenticationUtil.validateJwtToken(request); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("dr.mehta", response.getBody()); + } + + @Test + @DisplayName("validateJwtToken should refuse a request that carries no token cookie") + void validateJwtToken_shouldRefuseWhenCookieAbsent() { + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(Optional.empty()); + + ResponseEntity response = authenticationUtil.validateJwtToken(request); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertEquals("Error 401: Unauthorized - JWT Token is not set!", response.getBody()); + } + + @Test + @DisplayName("validateJwtToken should refuse a token the validator rejects") + void validateJwtToken_shouldRefuseInvalidToken() { + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(Optional.of(TOKEN)); + when(jwtUtil.validateToken(TOKEN)).thenReturn(null); + + ResponseEntity response = authenticationUtil.validateJwtToken(request); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertEquals("Error 401: Unauthorized - Invalid JWT Token!", response.getBody()); + } + + @Test + @DisplayName("validateJwtToken should refuse a valid token that names no subject") + void validateJwtToken_shouldRefuseTokenWithoutSubject() { + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(Optional.of(TOKEN)); + when(jwtUtil.validateToken(TOKEN)).thenReturn(claims); + when(claims.getSubject()).thenReturn(null); + + ResponseEntity response = authenticationUtil.validateJwtToken(request); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertEquals("Error 401: Unauthorized - Username is missing!", response.getBody()); + } + + @Test + @DisplayName("validateJwtToken should refuse a valid token whose subject is blank") + void validateJwtToken_shouldRefuseTokenWithBlankSubject() { + when(cookieUtil.getCookieValue(request, "Jwttoken")).thenReturn(Optional.of(TOKEN)); + when(jwtUtil.validateToken(TOKEN)).thenReturn(claims); + when(claims.getSubject()).thenReturn(""); + + ResponseEntity response = authenticationUtil.validateJwtToken(request); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertEquals("Error 401: Unauthorized - Username is missing!", response.getBody()); + } + + @Test + @DisplayName("validateUserIdAndJwtToken should accept a user the cache already holds") + void validateUserIdAndJwtToken_shouldAcceptCachedUser() throws IEMRException { + M_User cached = new M_User(); + cached.setUserID(3117); + when(jwtUtil.validateToken(TOKEN)).thenReturn(claims); + when(claims.get("userId", String.class)).thenReturn(USER_ID); + when(valueOperations.get("user_" + USER_ID)).thenReturn(cached); + + assertTrue(authenticationUtil.validateUserIdAndJwtToken(TOKEN)); + verify(userLoginRepo, never()).findByUserID(anyInt()); + } + + @Test + @DisplayName("validateUserIdAndJwtToken should fall back to the database and cache what it finds") + void validateUserIdAndJwtToken_shouldFallBackToDatabaseAndCache() throws IEMRException { + M_User stored = new M_User(); + stored.setUserID(3117); + stored.setUserName("dr.mehta"); + when(jwtUtil.validateToken(TOKEN)).thenReturn(claims); + when(claims.get("userId", String.class)).thenReturn(USER_ID); + when(valueOperations.get("user_" + USER_ID)).thenReturn(null); + when(userLoginRepo.findByUserID(3117)).thenReturn(stored); + + assertTrue(authenticationUtil.validateUserIdAndJwtToken(TOKEN)); + verify(valueOperations).set(eq("user_" + USER_ID), any(M_User.class), anyLong(), eq(TimeUnit.MINUTES)); + } + + @Test + @DisplayName("validateUserIdAndJwtToken should reject a token the validator does not accept") + void validateUserIdAndJwtToken_shouldRejectInvalidToken() { + when(jwtUtil.validateToken(TOKEN)).thenReturn(null); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> authenticationUtil.validateUserIdAndJwtToken(TOKEN)); + assertTrue(thrown.getMessage().contains("Invalid JWT token."), thrown.getMessage()); + } + + @Test + @DisplayName("validateUserIdAndJwtToken should reject a user neither the cache nor the database knows") + void validateUserIdAndJwtToken_shouldRejectUnknownUser() { + when(jwtUtil.validateToken(TOKEN)).thenReturn(claims); + when(claims.get("userId", String.class)).thenReturn(USER_ID); + when(valueOperations.get("user_" + USER_ID)).thenReturn(null); + when(userLoginRepo.findByUserID(3117)).thenReturn(null); + + IEMRException thrown = assertThrows(IEMRException.class, + () -> authenticationUtil.validateUserIdAndJwtToken(TOKEN)); + assertTrue(thrown.getMessage().contains("Invalid User ID."), thrown.getMessage()); + } + + @Test + @DisplayName("validateUserIdAndJwtToken should surface a non-numeric user id as a validation failure") + void validateUserIdAndJwtToken_shouldRejectNonNumericUserId() { + when(jwtUtil.validateToken(TOKEN)).thenReturn(claims); + when(claims.get("userId", String.class)).thenReturn("not-a-number"); + when(valueOperations.get(anyString())).thenReturn(null); + + assertThrows(IEMRException.class, () -> authenticationUtil.validateUserIdAndJwtToken(TOKEN)); + } +} diff --git a/src/test/java/com/iemr/admin/utils/JwtUserIdValidationFilterTest.java b/src/test/java/com/iemr/admin/utils/JwtUserIdValidationFilterTest.java new file mode 100644 index 0000000..f6494ef --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/JwtUserIdValidationFilterTest.java @@ -0,0 +1,415 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +import java.util.Arrays; + +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import com.iemr.admin.utils.exception.IEMRException; +import com.iemr.admin.utils.http.AuthorizationHeaderRequestWrapper; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; + +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.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +/** + * The filter is the service's front door: it lets the monitoring and login + * endpoints through untouched, turns away calls from origins that are not on the + * allow list, and rejects anything else that carries no usable JWT. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("JwtUserIdValidationFilter Test Suite") +class JwtUserIdValidationFilterTest { + + private static final String ALLOWED_ORIGINS = "https://amrit.example.org,http://localhost:*"; + private static final String TOKEN = "a.jwt.token"; + + @Mock + private JwtAuthenticationUtil jwtAuthenticationUtil; + + private RecordingChain chain; + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private JwtUserIdValidationFilter filter; + + /** Remembers what the filter handed on, so the wrapping can be inspected. */ + private static final class RecordingChain implements FilterChain { + private ServletRequest passedRequest; + private int invocations; + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) { + this.passedRequest = servletRequest; + this.invocations++; + } + } + + @BeforeEach + void setUp() { + chain = new RecordingChain(); + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + request.setMethod("POST"); + filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, ALLOWED_ORIGINS); + } + + @Nested + @DisplayName("Monitoring endpoints") + class MonitoringEndpointTests { + + @Test + @DisplayName("doFilter should let the health endpoint through without looking at a token") + void doFilter_shouldPassHealthEndpointThrough() throws Exception { + request.setMethod("GET"); + request.setRequestURI("/health"); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + assertSameRequestPassedOn(); + verifyNoInteractions(jwtAuthenticationUtil); + } + + @Test + @DisplayName("doFilter should let the version endpoint through without looking at a token") + void doFilter_shouldPassVersionEndpointThrough() throws Exception { + request.setMethod("GET"); + request.setRequestURI("/version"); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + verifyNoInteractions(jwtAuthenticationUtil); + } + + @Test + @DisplayName("doFilter should let the monitoring endpoints through even from an unlisted origin") + void doFilter_shouldPassHealthEndpointThroughForAnyOrigin() throws Exception { + request.setMethod("GET"); + request.setRequestURI("/health"); + request.addHeader("Origin", "https://attacker.example.net"); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + } + } + + @Nested + @DisplayName("Origin validation") + class OriginValidationTests { + + @Test + @DisplayName("doFilter should turn away a request from an origin that is not on the allow list") + void doFilter_shouldTurnAwayUnlistedOrigin() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Origin", "https://attacker.example.net"); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + assertEquals(0, chain.invocations); + } + + @Test + @DisplayName("doFilter should carry on for a request from an origin on the allow list") + void doFilter_shouldCarryOnForAllowedOrigin() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Origin", "https://amrit.example.org"); + request.setCookies(new Cookie(Constants.JWT_TOKEN, TOKEN)); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)).thenReturn(true); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + } + + @Test + @DisplayName("doFilter should accept a wildcard localhost origin from the allow list") + void doFilter_shouldAcceptWildcardLocalhostOrigin() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Origin", "http://localhost:4200"); + request.setCookies(new Cookie(Constants.JWT_TOKEN, TOKEN)); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)).thenReturn(true); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + } + + @Test + @DisplayName("doFilter should turn away every origin when no allow list is configured") + void doFilter_shouldTurnAwayEveryOriginWithoutAnAllowList() throws Exception { + filter = new JwtUserIdValidationFilter(jwtAuthenticationUtil, " "); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Origin", "https://amrit.example.org"); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + + @Test + @DisplayName("doFilter should turn away a preflight that names no origin") + void doFilter_shouldTurnAwayPreflightWithoutOrigin() throws Exception { + request.setMethod("OPTIONS"); + request.setRequestURI("/zonemaster/get/zones"); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + assertEquals(0, chain.invocations); + } + + @Test + @DisplayName("doFilter should turn away a preflight from an unlisted origin") + void doFilter_shouldTurnAwayPreflightFromUnlistedOrigin() throws Exception { + request.setMethod("OPTIONS"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Origin", "https://attacker.example.net"); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); + } + } + + @Nested + @DisplayName("Public endpoints") + class PublicEndpointTests { + + @Test + @DisplayName("doFilter should let the login endpoint through untouched") + void doFilter_shouldPassLoginThrough() throws Exception { + request.setRequestURI("/user/userAuthenticate"); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + assertSameRequestPassedOn(); + verifyNoInteractions(jwtAuthenticationUtil); + } + + @Test + @DisplayName("doFilter should let the concurrent-session logout through untouched") + void doFilter_shouldPassConcurrentLogoutThrough() throws Exception { + request.setRequestURI("/user/logOutUserFromConcurrentSession"); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + } + + @Test + @DisplayName("doFilter should let the swagger UI and its api-docs through untouched") + void doFilter_shouldPassSwaggerThrough() throws Exception { + for (String uri : Arrays.asList("/swagger-ui/index.html", "/v3/api-docs", "/public/anything", + "/user/refreshToken")) { + RecordingChain publicChain = new RecordingChain(); + MockHttpServletRequest publicRequest = new MockHttpServletRequest(); + publicRequest.setMethod("GET"); + publicRequest.setRequestURI(uri); + + filter.doFilter(publicRequest, new MockHttpServletResponse(), publicChain); + + assertEquals(1, publicChain.invocations, uri + " must be let through untouched"); + } + verifyNoInteractions(jwtAuthenticationUtil); + } + } + + @Nested + @DisplayName("Token validation") + class TokenValidationTests { + + @Test + @DisplayName("doFilter should carry on with a blanked Authorization header for a valid cookie token") + void doFilter_shouldCarryOnForValidCookieToken() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.setCookies(new Cookie(Constants.JWT_TOKEN, TOKEN)); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)).thenReturn(true); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + assertTrue(chain.passedRequest instanceof AuthorizationHeaderRequestWrapper, + "a validated request must reach the controllers with its Authorization header replaced"); + } + + @Test + @DisplayName("doFilter should carry on for a valid token supplied in the Jwttoken header") + void doFilter_shouldCarryOnForValidHeaderToken() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader(Constants.JWT_TOKEN, TOKEN); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)).thenReturn(true); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + assertTrue(chain.passedRequest instanceof AuthorizationHeaderRequestWrapper); + } + + @Test + @DisplayName("doFilter should reject a cookie token the validator does not accept") + void doFilter_shouldRejectUnacceptedCookieToken() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.setCookies(new Cookie(Constants.JWT_TOKEN, TOKEN)); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)).thenReturn(false); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); + assertEquals(0, chain.invocations); + } + + @Test + @DisplayName("doFilter should reject a request that carries no token at all") + void doFilter_shouldRejectRequestWithoutToken() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); + verify(jwtAuthenticationUtil, never()).validateUserIdAndJwtToken(anyString()); + } + + @Test + @DisplayName("doFilter should reject rather than propagate a failure raised while validating") + void doFilter_shouldRejectWhenValidationRaises() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.setCookies(new Cookie(Constants.JWT_TOKEN, TOKEN)); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)) + .thenThrow(new IEMRException("Validation error: Invalid User ID.")); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); + assertEquals(0, chain.invocations); + } + } + + @Nested + @DisplayName("Mobile clients") + class MobileClientTests { + + @Test + @DisplayName("doFilter should carry a mobile call through on its Authorization header alone") + void doFilter_shouldCarryMobileCallThrough() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader(Constants.USER_AGENT, "okhttp/4.9.3"); + request.addHeader("Authorization", "Bearer session-key"); + + filter.doFilter(request, response, chain); + + assertEquals(1, chain.invocations); + assertSameRequestPassedOn(); + assertNull(UserAgentContext.getUserAgent(), "the user agent must not outlive the request"); + } + + @Test + @DisplayName("doFilter should reject a mobile call that carries no Authorization header") + void doFilter_shouldRejectMobileCallWithoutAuthorization() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader(Constants.USER_AGENT, "okhttp/4.9.3"); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); + } + + @Test + @DisplayName("doFilter should reject a browser call that carries only an Authorization header") + void doFilter_shouldRejectBrowserCallWithOnlyAuthorization() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader(Constants.USER_AGENT, "Mozilla/5.0"); + request.addHeader("Authorization", "Bearer session-key"); + + filter.doFilter(request, response, chain); + + assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); + } + } + + @Nested + @DisplayName("userId cookie") + class UserIdCookieTests { + + @Test + @DisplayName("doFilter should expire a userId cookie a caller tries to smuggle in") + void doFilter_shouldExpireUserIdCookie() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.setCookies(new Cookie("userId", "3117"), new Cookie(Constants.JWT_TOKEN, TOKEN)); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)).thenReturn(true); + + filter.doFilter(request, response, chain); + + Cookie cleared = response.getCookie("userId"); + assertNotNull(cleared, "the smuggled cookie must be sent back expired"); + assertEquals(0, cleared.getMaxAge()); + assertNull(cleared.getValue()); + assertTrue(cleared.isHttpOnly()); + assertTrue(cleared.getSecure()); + } + + @Test + @DisplayName("doFilter should leave the response cookies alone when the request carries none") + void doFilter_shouldLeaveCookiesAloneWhenNoneArePresent() throws Exception { + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader(Constants.JWT_TOKEN, TOKEN); + when(jwtAuthenticationUtil.validateUserIdAndJwtToken(TOKEN)).thenReturn(true); + + filter.doFilter(request, response, chain); + + assertNull(response.getCookie("userId")); + } + } + + private void assertSameRequestPassedOn() { + assertTrue(chain.passedRequest == request, "the original request must be handed on unwrapped"); + } +} diff --git a/src/test/java/com/iemr/admin/utils/JwtUtilTest.java b/src/test/java/com/iemr/admin/utils/JwtUtilTest.java new file mode 100644 index 0000000..145153a --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/JwtUtilTest.java @@ -0,0 +1,198 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +import java.util.Date; + +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; + +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.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("JwtUtil Test Suite") +class JwtUtilTest { + + private static final String SECRET = "amrit-tm-test-secret-key-that-is-long-enough-for-hs256"; + private static final String OTHER_SECRET = "a-completely-different-secret-key-also-long-enough-for-hs256"; + + @Mock + private TokenDenylist tokenDenylist; + + private JwtUtil jwtUtil; + + @BeforeEach + @DisplayName("Wire the util with a test secret and a mocked denylist before each test") + void setUp() { + jwtUtil = new JwtUtil(); + ReflectionTestUtils.setField(jwtUtil, "SECRET_KEY", SECRET); + ReflectionTestUtils.setField(jwtUtil, "tokenDenylist", tokenDenylist); + } + + private String token(String secret, String subject, String jti, Date expiry) { + SecretKey key = Keys.hmacShaKeyFor(secret.getBytes()); + var builder = Jwts.builder().subject(subject).signWith(key); + if (jti != null) { + builder.id(jti); + } + if (expiry != null) { + builder.expiration(expiry); + } + return builder.compact(); + } + + private String validToken(String subject, String jti) { + return token(SECRET, subject, jti, new Date(System.currentTimeMillis() + 600_000)); + } + + @Nested + @DisplayName("validateToken") + class ValidateTokenTests { + + @Test + @DisplayName("validateToken should return the claims for a correctly signed token") + void validateToken_shouldReturnClaimsForValidToken() { + when(tokenDenylist.isTokenDenylisted("jti-1")).thenReturn(false); + + Claims claims = jwtUtil.validateToken(validToken("amrit-user", "jti-1")); + + assertNotNull(claims); + assertEquals("amrit-user", claims.getSubject()); + assertEquals("jti-1", claims.getId()); + } + + @Test + @DisplayName("validateToken should skip the denylist check for a token without a jti") + void validateToken_shouldSkipDenylistCheckWithoutJti() { + Claims claims = jwtUtil.validateToken(validToken("amrit-user", null)); + + assertNotNull(claims); + assertEquals("amrit-user", claims.getSubject()); + } + + @Test + @DisplayName("validateToken should reject a token whose jti has been denylisted") + void validateToken_shouldRejectDenylistedToken() { + when(tokenDenylist.isTokenDenylisted("jti-1")).thenReturn(true); + + assertNull(jwtUtil.validateToken(validToken("amrit-user", "jti-1"))); + } + + @Test + @DisplayName("validateToken should reject a token signed with a different secret") + void validateToken_shouldRejectTokenSignedWithDifferentSecret() { + String foreign = token(OTHER_SECRET, "amrit-user", "jti-1", + new Date(System.currentTimeMillis() + 600_000)); + + assertNull(jwtUtil.validateToken(foreign)); + } + + @Test + @DisplayName("validateToken should reject an expired token") + void validateToken_shouldRejectExpiredToken() { + String expired = token(SECRET, "amrit-user", "jti-1", + new Date(System.currentTimeMillis() - 60_000)); + + assertNull(jwtUtil.validateToken(expired)); + } + + @Test + @DisplayName("validateToken should reject a malformed token") + void validateToken_shouldRejectMalformedToken() { + assertNull(jwtUtil.validateToken("not-a-jwt")); + } + + @Test + @DisplayName("validateToken should reject a null token") + void validateToken_shouldRejectNullToken() { + assertNull(jwtUtil.validateToken(null)); + } + + @Test + @DisplayName("validateToken should reject every token when no secret is configured") + void validateToken_shouldRejectWhenSecretIsNotConfigured() { + String signed = validToken("amrit-user", null); + ReflectionTestUtils.setField(jwtUtil, "SECRET_KEY", null); + + assertNull(jwtUtil.validateToken(signed)); + } + } + + @Nested + @DisplayName("Claim extraction") + class ClaimExtractionTests { + + @Test + @DisplayName("extractUsername should return the token subject") + void extractUsername_shouldReturnSubject() { + assertEquals("amrit-user", jwtUtil.extractUsername(validToken("amrit-user", null))); + } + + @Test + @DisplayName("extractClaim should apply the supplied resolver to the claims") + void extractClaim_shouldApplySuppliedResolver() { + lenient().when(tokenDenylist.isTokenDenylisted("jti-9")).thenReturn(false); + + assertEquals("jti-9", jwtUtil.extractClaim(validToken("amrit-user", "jti-9"), Claims::getId)); + } + + @Test + @DisplayName("extractClaim should raise when the token cannot be parsed") + void extractClaim_shouldRaiseForMalformedToken() { + assertThrows(Exception.class, () -> jwtUtil.extractClaim("not-a-jwt", Claims::getSubject)); + } + + @Test + @DisplayName("extractUsername should raise when no secret is configured") + void extractUsername_shouldRaiseWhenSecretIsNotConfigured() { + String signed = validToken("amrit-user", null); + ReflectionTestUtils.setField(jwtUtil, "SECRET_KEY", ""); + + assertThrows(IllegalStateException.class, () -> jwtUtil.extractUsername(signed)); + } + } + +} diff --git a/src/test/java/com/iemr/admin/utils/RestTemplateUtilTest.java b/src/test/java/com/iemr/admin/utils/RestTemplateUtilTest.java new file mode 100644 index 0000000..334922a --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/RestTemplateUtilTest.java @@ -0,0 +1,219 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +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.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.Cookie; + +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.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DisplayName("RestTemplateUtil Test Suite") +class RestTemplateUtilTest { + + private static final String AUTHORIZATION = "session-key-123"; + private static final String BODY = "{\"benCount\":5}"; + private static final String JSON_UTF8 = "application/json;charset=utf-8"; + + private MockHttpServletRequest request; + + @BeforeEach + @DisplayName("Bind a fresh mock request to the request context before each test") + void setUp() { + request = new MockHttpServletRequest(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + } + + @AfterEach + @DisplayName("Clear the request context and User-Agent thread local after each test") + void tearDown() { + RequestContextHolder.resetRequestAttributes(); + UserAgentContext.clear(); + } + + @Nested + @DisplayName("Outside a web request") + class NoRequestContextTests { + + @Test + @DisplayName("createRequestEntity should build a minimal entity when no request is bound") + void createRequestEntity_shouldBuildMinimalEntityWithoutRequestContext() { + RequestContextHolder.resetRequestAttributes(); + + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertSame(BODY, entity.getBody()); + assertEquals(JSON_UTF8, entity.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + assertEquals(AUTHORIZATION, entity.getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + assertFalse(entity.getHeaders().containsKey("JwtToken")); + assertFalse(entity.getHeaders().containsKey(HttpHeaders.COOKIE)); + } + } + + @Nested + @DisplayName("Inside a web request") + class WithRequestContextTests { + + @Test + @DisplayName("createRequestEntity should carry the content type and authorization from the caller") + void createRequestEntity_shouldCarryContentTypeAndAuthorization() { + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertSame(BODY, entity.getBody()); + assertEquals(JSON_UTF8, entity.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + assertEquals(AUTHORIZATION, entity.getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + } + + @Test + @DisplayName("createRequestEntity should forward the inbound JwtToken header") + void createRequestEntity_shouldForwardInboundJwtTokenHeader() { + request.addHeader(Constants.JWT_TOKEN, "header-token"); + + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertEquals("header-token", entity.getHeaders().getFirst(Constants.JWT_TOKEN)); + } + + @Test + @DisplayName("createRequestEntity should replay the Jwttoken cookie as a Cookie header") + void createRequestEntity_shouldReplayJwtTokenCookie() { + request.setCookies(new Cookie("Jwttoken", "cookie-token")); + + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertEquals("Jwttoken=cookie-token", entity.getHeaders().getFirst(HttpHeaders.COOKIE)); + } + + @Test + @DisplayName("createRequestEntity should omit the Cookie header when no Jwttoken cookie is present") + void createRequestEntity_shouldOmitCookieHeaderWithoutJwtTokenCookie() { + request.setCookies(new Cookie("theme", "dark")); + + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertNull(entity.getHeaders().getFirst(HttpHeaders.COOKIE)); + } + + @Test + @DisplayName("createRequestEntity should propagate the mobile User-Agent when one is in scope") + void createRequestEntity_shouldPropagateMobileUserAgent() { + UserAgentContext.setUserAgent("okhttp/4.9.0"); + + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertEquals("okhttp/4.9.0", entity.getHeaders().getFirst(HttpHeaders.USER_AGENT)); + } + + @Test + @DisplayName("createRequestEntity should omit the User-Agent header when none is in scope") + void createRequestEntity_shouldOmitUserAgentWhenNoneInScope() { + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertNull(entity.getHeaders().getFirst(HttpHeaders.USER_AGENT)); + } + + @Test + @DisplayName("createRequestEntity should carry both the cookie and header tokens together") + void createRequestEntity_shouldCarryBothCookieAndHeaderTokens() { + request.addHeader(Constants.JWT_TOKEN, "header-token"); + request.setCookies(new Cookie("Jwttoken", "cookie-token")); + + HttpEntity entity = RestTemplateUtil.createRequestEntity(BODY, AUTHORIZATION); + + assertEquals("header-token", entity.getHeaders().getFirst(Constants.JWT_TOKEN)); + assertEquals("Jwttoken=cookie-token", entity.getHeaders().getFirst(HttpHeaders.COOKIE)); + } + } + + @Nested + @DisplayName("getJwttokenFromHeaders") + class GetJwttokenFromHeadersTests { + + @Test + @DisplayName("should replay the caller's token cookie onto the outbound headers") + void getJwttokenFromHeaders_shouldReplayTokenCookie() { + MockHttpServletRequest inbound = new MockHttpServletRequest(); + inbound.setCookies(new Cookie(Constants.JWT_TOKEN, "cookie-token")); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(inbound)); + HttpHeaders headers = new HttpHeaders(); + + RestTemplateUtil.getJwttokenFromHeaders(headers); + + assertEquals("Jwttoken=cookie-token", headers.getFirst(HttpHeaders.COOKIE)); + } + + @Test + @DisplayName("should fall back to the caller's token header when no cookie is present") + void getJwttokenFromHeaders_shouldFallBackToTokenHeader() { + MockHttpServletRequest inbound = new MockHttpServletRequest(); + inbound.addHeader(Constants.JWT_TOKEN, "header-token"); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(inbound)); + HttpHeaders headers = new HttpHeaders(); + + RestTemplateUtil.getJwttokenFromHeaders(headers); + + assertEquals("header-token", headers.getFirst(Constants.JWT_TOKEN)); + assertNull(headers.getFirst(HttpHeaders.COOKIE)); + } + + @Test + @DisplayName("should carry the mobile user agent when one is in scope") + void getJwttokenFromHeaders_shouldCarryUserAgent() { + MockHttpServletRequest inbound = new MockHttpServletRequest(); + inbound.setCookies(new Cookie(Constants.JWT_TOKEN, "cookie-token")); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(inbound)); + UserAgentContext.setUserAgent("okhttp/4.9.3"); + HttpHeaders headers = new HttpHeaders(); + try { + RestTemplateUtil.getJwttokenFromHeaders(headers); + } finally { + UserAgentContext.clear(); + } + + assertEquals("okhttp/4.9.3", headers.getFirst(HttpHeaders.USER_AGENT)); + } + + @Test + @DisplayName("should leave the headers untouched when no request is in scope") + void getJwttokenFromHeaders_shouldLeaveHeadersUntouchedWithoutRequest() { + RequestContextHolder.resetRequestAttributes(); + HttpHeaders headers = new HttpHeaders(); + + RestTemplateUtil.getJwttokenFromHeaders(headers); + + assertTrue(headers.isEmpty()); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/TokenDenylistTest.java b/src/test/java/com/iemr/admin/utils/TokenDenylistTest.java new file mode 100644 index 0000000..f7366ac --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/TokenDenylistTest.java @@ -0,0 +1,175 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +import java.util.concurrent.TimeUnit; + +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +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.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +/** + * The denylist keeps the identifiers of tokens that have been signed out, so a + * stolen but otherwise valid token stops being accepted. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("TokenDenylist Test Suite") +class TokenDenylistTest { + + private static final String JTI = "b0f1c2d3-4e5f-6789-abcd-ef0123456789"; + private static final String KEY = "denied_" + JTI; + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private ValueOperations valueOperations; + + private TokenDenylist tokenDenylist; + + @BeforeEach + void setUp() { + tokenDenylist = new TokenDenylist(); + ReflectionTestUtils.setField(tokenDenylist, "redisTemplate", redisTemplate); + when(redisTemplate.opsForValue()).thenReturn(valueOperations); + } + + @Nested + @DisplayName("addTokenToDenylist") + class AddTokenTests { + + @Test + @DisplayName("should store the prefixed key for the lifetime the caller asks for") + void addTokenToDenylist_shouldStorePrefixedKeyWithExpiry() { + tokenDenylist.addTokenToDenylist(JTI, 600_000L); + + verify(valueOperations).set(KEY, " ", 600_000L, TimeUnit.MILLISECONDS); + } + + @Test + @DisplayName("should ignore a null token id rather than write an orphan key") + void addTokenToDenylist_shouldIgnoreNullId() { + tokenDenylist.addTokenToDenylist(null, 600_000L); + + verifyNoInteractions(valueOperations); + } + + @Test + @DisplayName("should ignore a blank token id") + void addTokenToDenylist_shouldIgnoreBlankId() { + tokenDenylist.addTokenToDenylist(" ", 600_000L); + + verifyNoInteractions(valueOperations); + } + + @Test + @DisplayName("should refuse a null expiry rather than denylist a token forever") + void addTokenToDenylist_shouldRefuseNullExpiry() { + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, + () -> tokenDenylist.addTokenToDenylist(JTI, null)); + + assertEquals("Expiration time must be positive", thrown.getMessage()); + verify(valueOperations, never()).set(anyString(), anyString()); + } + + @Test + @DisplayName("should refuse an expiry that has already passed") + void addTokenToDenylist_shouldRefuseNonPositiveExpiry() { + assertThrows(IllegalArgumentException.class, () -> tokenDenylist.addTokenToDenylist(JTI, 0L)); + assertThrows(IllegalArgumentException.class, () -> tokenDenylist.addTokenToDenylist(JTI, -1L)); + } + + @Test + @DisplayName("should surface a store failure rather than report a sign-out that did not happen") + void addTokenToDenylist_shouldSurfaceStoreFailure() { + doThrow(new IllegalStateException("redis is down")) + .when(valueOperations).set(KEY, " ", 600_000L, TimeUnit.MILLISECONDS); + + RuntimeException thrown = assertThrows(RuntimeException.class, + () -> tokenDenylist.addTokenToDenylist(JTI, 600_000L)); + + assertEquals("Failed to denylist token", thrown.getMessage()); + } + } + + @Nested + @DisplayName("isTokenDenylisted") + class IsTokenDenylistedTests { + + @Test + @DisplayName("should report a token whose key the store still holds") + void isTokenDenylisted_shouldReportDenylistedToken() { + when(redisTemplate.hasKey(KEY)).thenReturn(Boolean.TRUE); + + assertTrue(tokenDenylist.isTokenDenylisted(JTI)); + } + + @Test + @DisplayName("should clear a token whose key the store no longer holds") + void isTokenDenylisted_shouldClearUnknownToken() { + when(redisTemplate.hasKey(KEY)).thenReturn(Boolean.FALSE); + + assertFalse(tokenDenylist.isTokenDenylisted(JTI)); + } + + @Test + @DisplayName("should treat a null token id as not denylisted") + void isTokenDenylisted_shouldTreatNullIdAsAllowed() { + assertFalse(tokenDenylist.isTokenDenylisted(null)); + } + + @Test + @DisplayName("should treat a blank token id as not denylisted") + void isTokenDenylisted_shouldTreatBlankIdAsAllowed() { + assertFalse(tokenDenylist.isTokenDenylisted(" ")); + } + + @Test + @DisplayName("should let requests through rather than block everyone when the store fails") + void isTokenDenylisted_shouldAllowWhenStoreFails() { + when(redisTemplate.hasKey(KEY)).thenThrow(new IllegalStateException("redis is down")); + + assertFalse(tokenDenylist.isTokenDenylisted(JTI)); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/UserAgentContextTest.java b/src/test/java/com/iemr/admin/utils/UserAgentContextTest.java new file mode 100644 index 0000000..de95140 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/UserAgentContextTest.java @@ -0,0 +1,88 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils; + +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@DisplayName("UserAgentContext Test Suite") +class UserAgentContextTest { + + @AfterEach + @DisplayName("Clear the thread local after each test") + void tearDown() { + UserAgentContext.clear(); + } + + @Test + @DisplayName("getUserAgent should be empty before anything is set") + void getUserAgent_shouldBeEmptyByDefault() { + assertNull(UserAgentContext.getUserAgent()); + } + + @Test + @DisplayName("setUserAgent should make the value readable on the same thread") + void setUserAgent_shouldBeReadableOnSameThread() { + UserAgentContext.setUserAgent("okhttp/4.9.0"); + + assertEquals("okhttp/4.9.0", UserAgentContext.getUserAgent()); + } + + @Test + @DisplayName("setUserAgent should overwrite a previously stored value") + void setUserAgent_shouldOverwritePreviousValue() { + UserAgentContext.setUserAgent("okhttp/4.9.0"); + UserAgentContext.setUserAgent("Java/17.0.2"); + + assertEquals("Java/17.0.2", UserAgentContext.getUserAgent()); + } + + @Test + @DisplayName("clear should remove the stored value") + void clear_shouldRemoveStoredValue() { + UserAgentContext.setUserAgent("okhttp/4.9.0"); + + UserAgentContext.clear(); + + assertNull(UserAgentContext.getUserAgent()); + } + + @Test + @DisplayName("the stored value should not leak into another thread") + void storedValue_shouldNotLeakAcrossThreads() throws Exception { + UserAgentContext.setUserAgent("okhttp/4.9.0"); + ExecutorService executor = Executors.newSingleThreadExecutor(); + + Future otherThreadValue = executor.submit(UserAgentContext::getUserAgent); + + assertNull(otherThreadValue.get(), "the User-Agent is per-request, so must stay thread-confined"); + executor.shutdown(); + } +} diff --git a/src/test/java/com/iemr/admin/utils/config/ConfigPropertiesTest.java b/src/test/java/com/iemr/admin/utils/config/ConfigPropertiesTest.java new file mode 100644 index 0000000..512fcf0 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/config/ConfigPropertiesTest.java @@ -0,0 +1,169 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.config; + +import java.util.Base64; +import java.util.Properties; + +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.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DisplayName("ConfigProperties Test Suite") +class ConfigPropertiesTest { + + private Properties originalProperties; + + @BeforeEach + @DisplayName("Instantiate the holder so application.properties is loaded, keeping the original statics") + void setUp() { + new ConfigProperties(); + originalProperties = (Properties) ReflectionTestUtils.getField(ConfigProperties.class, "properties"); + } + + @AfterEach + @DisplayName("Restore the shared static properties after each test") + void tearDown() { + ReflectionTestUtils.setField(ConfigProperties.class, "properties", originalProperties); + } + + @Nested + @DisplayName("Reading values from application.properties") + class PropertyLookupTests { + + @Test + @DisplayName("getPropertyByName should return the configured value for a known key") + void getPropertyByName_shouldReturnConfiguredValue() { + assertEquals("6379", ConfigProperties.getPropertyByName("spring.redis.port")); + } + + @Test + @DisplayName("getPropertyByName should return null for a key that is not configured") + void getPropertyByName_shouldReturnNullForUnknownKey() { + assertNull(ConfigProperties.getPropertyByName("no.such.key.configured")); + } + + @Test + @DisplayName("getBoolean should parse a boolean property") + void getBoolean_shouldParseBooleanProperty() { + assertTrue(ConfigProperties.getBoolean("iemr.extend.expiry.time")); + } + + @Test + @DisplayName("getBoolean should return false for a value that is not a boolean") + void getBoolean_shouldReturnFalseForNonBooleanValue() { + assertEquals(false, ConfigProperties.getBoolean("spring.redis.port")); + } + + @Test + @DisplayName("getInteger should parse an integer property") + void getInteger_shouldParseIntegerProperty() { + assertEquals(7200, ConfigProperties.getInteger("iemr.session.expiry.time")); + } + + @Test + @DisplayName("getInteger should fall back to zero when the value is not a number") + void getInteger_shouldFallBackToZeroForNonNumericValue() { + assertEquals(0, ConfigProperties.getInteger("spring.session.store-type")); + } + + @Test + @DisplayName("getLong should parse a long property") + void getLong_shouldParseLongProperty() { + assertEquals(7200L, ConfigProperties.getLong("iemr.session.expiry.time")); + } + + @Test + @DisplayName("getLong should fall back to zero when the value is not a number") + void getLong_shouldFallBackToZeroForNonNumericValue() { + assertEquals(0L, ConfigProperties.getLong("spring.session.store-type")); + } + + @Test + @DisplayName("getFloat should parse a numeric property") + void getFloat_shouldParseNumericProperty() { + assertEquals(6379F, ConfigProperties.getFloat("spring.redis.port")); + } + + @Test + @DisplayName("getFloat should fall back to zero when the value is not a number") + void getFloat_shouldFallBackToZeroForNonNumericValue() { + assertEquals(0F, ConfigProperties.getFloat("spring.session.store-type")); + } + } + + @Nested + @DisplayName("Session and Redis accessors") + class AccessorTests { + + @Test + @DisplayName("getSessionExpiryTime should resolve the configured session expiry") + void getSessionExpiryTime_shouldResolveConfiguredExpiry() { + assertEquals(7200, ConfigProperties.getSessionExpiryTime()); + } + + @Test + @DisplayName("getRedisPort should fall back to zero when no iemr.redis.port is configured") + void getRedisPort_shouldFallBackToZeroWhenUnconfigured() { + assertEquals(0, ConfigProperties.getRedisPort()); + } + + @Test + @DisplayName("getRedisUrl should return null when no iemr.redis.url is configured") + void getRedisUrl_shouldReturnNullWhenUnconfigured() { + assertNull(ConfigProperties.getRedisUrl()); + } + } + + @Nested + @DisplayName("Password handling") + class PasswordTests { + + @Test + @DisplayName("getPassword should return a plain-text password unchanged") + void getPassword_shouldReturnPlainTextUnchanged() { + Properties stub = new Properties(); + stub.setProperty("db.password", "plainSecret"); + ReflectionTestUtils.setField(ConfigProperties.class, "properties", stub); + + assertEquals("plainSecret", ConfigProperties.getPassword("db.password")); + } + + @Test + @DisplayName("getPassword should Base64-decode a password tagged with the 0X10 prefix") + void getPassword_shouldBase64DecodeTaggedPassword() { + String encoded = Base64.getEncoder().encodeToString("s3cr3t".getBytes()); + Properties stub = new Properties(); + stub.setProperty("db.password", "0X10:" + encoded); + ReflectionTestUtils.setField(ConfigProperties.class, "properties", stub); + + assertEquals("s3cr3t", ConfigProperties.getPassword("db.password")); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/gateway/email/GenericEmailServiceImplTest.java b/src/test/java/com/iemr/admin/utils/gateway/email/GenericEmailServiceImplTest.java new file mode 100644 index 0000000..5a9b243 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/gateway/email/GenericEmailServiceImplTest.java @@ -0,0 +1,156 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.gateway.email; + +import org.json.JSONException; +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.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.MailSendException; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; + +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.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +@DisplayName("GenericEmailServiceImpl Test Suite") +class GenericEmailServiceImplTest { + + @Mock + private JavaMailSender javaMailSender; + + private GenericEmailServiceImpl emailService; + + @BeforeEach + @DisplayName("Wire the service with a mocked mail sender before each test") + void setUp() { + emailService = new GenericEmailServiceImpl(); + emailService.setJavaMailSender(javaMailSender); + } + + private String request(String to) { + return String.format( + "{\"to\":\"%s\",\"from\":\"no-reply@amrit.example.org\"," + + "\"subject\":\"Specialist availability alert\"," + + "\"message\":\"Specialist availability was updated.\"}", + to); + } + + private SimpleMailMessage captureSentMessage() { + ArgumentCaptor captor = ArgumentCaptor.forClass(SimpleMailMessage.class); + verify(javaMailSender).send(captor.capture()); + return captor.getValue(); + } + + @Nested + @DisplayName("sendEmail without a template") + class SendEmailTests { + + @Test + @DisplayName("sendEmail should populate every field of the message from the JSON request") + void sendEmail_shouldPopulateMessageFromJsonRequest() throws Exception { + emailService.sendEmail(request("ops@amrit.example.org")); + + SimpleMailMessage sent = captureSentMessage(); + assertArrayEquals(new String[] { "ops@amrit.example.org" }, sent.getTo()); + assertEquals("no-reply@amrit.example.org", sent.getFrom()); + assertEquals("Specialist availability alert", sent.getSubject()); + assertEquals("Specialist availability was updated.", sent.getText()); + } + + @Test + @DisplayName("sendEmail should split a semicolon-separated recipient list into multiple addresses") + void sendEmail_shouldSplitSemicolonSeparatedRecipients() throws Exception { + emailService.sendEmail(request("ops@amrit.example.org;admin@amrit.example.org")); + + assertArrayEquals(new String[] { "ops@amrit.example.org", "admin@amrit.example.org" }, + captureSentMessage().getTo()); + } + + @Test + @DisplayName("sendEmail should reject a request that is missing a mandatory field") + void sendEmail_shouldRejectRequestMissingMandatoryField() throws Exception { + String incomplete = "{\"to\":\"ops@amrit.example.org\"}"; + + assertThrows(JSONException.class, () -> emailService.sendEmail(incomplete)); + verify(javaMailSender, never()).send(org.mockito.ArgumentMatchers.any(SimpleMailMessage.class)); + } + + @Test + @DisplayName("sendEmail should propagate a mail transport failure") + void sendEmail_shouldPropagateTransportFailure() throws Exception { + doThrow(new MailSendException("smtp unreachable")) + .when(javaMailSender).send(org.mockito.ArgumentMatchers.any(SimpleMailMessage.class)); + + assertThrows(MailSendException.class, () -> emailService.sendEmail(request("ops@amrit.example.org"))); + } + } + + @Nested + @DisplayName("sendEmail with a template") + class SendEmailWithTemplateTests { + + @Test + @DisplayName("sendEmail with a template should populate the message from the JSON request") + void sendEmail_withTemplate_shouldPopulateMessageFromJsonRequest() throws Exception { + emailService.sendEmail(request("ops@amrit.example.org"), "availability-alert-template"); + + SimpleMailMessage sent = captureSentMessage(); + assertArrayEquals(new String[] { "ops@amrit.example.org" }, sent.getTo()); + assertEquals("Specialist availability alert", sent.getSubject()); + assertEquals("Specialist availability was updated.", sent.getText()); + } + + @Test + @DisplayName("sendEmail with a template should keep a semicolon list as a single recipient") + void sendEmail_withTemplate_shouldKeepRecipientListUnsplit() throws Exception { + emailService.sendEmail(request("ops@amrit.example.org;admin@amrit.example.org"), + "availability-alert-template"); + + assertArrayEquals(new String[] { "ops@amrit.example.org;admin@amrit.example.org" }, + captureSentMessage().getTo()); + } + } + + @Nested + @DisplayName("sendEmailWithAttachment") + class SendEmailWithAttachmentTests { + + @Test + @DisplayName("sendEmailWithAttachment is not implemented and should send nothing") + void sendEmailWithAttachment_shouldSendNothing() throws Exception { + emailService.sendEmailWithAttachment(request("ops@amrit.example.org"), "template"); + + verify(javaMailSender, never()).send(org.mockito.ArgumentMatchers.any(SimpleMailMessage.class)); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/http/AuthorizationHeaderRequestWrapperTest.java b/src/test/java/com/iemr/admin/utils/http/AuthorizationHeaderRequestWrapperTest.java new file mode 100644 index 0000000..e20b7fd --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/http/AuthorizationHeaderRequestWrapperTest.java @@ -0,0 +1,128 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.http; + +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.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DisplayName("AuthorizationHeaderRequestWrapper Test Suite") +class AuthorizationHeaderRequestWrapperTest { + + private MockHttpServletRequest request; + + @BeforeEach + @DisplayName("Create a request carrying an inbound Authorization header before each test") + void setUp() { + request = new MockHttpServletRequest(); + request.addHeader("Authorization", "inbound-key"); + request.addHeader("JwtToken", "header-token"); + } + + @Test + @DisplayName("getHeader should return the overridden value for Authorization") + void getHeader_shouldReturnOverriddenAuthorization() { + AuthorizationHeaderRequestWrapper wrapper = + new AuthorizationHeaderRequestWrapper(request, "overridden-key"); + + assertEquals("overridden-key", wrapper.getHeader("Authorization")); + } + + @Test + @DisplayName("getHeader should match the Authorization name case-insensitively") + void getHeader_shouldMatchAuthorizationCaseInsensitively() { + AuthorizationHeaderRequestWrapper wrapper = + new AuthorizationHeaderRequestWrapper(request, "overridden-key"); + + assertEquals("overridden-key", wrapper.getHeader("authorization")); + assertEquals("overridden-key", wrapper.getHeader("AUTHORIZATION")); + } + + @Test + @DisplayName("getHeader should pass every other header through to the wrapped request") + void getHeader_shouldPassOtherHeadersThrough() { + AuthorizationHeaderRequestWrapper wrapper = + new AuthorizationHeaderRequestWrapper(request, "overridden-key"); + + assertEquals("header-token", wrapper.getHeader("JwtToken")); + assertNull(wrapper.getHeader("X-Not-Present")); + } + + @Test + @DisplayName("getHeader should return the blank override the JWT filter installs") + void getHeader_shouldReturnBlankOverride() { + AuthorizationHeaderRequestWrapper wrapper = new AuthorizationHeaderRequestWrapper(request, ""); + + assertEquals("", wrapper.getHeader("Authorization"), + "the filter blanks Authorization once the JWT has been validated"); + } + + @Test + @DisplayName("getHeaders should return the overridden Authorization as a single-valued enumeration") + void getHeaders_shouldReturnOverriddenAuthorizationAsSingleValue() { + AuthorizationHeaderRequestWrapper wrapper = + new AuthorizationHeaderRequestWrapper(request, "overridden-key"); + + assertEquals(List.of("overridden-key"), Collections.list(wrapper.getHeaders("Authorization"))); + } + + @Test + @DisplayName("getHeaders should pass every other header through to the wrapped request") + void getHeaders_shouldPassOtherHeadersThrough() { + AuthorizationHeaderRequestWrapper wrapper = + new AuthorizationHeaderRequestWrapper(request, "overridden-key"); + + assertEquals(List.of("header-token"), Collections.list(wrapper.getHeaders("JwtToken"))); + } + + @Test + @DisplayName("getHeaderNames should still list Authorization alongside the wrapped names") + void getHeaderNames_shouldListAuthorizationAlongsideWrappedNames() { + AuthorizationHeaderRequestWrapper wrapper = + new AuthorizationHeaderRequestWrapper(request, "overridden-key"); + + List names = Collections.list(wrapper.getHeaderNames()); + assertTrue(names.contains("Authorization")); + assertTrue(names.contains("JwtToken")); + assertEquals(1, names.stream().filter("Authorization"::equals).count(), + "Authorization must not be duplicated when the wrapped request already carries it"); + } + + @Test + @DisplayName("getHeaderNames should add Authorization when the wrapped request lacks it") + void getHeaderNames_shouldAddAuthorizationWhenWrappedRequestLacksIt() { + MockHttpServletRequest bare = new MockHttpServletRequest(); + bare.addHeader("JwtToken", "header-token"); + AuthorizationHeaderRequestWrapper wrapper = + new AuthorizationHeaderRequestWrapper(bare, "overridden-key"); + + assertTrue(Collections.list(wrapper.getHeaderNames()).contains("Authorization")); + } +} diff --git a/src/test/java/com/iemr/admin/utils/http/HTTPRequestInterceptorTest.java b/src/test/java/com/iemr/admin/utils/http/HTTPRequestInterceptorTest.java new file mode 100644 index 0000000..0d00b90 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/http/HTTPRequestInterceptorTest.java @@ -0,0 +1,312 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.http; + +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.ModelAndView; + +import com.iemr.admin.utils.redis.RedisSessionException; +import com.iemr.admin.utils.redis.RedisStorage; +import com.iemr.admin.utils.sessionobject.SessionObject; + +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 static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("HTTPRequestInterceptor Test Suite") +class HTTPRequestInterceptorTest { + + private static final String SESSION_KEY = "0d5f3a7c-2b91-4e6d-8f10-3a4b5c6d7e8f"; + private static final String SESSION_PAYLOAD = "{\"userName\":\"amrit-admin\"}"; + + @Mock + private RedisStorage redisStorage; + + @Mock + private SessionObject sessionObject; + + @InjectMocks + private HTTPRequestInterceptor interceptor; + + private MockHttpServletRequest request; + private MockHttpServletResponse response; + + @BeforeEach + @DisplayName("Prepare a request and response before each test") + void setUp() { + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + } + + @Nested + @DisplayName("preHandle") + class PreHandleTests { + + @Test + @DisplayName("preHandle should admit a request whose session key resolves in Redis") + void preHandle_shouldAdmitRequestWithResolvableSessionKey() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + when(sessionObject.getSessionObject(SESSION_KEY)).thenReturn(SESSION_PAYLOAD); + + assertTrue(interceptor.preHandle(request, response, new Object())); + } + + @Test + @DisplayName("preHandle should strip the Bearer prefix before resolving the session key") + void preHandle_shouldStripBearerPrefix() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", "Bearer " + SESSION_KEY); + when(sessionObject.getSessionObject(SESSION_KEY)).thenReturn(SESSION_PAYLOAD); + + assertTrue(interceptor.preHandle(request, response, new Object())); + verify(sessionObject).getSessionObject(SESSION_KEY); + } + + @Test + @DisplayName("preHandle should reject a request whose session key is unknown to Redis") + void preHandle_shouldRejectUnknownSessionKey() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + when(sessionObject.getSessionObject(SESSION_KEY)).thenReturn(null); + + assertFalse(interceptor.preHandle(request, response, new Object())); + assertTrue(response.getContentAsString().contains("5000"), response.getContentAsString()); + } + + @Test + @DisplayName("preHandle should reject a request whose session lookup fails") + void preHandle_shouldRejectWhenSessionLookupFails() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + when(sessionObject.getSessionObject(SESSION_KEY)) + .thenThrow(new RedisSessionException("Unable to fetch session object from Redis server")); + + assertFalse(interceptor.preHandle(request, response, new Object())); + assertNull(response.getHeader("Access-Control-Allow-Origin"), + "no allow-list is configured, so no origin may be echoed back"); + } + + @Test + @DisplayName("preHandle should echo an allowed origin on the rejection so the browser can read it") + void preHandle_shouldEchoAllowedOriginOnRejection() throws Exception { + ReflectionTestUtils.setField(interceptor, "allowedOrigins", "https://amrit.example.org"); + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + request.addHeader("Origin", "https://amrit.example.org"); + when(sessionObject.getSessionObject(SESSION_KEY)).thenReturn(null); + + assertFalse(interceptor.preHandle(request, response, new Object())); + assertEquals("https://amrit.example.org", response.getHeader("Access-Control-Allow-Origin")); + assertEquals("true", response.getHeader("Access-Control-Allow-Credentials")); + } + + @Test + @DisplayName("preHandle should withhold the CORS headers from a rejection for an unlisted origin") + void preHandle_shouldWithholdCorsHeadersForUnlistedOrigin() throws Exception { + ReflectionTestUtils.setField(interceptor, "allowedOrigins", "https://amrit.example.org"); + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + request.addHeader("Origin", "https://attacker.example.net"); + when(sessionObject.getSessionObject(SESSION_KEY)).thenReturn(null); + + assertFalse(interceptor.preHandle(request, response, new Object())); + assertNull(response.getHeader("Access-Control-Allow-Origin")); + } + + @Test + @DisplayName("preHandle should match a wildcard localhost entry in the allow list") + void preHandle_shouldMatchWildcardLocalhostOrigin() throws Exception { + ReflectionTestUtils.setField(interceptor, "allowedOrigins", "http://localhost:*"); + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + request.addHeader("Origin", "http://localhost:4200"); + when(sessionObject.getSessionObject(SESSION_KEY)).thenReturn(null); + + assertFalse(interceptor.preHandle(request, response, new Object())); + assertEquals("http://localhost:4200", response.getHeader("Access-Control-Allow-Origin")); + } + + @Test + @DisplayName("preHandle should pass a request without an Authorization header on to the security filters") + void preHandle_shouldPassRequestWithoutAuthorizationOn() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + + assertTrue(interceptor.preHandle(request, response, new Object())); + verify(sessionObject, never()).getSessionObject(anyString()); + } + + @Test + @DisplayName("preHandle should pass a request with a blank Authorization header on unchecked") + void preHandle_shouldPassBlankAuthorizationOn() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", ""); + + assertTrue(interceptor.preHandle(request, response, new Object())); + verify(sessionObject, never()).getSessionObject(anyString()); + } + + @Test + @DisplayName("preHandle should admit the unauthenticated health and version endpoints") + void preHandle_shouldAdmitPublicEndpoints() throws Exception { + request.setMethod("GET"); + request.setRequestURI("/health"); + assertTrue(interceptor.preHandle(request, response, new Object())); + + MockHttpServletRequest versionRequest = new MockHttpServletRequest(); + versionRequest.setMethod("GET"); + versionRequest.setRequestURI("/version"); + assertTrue(interceptor.preHandle(versionRequest, new MockHttpServletResponse(), new Object())); + + verify(sessionObject, never()).getSessionObject(anyString()); + } + + @Test + @DisplayName("preHandle should admit the swagger UI without checking a session") + void preHandle_shouldAdmitSwaggerUi() throws Exception { + request.setMethod("GET"); + request.setRequestURI("/swagger-ui/index.html"); + + assertTrue(interceptor.preHandle(request, response, new Object())); + verify(sessionObject, never()).getSessionObject(anyString()); + } + + @Test + @DisplayName("preHandle should admit the swagger support resources on a session key") + void preHandle_shouldAdmitSwaggerSupportResources() throws Exception { + for (String uri : new String[] { "/v3/api-docs", "/swagger-resources", "/swagger-config", + "/ui", "/index.html", "/swagger-initializer.js" }) { + MockHttpServletRequest swaggerRequest = new MockHttpServletRequest(); + swaggerRequest.setMethod("GET"); + swaggerRequest.setRequestURI(uri); + swaggerRequest.addHeader("Authorization", SESSION_KEY); + + assertTrue(interceptor.preHandle(swaggerRequest, new MockHttpServletResponse(), new Object()), + uri + " must be admitted without a session lookup"); + } + verify(sessionObject, never()).getSessionObject(anyString()); + } + + @Test + @DisplayName("preHandle should reject the error endpoint") + void preHandle_shouldRejectTheErrorEndpoint() throws Exception { + request.setMethod("GET"); + request.setRequestURI("/error"); + request.addHeader("Authorization", SESSION_KEY); + + assertFalse(interceptor.preHandle(request, response, new Object())); + } + + @Test + @DisplayName("preHandle should admit an OPTIONS preflight without checking a session") + void preHandle_shouldAdmitOptionsPreflight() throws Exception { + request.setMethod("OPTIONS"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + + assertTrue(interceptor.preHandle(request, response, new Object())); + verify(sessionObject, never()).getSessionObject(anyString()); + } + } + + @Nested + @DisplayName("postHandle") + class PostHandleTests { + + @Test + @DisplayName("postHandle should refresh the session it resolved for the request") + void postHandle_shouldRefreshTheSession() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", "Bearer " + SESSION_KEY); + when(sessionObject.getSessionObject(SESSION_KEY)).thenReturn(SESSION_PAYLOAD); + + interceptor.postHandle(request, response, new Object(), new ModelAndView()); + + verify(sessionObject).updateSessionObject(SESSION_KEY, SESSION_PAYLOAD); + } + + @Test + @DisplayName("postHandle should do nothing when the request carried no Authorization header") + void postHandle_shouldDoNothingWithoutAuthorization() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + + interceptor.postHandle(request, response, new Object(), new ModelAndView()); + + verify(sessionObject, never()).updateSessionObject(anyString(), anyString()); + } + + @Test + @DisplayName("postHandle should swallow a session refresh failure") + void postHandle_shouldSwallowRefreshFailure() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + request.addHeader("Authorization", SESSION_KEY); + when(sessionObject.getSessionObject(SESSION_KEY)) + .thenThrow(new RedisSessionException("Unable to fetch session object from Redis server")); + + interceptor.postHandle(request, response, new Object(), new ModelAndView()); + + verify(sessionObject, never()).updateSessionObject(anyString(), anyString()); + } + } + + @Test + @DisplayName("afterCompletion should complete without touching the session store") + void afterCompletion_shouldCompleteWithoutTouchingTheSessionStore() throws Exception { + request.setMethod("POST"); + request.setRequestURI("/zonemaster/get/zones"); + + interceptor.afterCompletion(request, response, new Object(), null); + + verify(sessionObject, never()).updateSessionObject(anyString(), anyString()); + } +} diff --git a/src/test/java/com/iemr/admin/utils/http/HttpUtilsTest.java b/src/test/java/com/iemr/admin/utils/http/HttpUtilsTest.java new file mode 100644 index 0000000..7ac2353 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/http/HttpUtilsTest.java @@ -0,0 +1,243 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.http; + +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +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.RestClientException; +import org.springframework.web.client.RestTemplate; + +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.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("HttpUtils Test Suite") +class HttpUtilsTest { + + private static final String URI = "http://localhost:8080/api/resource"; + + @Mock + private RestTemplate restTemplate; + + private HttpUtils httpUtils; + + @BeforeEach + @DisplayName("Replace the internal RestTemplate with a mock before each test") + void setUp() { + httpUtils = new HttpUtils(); + ReflectionTestUtils.setField(httpUtils, "rest", restTemplate); + } + + @SuppressWarnings("unchecked") + private ArgumentCaptor> captureRequest(HttpMethod method, ResponseEntity reply) { + ArgumentCaptor> captor = ArgumentCaptor.forClass(HttpEntity.class); + when(restTemplate.exchange(eq(URI), eq(method), captor.capture(), eq(String.class))).thenReturn(reply); + return captor; + } + + @Nested + @DisplayName("GET requests") + class GetTests + + { + @Test + @DisplayName("get should return the response body and record the status") + void get_shouldReturnBodyAndRecordStatus() { + when(restTemplate.exchange(eq(URI), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(new ResponseEntity<>("{\"ok\":true}", HttpStatus.OK)); + + assertEquals("{\"ok\":true}", httpUtils.get(URI)); + assertEquals(HttpStatus.OK, httpUtils.getStatus()); + } + + @Test + @DisplayName("get should send the default JSON content type") + void get_shouldSendDefaultJsonContentType() { + ArgumentCaptor> captor = + captureRequest(HttpMethod.GET, new ResponseEntity<>("body", HttpStatus.OK)); + + httpUtils.get(URI); + + assertEquals("application/json", + captor.getValue().getHeaders().getFirst("Content-Type")); + } + + @Test + @DisplayName("get should record a non-OK status returned by the server") + void get_shouldRecordNonOkStatus() { + when(restTemplate.exchange(eq(URI), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenReturn(new ResponseEntity<>(null, HttpStatus.NOT_FOUND)); + + assertNull(httpUtils.get(URI)); + assertEquals(HttpStatus.NOT_FOUND, httpUtils.getStatus()); + } + + @Test + @DisplayName("get with headers should forward the supplied Authorization header") + void get_shouldForwardSuppliedAuthorizationHeader() { + HashMap header = new HashMap<>(); + header.put(HttpHeaders.AUTHORIZATION, "session-key-123"); + ArgumentCaptor> captor = + captureRequest(HttpMethod.GET, new ResponseEntity<>("body", HttpStatus.OK)); + + assertEquals("body", httpUtils.get(URI, header)); + assertEquals("session-key-123", + captor.getValue().getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + } + + @Test + @DisplayName("get with headers should forward an explicit Content-Type") + void get_shouldForwardExplicitContentType() { + HashMap header = new HashMap<>(); + header.put(HttpHeaders.CONTENT_TYPE, "application/xml"); + ArgumentCaptor> captor = + captureRequest(HttpMethod.GET, new ResponseEntity<>("body", HttpStatus.OK)); + + httpUtils.get(URI, header); + + assertEquals("application/xml", + captor.getValue().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + } + + @Test + @DisplayName("get with headers should default the Content-Type to JSON when none is supplied") + void get_shouldDefaultContentTypeToJson() { + ArgumentCaptor> captor = + captureRequest(HttpMethod.GET, new ResponseEntity<>("body", HttpStatus.OK)); + + httpUtils.get(URI, new HashMap<>()); + + assertEquals("application/json", + captor.getValue().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)); + } + + @Test + @DisplayName("get should propagate a transport failure to the caller") + void get_shouldPropagateTransportFailure() { + when(restTemplate.exchange(eq(URI), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))) + .thenThrow(new RestClientException("connection refused")); + + assertThrows(RestClientException.class, () -> httpUtils.get(URI)); + } + } + + @Nested + @DisplayName("POST requests") + class PostTests { + + @Test + @DisplayName("post should send the JSON payload and return the response body") + void post_shouldSendPayloadAndReturnBody() { + ArgumentCaptor> captor = + captureRequest(HttpMethod.POST, new ResponseEntity<>("created", HttpStatus.CREATED)); + + assertEquals("created", httpUtils.post(URI, "{\"count\":5}")); + assertEquals("{\"count\":5}", captor.getValue().getBody()); + assertEquals(HttpStatus.CREATED, httpUtils.getStatus()); + } + + @Test + @DisplayName("post with headers should forward the supplied Authorization header") + void post_shouldForwardSuppliedAuthorizationHeader() { + HashMap header = new HashMap<>(); + header.put(HttpHeaders.AUTHORIZATION, "session-key-123"); + ArgumentCaptor> captor = + captureRequest(HttpMethod.POST, new ResponseEntity<>("created", HttpStatus.CREATED)); + + assertEquals("created", httpUtils.post(URI, "{\"count\":5}", header)); + assertEquals("session-key-123", + captor.getValue().getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + assertEquals("{\"count\":5}", captor.getValue().getBody()); + } + + @Test + @DisplayName("post with headers should omit the Authorization header when none is supplied") + void post_shouldOmitAuthorizationHeaderWhenNoneSupplied() { + ArgumentCaptor> captor = + captureRequest(HttpMethod.POST, new ResponseEntity<>("created", HttpStatus.CREATED)); + + httpUtils.post(URI, "{}", new HashMap<>()); + + assertNull(captor.getValue().getHeaders().getFirst(HttpHeaders.AUTHORIZATION)); + } + + @Test + @DisplayName("post should record a server error status") + void post_shouldRecordServerErrorStatus() { + when(restTemplate.exchange(eq(URI), eq(HttpMethod.POST), any(HttpEntity.class), eq(String.class))) + .thenReturn(new ResponseEntity<>("boom", HttpStatus.INTERNAL_SERVER_ERROR)); + + httpUtils.post(URI, "{}"); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, httpUtils.getStatus()); + } + + @Test + @DisplayName("post should issue the request against the supplied URI with the POST method") + void post_shouldIssueRequestWithPostMethod() { + when(restTemplate.exchange(eq(URI), eq(HttpMethod.POST), any(HttpEntity.class), eq(String.class))) + .thenReturn(new ResponseEntity<>("created", HttpStatus.CREATED)); + + httpUtils.post(URI, "{}"); + + verify(restTemplate).exchange(eq(URI), eq(HttpMethod.POST), any(HttpEntity.class), eq(String.class)); + } + } + + @Nested + @DisplayName("Status tracking") + class StatusTests { + + @Test + @DisplayName("getStatus should be null until a request has been made") + void getStatus_shouldBeNullBeforeAnyRequest() { + assertNull(httpUtils.getStatus()); + } + + @Test + @DisplayName("setStatus should record the supplied status code") + void setStatus_shouldRecordSuppliedStatusCode() { + httpUtils.setStatus(HttpStatus.ACCEPTED); + + assertEquals(HttpStatus.ACCEPTED, httpUtils.getStatus()); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/mapper/GsonMappersTest.java b/src/test/java/com/iemr/admin/utils/mapper/GsonMappersTest.java new file mode 100644 index 0000000..9b5dfcf --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/mapper/GsonMappersTest.java @@ -0,0 +1,133 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.mapper; + +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 com.google.gson.Gson; +import com.google.gson.annotations.Expose; + +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.assertTrue; + +/** + * The two mappers decide what leaves the service on the wire: the output mapper + * publishes only the fields a carrier marks {@code @Expose}, while the input + * mapper reads everything a caller sends. + */ +@DisplayName("Gson mapper Test Suite") +class GsonMappersTest { + + /** A carrier that exercises both the exposed and the hidden field handling. */ + private static final class Appointment { + @Expose + private String specialist; + @Expose + private Long appointmentId; + private String internalNote; + } + + @BeforeEach + @DisplayName("Prime the shared output builder, which only the constructor creates") + void primeOutputMapper() { + new OutputMapper(); + } + + @Nested + @DisplayName("OutputMapper") + class OutputMapperTests { + + @Test + @DisplayName("gson should publish the exposed fields and keep the hidden ones off the wire") + void gson_shouldSerialiseOnlyExposedFields() { + Appointment appointment = new Appointment(); + appointment.specialist = "dr.rao"; + appointment.internalNote = "not for the wire"; + + String json = OutputMapper.gson().toJson(appointment); + + assertTrue(json.contains("\"specialist\":\"dr.rao\""), json); + assertFalse(json.contains("internalNote"), "a field without @Expose must stay off the wire"); + } + + @Test + @DisplayName("gson should render a long as a string so large ids survive a JavaScript caller") + void gson_shouldRenderLongAsString() { + Appointment appointment = new Appointment(); + appointment.appointmentId = 9_007_199_254_740_993L; + + assertTrue(OutputMapper.gson().toJson(appointment).contains("\"9007199254740993\"")); + } + + @Test + @DisplayName("gson should serialise nulls rather than omit them") + void gson_shouldSerialiseNulls() { + assertTrue(OutputMapper.gson().toJson(new Appointment()).contains("null")); + } + + @Test + @DisplayName("gsonWithoutExpose should publish every field, annotated or not") + void gsonWithoutExpose_shouldPublishEveryField() { + Appointment appointment = new Appointment(); + appointment.specialist = "dr.rao"; + appointment.internalNote = "kept internally"; + + String json = OutputMapper.gsonWithoutExpose().toJson(appointment); + + assertTrue(json.contains("internalNote"), json); + assertTrue(json.contains("specialist"), json); + } + + @Test + @DisplayName("the constructor should reuse the shared builder across instances") + void constructor_shouldReuseSharedBuilder() { + new OutputMapper(); + + assertNotNull(OutputMapper.gson()); + } + } + + @Nested + @DisplayName("InputMapper and OutputMapper together") + class RoundTripTests { + + @Test + @DisplayName("a carrier written by the output mapper should be readable by the input mapper") + void carrier_shouldSurviveARoundTrip() { + Appointment appointment = new Appointment(); + appointment.specialist = "dr.rao"; + appointment.internalNote = "not for the wire"; + Gson writer = OutputMapper.gson(); + + Appointment restored = InputMapper.gson().fromJson(writer.toJson(appointment), Appointment.class); + + assertEquals("dr.rao", restored.specialist); + assertNull(restored.internalNote, "a hidden field must not come back over the wire"); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/mapper/InputMapperTest.java b/src/test/java/com/iemr/admin/utils/mapper/InputMapperTest.java new file mode 100644 index 0000000..4738014 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/mapper/InputMapperTest.java @@ -0,0 +1,133 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.mapper; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.google.gson.JsonSyntaxException; + +@DisplayName("InputMapper Test Suite") +class InputMapperTest { + + static class TestPojo { + String name; + int value; + Date date; + + public String getName() { return name; } + public int getValue() { return value; } + public Date getDate() { return date; } + } + + @Test + @DisplayName("Should return valid InputMapper instance from gson factory method") + void testGsonStaticFactoryMethod() { + InputMapper mapper = InputMapper.gson(); + assertNotNull(mapper); + assertTrue(mapper instanceof InputMapper); + } + + @Test + @DisplayName("Should successfully parse valid JSON to object") + void testFromJson_validJson() { + InputMapper mapper = InputMapper.gson(); + String json = "{\"name\":\"testName\", \"value\":100}"; + + TestPojo result = mapper.fromJson(json, TestPojo.class); + + assertNotNull(result); + assertEquals("testName", result.getName()); + assertEquals(100, result.getValue()); + assertNull(result.getDate()); + } + + @Test + @DisplayName("Should successfully parse JSON with date field") + void testFromJson_validJsonWithDate() throws ParseException { + InputMapper mapper = InputMapper.gson(); + String json = "{\"name\":\"itemWithDate\", \"value\":200, \"date\":\"2023-10-26T10:30:45.123\"}"; + + TestPojo result = mapper.fromJson(json, TestPojo.class); + + assertNotNull(result); + assertEquals("itemWithDate", result.getName()); + assertEquals(200, result.getValue()); + assertNotNull(result.getDate()); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + Date expectedDate = sdf.parse("2023-10-26T10:30:45.123"); + + assertEquals(expectedDate.getTime(), result.getDate().getTime()); + } + + @Test + @DisplayName("Should return null when JSON input is null") + void testFromJson_nullJson() { + InputMapper mapper = InputMapper.gson(); + String json = null; + + TestPojo result = mapper.fromJson(json, TestPojo.class); + assertNull(result); + } + + @Test + @DisplayName("Should return null when JSON input is empty string") + void testFromJson_emptyJsonString() { + InputMapper mapper = InputMapper.gson(); + String json = ""; + + // InputMapper's fromJson method (likely catching JsonSyntaxException internally) + // returns null when given an empty string. + TestPojo result = mapper.fromJson(json, TestPojo.class); + assertNull(result); + } + + @Test + @DisplayName("Should create object with default values when JSON is empty object") + void testFromJson_emptyJsonObject() { + InputMapper mapper = InputMapper.gson(); + String json = "{}"; + + TestPojo result = mapper.fromJson(json, TestPojo.class); + + assertNotNull(result); + assertNull(result.getName()); + assertEquals(0, result.getValue()); + assertNull(result.getDate()); + } + + @Test + @DisplayName("Should throw JsonSyntaxException when JSON is malformed") + void testFromJson_malformedJson() { + InputMapper mapper = InputMapper.gson(); + String json = "{\"name\":\"testName\", \"value\":,}"; + + JsonSyntaxException thrown = assertThrows(JsonSyntaxException.class, () -> mapper.fromJson(json, TestPojo.class)); + assertNotNull(thrown); + } +} diff --git a/src/test/java/com/iemr/admin/utils/redis/RedisSessionExceptionTest.java b/src/test/java/com/iemr/admin/utils/redis/RedisSessionExceptionTest.java new file mode 100644 index 0000000..261b2a4 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/redis/RedisSessionExceptionTest.java @@ -0,0 +1,74 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.redis; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.utils.exception.IEMRException; + +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; + +@DisplayName("RedisSessionException Test Suite") +class RedisSessionExceptionTest { + + private static final String MESSAGE = "Unable to fetch session object from Redis server"; + + @Test + @DisplayName("the message constructor should expose the message through both accessors") + void messageConstructor_shouldExposeMessage() { + RedisSessionException exception = new RedisSessionException(MESSAGE); + + assertEquals(MESSAGE, exception.getMessage()); + assertEquals(MESSAGE, exception.toString()); + } + + @Test + @DisplayName("a Redis session failure should be reportable as an AMRIT exception") + void redisSessionException_shouldBeAnIemrException() { + assertTrue(new RedisSessionException(MESSAGE) instanceof IEMRException); + } + + @Test + @DisplayName("the cause constructor should adopt the stack trace of the cause without chaining it") + void causeConstructor_shouldAdoptCauseStackTrace() { + RuntimeException cause = new RuntimeException("connection refused"); + cause.setStackTrace(new StackTraceElement[] { + new StackTraceElement("com.iemr.Origin", "connect", "Origin.java", 42) }); + + RedisSessionException exception = new RedisSessionException(MESSAGE, cause); + + assertEquals(MESSAGE, exception.getMessage()); + assertArrayEquals(cause.getStackTrace(), exception.getStackTrace()); + assertNull(exception.getCause()); + } + + @Test + @DisplayName("the Redis connection marker type should be instantiable") + void redisConnectionMarker_shouldBeInstantiable() { + assertNotNull(new RedisConnection()); + } +} diff --git a/src/test/java/com/iemr/admin/utils/redis/RedisStorageTest.java b/src/test/java/com/iemr/admin/utils/redis/RedisStorageTest.java new file mode 100644 index 0000000..af40210 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/redis/RedisStorageTest.java @@ -0,0 +1,189 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.redis; + +import java.nio.charset.StandardCharsets; + +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisStringCommands.SetOption; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.types.Expiration; +import org.springframework.test.util.ReflectionTestUtils; + +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.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("RedisStorage Test Suite") +class RedisStorageTest { + + private static final String KEY = "session-key"; + private static final int EXPIRY_SECONDS = 7200; + + @Mock + private LettuceConnectionFactory connectionFactory; + + @Mock + private RedisConnection redisConnection; + + private RedisStorage redisStorage; + + @BeforeEach + @DisplayName("Wire the store with a mocked Lettuce connection factory before each test") + void setUp() { + redisStorage = new RedisStorage(); + ReflectionTestUtils.setField(redisStorage, "connection", connectionFactory); + when(connectionFactory.getConnection()).thenReturn(redisConnection); + } + + private byte[] bytes(String value) { + return value.getBytes(StandardCharsets.UTF_8); + } + + @Nested + @DisplayName("setObject") + class SetObjectTests { + + @Test + @DisplayName("setObject should write the value when no session is stored yet") + void setObject_shouldWriteValueWhenKeyIsAbsent() throws RedisSessionException { + when(redisConnection.get(bytes(KEY))).thenReturn(null); + + assertEquals(KEY, redisStorage.setObject(KEY, "payload", EXPIRY_SECONDS)); + verify(redisConnection).set(eq(bytes(KEY)), eq(bytes("payload")), + eq(Expiration.seconds(EXPIRY_SECONDS)), eq(SetOption.UPSERT)); + } + + @Test + @DisplayName("setObject should write the value when the stored session is empty") + void setObject_shouldWriteValueWhenStoredSessionIsEmpty() throws RedisSessionException { + when(redisConnection.get(bytes(KEY))).thenReturn(bytes("")); + + assertEquals(KEY, redisStorage.setObject(KEY, "payload", EXPIRY_SECONDS)); + verify(redisConnection).set(any(byte[].class), any(byte[].class), any(Expiration.class), any(SetOption.class)); + } + + @Test + @DisplayName("setObject should leave an existing session untouched") + void setObject_shouldLeaveExistingSessionUntouched() throws RedisSessionException { + when(redisConnection.get(bytes(KEY))).thenReturn(bytes("existing")); + + assertEquals(KEY, redisStorage.setObject(KEY, "payload", EXPIRY_SECONDS)); + verify(redisConnection, never()).set(any(byte[].class), any(byte[].class), + any(Expiration.class), any(SetOption.class)); + } + } + + @Nested + @DisplayName("getObject") + class GetObjectTests { + + @Test + @DisplayName("getObject should return the stored session and extend its expiry") + void getObject_shouldReturnStoredSessionAndExtendExpiry() throws RedisSessionException { + when(redisConnection.get(bytes(KEY))).thenReturn(bytes("payload")); + + assertEquals("payload", redisStorage.getObject(KEY, true, EXPIRY_SECONDS)); + verify(redisConnection).expire(bytes(KEY), EXPIRY_SECONDS); + } + + @Test + @DisplayName("getObject should raise a session exception when the key is absent") + void getObject_shouldRaiseWhenKeyIsAbsent() { + when(redisConnection.get(bytes(KEY))).thenReturn(null); + + RedisSessionException thrown = assertThrows(RedisSessionException.class, + () -> redisStorage.getObject(KEY, true, EXPIRY_SECONDS)); + + assertEquals("Unable to fetch session object from Redis server", thrown.getMessage()); + } + + @Test + @DisplayName("getObject should raise a session exception when the stored value is blank") + void getObject_shouldRaiseWhenStoredValueIsBlank() { + when(redisConnection.get(bytes(KEY))).thenReturn(bytes(" ")); + + assertThrows(RedisSessionException.class, + () -> redisStorage.getObject(KEY, true, EXPIRY_SECONDS)); + verify(redisConnection, never()).expire(any(byte[].class), any(Long.class)); + } + } + + @Nested + @DisplayName("updateObject") + class UpdateObjectTests { + + @Test + @DisplayName("updateObject should overwrite an existing session") + void updateObject_shouldOverwriteExistingSession() throws RedisSessionException { + when(redisConnection.get(bytes(KEY))).thenReturn(bytes("old")); + + assertEquals(KEY, redisStorage.updateObject(KEY, "new", true, EXPIRY_SECONDS)); + verify(redisConnection).set(eq(bytes(KEY)), eq(bytes("new")), + eq(Expiration.seconds(EXPIRY_SECONDS)), eq(SetOption.UPSERT)); + } + + @Test + @DisplayName("updateObject should raise a session exception when there is nothing to update") + void updateObject_shouldRaiseWhenKeyIsAbsent() { + when(redisConnection.get(bytes(KEY))).thenReturn(null); + + RedisSessionException thrown = assertThrows(RedisSessionException.class, + () -> redisStorage.updateObject(KEY, "new", true, EXPIRY_SECONDS)); + + assertEquals("Unable to fetch session object from Redis server", thrown.getMessage()); + } + } + + @Nested + @DisplayName("deleteObject") + class DeleteObjectTests { + + @Test + @DisplayName("deleteObject should return the number of keys Redis removed") + void deleteObject_shouldReturnNumberOfKeysRemoved() throws RedisSessionException { + when(redisConnection.del(bytes(KEY))).thenReturn(1L); + + assertEquals(1L, redisStorage.deleteObject(KEY)); + } + + @Test + @DisplayName("deleteObject should return zero when the key was not present") + void deleteObject_shouldReturnZeroWhenKeyAbsent() throws RedisSessionException { + when(redisConnection.del(bytes(KEY))).thenReturn(0L); + + assertEquals(0L, redisStorage.deleteObject(KEY)); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/response/OutputResponseTest.java b/src/test/java/com/iemr/admin/utils/response/OutputResponseTest.java new file mode 100644 index 0000000..6a99924 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/response/OutputResponseTest.java @@ -0,0 +1,299 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.response; + +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.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.iemr.admin.utils.exception.IEMRException; + +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; + +@DisplayName("OutputResponse (response package) Test Suite") +class OutputResponseTest { + + private OutputResponse outputResponse; + + @BeforeEach + @DisplayName("Create a fresh response object before each test") + void setUp() { + outputResponse = new OutputResponse(); + } + + @Nested + @DisplayName("Default state") + class DefaultStateTests { + + @Test + @DisplayName("a new response should default to a generic failure") + void newResponse_shouldDefaultToGenericFailure() throws Exception { + assertEquals(OutputResponse.GENERIC_FAILURE, outputResponse.getStatusCode()); + assertEquals("Failed with generic error", outputResponse.getErrorMessage()); + assertEquals("FAILURE", outputResponse.getStatus()); + assertFalse(outputResponse.isSuccess()); + } + + @Test + @DisplayName("getData should return null when no data has been set") + void getData_shouldReturnNullWhenNoDataSet() throws Exception { + assertNull(outputResponse.getData()); + } + } + + @Nested + @DisplayName("setResponse") + class SetResponseTests { + + @Test + @DisplayName("setResponse should mark the response successful") + void setResponse_shouldMarkResponseSuccessful() throws Exception { + outputResponse.setResponse("done"); + + assertEquals(OutputResponse.SUCCESS, outputResponse.getStatusCode()); + assertEquals("Success", outputResponse.getErrorMessage()); + assertEquals("Success", outputResponse.getStatus()); + assertTrue(outputResponse.isSuccess()); + } + + @Test + @DisplayName("setResponse should keep a JSON object payload as an object") + void setResponse_shouldKeepJsonObjectPayload() throws Exception { + outputResponse.setResponse("{\"specialistAvailabilityID\":12345}"); + + assertTrue(outputResponse.getData().contains("\"specialistAvailabilityID\"")); + assertTrue(outputResponse.getData().startsWith("{")); + } + + @Test + @DisplayName("setResponse should keep a JSON array payload as an array") + void setResponse_shouldKeepJsonArrayPayload() throws Exception { + outputResponse.setResponse("[1,2,3]"); + + assertTrue(outputResponse.getData().startsWith("[")); + assertTrue(outputResponse.getData().contains("1")); + } + + @Test + @DisplayName("setResponse should wrap a plain string payload under a response key") + void setResponse_shouldWrapPlainStringPayload() throws Exception { + outputResponse.setResponse("plain text"); + + assertTrue(outputResponse.getData().contains("response")); + assertTrue(outputResponse.getData().contains("plain text")); + } + + @Test + @DisplayName("toString should serialise the exposed fields as JSON") + void toString_shouldSerialiseExposedFields() throws Exception { + outputResponse.setResponse("done"); + + String json = outputResponse.toString(); + + assertTrue(json.contains("\"statusCode\":200")); + assertTrue(json.contains("\"status\":\"Success\"")); + assertTrue(json.contains("\"errorMessage\":\"Success\"")); + assertTrue(json.contains("\"data\"")); + } + + @Test + @DisplayName("toString should omit null fields while toStringWithSerialization keeps them") + void toString_shouldOmitNullsUnlikeToStringWithSerialization() throws Exception { + assertFalse(outputResponse.toString().contains("\"data\"")); + assertTrue(outputResponse.toStringWithSerialization().contains("\"data\":null")); + } + } + + @Nested + @DisplayName("setError with an explicit code") + class SetErrorWithCodeTests { + + @Test + @DisplayName("setError should apply the supplied code, message and status") + void setError_shouldApplySuppliedCodeMessageAndStatus() throws Exception { + outputResponse.setError(OutputResponse.PREVILAGE_FAILURE, "not permitted", "PRIVILEGE"); + + assertEquals(OutputResponse.PREVILAGE_FAILURE, outputResponse.getStatusCode()); + assertEquals("not permitted", outputResponse.getErrorMessage()); + assertEquals("PRIVILEGE", outputResponse.getStatus()); + assertFalse(outputResponse.isSuccess()); + } + + @Test + @DisplayName("setError should reuse the message as the status when only a message is supplied") + void setError_shouldReuseMessageAsStatus() throws Exception { + outputResponse.setError(OutputResponse.PASSWORD_FAILURE, "bad password"); + + assertEquals(OutputResponse.PASSWORD_FAILURE, outputResponse.getStatusCode()); + assertEquals("bad password", outputResponse.getErrorMessage()); + assertEquals("bad password", outputResponse.getStatus()); + } + } + + @Nested + @DisplayName("setError mapped from a throwable") + class SetErrorFromThrowableTests { + + @Test + @DisplayName("setError should map IEMRException to a user login failure") + void setError_shouldMapIemrExceptionToUserIdFailure() throws Exception { + outputResponse.setError(new IEMRException("invalid credentials")); + + assertEquals(OutputResponse.USERID_FAILURE, outputResponse.getStatusCode()); + assertEquals("User login failed", outputResponse.getStatus()); + assertEquals("invalid credentials", outputResponse.getErrorMessage()); + } + + @Test + @DisplayName("setError should map JSONException to an object conversion failure") + void setError_shouldMapJsonExceptionToObjectFailure() throws Exception { + outputResponse.setError(new JSONException("bad json")); + + assertEquals(OutputResponse.OBJECT_FAILURE, outputResponse.getStatusCode()); + assertEquals("Invalid object conversion", outputResponse.getStatus()); + assertEquals("Invalid object conversion", outputResponse.getErrorMessage()); + } + + @Test + @DisplayName("setError should map SQLException to a code exception") + void setError_shouldMapSqlExceptionToCodeException() throws Exception { + outputResponse.setError(new SQLException("deadlock")); + + assertEquals(OutputResponse.CODE_EXCEPTION, outputResponse.getStatusCode()); + assertTrue(outputResponse.getStatus().startsWith("Failed with critical errors at ")); + assertEquals("deadlock", outputResponse.getErrorMessage()); + } + + @Test + @DisplayName("setError should map NullPointerException to a code exception") + void setError_shouldMapNullPointerExceptionToCodeException() throws Exception { + outputResponse.setError(new NullPointerException("npe")); + + assertEquals(OutputResponse.CODE_EXCEPTION, outputResponse.getStatusCode()); + } + + @Test + @DisplayName("setError should map ParseException to a code exception") + void setError_shouldMapParseExceptionToCodeException() throws Exception { + outputResponse.setError(new ParseException("bad date", 0)); + + assertEquals(OutputResponse.CODE_EXCEPTION, outputResponse.getStatusCode()); + } + + @Test + @DisplayName("setError should map ArrayIndexOutOfBoundsException to a code exception") + void setError_shouldMapArrayIndexExceptionToCodeException() throws Exception { + outputResponse.setError(new ArrayIndexOutOfBoundsException("index 5")); + + assertEquals(OutputResponse.CODE_EXCEPTION, outputResponse.getStatusCode()); + } + + @Test + @DisplayName("setError should map IOException to an environment exception") + void setError_shouldMapIoExceptionToEnvironmentException() throws Exception { + outputResponse.setError(new IOException("disk full")); + + assertEquals(OutputResponse.ENVIRONMENT_EXCEPTION, outputResponse.getStatusCode()); + assertTrue(outputResponse.getStatus().startsWith("Failed with connection issues at ")); + assertEquals("disk full", outputResponse.getErrorMessage()); + } + + @Test + @DisplayName("setError should map ConnectException to an environment exception") + void setError_shouldMapConnectExceptionToEnvironmentException() throws Exception { + outputResponse.setError(new ConnectException("refused")); + + assertEquals(OutputResponse.ENVIRONMENT_EXCEPTION, outputResponse.getStatusCode()); + } + + @Test + @DisplayName("setError should fall back to a generic failure for an unmapped exception") + void setError_shouldFallBackToGenericFailureForUnmappedException() throws Exception { + outputResponse.setError(new IllegalStateException("something odd")); + + assertEquals(OutputResponse.GENERIC_FAILURE, outputResponse.getStatusCode()); + assertTrue(outputResponse.getStatus().startsWith("Failed with something odd at ")); + assertEquals("something odd", outputResponse.getErrorMessage()); + } + } + + @Nested + @DisplayName("Error mapping for exception types raised by other AMRIT modules") + class ExternalExceptionMappingTests { + + // setError switches on getClass().getSimpleName(), so locally declared types with + // the same simple names reach the arms meant for Hibernate/JDBC exceptions. + private static class JDBCException extends Exception { + JDBCException(String message) { + super(message); + } + } + + private static class SQLGrammarException extends Exception { + SQLGrammarException(String message) { + super(message); + } + } + + private static class ConstraintViolationException extends Exception { + ConstraintViolationException(String message) { + super(message); + } + } + + @Test + @DisplayName("setError should map a JDBC failure to a DB connection environment error") + void setError_shouldMapJdbcFailure() throws Exception { + outputResponse.setError(new JDBCException("pool exhausted")); + + assertEquals(OutputResponse.ENVIRONMENT_EXCEPTION, outputResponse.getStatusCode()); + assertTrue(outputResponse.getStatus().startsWith("Failed with DB connection issues at ")); + assertEquals("pool exhausted", outputResponse.getErrorMessage()); + } + + @Test + @DisplayName("setError should map a SQL grammar failure to a code exception") + void setError_shouldMapSqlGrammarFailure() throws Exception { + outputResponse.setError(new SQLGrammarException("bad column")); + + assertEquals(OutputResponse.CODE_EXCEPTION, outputResponse.getStatusCode()); + } + + @Test + @DisplayName("setError should fall back to a generic failure for an unmapped constraint violation") + void setError_shouldMapConstraintViolation() throws Exception { + outputResponse.setError(new ConstraintViolationException("duplicate key")); + + assertEquals(OutputResponse.GENERIC_FAILURE, outputResponse.getStatusCode()); + } + } +} diff --git a/src/test/java/com/iemr/admin/utils/sessionobject/SessionObjectTest.java b/src/test/java/com/iemr/admin/utils/sessionobject/SessionObjectTest.java new file mode 100644 index 0000000..c7e41f5 --- /dev/null +++ b/src/test/java/com/iemr/admin/utils/sessionobject/SessionObjectTest.java @@ -0,0 +1,143 @@ +/* +* AMRIT - Accessible Medical Records via Integrated Technologies +* 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.admin.utils.sessionobject; + +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.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.iemr.admin.utils.config.ConfigProperties; +import com.iemr.admin.utils.redis.RedisSessionException; +import com.iemr.admin.utils.redis.RedisStorage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyBoolean; +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; + +@ExtendWith(MockitoExtension.class) +@DisplayName("SessionObject Test Suite") +class SessionObjectTest { + + private static final String KEY = "session-key"; + private static final String VALUE = "{\"userName\":\"amrit-user\"}"; + + @Mock + private RedisStorage objectStore; + + private SessionObject sessionObject; + private int expectedExpiry; + private boolean expectedExtend; + + @BeforeEach + @DisplayName("Wire the session holder with a mocked Redis store before each test") + void setUp() { + sessionObject = new SessionObject(); + sessionObject.setObjectStore(objectStore); + expectedExpiry = ConfigProperties.getSessionExpiryTime(); + expectedExtend = ConfigProperties.getExtendExpiryTime(); + } + + @Nested + @DisplayName("Reading and writing the session") + class ReadWriteTests { + + @Test + @DisplayName("getSessionObject should delegate to the store with the configured expiry settings") + void getSessionObject_shouldDelegateWithConfiguredExpiry() throws RedisSessionException { + when(objectStore.getObject(KEY, expectedExtend, expectedExpiry)).thenReturn(VALUE); + + assertEquals(VALUE, sessionObject.getSessionObject(KEY)); + verify(objectStore).getObject(KEY, expectedExtend, expectedExpiry); + } + + @Test + @DisplayName("setSessionObject should delegate to the store with the configured expiry") + void setSessionObject_shouldDelegateWithConfiguredExpiry() throws RedisSessionException { + when(objectStore.setObject(KEY, VALUE, expectedExpiry)).thenReturn(KEY); + + assertEquals(KEY, sessionObject.setSessionObject(KEY, VALUE)); + verify(objectStore).setObject(KEY, VALUE, expectedExpiry); + } + + @Test + @DisplayName("updateSessionObject should delegate to the store with the configured expiry settings") + void updateSessionObject_shouldDelegateWithConfiguredExpiry() throws RedisSessionException { + when(objectStore.updateObject(KEY, VALUE, expectedExtend, expectedExpiry)).thenReturn(KEY); + + assertEquals(KEY, sessionObject.updateSessionObject(KEY, VALUE)); + verify(objectStore).updateObject(KEY, VALUE, expectedExtend, expectedExpiry); + } + + @Test + @DisplayName("deleteSessionObject should delegate the removal to the store") + void deleteSessionObject_shouldDelegateRemoval() throws RedisSessionException { + when(objectStore.deleteObject(KEY)).thenReturn(1L); + + sessionObject.deleteSessionObject(KEY); + + verify(objectStore).deleteObject(KEY); + } + } + + @Nested + @DisplayName("Propagating store failures") + class FailureTests { + + @Test + @DisplayName("getSessionObject should propagate a missing-session failure") + void getSessionObject_shouldPropagateMissingSessionFailure() throws RedisSessionException { + when(objectStore.getObject(anyString(), anyBoolean(), anyInt())) + .thenThrow(new RedisSessionException("Unable to fetch session object from Redis server")); + + RedisSessionException thrown = assertThrows(RedisSessionException.class, + () -> sessionObject.getSessionObject(KEY)); + + assertEquals("Unable to fetch session object from Redis server", thrown.getMessage()); + } + + @Test + @DisplayName("updateSessionObject should propagate a missing-session failure") + void updateSessionObject_shouldPropagateMissingSessionFailure() throws RedisSessionException { + when(objectStore.updateObject(eq(KEY), eq(VALUE), anyBoolean(), anyInt())) + .thenThrow(new RedisSessionException("Unable to fetch session object from Redis server")); + + assertThrows(RedisSessionException.class, () -> sessionObject.updateSessionObject(KEY, VALUE)); + } + + @Test + @DisplayName("deleteSessionObject should propagate a store failure") + void deleteSessionObject_shouldPropagateStoreFailure() throws RedisSessionException { + when(objectStore.deleteObject(KEY)).thenThrow(new RedisSessionException("redis down")); + + assertThrows(RedisSessionException.class, () -> sessionObject.deleteSessionObject(KEY)); + } + } +}