|
|
@@ -0,0 +1,154 @@
|
|
|
+package com.zksy.web.controller.cockpit;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.zksy.base.alarm.domain.AlarmData;
|
|
|
+import com.zksy.base.alarm.service.AlarmDataService;
|
|
|
+import com.zksy.base.domain.EquipmentBase;
|
|
|
+import com.zksy.base.mapper.EquipmentBaseMapper;
|
|
|
+import com.zksy.base.warning.mapper.AlarmWarningRelMapper;
|
|
|
+import com.zksy.base.warning.service.EarlyWarningService;
|
|
|
+import com.zksy.cockpit.workorder.BusinessCockpitWorkOrderReadAdapter;
|
|
|
+import com.zksy.cockpit.workorder.CockpitWorkOrderReadSnapshot;
|
|
|
+import com.zksy.cockpit.workorder.CockpitWorkOrderSummary;
|
|
|
+import com.zksy.ops.domain.WorkOrder;
|
|
|
+import com.zksy.ops.service.WorkOrderService;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+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;
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 工单读适配器契约(执行票 12 范围 1)。
|
|
|
+ *
|
|
|
+ * <p>这两组断言针对**真实库暴露过的缺陷**,控制器层测试用 mock 适配器测不到:
|
|
|
+ * <ol>
|
|
|
+ * <li>`alarm_data.id` 在真实库中全部是同一个**全零占位值**(非标识性)——
|
|
|
+ * 若按 `id` 解析报警,`events/ALARM/{code}/work-orders` 恒回 EMPTY,
|
|
|
+ * 即使该报警确实有工单。故必须按 `alarm_no` 解析。</li>
|
|
|
+ * <li>设备维度返回该设备已有**全部**工单,未关联报警者须**显式**标 false,
|
|
|
+ * 且不得伪造 `eventAssociation`(不得拿全零 id 冒充报警标识)。</li>
|
|
|
+ * </ol>
|
|
|
+ */
|
|
|
+class BusinessCockpitWorkOrderReadAdapterTest {
|
|
|
+
|
|
|
+ private static final String DEVICE_CODE = "EQ-001";
|
|
|
+ private static final String EQUIPMENT_ID = "EQID-001";
|
|
|
+ /** 真实库中 alarm_data.id 的取值:全零占位,不具标识性。 */
|
|
|
+ private static final String PLACEHOLDER_ID = "00000000000000000000000000000000";
|
|
|
+
|
|
|
+ private WorkOrderService workOrderService;
|
|
|
+ private AlarmDataService alarmDataService;
|
|
|
+ private EquipmentBaseMapper equipmentBaseMapper;
|
|
|
+ private BusinessCockpitWorkOrderReadAdapter adapter;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() {
|
|
|
+ // MyBatis-Plus 的 LambdaQueryWrapper 需要实体的 lambda 缓存才能解析
|
|
|
+ // method reference(`EquipmentBase::getEquipmentCode`)。纯单测里没有
|
|
|
+ // Spring/MyBatis 上下文,需显式注册一次,否则构建 wrapper 就抛
|
|
|
+ // "can not find lambda cache for this entity"。
|
|
|
+ initLambdaCache(EquipmentBase.class);
|
|
|
+ initLambdaCache(WorkOrder.class);
|
|
|
+ initLambdaCache(AlarmData.class);
|
|
|
+
|
|
|
+ workOrderService = mock(WorkOrderService.class);
|
|
|
+ alarmDataService = mock(AlarmDataService.class);
|
|
|
+ equipmentBaseMapper = mock(EquipmentBaseMapper.class);
|
|
|
+ adapter = new BusinessCockpitWorkOrderReadAdapter(
|
|
|
+ workOrderService,
|
|
|
+ mock(EarlyWarningService.class),
|
|
|
+ mock(AlarmWarningRelMapper.class),
|
|
|
+ alarmDataService,
|
|
|
+ equipmentBaseMapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 注册实体的 lambda 缓存(等价于 MyBatis-Plus 启动时的 TableInfoHelper 初始化)。 */
|
|
|
+ private static void initLambdaCache(Class<?> entityClass) {
|
|
|
+ com.baomidou.mybatisplus.core.metadata.TableInfoHelper.initTableInfo(
|
|
|
+ new org.apache.ibatis.builder.MapperBuilderAssistant(
|
|
|
+ new org.apache.ibatis.session.Configuration(), ""),
|
|
|
+ entityClass);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 报警维度:必须按 alarm_no 解析,不能因主键是全零占位就恒回 EMPTY。 */
|
|
|
+ @Test
|
|
|
+ void alarmEventResolvesByAlarmNoNotByPlaceholderId() {
|
|
|
+ AlarmData alarm = alarm(PLACEHOLDER_ID, "BJ202609291243545660", 279L);
|
|
|
+ when(alarmDataService.list(any(LambdaQueryWrapper.class)))
|
|
|
+ .thenReturn(Collections.singletonList(alarm));
|
|
|
+ when(workOrderService.list(any(LambdaQueryWrapper.class)))
|
|
|
+ .thenReturn(Collections.singletonList(order(279L, "WO-279", 5)));
|
|
|
+
|
|
|
+ CockpitWorkOrderReadSnapshot snapshot =
|
|
|
+ adapter.loadByEvent("ALARM", "BJ202609291243545660");
|
|
|
+
|
|
|
+ assertNotNull(snapshot, "按真实单号必须能解析到报警");
|
|
|
+ assertEquals(1, snapshot.getWorkOrders().size(), "该报警确有工单,不得回 EMPTY");
|
|
|
+ CockpitWorkOrderSummary summary = snapshot.getWorkOrders().get(0);
|
|
|
+ assertEquals("WO-279", summary.getWorkOrderCode());
|
|
|
+ assertTrue(summary.isAssociatedWithCurrentEvent(), "报警维度命中的工单即实际关联");
|
|
|
+ // eventCode 必须回真实单号,不得回全零占位 id
|
|
|
+ assertEquals("BJ202609291243545660", summary.getEventAssociation().getEventCode());
|
|
|
+ assertEquals("BJ202609291243545660", snapshot.getEventCode());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 未关联报警的工单:显式 false 且不得伪造 eventAssociation。 */
|
|
|
+ @Test
|
|
|
+ void deviceScopeMarksUnassociatedOrdersExplicitlyWithoutFabricatingAssociation() {
|
|
|
+ when(equipmentBaseMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(equipment());
|
|
|
+ List<WorkOrder> orders = Arrays.asList(
|
|
|
+ order(279L, "WO-LINKED", 5),
|
|
|
+ order(281L, "WO-ORPHAN", 1));
|
|
|
+ // 只有 279 被报警引用;281 无任何报警关联
|
|
|
+ when(alarmDataService.list(any(LambdaQueryWrapper.class)))
|
|
|
+ .thenReturn(Collections.singletonList(alarm(PLACEHOLDER_ID, "BJ-001", 279L)));
|
|
|
+ when(workOrderService.list(any(LambdaQueryWrapper.class))).thenReturn(orders);
|
|
|
+
|
|
|
+ CockpitWorkOrderReadSnapshot snapshot = adapter.loadByDevice(DEVICE_CODE);
|
|
|
+
|
|
|
+ assertEquals(2, snapshot.getWorkOrders().size(), "设备维度返回已有全部工单");
|
|
|
+ CockpitWorkOrderSummary linked = snapshot.getWorkOrders().get(0);
|
|
|
+ assertTrue(linked.isAssociatedWithCurrentEvent());
|
|
|
+ assertEquals("BJ-001", linked.getEventAssociation().getEventCode());
|
|
|
+
|
|
|
+ CockpitWorkOrderSummary orphan = snapshot.getWorkOrders().get(1);
|
|
|
+ assertFalse(orphan.isAssociatedWithCurrentEvent(), "未关联必须显式 false");
|
|
|
+ assertNull(orphan.getEventAssociation(), "未关联不得伪造关联对象");
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- fixtures ---
|
|
|
+
|
|
|
+ private static AlarmData alarm(String id, String alarmNo, Long workOrderId) {
|
|
|
+ AlarmData alarm = new AlarmData();
|
|
|
+ alarm.setId(id);
|
|
|
+ alarm.setAlarmNo(alarmNo);
|
|
|
+ alarm.setWorkOrderId(workOrderId);
|
|
|
+ return alarm;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static WorkOrder order(Long orderId, String orderNo, int status) {
|
|
|
+ WorkOrder order = new WorkOrder();
|
|
|
+ order.setOrderId(orderId);
|
|
|
+ order.setOrderNo(orderNo);
|
|
|
+ order.setOrderStatus(status);
|
|
|
+ return order;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static EquipmentBase equipment() {
|
|
|
+ EquipmentBase base = new EquipmentBase();
|
|
|
+ base.setEquipmentId(EQUIPMENT_ID);
|
|
|
+ base.setEquipmentCode(DEVICE_CODE);
|
|
|
+ return base;
|
|
|
+ }
|
|
|
+}
|