| | |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | /** 使用生产 Mapper 和 Spring 事务验证导入;内存数据库不连接共享开发库。 */ |
| | | class ConsoleSingleSheetImportDatabaseTest |
| | | public class ConsoleSingleSheetImportDatabaseTest |
| | | { |
| | | private java.sql.Connection keepAlive; |
| | | private TTestController controller; |
| | | private TTestMapper mapper; |
| | | private ITestConsoleDataService service; |
| | | private JdbcTemplate jdbc; |
| | | |
| | | @BeforeEach void setUp() throws Exception |
| | | { |
| | | var source = new UnpooledDataSource("org.h2.Driver", "jdbc:h2:mem:" + UUID.randomUUID() + ";MODE=MySQL;DATABASE_TO_LOWER=TRUE", "sa", ""); |
| | | keepAlive = source.getConnection(); |
| | | var jdbc = new JdbcTemplate(source); |
| | | jdbc = new JdbcTemplate(source); |
| | | // H2 无 MySQL JSON_LENGTH;仅在测试中适配计数,真实 SQL 另在 MySQL 验证。 |
| | | jdbc.execute("create alias JSON_LENGTH for 'com.battery.business.web.ConsoleSingleSheetImportDatabaseTest.jsonLength'"); |
| | | jdbc.execute("create table sys_user(user_id bigint, nick_name varchar(64), del_flag varchar(1))"); |
| | | jdbc.execute("create table t_test_type(test_type_id bigint, test_type_name varchar(64), del_flag int)"); |
| | | jdbc.execute("create table t_test(test_id bigint primary key, test_name varchar(100), creator_id bigint, principal_id bigint, test_type_id bigint, test_purpose varchar(100), test_status varchar(20), reception_status varchar(20), monitor_timeout_minutes int, started_at_utc timestamp, reception_boundary_at_utc timestamp, ended_at_utc timestamp, del_flag int default 0, create_by varchar(64), create_time timestamp, update_by varchar(64), update_time timestamp, remark varchar(100), logic_step_mappings_json varchar(100), console_detail_json varchar(100))"); |
| | |
| | | jdbc.execute("create table t_detection_cabinet(cabinet_id bigint, adapter_code varchar(64))"); |
| | | jdbc.execute("insert into t_detection_cabinet values(1,'adapter-a')"); |
| | | jdbc.execute("create table t_test_data_revision_action(action_id bigint auto_increment primary key, test_id bigint, data_type varchar(20), operation_type varchar(20), scope_summary varchar(200), identity_summary json, before_content_json json, changes_summary json, reason varchar(200), operator_id bigint, operator_name varchar(64), created_at_utc timestamp)"); |
| | | jdbc.execute("alter table t_test_data_revision_action add constraint chk_data_revision_type check (data_type in ('REALTIME','STEP','CYCLE'))"); |
| | | org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(keepAlive, |
| | | new ClassPathResource("db/migration-final/V9__workbook_import_revision.sql")); |
| | | String common = "test_id bigint not null, test_battery_id bigint not null, binding_id bigint not null, adapter_code varchar(64) not null, source_identity varchar(256) not null, source_record_id varchar(128), server_received_at_utc timestamp(3), cycle_no int, content_sha256 binary(32) not null, extension_measurements_json varchar(2000)"; |
| | | String metrics = "step_no int, step_name varchar(191), step_type varchar(64), voltage_v decimal(30,12), current_a decimal(30,12), capacity_ah decimal(30,12), energy_wh decimal(30,12) check (energy_wh <> 999)"; |
| | | jdbc.execute("create table t_test_realtime_record(record_id bigint auto_increment primary key," + common + "," + metrics + ", cabinet_devtype int, cabinet_devid int, cabinet_subdevid int, cabinet_chlid int, cabinet_sequence_id bigint, cabinet_device_time_utc timestamp(3), cabinet_device_time_raw varchar(64), cabinet_adapter_timezone_id varchar(64), capture_slot_utc timestamp(3) not null, temperature decimal(30,12), positive_potential decimal(30,12), negative_potential decimal(30,12), collector_id bigint, collector_channel_code varchar(100), collector_device_time_utc timestamp(3), unique(test_id,test_battery_id,capture_slot_utc))"); |
| | |
| | | new ClassPathResource("db/migration-final/V8__import_records_without_polling_slots.sql")); |
| | | String deviceTime = "dedupe_key varchar(512) not null, device_time_utc timestamp(3) not null, device_time_raw varchar(64) not null, adapter_timezone_id varchar(64) not null, unique(test_battery_id,source_identity,dedupe_key)"; |
| | | jdbc.execute("create table t_test_step_record(step_record_id bigint auto_increment primary key," + common + "," + metrics + "," + deviceTime + ")"); |
| | | jdbc.execute("create table t_test_cycle_record(cycle_record_id bigint auto_increment primary key," + common + ",charge_capacity_ah decimal(30,12),discharge_capacity_ah decimal(30,12),charge_energy_wh decimal(30,12),discharge_energy_wh decimal(30,12),efficiency decimal(30,12)," + deviceTime + ")"); |
| | | jdbc.execute("create table t_test_cycle_record(cycle_record_id bigint auto_increment primary key," + common + ",source_kind varchar(16) default 'DEVICE',charge_capacity_ah decimal(30,12),discharge_capacity_ah decimal(30,12),charge_energy_wh decimal(30,12),discharge_energy_wh decimal(30,12),efficiency decimal(30,12)," + deviceTime + ")"); |
| | | var factory = new SqlSessionFactoryBean(); factory.setDataSource(source); |
| | | factory.setTypeAliasesPackage("com.battery.business.domain"); |
| | | factory.setMapperLocations(new ClassPathResource("mapper/battery/TTestMapper.xml")); |
| | |
| | | } |
| | | |
| | | @AfterEach void closeDatabase() throws Exception { if (keepAlive != null) keepAlive.close(); } |
| | | |
| | | /** 模拟 JSON 数组长度;H2 的 JSON 参数可能多包一层字符串。 */ |
| | | public static Integer jsonLength(String value) |
| | | { |
| | | if (value == null) return null; |
| | | Object parsed = JSON.parse(value); |
| | | if (parsed instanceof String text) parsed = JSON.parse(text); |
| | | return parsed instanceof List<?> rows ? rows.size() : 1; |
| | | } |
| | | |
| | | @Test void threeTypesPersistActualBindingsTimesAndNumbersAndSkipRetries() |
| | | { |
| | |
| | | assertEquals(3, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |
| | | |
| | | /** |
| | | * 循环记录的来源类别随记录落库并经查询交回:导入的循环来自授权文件, |
| | | * 由工步层聚合的循环标注为聚合,使界面与导出能区分"推导值"与"设备或文件给的值"。 |
| | | */ |
| | | @Test void cycleSourceKindDistinguishesImportedAndAggregatedRecords() |
| | | { |
| | | assertEquals(1, submit("CYCLE", List.of(row())).get("importedCount")); |
| | | assertEquals("IMPORTED", mapper.selectPersistedFinalCycleRecords(1L).get(0).get("sourcekind")); |
| | | |
| | | var aggregated = new com.battery.business.domain.TTestCycleRecord(); |
| | | aggregated.setTestId(1L); aggregated.setTestBatteryId(4L); aggregated.setBindingId(5L); |
| | | aggregated.setAdapterCode("adapter-a"); aggregated.setSourceIdentity("adapter-a:27:1:1:2"); |
| | | aggregated.setSourceRecordId("neware:442:cycle:3"); |
| | | aggregated.setSourceKind(com.battery.device.api.TestDataObservationAdapter.CycleRecord.SOURCE_AGGREGATED); |
| | | aggregated.setDedupeKey("record:neware:442:cycle:3"); |
| | | aggregated.setAdapterTimezoneId("Asia/Hong_Kong"); |
| | | aggregated.setDeviceTimeUtc(new java.util.Date(1683517331000L)); |
| | | aggregated.setDeviceTimeRaw("2023-05-08 11:42:11"); |
| | | aggregated.setServerReceivedAtUtc(new java.util.Date(1683517331000L)); |
| | | aggregated.setCycleNo(3); |
| | | aggregated.setChargeCapacityAh(new java.math.BigDecimal("2.375")); |
| | | aggregated.setDischargeCapacityAh(new java.math.BigDecimal("1.383")); |
| | | aggregated.setEfficiency(null); |
| | | aggregated.setContentSha256(new byte[32]); |
| | | assertEquals(1, mapper.insertCycleRecord(aggregated)); |
| | | |
| | | var rows = mapper.selectPersistedFinalCycleRecords(1L); |
| | | assertEquals(2, rows.size()); |
| | | var stored = rows.stream().filter(item -> "3".equals(String.valueOf(item.get("cycleno")))).findFirst().orElseThrow(); |
| | | assertEquals("AGGREGATED", stored.get("sourcekind")); |
| | | // 没有可用充电容量就没有效率:空值不是零。 |
| | | assertNull(stored.get("efficiency")); |
| | | } |
| | | |
| | | @Test void databaseFailureRollsBackEarlierRowsAndTheRevision() |
| | | { |
| | | var failing = row(); failing.put("recordNo", "18"); failing.put("recordedAt", 1683517391000L); failing.put("energy", 999); |
| | | assertThrows(RuntimeException.class, () -> submit("REALTIME", List.of(row(), failing))); |
| | | assertTrue(mapper.selectImportDataRecords(1L, "REALTIME").isEmpty()); |
| | | assertTrue(service.selectActions(new TTestDataRevisionAction()).isEmpty()); |
| | | } |
| | | |
| | | @Test void importAndDeleteDetailsReturnOnlySummaryWhileAuditRemainsStored() throws Exception |
| | | { |
| | | for (String operation : List.of("IMPORT", "DELETE")) |
| | | { |
| | | var action = new TTestDataRevisionAction(); |
| | | action.setTestId(1L); action.setDataType("REALTIME"); action.setOperationType(operation); |
| | | action.setScopeSummary("批量8000条"); action.setReason("摘要查询验证"); |
| | | action.setIdentitySummary(JSON.toJSONString(java.util.stream.IntStream.range(0, 8000).mapToObj(i -> "realtime:" + i).toList())); |
| | | action.setChangesSummary(JSON.toJSONString(java.util.Collections.nCopies(8000, Map.of("voltage", 1.5)))); |
| | | action.setBeforeContentJson(action.getChangesSummary()); |
| | | mapper.insertDataRevisionAction(action); |
| | | var summary = service.selectActionById(action.getActionId()); |
| | | assertNull(summary.getIdentitySummary(), "详情查询不得将批量身份 JSON 加载到服务层"); |
| | | assertNull(summary.getChangesSummary(), "详情查询不得将批量变化 JSON 加载到服务层"); |
| | | assertNull(summary.getBeforeContentJson(), "详情查询不得加载完整审计快照"); |
| | | var details = new TTestDataRevisionController(); |
| | | ReflectionTestUtils.setField(details, "service", service); |
| | | var json = new com.fasterxml.jackson.databind.ObjectMapper(); |
| | | String response = json.writeValueAsString(details.detail(action.getActionId())); |
| | | assertEquals(8000, json.readTree(response).get("data").get("affectedRecordCount").intValue()); |
| | | assertTrue(response.length() < 1500, "摘要响应不能随批量明细膨胀"); |
| | | // 展示裁剪只发生在 SELECT;数据库审计原文仍完整保留。 |
| | | assertTrue(jdbc.queryForObject("select length(cast(changes_summary as varchar)) from t_test_data_revision_action where action_id=?", Integer.class, action.getActionId()) > 100000); |
| | | } |
| | | var list = service.selectActions(new TTestDataRevisionAction()); |
| | | assertEquals(2, list.size()); |
| | | assertTrue(list.stream().allMatch(row -> row.getIdentitySummary() == null && row.getChangesSummary() == null && row.getBeforeContentJson() == null)); |
| | | } |
| | | |
| | | @Test void stepMedianVoltagesPersistAndRemainIndependentOfLegacyPeaks() |
| | | { |
| | | var imported = row(); |
| | | imported.put("chargeMedianVoltage", 0); |
| | | imported.put("dischargeMedianVoltage", 1.1709); |
| | | submit("STEP", List.of(imported)); |
| | | var stored = mapper.selectImportDataRecords(1L, "STEP").get(0); |
| | | var extension = JSON.parseObject(stored.get("extension_measurements_json").toString()); |
| | | assertNotNull(extension.getBigDecimal("chargeMedianVoltage")); |
| | | assertEquals(0, extension.getBigDecimal("chargeMedianVoltage").signum()); |
| | | assertEquals(new java.math.BigDecimal("1.1709"), extension.getBigDecimal("dischargeMedianVoltage")); |
| | | assertEquals(1, submit("STEP", List.of(imported)).get("skippedCount")); |
| | | |
| | | // 旧峰谷值仍是独立事实;不能在查询时作为缺失中值的回退来源。 |
| | | var queryMapper = org.mockito.Mockito.mock(TTestMapper.class); |
| | | var query = new com.battery.business.service.impl.TestDataQueryServiceImpl(queryMapper, |
| | | org.mockito.Mockito.mock(com.battery.business.service.ITTestService.class)); |
| | | var current = new LinkedHashMap<String, Object>(Map.of("extensionMeasurementsJson", extension.toJSONString())); |
| | | var legacy = new LinkedHashMap<String, Object>(Map.of("extensionMeasurementsJson", "{\"chargePeakVoltage\":1.8,\"dischargeValleyVoltage\":0.8}")); |
| | | // 设备工步层的四组容量能量是独立事实:查询要交出它们,供方向未确认的工步核对。 |
| | | var deviceStep = new LinkedHashMap<String, Object>(Map.of("extensionMeasurementsJson", |
| | | "{\"chargeCapacity\":13.888888888889,\"dischargeCapacity\":0,\"chargeEnergy\":25,\"dischargeEnergy\":0}")); |
| | | org.mockito.Mockito.when(queryMapper.selectPersistedFinalStepRecords(1L)).thenReturn(List.of(current, legacy, deviceStep)); |
| | | var result = query.steps(1L); |
| | | assertEquals(new java.math.BigDecimal("1.1709"), result.get(0).get("dischargeMedianVoltage")); |
| | | assertEquals(0.0, ((Number) result.get(0).get("chargeMedianVoltage")).doubleValue()); |
| | | assertNull(result.get(1).get("chargeMedianVoltage")); |
| | | assertNull(result.get(1).get("dischargeMedianVoltage")); |
| | | assertEquals(0, new java.math.BigDecimal("13.888888888889").compareTo( |
| | | new java.math.BigDecimal(String.valueOf(result.get(2).get("chargeCapacity"))))); |
| | | assertEquals(0, java.math.BigDecimal.ZERO.compareTo( |
| | | new java.math.BigDecimal(String.valueOf(result.get(2).get("dischargeCapacity"))))); |
| | | assertEquals(0, new java.math.BigDecimal("25.0").compareTo( |
| | | new java.math.BigDecimal(String.valueOf(result.get(2).get("chargeEnergy"))))); |
| | | assertEquals(0, java.math.BigDecimal.ZERO.compareTo( |
| | | new java.math.BigDecimal(String.valueOf(result.get(2).get("dischargeEnergy"))))); |
| | | assertNull(result.get(1).get("chargeCapacity")); |
| | | |
| | | imported.put("dischargeMedianVoltage", 1.2); |
| | | assertTrue(assertThrows(RuntimeException.class, () -> submit("STEP", List.of(imported))).getMessage().contains("冲突拒绝")); |
| | | assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |
| | | |
| | | @Test void workbookImportsThreeSheetsWithSeparateRevisionsAndSkipsRetry() |
| | | { |
| | | var sheets = Map.of("REALTIME", List.of(row(), row()), "STEP", List.of(row()), "CYCLE", List.of(row())); |
| | | var result = submitWorkbook(sheets); |
| | | assertEquals(3, result.get("importedCount")); |
| | | assertEquals(1, result.get("skippedCount")); |
| | | assertNotNull(result.get("actionId")); |
| | | for (String type : List.of("REALTIME", "STEP", "CYCLE")) |
| | | { |
| | | assertEquals(1, mapper.selectImportDataRecords(1L, type).size()); |
| | | var query = new TTestDataRevisionAction(); query.setDataType(type); |
| | | assertEquals(1, service.selectActions(query).size()); |
| | | } |
| | | var actions = service.selectActions(new TTestDataRevisionAction()); |
| | | assertEquals(3, actions.size()); |
| | | assertEquals(3, ((List<?>) result.get("actionIds")).size()); |
| | | assertEquals(java.util.Set.of("REALTIME", "STEP", "CYCLE"), actions.stream().map(TTestDataRevisionAction::getDataType).collect(java.util.stream.Collectors.toSet())); |
| | | for (var revision : actions) |
| | | { |
| | | String label = switch (revision.getDataType()) { case "REALTIME" -> "实时"; case "STEP" -> "工步"; default -> "循环"; }; |
| | | assertEquals("导入1条" + label + "数据", revision.getScopeSummary()); |
| | | assertEquals(1L, service.selectActionById(revision.getActionId()).getAffectedRecordCount()); |
| | | } |
| | | assertEquals("三页验收", actions.get(0).getReason()); |
| | | var retry = submitWorkbook(sheets); |
| | | assertEquals(0, retry.get("importedCount")); |
| | | assertEquals(4, retry.get("skippedCount")); |
| | | assertNull(retry.get("actionId")); |
| | | assertEquals(List.of(), retry.get("actionIds")); |
| | | assertEquals(3, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |
| | | |
| | | private com.ruoyi.common.core.domain.AjaxResult submitWorkbook(Map<String, List<Map<String, Object>>> sheets) |
| | | { |
| | | return controller.dataAction(1L, new TTestController.ConsoleDataActionRequest("ALL", "IMPORT", "三 Sheet 导入", "[]", |
| | | JSON.toJSONString(sheets), "三页验收", List.of())); |
| | | } |
| | | |
| | | @Test void workbookLateDatabaseFailureRollsBackAllSheetsAndRevision() |
| | | { |
| | | var cycle = row(); cycle.put("dischargeEnergy", 999); |
| | | jdbc.execute("alter table t_test_cycle_record add check (discharge_energy_wh <> 999)"); |
| | | assertThrows(RuntimeException.class, () -> submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(cycle)))); |
| | | assertWorkbookEmpty(); |
| | | } |
| | | |
| | | @Test void workbookRevisionFailureRollsBackEarlierRevisionsAndAllData() |
| | | { |
| | | jdbc.execute("alter table t_test_data_revision_action add check (data_type <> 'CYCLE')"); |
| | | assertThrows(RuntimeException.class, () -> submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(row())))); |
| | | assertWorkbookEmpty(); |
| | | } |
| | | |
| | | @Test void partiallyRepeatedWorkbookRecordsActualPerTypeCounts() |
| | | { |
| | | submit("REALTIME", List.of(row())); |
| | | var result = submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(row()))); |
| | | assertEquals(2, result.get("importedCount")); |
| | | assertEquals(1, result.get("skippedCount")); |
| | | assertEquals(3, ((List<?>) result.get("actionIds")).size()); |
| | | var realtime = new TTestDataRevisionAction(); realtime.setDataType("REALTIME"); |
| | | var revisions = service.selectActions(realtime); |
| | | assertEquals(2, revisions.size()); |
| | | assertEquals("导入0条实时数据", revisions.get(0).getScopeSummary()); |
| | | assertEquals(0L, service.selectActionById(revisions.get(0).getActionId()).getAffectedRecordCount()); |
| | | var invalid = new TTestDataRevisionAction(); invalid.setDataType("ALL"); |
| | | assertThrows(RuntimeException.class, () -> service.selectActions(invalid)); |
| | | } |
| | | |
| | | @Test void workbookMissingEmptyInvalidOrConflictingSheetNeverWritesAnySheet() |
| | | { |
| | | var sheets = new LinkedHashMap<String, List<Map<String, Object>>>(); |
| | | sheets.put("REALTIME", List.of(row())); sheets.put("STEP", List.of(row())); |
| | | assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)); |
| | | assertWorkbookEmpty(); |
| | | sheets.put("CYCLE", List.of()); |
| | | assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)); |
| | | assertWorkbookEmpty(); |
| | | var invalid = row(); invalid.put("cycleNo", 0); invalid.put("sourceRow", 23); |
| | | sheets.put("CYCLE", List.of(invalid)); |
| | | assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("循环工作表,第23行")); |
| | | assertWorkbookEmpty(); |
| | | var conflict = row(); conflict.put("dischargeCapacity", 88); |
| | | sheets.put("CYCLE", List.of(row(), conflict)); |
| | | assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("冲突拒绝")); |
| | | assertWorkbookEmpty(); |
| | | } |
| | | |
| | | @Test void workbookRejectsDifferentBatterySetsEvenWhenBothBatteriesBelongToTest() |
| | | { |
| | | jdbc.execute("insert into t_test_battery values(6,1,'BAT-2',0)"); |
| | | jdbc.execute("insert into t_test_channel_binding values(7,6,11)"); |
| | | var other = row(); other.put("batteryNo", "BAT-2"); |
| | | var sheets = Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(other)); |
| | | assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("电池号集合不一致")); |
| | | assertWorkbookEmpty(); |
| | | other.put("batteryNo", "OTHER"); |
| | | assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("不属于当前测试")); |
| | | assertWorkbookEmpty(); |
| | | } |
| | | |
| | | @Test void workbookConflictWithExistingCyclePreservesFactsAndRejectsOtherSheets() |
| | | { |
| | | submit("CYCLE", List.of(row())); |
| | | var conflict = row(); conflict.put("dischargeCapacity", 88); |
| | | assertThrows(RuntimeException.class, () -> submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(conflict)))); |
| | | assertTrue(mapper.selectImportDataRecords(1L, "REALTIME").isEmpty()); |
| | | assertTrue(mapper.selectImportDataRecords(1L, "STEP").isEmpty()); |
| | | assertEquals(1, mapper.selectImportDataRecords(1L, "CYCLE").size()); |
| | | assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |
| | | |
| | | @Test void workbookCannotImportBeforeTestCompletionOrWithoutReason() |
| | | { |
| | | var sheets = Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(row())); |
| | | assertThrows(RuntimeException.class, () -> controller.dataAction(1L, new TTestController.ConsoleDataActionRequest("ALL", "IMPORT", "三 Sheet 导入", "[]", JSON.toJSONString(sheets), " ", List.of()))); |
| | | jdbc.update("update t_test set test_status='RUNNING', reception_status='RECEIVING' where test_id=1"); |
| | | assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)); |
| | | assertWorkbookEmpty(); |
| | | } |
| | | |
| | | @Test void workbookRejectsExistingExtraStepMeasurementInsteadOfSkippingIt() |
| | | { |
| | | submit("STEP", List.of(row())); |
| | | jdbc.update("update t_test_step_record set extension_measurements_json=?", "{\"stepDuration\":3752,\"auxTemperature\":99}"); |
| | | var sheets = Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(row())); |
| | | assertTrue(assertThrows(RuntimeException.class, () -> submitWorkbook(sheets)).getMessage().contains("冲突拒绝")); |
| | | assertTrue(mapper.selectImportDataRecords(1L, "REALTIME").isEmpty()); |
| | | assertTrue(mapper.selectImportDataRecords(1L, "CYCLE").isEmpty()); |
| | | assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |
| | | |
| | | private void assertWorkbookEmpty() |
| | | { |
| | | for (String type : List.of("REALTIME", "STEP", "CYCLE")) assertTrue(mapper.selectImportDataRecords(1L, type).isEmpty()); |
| | | assertTrue(service.selectActions(new TTestDataRevisionAction()).isEmpty()); |
| | | } |
| | | |
| | |
| | | void suppliedPartyWorkbookImportsAllRowsAndSkipsEveryRetry() throws Exception |
| | | { |
| | | var sample = JSON.parseObject(java.nio.file.Files.readString(java.nio.file.Path.of(System.getenv("TICKET03_SAMPLE_JSON")))); |
| | | Map<String, List<Map<String, Object>>> sheets = new LinkedHashMap<>(); |
| | | for (String type : List.of("REALTIME", "STEP", "CYCLE")) sheets.put(type, (List) sample.getJSONArray(type).toJavaList(Map.class)); |
| | | assertEquals(7767, submitWorkbook(sheets).get("importedCount")); |
| | | assertEquals(7767, submitWorkbook(sheets).get("skippedCount")); |
| | | for (String type : List.of("REALTIME", "STEP", "CYCLE")) |
| | | { |
| | | List<Map<String, Object>> rows = (List) sample.getJSONArray(type).toJavaList(Map.class); |
| | | int count = switch (type) { case "REALTIME" -> 7605; case "STEP" -> 129; default -> 33; }; |
| | | assertEquals(count, submit(type, rows).get("importedCount")); |
| | | assertEquals(count, mapper.selectImportDataRecords(1L, type).size()); |
| | | assertEquals(count, submit(type, rows).get("skippedCount")); |
| | | var query = new TTestDataRevisionAction(); query.setDataType(type); |
| | | var actions = service.selectActions(query); |
| | | assertEquals(1, actions.size()); |
| | | assertEquals((long) count, service.selectActionById(actions.get(0).getActionId()).getAffectedRecordCount()); |
| | | String label = switch (type) { case "REALTIME" -> "实时"; case "STEP" -> "工步"; default -> "循环"; }; |
| | | assertEquals("导入" + count + "条" + label + "数据", actions.get(0).getScopeSummary()); |
| | | if ("STEP".equals(type) || "REALTIME".equals(type)) |
| | | { |
| | | var persisted = mapper.selectImportDataRecords(1L, type).stream().collect(java.util.stream.Collectors.toMap( |
| | | record -> record.get("source_record_id").toString(), record -> record)); |
| | | for (var input : rows) |
| | | { |
| | | var stored = JSON.parseObject(persisted.get(input.get("recordNo").toString()).get("extension_measurements_json").toString()); |
| | | for (String field : "STEP".equals(type) ? List.of("stepDuration", "chargeMedianVoltage", "dischargeMedianVoltage") : List.of("relativeTimeSeconds")) |
| | | if (input.get(field) == null) assertNull(stored.get(field)); |
| | | else assertEquals(0, new java.math.BigDecimal(input.get(field).toString()).compareTo(stored.getBigDecimal(field)), field); |
| | | } |
| | | } |
| | | } |
| | | assertEquals(3, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |
| | | |
| | | @Test void realtimeRelativeTimeSurvivesImportQueryAndDuplicateValidation() |
| | | { |
| | | var input = row(); input.put("relativeTimeSeconds", 3752.125); |
| | | submit("REALTIME", List.of(input)); |
| | | var stored = mapper.selectImportDataRecords(1L, "REALTIME").get(0); |
| | | assertNotNull(stored.get("extension_measurements_json")); |
| | | var extension = JSON.parseObject(stored.get("extension_measurements_json").toString()); |
| | | assertEquals(0, new java.math.BigDecimal("3752.125").compareTo(extension.getBigDecimal("relativeTimeSeconds"))); |
| | | var queryMapper = org.mockito.Mockito.mock(TTestMapper.class); |
| | | var query = new com.battery.business.service.impl.TestDataQueryServiceImpl(queryMapper, |
| | | org.mockito.Mockito.mock(com.battery.business.service.ITTestService.class)); |
| | | var current = new LinkedHashMap<String, Object>(); current.put("extensionMeasurementsJson", extension.toJSONString()); |
| | | var legacy = new LinkedHashMap<String, Object>(); |
| | | org.mockito.Mockito.when(queryMapper.selectPersistedFinalRealtimeRecords(1L)).thenReturn(List.of(current, legacy)); |
| | | assertEquals(new java.math.BigDecimal("3752.125"), query.records(1L).get(0).get("relativeTimeSeconds")); |
| | | assertNull(query.records(1L).get(1).get("relativeTimeSeconds")); |
| | | assertEquals(1, submit("REALTIME", List.of(input)).get("skippedCount")); |
| | | input.put("relativeTimeSeconds", 3752.126); |
| | | assertThrows(RuntimeException.class, () -> submit("REALTIME", List.of(input))); |
| | | } |
| | | |
| | | private Map<String, Object> row() |
| | | { |
| | | var row = new LinkedHashMap<String, Object>(Map.of("recordNo", "17", "batteryNo", "BAT-1", "recordedAt", 1683517331000L, |