From 2cc61346cc0a3e5338aa8957f79f4c99c204f339 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期一, 27 四月 2026 14:02:58 +0800
Subject: [PATCH] fix: preserve unlimited station limits in auto tune apply

---
 src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java |  101 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 101 insertions(+), 0 deletions(-)

diff --git a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
index 29a4323..864c535 100644
--- a/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
+++ b/src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
@@ -11,10 +11,12 @@
 import com.zy.asrs.entity.BasDualCrnp;
 import com.zy.asrs.entity.BasStation;
 import com.zy.asrs.entity.StationFlowCapacity;
+import com.zy.asrs.entity.WrkMast;
 import com.zy.asrs.service.BasCrnpService;
 import com.zy.asrs.service.BasDualCrnpService;
 import com.zy.asrs.service.BasStationService;
 import com.zy.asrs.service.StationFlowCapacityService;
+import com.zy.asrs.service.WrkMastService;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.enums.RedisKeyType;
 import com.zy.system.entity.Config;
@@ -79,6 +81,8 @@
     @Mock
     private StationFlowCapacityService stationFlowCapacityService;
     @Mock
+    private WrkMastService wrkMastService;
+    @Mock
     private RedisUtil redisUtil;
     private RecordingTransactionManager transactionManager;
 
@@ -93,6 +97,7 @@
         ReflectionTestUtils.setField(service, "basCrnpService", basCrnpService);
         ReflectionTestUtils.setField(service, "basDualCrnpService", basDualCrnpService);
         ReflectionTestUtils.setField(service, "stationFlowCapacityService", stationFlowCapacityService);
+        ReflectionTestUtils.setField(service, "wrkMastService", wrkMastService);
         ReflectionTestUtils.setField(service, "transactionManager", transactionManager);
         ReflectionTestUtils.setField(service, "redisUtil", redisUtil);
 
@@ -109,6 +114,7 @@
         });
         when(aiAutoTuneChangeService.saveBatch(any(Collection.class))).thenReturn(true);
         when(aiAutoTuneChangeService.list(any(Wrapper.class))).thenReturn(Collections.emptyList());
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(0L);
         when(configService.getConfigValue(eq("aiAutoTuneIntervalMinutes"), any())).thenReturn("10");
         when(configService.saveConfigValue(any(), any())).thenReturn(true);
         when(basStationService.update(any(Wrapper.class))).thenReturn(true);
@@ -206,6 +212,44 @@
     }
 
     @Test
+    void rejectStationOutTaskLimitNullUnlimitedCurrentValue() {
+        when(basStationService.getById(101)).thenReturn(station(101, null));
+
+        service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
+
+        List<AiAutoTuneChange> changes = savedChanges();
+        assertEquals("rejected", changes.get(0).getResultStatus());
+        assertTrue(changes.get(0).getRejectReason().contains("闇�瑕佷汉宸ュ厛鍒濆鍖栦负鏈夐檺鍊�"));
+        verify(stationFlowCapacityService, never()).getOne(any(Wrapper.class));
+    }
+
+    @Test
+    void rejectStationOutTaskLimitNegativeUnlimitedCurrentValue() {
+        when(basStationService.getById(101)).thenReturn(station(101, -1));
+
+        service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
+
+        List<AiAutoTuneChange> changes = savedChanges();
+        assertEquals("rejected", changes.get(0).getResultStatus());
+        assertTrue(changes.get(0).getRejectReason().contains("闇�瑕佷汉宸ュ厛鍒濆鍖栦负鏈夐檺鍊�"));
+        verify(stationFlowCapacityService, never()).getOne(any(Wrapper.class));
+    }
+
+    @Test
+    void allowStationOutTaskLimitZeroToOneAsFiniteStep() {
+        when(basStationService.getById(101)).thenReturn(station(101, 0));
+        when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 1));
+
+        AutoTuneApplyResult result = service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
+
+        List<AiAutoTuneChange> changes = savedChanges();
+        assertTrue(result.getSuccess());
+        assertEquals("dry_run", changes.get(0).getResultStatus());
+        assertEquals("0", changes.get(0).getOldValue());
+        assertEquals("1", changes.get(0).getRequestedValue());
+    }
+
+    @Test
     void rejectCooldownHit() {
         when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
         AiAutoTuneChange cooldownChange = new AiAutoTuneChange();
@@ -252,6 +296,46 @@
         verify(basStationService, never()).update(any(Wrapper.class));
         verify(basCrnpService, never()).update(any(Wrapper.class));
         verify(basDualCrnpService, never()).update(any(Wrapper.class));
+    }
+
+    @Test
+    void applyJobRecordsActiveTasksWhenCountIsPositive() {
+        when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L);
+
+        service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+        AiAutoTuneJob updatedJob = updatedJob();
+        assertEquals(1, updatedJob.getHasActiveTasks());
+
+        ArgumentCaptor<Wrapper<WrkMast>> captor = ArgumentCaptor.forClass(Wrapper.class);
+        verify(wrkMastService).count(captor.capture());
+        String sqlSegment = captor.getValue().getSqlSegment();
+        assertTrue(sqlSegment.contains("NOT IN"));
+        assertTrue(sqlSegment.contains("OR"));
+        assertTrue(sqlSegment.contains("wrk_sts IS NULL"));
+    }
+
+    @Test
+    void applyJobRecordsNoActiveTasksWhenCountIsZero() {
+        when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(0L);
+
+        service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+        AiAutoTuneJob updatedJob = updatedJob();
+        assertEquals(0, updatedJob.getHasActiveTasks());
+    }
+
+    @Test
+    void applyJobFallsBackToNoActiveTasksWhenCountFails() {
+        when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
+        when(wrkMastService.count(any(Wrapper.class))).thenThrow(new IllegalStateException("count failed"));
+
+        service.apply(request(true, command("sys_config", null, "conveyorStationTaskLimit", "15")));
+
+        AiAutoTuneJob updatedJob = updatedJob();
+        assertEquals(0, updatedJob.getHasActiveTasks());
     }
 
     @Test
@@ -493,6 +577,23 @@
     }
 
     @Test
+    void rollbackJobRecordsActiveTasksWhenCountIsPositive() {
+        AiAutoTuneJob latestRealJob = job(10L, "manual", "success");
+        AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15");
+        when(wrkMastService.count(any(Wrapper.class))).thenReturn(1L);
+        when(aiAutoTuneChangeService.list(any(Wrapper.class)))
+                .thenReturn(List.of(configChange))
+                .thenReturn(List.of(configChange));
+        when(aiAutoTuneJobService.getById(10L)).thenReturn(latestRealJob);
+        when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "15"));
+
+        service.rollbackLastSuccessfulJob("manual rollback");
+
+        AiAutoTuneJob updatedJob = updatedJob();
+        assertEquals(1, updatedJob.getHasActiveTasks());
+    }
+
+    @Test
     void rollbackCacheRefreshFailureDoesNotAppendFailedAuditOrJob() {
         AiAutoTuneJob latestRealJob = job(10L, "manual", "success");
         AiAutoTuneChange configChange = successChange(10L, "sys_config", "", "conveyorStationTaskLimit", "10", "15");

--
Gitblit v1.9.1