yeg
2026-09-08 59a93bdc51766da1387b71df7ba0342e97b068e2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.battery.business.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<TTest> 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<String, Object> 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));
        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<String, Object> 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<String, Object> 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<String, Object> 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("同名电池"));
        }
    }
}