package jnpf.elnService.impl;
|
|
import jnpf.elnEntity.formula.ElnFormulaParameterSlotEntity;
|
import jnpf.elnEntity.formula.ElnFormulaVersionEntity;
|
import jnpf.elnEntity.formula.ElnTemplateFormulaBindingEntity;
|
import jnpf.elnEntity.runtime.ElnFormulaCalculationLogEntity;
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRevisionEntity;
|
import jnpf.elnEntity.template.ElnTemplateBindingEntity;
|
import jnpf.elnMapper.ElnFormulaCalculationLogMapper;
|
import jnpf.elnMapper.ElnFormulaParameterSlotMapper;
|
import jnpf.elnMapper.ElnFormulaVersionMapper;
|
import jnpf.elnMapper.ElnTemplateBindingMapper;
|
import jnpf.elnMapper.ElnTemplateFormulaBindingMapper;
|
import jnpf.elnService.formula.FormulaDependencyGraph;
|
import jnpf.elnService.formula.impl.FormulaPreviewServiceImpl;
|
import jnpf.elnService.formula.impl.FormulaValidationServiceImpl;
|
import org.junit.Test;
|
|
import java.lang.reflect.Proxy;
|
import java.math.BigDecimal;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertTrue;
|
|
public class ElnRecordFormulaCalculationServiceImplTest {
|
|
@Test
|
public void recalculatesOnlyAffectedFormulaClosureInTopologicalOrder() {
|
Fixture fixture = new Fixture(false);
|
|
List<ElnRecordFieldValueEntity> calculated = fixture.service.recalculateAffected(
|
"tenant-a", fixture.record, fixture.revision, fixture.values,
|
new LinkedHashSet<>(Collections.singletonList("input-a")),
|
"save-1", "user-1");
|
|
assertEquals(Arrays.asList("result-a", "result-b"), bindingIds(calculated));
|
assertEquals(new BigDecimal("3"), fixture.value("result-a").getZhiShuzhi());
|
assertEquals(new BigDecimal("4"), fixture.value("result-b").getZhiShuzhi());
|
assertEquals(new BigDecimal("300"), fixture.value("result-c").getZhiShuzhi());
|
assertEquals(2, fixture.logs.size());
|
assertEquals("SUCCESS", fixture.logs.get(0).getJisuanZhuangtai());
|
assertEquals("SUCCESS", fixture.logs.get(1).getJisuanZhuangtai());
|
assertEquals(new BigDecimal("2"), fixture.logs.get(0).getShuruKuaizhao().get("x"));
|
assertEquals(new BigDecimal("3"), fixture.logs.get(1).getShuruKuaizhao().get("x"));
|
}
|
|
@Test
|
public void clearsFailedFormulaAndEveryDownstreamOldResultWithLogs() {
|
Fixture fixture = new Fixture(true);
|
|
List<ElnRecordFieldValueEntity> calculated = fixture.service.recalculateAffected(
|
"tenant-a", fixture.record, fixture.revision, fixture.values,
|
new LinkedHashSet<>(Collections.singletonList("input-a")),
|
"save-2", "user-1");
|
|
assertEquals(Arrays.asList("result-a", "result-b"), bindingIds(calculated));
|
assertNull(fixture.value("result-a").getZhiWenben());
|
assertNull(fixture.value("result-a").getZhiShuzhi());
|
assertNull(fixture.value("result-b").getZhiWenben());
|
assertNull(fixture.value("result-b").getZhiShuzhi());
|
assertEquals("MISSING_INPUT", fixture.value("result-a").getJisuanZhuangtai());
|
assertEquals("MISSING_INPUT", fixture.value("result-b").getJisuanZhuangtai());
|
assertEquals(new BigDecimal("300"), fixture.value("result-c").getZhiShuzhi());
|
assertEquals(2, fixture.logs.size());
|
assertEquals("MISSING_INPUT", fixture.logs.get(0).getJisuanZhuangtai());
|
assertEquals("MISSING_INPUT", fixture.logs.get(1).getJisuanZhuangtai());
|
}
|
|
@Test
|
public void recordsDownstreamAsMissingWhenUpstreamFormulaErrors() {
|
Fixture fixture = new Fixture(false);
|
fixture.value("input-a").setZhiShuzhi(null);
|
fixture.value("input-a").setZhiWenben("invalid-number");
|
|
fixture.service.recalculateAffected(
|
"tenant-a", fixture.record, fixture.revision, fixture.values,
|
new LinkedHashSet<>(Collections.singletonList("input-a")),
|
"save-3", "user-1");
|
|
assertEquals("ERROR", fixture.value("result-a").getJisuanZhuangtai());
|
assertEquals("MISSING_INPUT", fixture.value("result-b").getJisuanZhuangtai());
|
assertNull(fixture.value("result-b").getZhiShuzhi());
|
assertEquals("MISSING_INPUT", fixture.logs.get(1).getJisuanZhuangtai());
|
assertTrue(fixture.logs.get(1).getShibaiYuanyin()
|
.contains("code-result-a"));
|
}
|
|
@Test
|
public void recalculatesEveryFormulaInTopologicalOrder() {
|
Fixture fixture = new Fixture(false);
|
|
List<ElnRecordFieldValueEntity> calculated = fixture.service.recalculateAll(
|
"tenant-a", fixture.record, fixture.revision, fixture.values,
|
"all-1", "user-1");
|
|
assertEquals(Arrays.asList("result-a", "result-b", "result-c"),
|
bindingIds(calculated));
|
assertEquals(new BigDecimal("3"), fixture.value("result-a").getZhiShuzhi());
|
assertEquals(new BigDecimal("4"), fixture.value("result-b").getZhiShuzhi());
|
assertEquals(new BigDecimal("10"), fixture.value("result-c").getZhiShuzhi());
|
assertEquals(3, fixture.logs.size());
|
}
|
|
private static List<String> bindingIds(List<ElnRecordFieldValueEntity> values) {
|
List<String> result = new ArrayList<>();
|
for (ElnRecordFieldValueEntity value : values) result.add(value.getMubanBangdingId());
|
return result;
|
}
|
|
private static final class Fixture {
|
private final ElnRecordEntity record = new ElnRecordEntity();
|
private final ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
private final List<ElnRecordFieldValueEntity> values = new ArrayList<>();
|
private final List<ElnFormulaCalculationLogEntity> logs = new ArrayList<>();
|
private final ElnRecordFormulaCalculationServiceImpl service;
|
|
private Fixture(boolean missingInput) {
|
record.setId("record-1");
|
record.setMubanId("template-1");
|
revision.setId("revision-1");
|
revision.setDianziJiluId(record.getId());
|
|
List<ElnTemplateFormulaBindingEntity> formulas = Arrays.asList(
|
formula("formula-a", "definition-a", "version-a", "input-a", "result-a"),
|
formula("formula-b", "definition-b", "version-b", "result-a", "result-b"),
|
formula("formula-c", "definition-c", "version-c", "input-c", "result-c"));
|
List<ElnTemplateBindingEntity> bindings = Arrays.asList(
|
binding("input-a"), binding("result-a"), binding("result-b"),
|
binding("input-c"), binding("result-c"));
|
values.add(value("input-a", missingInput ? null : new BigDecimal("2")));
|
values.add(value("result-a", new BigDecimal("100")));
|
values.add(value("result-b", new BigDecimal("200")));
|
values.add(value("input-c", new BigDecimal("9")));
|
values.add(value("result-c", new BigDecimal("300")));
|
AtomicInteger versionIndex = new AtomicInteger();
|
AtomicInteger slotIndex = new AtomicInteger();
|
List<ElnFormulaVersionEntity> versions = Arrays.asList(
|
version("version-a", "definition-a"),
|
version("version-b", "definition-b"),
|
version("version-c", "definition-c"));
|
List<List<ElnFormulaParameterSlotEntity>> slots = Arrays.asList(
|
Collections.singletonList(slot("version-a")),
|
Collections.singletonList(slot("version-b")),
|
Collections.singletonList(slot("version-c")));
|
|
ElnTemplateFormulaBindingMapper formulaMapper = proxy(
|
ElnTemplateFormulaBindingMapper.class,
|
(method, args) -> "selectList".equals(method) ? formulas : null);
|
ElnFormulaVersionMapper versionMapper = proxy(ElnFormulaVersionMapper.class,
|
(method, args) -> "selectOne".equals(method)
|
? versions.get(versionIndex.getAndIncrement()) : null);
|
ElnFormulaParameterSlotMapper slotMapper = proxy(
|
ElnFormulaParameterSlotMapper.class,
|
(method, args) -> "selectList".equals(method)
|
? slots.get(slotIndex.getAndIncrement()) : null);
|
ElnTemplateBindingMapper bindingMapper = proxy(ElnTemplateBindingMapper.class,
|
(method, args) -> "selectList".equals(method) ? bindings : null);
|
ElnFormulaCalculationLogMapper logMapper = proxy(
|
ElnFormulaCalculationLogMapper.class, (method, args) -> {
|
if ("insert".equals(method)) {
|
logs.add((ElnFormulaCalculationLogEntity) args[0]);
|
return 1;
|
}
|
return null;
|
});
|
service = new ElnRecordFormulaCalculationServiceImpl(
|
formulaMapper, versionMapper, slotMapper, bindingMapper, logMapper,
|
new FormulaPreviewServiceImpl(new FormulaValidationServiceImpl()),
|
new FormulaDependencyGraph());
|
}
|
|
private ElnRecordFieldValueEntity value(String bindingId) {
|
for (ElnRecordFieldValueEntity value : values) {
|
if (bindingId.equals(value.getMubanBangdingId())) return value;
|
}
|
throw new AssertionError(bindingId);
|
}
|
|
private ElnRecordFieldValueEntity value(String bindingId, BigDecimal decimal) {
|
ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity();
|
value.setId("value-" + bindingId);
|
value.setDianziJiluId(record.getId());
|
value.setXiudingId(revision.getId());
|
value.setMubanId(record.getMubanId());
|
value.setMubanBangdingId(bindingId);
|
value.setZiduanDingyiId("field-" + bindingId);
|
value.setZiduanBianma("code-" + bindingId);
|
value.setZiduanBanbenId("field-version-" + bindingId);
|
value.setBangdingJuese("PRIMARY");
|
value.setBangdingZhuangtai("ACTIVE");
|
value.setZhiShuzhi(decimal);
|
value.setZhiWenben(decimal == null ? null : decimal.toPlainString());
|
if (bindingId.startsWith("result-")) value.setJisuanZhuangtai("SUCCESS");
|
return value;
|
}
|
}
|
|
private static ElnTemplateFormulaBindingEntity formula(
|
String id, String definitionId, String versionId,
|
String inputBindingId, String resultBindingId) {
|
ElnTemplateFormulaBindingEntity formula = new ElnTemplateFormulaBindingEntity();
|
formula.setId(id);
|
formula.setMubanId("template-1");
|
formula.setGongshiDingyiId(definitionId);
|
formula.setGongshiBanbenId(versionId);
|
formula.setJieguoBangdingId(resultBindingId);
|
formula.setBangdingZhuangtai("ACTIVE");
|
Map<String, String> mappings = new LinkedHashMap<>();
|
mappings.put("x", inputBindingId);
|
formula.setCanshuYingshe(mappings);
|
return formula;
|
}
|
|
private static ElnTemplateBindingEntity binding(String id) {
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId(id);
|
binding.setMubanId("template-1");
|
binding.setZiduanDingyiId("field-" + id);
|
binding.setZiduanBianma("code-" + id);
|
binding.setZiduanBanbenId("field-version-" + id);
|
binding.setKongjianJuese("PRIMARY");
|
binding.setBangdingZhuangtai("ACTIVE");
|
return binding;
|
}
|
|
private static ElnFormulaVersionEntity version(String id, String definitionId) {
|
ElnFormulaVersionEntity version = new ElnFormulaVersionEntity();
|
version.setId(id);
|
version.setGongshiDingyiId(definitionId);
|
version.setBiaodashi("x + 1");
|
version.setJieguoLeixing("DECIMAL");
|
version.setBizStatus("PUBLISHED");
|
return version;
|
}
|
|
private static ElnFormulaParameterSlotEntity slot(String versionId) {
|
ElnFormulaParameterSlotEntity slot = new ElnFormulaParameterSlotEntity();
|
slot.setId("slot-" + versionId);
|
slot.setGongshiBanbenId(versionId);
|
slot.setCanshuBianma("x");
|
slot.setCanshuMingcheng("输入");
|
slot.setShujuLeixing("DECIMAL");
|
slot.setShifouBitian(1);
|
slot.setPaixu(1);
|
return slot;
|
}
|
|
@SuppressWarnings("unchecked")
|
private static <T> T proxy(Class<T> type, Invocation invocation) {
|
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type},
|
(target, method, args) -> {
|
if (method.getDeclaringClass() == Object.class) {
|
if ("toString".equals(method.getName())) return type.getSimpleName();
|
if ("hashCode".equals(method.getName())) return System.identityHashCode(target);
|
if ("equals".equals(method.getName())) return target == args[0];
|
}
|
Object result = invocation.invoke(method.getName(), args);
|
if (result != null || !method.getReturnType().isPrimitive()) return result;
|
if (method.getReturnType() == boolean.class) return false;
|
return 0;
|
});
|
}
|
|
@FunctionalInterface
|
private interface Invocation {
|
Object invoke(String method, Object[] args);
|
}
|
}
|