|
|
@@ -1,435 +0,0 @@
|
|
|
-package com.zksy.web.controller.cockpit;
|
|
|
-
|
|
|
-import com.zksy.cockpit.detail.CockpitEquipmentDetailAggregateService;
|
|
|
-import com.zksy.cockpit.equipment.CockpitEquipmentArchive;
|
|
|
-import com.zksy.cockpit.equipment.CockpitEquipmentArchiveResponse;
|
|
|
-import com.zksy.cockpit.equipment.CockpitEquipmentArchiveService;
|
|
|
-import com.zksy.cockpit.equipment.CockpitEquipmentArchiveSnapshot;
|
|
|
-import com.zksy.cockpit.equipment.CockpitEquipmentImage;
|
|
|
-import com.zksy.cockpit.event.CockpitAlarmWarningRelation;
|
|
|
-import com.zksy.cockpit.event.CockpitEventAlarm;
|
|
|
-import com.zksy.cockpit.event.CockpitEventReadResponse;
|
|
|
-import com.zksy.cockpit.event.CockpitEventReadService;
|
|
|
-import com.zksy.cockpit.event.CockpitEventSnapshot;
|
|
|
-import com.zksy.cockpit.event.CockpitEventWarning;
|
|
|
-import com.zksy.cockpit.foundation.CockpitDeviceNotFoundException;
|
|
|
-import com.zksy.cockpit.foundation.CockpitReadException;
|
|
|
-import com.zksy.cockpit.type.CockpitMetric;
|
|
|
-import com.zksy.cockpit.type.CockpitTypeMetricsResponse;
|
|
|
-import com.zksy.cockpit.type.CockpitTypeMetricsService;
|
|
|
-import com.zksy.cockpit.workorder.CockpitWorkOrderEventAssociation;
|
|
|
-import com.zksy.cockpit.workorder.CockpitWorkOrderReadResponse;
|
|
|
-import com.zksy.cockpit.workorder.CockpitWorkOrderReadService;
|
|
|
-import com.zksy.cockpit.workorder.CockpitWorkOrderStateDictionary;
|
|
|
-import com.zksy.cockpit.workorder.CockpitWorkOrderSummary;
|
|
|
-import com.zksy.cockpit.workorder.CockpitWorkOrderTimelineEntry;
|
|
|
-import com.zksy.cockpit.workorder.CockpitWorkOrderTimelineResponse;
|
|
|
-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.lang.reflect.Method;
|
|
|
-import java.math.BigDecimal;
|
|
|
-import java.time.Clock;
|
|
|
-import java.time.Instant;
|
|
|
-import java.time.ZoneOffset;
|
|
|
-import java.util.Arrays;
|
|
|
-import java.util.Collections;
|
|
|
-
|
|
|
-import static org.hamcrest.Matchers.hasSize;
|
|
|
-import static org.mockito.Mockito.mock;
|
|
|
-import static org.mockito.Mockito.verifyNoInteractions;
|
|
|
-import static org.mockito.Mockito.when;
|
|
|
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
|
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
|
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
|
|
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
-
|
|
|
-class CockpitEquipmentDetailAggregateControllerTest {
|
|
|
- private static final String DEVICE_CODE = "TC-PS-WS-202609020012";
|
|
|
-
|
|
|
- private CockpitEquipmentArchiveService archiveService;
|
|
|
- private CockpitTypeMetricsService metricsService;
|
|
|
- private CockpitEventReadService eventService;
|
|
|
- private CockpitWorkOrderReadService workOrderService;
|
|
|
- private MockMvc mockMvc;
|
|
|
-
|
|
|
- @BeforeEach
|
|
|
- void setUp() {
|
|
|
- archiveService = mock(CockpitEquipmentArchiveService.class);
|
|
|
- metricsService = mock(CockpitTypeMetricsService.class);
|
|
|
- eventService = mock(CockpitEventReadService.class);
|
|
|
- workOrderService = mock(CockpitWorkOrderReadService.class);
|
|
|
- CockpitEquipmentDetailAggregateService service = new CockpitEquipmentDetailAggregateService(
|
|
|
- archiveService,
|
|
|
- metricsService,
|
|
|
- eventService,
|
|
|
- workOrderService,
|
|
|
- Clock.fixed(Instant.parse("2026-09-02T02:00:00Z"), ZoneOffset.UTC));
|
|
|
- mockMvc = MockMvcBuilders
|
|
|
- .standaloneSetup(new CockpitEquipmentDetailAggregateController(service))
|
|
|
- .build();
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void aggregateReturnsIndependentSuccessfulSections() throws Exception {
|
|
|
- stubSuccessfulSections();
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.code").value(200))
|
|
|
- .andExpect(jsonPath("$.data.requestId").value("detail-aggregate-001"))
|
|
|
- .andExpect(jsonPath("$.data.apiVersion").value("v1"))
|
|
|
- .andExpect(jsonPath("$.data.readOnly").value(true))
|
|
|
- .andExpect(jsonPath("$.data.deviceCode").value(DEVICE_CODE))
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("READY"))
|
|
|
- .andExpect(jsonPath("$.data.archive.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.archive.data.deviceCode").value(DEVICE_CODE))
|
|
|
- .andExpect(jsonPath("$.data.archive.data.deviceName").value("污水环境监测设备"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.data.deviceCode").value(DEVICE_CODE))
|
|
|
- .andExpect(jsonPath("$.data.metrics.data.state").value("CURRENT"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.data.metrics", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.warnings.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.data", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.alarms.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.alarms.data", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.alarmWarningRelations.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.alarmWarningRelations.data", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.data", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.timelines.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.data", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.timelines.data[0].timelineEntries", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.latestImage.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.latestImage.data.url").value("https://cockpit.example/latest.jpg"));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void successfulEmptySectionsDoNotBecomeFailures() throws Exception {
|
|
|
- stubEmptySections();
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.archive.state").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.state").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.state").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.alarms.state").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.alarmWarningRelations.state").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.state").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.state").value("EMPTY"))
|
|
|
- .andExpect(jsonPath("$.data.latestImage.state").value("EMPTY"));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void singleArchiveFailurePreservesSuccessfulSiblings() throws Exception {
|
|
|
- when(archiveService.load("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenThrow(new CockpitReadException("archive read failed"));
|
|
|
- stubSuccessfulSectionsExceptArchive();
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("PARTIAL"))
|
|
|
- .andExpect(jsonPath("$.data.archive.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.archive.reason").value("READ_FAILURE"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.alarms.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.latestImage.state").value("FAILED"));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void multipleSectionFailuresAreReportedIndependently() throws Exception {
|
|
|
- stubArchive();
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenThrow(new CockpitReadException("metrics failed"));
|
|
|
- when(eventService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenThrow(new CockpitDeviceNotFoundException("events missing"));
|
|
|
- when(workOrderService.loadByDevice("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenThrow(new CockpitReadException("work orders failed"));
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("PARTIAL"))
|
|
|
- .andExpect(jsonPath("$.data.archive.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.reason").value("READ_FAILURE"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.reason").value("NOT_FOUND"))
|
|
|
- .andExpect(jsonPath("$.data.alarms.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.alarms.reason").value("NOT_FOUND"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.reason").value("READ_FAILURE"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.reason").value("SOURCE_SECTION_FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.latestImage.state").value("SUCCESS"));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void failedTimelineRetainsOtherTimelineAndIndependentSections() throws Exception {
|
|
|
- stubArchive();
|
|
|
- stubEventSection();
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("CURRENT", "READY"));
|
|
|
- CockpitWorkOrderSummary first = workOrderSummary();
|
|
|
- CockpitWorkOrderSummary second = workOrderSummary("WO-2026090200002");
|
|
|
- when(workOrderService.loadByDevice("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(workOrderResponse(DEVICE_CODE, "READY", "READY", Arrays.asList(first, second)));
|
|
|
- when(workOrderService.loadTimeline("detail-aggregate-001", first.getWorkOrderCode()))
|
|
|
- .thenReturn(timelineResponse(first));
|
|
|
- when(workOrderService.loadTimeline("detail-aggregate-001", second.getWorkOrderCode()))
|
|
|
- .thenThrow(new CockpitReadException("timeline read failed"));
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("PARTIAL"))
|
|
|
- .andExpect(jsonPath("$.data.archive.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.data", hasSize(2)))
|
|
|
- .andExpect(jsonPath("$.data.timelines.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.reason").value("TIMELINE_FAILURE"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.data", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.timelines.data[0].workOrderCode").value(first.getWorkOrderCode()));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void foreignTimelineDoesNotLeakIntoAggregate() throws Exception {
|
|
|
- stubArchive();
|
|
|
- stubEventSection();
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("CURRENT", "READY"));
|
|
|
- CockpitWorkOrderSummary first = workOrderSummary();
|
|
|
- CockpitWorkOrderSummary second = workOrderSummary("WO-2026090200002");
|
|
|
- when(workOrderService.loadByDevice("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(workOrderResponse(DEVICE_CODE, "READY", "READY", Arrays.asList(first, second)));
|
|
|
- when(workOrderService.loadTimeline("detail-aggregate-001", first.getWorkOrderCode()))
|
|
|
- .thenReturn(timelineResponse(first));
|
|
|
- when(workOrderService.loadTimeline("detail-aggregate-001", second.getWorkOrderCode()))
|
|
|
- .thenReturn(timelineResponse(first));
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("PARTIAL"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.data", hasSize(2)))
|
|
|
- .andExpect(jsonPath("$.data.timelines.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.data", hasSize(1)))
|
|
|
- .andExpect(jsonPath("$.data.timelines.data[0].workOrderCode").value(first.getWorkOrderCode()));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void timelineWithForeignLogEntryIsRejected() throws Exception {
|
|
|
- stubArchive();
|
|
|
- stubEventSection();
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("CURRENT", "READY"));
|
|
|
- CockpitWorkOrderSummary summary = workOrderSummary();
|
|
|
- when(workOrderService.loadByDevice("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(workOrderResponse(DEVICE_CODE, "READY", "READY", Collections.singletonList(summary)));
|
|
|
- when(workOrderService.loadTimeline("detail-aggregate-001", summary.getWorkOrderCode()))
|
|
|
- .thenReturn(new CockpitWorkOrderTimelineResponse(
|
|
|
- "detail-aggregate-001", "v1", true, summary.getWorkOrderCode(),
|
|
|
- CockpitWorkOrderStateDictionary.states(), summary,
|
|
|
- Collections.singletonList(new CockpitWorkOrderTimelineEntry(
|
|
|
- 1L, "WO-OTHER", 4, "待验收", "提交验收", "处理异常", "系统",
|
|
|
- "2026-09-02T09:00:00+08:00"))));
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("PARTIAL"))
|
|
|
- .andExpect(jsonPath("$.data.archive.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.workOrders.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.timelines.data").doesNotExist());
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void staleMetricsRemainVisibleWithIndependentStaleState() throws Exception {
|
|
|
- stubArchive();
|
|
|
- stubEventSection();
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("STALE", "SAMPLE_OUTDATED"));
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.aggregateState").value("PARTIAL"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.state").value("STALE"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.data.state").value("STALE"))
|
|
|
- .andExpect(jsonPath("$.data.metrics.data.metrics", hasSize(1)));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void deviceCodeMismatchDoesNotLeakForeignSectionData() throws Exception {
|
|
|
- stubArchive();
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("CURRENT", "READY"));
|
|
|
- when(eventService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(eventResponse("CN-GS-202609020099", "EMPTY", "NO_EVENTS",
|
|
|
- Collections.emptyList(), Collections.emptyList(), Collections.emptyList()));
|
|
|
-
|
|
|
- mockMvc.perform(get("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate")
|
|
|
- .header("X-Request-Id", "detail-aggregate-001"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.data.metrics.state").value("SUCCESS"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.reason").value("DEVICE_CODE_MISMATCH"))
|
|
|
- .andExpect(jsonPath("$.data.warnings.data").doesNotExist())
|
|
|
- .andExpect(jsonPath("$.data.alarms.state").value("FAILED"))
|
|
|
- .andExpect(jsonPath("$.data.alarms.reason").value("DEVICE_CODE_MISMATCH"));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void controllerExposesReadOnlyMappedMethodsOnly() throws Exception {
|
|
|
- Method[] methods = CockpitEquipmentDetailAggregateController.class.getDeclaredMethods();
|
|
|
- for (Method method : methods) {
|
|
|
- if (method.isAnnotationPresent(org.springframework.web.bind.annotation.GetMapping.class)) {
|
|
|
- assertTrue(!method.isAnnotationPresent(org.springframework.web.bind.annotation.PostMapping.class)
|
|
|
- && !method.isAnnotationPresent(org.springframework.web.bind.annotation.PutMapping.class)
|
|
|
- && !method.isAnnotationPresent(org.springframework.web.bind.annotation.PatchMapping.class)
|
|
|
- && !method.isAnnotationPresent(org.springframework.web.bind.annotation.DeleteMapping.class));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- stubSuccessfulSections();
|
|
|
- mockMvc.perform(post("/api/cockpit/v1/equipments/" + DEVICE_CODE + "/detail-aggregate"))
|
|
|
- .andExpect(status().isMethodNotAllowed());
|
|
|
- }
|
|
|
-
|
|
|
- private void assertTrue(boolean condition) {
|
|
|
- org.junit.jupiter.api.Assertions.assertTrue(condition);
|
|
|
- }
|
|
|
-
|
|
|
- private void stubSuccessfulSections() {
|
|
|
- stubArchive();
|
|
|
- stubSuccessfulSectionsExceptArchive();
|
|
|
- }
|
|
|
-
|
|
|
- private void stubSuccessfulSectionsExceptArchive() {
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("CURRENT", "READY"));
|
|
|
- stubEventSection();
|
|
|
- stubWorkOrderSections();
|
|
|
- }
|
|
|
-
|
|
|
- private void stubEmptySections() {
|
|
|
- when(archiveService.load("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(new CockpitEquipmentArchiveResponse(
|
|
|
- "detail-aggregate-001", "v1", true, new CockpitEquipmentArchive(null, "WGS84", null)));
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("EMPTY", "NO_METRICS"));
|
|
|
- when(eventService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(eventResponse(DEVICE_CODE, "EMPTY", "NO_EVENTS", Collections.emptyList(), Collections.emptyList(), Collections.emptyList()));
|
|
|
- when(workOrderService.loadByDevice("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(workOrderResponse(DEVICE_CODE, "EMPTY", "NO_WORK_ORDERS", Collections.emptyList()));
|
|
|
- }
|
|
|
-
|
|
|
- private void stubSuccessfulSectionsExceptWorkOrders() {
|
|
|
- stubArchive();
|
|
|
- when(metricsService.loadCurrent("detail-aggregate-001", DEVICE_CODE))
|
|
|
- .thenReturn(metricsResponse("CURRENT", "READY"));
|
|
|
- stubEventSection();
|
|
|
- }
|
|
|
-
|
|
|
- private void stubArchive() {
|
|
|
- CockpitEquipmentImage image = new CockpitEquipmentImage(
|
|
|
- "https://cockpit.example/latest.jpg", "2026-08-30T10:20:00+08:00");
|
|
|
- CockpitEquipmentArchiveSnapshot snapshot = new CockpitEquipmentArchiveSnapshot(
|
|
|
- DEVICE_CODE, "污水环境监测设备", "WS-PRO-2000", "悬浮物/COD/氨氮/电导率/PH",
|
|
|
- "沅陵专用仪表厂", "2025-12-01", "ws_sewage_environment", "污水环境监测设备",
|
|
|
- new BigDecimal("128000"), 10, new BigDecimal("110.141"), new BigDecimal("28.452"),
|
|
|
- "太常片区", "2026-01-10 09:30:00", "张维护", "13800000000", "沅陵县排水管理所", "演示设备",
|
|
|
- Collections.singletonList(image));
|
|
|
- when(archiveService.load("detail-aggregate-001", DEVICE_CODE)).thenReturn(new CockpitEquipmentArchiveResponse(
|
|
|
- "detail-aggregate-001", "v1", true, new CockpitEquipmentArchive(snapshot, "WGS84", image)));
|
|
|
- }
|
|
|
-
|
|
|
- private void stubEventSection() {
|
|
|
- when(eventService.loadCurrent("detail-aggregate-001", DEVICE_CODE)).thenReturn(eventResponse(
|
|
|
- DEVICE_CODE, "READY", "READY",
|
|
|
- Collections.singletonList(warning("WRN-20260902-001")),
|
|
|
- Collections.singletonList(alarm("ALM-20260902-001")),
|
|
|
- Collections.singletonList(new CockpitAlarmWarningRelation(
|
|
|
- "REL-001", "WRN-20260902-001", "ALM-20260902-001", "2026-09-02T10:00:00+08:00"))));
|
|
|
- }
|
|
|
-
|
|
|
- private void stubWorkOrderSections() {
|
|
|
- CockpitWorkOrderSummary summary = workOrderSummary();
|
|
|
- when(workOrderService.loadByDevice("detail-aggregate-001", DEVICE_CODE)).thenReturn(workOrderResponse(
|
|
|
- DEVICE_CODE, "READY", "READY", Collections.singletonList(summary)));
|
|
|
- when(workOrderService.loadTimeline("detail-aggregate-001", "WO-2026090200001")).thenReturn(timelineResponse(summary));
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitTypeMetricsResponse metricsResponse(String state, String stateReason) {
|
|
|
- CockpitMetric metric = new CockpitMetric(
|
|
|
- "COD", "COD", new BigDecimal("18.2"), "mg/L", "WARNING",
|
|
|
- "2026-09-02T09:59:30+08:00", "CURRENT", Collections.emptyList());
|
|
|
- return new CockpitTypeMetricsResponse(
|
|
|
- "detail-aggregate-001", "v1", true, DEVICE_CODE, "ws_sewage_environment", "污水环境监测设备",
|
|
|
- state, stateReason, Collections.singletonList(metric), null, null, "Asia/Shanghai", 1, 0, 1);
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitEventReadResponse eventResponse(
|
|
|
- String deviceCode, String state, String stateReason,
|
|
|
- java.util.List<CockpitEventWarning> warnings,
|
|
|
- java.util.List<CockpitEventAlarm> alarms,
|
|
|
- java.util.List<CockpitAlarmWarningRelation> relations) {
|
|
|
- return new CockpitEventReadResponse(
|
|
|
- "detail-aggregate-001", "v1", true, deviceCode, "ws_sewage_environment", "污水环境监测设备",
|
|
|
- state, stateReason, warnings, alarms, relations, null, null, "Asia/Shanghai", 1, 0, 2);
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitWorkOrderReadResponse workOrderResponse(
|
|
|
- String deviceCode, String state, String stateReason,
|
|
|
- java.util.List<CockpitWorkOrderSummary> workOrders) {
|
|
|
- return new CockpitWorkOrderReadResponse(
|
|
|
- "detail-aggregate-001", "v1", true, deviceCode, null, null,
|
|
|
- state, stateReason, CockpitWorkOrderStateDictionary.states(), workOrders);
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitWorkOrderTimelineResponse timelineResponse(CockpitWorkOrderSummary summary) {
|
|
|
- return new CockpitWorkOrderTimelineResponse(
|
|
|
- "detail-aggregate-001", "v1", true, summary.getWorkOrderCode(),
|
|
|
- CockpitWorkOrderStateDictionary.states(), summary,
|
|
|
- Collections.singletonList(new CockpitWorkOrderTimelineEntry(
|
|
|
- 1L, summary.getWorkOrderCode(), 4, "处理中", "创建工单", "处理异常", "系统",
|
|
|
- "2026-09-02T09:00:00+08:00")));
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitWorkOrderSummary workOrderSummary() {
|
|
|
- return workOrderSummary("WO-2026090200001");
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitWorkOrderSummary workOrderSummary(String code) {
|
|
|
- return new CockpitWorkOrderSummary(
|
|
|
- code, DEVICE_CODE, 1, "处理异常", 4, "处理中",
|
|
|
- "2026-09-02T09:00:00+08:00", "2026-09-02T10:00:00+08:00",
|
|
|
- "2026-09-03T09:00:00+08:00", null,
|
|
|
- new CockpitWorkOrderEventAssociation("ALARM", "ALM-20260902-001", "2026-09-02T10:00:00+08:00"),
|
|
|
- true);
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitEventWarning warning(String warningNo) {
|
|
|
- return new CockpitEventWarning(
|
|
|
- warningNo, "污水COD预警", "WS_ENV", 1, "RED", "PROCESSING",
|
|
|
- "2026-09-02T09:59:30+08:00", new BigDecimal("18.2"), "mg/L",
|
|
|
- DEVICE_CODE, "污水环境监测设备", "太常片区");
|
|
|
- }
|
|
|
-
|
|
|
- private CockpitEventAlarm alarm(String alarmId) {
|
|
|
- return new CockpitEventAlarm(
|
|
|
- alarmId, "WS_ENV_HIGH", "污水COD报警", "WS_ENV", 1, "RED", "UNHANDLED",
|
|
|
- "2026-09-02T10:00:00+08:00", new BigDecimal("42"), new BigDecimal("40"),
|
|
|
- new BigDecimal("50"), "mg/L", DEVICE_CODE, "污水环境监测设备", "太常片区");
|
|
|
- }
|
|
|
-}
|