package com.zksy.web.controller.cockpit;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
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.CockpitWorkOrderReadService;
import com.zksy.ops.domain.WorkOrder;
import com.zksy.ops.service.WorkOrderService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* 全量工单列表端点(票 65):报警效率面板的「当前工单列表」。
*
*
锁四件事,都是**会真的画错界面**的那几件:
*
* - 不按设备/事件筛——全量口径(查询条件里不得出现 device_id);
* - 总数取独立 count,不由本页条数反推(票 61 纪律);
* - 非法 page/pageSize/stateCode 被夹到合法区间并**回显实际生效值**;
* - 状态字典与每张工单的关联标记照常给出(卡片要按同一份词表上色)。
*
*/
class CockpitWorkOrderListContractTest {
private static final String BASE = "/api/cockpit/v1";
private static final String ORDER_CODE = "WO202610101511303516";
private static final String DEVICE_CODE = "21119740";
private static final String ALARM_ID = "cd57be18d02f7d3390fb98539b93";
private static final String CREATE_TIME = "2026-10-10T15:11:30+08:00";
private WorkOrderService workOrderService;
private AlarmDataService alarmDataService;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
for (Class> entity : Arrays.asList(EquipmentBase.class, WorkOrder.class, AlarmData.class)) {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), ""), entity);
}
workOrderService = mock(WorkOrderService.class);
alarmDataService = mock(AlarmDataService.class);
EquipmentBaseMapper equipmentMapper = mock(EquipmentBaseMapper.class);
EquipmentBase equipment = new EquipmentBase();
equipment.setEquipmentId("equipment-id");
equipment.setEquipmentCode(DEVICE_CODE);
when(equipmentMapper.selectById("equipment-id")).thenReturn(equipment);
WorkOrder order = new WorkOrder();
order.setOrderId(279L);
order.setOrderNo(ORDER_CODE);
order.setDeviceId("equipment-id");
order.setOrderLevel(2);
order.setOrderDesc("监测报警,请现场核实处理");
order.setOrderStatus(0);
order.setCreateTime(Date.from(Instant.parse("2026-10-10T07:11:30Z")));
order.setUpdateTime(Date.from(Instant.parse("2026-10-10T07:11:30Z")));
when(workOrderService.count(any(LambdaQueryWrapper.class))).thenReturn(7L);
when(workOrderService.list(any(LambdaQueryWrapper.class)))
.thenReturn(Collections.singletonList(order));
AlarmData alarm = new AlarmData();
alarm.setId(ALARM_ID);
alarm.setWorkOrderId(279L);
when(alarmDataService.list(any(LambdaQueryWrapper.class)))
.thenReturn(Collections.singletonList(alarm));
BusinessCockpitWorkOrderReadAdapter adapter = new BusinessCockpitWorkOrderReadAdapter(
workOrderService, mock(EarlyWarningService.class), mock(AlarmWarningRelMapper.class),
alarmDataService, equipmentMapper);
CockpitWorkOrderReadService service = new CockpitWorkOrderReadService(
adapter, Clock.fixed(Instant.parse("2026-10-10T07:11:30Z"), ZoneOffset.UTC));
mockMvc = MockMvcBuilders.standaloneSetup(new CockpitWorkOrderReadController(service)).build();
}
@Test
void listIsGlobalNotScopedToDeviceOrEvent() throws Exception {
mockMvc.perform(get(BASE + "/work-orders"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.state").value("READY"))
.andExpect(jsonPath("$.data.workOrders[0].workOrderCode").value(ORDER_CODE))
// 总数是 count 的结果(7),不是本页 1 条
.andExpect(jsonPath("$.data.pagination.total").value(7))
.andExpect(jsonPath("$.data.pagination.page").value(1))
.andExpect(jsonPath("$.data.pagination.pageSize").value(10))
.andExpect(jsonPath("$.data.pagination.totalPages").value(1))
// 状态字典必须给(卡片按同一词表上色)
.andExpect(jsonPath("$.data.stateDictionary[0].code").value(0));
}
@Test
void pageAndSizeAreClampedAndEchoedBack() throws Exception {
mockMvc.perform(get(BASE + "/work-orders").param("page", "0").param("pageSize", "9999"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.pagination.page").value(1))
.andExpect(jsonPath("$.data.pagination.pageSize").value(100));
// 夹取后才下到适配器:offset 必须由生效值算出,不能让非法值进 SQL
verify(workOrderService, org.mockito.Mockito.atLeastOnce())
.list(any(LambdaQueryWrapper.class));
}
@Test
void invalidStateCodeFallsBackToNoFilter() throws Exception {
mockMvc.perform(get(BASE + "/work-orders").param("stateCode", "99"))
.andExpect(status().isOk());
// 越界状态码当成"不筛",仍然返回数据(而不是空列表假读数)
verify(workOrderService, org.mockito.Mockito.atLeastOnce()).count(any(LambdaQueryWrapper.class));
}
@Test
void validStateCodeIsApplied() throws Exception {
mockMvc.perform(get(BASE + "/work-orders").param("stateCode", "3"))
.andExpect(status().isOk());
verify(workOrderService).count(any(LambdaQueryWrapper.class));
}
}