package jnpf.elnService.impl; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.runtime.ElnRecordEntity; import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity; import jnpf.elnEntity.runtime.ElnRecordForms; import jnpf.elnEntity.runtime.ElnRecordRepeatRowEntity; import jnpf.elnEntity.runtime.ElnRecordRepeatValueEntity; import jnpf.elnEntity.runtime.ElnRecordRevisionEntity; import jnpf.elnEntity.runtime.ElnRecordVos; import jnpf.elnMapper.ElnFieldVersionMapper; import jnpf.elnMapper.ElnRecordFieldValueMapper; import jnpf.elnMapper.ElnRecordMapper; import jnpf.elnMapper.ElnRecordRepeatRowMapper; import jnpf.elnMapper.ElnRecordRepeatValueMapper; import jnpf.elnMapper.ElnRecordRevisionMapper; import jnpf.elnService.ElnTestWorkItemSnapshotProvider; import jnpf.elnService.ElnRecordFormulaCalculationService; import jnpf.elnService.template.ElnTemplateFileService; import jnpf.exception.DataException; import jnpf.lims.api.model.TestWorkItemSnapshotBatch; import jnpf.lims.api.model.TestWorkItemSnapshot; import jnpf.lims.api.model.TestWorkItemSnapshotProperty; import org.junit.Test; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.lang.reflect.Proxy; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class ElnRecordRevisionServiceImplTest { @Test public void auditRejectedCreatesExclusiveRevisionAndRefreshesAuthoritativeValues() { Fixture fixture = new Fixture("audit_rejected"); ElnRecordVos.Created created = fixture.open("revision-operation-1"); assertFalse(created.isExisting()); assertEquals("record-1", created.getRecordId()); assertNotEquals("revision-1", created.getRevisionId()); assertEquals(created.getRevisionId() + ".docx", created.getDocument().getFileId()); assertEquals("HISTORICAL", fixture.sourceRevision.getBizStatus()); assertEquals(created.getRevisionId(), fixture.record.getDangqianXiudingId()); assertEquals(5, fixture.record.getFVersion().intValue()); assertEquals(1, fixture.markHistoricalCalls.get()); assertEquals(1, fixture.switchCalls.get()); assertEquals(1, fixture.copyCalls.get()); assertTrue(fixture.copySource.startsWith("[")); assertTrue(fixture.copySource.contains("revision-1.docx")); assertEquals(created.getRevisionId(), fixture.copyTarget); assertEquals(3, fixture.insertedValues.size()); ElnRecordFieldValueEntity manual = fixture.insertedValues.get(0); assertEquals("manual value", manual.getZhiWenben()); assertEquals("MANUAL", manual.getZhiLaiyuan()); ElnRecordFieldValueEntity lims = fixture.insertedValues.get(1); assertEquals("new basis", lims.getZhiWenben()); assertEquals("LIMS_TASK", lims.getZhiLaiyuan()); assertEquals("test.basis", lims.getYuanShuxing()); assertEquals("item-1", lims.getYuanYinyongId()); ElnRecordFieldValueEntity system = fixture.insertedValues.get(2); assertEquals("新操作人", system.getZhiWenben()); assertEquals("SYSTEM", system.getZhiLaiyuan()); assertEquals("user-2", system.getYuanYinyongId()); assertEquals(1, fixture.recalculateAllCalls.get()); assertEquals(1, fixture.formulaUpdateCalls.get()); assertEquals(1, fixture.fenceAcquireCalls.get()); assertEquals(1, fixture.fenceCompleteCalls.get()); assertEquals(0, fixture.fenceReleaseCalls.get()); assertEquals(4, fixture.insertedRepeatRows.size()); assertEquals(4, fixture.insertedRepeatValues.size()); for (int index = 0; index < fixture.sourceRepeatRows.size(); index++) { ElnRecordRepeatRowEntity source = fixture.sourceRepeatRows.get(index); ElnRecordRepeatRowEntity copied = fixture.insertedRepeatRows.get(index); assertNotEquals(source.getId(), copied.getId()); assertEquals(created.getRevisionId(), copied.getXiudingId()); assertEquals(source.getRepeatGroupCode(), copied.getRepeatGroupCode()); assertEquals(source.getRowId(), copied.getRowId()); assertEquals(source.getRowOrder(), copied.getRowOrder()); assertEquals(source.getOperationCode(), copied.getOperationCode()); assertEquals(source.getOperationStatus(), copied.getOperationStatus()); assertEquals(source.getDeleteReason(), copied.getDeleteReason()); assertEquals(source.getFDeleteMark(), copied.getFDeleteMark()); assertEquals(0, copied.getFVersion().intValue()); assertEquals("user-2", copied.getFCreatorUserId()); assertEquals("revision-1", source.getXiudingId()); assertEquals(source.getRowOrder(), source.getFVersion()); ElnRecordRepeatValueEntity sourceValue = fixture.sourceRepeatValues.get(index); ElnRecordRepeatValueEntity copiedValue = fixture.insertedRepeatValues.get(index); assertNotEquals(sourceValue.getId(), copiedValue.getId()); assertEquals(created.getRevisionId(), copiedValue.getXiudingId()); assertEquals(sourceValue.getRepeatRowId(), copiedValue.getRepeatRowId()); assertEquals(sourceValue.getRepeatGroupCode(), copiedValue.getRepeatGroupCode()); assertEquals(sourceValue.getMubanBangdingId(), copiedValue.getMubanBangdingId()); assertEquals(sourceValue.getZhiWenben(), copiedValue.getZhiWenben()); assertEquals(sourceValue.getFDeleteMark(), copiedValue.getFDeleteMark()); assertEquals(0, copiedValue.getFVersion().intValue()); assertEquals("user-2", copiedValue.getFCreatorUserId()); assertEquals("revision-1", sourceValue.getXiudingId()); } } @Test public void auditRejectedCopiesConfiguredSourceSnapshotWithoutRefreshingIt() { Fixture fixture = new Fixture("audit_rejected"); ElnRecordFieldValueEntity source = fixture.sourceValues.get(1); Date acquiredAt = new Date(123456L); Date dateValue = new Date(654321L); source.setZhiWenben("configured snapshot"); source.setZhiShuzhi(new BigDecimal("123.456789012345678901")); source.setZhiRiqi(dateValue); source.setYuanShuxing("verification.test_basis"); source.setYuanShuxingBanben(1); source.setYuanYinyongLeixing("LIMS_TASK"); source.setYuanYinyongId("item-1"); source.setYuanHuoquShijian(acquiredAt); source.setYuanQuzhiZhuangtai("CLEARED"); source.setYuanQuzhiYuanyin("按审核要求保留空值"); source.setYuanXueyuan("verification.task_snapshot@1/test_basis"); fixture.versionResults.remove(1); fixture.open("revision-configured-source-copy"); ElnRecordFieldValueEntity copied = fixture.insertedValues.get(1); assertEquals("configured snapshot", copied.getZhiWenben()); assertEquals(0, new BigDecimal("123.456789012345678901") .compareTo(copied.getZhiShuzhi())); assertEquals(dateValue, copied.getZhiRiqi()); assertEquals("LIMS_TASK", copied.getZhiLaiyuan()); assertEquals("verification.test_basis", copied.getYuanShuxing()); assertEquals(Integer.valueOf(1), copied.getYuanShuxingBanben()); assertEquals("LIMS_TASK", copied.getYuanYinyongLeixing()); assertEquals("item-1", copied.getYuanYinyongId()); assertEquals(acquiredAt, copied.getYuanHuoquShijian()); assertEquals("CLEARED", copied.getYuanQuzhiZhuangtai()); assertEquals("按审核要求保留空值", copied.getYuanQuzhiYuanyin()); assertEquals("verification.task_snapshot@1/test_basis", copied.getYuanXueyuan()); } @Test public void cancelledAlsoCreatesANewRevisionOnTheSameRecord() { Fixture fixture = new Fixture("cancelled"); ElnRecordVos.Created created = fixture.open("revision-operation-cancelled"); assertFalse(created.isExisting()); assertEquals("record-1", created.getRecordId()); assertEquals(2, fixture.currentRevision.getXiudingHao().intValue()); assertEquals("revision-1", fixture.currentRevision.getLaiyuanXiudingId()); } @Test public void pendingTestReusesCurrentDraftWithoutRefreshingSnapshots() { Fixture fixture = new Fixture("pending_test"); ElnRecordVos.Created created = fixture.open("ordinary-open"); assertTrue(created.isExisting()); assertEquals("record-1", created.getRecordId()); assertEquals("revision-1", created.getRevisionId()); assertEquals(0, fixture.insertedValues.size()); assertEquals(0, fixture.versionReads.get()); assertEquals(0, fixture.copyCalls.get()); assertEquals(0, fixture.markHistoricalCalls.get()); assertEquals(0, fixture.switchCalls.get()); assertEquals(0, fixture.recalculateAllCalls.get()); } @Test public void copiesCrossTemplateLineageWithoutReadingTheSourceAgain() { Fixture fixture = new Fixture("audit_rejected"); ElnRecordFieldValueEntity source = fixture.sourceValues.get(1); fixture.versionResults.remove(1); source.setZhiLaiyuan("CROSS_TEMPLATE"); source.setZhiWenben("cross-template snapshot"); source.setKuamoLaiyuanJiluId("source-record"); source.setKuamoLaiyuanXiudingId("source-revision"); source.setKuamoLaiyuanZhiId("source-value"); source.setKuamoLaiyuanZiduanBanbenId("source-field-version"); source.setKuamoHuoquShijian(new Date(123456L)); fixture.open("revision-cross-copy"); ElnRecordFieldValueEntity copied = fixture.insertedValues.get(1); assertEquals("CROSS_TEMPLATE", copied.getZhiLaiyuan()); assertEquals("cross-template snapshot", copied.getZhiWenben()); assertEquals("source-record", copied.getKuamoLaiyuanJiluId()); assertEquals("source-revision", copied.getKuamoLaiyuanXiudingId()); assertEquals("source-value", copied.getKuamoLaiyuanZhiId()); assertEquals("source-field-version", copied.getKuamoLaiyuanZiduanBanbenId()); assertEquals(new Date(123456L), copied.getKuamoHuoquShijian()); assertEquals(2, fixture.versionReads.get()); } @Test public void repeatedOpenInTheSameReturnCycleReusesTheNewDraft() { Fixture fixture = new Fixture("audit_rejected"); ElnRecordVos.Created first = fixture.open("revision-operation-1"); ElnRecordVos.Created second = fixture.open("revision-operation-2"); assertTrue(second.isExisting()); assertEquals(first.getRevisionId(), second.getRevisionId()); assertEquals(1, fixture.copyCalls.get()); assertEquals(1, fixture.markHistoricalCalls.get()); assertEquals(1, fixture.switchCalls.get()); } @Test public void repeatedOperationCodeIsIdempotent() { Fixture fixture = new Fixture("audit_rejected"); ElnRecordVos.Created first = fixture.open("revision-operation-1"); ElnRecordVos.Created second = fixture.open("revision-operation-1"); assertTrue(second.isExisting()); assertEquals(first.getRevisionId(), second.getRevisionId()); assertEquals(1, fixture.copyCalls.get()); assertEquals(1, fixture.switchCalls.get()); assertEquals(4, fixture.insertedRepeatRows.size()); assertEquals(4, fixture.insertedRepeatValues.size()); } @Test public void repeatValueCopyFailureStopsBeforeCurrentSwitchAndFormulaCalculation() { Fixture fixture = new Fixture("audit_rejected"); fixture.repeatValueInsertResult = 0; expectDataException(() -> fixture.open("revision-operation-1"), "重复字段值复制失败"); assertEquals(4, fixture.insertedRepeatRows.size()); assertEquals(0, fixture.insertedRepeatValues.size()); assertEquals(0, fixture.switchCalls.get()); assertEquals(0, fixture.recalculateAllCalls.get()); assertEquals("revision-1", fixture.record.getDangqianXiudingId()); assertEquals(1, fixture.fenceReleaseCalls.get()); } @Test public void repeatRowCopyFailureStopsBeforeValuesAndCurrentSwitch() { Fixture fixture = new Fixture("audit_rejected"); fixture.repeatRowInsertResult = 0; expectDataException(() -> fixture.open("revision-operation-1"), "重复行复制失败"); assertEquals(0, fixture.insertedRepeatRows.size()); assertEquals(0, fixture.insertedRepeatValues.size()); assertEquals(0, fixture.switchCalls.get()); assertEquals(0, fixture.recalculateAllCalls.get()); assertEquals("revision-1", fixture.record.getDangqianXiudingId()); assertEquals(1, fixture.fenceReleaseCalls.get()); } @Test public void historicalTransitionConflictStopsBeforeCopyAndCurrentSwitch() { Fixture fixture = new Fixture("audit_rejected"); fixture.markHistoricalResult = 0; expectDataException(() -> fixture.open("revision-operation-1"), "状态冲突"); assertEquals(0, fixture.copyCalls.get()); assertEquals(0, fixture.switchCalls.get()); assertEquals("revision-1", fixture.record.getDangqianXiudingId()); assertEquals(1, fixture.fenceReleaseCalls.get()); } @Test public void currentRevisionCasConflictIsRejectedBeforeFormulaCalculation() { Fixture fixture = new Fixture("audit_rejected"); fixture.switchResult = 0; expectDataException(() -> fixture.open("revision-operation-1"), "当前修订切换冲突"); assertEquals(1, fixture.switchCalls.get()); assertEquals(0, fixture.recalculateAllCalls.get()); assertEquals(1, fixture.fenceReleaseCalls.get()); } @Test public void activeOnlyOfficeSessionStopsRevisionBeforeHistoricalMutation() { Fixture fixture = new Fixture("audit_rejected"); fixture.fenceBusy = true; expectDataException(() -> fixture.open("revision-operation-1"), "正在编辑"); assertEquals(1, fixture.fenceAcquireCalls.get()); assertEquals(0, fixture.markHistoricalCalls.get()); assertEquals(0, fixture.copyCalls.get()); assertEquals(0, fixture.switchCalls.get()); } @Test public void transactionCommitCompletesFenceOnlyAfterCommit() { Fixture fixture = new Fixture("audit_rejected"); TransactionSynchronizationManager.initSynchronization(); try { fixture.open("revision-operation-1"); assertEquals(0, fixture.fenceCompleteCalls.get()); assertEquals(0, fixture.fenceReleaseCalls.get()); for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) { synchronization.afterCompletion(TransactionSynchronization.STATUS_COMMITTED); } assertEquals(1, fixture.fenceCompleteCalls.get()); assertEquals(0, fixture.fenceReleaseCalls.get()); } finally { TransactionSynchronizationManager.clearSynchronization(); } } @Test public void transactionRollbackReleasesFenceAfterFailure() { Fixture fixture = new Fixture("audit_rejected"); fixture.markHistoricalResult = 0; TransactionSynchronizationManager.initSynchronization(); try { expectDataException(() -> fixture.open("revision-operation-1"), "状态冲突"); assertEquals(0, fixture.fenceCompleteCalls.get()); assertEquals(0, fixture.fenceReleaseCalls.get()); for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) { synchronization.afterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK); } assertEquals(0, fixture.fenceCompleteCalls.get()); assertEquals(1, fixture.fenceReleaseCalls.get()); } finally { TransactionSynchronizationManager.clearSynchronization(); } } private static void expectDataException(Runnable action, String messagePart) { try { action.run(); fail("预期抛出 DataException"); } catch (DataException expected) { assertTrue(expected.getMessage().contains(messagePart)); } } private static final class Fixture { private final String taskStatus; private final ElnRecordEntity record = record(); private final ElnRecordRevisionEntity sourceRevision = revision(); private final List sourceValues = sourceValues(); private final List sourceRepeatRows = sourceRepeatRows(); private final List sourceRepeatValues = sourceRepeatValues(sourceRepeatRows); private final Map revisionsByOperation = new LinkedHashMap<>(); private final List insertedValues = new ArrayList<>(); private final List insertedRepeatRows = new ArrayList<>(); private final List insertedRepeatValues = new ArrayList<>(); private final AtomicInteger markHistoricalCalls = new AtomicInteger(); private final AtomicInteger switchCalls = new AtomicInteger(); private final AtomicInteger copyCalls = new AtomicInteger(); private final AtomicInteger recalculateAllCalls = new AtomicInteger(); private final AtomicInteger formulaUpdateCalls = new AtomicInteger(); private final AtomicInteger fenceAcquireCalls = new AtomicInteger(); private final AtomicInteger fenceCompleteCalls = new AtomicInteger(); private final AtomicInteger fenceReleaseCalls = new AtomicInteger(); private final AtomicInteger revisionLookupCalls = new AtomicInteger(); private final AtomicInteger versionReads = new AtomicInteger(); private final List versionResults = new ArrayList<>(); private final ElnRecordRevisionServiceImpl service; private ElnRecordRevisionEntity currentRevision = sourceRevision; private String lookupOperationCode; private String copySource; private String copyTarget; private int markHistoricalResult = 1; private int switchResult = 1; private int repeatRowInsertResult = 1; private int repeatValueInsertResult = 1; private boolean fenceBusy; private Fixture(String taskStatus) { this.taskStatus = taskStatus; ElnRecordMapper recordMapper = proxy(ElnRecordMapper.class, (method, args) -> { if ("selectLiveByTestWorkItem".equals(method) || "selectLiveForUpdate".equals(method)) return record; if ("switchCurrentRevision".equals(method)) { switchCalls.incrementAndGet(); return switchResult; } return null; }); ElnRecordRevisionMapper revisionMapper = proxy( ElnRecordRevisionMapper.class, (method, args) -> { if ("selectOne".equals(method)) { return revisionLookupCalls.getAndIncrement() % 2 == 0 ? currentRevision : revisionsByOperation.get(lookupOperationCode); } if ("markHistorical".equals(method)) { markHistoricalCalls.incrementAndGet(); if (markHistoricalResult == 1) { sourceRevision.setBizStatus("HISTORICAL"); sourceRevision.setFVersion(sourceRevision.getFVersion() + 1); } return markHistoricalResult; } if ("insert".equals(method)) { currentRevision = (ElnRecordRevisionEntity) args[0]; revisionsByOperation.put( currentRevision.getXiudingCaozuoBianma(), currentRevision); return 1; } return null; }); ElnRecordFieldValueMapper valueMapper = proxy( ElnRecordFieldValueMapper.class, (method, args) -> { if ("selectList".equals(method)) return new ArrayList<>(sourceValues); if ("insert".equals(method)) { insertedValues.add((ElnRecordFieldValueEntity) args[0]); return 1; } if ("updateById".equals(method)) { formulaUpdateCalls.incrementAndGet(); return 1; } return null; }); ElnRecordRepeatRowMapper repeatRowMapper = proxy( ElnRecordRepeatRowMapper.class, (method, args) -> { if ("selectList".equals(method)) { return new ArrayList<>(sourceRepeatRows); } if ("insert".equals(method)) { if (repeatRowInsertResult == 1) { insertedRepeatRows.add((ElnRecordRepeatRowEntity) args[0]); } return repeatRowInsertResult; } return null; }); ElnRecordRepeatValueMapper repeatValueMapper = proxy( ElnRecordRepeatValueMapper.class, (method, args) -> { if ("selectList".equals(method)) { return new ArrayList<>(sourceRepeatValues); } if ("insert".equals(method)) { if (repeatValueInsertResult == 1) { insertedRepeatValues.add((ElnRecordRepeatValueEntity) args[0]); } return repeatValueInsertResult; } return null; }); versionResults.addAll(Arrays.asList( version("version-manual", "definition-manual", null, null), version("version-lims", "definition-lims", "LIMS_TASK", "test.basis"), version("version-system", "definition-system", "SYSTEM", "current_user.real_name"))); ElnFieldVersionMapper versionMapper = proxy( ElnFieldVersionMapper.class, (method, args) -> "selectOne".equals(method) ? versionResults.get(versionReads.getAndIncrement()) : null); ElnTemplateFileService fileService = new ElnTemplateFileService(null, null) { @Override public String copyDocx(String sourceAttachments, String targetTemplateId) { copyCalls.incrementAndGet(); copySource = sourceAttachments; copyTarget = targetTemplateId; return "[{\"fileId\":\"" + targetTemplateId + ".docx\"," + "\"name\":\"" + targetTemplateId + ".docx\"," + "\"fileExtension\":\"docx\"}]"; } }; ElnRecordFormulaCalculationService calculation = new ElnRecordFormulaCalculationService() { @Override public List recalculateAffected( String tenantId, ElnRecordEntity record, ElnRecordRevisionEntity revision, List values, Set changedBindingIds, String operationCode, String operatorUserId) { return Collections.emptyList(); } @Override public List recalculateAll( String tenantId, ElnRecordEntity actualRecord, ElnRecordRevisionEntity actualRevision, List values, String operationCode, String operatorUserId) { recalculateAllCalls.incrementAndGet(); assertEquals("tenant-1", tenantId); assertEquals(currentRevision.getId(), actualRevision.getId()); assertEquals(3, values.size()); assertEquals("user-2", operatorUserId); ElnRecordFieldValueEntity calculated = values.get(0); calculated.setJisuanZhuangtai("SUCCESS"); return Collections.singletonList(calculated); } }; ElnTestWorkItemSnapshotProvider taskProvider = new ElnTestWorkItemSnapshotProvider() { @Override public TestWorkItemSnapshotBatch resolve( List testWorkItemIds, List properties) { throw new AssertionError("必须使用显式负责人快照"); } @Override public TestWorkItemSnapshotBatch resolveAsActor( String tenantId, String userId, List testWorkItemIds, List properties) { assertEquals("tenant-1", tenantId); assertEquals("user-2", userId); assertEquals(Collections.singletonList("item-1"), testWorkItemIds); assertEquals(Collections.singletonList("test.basis"), properties); return snapshot(Fixture.this.taskStatus); } }; ElnRecordRevisionServiceImpl.RecordRevisionFence fence = new ElnRecordRevisionServiceImpl.RecordRevisionFence() { @Override public String acquire(String tenantId, String recordId, String fileId, String fileType) { fenceAcquireCalls.incrementAndGet(); assertEquals("tenant-1", tenantId); assertEquals("record-1", recordId); assertEquals("revision-1.docx", fileId); assertEquals("annex", fileType); if (fenceBusy) { throw new DataException("记录文档正在编辑"); } return "fence-token"; } @Override public void complete(ElnRecordRevisionServiceImpl.FenceLease lease) { fenceCompleteCalls.incrementAndGet(); } @Override public void release(ElnRecordRevisionServiceImpl.FenceLease lease) { fenceReleaseCalls.incrementAndGet(); } }; service = new ElnRecordRevisionServiceImpl( taskProvider, recordMapper, revisionMapper, valueMapper, versionMapper, repeatRowMapper, repeatValueMapper, fileService, calculation, fence, () -> new ElnRecordRevisionServiceImpl.UserContext( "tenant-1", "user-2", "新操作人")); } private ElnRecordVos.Created open(String operationCode) { lookupOperationCode = operationCode; ElnRecordForms.Create command = new ElnRecordForms.Create(); command.setTestWorkItemId("item-1"); command.setOperationCode(operationCode); return service.open(command); } } private static ElnRecordEntity record() { ElnRecordEntity record = new ElnRecordEntity(); record.setId("record-1"); record.setJianyanXiangId("item-1"); record.setMubanId("template-1"); record.setDangqianXiudingId("revision-1"); record.setFTenantId("tenant-1"); record.setFVersion(4); record.setFDeleteMark(0); return record; } private static ElnRecordRevisionEntity revision() { ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity(); revision.setId("revision-1"); revision.setDianziJiluId("record-1"); revision.setXiudingHao(1); revision.setXiudingCaozuoBianma("create-operation"); revision.setJiluWendang("revision-1.docx"); revision.setBizStatus("DRAFT"); revision.setFTenantId("tenant-1"); revision.setFVersion(2); revision.setFDeleteMark(0); return revision; } private static List sourceValues() { return Arrays.asList( value("value-manual", "binding-manual", "definition-manual", "version-manual", "manual_field", "MANUAL", "manual value"), value("value-lims", "binding-lims", "definition-lims", "version-lims", "test_basis", "LIMS_TASK", "old basis"), value("value-system", "binding-system", "definition-system", "version-system", "operator_name", "SYSTEM", "旧操作人")); } private static List sourceRepeatRows() { ElnRecordRepeatRowEntity active = repeatRow( "repeat-physical-active", "group-a", "row-a1", 1, "ACTIVE", 0); ElnRecordRepeatRowEntity prepared = repeatRow( "repeat-physical-prepared", "group-a", "row-a2", 2, "PREPARED", 0); ElnRecordRepeatRowEntity deleting = repeatRow( "repeat-physical-deleting", "group-b", "row-b1", 1, "DELETE_PREPARED", 0); deleting.setDeleteOperationCode("delete-operation-b1"); deleting.setDeleteReason("录入错误"); deleting.setDeleteRequestedUserId("user-1"); deleting.setDeleteRequestedTime(new Date(1000L)); ElnRecordRepeatRowEntity deleted = repeatRow( "repeat-physical-deleted", "group-b", "row-b2", 2, "DELETED", 1); deleted.setDeleteOperationCode("delete-operation-b2"); deleted.setDeleteReason("样品作废"); deleted.setDeleteRequestedUserId("user-1"); deleted.setDeleteRequestedTime(new Date(2000L)); deleted.setFDeleteUserId("user-1"); deleted.setFDeleteTime(new Date(3000L)); return Arrays.asList(active, prepared, deleting, deleted); } private static ElnRecordRepeatRowEntity repeatRow( String id, String groupCode, String rowId, int rowOrder, String status, int deleteMark) { ElnRecordRepeatRowEntity row = new ElnRecordRepeatRowEntity(); row.setId(id); row.setDianziJiluId("record-1"); row.setXiudingId("revision-1"); row.setMubanId("template-1"); row.setRepeatGroupCode(groupCode); row.setRowId(rowId); row.setRowOrder(rowOrder); row.setOperationCode("append-" + rowId); row.setOperationStatus(status); row.setFTenantId("tenant-1"); row.setFVersion(rowOrder); row.setFDeleteMark(deleteMark); return row; } private static List sourceRepeatValues( List rows) { List values = new ArrayList<>(); for (ElnRecordRepeatRowEntity row : rows) { ElnRecordRepeatValueEntity value = new ElnRecordRepeatValueEntity(); value.setId("repeat-value-" + row.getRowId()); value.setDianziJiluId("record-1"); value.setXiudingId("revision-1"); value.setMubanId("template-1"); value.setRepeatRowId(row.getRowId()); value.setRepeatGroupCode(row.getRepeatGroupCode()); value.setMubanBangdingId("repeat-binding-" + row.getRepeatGroupCode()); value.setZiduanDingyiId("repeat-definition-" + row.getRepeatGroupCode()); value.setZiduanBianma("result"); value.setZiduanBanbenId("repeat-version-" + row.getRepeatGroupCode()); value.setBangdingJuese("REPEAT"); value.setBangdingZhuangtai("ACTIVE"); value.setZhiWenben("value-" + row.getRowId()); value.setZhiLaiyuan("MANUAL"); value.setFTenantId("tenant-1"); value.setFVersion(row.getFVersion()); value.setFDeleteMark(row.getFDeleteMark()); value.setFDeleteUserId(row.getFDeleteUserId()); value.setFDeleteTime(row.getFDeleteTime()); values.add(value); } return values; } private static ElnRecordFieldValueEntity value( String id, String bindingId, String definitionId, String versionId, String code, 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.setZiduanDingyiId(definitionId); value.setZiduanBanbenId(versionId); value.setZiduanBianma(code); value.setBangdingJuese("PRIMARY"); value.setBangdingZhuangtai("ACTIVE"); value.setZhiLaiyuan(source); value.setZhiWenben(text); value.setFTenantId("tenant-1"); value.setFVersion(3); value.setFDeleteMark(0); return value; } private static ElnFieldVersionEntity version( String id, String definitionId, String source, String property) { ElnFieldVersionEntity version = new ElnFieldVersionEntity(); version.setId(id); version.setZiduanDingyiId(definitionId); version.setShujuLeixing("TEXT"); if (source != null) { version.setBangdingPeizhi("{\"read\":{\"source\":\"" + source + "\",\"property\":\"" + property + "\"}}"); } return version; } private static TestWorkItemSnapshotBatch snapshot(String status) { TestWorkItemSnapshotProperty property = new TestWorkItemSnapshotProperty(); property.setProperty("test.basis"); property.setDataType("TEXT"); property.setValue("new basis"); property.setSource("LIMS_TASK"); property.setSourceRef("item-1"); property.setOutcomeCode("SUCCESS"); TestWorkItemSnapshot item = new TestWorkItemSnapshot(); item.setTestWorkItemId("item-1"); item.setTaskStatus(status); item.setOutcomeCode("SUCCESS"); item.setProperties(Collections.singletonList(property)); TestWorkItemSnapshotBatch batch = new TestWorkItemSnapshotBatch(); batch.setItems(Collections.singletonList(item)); return batch; } @SuppressWarnings("unchecked") private static T proxy(Class type, MapperInvocation invocation) { return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, (proxy, method, args) -> { if (method.getDeclaringClass() == Object.class) { if ("toString".equals(method.getName())) return type.getSimpleName() + "Fake"; if ("hashCode".equals(method.getName())) return System.identityHashCode(proxy); if ("equals".equals(method.getName())) return proxy == 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 MapperInvocation { Object invoke(String method, Object[] args); } }