package com.battery.business.web; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.List; import java.util.Map; import java.util.ArrayList; import java.util.LinkedHashMap; import com.alibaba.fastjson2.JSON; import com.battery.business.domain.TTest; import com.battery.business.mapper.TTestMapper; import com.battery.business.service.impl.TestConsoleDataServiceImpl; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import com.ruoyi.common.exception.ServiceException; import org.springframework.test.util.ReflectionTestUtils; /** 从控制台提交入口验证单页导入,数据库使用边界替身。 */ class ConsoleSingleSheetImportTest { private TTestMapper mapper; private TTestController controller; private final List> saved = new ArrayList<>(); @BeforeEach void setUp() { mapper = mock(TTestMapper.class); TTest test = new TTest(); test.setTestStatus("COMPLETED"); when(mapper.selectTestByIdForUpdate(1L)).thenReturn(test); when(mapper.selectImportBinding(1L, "BAT-1")).thenReturn(Map.of( "test_battery_id", 4L, "binding_id", 5L, "adapter_code", "adapter-a", "devtype", 27, "devid", 1, "subdevid", 1, "chlid", 1)); when(mapper.selectImportBinding(1L, "BAT-2")).thenReturn(Map.of( "test_battery_id", 6L, "binding_id", 7L, "adapter_code", "adapter-a", "devtype", 27, "devid", 1, "subdevid", 1, "chlid", 2)); when(mapper.selectImportDataRecords(eq(1L), anyString())).thenAnswer(call -> new ArrayList<>(saved)); when(mapper.insertImportedDataRecord(anyString(), anyMap())).thenAnswer(call -> { saved.add(new LinkedHashMap<>(call.getArgument(1))); return 1; }); controller = new TTestController() { @Override public Long getUserId() { return 1L; } @Override public String getUsername() { return "tester"; } }; ReflectionTestUtils.setField(controller, "consoleDataService", new TestConsoleDataServiceImpl(mapper)); } @Test void identicalRowsInOneSheetAreSkipped() { var response = submit("REALTIME", List.of(row(), row())); assertEquals(1, response.get("importedCount")); assertEquals(1, response.get("skippedCount")); } @Test void allThreeTypesSkipRetryButRejectChangedContent() { for (String type : List.of("REALTIME", "STEP", "CYCLE")) { saved.clear(); var row = row(); row.put("deviceTimeRaw", "2023-05-08 11:42:11"); row.put("adapterTimezoneId", "Asia/Hong_Kong"); row.put("capacity", 8.5122); row.put("dischargeCapacity", 8.5122); assertEquals(1, submit(type, List.of(row)).get("importedCount")); row.put("sourceRow", 19); row.put("capacity", new java.math.BigDecimal("8.512200")); var repeated = submit(type, List.of(row)); assertEquals(0, repeated.get("importedCount")); assertEquals(1, repeated.get("skippedCount")); assertNull(repeated.get("actionId")); row.put("cycleNo", 2); var failure = assertThrows(ServiceException.class, () -> submit(type, List.of(row))); assertTrue(failure.getMessage().contains("第19行,列「记录编号」")); assertEquals(1, saved.size()); } } @Test void sameRecordNumberForDifferentBatteriesHasIndependentIdentity() { var other = row(); other.put("batteryNo", "BAT-2"); assertEquals(2, submit("REALTIME", List.of(row(), other)).get("importedCount")); assertEquals("adapter-a:27:1:1:1", saved.get(0).get("source_identity")); assertEquals("adapter-a:27:1:1:2", saved.get(1).get("source_identity")); } @Test void pollingSlotIsNotPartOfImportedDeviceContent() { submit("REALTIME", List.of(row())); saved.get(0).put("capture_slot_utc", new java.util.Date(1683517380000L)); assertEquals(1, submit("REALTIME", List.of(row())).get("skippedCount")); } @Test void anyInvalidRowLeavesWholeSheetAndRevisionsUnwritten() { for (Map.Entry invalid : Map.of("cycleNo", 0, "recordedAt", "2023-05-08", "voltage", "NaN", "batteryNo", "OTHER").entrySet()) { var bad = row(); bad.put(invalid.getKey(), invalid.getValue()); bad.put("sourceRow", 23); var failure = assertThrows(ServiceException.class, () -> submit("REALTIME", List.of(row(), bad))); assertTrue(failure.getMessage().contains("第23行,列「")); assertTrue(saved.isEmpty()); } verify(mapper, never()).insertDataRevisionAction(any()); } @Test void conflictAfterValidRowDoesNotWriteValidPrefixOrAudit() { submit("REALTIME", List.of(row())); clearInvocations(mapper); var next = row(); next.put("recordNo", "18"); next.put("recordedAt", 1683517391000L); var conflict = row(); conflict.put("voltage", 9); assertThrows(ServiceException.class, () -> submit("REALTIME", List.of(next, conflict))); assertEquals(1, saved.size()); verify(mapper, never()).insertDataRevisionAction(any()); var sameSecond = row(); sameSecond.put("recordNo", "19"); sameSecond.put("sequenceId", 19); assertEquals(1, submit("REALTIME", List.of(sameSecond)).get("importedCount")); } private Map row() { return new LinkedHashMap<>(Map.of("recordNo", "17", "batteryNo", "BAT-1", "recordedAt", 1683517331000L, "cycleNo", 1, "stepNo", 1, "stepName", "恒流放电", "sequenceId", 17, "voltage", 1.3349)); } private com.ruoyi.common.core.domain.AjaxResult submit(String type, List> rows) { return controller.dataAction(1L, new TTestController.ConsoleDataActionRequest(type, "IMPORT", "单页导入", "[]", JSON.toJSONString(rows), "开发测试", List.of())); } }