yeg
2026-09-28 eade173a673af574a39e1de2b96a383470f75ea6
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.battery.business.service.impl;
 
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.ArgumentMatchers.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import com.alibaba.fastjson2.JSON;
import com.battery.business.domain.*;
import com.battery.business.mapper.TTestMapper;
import com.ruoyi.common.exception.ServiceException;
 
/** 修订入口必须保留数据库中的修改前事实,并拒绝越界身份和字段。 */
class TestConsoleDataRevisionQueryServiceTest
{
    private final TTestMapper mapper = mock(TTestMapper.class);
    private final TestConsoleDataServiceImpl service = new TestConsoleDataServiceImpl(mapper);
 
    @org.junit.jupiter.api.BeforeEach void generatedRevisionId()
    {
        when(mapper.insertDataRevisionAction(any())).thenAnswer(call -> {
            ((TTestDataRevisionAction) call.getArgument(0)).setActionId(1L); return 1;
        });
    }
 
    @Test void savesDatabaseBeforeContentInsteadOfClientClaims()
    {
        var action = action("EDIT", "realtime:7");
        when(mapper.selectDataRecord(1L, "REALTIME", 7L)).thenReturn(Map.of("voltage_v", new BigDecimal("3.7"), "source_identity", "cabinet-a"));
        var item = new DataRevisionItem(); item.setStableIdentity("realtime:7"); item.setFieldValues("{\"voltage\":4.125}");
        service.submitAction(action, List.of(item));
        var before = JSON.parseArray(action.getBeforeContentJson()).getJSONObject(0);
        assertEquals("cabinet-a", before.getString("source_identity"));
        assertEquals(new BigDecimal("3.7"), before.getBigDecimal("voltage_v"));
        var order = inOrder(mapper);
        order.verify(mapper).insertDataRevisionAction(action);
        order.verify(mapper).updateDataRecord(1L, "REALTIME", 7L, Map.of("voltage_v", new BigDecimal("4.125")));
    }
 
    @Test void deletionPersistsCompleteContentBeforeRemovingRecord()
    {
        var action = action("DELETE", "realtime:7");
        when(mapper.selectDataRecord(1L, "REALTIME", 7L)).thenReturn(Map.of("cabinet_device_time_raw", "2026-09-05 12:00:00", "voltage_v", 3.7));
        service.submitAction(action, List.of());
        assertTrue(action.getBeforeContentJson().contains("cabinet_device_time_raw"));
        var order = inOrder(mapper);
        order.verify(mapper).insertDataRevisionAction(action);
        order.verify(mapper).deleteDataRecord(1L, "REALTIME", 7L);
    }
 
    @Test void stepExtensionEditPreservesOtherMetricsAndDisplaysPreviousValue()
    {
        var action = action("EDIT", "step:7"); action.setDataType("STEP");
        when(mapper.selectDataRecord(1L, "STEP", 7L)).thenReturn(Map.of("extension_measurements_json", "{\"startVoltage\":3.2,\"stepDuration\":60}"));
        var item = new DataRevisionItem(); item.setStableIdentity("step:7"); item.setFieldValues("{\"startVoltage\":3.4}");
        service.submitAction(action, List.of(item));
        var change = JSON.parseArray(action.getChangesSummary()).getJSONObject(0);
        assertEquals(new BigDecimal("3.2"), change.getJSONObject("before").getBigDecimal("startVoltage"));
        var values = org.mockito.ArgumentCaptor.forClass(Map.class);
        verify(mapper).updateDataRecord(eq(1L), eq("STEP"), eq(7L), values.capture());
        var extension = JSON.parseObject(values.getValue().get("extension_measurements_json").toString());
        assertEquals(60, extension.getIntValue("stepDuration"));
        assertEquals(new BigDecimal("3.4"), extension.getBigDecimal("startVoltage"));
    }
 
    @Test void rejectsWrongDataTypeIdentityAndOtherTestsRecords()
    {
        assertThrows(ServiceException.class, () -> service.submitAction(action("DELETE", "step:7"), List.of()));
        assertThrows(ServiceException.class, () -> service.submitAction(action("DELETE", "realtime:8"), List.of()));
        verify(mapper, never()).insertDataRevisionAction(any());
    }
 
    @Test void medianEditAuditsPreviousValuesAndPreservesLegacyFacts()
    {
        var action = action("EDIT", "step:7"); action.setDataType("STEP");
        String previous = "{\"chargeMedianVoltage\":0,\"dischargeMedianVoltage\":1.1709,\"chargePeakVoltage\":1.8,\"dischargeValleyVoltage\":0.8}";
        when(mapper.selectDataRecord(1L, "STEP", 7L)).thenReturn(Map.of("extension_measurements_json", previous));
        var item = new DataRevisionItem(); item.setStableIdentity("step:7");
        item.setFieldValues("{\"chargeMedianVoltage\":1.6431,\"dischargeMedianVoltage\":null}");
        service.submitAction(action, List.of(item));
        assertEquals(previous, JSON.parseArray(action.getBeforeContentJson()).getJSONObject(0).getString("extension_measurements_json"));
        var before = JSON.parseArray(action.getChangesSummary()).getJSONObject(0).getJSONObject("before");
        assertEquals(new BigDecimal("1.1709"), before.getBigDecimal("dischargeMedianVoltage"));
        var values = org.mockito.ArgumentCaptor.forClass(Map.class);
        verify(mapper).updateDataRecord(eq(1L), eq("STEP"), eq(7L), values.capture());
        var extension = JSON.parseObject(values.getValue().get("extension_measurements_json").toString());
        assertEquals(new BigDecimal("1.6431"), extension.getBigDecimal("chargeMedianVoltage"));
        assertNull(extension.get("dischargeMedianVoltage"));
        assertEquals(new BigDecimal("1.8"), extension.getBigDecimal("chargePeakVoltage"));
        assertEquals(new BigDecimal("0.8"), extension.getBigDecimal("dischargeValleyVoltage"));
    }
 
    @Test void rejectsIdentityEditsAndNonNumericMeasurements()
    {
        when(mapper.selectDataRecord(1L, "REALTIME", 7L)).thenReturn(Map.of("voltage_v", 3.7));
        for (String fields : List.of("{\"test_id\":2}", "{\"voltage\":\"invalid\"}")) {
            var item = new DataRevisionItem(); item.setStableIdentity("realtime:7"); item.setFieldValues(fields);
            assertThrows(ServiceException.class, () -> service.submitAction(action("EDIT", "realtime:7"), List.of(item)));
        }
        verify(mapper, never()).insertDataRevisionAction(any());
    }
 
    @Test void importRequiresCompletedTest()
    {
        assertThrows(ServiceException.class, () -> service.submitAction(action("IMPORT", "manual-1"), List.of()));
        verify(mapper, never()).insertImportedDataRecord(anyString(), any());
    }
 
    @Test void completedRealtimeImportKeepsSourceRecordAndSequence()
    {
        var action = action("IMPORT", "manual-1");
        mapper.selectTestByIdForUpdate(1L).setTestStatus("COMPLETED");
        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));
        action.setChangesSummary("[{\"recordNo\":\"manual-1\",\"batteryNo\":\"BAT-1\",\"recordedAt\":1770000000123,\"cycleNo\":1,\"stepNo\":1,\"stepName\":\"恒流充电\",\"sequenceId\":17,\"voltage\":3.6}]");
        service.submitAction(action, List.of());
        var values = org.mockito.ArgumentCaptor.forClass(Map.class);
        verify(mapper).insertImportedDataRecord(eq("REALTIME"), values.capture());
        assertEquals("manual-1", values.getValue().get("source_record_id"));
        assertEquals(17L, values.getValue().get("cabinet_sequence_id"));
        assertEquals("adapter-a:27:1:1:1", values.getValue().get("source_identity"));
    }
 
    private TTestDataRevisionAction action(String operation, String identity)
    {
        TTest test = new TTest(); test.setTestId(1L); test.setTestStatus("RUNNING");
        when(mapper.selectTestByIdForUpdate(1L)).thenReturn(test);
        var action = new TTestDataRevisionAction(); action.setTestId(1L); action.setDataType("REALTIME");
        action.setOperationType(operation); action.setIdentitySummary(JSON.toJSONString(List.of(identity)));
        action.setScopeSummary("核对一条数据"); action.setReason("现场核对"); action.setOperatorName("operator");
        action.setChangesSummary("[]"); return action;
    }
}