package com.malk.service.workhours; import com.malk.server.aliwork.YDConf; import com.malk.server.aliwork.YDParam; import com.malk.server.common.McException; import com.malk.server.dingtalk.DDR_New; import com.malk.server.workhours.TimeCardApprovalStartRequest; import com.malk.server.workhours.WHConf; import com.malk.service.aliwork.YDClient; import com.malk.service.dingtalk.DDClient; import com.malk.service.dingtalk.DDClient_Contacts; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.test.util.ReflectionTestUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; 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.mock; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; class ReSubmitApprovalServiceTest { private static final String DEFAULT_MANAGER_ID = "634735129"; @Test void shouldTreatBlankHistoricalOpinionAsCompletedApprovalCoverage() { assertTrue(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", "")); assertTrue(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", null)); assertTrue(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", "同意")); assertFalse(ReSubmitApprovalService.isCompletedApprovedRow("COMPLETED", "agree", "拒绝")); assertFalse(ReSubmitApprovalService.isCompletedApprovedRow("RUNNING", "agree", "")); } @Test void shouldSkipRowsAlreadyInRunningApproval() { ReSubmitApprovalService.ApprovalCoverage coverage = new ReSubmitApprovalService.ApprovalCoverage(); String key = ReSubmitApprovalService.approvalKey( "billable", "user-1", "20260611", "P1"); coverage.runningKeys.add(key); assertTrue(coverage.shouldSkip(key, 0)); } @Test void shouldRouteUnaccountedCompletedApprovalToWriteBackInsteadOfResubmit() { ReSubmitApprovalService.ApprovalCoverage coverage = new ReSubmitApprovalService.ApprovalCoverage(); String key = ReSubmitApprovalService.approvalKey( "billable", "user-1", "20260611", "P1"); coverage.completedAgreeHours.put(key, 8d); assertTrue(coverage.shouldSkip(key, 0)); assertFalse(coverage.shouldSkip(key, 8)); } @Test void shouldStartApprovalWithConfiguredHumanOriginator() { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); whConf.setApprovalOriginatorDeptId("dept-default"); whConf.setApprovalFillTypeField("radioField_fill_type"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); attachOpenMonth(service); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setDeptId("dept-request"); request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"value\"}"); assertTrue(service.startApproval(request)); ArgumentCaptor captor = ArgumentCaptor.forClass(YDParam.class); verify(ydClient).operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start)); assertEquals("system-user", captor.getValue().getUserId()); assertEquals("dept-request", captor.getValue().getDeptId()); Map started = com.alibaba.fastjson.JSON.parseObject( captor.getValue().getFormDataJson(), Map.class); assertEquals("正常", started.get("radioField_fill_type")); } @Test void shouldNotStartSameNormalApprovalTwiceWithinGuardWindow() { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); whConf.setApprovalFillTypeField("radioField_fill_type"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); attachOpenMonth(service); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"same-hours\"}"); assertTrue(service.startApproval(request)); assertTrue(service.startApproval(request)); verify(ydClient, times(1)).operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start)); request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"changed-hours\"}"); assertTrue(service.startApproval(request)); verify(ydClient, times(2)).operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start)); } @Test void shouldBlockConcurrentNormalApprovalStart() throws Exception { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); CountDownLatch entered = new CountDownLatch(1); CountDownLatch release = new CountDownLatch(1); when(ydClient.operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start))) .thenAnswer(invocation -> { entered.countDown(); release.await(5, TimeUnit.SECONDS); return null; }); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); attachOpenMonth(service); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\",\"textField_test\":\"same-hours\"}"); CompletableFuture first = CompletableFuture.supplyAsync(() -> service.startApproval(request)); try { assertTrue(entered.await(5, TimeUnit.SECONDS)); assertThrows(RuntimeException.class, () -> service.startApproval(request)); } finally { release.countDown(); } assertTrue(first.get(5, TimeUnit.SECONDS)); verify(ydClient, times(1)).operateData(any(YDParam.class), eq(YDConf.FORM_OPERATION.start)); } @Test void shouldBlockNewApprovalAndResubmitWhileMonthIsLocked() { YDClient ydClient = mock(YDClient.class); MonthClosingService monthClosingService = mock(MonthClosingService.class); when(monthClosingService.isLocked("202609")).thenReturn(true); WHConf whConf = new WHConf(); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); ReflectionTestUtils.setField(service, "monthClosingService", monthClosingService); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setFormDataJson("{\"textField_mmd2lv0y\":\"202609\"}"); assertThrows(McException.class, () -> service.startApproval(request)); assertThrows(McException.class, () -> service.resubmit("202609", "user-1", false)); verifyNoInteractions(ydClient); } @Test void shouldBlockRepairApprovalBySubmitMonthWhileSourceMonthIsOpen() { YDClient ydClient = mock(YDClient.class); MonthClosingService monthClosingService = mock(MonthClosingService.class); when(monthClosingService.isLocked("202609")).thenReturn(true); WHConf whConf = new WHConf(); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); whConf.setApprovalFillTypeField("radioField_fill_type"); whConf.setApprovalRepairSubmitDateField("dateField_repair_submit"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); ReflectionTestUtils.setField(service, "monthClosingService", monthClosingService); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setFormDataJson("{\"textField_mmd2lv0y\":\"202608\",\"radioField_fill_type\":\"补填\",\"dateField_repair_submit\":\"2026-09-22\"}"); assertThrows(McException.class, () -> service.startApproval(request)); verify(monthClosingService).isLocked("202609"); verify(monthClosingService, org.mockito.Mockito.never()).isLocked("202608"); verifyNoInteractions(ydClient); } @Test void shouldKeepExplicitRepairFillTypeWhenStartingApproval() { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); whConf.setApprovalFillTypeField("radioField_fill_type"); whConf.setApprovalRepairSubmitDateField("dateField_repair_submit"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); MonthClosingService monthClosingService = attachOpenMonth(service); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setFormDataJson("{\"textField_mmd2lv0y\":\"202608\",\"radioField_fill_type\":\"补填\",\"dateField_repair_submit\":\"2026-09-22\"}"); assertTrue(service.startApproval(request)); ArgumentCaptor captor = ArgumentCaptor.forClass(YDParam.class); verify(ydClient).operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start)); Map started = com.alibaba.fastjson.JSON.parseObject( captor.getValue().getFormDataJson(), Map.class); assertEquals("补填", started.get("radioField_fill_type")); verify(monthClosingService, org.mockito.Mockito.times(2)).isLocked("202609"); verify(monthClosingService, org.mockito.Mockito.never()).isLocked("202608"); } @Test void shouldPaginateSummaryRecordsWithCurrentPage() { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form))) .thenAnswer(invocation -> { YDParam param = invocation.getArgument(0); com.malk.server.dingtalk.DDR_New response = new com.malk.server.dingtalk.DDR_New(); List rows = new ArrayList<>(); int size = param.getCurrentPage() == 1 ? 100 : 1; for (int index = 0; index < size; index++) { rows.add(Collections.singletonMap( "formInstanceId", param.getCurrentPage() + "-" + index)); } response.setData(rows); return response; }); List rows = service.queryAll("FORM-SUMMARY", Collections.emptyMap()); assertEquals(101, rows.size()); ArgumentCaptor captor = ArgumentCaptor.forClass(YDParam.class); verify(ydClient, org.mockito.Mockito.times(2)) .queryData(captor.capture(), eq(YDConf.FORM_QUERY.retrieve_search_form)); assertEquals(1, captor.getAllValues().get(0).getCurrentPage()); assertEquals(2, captor.getAllValues().get(1).getCurrentPage()); } @Test void shouldCreateApprovalShardWhenSubtableWouldExceedLimit() { assertTrue(ReSubmitApprovalService.hasSubtableCapacity(500)); assertFalse(ReSubmitApprovalService.hasSubtableCapacity(501)); assertTrue(ReSubmitApprovalService.canAppendToApprovalShard(250, 300)); assertFalse(ReSubmitApprovalService.canAppendToApprovalShard(250, 301)); assertTrue(ReSubmitApprovalService.canAppendToApprovalShard(0, 450)); assertTrue(ReSubmitApprovalService.canAppendToApprovalShard(450, 450)); assertFalse(ReSubmitApprovalService.canAppendToApprovalShard(0, 501)); } @Test void shouldSplitFallbackManagerGroupByProject() { String projectA = ReSubmitApprovalService.approvalGroupKey(DEFAULT_MANAGER_ID, "P-A", "项目 A"); String projectB = ReSubmitApprovalService.approvalGroupKey(DEFAULT_MANAGER_ID, "P-B", "项目 B"); assertFalse(projectA.equals(projectB)); assertEquals(DEFAULT_MANAGER_ID + "|project|P-A", projectA); } @Test void shouldKeepNormalManagerGroupAcrossProjects() { assertEquals( ReSubmitApprovalService.approvalGroupKey("manager-1", "P-A", "项目 A"), ReSubmitApprovalService.approvalGroupKey("manager-1", "P-B", "项目 B")); } @Test void shouldAppendFallbackApprovalOnlyToMatchingProject() { Map projectABillable = new HashMap<>(); projectABillable.put("textField_mmacxew3", "P-A"); Map projectANonBillable = new HashMap<>(); projectANonBillable.put("textField_mmd1wu9d", "P-A"); Map projectBRow = new HashMap<>(); projectBRow.put("textField_mmacxew3", "P-B"); assertTrue(ReSubmitApprovalService.approvalMatchesProject( Collections.singletonList((Map) projectABillable), Collections.singletonList((Map) projectANonBillable), "P-A", "项目 A")); assertFalse(ReSubmitApprovalService.approvalMatchesProject( Collections.singletonList((Map) projectABillable), Collections.singletonList((Map) projectBRow), "P-A", "项目 A")); assertFalse(ReSubmitApprovalService.approvalMatchesProject( Collections.emptyList(), Collections.emptyList(), "P-A", "项目 A")); assertTrue(ReSubmitApprovalService.approvalMatchesProject( Collections.singletonList((Map) projectABillable), Collections.singletonList((Map) projectBRow), "", "")); } @Test void shouldUseProjectNameWhenFallbackProjectCodeIsBlank() { assertFalse(ReSubmitApprovalService.approvalGroupKey( DEFAULT_MANAGER_ID, "", "项目 A").equals( ReSubmitApprovalService.approvalGroupKey(DEFAULT_MANAGER_ID, "", "项目 B"))); Map projectA = new HashMap<>(); projectA.put("textField_mmacxew3", ""); projectA.put("textField_mkov5ozd", "项目 A"); Map projectB = new HashMap<>(); projectB.put("textField_mmacxew3", ""); projectB.put("textField_mkov5ozd", "项目 B"); assertTrue(ReSubmitApprovalService.approvalMatchesProject( Collections.singletonList((Map) projectA), Collections.emptyList(), "", "项目 A")); assertFalse(ReSubmitApprovalService.approvalMatchesProject( Collections.singletonList((Map) projectB), Collections.emptyList(), "", "项目 A")); } @Test void shouldKeepSingleSubmitterRowsTogetherWithinHardLimit() { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); attachOpenMonth(service); Map formData = new HashMap<>(); formData.put("textField_mmd2lv0y", "202609"); List> rows = new ArrayList<>(); for (int index = 0; index < 450; index++) { rows.add(Collections.singletonMap("numberField_mmacxew9", 1)); } formData.put("tableField_mmae8t99", rows); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setFormDataJson(com.alibaba.fastjson.JSON.toJSONString(formData)); assertTrue(service.startApproval(request)); ArgumentCaptor captor = ArgumentCaptor.forClass(YDParam.class); verify(ydClient).operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start)); Map started = com.alibaba.fastjson.JSON.parseObject( captor.getValue().getFormDataJson(), Map.class); assertEquals(450, ((List) started.get("tableField_mmae8t99")).size()); } @Test void shouldSplitSingleSubmitterOnlyWhenStartRowsExceedHardLimit() { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidApproval("FORM-APPROVAL"); whConf.setApprovalProcessCode("PROCESS-APPROVAL"); whConf.setApprovalOriginatorUserId("system-user"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); attachOpenMonth(service); Map formData = new HashMap<>(); formData.put("textField_mmd2lv0y", "202609"); List> rows = new ArrayList<>(); for (int index = 0; index < 501; index++) { Map row = new HashMap<>(); row.put("numberField_mmacxew9", 1); rows.add(row); } formData.put("tableField_mmae8t99", rows); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-APPROVAL"); request.setProcessCode("PROCESS-APPROVAL"); request.setFormDataJson(com.alibaba.fastjson.JSON.toJSONString(formData)); assertTrue(service.startApproval(request)); ArgumentCaptor captor = ArgumentCaptor.forClass(YDParam.class); verify(ydClient, org.mockito.Mockito.times(2)) .operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start)); Map first = com.alibaba.fastjson.JSON.parseObject( captor.getAllValues().get(0).getFormDataJson(), Map.class); Map second = com.alibaba.fastjson.JSON.parseObject( captor.getAllValues().get(1).getFormDataJson(), Map.class); assertEquals(500, ((List) first.get("tableField_mmae8t99")).size()); assertEquals(1, ((List) second.get("tableField_mmae8t99")).size()); assertEquals(500d, ((Number) first.get("numberField_mmd5b5gl")).doubleValue()); assertEquals(1d, ((Number) second.get("numberField_mmd5b5gl")).doubleValue()); } @Test void shouldSplitOtherApprovalRowsAtHardLimit() { YDClient ydClient = mock(YDClient.class); WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidOtherApproval("FORM-OTHER"); whConf.setOtherApprovalProcessCode("PROCESS-OTHER"); whConf.setApprovalOriginatorUserId("system-user"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); attachOpenMonth(service); Map formData = new HashMap<>(); formData.put("textField_mmd2lv0y", "202609"); List> rows = new ArrayList<>(); for (int index = 0; index < 501; index++) { rows.add(Collections.singletonMap("numberField_mmd1wu9e", 2)); } formData.put("tableField_mmd1wu9h", rows); TimeCardApprovalStartRequest request = new TimeCardApprovalStartRequest(); request.setFormUuid("FORM-OTHER"); request.setProcessCode("PROCESS-OTHER"); request.setFormDataJson(com.alibaba.fastjson.JSON.toJSONString(formData)); assertTrue(service.startApproval(request)); ArgumentCaptor captor = ArgumentCaptor.forClass(YDParam.class); verify(ydClient, org.mockito.Mockito.times(2)) .operateData(captor.capture(), eq(YDConf.FORM_OPERATION.start)); Map first = com.alibaba.fastjson.JSON.parseObject( captor.getAllValues().get(0).getFormDataJson(), Map.class); Map second = com.alibaba.fastjson.JSON.parseObject( captor.getAllValues().get(1).getFormDataJson(), Map.class); assertEquals(500, ((List) first.get("tableField_mmd1wu9h")).size()); assertEquals(1, ((List) second.get("tableField_mmd1wu9h")).size()); assertEquals(1000d, ((Number) first.get("numberField_mmd5b5gl")).doubleValue()); assertEquals(2d, ((Number) second.get("numberField_mmd5b5gl")).doubleValue()); } @Test void shouldKeepActiveProjectManager() { DDClient ddClient = mock(DDClient.class); DDClient_Contacts contacts = mock(DDClient_Contacts.class); YDClient ydClient = mock(YDClient.class); ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient); when(ddClient.getAccessToken()).thenReturn("token"); when(contacts.getUserInfoById("token", "pm-active")) .thenReturn(Collections.singletonMap("userid", "pm-active")); assertEquals("pm-active", service.resolveProjectManager("pm-active")); verifyNoInteractions(ydClient); } @Test void shouldUseActiveSupervisorWhenProjectManagerHasLeft() { DDClient ddClient = mock(DDClient.class); DDClient_Contacts contacts = mock(DDClient_Contacts.class); YDClient ydClient = mock(YDClient.class); ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient); when(ddClient.getAccessToken()).thenReturn("token"); when(contacts.getUserInfoById("token", "pm-left")) .thenThrow(new IllegalStateException("user not found")); when(contacts.getUserInfoById("token", "supervisor-active")) .thenReturn(Collections.singletonMap("userid", "supervisor-active")); when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form))) .thenReturn(personnelResponse("supervisor-active")); assertEquals("supervisor-active", service.resolveProjectManager("pm-left")); } @Test void shouldFallBackWhenSupervisorIsInactive() { DDClient ddClient = mock(DDClient.class); DDClient_Contacts contacts = mock(DDClient_Contacts.class); YDClient ydClient = mock(YDClient.class); ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient); when(ddClient.getAccessToken()).thenReturn("token"); when(contacts.getUserInfoById("token", "pm-left")) .thenThrow(new IllegalStateException("user not found")); when(contacts.getUserInfoById("token", "supervisor-left")) .thenThrow(new IllegalStateException("user not found")); when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form))) .thenReturn(personnelResponse("supervisor-left")); assertEquals(DEFAULT_MANAGER_ID, service.resolveProjectManager("pm-left")); } @Test void shouldFallBackWhenSupervisorIsMissing() { DDClient ddClient = mock(DDClient.class); DDClient_Contacts contacts = mock(DDClient_Contacts.class); YDClient ydClient = mock(YDClient.class); ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient); when(ddClient.getAccessToken()).thenReturn("token"); when(contacts.getUserInfoById("token", "pm-left")) .thenThrow(new IllegalStateException("user not found")); when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form))) .thenReturn(new DDR_New()); assertEquals(DEFAULT_MANAGER_ID, service.resolveProjectManager("pm-left")); } @Test void shouldFallBackWhenPersonnelLookupFails() { DDClient ddClient = mock(DDClient.class); DDClient_Contacts contacts = mock(DDClient_Contacts.class); YDClient ydClient = mock(YDClient.class); ReSubmitApprovalService service = managerResolutionService(ddClient, contacts, ydClient); when(ddClient.getAccessToken()).thenReturn("token"); when(contacts.getUserInfoById("token", "pm-left")) .thenThrow(new IllegalStateException("user not found")); when(ydClient.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_form))) .thenThrow(new IllegalStateException("query failed")); assertEquals(DEFAULT_MANAGER_ID, service.resolveProjectManager("pm-left")); } private static MonthClosingService attachOpenMonth(ReSubmitApprovalService service) { MonthClosingService monthClosingService = mock(MonthClosingService.class); ReflectionTestUtils.setField(service, "monthClosingService", monthClosingService); return monthClosingService; } private ReSubmitApprovalService managerResolutionService(DDClient ddClient, DDClient_Contacts contacts, YDClient ydClient) { WHConf whConf = new WHConf(); whConf.setYidaAppType("APP"); whConf.setYidaSystemToken("TOKEN"); whConf.setFormUuidPersonnel("FORM-PERSONNEL"); ReSubmitApprovalService service = new ReSubmitApprovalService(); ReflectionTestUtils.setField(service, "ddClient", ddClient); ReflectionTestUtils.setField(service, "ddClient_contacts", contacts); ReflectionTestUtils.setField(service, "ydClient", ydClient); ReflectionTestUtils.setField(service, "whConf", whConf); return service; } private DDR_New personnelResponse(String supervisorId) { Map formData = new HashMap<>(); formData.put("employeeField_mh8xhqc3_id", Collections.singletonList(supervisorId)); Map record = new HashMap<>(); record.put("formData", formData); DDR_New response = new DDR_New(); response.setData(Collections.singletonList(record)); return response; } }