package jnpf.elnService.impl;
|
|
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity;
|
import jnpf.elnEntity.template.ElnTemplateBindingEntity;
|
import jnpf.elnEntity.template.ElnTemplateCrossReferenceEntity;
|
import jnpf.elnMapper.ElnRecordFieldValueMapper;
|
import jnpf.elnMapper.ElnTemplateCrossReferenceMapper;
|
import jnpf.elnService.ElnTestRequestIdentityLookup;
|
import jnpf.elnService.ElnRecordSourceResolver;
|
import jnpf.exception.DataException;
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
import org.junit.Test;
|
|
import java.math.BigDecimal;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.Map;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.when;
|
import org.mockito.ArgumentCaptor;
|
|
public class ElnCrossTemplateSnapshotResolverImplTest {
|
|
@Test
|
public void resolvesTextValueFromExactSourceRecordRevisionAndFieldVersion() {
|
Fixture fixture = new Fixture();
|
ElnRecordFieldValueEntity sourceValue = fixture.sourceValue("source text", "MANUAL");
|
when(fixture.valueMapper.selectOne(any(Wrapper.class))).thenReturn(sourceValue);
|
|
Map<String, ElnRecordFieldValueEntity> result = fixture.resolve();
|
|
assertEquals(1, result.size());
|
assertSame(sourceValue, result.get("target-binding"));
|
verify(fixture.sourceResolver).resolve("tenant-1", "request-1", "SOURCE-TEMPLATE");
|
}
|
|
@Test
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public void excludesSoftDeletedSourceValuesFromResolveAndFreezeValidation() {
|
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(
|
new MybatisConfiguration(), "cross-template-soft-delete-test"),
|
ElnRecordFieldValueEntity.class);
|
Fixture fixture = new Fixture();
|
ElnRecordFieldValueEntity source = fixture.sourceValue("source text", "MANUAL");
|
when(fixture.valueMapper.selectOne(any(Wrapper.class))).thenReturn(source);
|
|
fixture.resolve();
|
fixture.validate(fixture.snapshotOf(source));
|
|
ArgumentCaptor<Wrapper> queries = ArgumentCaptor.forClass(Wrapper.class);
|
verify(fixture.valueMapper, times(2)).selectOne(queries.capture());
|
for (Wrapper query : queries.getAllValues()) {
|
assertTrue(query.getSqlSegment().contains("f_delete_mark"));
|
}
|
}
|
|
@Test
|
public void returnsEmptyWithoutCallingIdentityLookupWhenTemplateHasNoCrossRules() {
|
Fixture fixture = new Fixture();
|
when(fixture.referenceMapper.selectOne(any(Wrapper.class))).thenReturn(null);
|
|
assertTrue(fixture.resolve().isEmpty());
|
|
verify(fixture.identityLookup, never()).resolve(any(), any());
|
verify(fixture.sourceResolver, never()).resolve(any(), any(), any());
|
}
|
|
@Test
|
public void rejectsMissingTargetTestRequestIdentity() {
|
Fixture fixture = new Fixture();
|
when(fixture.identityLookup.resolve(eq("tenant-1"), any())).thenReturn(null);
|
|
expectDataException(fixture::resolve, "SOURCE_RECORD_IDENTITY_INVALID");
|
|
verify(fixture.sourceResolver, never()).resolve(any(), any(), any());
|
}
|
|
@Test
|
public void rejectsMissingEmptyOrVersionMismatchedSourceValue() {
|
Fixture fixture = new Fixture();
|
when(fixture.valueMapper.selectOne(any(Wrapper.class))).thenReturn(null);
|
|
expectDataException(fixture::resolve, "SOURCE_FIELD_VALUE_INVALID");
|
}
|
|
@Test
|
public void rejectsCrossTemplateReferenceChains() {
|
Fixture fixture = new Fixture();
|
when(fixture.valueMapper.selectOne(any(Wrapper.class)))
|
.thenReturn(fixture.sourceValue("source text", "CROSS_TEMPLATE"));
|
|
expectDataException(fixture::resolve, "SOURCE_FIELD_CHAIN_NOT_SUPPORTED");
|
}
|
|
@Test
|
public void propagatesSourceRecordStateFailureWithoutReturningPartialSnapshots() {
|
Fixture fixture = new Fixture();
|
when(fixture.sourceResolver.resolve("tenant-1", "request-1", "SOURCE-TEMPLATE"))
|
.thenThrow(new DataException("SOURCE_RECORD_STATE_NOT_ALLOWED: source not submitted"));
|
|
expectDataException(fixture::resolve, "SOURCE_RECORD_STATE_NOT_ALLOWED");
|
}
|
|
@Test
|
public void acceptsPersistedSnapshotWhenLineageAndAllValueSlotsAreUnchanged() {
|
Fixture fixture = new Fixture();
|
ElnRecordFieldValueEntity source = fixture.sourceValue("source text", "MANUAL");
|
source.setZhiShuzhi(new BigDecimal("12.30"));
|
source.setZhiRiqi(new Date(1722513600000L));
|
when(fixture.valueMapper.selectOne(any(Wrapper.class))).thenReturn(source);
|
|
List<jnpf.elnService.ElnCrossTemplateSnapshotResolver.SnapshotIssue> issues =
|
fixture.validate(fixture.snapshotOf(source));
|
|
assertTrue(issues.isEmpty());
|
verify(fixture.valueMapper, never()).updateById(any(ElnRecordFieldValueEntity.class));
|
}
|
|
@Test
|
public void reportsRecordRevisionValueIdentityVersionAndEachValueSlotDrift() {
|
assertSnapshotIssue("SOURCE_RECORD_DRIFT", (fixture, snapshot, current) ->
|
snapshot.setKuamoLaiyuanJiluId("previous-record"));
|
assertSnapshotIssue("SOURCE_REVISION_DRIFT", (fixture, snapshot, current) ->
|
snapshot.setKuamoLaiyuanXiudingId("previous-revision"));
|
assertSnapshotIssue("SOURCE_VALUE_ID_DRIFT", (fixture, snapshot, current) ->
|
snapshot.setKuamoLaiyuanZhiId("previous-value"));
|
assertSnapshotIssue("SOURCE_FIELD_VERSION_DRIFT", (fixture, snapshot, current) ->
|
current.setZiduanBanbenId("new-version"));
|
assertSnapshotIssue("SOURCE_VALUE_DRIFT", (fixture, snapshot, current) ->
|
current.setZhiWenben("new text"));
|
assertSnapshotIssue("SOURCE_VALUE_DRIFT", (fixture, snapshot, current) ->
|
current.setZhiShuzhi(new BigDecimal("99.1")));
|
assertSnapshotIssue("SOURCE_VALUE_DRIFT", (fixture, snapshot, current) ->
|
current.setZhiRiqi(new Date(1722513601000L)));
|
}
|
|
@Test
|
public void aggregatesUnavailableSourcesForEveryPersistedTargetBinding() {
|
Fixture fixture = new Fixture();
|
ElnRecordFieldValueEntity first = fixture.snapshotOf(fixture.sourceValue("one", "MANUAL"));
|
ElnRecordFieldValueEntity second = fixture.snapshotOf(fixture.sourceValue("two", "MANUAL"));
|
second.setMubanBangdingId("target-binding-2");
|
second.setZiduanBianma("target-field-2");
|
when(fixture.sourceResolver.resolve("tenant-1", "request-1", "SOURCE-TEMPLATE"))
|
.thenThrow(new DataException("SOURCE_RECORD_STATE_NOT_ALLOWED: source canceled"));
|
|
List<jnpf.elnService.ElnCrossTemplateSnapshotResolver.SnapshotIssue> issues =
|
fixture.resolver.validatePersistedSnapshots(
|
"tenant-1", "target-item", java.util.Arrays.asList(first, second));
|
|
assertEquals(2, issues.size());
|
assertEquals("target-binding", issues.get(0).getTargetBindingId());
|
assertEquals("target-binding-2", issues.get(1).getTargetBindingId());
|
assertEquals("SOURCE_RECORD_STATE_NOT_ALLOWED", issues.get(0).getCode());
|
}
|
|
private static void assertSnapshotIssue(String expectedCode, SnapshotChange change) {
|
Fixture fixture = new Fixture();
|
ElnRecordFieldValueEntity current = fixture.sourceValue("source text", "MANUAL");
|
current.setZhiShuzhi(new BigDecimal("12.30"));
|
current.setZhiRiqi(new Date(1722513600000L));
|
ElnRecordFieldValueEntity snapshot = fixture.snapshotOf(current);
|
change.apply(fixture, snapshot, current);
|
when(fixture.valueMapper.selectOne(any(Wrapper.class))).thenReturn(current);
|
|
List<jnpf.elnService.ElnCrossTemplateSnapshotResolver.SnapshotIssue> issues =
|
fixture.validate(snapshot);
|
|
assertEquals(1, issues.size());
|
assertEquals(expectedCode, issues.get(0).getCode());
|
assertEquals("target-binding", issues.get(0).getTargetBindingId());
|
assertEquals("target-field", issues.get(0).getTargetFieldCode());
|
assertEquals("SOURCE-TEMPLATE", issues.get(0).getSourceTemplateCode());
|
}
|
|
private static void expectDataException(Action action, String messageFragment) {
|
try {
|
action.run();
|
fail("expected DataException containing " + messageFragment);
|
} catch (DataException expected) {
|
org.junit.Assert.assertTrue(expected.getMessage().contains(messageFragment));
|
}
|
}
|
|
private interface Action {
|
void run();
|
}
|
|
private interface SnapshotChange {
|
void apply(Fixture fixture, ElnRecordFieldValueEntity snapshot,
|
ElnRecordFieldValueEntity current);
|
}
|
|
private static final class Fixture {
|
private final ElnTemplateCrossReferenceMapper referenceMapper =
|
mock(ElnTemplateCrossReferenceMapper.class);
|
private final ElnRecordFieldValueMapper valueMapper = mock(ElnRecordFieldValueMapper.class);
|
private final ElnTestRequestIdentityLookup identityLookup =
|
mock(ElnTestRequestIdentityLookup.class);
|
private final ElnRecordSourceResolver sourceResolver = mock(ElnRecordSourceResolver.class);
|
private final ElnCrossTemplateSnapshotResolverImpl resolver;
|
private final ElnTemplateBindingEntity targetBinding = new ElnTemplateBindingEntity();
|
|
private Fixture() {
|
targetBinding.setId("target-binding");
|
ElnTemplateCrossReferenceEntity rule = new ElnTemplateCrossReferenceEntity();
|
rule.setMubanBangdingId("target-binding");
|
rule.setLaiyuanMubanBianhao("SOURCE-TEMPLATE");
|
rule.setLaiyuanZiduanDingyiId("source-field");
|
rule.setLaiyuanZiduanBanbenId("source-version");
|
when(identityLookup.resolve(eq("tenant-1"), any())).thenReturn(Collections.singletonMap(
|
"target-item", new ElnTestRequestIdentityLookup.Identity("request-1", "pending_test")));
|
when(referenceMapper.selectOne(any(Wrapper.class))).thenReturn(rule);
|
ElnRecordEntity source = new ElnRecordEntity();
|
source.setId("source-record");
|
source.setDangqianXiudingId("source-revision");
|
when(sourceResolver.resolve("tenant-1", "request-1", "SOURCE-TEMPLATE")).thenReturn(source);
|
resolver = new ElnCrossTemplateSnapshotResolverImpl(
|
referenceMapper, valueMapper, identityLookup, sourceResolver);
|
}
|
|
private Map<String, ElnRecordFieldValueEntity> resolve() {
|
return resolver.resolve("tenant-1", "target-item", Collections.singletonList(targetBinding));
|
}
|
|
private List<jnpf.elnService.ElnCrossTemplateSnapshotResolver.SnapshotIssue> validate(
|
ElnRecordFieldValueEntity snapshot) {
|
return resolver.validatePersistedSnapshots(
|
"tenant-1", "target-item", Collections.singletonList(snapshot));
|
}
|
|
private ElnRecordFieldValueEntity snapshotOf(ElnRecordFieldValueEntity source) {
|
ElnRecordFieldValueEntity snapshot = new ElnRecordFieldValueEntity();
|
snapshot.setFTenantId("tenant-1");
|
snapshot.setMubanBangdingId("target-binding");
|
snapshot.setZiduanBianma("target-field");
|
snapshot.setZhiLaiyuan("CROSS_TEMPLATE");
|
snapshot.setKuamoLaiyuanJiluId(source.getDianziJiluId());
|
snapshot.setKuamoLaiyuanXiudingId(source.getXiudingId());
|
snapshot.setKuamoLaiyuanZhiId(source.getId());
|
snapshot.setKuamoLaiyuanZiduanBanbenId(source.getZiduanBanbenId());
|
snapshot.setZhiWenben(source.getZhiWenben());
|
snapshot.setZhiShuzhi(source.getZhiShuzhi());
|
snapshot.setZhiRiqi(source.getZhiRiqi());
|
return snapshot;
|
}
|
|
private ElnRecordFieldValueEntity sourceValue(String text, String source) {
|
ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity();
|
value.setId("source-value");
|
value.setDianziJiluId("source-record");
|
value.setXiudingId("source-revision");
|
value.setZiduanDingyiId("source-field");
|
value.setZiduanBanbenId("source-version");
|
value.setZhiWenben(text);
|
value.setZhiLaiyuan(source);
|
return value;
|
}
|
}
|
}
|