package jnpf.elnService.impl;
|
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.elnEntity.field.ElnFieldVersionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity;
|
import jnpf.elnEntity.runtime.ElnRecordForms;
|
import jnpf.elnEntity.runtime.ElnRecordRevisionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordVos;
|
import jnpf.elnEntity.template.ElnTemplateBindingEntity;
|
import jnpf.elnMapper.ElnFieldVersionMapper;
|
import jnpf.elnMapper.ElnRecordFieldValueMapper;
|
import jnpf.elnMapper.ElnRecordMapper;
|
import jnpf.elnMapper.ElnRecordRevisionMapper;
|
import jnpf.elnMapper.ElnTemplateBindingMapper;
|
import jnpf.elnService.ElnTestWorkItemSnapshotProvider;
|
import jnpf.elnService.ElnRecordFormulaCalculationService;
|
import jnpf.elnService.ElnCrossTemplateSnapshotResolver;
|
import jnpf.elnService.ElnRecordFillTicketService;
|
import jnpf.elnService.ElnRecordSourceSyncService;
|
import jnpf.elnService.ElnRecordSourceSyncOperationCoordinator;
|
import jnpf.exception.DataException;
|
import jnpf.lims.api.model.TestWorkItemSnapshotBatch;
|
import jnpf.lims.api.model.TestWorkItemSnapshot;
|
import jnpf.lims.api.model.TestWorkItemSnapshotProperty;
|
import jnpf.lims.api.model.ElnSourceSnapshot;
|
import jnpf.lims.api.model.ElnSourcePropertyRef;
|
import org.junit.Test;
|
import jnpf.util.JsonUtil;
|
|
import java.lang.reflect.Proxy;
|
import java.math.BigDecimal;
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.LinkedHashMap;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
|
public class ElnRecordSourceSyncServiceImplTest {
|
|
@Test
|
public void explicitlyClearsAnOptionalMissingSnapshotAndAuditsTheReason() {
|
Fixture fixture = configuredFixture();
|
fixture.configuredSnapshot.setOutcomeCode("MISSING");
|
fixture.configuredSnapshot.setReasonCode("NAME_MISSING");
|
fixture.configuredSnapshot.setValue(null);
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
Map<String, Object> request = new LinkedHashMap<>();
|
request.put("expectedRecordVersion", preview.getRecordVersion());
|
request.put("previewToken", preview.getPreviewToken());
|
request.put("operationCode", "sync-1");
|
request.put("clearReasons", Collections.singletonMap("binding-lims", "Confirmed source no longer has a name"));
|
|
ElnRecordVos.SourceSynchronized result = fixture.service.synchronize("record-1",
|
JsonUtil.getJsonToBean(request, ElnRecordForms.SourceSync.class));
|
|
assertEquals(null, fixture.limsValue.getZhiWenben());
|
assertEquals("CLEARED", fixture.limsValue.getYuanQuzhiZhuangtai());
|
assertEquals("NAME_MISSING", fixture.limsValue.getYuanQuzhiYuanyin());
|
assertEquals(Date.from(java.time.Instant.parse("2026-09-06T02:00:00Z")), fixture.limsValue.getYuanHuoquShijian());
|
assertEquals(1, result.getChangedCount());
|
assertTrue(fixture.auditEvents.get(0).getDataSnapshot().contains("Confirmed source no longer has a name"));
|
assertTrue(fixture.auditEvents.get(0).getFieldDiffs().contains("old basis"));
|
}
|
|
@Test
|
public void rejectsConfirmationWhenConfiguredOutcomeOrLineageChangedAfterPreview() {
|
List<java.util.function.Consumer<ElnSourceSnapshot>> changes = Arrays.asList(
|
snapshot -> { snapshot.setOutcomeCode("MISSING"); snapshot.setValue(null); },
|
snapshot -> snapshot.setLineage("task.details@2/basis"));
|
for (java.util.function.Consumer<ElnSourceSnapshot> change : changes) {
|
Fixture fixture = configuredFixture();
|
fixture.configuredSnapshot.setValue("old basis");
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
change.accept(fixture.configuredSnapshot);
|
|
try {
|
fixture.service.synchronize("record-1", syncCommand(preview));
|
fail("Changed source outcome or lineage must invalidate the preview");
|
} catch (DataException expected) { }
|
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
assertEquals(new Date(1000), fixture.limsValue.getYuanHuoquShijian());
|
}
|
}
|
|
@Test
|
public void optionalMissingSourcesKeepTheOldValueAndAcquisitionTimeDuringSync() {
|
for (String outcome : Arrays.asList("MISSING", "NOT_APPLICABLE")) {
|
Fixture fixture = configuredFixture();
|
fixture.configuredSnapshot.setOutcomeCode(outcome);
|
fixture.configuredSnapshot.setReasonCode("NAME_MISSING");
|
fixture.configuredSnapshot.setValue(null);
|
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
assertEquals(0, preview.getChangedCount());
|
assertEquals("old basis", preview.getDifferences().get(0).getCurrentValue());
|
assertEquals(new Date(1000), preview.getDifferences().get(0).getCurrentFetchedTime());
|
com.fasterxml.jackson.databind.JsonNode difference = new com.fasterxml.jackson.databind.ObjectMapper()
|
.valueToTree(preview.getDifferences().get(0));
|
assertEquals(outcome, difference.path("sourceOutcome").asText());
|
assertEquals("NAME_MISSING", difference.path("sourceReason").asText());
|
fixture.service.synchronize("record-1", syncCommand(preview));
|
|
assertEquals("old basis", fixture.limsValue.getZhiWenben());
|
assertEquals(new Date(1000), fixture.limsValue.getYuanHuoquShijian());
|
assertEquals("SUCCESS", fixture.limsValue.getYuanQuzhiZhuangtai());
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
}
|
}
|
|
@Test
|
public void explicitlySynchronizesThePinnedRevisionAndActualAcquisitionMetadata() {
|
Fixture fixture = configuredFixture();
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
assertEquals("configured V1", preview.getDifferences().get(0).getCurrentValue());
|
assertEquals(Collections.emptyList(), fixture.requestedProperties.get());
|
fixture.configuredSnapshot.setAcquiredAt("2026-09-06T03:00:00Z");
|
|
fixture.service.synchronize("record-1", syncCommand(preview));
|
|
assertEquals("configured V1", fixture.limsValue.getZhiWenben());
|
assertEquals(Integer.valueOf(1), fixture.limsValue.getYuanShuxingBanben());
|
assertEquals("task.details@1/basis", fixture.limsValue.getYuanXueyuan());
|
assertEquals(Date.from(java.time.Instant.parse("2026-09-06T03:00:00Z")), fixture.limsValue.getYuanHuoquShijian());
|
assertEquals("SUCCESS", fixture.limsValue.getYuanQuzhiZhuangtai());
|
}
|
|
@Test
|
public void retriesAnUninitializedConfiguredSnapshotThroughTheControlledSyncFlow() {
|
Fixture fixture = configuredFixture();
|
fixture.limsValue.setYuanShuxingBanben(null);
|
fixture.limsValue.setYuanQuzhiZhuangtai(null);
|
fixture.limsValue.setYuanXueyuan(null);
|
fixture.limsValue.setYuanHuoquShijian(null);
|
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
|
assertEquals(1, preview.getChangedCount());
|
assertEquals("old basis", preview.getDifferences().get(0).getPreviousValue());
|
assertEquals("configured V1", preview.getDifferences().get(0).getCurrentValue());
|
fixture.service.synchronize("record-1", syncCommand(preview));
|
assertEquals("configured V1", fixture.limsValue.getZhiWenben());
|
assertEquals(Integer.valueOf(1), fixture.limsValue.getYuanShuxingBanben());
|
assertEquals("SUCCESS", fixture.limsValue.getYuanQuzhiZhuangtai());
|
assertEquals("task.details@1/basis", fixture.limsValue.getYuanXueyuan());
|
assertEquals(Date.from(java.time.Instant.parse("2026-09-06T02:00:00Z")),
|
fixture.limsValue.getYuanHuoquShijian());
|
assertEquals("system user", fixture.systemValue.getZhiWenben());
|
assertEquals(2, fixture.updateCalls.get());
|
assertEquals(1, fixture.formulaCalls.get());
|
}
|
|
@Test
|
public void initializesAnOptionalMissingSourceAsAnEmptySnapshotDuringRecovery() {
|
Fixture fixture = configuredFixture();
|
fixture.limsValue.setYuanShuxingBanben(null);
|
fixture.limsValue.setYuanQuzhiZhuangtai(null);
|
fixture.limsValue.setYuanXueyuan(null);
|
fixture.limsValue.setYuanHuoquShijian(null);
|
fixture.configuredSnapshot.setOutcomeCode("MISSING");
|
fixture.configuredSnapshot.setReasonCode("NAME_MISSING");
|
fixture.configuredSnapshot.setValue(null);
|
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
|
assertEquals(1, preview.getChangedCount());
|
assertEquals(null, preview.getDifferences().get(0).getCurrentValue());
|
fixture.service.synchronize("record-1", syncCommand(preview));
|
assertEquals(null, fixture.limsValue.getZhiWenben());
|
assertEquals(Integer.valueOf(1), fixture.limsValue.getYuanShuxingBanben());
|
assertEquals("MISSING", fixture.limsValue.getYuanQuzhiZhuangtai());
|
assertEquals("NAME_MISSING", fixture.limsValue.getYuanQuzhiYuanyin());
|
assertEquals("task.details@1/basis", fixture.limsValue.getYuanXueyuan());
|
assertEquals(Date.from(java.time.Instant.parse("2026-09-06T02:00:00Z")),
|
fixture.limsValue.getYuanHuoquShijian());
|
}
|
|
@Test
|
public void failedRecoveryLeavesTheUninitializedRecordUntouched() {
|
Fixture fixture = configuredFixture();
|
fixture.limsValue.setYuanShuxingBanben(null);
|
fixture.limsValue.setYuanQuzhiZhuangtai(null);
|
fixture.limsValue.setYuanXueyuan(null);
|
fixture.limsValue.setYuanHuoquShijian(null);
|
fixture.configuredSnapshot.setOutcomeCode("NOT_AVAILABLE");
|
fixture.configuredSnapshot.setReasonCode("DEPENDENCY_UNAVAILABLE");
|
fixture.configuredSnapshot.setValue(null);
|
|
try {
|
fixture.service.preview("record-1");
|
fail("Unavailable source must keep recovery incomplete");
|
} catch (DataException expected) {
|
assertTrue(expected.getMessage().contains("LIMS 来源取数失败"));
|
}
|
|
assertEquals("old basis", fixture.limsValue.getZhiWenben());
|
assertEquals(null, fixture.limsValue.getYuanShuxingBanben());
|
assertEquals(null, fixture.limsValue.getYuanQuzhiZhuangtai());
|
assertEquals(null, fixture.limsValue.getYuanXueyuan());
|
assertEquals(null, fixture.limsValue.getYuanHuoquShijian());
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
assertEquals(0, fixture.formulaCalls.get());
|
}
|
|
@Test
|
public void restoresAMissingConfiguredSourceValueThroughTheControlledSyncFlow() {
|
Fixture fixture = configuredFixture();
|
fixture.includeLimsValue.set(false);
|
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
|
assertEquals(1, preview.getChangedCount());
|
assertEquals(1, preview.getDifferences().size());
|
assertEquals("binding-lims", preview.getDifferences().get(0).getBindingId());
|
assertEquals(null, preview.getDifferences().get(0).getPreviousValue());
|
assertEquals("configured V1", preview.getDifferences().get(0).getCurrentValue());
|
assertEquals(0, fixture.insertCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals("system user", fixture.systemValue.getZhiWenben());
|
|
ElnRecordVos.SourceSynchronized result = fixture.service.synchronize(
|
"record-1", syncCommand(preview));
|
|
ElnRecordFieldValueEntity restored = fixture.insertedValue.get();
|
assertNotNull(restored);
|
assertEquals("record-1", restored.getDianziJiluId());
|
assertEquals("revision-1", restored.getXiudingId());
|
assertEquals("template-1", restored.getMubanId());
|
assertEquals("binding-lims", restored.getMubanBangdingId());
|
assertEquals("version-lims", restored.getZiduanBanbenId());
|
assertEquals("definition-lims", restored.getZiduanDingyiId());
|
assertEquals("test_basis", restored.getZiduanBianma());
|
assertEquals("tenant-a", restored.getFTenantId());
|
assertEquals("configured V1", restored.getZhiWenben());
|
assertEquals(Integer.valueOf(1), restored.getYuanShuxingBanben());
|
assertEquals("SUCCESS", restored.getYuanQuzhiZhuangtai());
|
assertEquals("task.details@1/basis", restored.getYuanXueyuan());
|
assertEquals(Date.from(java.time.Instant.parse("2026-09-06T02:00:00Z")),
|
restored.getYuanHuoquShijian());
|
assertEquals(1, fixture.insertCalls.get());
|
assertEquals(1, fixture.casCalls.get());
|
assertEquals(1, fixture.formulaCalls.get());
|
assertEquals(Collections.singleton("binding-lims"), fixture.changedBindings.get());
|
assertEquals(1, result.getChangedCount());
|
assertEquals(Integer.valueOf(5), result.getRecordVersion());
|
assertEquals("system user", fixture.systemValue.getZhiWenben());
|
}
|
|
private static Fixture configuredFixture() {
|
Fixture fixture = new Fixture("old basis", "legacy basis");
|
fixture.limsVersion.setBangdingPeizhi("{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"test.basis\",\"propertyRevision\":1}}");
|
fixture.limsValue.setYuanShuxingBanben(1);
|
fixture.limsValue.setYuanQuzhiZhuangtai("SUCCESS");
|
fixture.limsValue.setYuanHuoquShijian(new Date(1000));
|
ElnSourceSnapshot snapshot = new ElnSourceSnapshot();
|
snapshot.setSourceRef("item-1");
|
snapshot.setPropertyCode("test.basis");
|
snapshot.setPropertyRevision(1);
|
snapshot.setDataType("TEXT");
|
snapshot.setValue("configured V1");
|
snapshot.setOutcomeCode("SUCCESS");
|
snapshot.setAcquiredAt("2026-09-06T02:00:00Z");
|
snapshot.setLineage("task.details@1/basis");
|
fixture.configuredSnapshot = snapshot;
|
return fixture;
|
}
|
|
@Test
|
public void previewsWithTheRecordFillTicketActorAndCurrentDocument() {
|
Fixture fixture = new Fixture("old basis", "new basis",
|
() -> { throw new AssertionError("login user must not be read"); });
|
|
ElnRecordVos.SourceSyncPreview result = fixture.service.preview(
|
recordTicket("revision-1.docx"));
|
|
assertEquals("record-1", result.getRecordId());
|
assertEquals(1, result.getChangedCount());
|
assertEquals(1, fixture.providerCalls.get());
|
}
|
|
@Test
|
public void routesRecordFillTicketWorkToTheTicketTenant() {
|
List<String> routes = new ArrayList<>();
|
ElnRecordSourceSyncServiceImpl.TenantRoute route =
|
new ElnRecordSourceSyncServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) {
|
routes.add("switch:" + tenantId);
|
}
|
@Override public void clear() { routes.add("clear"); }
|
};
|
Fixture fixture = new Fixture("old basis", "new basis",
|
() -> { throw new AssertionError("login user must not be read"); }, route);
|
|
fixture.service.preview(recordTicket("revision-1.docx"));
|
|
assertEquals(Arrays.asList("switch:tenant-a", "clear"), routes);
|
}
|
|
@Test
|
public void rejectsRecordFillTicketForAnotherDocumentBeforeReadingProvider() {
|
Fixture fixture = new Fixture("old basis", "new basis",
|
() -> { throw new AssertionError("login user must not be read"); });
|
|
try {
|
fixture.service.preview(recordTicket("stale-revision.docx"));
|
fail("expected DataException");
|
} catch (DataException e) {
|
assertTrue(e.getMessage().contains("文档与当前修订不匹配"));
|
}
|
|
assertEquals(0, fixture.providerCalls.get());
|
}
|
|
@Test
|
public void synchronizesWithTheRecordFillTicketActorAndCurrentDocument() {
|
MemoryOperationCoordinator operations = new MemoryOperationCoordinator();
|
Fixture fixture = new Fixture("old basis", "new basis",
|
() -> { throw new AssertionError("login user must not be read"); },
|
null, bindingIds -> Collections.emptyMap(), operations);
|
ElnRecordFillTicketService.FillTicketContext ticket =
|
recordTicket("revision-1.docx");
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview(ticket);
|
ElnRecordForms.SourceSync command = new ElnRecordForms.SourceSync();
|
command.setExpectedRecordVersion(preview.getRecordVersion());
|
command.setPreviewToken(preview.getPreviewToken());
|
command.setOperationCode("sync-1");
|
|
ElnRecordVos.SourceSynchronized result =
|
fixture.service.synchronize(ticket, command);
|
Date fetchedTime = new Date(123L);
|
result.getDifferences().get(0).setCurrentFetchedTime(fetchedTime);
|
|
assertEquals(Integer.valueOf(5), result.getRecordVersion());
|
assertEquals("new basis", fixture.limsValue.getZhiWenben());
|
assertEquals(2, fixture.providerCalls.get());
|
assertEquals(1, fixture.casCalls.get());
|
assertEquals(0, fixture.auditEvents.size());
|
assertEquals("sync-1", fixture.service.pending(ticket).getOperationCode());
|
ElnRecordForms.SourceSync second = new ElnRecordForms.SourceSync();
|
second.setExpectedRecordVersion(preview.getRecordVersion());
|
second.setPreviewToken(preview.getPreviewToken());
|
second.setOperationCode("sync-2");
|
try {
|
fixture.service.synchronize(ticket, second);
|
fail("expected pending document confirmation to block another operation");
|
} catch (DataException expected) {
|
assertEquals("电子记录已有来源同步等待文档保存", expected.getMessage());
|
}
|
assertEquals(1, fixture.casCalls.get());
|
|
fixture.service.confirmSavedDocument(
|
"tenant-1", "user-1", "record-1", "revision-1");
|
assertEquals("user-1", fixture.auditEvents.get(0).getOperatorId());
|
assertEquals(fetchedTime, fixture.auditEvents.get(0).getEventTime());
|
fixture.service.confirmSavedDocument(
|
"tenant-1", "user-1", "record-1", "revision-1");
|
assertEquals(1, fixture.auditEvents.size());
|
assertEquals(null, fixture.service.pending(ticket));
|
}
|
|
@Test
|
public void previewsOnlyLimsTaskFieldsWithoutWriting() {
|
Fixture fixture = new Fixture("old basis", "new basis");
|
|
ElnRecordVos.SourceSyncPreview result = fixture.service.preview("record-1");
|
|
assertEquals(Integer.valueOf(4), result.getRecordVersion());
|
assertEquals(64, result.getPreviewToken().length());
|
assertEquals(1, result.getDifferences().size());
|
assertEquals(1, result.getChangedCount());
|
assertEquals("test.basis", result.getDifferences().get(0).getSourceProperty());
|
assertEquals("old basis", result.getDifferences().get(0).getPreviousValue());
|
assertEquals("new basis", result.getDifferences().get(0).getCurrentValue());
|
assertTrue(result.getDifferences().get(0).isChanged());
|
assertEquals(1, fixture.providerCalls.get());
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
assertEquals(0, fixture.auditEvents.size());
|
assertEquals(Collections.singletonList("test.basis"), fixture.requestedProperties.get());
|
}
|
|
@Test
|
public void previewsLimsAndCrossTemplateDifferencesWithSourceLineage() {
|
ElnRecordFieldValueEntity source = value("source-value", "source-binding",
|
"source-version", "source-definition", "source_field", "MANUAL", "new source");
|
source.setDianziJiluId("source-record");
|
source.setXiudingId("source-revision");
|
Fixture fixture = new Fixture("old basis", "new basis", targetBindingIds ->
|
Collections.singletonMap("binding-cross", source));
|
fixture.crossValue = value("target-cross", "binding-cross", "version-cross",
|
"definition-cross", "cross_field", "CROSS_TEMPLATE", "old source");
|
fixture.crossValue.setKuamoLaiyuanJiluId("old-source-record");
|
fixture.crossValue.setKuamoLaiyuanXiudingId("old-source-revision");
|
fixture.crossValue.setKuamoLaiyuanZhiId("old-source-value");
|
fixture.crossValue.setKuamoLaiyuanZiduanBanbenId("old-source-version");
|
|
ElnRecordVos.SourceSyncPreview result = fixture.service.preview("record-1");
|
|
assertEquals(2, result.getDifferences().size());
|
assertEquals(2, result.getChangedCount());
|
ElnRecordVos.SourceSyncDifference cross = result.getDifferences().get(1);
|
assertEquals("CROSS_TEMPLATE", cross.getSourceType());
|
assertEquals("old source", cross.getPreviousValue());
|
assertEquals("new source", cross.getCurrentValue());
|
assertEquals("source-record", cross.getSourceRecordId());
|
assertEquals("source-revision", cross.getSourceRevisionId());
|
assertEquals("source-value", cross.getSourceValueId());
|
assertEquals("source-version", cross.getSourceFieldVersionId());
|
assertNotNull(cross.getSourceFetchedAt());
|
}
|
|
@Test
|
public void rejectsWholeSyncWhenCrossTemplateSourceChangesAfterPreview() {
|
AtomicReference<String> sourceText = new AtomicReference<>("new source");
|
Fixture fixture = new Fixture("old basis", "new basis", targetBindingIds -> {
|
ElnRecordFieldValueEntity source = value("source-value", "source-binding",
|
"source-version", "source-definition", "source_field", "MANUAL", sourceText.get());
|
source.setDianziJiluId("source-record");
|
source.setXiudingId("source-revision");
|
return Collections.singletonMap("binding-cross", source);
|
});
|
fixture.crossValue = value("target-cross", "binding-cross", "version-cross",
|
"definition-cross", "cross_field", "CROSS_TEMPLATE", "old source");
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
sourceText.set("newer source");
|
ElnRecordForms.SourceSync command = new ElnRecordForms.SourceSync();
|
command.setExpectedRecordVersion(4);
|
command.setPreviewToken(preview.getPreviewToken());
|
command.setOperationCode("sync-stale-cross");
|
|
try {
|
fixture.service.synchronize("record-1", command);
|
fail("expected DataException");
|
} catch (DataException e) {
|
assertTrue(e.getMessage().contains("重新预览"));
|
}
|
|
assertEquals("old basis", fixture.limsValue.getZhiWenben());
|
assertEquals("old source", fixture.crossValue.getZhiWenben());
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
}
|
|
@Test
|
public void synchronizesCrossTemplateValueAndRefreshesAllLineage() {
|
ElnRecordFieldValueEntity source = value("source-value", "source-binding",
|
"source-version", "source-definition", "source_field", "MANUAL", "new source");
|
source.setDianziJiluId("source-record");
|
source.setXiudingId("source-revision");
|
Fixture fixture = new Fixture("same basis", "same basis", targetBindingIds ->
|
Collections.singletonMap("binding-cross", source));
|
fixture.crossValue = value("target-cross", "binding-cross", "version-cross",
|
"definition-cross", "cross_field", "CROSS_TEMPLATE", "old source");
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
ElnRecordForms.SourceSync command = new ElnRecordForms.SourceSync();
|
command.setExpectedRecordVersion(preview.getRecordVersion());
|
command.setPreviewToken(preview.getPreviewToken());
|
command.setOperationCode("sync-1");
|
|
ElnRecordVos.SourceSynchronized result = fixture.service.synchronize("record-1", command);
|
|
assertEquals(1, result.getChangedCount());
|
assertEquals("new source", fixture.crossValue.getZhiWenben());
|
assertEquals("source-record", fixture.crossValue.getKuamoLaiyuanJiluId());
|
assertEquals("source-revision", fixture.crossValue.getKuamoLaiyuanXiudingId());
|
assertEquals("source-value", fixture.crossValue.getKuamoLaiyuanZhiId());
|
assertEquals("source-version", fixture.crossValue.getKuamoLaiyuanZiduanBanbenId());
|
assertNotNull(fixture.crossValue.getKuamoHuoquShijian());
|
assertEquals(Collections.singleton("binding-cross"), fixture.changedBindings.get());
|
}
|
|
@Test
|
public void synchronizesDecimalCrossTemplateValueThroughTheNumericSlot() {
|
ElnRecordFieldValueEntity source = value("source-decimal", "source-binding",
|
"source-version", "source-definition", "source_decimal", "MANUAL", null);
|
source.setDianziJiluId("source-record");
|
source.setXiudingId("source-revision");
|
source.setZhiWenben("wrong text slot");
|
source.setZhiShuzhi(new BigDecimal("12.50"));
|
Fixture fixture = new Fixture("same basis", "same basis", targetBindingIds ->
|
Collections.singletonMap("binding-cross", source));
|
fixture.crossDataType.set("DECIMAL");
|
fixture.crossValue = value("target-decimal", "binding-cross", "version-cross",
|
"definition-cross", "cross_decimal", "CROSS_TEMPLATE", "stale text");
|
fixture.crossValue.setZhiWenben(null);
|
fixture.crossValue.setZhiShuzhi(new BigDecimal("3.00"));
|
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
ElnRecordVos.SourceSyncDifference difference = preview.getDifferences().get(1);
|
assertEquals("DECIMAL", difference.getDataType());
|
assertEquals("3.00", difference.getPreviousValue());
|
assertEquals("12.50", difference.getCurrentValue());
|
|
ElnRecordForms.SourceSync command = syncCommand(preview);
|
fixture.service.synchronize("record-1", command);
|
|
assertEquals(new BigDecimal("12.50"), fixture.crossValue.getZhiShuzhi());
|
assertEquals(null, fixture.crossValue.getZhiWenben());
|
assertEquals(null, fixture.crossValue.getZhiRiqi());
|
}
|
|
@Test
|
public void synchronizesDateCrossTemplateValueThroughTheDateSlot() {
|
Date sourceDate = new Date(1700000000000L);
|
ElnRecordFieldValueEntity source = value("source-date", "source-binding",
|
"source-version", "source-definition", "source_date", "MANUAL", null);
|
source.setDianziJiluId("source-record");
|
source.setXiudingId("source-revision");
|
source.setZhiWenben("wrong text slot");
|
source.setZhiRiqi(sourceDate);
|
Fixture fixture = new Fixture("same basis", "same basis", targetBindingIds ->
|
Collections.singletonMap("binding-cross", source));
|
fixture.crossDataType.set("DATE");
|
fixture.crossValue = value("target-date", "binding-cross", "version-cross",
|
"definition-cross", "cross_date", "CROSS_TEMPLATE", "stale text");
|
fixture.crossValue.setZhiWenben(null);
|
fixture.crossValue.setZhiRiqi(new Date(1600000000000L));
|
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
ElnRecordVos.SourceSyncDifference difference = preview.getDifferences().get(1);
|
assertEquals("DATE", difference.getDataType());
|
assertEquals(new SimpleDateFormat("yyyy-MM-dd").format(sourceDate),
|
difference.getCurrentValue());
|
|
ElnRecordForms.SourceSync command = syncCommand(preview);
|
fixture.service.synchronize("record-1", command);
|
|
assertEquals(sourceDate, fixture.crossValue.getZhiRiqi());
|
assertEquals(null, fixture.crossValue.getZhiWenben());
|
assertEquals(null, fixture.crossValue.getZhiShuzhi());
|
}
|
|
@Test
|
public void synchronizesChangedSnapshotAndRecalculatesAffectedClosure() {
|
Fixture fixture = new Fixture("old basis", "new basis");
|
ElnRecordSourceSyncService application = fixture.service;
|
ElnRecordVos.SourceSyncPreview preview = application.preview("record-1");
|
ElnRecordForms.SourceSync command = new ElnRecordForms.SourceSync();
|
command.setExpectedRecordVersion(4);
|
command.setPreviewToken(preview.getPreviewToken());
|
command.setOperationCode("sync-1");
|
|
ElnRecordVos.SourceSynchronized result = application.synchronize("record-1", command);
|
|
assertEquals(Integer.valueOf(5), result.getRecordVersion());
|
assertEquals(1, result.getChangedCount());
|
assertEquals(1, result.getRecalculatedCount());
|
assertEquals(Arrays.asList("test_basis", "formula_result"),
|
result.getChangedFieldCodes());
|
assertEquals("new basis", fixture.limsValue.getZhiWenben());
|
assertEquals("LIMS_TASK", fixture.limsValue.getZhiLaiyuan());
|
assertEquals("item-1", fixture.limsValue.getYuanYinyongId());
|
assertEquals("system user", fixture.systemValue.getZhiWenben());
|
assertEquals(Collections.singleton("binding-lims"), fixture.changedBindings.get());
|
assertEquals(1, fixture.casCalls.get());
|
assertEquals(2, fixture.updateCalls.get());
|
assertEquals(1, fixture.auditEvents.size());
|
assertEquals("sync-1", fixture.auditEvents.get(0).getOperationId());
|
String fieldDiffs = fixture.auditEvents.get(0).getFieldDiffs();
|
assertTrue(fieldDiffs.contains("\"field\":\"zhi_wenben\""));
|
assertTrue(fieldDiffs.contains("\"fieldName\":\"检验依据\""));
|
assertTrue(fieldDiffs.contains("\"oldData\":\"old basis\""));
|
assertTrue(fieldDiffs.contains("\"newData\":\"new basis\""));
|
assertTrue(fieldDiffs.contains("\"targetTable\":\"eln_record_field_value\""));
|
assertTrue(fieldDiffs.contains("\"targetId\":\"value-lims\""));
|
assertTrue(fieldDiffs.contains("\"elnFieldCode\":\"test_basis\""));
|
assertTrue(fieldDiffs.contains("\"sourceProperty\":\"test.basis\""));
|
}
|
|
@Test
|
public void recordsNoChangeSyncWithoutCasOrFormula() {
|
Fixture fixture = new Fixture("same basis", "same basis");
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
ElnRecordForms.SourceSync command = new ElnRecordForms.SourceSync();
|
command.setExpectedRecordVersion(4);
|
command.setPreviewToken(preview.getPreviewToken());
|
command.setOperationCode("sync-2");
|
|
ElnRecordVos.SourceSynchronized result = fixture.service.synchronize("record-1", command);
|
|
assertEquals(Integer.valueOf(4), result.getRecordVersion());
|
assertEquals(0, result.getChangedCount());
|
assertFalse(result.getDifferences().get(0).isChanged());
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
assertEquals(0, fixture.formulaCalls.get());
|
assertEquals(1, fixture.auditEvents.size());
|
assertEquals("BIZ_ACTION", fixture.auditEvents.get(0).getEventType());
|
}
|
|
@Test
|
public void replaysChangedAndUnchangedSyncOperationsWithoutExecutingAgain() {
|
assertOperationReplay("old basis", "new basis", 1);
|
assertOperationReplay("same basis", "same basis", 0);
|
}
|
|
@Test
|
public void rejectsReusingSyncOperationCodeForAnotherRequest() {
|
MemoryOperationCoordinator operations = new MemoryOperationCoordinator();
|
Fixture fixture = new Fixture("same basis", "same basis", operations);
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
ElnRecordForms.SourceSync first = syncCommand(preview);
|
fixture.service.synchronize("record-1", first);
|
ElnRecordForms.SourceSync changed = syncCommand(preview);
|
changed.setPreviewToken(preview.getPreviewToken() + "-other");
|
|
try {
|
fixture.service.synchronize("record-1", changed);
|
fail("同一操作码的不同请求必须冲突");
|
} catch (DataException expected) {
|
assertTrue(expected.getMessage().contains("操作码已用于其他请求"));
|
}
|
assertEquals(1, fixture.auditEvents.size());
|
}
|
|
private static void assertOperationReplay(
|
String previousValue, String currentValue, int expectedCasCalls) {
|
MemoryOperationCoordinator operations = new MemoryOperationCoordinator();
|
Fixture fixture = new Fixture(previousValue, currentValue, operations);
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
ElnRecordForms.SourceSync command = syncCommand(preview);
|
|
ElnRecordVos.SourceSynchronized first = fixture.service.synchronize("record-1", command);
|
ElnRecordVos.SourceSynchronized replay = fixture.service.synchronize("record-1", command);
|
|
assertEquals(first.getRecordVersion(), replay.getRecordVersion());
|
assertEquals(expectedCasCalls, fixture.casCalls.get());
|
assertEquals(1, fixture.auditEvents.size());
|
assertEquals(2, operations.beginCalls.get());
|
assertEquals(1, operations.completeCalls.get());
|
}
|
|
@Test
|
public void rejectsConfirmationWhenSnapshotChangedAfterPreview() {
|
Fixture fixture = new Fixture("old basis", "new basis");
|
ElnRecordVos.SourceSyncPreview preview = fixture.service.preview("record-1");
|
fixture.currentValue.set("newer basis");
|
ElnRecordForms.SourceSync command = new ElnRecordForms.SourceSync();
|
command.setExpectedRecordVersion(4);
|
command.setPreviewToken(preview.getPreviewToken());
|
command.setOperationCode("sync-stale-preview");
|
|
try {
|
fixture.service.synchronize("record-1", command);
|
fail("expected DataException");
|
} catch (DataException e) {
|
assertTrue(e.getMessage().contains("重新预览"));
|
}
|
|
assertEquals("old basis", fixture.limsValue.getZhiWenben());
|
assertEquals(0, fixture.casCalls.get());
|
assertEquals(0, fixture.updateCalls.get());
|
assertEquals(0, fixture.formulaCalls.get());
|
assertEquals(0, fixture.auditEvents.size());
|
}
|
|
@Test
|
public void rejectsNonDraftRevisionBeforeReadingProvider() {
|
Fixture fixture = new Fixture("old basis", "new basis");
|
fixture.revision.setBizStatus("FROZEN");
|
|
try {
|
fixture.service.preview("record-1");
|
fail("expected DataException");
|
} catch (DataException e) {
|
assertTrue(e.getMessage().contains("不允许重新同步"));
|
}
|
assertEquals(0, fixture.providerCalls.get());
|
}
|
|
@Test
|
public void rejectsSyncWhenTestWorkItemIsNoLongerPendingTest() {
|
Fixture fixture = new Fixture("old basis", "new basis");
|
fixture.taskStatus.set("pending_audit");
|
|
try {
|
fixture.service.preview("record-1");
|
fail("expected DataException");
|
} catch (DataException e) {
|
assertTrue(e.getMessage().contains("不允许重新同步"));
|
}
|
|
assertEquals(1, fixture.providerCalls.get());
|
assertEquals(0, fixture.casCalls.get());
|
}
|
|
private static final class Fixture {
|
private final ElnRecordEntity record = record();
|
private final ElnRecordRevisionEntity revision = revision();
|
private final ElnRecordFieldValueEntity limsValue;
|
private final ElnRecordFieldValueEntity systemValue;
|
private ElnRecordFieldValueEntity crossValue;
|
private final ElnFieldVersionEntity limsVersion = version("version-lims", "definition-lims",
|
"检验依据", "LIMS_TASK", "test.basis");
|
private ElnSourceSnapshot configuredSnapshot;
|
private final AtomicInteger providerCalls = new AtomicInteger();
|
private final AtomicInteger casCalls = new AtomicInteger();
|
private final AtomicInteger insertCalls = new AtomicInteger();
|
private final AtomicInteger updateCalls = new AtomicInteger();
|
private final AtomicInteger formulaCalls = new AtomicInteger();
|
private final AtomicBoolean includeLimsValue = new AtomicBoolean(true);
|
private final AtomicReference<ElnRecordFieldValueEntity> insertedValue = new AtomicReference<>();
|
private final AtomicReference<List<String>> requestedProperties = new AtomicReference<>();
|
private final AtomicReference<Set<String>> changedBindings = new AtomicReference<>();
|
private final AtomicReference<String> currentValue = new AtomicReference<>();
|
private final AtomicReference<String> taskStatus = new AtomicReference<>("pending_test");
|
private final AtomicReference<String> crossDataType = new AtomicReference<>("TEXT");
|
private final List<AuditEventDTO> auditEvents = new ArrayList<>();
|
private final ElnRecordSourceSyncServiceImpl service;
|
|
private Fixture(String previousValue, String currentValue) {
|
this(previousValue, currentValue,
|
() -> new ElnRecordSourceSyncServiceImpl.UserContext(
|
"tenant-a", "user-1"));
|
}
|
|
private Fixture(String previousValue, String currentValue,
|
ElnRecordSourceSyncOperationCoordinator operationCoordinator) {
|
this(previousValue, currentValue,
|
() -> new ElnRecordSourceSyncServiceImpl.UserContext("tenant-a", "user-1"),
|
null, bindingIds -> Collections.emptyMap(), operationCoordinator);
|
}
|
|
private Fixture(String previousValue, String currentValue,
|
ElnRecordSourceSyncServiceImpl.CurrentUserProvider currentUserProvider) {
|
this(previousValue, currentValue, currentUserProvider, null);
|
}
|
|
private Fixture(String previousValue, String currentValue,
|
ElnRecordSourceSyncServiceImpl.CurrentUserProvider currentUserProvider,
|
ElnRecordSourceSyncServiceImpl.TenantRoute tenantRoute) {
|
this(previousValue, currentValue, currentUserProvider, tenantRoute,
|
bindingIds -> Collections.emptyMap());
|
}
|
|
private Fixture(String previousValue, String currentValue,
|
java.util.function.Function<Set<String>, Map<String,
|
ElnRecordFieldValueEntity>> crossResolver) {
|
this(previousValue, currentValue,
|
() -> new ElnRecordSourceSyncServiceImpl.UserContext("tenant-a", "user-1"),
|
null, crossResolver);
|
}
|
|
private Fixture(String previousValue, String currentValue,
|
ElnRecordSourceSyncServiceImpl.CurrentUserProvider currentUserProvider,
|
ElnRecordSourceSyncServiceImpl.TenantRoute tenantRoute,
|
java.util.function.Function<Set<String>, Map<String,
|
ElnRecordFieldValueEntity>> crossResolver) {
|
this(previousValue, currentValue, currentUserProvider, tenantRoute, crossResolver,
|
ElnRecordSourceSyncOperationCoordinator.none());
|
}
|
|
private Fixture(String previousValue, String currentValue,
|
ElnRecordSourceSyncServiceImpl.CurrentUserProvider currentUserProvider,
|
ElnRecordSourceSyncServiceImpl.TenantRoute tenantRoute,
|
java.util.function.Function<Set<String>, Map<String,
|
ElnRecordFieldValueEntity>> crossResolver,
|
ElnRecordSourceSyncOperationCoordinator operationCoordinator) {
|
this.currentValue.set(currentValue);
|
limsValue = value("value-lims", "binding-lims", "version-lims",
|
"definition-lims", "test_basis", "LIMS_TASK", previousValue);
|
limsValue.setYuanShuxing("test.basis");
|
limsValue.setYuanYinyongLeixing("LIMS_TASK");
|
limsValue.setYuanYinyongId("item-1");
|
systemValue = value("value-system", "binding-system", "version-system",
|
"definition-system", "operator", "SYSTEM", "system user");
|
ElnFieldVersionEntity systemVersion = version("version-system", "definition-system",
|
"检验人", "SYSTEM", "current_user.real_name");
|
ElnFieldVersionEntity crossVersion = version("version-cross", "definition-cross",
|
"跨模板字段", null, null);
|
AtomicInteger versionReads = new AtomicInteger();
|
ElnRecordMapper recordMapper = proxy(ElnRecordMapper.class, (method, args) -> {
|
if ("selectOne".equals(method)) return record;
|
if ("advanceVersion".equals(method)) {
|
assertEquals(4, args[2]);
|
casCalls.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
ElnRecordRevisionMapper revisionMapper = proxy(
|
ElnRecordRevisionMapper.class,
|
(method, args) -> "selectOne".equals(method) ? revision : null);
|
ElnTemplateBindingMapper bindingMapper = proxy(
|
ElnTemplateBindingMapper.class, (method, args) -> {
|
if (!"selectList".equals(method)) return null;
|
List<ElnTemplateBindingEntity> bindings = new ArrayList<>(Arrays.asList(
|
binding("binding-lims", "version-lims", "definition-lims", "test_basis"),
|
binding("binding-system", "version-system", "definition-system", "operator")));
|
if (crossValue != null) {
|
bindings.add(binding("binding-cross", "version-cross",
|
"definition-cross", "cross_field"));
|
}
|
return bindings;
|
});
|
ElnRecordFieldValueMapper valueMapper = proxy(
|
ElnRecordFieldValueMapper.class, (method, args) -> {
|
if ("selectList".equals(method)) {
|
versionReads.set(0);
|
List<ElnRecordFieldValueEntity> values = new ArrayList<>();
|
if (includeLimsValue.get()) values.add(limsValue);
|
values.add(systemValue);
|
if (crossValue != null) values.add(crossValue);
|
return values;
|
}
|
if ("insert".equals(method)) {
|
insertCalls.incrementAndGet();
|
insertedValue.set((ElnRecordFieldValueEntity) args[0]);
|
return 1;
|
}
|
if ("updateById".equals(method) || "updateConfiguredSnapshot".equals(method)) {
|
updateCalls.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
ElnFieldVersionMapper versionMapper = proxy(
|
ElnFieldVersionMapper.class, (method, args) -> {
|
if (!"selectOne".equals(method)) return null;
|
int index = versionReads.getAndIncrement();
|
if (!includeLimsValue.get()) {
|
if (index == 0) return systemVersion;
|
return limsVersion;
|
}
|
index %= 3;
|
if (index == 1) return systemVersion;
|
if (index == 2) {
|
crossVersion.setShujuLeixing(crossDataType.get());
|
return crossVersion;
|
}
|
return limsVersion;
|
});
|
ElnTestWorkItemSnapshotProvider provider = new ElnTestWorkItemSnapshotProvider() {
|
@Override
|
public List<ElnSourceSnapshot> resolveConfiguredAsActor(String tenantId, String userId,
|
List<String> itemIds, List<ElnSourcePropertyRef> properties) {
|
assertEquals("tenant-a", tenantId);
|
assertEquals("user-1", userId);
|
assertEquals(Collections.singletonList("item-1"), itemIds);
|
assertEquals(Collections.singletonList(new ElnSourcePropertyRef("test.basis", 1)), properties);
|
return Collections.singletonList(configuredSnapshot);
|
}
|
|
@Override
|
public TestWorkItemSnapshotBatch resolve(
|
List<String> testWorkItemIds, List<String> properties) {
|
throw new AssertionError("explicit actor provider required");
|
}
|
|
@Override
|
public TestWorkItemSnapshotBatch resolveAsActor(
|
String tenantId, String userId,
|
List<String> testWorkItemIds, List<String> properties) {
|
assertEquals("tenant-a", tenantId);
|
assertEquals("user-1", userId);
|
assertEquals(Collections.singletonList("item-1"), testWorkItemIds);
|
providerCalls.incrementAndGet();
|
requestedProperties.set(properties);
|
return snapshot(Fixture.this.currentValue.get(), Fixture.this.taskStatus.get());
|
}
|
};
|
ElnRecordFormulaCalculationService calculation =
|
new ElnRecordFormulaCalculationService() {
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAffected(
|
String tenantId, ElnRecordEntity actualRecord,
|
ElnRecordRevisionEntity actualRevision,
|
List<ElnRecordFieldValueEntity> values,
|
Set<String> changedBindingIds,
|
String operationCode, String operatorUserId) {
|
formulaCalls.incrementAndGet();
|
changedBindings.set(changedBindingIds);
|
assertEquals("sync-1", operationCode);
|
ElnRecordFieldValueEntity result = new ElnRecordFieldValueEntity();
|
result.setId("formula-value");
|
result.setZiduanBianma("formula_result");
|
return Collections.singletonList(result);
|
}
|
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAll(
|
String tenantId, ElnRecordEntity record,
|
ElnRecordRevisionEntity revision,
|
List<ElnRecordFieldValueEntity> values,
|
String operationCode, String operatorUserId) {
|
return Collections.emptyList();
|
}
|
};
|
ElnCrossTemplateSnapshotResolver resolver = new ElnCrossTemplateSnapshotResolver() {
|
@Override
|
public Map<String, ElnRecordFieldValueEntity> resolve(String tenantId,
|
String testWorkItemId, List<jnpf.elnEntity.template.ElnTemplateBindingEntity> bindings) {
|
return Collections.emptyMap();
|
}
|
|
@Override
|
public Map<String, ElnRecordFieldValueEntity> resolveByBindingIds(String tenantId,
|
String testWorkItemId, Set<String> bindingIds) {
|
return crossResolver.apply(bindingIds);
|
}
|
};
|
service = tenantRoute == null
|
? new ElnRecordSourceSyncServiceImpl(
|
recordMapper, revisionMapper, valueMapper, bindingMapper, versionMapper,
|
provider, calculation, auditEvents::add, currentUserProvider, resolver,
|
operationCoordinator)
|
: new ElnRecordSourceSyncServiceImpl(
|
recordMapper, revisionMapper, valueMapper, bindingMapper, versionMapper,
|
provider, calculation, auditEvents::add, currentUserProvider,
|
tenantRoute, null, null, resolver, operationCoordinator);
|
}
|
}
|
|
private static final class MemoryOperationCoordinator
|
implements ElnRecordSourceSyncOperationCoordinator {
|
private final AtomicInteger beginCalls = new AtomicInteger();
|
private final AtomicInteger completeCalls = new AtomicInteger();
|
private String operationCode;
|
private String digest;
|
private ElnRecordVos.SourceSynchronized result;
|
private boolean completed;
|
|
@Override
|
public ElnRecordVos.SourceSynchronized begin(
|
String tenantId, String userId, String operationCode, String requestDigest) {
|
beginCalls.incrementAndGet();
|
if (digest == null) {
|
this.operationCode = operationCode;
|
digest = requestDigest;
|
return null;
|
}
|
if (!this.operationCode.equals(operationCode)) return null;
|
if (!digest.equals(requestDigest)) {
|
throw new DataException("来源同步操作码已用于其他请求");
|
}
|
return result;
|
}
|
|
@Override
|
public ElnRecordVos.SourceSynchronized complete(
|
String tenantId, String userId, String operationCode, String requestDigest,
|
ElnRecordVos.SourceSynchronized completed) {
|
completeCalls.incrementAndGet();
|
result = completed;
|
this.completed = true;
|
return completed;
|
}
|
|
@Override
|
public ElnRecordVos.SourceSynchronized prepare(
|
String tenantId, String userId, String operationCode, String requestDigest,
|
ElnRecordVos.SourceSynchronized prepared) {
|
result = prepared;
|
return prepared;
|
}
|
|
@Override
|
public ElnRecordVos.SourceSynchronized pending(
|
String tenantId, String userId, String recordId) {
|
return completed || result == null || !recordId.equals(result.getRecordId())
|
? null : result;
|
}
|
|
@Override
|
public boolean confirm(String tenantId, String userId, String operationCode) {
|
if (completed) return false;
|
completed = true;
|
return true;
|
}
|
|
@Override
|
public boolean hasPendingDocumentConfirmation(String tenantId, String recordId) {
|
return !completed && result != null && recordId.equals(result.getRecordId());
|
}
|
}
|
|
private static ElnRecordEntity record() {
|
ElnRecordEntity value = new ElnRecordEntity();
|
value.setId("record-1");
|
value.setJiluBianhao("ELN-1");
|
value.setJianyanXiangId("item-1");
|
value.setMubanId("template-1");
|
value.setDangqianXiudingId("revision-1");
|
value.setFVersion(4);
|
return value;
|
}
|
|
private static ElnRecordRevisionEntity revision() {
|
ElnRecordRevisionEntity value = new ElnRecordRevisionEntity();
|
value.setId("revision-1");
|
value.setDianziJiluId("record-1");
|
value.setJiluWendang("revision-1.docx");
|
value.setBizStatus("DRAFT");
|
return value;
|
}
|
|
private static ElnRecordFillTicketService.FillTicketContext recordTicket(String fileId) {
|
Map<String, String> payload = ticketPayload(fileId);
|
payload.put("scope", ElnRecordFillTicketService.SCOPE);
|
payload.put("bizModule", ElnRecordFillTicketService.BIZ_MODULE);
|
payload.put("bizScene", ElnRecordFillTicketService.BIZ_SCENE);
|
return parseTicket(payload);
|
}
|
|
private static Map<String, String> ticketPayload(String fileId) {
|
Map<String, String> payload = new LinkedHashMap<>();
|
payload.put("tenantId", "tenant-a");
|
payload.put("userId", "user-1");
|
payload.put("fileId", fileId);
|
payload.put("bizDataId", "record-1");
|
payload.put("pluginGuid", "asc.{7F4E8F35-5D66-47F8-A5B4-6AB3FAACF565}");
|
return payload;
|
}
|
|
private static ElnRecordFillTicketService.FillTicketContext parseTicket(
|
Map<String, String> payload) {
|
return new ElnRecordFillTicketService(
|
ticket -> JsonUtil.getObjectToString(payload)).parse("ticket-1");
|
}
|
|
private static ElnRecordFieldValueEntity value(
|
String id, String bindingId, String versionId, String definitionId,
|
String fieldCode, String source, String text) {
|
ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity();
|
value.setId(id);
|
value.setDianziJiluId("record-1");
|
value.setXiudingId("revision-1");
|
value.setMubanId("template-1");
|
value.setMubanBangdingId(bindingId);
|
value.setZiduanBanbenId(versionId);
|
value.setZiduanDingyiId(definitionId);
|
value.setZiduanBianma(fieldCode);
|
value.setBangdingJuese("PRIMARY");
|
value.setBangdingZhuangtai("ACTIVE");
|
value.setZhiLaiyuan(source);
|
value.setZhiWenben(text);
|
return value;
|
}
|
|
private static ElnTemplateBindingEntity binding(
|
String id, String versionId, String definitionId, String fieldCode) {
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId(id);
|
binding.setMubanId("template-1");
|
binding.setZiduanBanbenId(versionId);
|
binding.setZiduanDingyiId(definitionId);
|
binding.setZiduanBianma(fieldCode);
|
binding.setKongjianJuese("PRIMARY");
|
binding.setBangdingZhuangtai("ACTIVE");
|
return binding;
|
}
|
|
private static ElnRecordForms.SourceSync syncCommand(ElnRecordVos.SourceSyncPreview preview) {
|
ElnRecordForms.SourceSync command = new ElnRecordForms.SourceSync();
|
command.setExpectedRecordVersion(preview.getRecordVersion());
|
command.setPreviewToken(preview.getPreviewToken());
|
command.setOperationCode("sync-1");
|
return command;
|
}
|
|
private static ElnFieldVersionEntity version(
|
String id, String definitionId, String name,
|
String source, String property) {
|
ElnFieldVersionEntity value = new ElnFieldVersionEntity();
|
value.setId(id);
|
value.setZiduanDingyiId(definitionId);
|
value.setZiduanMingcheng(name);
|
value.setShujuLeixing("TEXT");
|
value.setBangdingPeizhi("{\"read\":{\"source\":\"" + source
|
+ "\",\"property\":\"" + property
|
+ "\"},\"write\":{\"target\":\"ELN_RECORD\"}}");
|
return value;
|
}
|
|
private static TestWorkItemSnapshotBatch snapshot(String currentValue, String taskStatus) {
|
TestWorkItemSnapshotProperty property = new TestWorkItemSnapshotProperty();
|
property.setProperty("test.basis");
|
property.setDataType("TEXT");
|
property.setSource("LIMS_TASK");
|
property.setSourceRef("item-1");
|
property.setValue(currentValue);
|
property.setOutcomeCode("SUCCESS");
|
TestWorkItemSnapshot item = new TestWorkItemSnapshot();
|
item.setTestWorkItemId("item-1");
|
item.setTaskStatus(taskStatus);
|
item.setOutcomeCode("SUCCESS");
|
item.setProperties(Collections.singletonList(property));
|
TestWorkItemSnapshotBatch batch = new TestWorkItemSnapshotBatch();
|
batch.setItems(Collections.singletonList(item));
|
return batch;
|
}
|
|
@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);
|
}
|
}
|