package com.battery.business.report;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
import static org.mockito.Mockito.*;
|
import java.math.BigDecimal;
|
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.Direction;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import org.h2.jdbcx.JdbcDataSource;
|
import org.junit.jupiter.api.Test;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
class FormationRealReportTest
|
{
|
private final ObjectMapper mapper = new ObjectMapper();
|
private final ITTestService tests = mock(ITTestService.class);
|
private final ITestDataQueryService data = mock(ITestDataQueryService.class);
|
private final FormationReportService service;
|
|
FormationRealReportTest()
|
{
|
var db = new JdbcDataSource();
|
db.setURL("jdbc:h2:mem:formation_" + UUID.randomUUID() + ";MODE=MySQL;DB_CLOSE_DELAY=-1");
|
var jdbc = new JdbcTemplate(db);
|
jdbc.execute("CREATE TABLE t_formation_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_formation_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))");
|
service = new FormationReportService(tests, data, new FormationReportRepository(db), mock(ITBatteryGroupFieldService.class),
|
type -> "cc".equals(type) ? Direction.CHARGE : "dc".equals(type) ? Direction.DISCHARGE : Direction.UNKNOWN);
|
var battery = new TTestBattery(); battery.setTestBatteryId(11L); battery.setBatteryNo("电池1"); battery.setGroupId(1L); battery.setGroupName("组1");
|
battery.setFields(new ArrayList<>());
|
Map.of("H", "2", "Z", "3", "A", "4", "F", "5", "B", "6").forEach((name, value) -> {
|
var field = new CurrentBatteryField(); field.setFieldName(name); field.setFieldValue(value); battery.getFields().add(field);
|
});
|
var test = new TTest(); test.setTestId(1L); test.setTestName("化成甲"); test.setTestStatus("COMPLETED"); test.setTestTypeName("化成"); test.setBatteries(List.of(battery));
|
when(tests.selectTestById(1L)).thenReturn(test);
|
when(data.records(1L)).thenReturn(List.of()); when(data.cycles(1L)).thenReturn(List.of());
|
when(data.steps(1L)).thenReturn(List.of(step(1, "cc", "4.1", "0.1"), step(2, "cc", "4.2", "0.2"), step(3, "dc", "1", "0.25")));
|
}
|
|
private Map<String, Object> step(int no, String type, String voltage, String capacity)
|
{
|
var row = new LinkedHashMap<String, Object>(); row.put("batteryNo", "电池1"); row.put("cycleNo", 1); row.put("stepNo", no); row.put("stepType", type);
|
row.put("endVoltage", new BigDecimal(voltage)); row.put("capacity", capacity == null ? null : new BigDecimal(capacity)); return row;
|
}
|
|
private JSONObject request()
|
{
|
return JSON.parseObject("""
|
{"name":"化成真实报表","description":"","selections":[{"testId":1,"testBatteryId":11}],
|
"settings":{"fields":[{"key":"manual","scope":"cycle","kind":"manual","displayName":"数字","unit":""}],
|
"values":{"1|11|1|manual":"2"},"chargeStepSelections":{}}}
|
""");
|
}
|
|
private JSONObject create(JSONObject request) throws Exception
|
{ return JSON.parseObject(JSON.toJSONString(service.create(mapper.readValue(request.toJSONString(), FormationReportRequest.class), 1, "操作员"))); }
|
|
@Test
|
void freezesFormulaInputsAndLastDischargeCapacityWithoutDependingOnDisplayedFields() throws Exception
|
{
|
var saved = create(request());
|
var values = saved.getJSONObject("calculatedValues");
|
assertNotNull(values, "公式结果必须随版本保存");
|
assertEquals(720, values.getBigDecimal("1|11|0|C").intValueExact());
|
assertEquals(240, values.getBigDecimal("1|11|1|chargePerGram").intValueExact());
|
assertEquals(288, values.getBigDecimal("1|11|1|chargePerGramTotal").intValueExact());
|
assertEquals(250, values.getBigDecimal("1|11|1|capacity1v").intValueExact());
|
assertEquals(50, values.getBigDecimal("1|11|1|capacity1vf").intValueExact());
|
assertEquals(5, saved.getJSONArray("items").getJSONObject(0).getJSONArray("businessFields").size());
|
assertEquals(1, service.list().get(0).versionNo());
|
clearInvocations(tests, data);
|
assertEquals(values, JSON.parseObject(JSON.toJSONString(service.get(saved.getLongValue("id"), null))).getJSONObject("calculatedValues"));
|
verifyNoInteractions(tests, data);
|
}
|
|
@Test
|
void reportFieldEditsRecalculateWithoutReadingOrChangingDeletedSource() throws Exception
|
{
|
var request = request();
|
request.getJSONObject("settings").getJSONArray("fields").add(JSON.parseObject("{\"key\":\"F\",\"scope\":\"detail\",\"kind\":\"battery\",\"displayName\":\"F\",\"unit\":\"\"}"));
|
var first = create(request); long id = first.getLongValue("id");
|
clearInvocations(tests, data);
|
when(tests.selectTestById(1L)).thenThrow(new IllegalStateException("来源已删除"));
|
request.remove("selections"); request.put("baseVersionNo", 1); request.put("changeSummary", "修改报表 F");
|
request.getJSONObject("settings").getJSONObject("values").put("1|11|0|F", "10");
|
var second = JSON.parseObject(JSON.toJSONString(service.edit(id, mapper.readValue(request.toJSONString(), FormationReportRequest.Edit.class), 1, "操作员")));
|
assertEquals(1440, second.getJSONObject("calculatedValues").getBigDecimal("1|11|0|C").intValueExact());
|
assertEquals(480, second.getJSONObject("calculatedValues").getBigDecimal("1|11|1|chargePerGram").intValueExact());
|
assertEquals(25, second.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1vf").intValueExact());
|
assertEquals(720, JSON.parseObject(JSON.toJSONString(service.get(id, 1))).getJSONObject("calculatedValues").getBigDecimal("1|11|0|C").intValueExact());
|
verifyNoInteractions(tests, data);
|
}
|
|
@Test
|
void missingLastDischargeDoesNotBorrowEarlierCapacityAndManualMustBeNonnegative() throws Exception
|
{
|
when(data.steps(1L)).thenReturn(List.of(step(1,"dc","1","0.25"),step(2,"dc","1",null)));
|
var saved=create(request());
|
assertNull(saved.getJSONObject("calculatedValues").get("1|11|1|capacity1v"));
|
var bad=request();bad.getJSONObject("settings").getJSONObject("values").put("1|11|1|manual","-1");
|
assertThrows(com.ruoyi.common.exception.ServiceException.class,()->create(bad));
|
bad.getJSONObject("settings").getJSONObject("values").put("1|11|1|manual","电量");
|
assertThrows(com.ruoyi.common.exception.ServiceException.class,()->create(bad));
|
}
|
|
@Test
|
void importedStepNamesSupplyDirectionWithoutInventingProtocolTypes() throws Exception
|
{
|
var discharge = step(1, null, "1", "0.25"); discharge.put("stepName", "恒流放电");
|
var rest = step(2, null, "1", null); rest.put("stepName", "搁置");
|
var charge = step(3, null, "4.2", "0.2"); charge.put("stepName", "恒流充电");
|
var ambiguous = step(4, "pulse", "99", "99"); ambiguous.put("stepName", "恒流充电");
|
when(data.steps(1L)).thenReturn(List.of(discharge, rest, charge, ambiguous));
|
var saved = create(request());
|
var choices = saved.getJSONObject("settings").getJSONObject("curveStepSelections").getJSONObject("1|11|1");
|
assertEquals(List.of(3), choices.getList("charge", Integer.class));
|
assertEquals(List.of(1), choices.getList("discharge", Integer.class));
|
assertEquals(250, saved.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact());
|
var steps = saved.getJSONArray("items").getJSONObject(0).getJSONObject("chargeSteps").getJSONArray("1");
|
assertEquals("REST", steps.getJSONObject(1).getString("direction"));
|
assertEquals("UNKNOWN", steps.getJSONObject(3).getString("direction"));
|
}
|
|
@Test
|
void explicitSourceRefreshKeepsManualValuesAndHistoricalClassification() throws Exception
|
{
|
var imported = step(1, null, "1", "0.25");
|
when(data.steps(1L)).thenReturn(List.of(imported));
|
var first = create(request());
|
long id = first.getLongValue("id");
|
assertNull(first.getJSONObject("calculatedValues").get("1|11|1|capacity1v"));
|
imported.put("stepName", "恒流放电");
|
var refreshed = JSON.parseObject(JSON.toJSONString(service.refresh(id, new CompareReportEditRequest.Refresh(1, "重新读取实际工步"), 1, "操作员")));
|
assertEquals(250, refreshed.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact());
|
assertEquals("2", refreshed.getJSONObject("settings").getJSONObject("values").getString("1|11|1|manual"));
|
var historical = JSON.parseObject(JSON.toJSONString(service.get(id, 1)));
|
assertNull(historical.getJSONObject("calculatedValues").get("1|11|1|capacity1v"));
|
assertEquals("UNKNOWN", historical.getJSONArray("items").getJSONObject(0).getJSONObject("chargeSteps").getJSONArray("1").getJSONObject(0).getString("direction"));
|
}
|
|
@Test
|
void automaticDirectionSelectionExcludesUnknownAndKeepsAllRealChargeSteps() throws Exception
|
{
|
when(data.steps(1L)).thenReturn(List.of(step(1,"cc","4.1","0.1"),step(2,"unknown","99","99"),step(3,"cc","4.2","0.2"),step(4,"dc","1","0.25")));
|
var request=request();
|
request.getJSONObject("settings").put("manualChargeSteps",Map.of("1|11|1",List.of(2)));
|
request.getJSONObject("settings").put("curveStepSelections",Map.of("1|11|1",Map.of("charge",List.of(1,2),"discharge",List.of(4))));
|
var saved=create(request);
|
var selected=saved.getJSONObject("settings").getJSONObject("curveStepSelections").getJSONObject("1|11|1");
|
assertEquals(List.of(1,3),selected.getList("charge",Integer.class));
|
assertEquals(List.of(4),selected.getList("discharge",Integer.class));
|
assertEquals(250,saved.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact());
|
}
|
}
|