| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- package com.malk.timer;
- import com.malk.server.aliwork.YDConf;
- import com.malk.server.aliwork.YDParam;
- import com.malk.server.dingtalk.DDR_New;
- import com.malk.server.workhours.ApprovalWriteBackResult;
- import com.malk.service.workhours.WorkHoursCalcService;
- import com.malk.server.workhours.WHConf;
- import com.malk.service.aliwork.YDClient;
- import com.malk.service.workhours.ApprovalWriteBackService;
- import com.malk.service.workhours.BackendExceptionLogService;
- import org.junit.Test;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
- import org.springframework.scheduling.annotation.Scheduled;
- import org.springframework.test.util.ReflectionTestUtils;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.concurrent.atomic.AtomicInteger;
- import static org.junit.Assert.assertEquals;
- import static org.junit.Assert.assertFalse;
- import static org.junit.Assert.assertTrue;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.times;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.when;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.ArgumentMatchers.eq;
- public class WorkHoursTimerScheduleTest {
- @Test
- public void workHoursIncrementalTimerShouldRunAtThreeThirtyAndTwelveFortyFive() throws Exception {
- Method method = WorkHoursCalcTimer.class.getMethod("calcDailyIncrementalSync");
- assertEquals(setOf("0 30 3 * * ?", "0 45 12 * * ?"), scheduledCrons(method));
- }
- @Test
- public void workHoursIncrementalTimerShouldUseSevenDayRecoveryWindow() {
- WorkHoursCalcService service = mock(WorkHoursCalcService.class);
- WorkHoursCalcTimer timer = new WorkHoursCalcTimer();
- ReflectionTestUtils.setField(timer, "workHoursCalcService", service);
- timer.calcDailyIncrementalSync();
- verify(service).incrementalSync(7);
- }
- @Test
- public void workHoursTimerShouldRespectGlobalSchedulingSwitch() {
- ConditionalOnProperty condition = WorkHoursCalcTimer.class.getAnnotation(ConditionalOnProperty.class);
- assertEquals("enable", condition.prefix());
- assertEquals(Arrays.asList("scheduling"), Arrays.asList(condition.name()));
- assertEquals("true", condition.havingValue());
- assertEquals(false, condition.matchIfMissing());
- }
- @Test
- public void projectChangeTimerShouldRunFifteenMinutesBeforeWorkHoursTimer() throws Exception {
- Method method = ProjectChangeSyncTimer.class.getMethod("syncProjectChanges");
- assertEquals(setOf("0 15 3 * * ?", "0 30 12 * * ?"), scheduledCrons(method));
- }
- @Test
- public void projectChangeTimerShouldRequireExplicitEnablement() {
- ConditionalOnProperty condition = ProjectChangeSyncTimer.class.getAnnotation(ConditionalOnProperty.class);
- assertEquals("workhours", condition.prefix());
- assertEquals(Arrays.asList("projectChangeSyncEnabled"), Arrays.asList(condition.name()));
- assertEquals("true", condition.havingValue());
- assertEquals(false, condition.matchIfMissing());
- }
- @Test
- public void approvalWriteBackShouldRunBeforeApprovalResubmit() throws Exception {
- Method writeBack = ApprovalWriteBackTimer.class.getMethod("scanAndWriteBack");
- Method resubmit = ReSubmitApprovalTimer.class.getMethod("scheduledMorningResubmit");
- Method noonResubmit = ReSubmitApprovalTimer.class.getMethod("scheduledAfternoonResubmit");
- assertEquals(setOf("0 20 4 * * ?", "0 20 12 * * ?"), scheduledCrons(writeBack));
- assertEquals(setOf("0 0 7 * * ?"), scheduledCrons(resubmit));
- assertEquals(setOf("0 0 16 * * ?"), scheduledCrons(noonResubmit));
- }
- @Test
- public void approvalResubmitShouldDefaultToSevenDayRecoveryWindow() {
- assertEquals(7, new WHConf().getApprovalResubmitModifiedLookbackDays());
- }
- @Test
- public void approvalFallbackTimersShouldRespectGlobalSchedulingSwitch() {
- assertSchedulingSwitch(ApprovalWriteBackTimer.class);
- assertSchedulingSwitch(ReSubmitApprovalTimer.class);
- assertSchedulingSwitch(PersonnelSyncTimer.class);
- assertSchedulingSwitch(BeisenSyncTimer.class);
- }
- @Test
- public void personnelTimerShouldRetryWriteAndManagerLookupFailures() {
- assertTrue(PersonnelSyncTimer.hasSyncFailures(null));
- Map<String, Object> stats = new HashMap<>();
- stats.put("failed", 0);
- stats.put("managerLookupFailed", 1);
- assertTrue(PersonnelSyncTimer.hasSyncFailures(stats));
- stats.put("managerLookupFailed", 0);
- stats.put("failed", 1);
- assertTrue(PersonnelSyncTimer.hasSyncFailures(stats));
- stats.put("failed", 0);
- assertFalse(PersonnelSyncTimer.hasSyncFailures(stats));
- }
- @Test
- public void approvalWriteBackShouldReadCurrentProcessDataShape() {
- Map<String, Object> formData = new HashMap<>();
- formData.put("selectField_mq58cd5p", "全部成功");
- Map<String, Object> process = new HashMap<>();
- process.put("data", formData);
- assertEquals(formData, ApprovalWriteBackTimer.extractFormData(process));
- }
- @Test
- public void approvalWriteBackShouldRemainCompatibleWithLegacyFormDataShape() {
- Map<String, Object> formData = new HashMap<>();
- formData.put("selectField_mq58cd5p", "全部成功");
- Map<String, Object> process = new HashMap<>();
- process.put("formData", formData);
- assertEquals(formData, ApprovalWriteBackTimer.extractFormData(process));
- }
- @Test
- public void approvalWriteBackShouldContinueAfterLegacyTwentyPageLimit() {
- assertTrue(ApprovalWriteBackTimer.hasNextPage(20, 100, 2500, 100));
- assertFalse(ApprovalWriteBackTimer.hasNextPage(25, 100, 2500, 100));
- assertFalse(ApprovalWriteBackTimer.hasNextPage(20, 100, 2500, 99));
- }
- @Test
- public void approvalWriteBackShouldRetryEveryStatusExceptAllSuccess() {
- assertTrue(ApprovalWriteBackTimer.shouldRetrySyncStatus(""));
- assertTrue(ApprovalWriteBackTimer.shouldRetrySyncStatus("部分失败"));
- assertTrue(ApprovalWriteBackTimer.shouldRetrySyncStatus("全部失败"));
- assertTrue(ApprovalWriteBackTimer.shouldRetrySyncStatus("未知状态"));
- assertFalse(ApprovalWriteBackTimer.shouldRetrySyncStatus("全部成功"));
- }
- @Test
- public void approvalWriteBackShouldUseStatusFieldForEachApprovalType() {
- WHConf conf = new WHConf();
- conf.setApprovalSyncStatusField("bill-status-field");
- conf.setOtherApprovalSyncStatusField("other-status-field");
- ApprovalWriteBackTimer timer = new ApprovalWriteBackTimer();
- ReflectionTestUtils.setField(timer, "whConf", conf);
- assertEquals("bill-status-field", timer.resolveSyncStatusField("bill"));
- assertEquals("other-status-field", timer.resolveSyncStatusField("other"));
- }
- @Test
- public void approvalWriteBackShouldFailClosedWhenApprovalResultIsMissing() {
- assertEquals(Integer.valueOf(1), ApprovalWriteBackTimer.resolveResultCode("agree"));
- assertEquals(Integer.valueOf(0), ApprovalWriteBackTimer.resolveResultCode("refuse"));
- assertEquals(null, ApprovalWriteBackTimer.resolveResultCode(null));
- assertEquals(null, ApprovalWriteBackTimer.resolveResultCode("unknown"));
- }
- @Test
- public void approvalWriteBackShouldRetryFailedItemsAfterBothFormsFinish() {
- YDClient client = mock(YDClient.class);
- ApprovalWriteBackService service = mock(ApprovalWriteBackService.class);
- ApprovalWriteBackTimer timer = approvalTimer(client, service);
- when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
- .thenReturn(approvalResponse("bill-id"), approvalResponse("other-id"));
- ApprovalWriteBackResult success = ApprovalWriteBackResult.builder().syncStatus("全部成功").build();
- List<String> calls = new ArrayList<>();
- AtomicInteger billCalls = new AtomicInteger();
- when(service.writeBack("bill-id", 1)).thenAnswer(invocation -> {
- calls.add("bill");
- return billCalls.incrementAndGet() == 1
- ? ApprovalWriteBackResult.builder().syncStatus("部分失败").build() : success;
- });
- when(service.writeBack("other-id", 1)).thenAnswer(invocation -> {
- calls.add("other");
- return success;
- });
- timer.scanAndWriteBack();
- assertEquals(Arrays.asList("bill", "other", "bill"), calls);
- verify(service, times(2)).writeBack("bill-id", 1);
- }
- @Test
- public void approvalWriteBackShouldStopAfterOneDeferredRetry() {
- YDClient client = mock(YDClient.class);
- ApprovalWriteBackService service = mock(ApprovalWriteBackService.class);
- ApprovalWriteBackTimer timer = approvalTimer(client, service);
- when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
- .thenReturn(approvalResponse("bill-id"), approvalResponse("other-id"));
- when(service.writeBack("bill-id", 1)).thenThrow(new IllegalStateException("temporary failure"));
- when(service.writeBack("other-id", 1))
- .thenReturn(ApprovalWriteBackResult.builder().syncStatus("全部成功").build());
- timer.scanAndWriteBack();
- verify(service, times(2)).writeBack("bill-id", 1);
- verify(service).writeBack("other-id", 1);
- }
- @Test
- public void approvalWriteBackShouldRetryFailedScanAfterBothForms() {
- YDClient client = mock(YDClient.class);
- ApprovalWriteBackService service = mock(ApprovalWriteBackService.class);
- ApprovalWriteBackTimer timer = approvalTimer(client, service);
- List<String> calls = new ArrayList<>();
- AtomicInteger billScans = new AtomicInteger();
- when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
- .thenAnswer(invocation -> {
- YDParam param = invocation.getArgument(0);
- String formUuid = param.getFormUuid();
- calls.add("scan:" + formUuid);
- if ("bill-form".equals(formUuid) && billScans.incrementAndGet() == 1) {
- throw new IllegalStateException("temporary query failure");
- }
- return approvalResponse("bill-form".equals(formUuid) ? "bill-id" : "other-id");
- });
- ApprovalWriteBackResult success = ApprovalWriteBackResult.builder().syncStatus("全部成功").build();
- when(service.writeBack("bill-id", 1)).thenAnswer(invocation -> {
- calls.add("write:bill");
- return success;
- });
- when(service.writeBack("other-id", 1)).thenAnswer(invocation -> {
- calls.add("write:other");
- return success;
- });
- timer.scanAndWriteBack();
- assertEquals(Arrays.asList("scan:bill-form", "scan:other-form", "write:other",
- "scan:bill-form", "write:bill"), calls);
- verify(service).writeBack("bill-id", 1);
- verify(service).writeBack("other-id", 1);
- }
- @Test
- public void approvalWriteBackShouldRescanMissingFormDataAfterBothForms() {
- YDClient client = mock(YDClient.class);
- ApprovalWriteBackService service = mock(ApprovalWriteBackService.class);
- ApprovalWriteBackTimer timer = approvalTimer(client, service);
- DDR_New<Object> missing = approvalResponse("bill-id");
- ((Map) ((List) missing.getData()).get(0)).remove("data");
- List<String> calls = new ArrayList<>();
- when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
- .thenAnswer(invocation -> {
- YDParam param = invocation.getArgument(0);
- calls.add("scan:" + param.getFormUuid());
- if (calls.size() == 1) return missing;
- return approvalResponse("bill-form".equals(param.getFormUuid()) ? "bill-id" : "other-id");
- });
- ApprovalWriteBackResult success = ApprovalWriteBackResult.builder().syncStatus("全部成功").build();
- when(service.writeBack("bill-id", 1)).thenAnswer(invocation -> {
- calls.add("write:bill");
- return success;
- });
- when(service.writeBack("other-id", 1)).thenAnswer(invocation -> {
- calls.add("write:other");
- return success;
- });
- timer.scanAndWriteBack();
- assertEquals(Arrays.asList("scan:bill-form", "scan:other-form", "write:other",
- "scan:bill-form", "write:bill"), calls);
- }
- @Test
- public void approvalWriteBackShouldRescanUnknownResultWithoutGuessing() {
- YDClient client = mock(YDClient.class);
- ApprovalWriteBackService service = mock(ApprovalWriteBackService.class);
- ApprovalWriteBackTimer timer = approvalTimer(client, service);
- DDR_New<Object> unknown = approvalResponse("bill-id");
- ((Map) ((List) unknown.getData()).get(0)).remove("approvedResult");
- when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
- .thenReturn(unknown, approvalResponse("other-id"), approvalResponse("bill-id"));
- when(service.writeBack(any(String.class), eq(1)))
- .thenReturn(ApprovalWriteBackResult.builder().syncStatus("全部成功").build());
- timer.scanAndWriteBack();
- verify(service, times(1)).writeBack("bill-id", 1);
- verify(service, times(1)).writeBack("other-id", 1);
- }
- @Test
- public void approvalWriteBackShouldSkipRetryWhenStatusAlreadySucceeded() {
- YDClient client = mock(YDClient.class);
- ApprovalWriteBackService service = mock(ApprovalWriteBackService.class);
- ApprovalWriteBackTimer timer = approvalTimer(client, service);
- ((WHConf) ReflectionTestUtils.getField(timer, "whConf"))
- .setApprovalSyncStatusField("selectField_mq58cd5p");
- when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_search_process)))
- .thenReturn(approvalResponse("bill-id"), approvalResponse("other-id"));
- DDR_New<Object> current = new DDR_New<>();
- current.setFormData(Collections.singletonMap("selectField_mq58cd5p", "全部成功"));
- when(client.queryData(any(YDParam.class), eq(YDConf.FORM_QUERY.retrieve_id)))
- .thenReturn(current);
- when(service.writeBack("bill-id", 1))
- .thenReturn(ApprovalWriteBackResult.builder().syncStatus("部分失败").build());
- when(service.writeBack("other-id", 1))
- .thenReturn(ApprovalWriteBackResult.builder().syncStatus("全部成功").build());
- timer.scanAndWriteBack();
- verify(service, times(1)).writeBack("bill-id", 1);
- }
- private static ApprovalWriteBackTimer approvalTimer(YDClient client, ApprovalWriteBackService service) {
- WHConf conf = new WHConf();
- conf.setFormUuidApproval("bill-form");
- conf.setFormUuidOtherApproval("other-form");
- ApprovalWriteBackTimer timer = new ApprovalWriteBackTimer();
- ReflectionTestUtils.setField(timer, "ydClient", client);
- ReflectionTestUtils.setField(timer, "whConf", conf);
- ReflectionTestUtils.setField(timer, "approvalWriteBackService", service);
- ReflectionTestUtils.setField(timer, "backendExceptionLogService", mock(BackendExceptionLogService.class));
- return timer;
- }
- private static DDR_New<Object> approvalResponse(String instanceId) {
- Map<String, Object> formData = new HashMap<>();
- formData.put("selectField_mq58cd5p", "部分失败");
- Map<String, Object> process = new HashMap<>();
- process.put("formInstanceId", instanceId);
- process.put("approvedResult", "agree");
- process.put("data", formData);
- DDR_New<Object> response = new DDR_New<>();
- response.setData(Collections.singletonList(process));
- response.setTotalCount(1);
- return response;
- }
- private static void assertSchedulingSwitch(Class<?> timerClass) {
- ConditionalOnProperty condition = timerClass.getAnnotation(ConditionalOnProperty.class);
- assertEquals("enable", condition.prefix());
- assertEquals(Arrays.asList("scheduling"), Arrays.asList(condition.name()));
- assertEquals("true", condition.havingValue());
- assertEquals(false, condition.matchIfMissing());
- }
- private static Set<String> scheduledCrons(Method method) {
- Set<String> crons = new HashSet<>();
- for (Scheduled scheduled : method.getAnnotationsByType(Scheduled.class)) {
- crons.add(scheduled.cron());
- }
- return crons;
- }
- private static Set<String> setOf(String... values) {
- return new HashSet<>(Arrays.asList(values));
- }
- }
|