package com.battery.business.service.report; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.*; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.battery.business.domain.*; import com.battery.business.service.*; import com.battery.device.api.StepDirectionResolver; import com.fasterxml.jackson.databind.ObjectMapper; import org.h2.jdbcx.JdbcDataSource; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.jdbc.core.JdbcTemplate; class ReportTestDetailPersistenceTest { private final ObjectMapper mapper = new ObjectMapper(); @ParameterizedTest @ValueSource(strings = { "compare", "formation", "splice" }) void reportOwnsAddedAndRemovedFieldsAcrossSaveAndReopen(String type) throws Exception { var database = new JdbcDataSource(); database.setURL("jdbc:h2:mem:detail_" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1"); var jdbc = new JdbcTemplate(database); jdbc.execute("CREATE TABLE t_" + type + "_report(report_id BIGINT AUTO_INCREMENT PRIMARY KEY, report_name VARCHAR(100), creator_id BIGINT, creator_name VARCHAR(100), created_at_utc TIMESTAMP)"); jdbc.execute("CREATE TABLE t_" + type + "_report_version(report_id BIGINT, version_no INT, snapshot_json CLOB, operator_id BIGINT, operator_name VARCHAR(100), created_at_utc TIMESTAMP, change_summary VARCHAR(500), PRIMARY KEY(report_id,version_no))"); var tests = mock(ITTestService.class); var data = mock(ITestDataQueryService.class); var definitions = mock(ITBatteryGroupFieldService.class); List originals = new ArrayList<>(); for (long id : List.of(1L, 2L)) { var battery = new TTestBattery(); battery.setTestBatteryId(id); battery.setGroupId(id); battery.setGroupName("组" + id); battery.setBatteryNo("同名电池"); battery.setFields(List.of()); var test = new TTest(); test.setTestId(id); test.setTestName("测试" + id); test.setTestTypeName("化成"); test.setTestStatus("COMPLETED"); test.setBatteries(List.of(battery)); test.setLogicStepMappings(List.of()); var detail = new TTestConsoleDetail(); detail.setExperimentName("仅控制台实验"); detail.setBatteryFields(List.of("额定容量")); detail.setSpecialFields(List.of("待删除")); detail.setFieldValues(new LinkedHashMap<>(Map.of("额定容量", Map.of("同名电池", "来源" + id), "待删除", Map.of("同名电池", "原值")))); test.setConsoleDetail(detail); originals.add(test); when(tests.selectTestById(id)).thenReturn(test); Map row = Map.of("testBatteryId", id, "batteryNo", "同名电池", "cycleNo", 1, "stepNo", 1, "stepName", "充电", "sourceType", "SNAPSHOT", "deviceTime", 1000L, "voltage", 3); when(data.records(id)).thenReturn(List.of(row)); when(data.cycles(id)).thenReturn(List.of()); when(data.steps(id)).thenReturn(List.of()); } var compare = new CompareReportService(tests, data, new CompareReportRepository(database)); var formation = new FormationReportService(tests, data, new FormationReportRepository(database), definitions, value -> StepDirectionResolver.Direction.UNKNOWN); var splice = new SpliceReportService(tests, data, new SpliceReportRepository(database), new SpliceFieldTemplateService(database, definitions), mock(ITBatteryGroupService.class)); var settings = JSON.parseObject(switch (type) { case "compare" -> "{\"metrics\":[],\"seriesColors\":[],\"batteryTemplateFields\":[],\"specialFields\":[],\"fieldStructure\":[],\"detailFieldValues\":{}}"; case "formation" -> "{\"fields\":[{\"key\":\"chargeCapacity\",\"scope\":\"cycle\",\"kind\":\"metric\",\"displayName\":\"充电容量\",\"unit\":\"mAh\"}],\"values\":{},\"chargeStepSelections\":{},\"metrics\":[],\"seriesColors\":{}}"; default -> "{\"fieldStructure\":[],\"detailFieldValues\":{}}"; }); var details = JSON.parseObject(""" {"1|1":{"batteryFields":["额定容量"],"specialFields":["新字段"],"fieldValues":{"battery|额定容量":"报表甲","special|新字段":""}}, "2|2":{"batteryFields":["额定容量"],"specialFields":["新字段"],"fieldValues":{"battery|额定容量":"报表乙","special|新字段":"乙值"}}} """); settings.put("sourceDetailOverrides", details); var request = new JSONObject(); request.put("name", "独立详情"); request.put("description", ""); request.put("settings", settings); request.put("selections", type.equals("compare") ? List.of(Map.of("testId", 1, "testBatteryId", 1, "cycleNo", 1, "stepNo", 1), Map.of("testId", 2, "testBatteryId", 2, "cycleNo", 1, "stepNo", 1)) : List.of(Map.of("testId", 1, "testBatteryId", 1), Map.of("testId", 2, "testBatteryId", 2))); Map created = switch (type) { case "compare" -> compare.create(mapper.readValue(request.toJSONString(), CompareReportRequest.class), 1, "操作员"); case "formation" -> formation.create(mapper.readValue(request.toJSONString(), FormationReportRequest.class), 1, "操作员"); default -> splice.create(mapper.readValue(request.toJSONString(), SpliceReportRequest.class), 1, "操作员"); }; long id = ((Number) created.get("id")).longValue(); assertEquals(details, JSON.parseObject(JSON.toJSONString(created.get("settings"))).getJSONObject("sourceDetailOverrides")); clearInvocations(tests, data); details.getJSONObject("1|1").put("batteryFields", List.of()); details.getJSONObject("1|1").put("specialFields", List.of()); details.getJSONObject("1|1").put("fieldValues", Map.of()); details.getJSONObject("2|2").getJSONObject("fieldValues").put("special|新字段", "二次修改"); request.remove("selections"); request.put("baseVersionNo", 1); request.put("changeSummary", "修改本报表详情"); switch (type) { case "compare" -> compare.edit(id, mapper.readValue(request.toJSONString(), CompareReportEditRequest.class), 1, "操作员"); case "formation" -> formation.edit(id, mapper.readValue(request.toJSONString(), FormationReportRequest.Edit.class), 1, "操作员"); default -> splice.edit(id, mapper.readValue(request.toJSONString(), SpliceReportRequest.Edit.class), 1, "操作员"); } Map reopened = switch (type) { case "compare" -> compare.get(id); case "formation" -> formation.get(id, null); default -> splice.get(id, null); }; var savedDetails = JSON.parseObject(JSON.toJSONString(reopened.get("settings"))).getJSONObject("sourceDetailOverrides"); assertEquals(details, savedDetails); assertTrue(savedDetails.getJSONObject("1|1").getJSONArray("specialFields").isEmpty()); assertEquals("二次修改", savedDetails.getJSONObject("2|2").getJSONObject("fieldValues").getString("special|新字段")); Map history = switch (type) { case "compare" -> compare.get(id, 1); case "formation" -> formation.get(id, 1); default -> splice.get(id, 1); }; assertEquals("", JSON.parseObject(JSON.toJSONString(history.get("settings"))).getJSONObject("sourceDetailOverrides").getJSONObject("1|1").getJSONObject("fieldValues").getString("special|新字段")); verifyNoInteractions(tests, data); for (var original : originals) { assertEquals(List.of("待删除"), original.getConsoleDetail().getSpecialFields()); assertEquals("来源" + original.getTestId(), original.getConsoleDetail().getFieldValues().get("额定容量").get("同名电池")); } } }