package com.malk.benteler.service; import com.alibaba.fastjson.JSON; import com.malk.benteler.config.BentelerYidaConf; import com.malk.benteler.dto.EiamBatchItemResult; import com.malk.benteler.dto.EiamBatchOperation; import com.malk.benteler.dto.EiamBatchResult; import com.malk.benteler.dto.EiamBatchStage; import com.malk.benteler.dto.EiamFormSyncResult; import com.malk.benteler.dto.EiamUpdateUserItem; import com.malk.server.aliwork.YDAuth; import com.malk.server.aliwork.YDConf; import com.malk.server.aliwork.YDParam; import com.malk.server.common.McException; import com.malk.service.aliwork.YDClient_Form; import com.malk.service.aliwork.YDService; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * {@link BentelerYidaSyncService} 单实例同步闭环测试。 */ public class BentelerYidaSyncServiceTest { private YDClient_Form ydClientForm; private YDService ydService; private EiamLocalService eiamLocalService; private BentelerDingTalkSyncTaskService dingTalkSyncTaskService; private BentelerYidaConf conf; private BentelerYidaSyncService service; @Before public void setUp() { ydClientForm = mock(YDClient_Form.class); ydService = mock(YDService.class); eiamLocalService = mock(EiamLocalService.class); dingTalkSyncTaskService = mock(BentelerDingTalkSyncTaskService.class); conf = TestBentelerYidaConf.create(); YDConf ydConf = new YDConf(); ydConf.setAppType("APP_TEST"); ydConf.setSystemToken("TOKEN_TEST"); service = new BentelerYidaSyncService(ydClientForm, ydService, ydConf, conf, new BentelerYidaFormMapper(conf), eiamLocalService, dingTalkSyncTaskService); } @Test public void syncCreate_queriesInstanceAndWritesBackFullRowsAndStatistics() { Map formData = new HashMap<>(); formData.put(conf.getOnboardingOrganizationalUnitIdFieldId(), "ou_technical"); formData.put(conf.getOnboardingTableFieldId(), Arrays.asList( createRow("人员一", "13800000001"), createRow("人员二", "13800000002"))); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchCreate(any())).thenReturn(batchResult( EiamBatchOperation.CREATE, true, false)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); EiamFormSyncResult result = service.syncCreate("form_test"); assertTrue(result.isWritebackSuccess()); assertEquals(2, result.getTotal()); assertEquals(1, result.getSuccessCount()); ArgumentCaptor jsonCaptor = ArgumentCaptor.forClass(String.class); verify(ydClientForm).updateForm(any(YDAuth.class), anyString(), jsonCaptor.capture(), anyMap()); Map update = JSON.parseObject(jsonCaptor.getValue(), Map.class); List> rows = (List>) update.get( conf.getOnboardingTableFieldId()); assertEquals(2, rows.size()); assertEquals("成功", rows.get(0).get(conf.getDetailStatusFieldId())); assertEquals("失败", rows.get(1).get(conf.getDetailStatusFieldId())); assertEquals(2, ((Number) update.get(conf.getTotalFieldId())).intValue()); assertEquals("人员二 - 执行失败", update.get(conf.getFailureMessageFieldId())); } @Test public void syncCreate_afterWritebackEnqueuesOutsourcingCompanyForSuccessfulRows() { Map formData = new HashMap<>(); formData.put(conf.getOnboardingOrganizationalUnitIdFieldId(), "ou_technical"); formData.put(conf.getOnboardingOutsourcingCompanyFieldId(), "众川"); formData.put(conf.getOnboardingTableFieldId(), Collections.singletonList( createRow("人员一", "13800000001"))); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchCreate(any())).thenReturn(batchResultWithUserId( EiamBatchOperation.CREATE, "user_1", true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); service.syncCreate("form_create"); verify(dingTalkSyncTaskService).enqueueRosterUpdate("form_create", 0, "user_1", "众川"); } @Test public void syncUpdate_afterWritebackEnqueuesNonBlankOutsourcingCompany() { Map row = new HashMap<>(); row.put(conf.getUpdateEmployeeFieldId() + "_id", Collections.singletonList("user_1")); Map formData = new HashMap<>(); formData.put(conf.getUpdateContentFieldId(), Collections.singletonList(BentelerYidaFormMapper.UPDATE_PHONE)); formData.put(conf.getUpdateOutsourcingCompanyFieldId(), "翰辉"); formData.put(conf.getUpdateTableFieldId(), Collections.singletonList(row)); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchUpdate(any())).thenReturn(batchResult( EiamBatchOperation.UPDATE, true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); service.syncUpdate("form_update"); verify(dingTalkSyncTaskService).enqueueRosterUpdate("form_update", 0, "user_1", "翰辉"); } @Test public void syncUpdate_tianjinProbationConfirmationEnqueuesRosterClear() { Map row = new HashMap<>(); row.put(conf.getUpdateEmployeeFieldId() + "_id", Collections.singletonList("user_1")); Map formData = new HashMap<>(); formData.put(conf.getUpdateContentFieldId(), Collections.singletonList(BentelerYidaFormMapper.UPDATE_PHONE)); formData.put(conf.getUpdateFactoryFieldId(), "BATJ II"); formData.put(conf.getUpdateProbationConfirmationFieldId(), "是"); formData.put(conf.getUpdateTableFieldId(), Collections.singletonList(row)); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchUpdate(any())).thenReturn(batchResult(EiamBatchOperation.UPDATE, true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); service.syncUpdate("form_update"); verify(dingTalkSyncTaskService).enqueueRosterClear("form_update", 0, "user_1"); } @Test public void syncUpdate_addDepartmentEnqueuesContactPrimaryDepartmentUpdate() { Map row = new HashMap<>(); row.put(conf.getUpdateEmployeeFieldId() + "_id", Collections.singletonList("user_1")); Map formData = new HashMap<>(); formData.put(conf.getUpdateContentFieldId(), Collections.singletonList(BentelerYidaFormMapper.ADD_ORG)); formData.put(conf.getUpdateOrganizationalUnitIdFieldId(), "ou_target"); formData.put(conf.getUpdateDingTalkDepartmentFieldId(), Collections.singletonList("100")); formData.put(conf.getUpdateTableFieldId(), Collections.singletonList(row)); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchUpdate(any())).thenReturn(batchResult(EiamBatchOperation.UPDATE, true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); service.syncUpdate("form_update"); verify(dingTalkSyncTaskService).enqueueContactPrimaryDepartmentUpdate( "form_update", 0, "user_1", 100L); } @Test public void syncCreate_blankOutsourcingCompanyDoesNotEnqueueTask() { Map formData = new HashMap<>(); formData.put(conf.getOnboardingOrganizationalUnitIdFieldId(), "ou_technical"); formData.put(conf.getOnboardingOutsourcingCompanyFieldId(), " "); formData.put(conf.getOnboardingTableFieldId(), Collections.singletonList( createRow("人员一", "13800000001"))); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchCreate(any())).thenReturn(batchResultWithUserId( EiamBatchOperation.CREATE, "user_1", true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); service.syncCreate("form_blank"); verify(dingTalkSyncTaskService, never()).enqueueRosterUpdate(anyString(), org.mockito.ArgumentMatchers.anyInt(), anyString(), anyString()); } @Test public void syncCreate_writebackFailureReturnsExecutionResultWithoutRetryingEiam() { Map formData = new HashMap<>(); formData.put(conf.getOnboardingOrganizationalUnitIdFieldId(), "ou_technical"); formData.put(conf.getOnboardingTableFieldId(), Collections.singletonList( createRow("人员一", "13800000001"))); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchCreate(any())).thenReturn(batchResult( EiamBatchOperation.CREATE, true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenThrow(new IllegalStateException("writeback failed")); EiamFormSyncResult result = service.syncCreate("form_test"); assertFalse(result.isWritebackSuccess()); assertEquals(1, result.getSuccessCount()); verify(eiamLocalService).batchCreate(any()); verify(ydClientForm, times(1)).updateForm(any(YDAuth.class), anyString(), anyString(), anyMap()); } @Test public void syncCreate_versionConflictRetriesWritebackWithoutRetryingEiam() { Map formData = new HashMap<>(); formData.put(conf.getOnboardingOrganizationalUnitIdFieldId(), "ou_technical"); formData.put(conf.getOnboardingTableFieldId(), Collections.singletonList( createRow("人员一", "13800000001"))); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData), instance(formData)); when(eiamLocalService.batchCreate(any())).thenReturn(batchResult( EiamBatchOperation.CREATE, true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenThrow(new McException("YIDA_ERROR", "宜搭服务内部异常信息:实例数据已修改,请刷新当前页面")) .thenReturn(Collections.emptyMap()); EiamFormSyncResult result = service.syncCreate("form_version_conflict"); assertTrue(result.isWritebackSuccess()); assertEquals("冲突重试回写成功", result.getWritebackMessage()); verify(eiamLocalService, times(1)).batchCreate(any()); verify(ydClientForm, times(2)).updateForm(any(YDAuth.class), anyString(), anyString(), anyMap()); } @Test @SuppressWarnings("unchecked") public void syncUpdate_readsEiamOrganizationalUnitIdDirectly() { Map row = new HashMap<>(); row.put(conf.getUpdateEmployeeFieldId() + "_id", Collections.singletonList("user_1")); Map formData = new HashMap<>(); formData.put(conf.getUpdateContentFieldId(), Collections.singletonList(BentelerYidaFormMapper.REPLACE_PRIMARY_ORG)); formData.put(conf.getUpdateOrganizationalUnitIdFieldId(), "ou_target"); formData.put(conf.getUpdateTableFieldId(), Collections.singletonList(row)); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchUpdate(any())).thenReturn(batchResult( EiamBatchOperation.UPDATE, true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); EiamFormSyncResult result = service.syncUpdate("form_update"); assertEquals(1, result.getSuccessCount()); ArgumentCaptor itemsCaptor = ArgumentCaptor.forClass(List.class); verify(eiamLocalService).batchUpdate(itemsCaptor.capture()); List items = (List) itemsCaptor.getValue(); assertEquals("user_1", items.get(0).getUserId()); assertEquals("ou_target", items.get(0).getPrimaryOrganizationalUnitId()); } @Test public void syncUpdate_replaceAndAddDepartment_rejectsBeforeEiamCall() { Map row = new HashMap<>(); row.put(conf.getUpdateEmployeeFieldId() + "_id", Collections.singletonList("user_1")); Map formData = new HashMap<>(); formData.put(conf.getUpdateContentFieldId(), Arrays.asList( BentelerYidaFormMapper.REPLACE_PRIMARY_ORG, BentelerYidaFormMapper.ADD_ORG)); formData.put(conf.getUpdateOrganizationalUnitIdFieldId(), "ou_target"); formData.put(conf.getUpdateTableFieldId(), Collections.singletonList(row)); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); try { service.syncUpdate("form_update_conflict"); } catch (McException ex) { assertEquals("EIAM_UPDATE_CONTENT_CONFLICT", ex.getCode()); assertEquals("替换部门和增加部门不能同时选择", ex.getMessage()); verify(eiamLocalService, never()).batchUpdate(any()); return; } throw new AssertionError("替换部门和增加部门同时选择时应拒绝请求"); } @Test @SuppressWarnings("unchecked") public void syncUpdate_phoneOnly_doesNotRequireOrganizationalUnit() { Map row = new HashMap<>(); row.put(conf.getUpdateEmployeeFieldId() + "_id", Collections.singletonList("user_1")); row.put(conf.getUpdatePhoneFieldId(), "17612168216"); Map formData = new HashMap<>(); formData.put(conf.getUpdateContentFieldId(), Collections.singletonList(BentelerYidaFormMapper.UPDATE_PHONE)); formData.put(conf.getUpdateTableFieldId(), Collections.singletonList(row)); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchUpdate(any())).thenReturn(batchResult( EiamBatchOperation.UPDATE, true)); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); EiamFormSyncResult result = service.syncUpdate("form_update_phone"); assertEquals(1, result.getSuccessCount()); ArgumentCaptor itemsCaptor = ArgumentCaptor.forClass(List.class); verify(eiamLocalService).batchUpdate(itemsCaptor.capture()); List items = (List) itemsCaptor.getValue(); assertEquals("17612168216", items.get(0).getUsername()); assertEquals(null, items.get(0).getPrimaryOrganizationalUnitId()); } @Test public void syncUpdate_exactlyFiftyRowsQueriesDetailsWithConfiguredFormUuid() { List> inlineRows = new ArrayList<>(); for (int index = 0; index < 50; index++) { Map row = new HashMap<>(); row.put(conf.getUpdateEmployeeFieldId() + "_id", Collections.singletonList("user_" + index)); inlineRows.add(row); } Map formData = new HashMap<>(); formData.put(conf.getUpdateContentFieldId(), Collections.singletonList(BentelerYidaFormMapper.UPDATE_PHONE)); formData.put(conf.getUpdateTableFieldId(), inlineRows); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(ydService.queryDetails(any(YDParam.class))).thenReturn((List) inlineRows); when(eiamLocalService.batchUpdate(any())).thenReturn(batchResult( EiamBatchOperation.UPDATE, successFlags(50))); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); service.syncUpdate("form_update_50"); ArgumentCaptor paramCaptor = ArgumentCaptor.forClass(YDParam.class); verify(ydService).queryDetails(paramCaptor.capture()); assertEquals("FORM-UPDATE", paramCaptor.getValue().getFormUuid()); } @Test public void syncDelete_exactlyFiftyRowsFallsBackWhenDetailQueryThrowsNpe() { List> inlineRows = new ArrayList<>(); for (int index = 0; index < 50; index++) { Map row = new HashMap<>(); row.put(conf.getOffboardingEmployeeFieldId() + "_id", Collections.singletonList("user_" + index)); inlineRows.add(row); } Map formData = new HashMap<>(); formData.put(conf.getOffboardingTableFieldId(), inlineRows); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(ydService.queryDetails(any(YDParam.class))).thenThrow(new NullPointerException()); when(eiamLocalService.batchDelete(any())).thenReturn(batchResult( EiamBatchOperation.DELETE, successFlags(50))); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); EiamFormSyncResult result = service.syncDelete("form_delete"); assertEquals(50, result.getTotal()); verify(ydService).queryDetails(any(YDParam.class)); } @Test public void syncDelete_lessThanFiftyRowsUsesInlineDetails() { List> inlineRows = new ArrayList<>(); for (int index = 0; index < 49; index++) { Map row = new HashMap<>(); row.put(conf.getOffboardingEmployeeFieldId() + "_id", Collections.singletonList("user_" + index)); inlineRows.add(row); } Map formData = new HashMap<>(); formData.put(conf.getOffboardingTableFieldId(), inlineRows); when(ydClientForm.getForm(any(YDAuth.class), anyString(), isNull())) .thenReturn(instance(formData)); when(eiamLocalService.batchDelete(any())).thenReturn(batchResult( EiamBatchOperation.DELETE, successFlags(49))); when(ydClientForm.updateForm(any(YDAuth.class), anyString(), anyString(), anyMap())) .thenReturn(Collections.emptyMap()); EiamFormSyncResult result = service.syncDelete("form_delete_49"); assertEquals(49, result.getTotal()); verify(ydService, never()).queryDetails(any(YDParam.class)); } private Map createRow(String name, String phone) { Map row = new HashMap<>(); row.put(conf.getOnboardingNameFieldId(), name); row.put(conf.getOnboardingPhoneFieldId(), phone); return row; } private Map instance(Map formData) { Map instance = new HashMap<>(); instance.put("formData", formData); return instance; } private boolean[] successFlags(int size) { boolean[] flags = new boolean[size]; Arrays.fill(flags, true); return flags; } private EiamBatchResult batchResult(EiamBatchOperation operation, boolean... successFlags) { List items = new ArrayList<>(); for (int index = 0; index < successFlags.length; index++) { items.add(EiamBatchItemResult.builder().index(index).success(successFlags[index]) .stage(EiamBatchStage.COMPLETED).build()); } return EiamBatchResult.of(operation, items); } private EiamBatchResult batchResultWithUserId(EiamBatchOperation operation, String userId, boolean success) { return EiamBatchResult.of(operation, Collections.singletonList(EiamBatchItemResult.builder() .index(0).userId(userId).success(success).stage(EiamBatchStage.COMPLETED).build())); } }