package jnpf.elnService.template; import jnpf.audit.model.AuditEventDTO; import jnpf.elnEntity.field.ElnFieldDefEntity; import jnpf.elnEntity.field.ElnFieldForms; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.field.ElnFieldVos; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateForms; import jnpf.elnEntity.template.ElnTemplateRepeatGroupEntity; import jnpf.elnEntity.template.ElnTemplateVersionEntity; import jnpf.elnEntity.template.ElnTemplateVos; import jnpf.elnService.ElnFieldCatalogService; import jnpf.elnService.template.impl.ElnTemplateBindingServiceImpl; import jnpf.elnService.template.impl.ElnTemplateRepeatGroupServiceImpl; import jnpf.exception.DataException; import jnpf.util.JsonUtil; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.SimpleTransactionStatus; import org.springframework.transaction.support.TransactionTemplate; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; 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 ElnTemplateBindingServiceImplTest { @Test public void templateKeepsItsConfiguredFieldVersionWhenTheCurrentFieldVersionChanges() { Fixture f = new Fixture(); f.versions.get("version-current").setBangdingPeizhi( "{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"test.basis\",\"propertyRevision\":1}," + "\"write\":{\"target\":\"ELN_RECORD\"}}"); f.versions.get("version-next").setBangdingPeizhi( "{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"test.basis\",\"propertyRevision\":2}," + "\"write\":{\"target\":\"ELN_RECORD\"}}"); ElnTemplateVos.Binding reserved = f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "configured-source-reserve")); f.service.activate(f.authorization(), reserved.getId(), "configured-source-activate"); f.selectNextVersion(); ElnTemplateVos.AnnotationContext context = f.service.context(f.authorization()); ElnTemplateVos.Binding bound = context.getBindings().stream() .filter(binding -> reserved.getId().equals(binding.getId())).findFirst().get(); assertEquals("version-current", bound.getFieldVersionId()); ElnFieldVos.Version available = context.getFields().stream() .filter(field -> "field-1".equals(field.getId())).findFirst().get().getVersions().stream() .filter(version -> "version-next".equals(version.getId())).findFirst().get(); assertEquals(Integer.valueOf(2), available.getBinding().getRead().getPropertyRevision()); } @Test public void contextExposesTemplateCheckboxGroupRules() { ElnTemplateVos.CheckboxGroup configured = new ElnTemplateVos.CheckboxGroup(); configured.setGroupCode("result_choice"); configured.setMinSelected(1); configured.setMaxSelected(2); configured.setVersion(3); Fixture f = new Fixture(new StubCheckboxGroupService( Collections.singletonList(configured))); ElnTemplateVos.AnnotationContext context = f.service.context(f.authorization()); assertEquals(1, context.getCheckboxGroups().size()); assertEquals("result_choice", context.getCheckboxGroups().get(0).getGroupCode()); assertEquals(Integer.valueOf(1), context.getCheckboxGroups().get(0).getMinSelected()); assertEquals(Integer.valueOf(2), context.getCheckboxGroups().get(0).getMaxSelected()); assertEquals(Integer.valueOf(3), context.getCheckboxGroups().get(0).getVersion()); } @Test public void contextExposesGlobalFieldCheckboxGroupRecommendation() { Fixture f = new Fixture(); f.addCheckboxFields(); ElnFieldVersionEntity version = f.versions.get("checkbox-version-1"); version.setRecommendedCheckboxGroupCode("result_choice"); version.setRecommendedCheckboxMinSelected(1); version.setRecommendedCheckboxMaxSelected(2); ElnTemplateVos.AnnotationContext context = f.service.context(f.authorization()); ElnFieldVos.Version exposed = context.getFields().stream() .filter(field -> "checkbox-1".equals(field.getId())) .findFirst().get().getVersions().get(0); assertEquals("result_choice", exposed.getRecommendedCheckboxGroupCode()); assertEquals(Integer.valueOf(1), exposed.getRecommendedCheckboxMinSelected()); assertEquals(Integer.valueOf(2), exposed.getRecommendedCheckboxMaxSelected()); } @Test public void reserveCreatesTemplateCheckboxGroupWithRequestedBounds() { StubCheckboxGroupService groups = new StubCheckboxGroupService(new ArrayList<>()); Fixture f = new Fixture(groups); f.addCheckboxFields(); ElnTemplateForms.ReserveBinding form = reserve( "checkbox-1", "checkbox-version-1", "create-selection-group"); form.setCheckboxGroupCode("result_choice"); form.setCheckboxGroupMinSelected(1); form.setCheckboxGroupMaxSelected(2); ElnTemplateVos.Binding binding = f.service.reservePrimary(f.authorization(), form); assertEquals("result_choice", binding.getCheckboxGroupCode()); assertEquals(1, groups.groups.size()); assertEquals(Integer.valueOf(1), groups.groups.get(0).getMinSelected()); assertEquals(Integer.valueOf(2), groups.groups.get(0).getMaxSelected()); } @Test public void reserveCannotOverrideExistingTemplateCheckboxGroupRule() { ElnTemplateVos.CheckboxGroup configured = checkboxGroup("result_choice", 1, 2, 0); StubCheckboxGroupService groups = new StubCheckboxGroupService( new ArrayList<>(Collections.singletonList(configured))); Fixture f = new Fixture(groups); f.addCheckboxFields(); ElnTemplateForms.ReserveBinding form = reserve( "checkbox-1", "checkbox-version-1", "mismatch-selection-group"); form.setCheckboxGroupCode("result_choice"); form.setCheckboxGroupMinSelected(0); form.setCheckboxGroupMaxSelected(1); expectDataException(() -> f.service.reservePrimary(f.authorization(), form)); assertEquals(0, f.store.bindings.size()); assertEquals(Integer.valueOf(1), groups.groups.get(0).getMinSelected()); assertEquals(Integer.valueOf(2), groups.groups.get(0).getMaxSelected()); } @Test public void reserveRetryRejectsChangedCheckboxGroupBounds() { StubCheckboxGroupService groups = new StubCheckboxGroupService(new ArrayList<>()); Fixture f = new Fixture(groups); f.addCheckboxFields(); ElnTemplateForms.ReserveBinding original = reserve( "checkbox-1", "checkbox-version-1", "retry-selection-group"); original.setCheckboxGroupCode("result_choice"); original.setCheckboxGroupMinSelected(1); original.setCheckboxGroupMaxSelected(2); f.service.reservePrimary(f.authorization(), original); ElnTemplateForms.ReserveBinding changed = reserve( "checkbox-1", "checkbox-version-1", "retry-selection-group"); changed.setCheckboxGroupCode("result_choice"); changed.setCheckboxGroupMinSelected(0); changed.setCheckboxGroupMaxSelected(1); expectDataException(() -> f.service.reservePrimary(f.authorization(), changed)); } @Test public void changesTemplateCheckboxGroupRuleWithCasAndAudit() { StubCheckboxGroupService groups = new StubCheckboxGroupService(new ArrayList<>( Collections.singletonList(checkboxGroup("result_choice", 0, 1, 3)))); Fixture f = new Fixture(groups); ElnTemplateForms.ChangeCheckboxGroupRule form = new ElnTemplateForms.ChangeCheckboxGroupRule(); form.setMinSelected(1); form.setMaxSelected(4); form.setExpectedVersion(3); form.setOperationCode("change-selection-rule"); ElnTemplateVos.CheckboxGroup changed = f.service.changeCheckboxGroupRule( f.authorization(), "result_choice", form); assertEquals(Integer.valueOf(1), changed.getMinSelected()); assertEquals(Integer.valueOf(4), changed.getMaxSelected()); assertEquals(Integer.valueOf(4), changed.getVersion()); assertEquals(1, f.countAudits("ELN_TEMPLATE_CHECKBOX_GROUP_RULE_CHANGE")); } @Test public void reservesPrimaryWithSingleStepCompatibilityAssignment() { Fixture f = new Fixture(); ElnTemplateVos.Binding reserved = f.service.reservePrimary( f.authorization(), reserve("field-1", "version-current", "single-step")); assertEquals("test-single-step", reserved.getStepId()); assertEquals("test-single-step", f.store.binding(reserved.getId()).getStepId()); } @Test public void reservesCheckboxGroupAndRequiresRetryToKeepTheSameGroup() { Fixture f = new Fixture(); f.addCheckboxFields(); ElnTemplateForms.ReserveBinding form = reserve( "checkbox-1", "checkbox-version-1", "checkbox-group-reserve"); form.setCheckboxGroupCode("result_choice"); ElnTemplateVos.Binding reserved = f.service.reservePrimary(f.authorization(), form); ElnTemplateVos.Binding retried = f.service.reservePrimary(f.authorization(), form); assertEquals("result_choice", reserved.getCheckboxGroupCode()); assertEquals("result_choice", retried.getCheckboxGroupCode()); assertEquals("result_choice", f.store.binding(reserved.getId()).getCheckboxGroupCode()); ElnTemplateForms.ReserveBinding changed = reserve( "checkbox-1", "checkbox-version-1", "checkbox-group-reserve"); changed.setCheckboxGroupCode("another_group"); expectOperationStatus(409, () -> f.service.reservePrimary(f.authorization(), changed)); } @Test public void rejectsInvalidOrIneligibleCheckboxGroupAssignments() { Fixture invalidCode = new Fixture(); invalidCode.addCheckboxFields(); ElnTemplateForms.ReserveBinding invalid = reserve( "checkbox-1", "checkbox-version-1", "invalid-checkbox-group"); invalid.setCheckboxGroupCode("含中文"); expectDataException(() -> invalidCode.service.reservePrimary( invalidCode.authorization(), invalid)); Fixture text = new Fixture(); ElnTemplateForms.ReserveBinding textForm = reserve( "field-1", "version-current", "text-checkbox-group"); textForm.setCheckboxGroupCode("result_choice"); expectDataException(() -> text.service.reservePrimary(text.authorization(), textForm)); Fixture readOnly = new Fixture(); readOnly.addCheckboxFields(); readOnly.versions.get("checkbox-version-1").setBangdingPeizhi( "{\"read\":{\"source\":\"LIMS_TASK\"}}"); ElnTemplateForms.ReserveBinding readOnlyForm = reserve( "checkbox-1", "checkbox-version-1", "readonly-checkbox-group"); readOnlyForm.setCheckboxGroupCode("result_choice"); expectDataException(() -> readOnly.service.reservePrimary( readOnly.authorization(), readOnlyForm)); } @Test public void rejectsCheckboxGroupsAcrossStepsAndRepeatScopes() { Fixture steps = new Fixture(); steps.addCheckboxFields(); ElnTemplateForms.ReserveBinding first = reserve( "checkbox-1", "checkbox-version-1", "checkbox-step-a"); first.setStepId("step-a"); first.setCheckboxGroupCode("result_choice"); steps.service.reservePrimary(steps.authorization(), first); ElnTemplateForms.ReserveBinding second = reserve( "checkbox-2", "checkbox-version-2", "checkbox-step-b"); second.setStepId("step-b"); second.setCheckboxGroupCode("result_choice"); expectDataException(() -> steps.service.reservePrimary(steps.authorization(), second)); Fixture ordinaryAndRepeat = new Fixture(); ordinaryAndRepeat.addCheckboxFields(); ElnTemplateForms.ReserveBinding ordinary = reserve( "checkbox-1", "checkbox-version-1", "checkbox-ordinary"); ordinary.setCheckboxGroupCode("result_choice"); ordinaryAndRepeat.service.reservePrimary(ordinaryAndRepeat.authorization(), ordinary); ElnTemplateForms.ReserveBinding repeated = repeatReserve( "checkbox-2", "checkbox-version-2", "checkbox-repeat", "results", 0, 5); repeated.setCheckboxGroupCode("result_choice"); expectDataException(() -> ordinaryAndRepeat.service.reservePrimary( ordinaryAndRepeat.authorization(), repeated)); Fixture repeatGroups = new Fixture(); repeatGroups.addCheckboxFields(); ElnTemplateForms.ReserveBinding resultGroup = repeatReserve( "checkbox-1", "checkbox-version-1", "checkbox-results", "results", 0, 5); resultGroup.setCheckboxGroupCode("result_choice"); repeatGroups.service.reservePrimary(repeatGroups.authorization(), resultGroup); ElnTemplateForms.ReserveBinding sampleGroup = repeatReserve( "checkbox-2", "checkbox-version-2", "checkbox-samples", "samples", 0, 5); sampleGroup.setCheckboxGroupCode("result_choice"); expectDataException(() -> repeatGroups.service.reservePrimary( repeatGroups.authorization(), sampleGroup)); } @Test public void enforcesConfiguredMaximumDefaultCheckedMembersDuringReserve() { StubCheckboxGroupService groups = new StubCheckboxGroupService(new ArrayList<>( Collections.singletonList(checkboxGroup("result_choice", 0, 2, 0)))); Fixture f = new Fixture(groups); f.addCheckboxFields(); f.addField("checkbox-3", "result_review", "checkbox-version-3"); f.addVersion("checkbox-version-3", "checkbox-3", "PUBLISHED", "CHECKBOX", "复核"); f.versions.get("checkbox-version-1").setKongjianPeizhi("{\"defaultChecked\":true}"); f.versions.get("checkbox-version-2").setKongjianPeizhi("{\"defaultChecked\":true}"); f.versions.get("checkbox-version-3").setKongjianPeizhi("{\"defaultChecked\":true}"); ElnTemplateForms.ReserveBinding first = reserve( "checkbox-1", "checkbox-version-1", "checkbox-default-first"); first.setCheckboxGroupCode("result_choice"); first.setCheckboxGroupMinSelected(0); first.setCheckboxGroupMaxSelected(2); f.service.reservePrimary(f.authorization(), first); ElnTemplateForms.ReserveBinding second = reserve( "checkbox-2", "checkbox-version-2", "checkbox-default-second"); second.setCheckboxGroupCode("result_choice"); second.setCheckboxGroupMinSelected(0); second.setCheckboxGroupMaxSelected(2); f.service.reservePrimary(f.authorization(), second); ElnTemplateForms.ReserveBinding third = reserve( "checkbox-3", "checkbox-version-3", "checkbox-default-third"); third.setCheckboxGroupCode("result_choice"); third.setCheckboxGroupMinSelected(0); third.setCheckboxGroupMaxSelected(2); expectDataException(() -> f.service.reservePrimary(f.authorization(), third)); } @Test public void missingCheckboxGroupRuleKeepsLegacySingleDefaultMaximum() { Fixture f = new Fixture(); f.addCheckboxFields(); f.versions.get("checkbox-version-1").setKongjianPeizhi("{\"defaultChecked\":true}"); f.versions.get("checkbox-version-2").setKongjianPeizhi("{\"defaultChecked\":true}"); ElnTemplateForms.ReserveBinding first = reserve( "checkbox-1", "checkbox-version-1", "legacy-default-first"); first.setCheckboxGroupCode("legacy_result"); f.service.reservePrimary(f.authorization(), first); ElnTemplateForms.ReserveBinding second = reserve( "checkbox-2", "checkbox-version-2", "legacy-default-second"); second.setCheckboxGroupCode("legacy_result"); expectDataException(() -> f.service.reservePrimary(f.authorization(), second)); } @Test public void changesAndRemovesCheckboxGroupWithOptimisticLockAndIdempotentAudit() { StubCheckboxGroupService checkboxGroups = new StubCheckboxGroupService( new ArrayList()); Fixture f = new Fixture(checkboxGroups); f.addCheckboxFields(); String bindingId = f.activePrimary( "checkbox-binding", "checkbox-1", "checkbox-version-1"); ElnTemplateForms.ChangeCheckboxGroup change = checkboxGroupChange( "result_choice", "change-checkbox-group", 3); ElnTemplateVos.Binding changed = changeCheckboxGroup( f.service, f.authorization(), bindingId, change); ElnTemplateVos.Binding retried = changeCheckboxGroup( f.service, f.authorization(), bindingId, change); assertEquals("result_choice", changed.getCheckboxGroupCode()); assertEquals(Integer.valueOf(4), changed.getVersion()); assertEquals("result_choice", retried.getCheckboxGroupCode()); assertEquals(1, f.countAudits("ELN_TEMPLATE_BINDING_CHECKBOX_GROUP_CHANGE")); assertTrue(f.audit("ELN_TEMPLATE_BINDING_CHECKBOX_GROUP_CHANGE") .getDataSnapshot().contains("result_choice")); ElnTemplateForms.ChangeCheckboxGroup reused = checkboxGroupChange( "other_group", "change-checkbox-group", 4); expectOperationStatus(409, () -> changeCheckboxGroup( f.service, f.authorization(), bindingId, reused)); ElnTemplateForms.ChangeCheckboxGroup remove = checkboxGroupChange( " ", "remove-checkbox-group", 4); ElnTemplateVos.Binding removed = changeCheckboxGroup( f.service, f.authorization(), bindingId, remove); assertEquals(null, removed.getCheckboxGroupCode()); assertEquals(Integer.valueOf(5), removed.getVersion()); assertTrue(checkboxGroups.list("tenant-1", "template-1").isEmpty()); } @Test public void rejectsStaleOrConflictingCheckboxGroupChange() { Fixture stale = new Fixture(); stale.addCheckboxFields(); String staleId = stale.activePrimary( "checkbox-stale", "checkbox-1", "checkbox-version-1"); expectOperationStatus(409, () -> changeCheckboxGroup(stale.service, stale.authorization(), staleId, checkboxGroupChange("result_choice", "stale-checkbox-group", 2))); Fixture conflict = new Fixture(); conflict.addCheckboxFields(); String firstId = conflict.activePrimary( "checkbox-first", "checkbox-1", "checkbox-version-1"); conflict.store.binding(firstId).setCheckboxGroupCode("result_choice"); conflict.store.binding(firstId).setStepId("step-a"); String secondId = conflict.activePrimary( "checkbox-second", "checkbox-2", "checkbox-version-2"); conflict.store.binding(secondId).setStepId("step-b"); expectDataException(() -> changeCheckboxGroup(conflict.service, conflict.authorization(), secondId, checkboxGroupChange("result_choice", "conflicting-checkbox-group", 3))); assertEquals(0, conflict.countAudits( "ELN_TEMPLATE_BINDING_CHECKBOX_GROUP_CHANGE")); } @Test public void rejectsCheckboxGroupWhenDependencyValidatorMarksBindingReadOnly() { Fixture f = new Fixture(); f.addCheckboxFields(); String bindingId = f.activePrimary( "checkbox-readonly", "checkbox-1", "checkbox-version-1"); f.checkboxGroupDiagnostics = Collections.singletonList( error("CHECKBOX_GROUP_BINDING_READ_ONLY")); expectDataException(() -> changeCheckboxGroup(f.service, f.authorization(), bindingId, checkboxGroupChange("result_choice", "readonly-checkbox-change", 3))); } @Test public void allowsUpgradeWithinConfiguredMaximumDefaultCheckedMembers() { StubCheckboxGroupService groups = new StubCheckboxGroupService(new ArrayList<>( Collections.singletonList(checkboxGroup("result_choice", 0, 2, 0)))); Fixture f = new Fixture(groups); f.addCheckboxFields(); f.versions.get("checkbox-version-1").setKongjianPeizhi( "{\"defaultChecked\":true}"); f.versions.get("checkbox-version-2").setKongjianPeizhi( "{\"defaultChecked\":false}"); f.addVersion("checkbox-version-2-default", "checkbox-2", "PUBLISHED", "CHECKBOX", "不合格(默认)"); f.versions.get("checkbox-version-2-default").setKongjianPeizhi( "{\"defaultChecked\":true}"); f.fields.get("checkbox-2").setDangqianZiduanBanbenId( "checkbox-version-2-default"); String firstId = f.activePrimary( "checkbox-default-first", "checkbox-1", "checkbox-version-1"); f.store.binding(firstId).setCheckboxGroupCode("result_choice"); String secondId = f.activePrimary( "checkbox-default-second", "checkbox-2", "checkbox-version-2"); f.store.binding(secondId).setCheckboxGroupCode("result_choice"); ElnTemplateVos.ChangeCheck check = f.service.checkUpgrade( f.authorization(), secondId, "checkbox-version-2-default"); f.service.confirmUpgrade(f.authorization(), secondId, upgradeConfirm(check, "checkbox-version-2-default")); assertEquals("checkbox-version-2-default", f.store.binding(secondId).getZiduanBanbenId()); } @Test public void marksProductionConstructorForSpringInjection() { Constructor production = null; for (Constructor constructor : ElnTemplateBindingServiceImpl.class.getConstructors()) { if (constructor.getAnnotation(Autowired.class) != null) { production = constructor; } } assertNotNull("未找到生产构造器", production); assertNotNull("多 public 构造器时生产构造器必须显式 @Autowired", production.getAnnotation(Autowired.class)); assertTrue("生产构造器必须注入 PENDING 对账服务", Arrays.asList(production.getParameterTypes()) .contains(ElnPendingBindingCleanupService.class)); } @Test public void reservesActivatesCancelsAndRetriesSameOperationIdempotently() { Fixture f = new Fixture(); ElnTemplateVos.Binding reserved = f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "reserve-1")); ElnTemplateVos.Binding retried = f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "reserve-1")); assertEquals(reserved.getId(), retried.getId()); assertEquals("eln.field.sample_code", reserved.getControlTag()); assertEquals("PENDING", reserved.getStatus()); f.service.activate(f.authorization(), reserved.getId(), "activate-1"); assertEquals("ACTIVE", f.store.binding(reserved.getId()).getBangdingZhuangtai()); ElnTemplateVos.Binding cancelled = f.service.reservePrimary(f.authorization(), reserve("field-2", "version-2", "reserve-2")); f.service.cancel(f.authorization(), cancelled.getId(), "cancel-1", "控件插入失败"); assertEquals("CANCELLED", f.store.binding(cancelled.getId()).getBangdingZhuangtai()); assertEquals(Collections.singletonList(cancelled.getId()), f.terminatedBindingIds); assertTrue(f.hasAudit("ELN_TEMPLATE_BINDING_RESERVE")); assertTrue(f.hasAudit("ELN_TEMPLATE_BINDING_ACTIVATE")); assertTrue(f.hasAudit("ELN_TEMPLATE_BINDING_CANCEL")); for (AuditEventDTO audit : f.audits) { assertFalse(audit.getDataSnapshot().toLowerCase().contains("ticket")); } } @Test public void bindingMutationsAcquireTheTemplateWriteLock() { Fixture f = new Fixture(); ElnTemplateVos.Binding reserved = f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "reserve-with-lock")); assertEquals(Collections.singletonList("template-1"), f.store.lockedTemplateIds); f.store.lockedTemplateIds.clear(); f.service.activate(f.authorization(), reserved.getId(), "activate-with-lock"); assertEquals(Collections.singletonList("template-1"), f.store.lockedTemplateIds); } @Test public void rejectsSecondLivePrimaryAndBuildsReferenceFromActivePrimary() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); expectDataException(() -> f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "other-primary"))); ElnTemplateVos.Binding reference = f.service.reserveReference( f.authorization(), primary, "reference-1"); assertEquals("version-current", reference.getFieldVersionId()); assertEquals(primary, reference.getPrimaryBindingId()); assertTrue(reference.getControlTag().matches("eln\\.ref\\.sample_code\\.[0-9a-f]{32}")); assertNotEquals(f.store.binding(primary).getKongjianBiaoqian(), reference.getControlTag()); } @Test public void rejectsReferenceWithoutActivePrimary() { Fixture f = new Fixture(); String pending = f.pendingPrimary("primary-1", "field-1", "version-current", "pending-op"); expectDataException(() -> f.service.reserveReference(f.authorization(), pending, "reference-1")); } @Test public void contextReturnsOnlyAuthorizedTemplateLiveBindingsAndRejectsCrossTemplateBinding() { Fixture f = new Fixture(); f.template.setShengbanCaozuoBianma("upgrade-template-1"); f.activePrimary("primary-1", "field-1", "version-current"); f.pendingPrimary("pending-1", "field-2", "version-2", "pending-context"); ElnTemplateBindingEntity removed = f.binding( "removed-1", "field-2", "version-2", "PRIMARY", null, "REMOVED"); removed.setCaozuoBianma("removed-context"); f.store.bindings.put(removed.getId(), removed); ElnTemplateVos.AnnotationContext context = f.service.context(f.authorization()); assertEquals("template-1", context.getTemplateId()); assertEquals("upgrade-template-1", context.getUpgradeOperationCode()); assertTrue(context.getStepConfiguration().isSingleStep()); assertEquals("test-single-step", context.getStepConfiguration().getSteps().get(0).getId()); assertEquals(2, context.getFields().size()); assertEquals(2, context.getFields().get(0).getVersions().size()); assertEquals(2, context.getBindings().size()); ElnTemplateBindingEntity otherTemplate = f.binding( "other-binding", "field-2", "version-2", "PRIMARY", null, "ACTIVE"); otherTemplate.setMubanId("template-2"); f.store.bindings.put(otherTemplate.getId(), otherTemplate); expectDataException(() -> f.service.activate( f.authorization(), otherTemplate.getId(), "cross-template")); } @Test public void contextMarksOnlyCopiedPendingBindingsForVersionCopyConfirmation() { Fixture f = new Fixture(); f.pendingPrimary("ordinary-pending", "field-2", "version-2", "ordinary-op"); f.pendingCopied("copied-pending", "source-binding-1", "copy-op"); f.activePrimary("copied-active", "field-1", "version-current"); f.store.binding("copied-active").setLaiyuanBangdingId("source-binding-2"); ElnTemplateVos.AnnotationContext context = f.service.context(f.authorization()); assertEquals(3, context.getBindings().size()); assertEquals(Collections.singletonList("copied-pending"), context.getCopiedPendingBindingIds()); } @Test public void contextAndReserveDownloadFreshDocumentEvidenceOncePerCall() { Fixture f = new Fixture(); f.service.context(f.authorization()); assertEquals(1, f.evidenceReads); f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "download-before-reserve")); assertEquals(2, f.evidenceReads); } @Test public void returnsAuthoritativeControlConfigAndIgnoresUnprovableTextDefaults() { Fixture f = new Fixture(); f.selectNextVersion(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); ElnTemplateVos.AnnotationContext context = f.service.context(f.authorization()); assertEquals("version-current", controlConfig(context.getBindings().get(0)) .get("placeholder")); ElnTemplateVos.RepairContext repair = f.service.repairContext(f.authorization(), primary); assertEquals("version-current", controlConfig(repair.getBinding()).get("placeholder")); ElnTemplateVos.ChangeCheck upgrade = f.service.checkUpgrade( f.authorization(), primary, "version-next"); assertEquals("version-next", controlConfig(upgrade.getControls().get(0)) .get("placeholder")); ElnTemplateForms.UpgradeConfirm confirmed = upgradeConfirm(upgrade, "version-next"); setControlConfig(confirmed.getControls().get(0), Collections.emptyMap()); f.service.confirmUpgrade(f.authorization(), primary, confirmed); assertEquals("version-next", f.store.binding(primary).getZiduanBanbenId()); } @Test public void matchesDropdownOptionsSemanticallyAndRejectsChangedOption() { Fixture accepted = new Fixture(); accepted.addDropdownVersion("version-select"); String acceptedPrimary = accepted.activePrimary( "primary-1", "field-1", "version-current"); ElnTemplateVos.ChangeCheck acceptedCheck = accepted.service.checkUpgrade( accepted.authorization(), acceptedPrimary, "version-select"); ElnTemplateForms.UpgradeConfirm equivalent = upgradeConfirm( acceptedCheck, "version-select"); Map docxOptions = new LinkedHashMap<>(); docxOptions.put("A", "选项 A"); docxOptions.put("B", "选项 B"); setControlConfig(equivalent.getControls().get(0), docxOptions); accepted.service.confirmUpgrade( accepted.authorization(), acceptedPrimary, equivalent); assertEquals("version-select", accepted.store.binding(acceptedPrimary).getZiduanBanbenId()); Fixture rejected = new Fixture(); rejected.addDropdownVersion("version-select"); String rejectedPrimary = rejected.activePrimary( "primary-1", "field-1", "version-current"); ElnTemplateVos.ChangeCheck rejectedCheck = rejected.service.checkUpgrade( rejected.authorization(), rejectedPrimary, "version-select"); ElnTemplateForms.UpgradeConfirm changed = upgradeConfirm( rejectedCheck, "version-select"); Map changedOptions = new LinkedHashMap<>(); changedOptions.put("A", "选项 A"); changedOptions.put("B", "已篡改"); setControlConfig(changed.getControls().get(0), changedOptions); expectDataException(() -> rejected.service.confirmUpgrade( rejected.authorization(), rejectedPrimary, changed)); } @Test public void acceptsOnlyEnabledPreReleaseOrCurrentPublishedFieldVersions() { Fixture f = new Fixture(); f.fields.get("field-1").setBizEnabled("disabled"); expectDataException(() -> f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "disabled-field"))); f.fields.get("field-1").setBizEnabled("enabled"); expectDataException(() -> f.service.reservePrimary(f.authorization(), reserve("field-1", "version-old", "old-version"))); expectDataException(() -> f.service.reservePrimary(f.authorization(), reserve("field-1", "version-draft", "draft-version"))); ElnTemplateVos.Binding preRelease = f.service.reservePrimary(f.authorization(), reserve("field-1", "version-pre", "pre-release")); assertEquals("version-pre", preRelease.getFieldVersionId()); } @Test public void rejectsNonDraftDisabledAndRepeatRequests() { Fixture f = new Fixture(); f.template.setBizStatus("PUBLISHED"); expectDataException(() -> f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "published-template"))); f.template.setBizStatus("DRAFT"); f.template.setBizEnabled("disabled"); expectDataException(() -> f.service.reservePrimary(f.authorization(), reserve("field-1", "version-current", "disabled-template"))); f.template.setBizEnabled("enabled"); ElnTemplateForms.ReserveBinding repeat = reserve("field-1", "version-current", "repeat"); repeat.setRepeatRequest(true); expectDataException(() -> f.service.reservePrimary(f.authorization(), repeat)); } @Test public void reservesIndependentRepeatGroupsAndReturnsTheirConstraints() { Fixture f = new Fixture(); ElnTemplateForms.ReserveBinding results = repeatReserve( "field-1", "version-current", "repeat-results", "results", 2, 20); ElnTemplateForms.ReserveBinding samples = repeatReserve( "field-2", "version-2", "repeat-samples", "samples", 0, 5); ElnTemplateVos.Binding first = f.service.reservePrimary(f.authorization(), results); ElnTemplateVos.Binding second = f.service.reservePrimary(f.authorization(), samples); ElnTemplateVos.AnnotationContext context = f.service.context(f.authorization()); assertEquals("results", first.getRepeatGroupCode()); assertEquals("samples", second.getRepeatGroupCode()); assertEquals(2, context.getRepeatGroups().size()); assertEquals("results", context.getRepeatGroups().get(0).getGroupCode()); assertEquals(Integer.valueOf(2), context.getRepeatGroups().get(0).getMinRows()); assertEquals(Integer.valueOf(20), context.getRepeatGroups().get(0).getMaxRows()); assertEquals("test-single-step", context.getRepeatGroups().get(0).getStepId()); assertEquals("samples", context.getRepeatGroups().get(1).getGroupCode()); assertEquals(null, first.getStepId()); assertEquals(null, second.getStepId()); expectDataException(() -> f.service.reservePrimary(f.authorization(), repeatReserve( "field-1", "version-current", "repeat-results-duplicate", "results", 2, 20))); expectDataException(() -> f.service.reservePrimary(f.authorization(), repeatReserve( "field-2", "version-2", "repeat-results-conflict", "results", 1, 20))); } @Test public void createsPrivatePreReleaseFieldThenReservesPrimaryWithoutDeletingItOnCancel() { Fixture f = new Fixture(); ElnTemplateForms.PrivateField form = new ElnTemplateForms.PrivateField(); form.setOperationCode("private-1"); form.setZiduanMingcheng("模板备注"); form.setShujuLeixing("TEXT"); ElnTemplateVos.Binding binding = f.service.createPrivateFieldAndReserve(f.authorization(), form); f.service.cancel(f.authorization(), binding.getId(), "cancel-private", "插件插入失败"); assertEquals("TEMPLATE", f.createdPrivate.getZuoyongFanwei()); assertEquals("TMP-001", f.createdPrivate.getMubanBianhao()); assertEquals("NONE", f.privateCommand.getBinding().getRead().getSource()); assertEquals("ELN_RECORD", f.privateCommand.getBinding().getWrite().getTarget()); assertEquals("RECORD_PRIVATE", f.privateCommand.getPiciMoshi()); assertEquals("CANCELLED", f.store.binding(binding.getId()).getBangdingZhuangtai()); assertTrue(f.fields.containsKey("private-field")); } @Test public void privateFieldRetryRequiresSameBusinessSemantics() { Fixture f = new Fixture(); ElnTemplateForms.PrivateField form = privateField("private-1", "模板备注", "TEXT"); form.setDanwei("mg"); form.setShifouBitian(1); form.setBeizhu("原始备注"); form.setKongjianPeizhi(new LinkedHashMap<>(Collections.singletonMap( "placeholder", "请输入"))); ElnTemplateVos.Binding first = f.service.createPrivateFieldAndReserve( f.authorization(), form); ElnTemplateVos.Binding retried = f.service.createPrivateFieldAndReserve( f.authorization(), privateFieldCopy(form)); assertEquals(first.getId(), retried.getId()); assertEquals(1, f.privateCreateCalls.get()); ElnTemplateForms.PrivateField changed = privateFieldCopy(form); changed.setZiduanMingcheng("另一个字段"); expectDataException(() -> f.service.createPrivateFieldAndReserve( f.authorization(), changed)); f.createdPrivate.setZuoyongFanwei("GLOBAL"); expectDataException(() -> f.service.createPrivateFieldAndReserve( f.authorization(), privateFieldCopy(form))); } @Test public void privateCheckboxReservationCarriesGroupAndRetryRequiresSameGroup() { StubCheckboxGroupService groups = new StubCheckboxGroupService(new ArrayList<>()); Fixture f = new Fixture(groups); ElnTemplateForms.PrivateField form = privateField( "private-checkbox-1", "合格", "CHECKBOX"); form.setCheckboxGroupCode("test_result"); form.setCheckboxGroupMinSelected(1); form.setCheckboxGroupMaxSelected(3); form.setKongjianPeizhi(new LinkedHashMap<>(Collections.singletonMap( "defaultChecked", false))); ElnTemplateVos.Binding first = f.service.createPrivateFieldAndReserve( f.authorization(), form); ElnTemplateVos.Binding retried = f.service.createPrivateFieldAndReserve( f.authorization(), privateFieldCopy(form)); assertEquals("test_result", first.getCheckboxGroupCode()); assertEquals(Integer.valueOf(1), groups.groups.get(0).getMinSelected()); assertEquals(Integer.valueOf(3), groups.groups.get(0).getMaxSelected()); assertEquals(first.getId(), retried.getId()); assertEquals(1, f.privateCreateCalls.get()); ElnTemplateForms.PrivateField changed = privateFieldCopy(form); changed.setCheckboxGroupCode("review_result"); expectDataException(() -> f.service.createPrivateFieldAndReserve( f.authorization(), changed)); ElnTemplateForms.PrivateField changedBounds = privateFieldCopy(form); changedBounds.setCheckboxGroupMaxSelected(4); expectDataException(() -> f.service.createPrivateFieldAndReserve( f.authorization(), changedBounds)); } @Test public void pendingReferenceBlocksPrimaryChangesAndActivationRevalidatesPrimary() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); f.pendingReference("pending-ref", primary, "field-1", "version-current"); expectDataException(() -> f.service.checkRemove(f.authorization(), primary)); f.selectNextVersion(); expectDataException(() -> f.service.checkUpgrade( f.authorization(), primary, "version-next")); f.activeReference("active-ref", primary, "field-1", "version-current"); expectDataException(() -> f.service.checkPromoteReference( f.authorization(), "active-ref")); f.store.binding(primary).setBangdingZhuangtai("REMOVED"); expectDataException(() -> f.service.activate( f.authorization(), "pending-ref", "activate-pending-ref")); assertEquals("PENDING", f.store.binding("pending-ref").getBangdingZhuangtai()); } @Test public void locksPrimaryForReferenceReserveActivationAndDestructiveChecks() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); f.service.reserveReference(f.authorization(), primary, "reserve-reference"); assertTrue(f.store.lockedBindingIds.contains(primary)); f.store.lockedBindingIds.clear(); f.pendingReference("pending-ref", primary, "field-1", "version-current"); f.service.activate(f.authorization(), "pending-ref", "activate-reference"); assertTrue(f.store.lockedBindingIds.contains(primary)); f.store.lockedBindingIds.clear(); expectDataException(() -> f.service.checkRemove(f.authorization(), primary)); assertTrue(f.store.lockedBindingIds.contains(primary)); Fixture upgrade = new Fixture(); String upgradePrimary = upgrade.activePrimary( "upgrade-primary", "field-1", "version-current"); upgrade.selectNextVersion(); upgrade.service.checkUpgrade( upgrade.authorization(), upgradePrimary, "version-next"); assertTrue(upgrade.store.lockedBindingIds.contains(upgradePrimary)); Fixture promote = new Fixture(); String promotePrimary = promote.activePrimary( "promote-primary", "field-1", "version-current"); promote.activeReference( "promote-reference", promotePrimary, "field-1", "version-current"); promote.service.checkPromoteReference( promote.authorization(), "promote-reference"); assertTrue(promote.store.lockedBindingIds.contains(promotePrimary)); } @Test public void repairsWithoutChangingActiveStateAndRemovesPermanently() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); ElnTemplateVos.RepairContext repair = f.service.repairContext(f.authorization(), primary); ElnTemplateForms.RepairConfirm repairForm = repairConfirm(repair); int repairVersion = f.store.binding(primary).getFVersion(); f.service.confirmRepair(f.authorization(), primary, repairForm); assertEquals("ACTIVE", f.store.binding(primary).getBangdingZhuangtai()); assertEquals(repairVersion + 1, (int) f.store.binding(primary).getFVersion()); f.service.confirmRepair(f.authorization(), primary, repairForm); assertEquals(repairVersion + 1, (int) f.store.binding(primary).getFVersion()); assertTrue(f.hasAudit("ELN_TEMPLATE_BINDING_REPAIR")); ElnTemplateVos.ChangeCheck remove = f.service.checkRemove(f.authorization(), primary); f.service.confirmRemove(f.authorization(), primary, removeConfirm(remove)); assertEquals("REMOVED", f.store.binding(primary).getBangdingZhuangtai()); assertEquals(Collections.singletonList(primary), f.terminatedBindingIds); expectDataException(() -> f.service.activate(f.authorization(), primary, "restore-forbidden")); } @Test public void removeConfirmRejectsMissingStaleOrDuplicateDocumentTagEvidence() { Fixture missing = new Fixture(); String missingId = missing.activePrimary( "primary-1", "field-1", "version-current"); ElnTemplateVos.ChangeCheck missingCheck = missing.service.checkRemove( missing.authorization(), missingId); ElnTemplateForms.RemoveConfirm missingForm = removeConfirm(missingCheck); missingForm.setObservedControlTags(null); expectDataException(() -> missing.service.confirmRemove( missing.authorization(), missingId, missingForm)); assertEquals("ACTIVE", missing.store.binding(missingId).getBangdingZhuangtai()); Fixture stale = new Fixture(); String staleId = stale.activePrimary( "primary-1", "field-1", "version-current"); ElnTemplateVos.ChangeCheck staleCheck = stale.service.checkRemove( stale.authorization(), staleId); ElnTemplateForms.RemoveConfirm staleForm = removeConfirm(staleCheck); staleForm.getObservedControlTags().add("eln.field.sample_code"); expectDataException(() -> stale.service.confirmRemove( stale.authorization(), staleId, staleForm)); assertEquals("ACTIVE", stale.store.binding(staleId).getBangdingZhuangtai()); Fixture duplicate = new Fixture(); String duplicateId = duplicate.activePrimary( "primary-1", "field-1", "version-current"); duplicate.activePrimary("primary-2", "field-2", "version-2"); ElnTemplateVos.ChangeCheck duplicateCheck = duplicate.service.checkRemove( duplicate.authorization(), duplicateId); ElnTemplateForms.RemoveConfirm duplicateForm = removeConfirm(duplicateCheck); duplicateForm.getObservedControlTags().add( duplicateForm.getObservedControlTags().get(0)); expectDataException(() -> duplicate.service.confirmRemove( duplicate.authorization(), duplicateId, duplicateForm)); assertEquals("ACTIVE", duplicate.store.binding(duplicateId).getBangdingZhuangtai()); } @Test public void promoteConfirmRejectsMissingOrResidualReferenceTagEvidence() { Fixture missing = new Fixture(); String missingPrimary = missing.activePrimary( "primary-1", "field-1", "version-current"); missing.activeReference( "reference-1", missingPrimary, "field-1", "version-current"); ElnTemplateVos.ChangeCheck missingCheck = missing.service.checkPromoteReference( missing.authorization(), "reference-1"); ElnTemplateForms.PromoteConfirm missingForm = promoteConfirm(missingCheck); missingForm.setObservedControlTags(null); expectDataException(() -> missing.service.confirmPromoteReference( missing.authorization(), "reference-1", missingForm)); assertEquals("REFERENCE", missing.store.binding("reference-1").getKongjianJuese()); Fixture stale = new Fixture(); String stalePrimary = stale.activePrimary( "primary-1", "field-1", "version-current"); stale.activeReference( "reference-1", stalePrimary, "field-1", "version-current"); ElnTemplateVos.ChangeCheck staleCheck = stale.service.checkPromoteReference( stale.authorization(), "reference-1"); ElnTemplateForms.PromoteConfirm staleForm = promoteConfirm(staleCheck); staleForm.getObservedControlTags().add("eln.ref.sample_code.reference-1"); expectDataException(() -> stale.service.confirmPromoteReference( stale.authorization(), "reference-1", staleForm)); assertEquals("REFERENCE", stale.store.binding("reference-1").getKongjianJuese()); } @Test public void repairCasConflictRollsBackAndWritesIndependentFailureAudit() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); ElnTemplateVos.RepairContext repair = f.service.repairContext(f.authorization(), primary); ElnTemplateForms.RepairConfirm form = repairConfirm(repair); int beforeVersion = f.store.binding(primary).getFVersion(); f.store.failCasOnId = primary; expectDataException(() -> f.service.confirmRepair(f.authorization(), primary, form)); assertEquals("ACTIVE", f.store.binding(primary).getBangdingZhuangtai()); assertEquals(beforeVersion, (int) f.store.binding(primary).getFVersion()); assertTrue(f.hasAudit("ELN_TEMPLATE_BINDING_CONFIRM_FAILED")); } @Test public void successAuditsCapturePersistedCasVersions() { Fixture activated = new Fixture(); ElnTemplateVos.Binding reserved = activated.service.reservePrimary( activated.authorization(), reserve( "field-1", "version-current", "reserve-for-activate")); activated.service.activate( activated.authorization(), reserved.getId(), "activate-for-audit"); assertEquals(1, numberProperty( auditBinding(activated, "ELN_TEMPLATE_BINDING_ACTIVATE", "after"), "fVersion")); Fixture cancelled = new Fixture(); String pending = cancelled.pendingPrimary( "pending-1", "field-1", "version-current", "reserve-for-cancel"); cancelled.service.cancel( cancelled.authorization(), pending, "cancel-for-audit", "插入失败"); assertEquals(4, numberProperty( auditBinding(cancelled, "ELN_TEMPLATE_BINDING_CANCEL", "after"), "fVersion")); Fixture repaired = new Fixture(); String repairId = repaired.activePrimary( "repair-1", "field-1", "version-current"); ElnTemplateVos.RepairContext repair = repaired.service.repairContext( repaired.authorization(), repairId); repaired.service.confirmRepair( repaired.authorization(), repairId, repairConfirm(repair)); assertEquals(4, numberProperty( auditBinding(repaired, "ELN_TEMPLATE_BINDING_REPAIR", "after"), "fVersion")); Fixture removed = new Fixture(); String removeId = removed.activePrimary( "remove-1", "field-1", "version-current"); ElnTemplateVos.ChangeCheck remove = removed.service.checkRemove( removed.authorization(), removeId); removed.service.confirmRemove( removed.authorization(), removeId, removeConfirm(remove)); assertEquals(4, numberProperty( auditBinding(removed, "ELN_TEMPLATE_BINDING_REMOVE", "after"), "fVersion")); Fixture promoted = new Fixture(); String primary = promoted.activePrimary( "primary-1", "field-1", "version-current"); promoted.activeReference( "reference-1", primary, "field-1", "version-current"); ElnTemplateVos.ChangeCheck promote = promoted.service.checkPromoteReference( promoted.authorization(), "reference-1"); promoted.service.confirmPromoteReference( promoted.authorization(), "reference-1", promoteConfirm(promote)); List> after = bindingSnapshots( promoted.auditSnapshot("ELN_TEMPLATE_BINDING_PROMOTE"), "after"); assertEquals(2, countProperty(after, "fVersion", 4)); } @Test public void upgradesPrimaryAndAllReferencesAtomically() { Fixture f = new Fixture(); f.selectNextVersion(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); f.activeReference("reference-1", primary, "field-1", "version-current"); f.activeReference("reference-2", primary, "field-1", "version-current"); ElnTemplateVos.ChangeCheck check = f.service.checkUpgrade( f.authorization(), primary, "version-next"); f.service.confirmUpgrade(f.authorization(), primary, upgradeConfirm(check, "version-next")); assertEquals("version-next", f.store.binding(primary).getZiduanBanbenId()); assertEquals("version-next", f.store.binding("reference-1").getZiduanBanbenId()); assertEquals("version-next", f.store.binding("reference-2").getZiduanBanbenId()); assertEquals(3, new LinkedHashSet<>(Arrays.asList( f.store.binding(primary).getCaozuoBianma(), f.store.binding("reference-1").getCaozuoBianma(), f.store.binding("reference-2").getCaozuoBianma())).size()); Map audit = f.auditSnapshot("ELN_TEMPLATE_BINDING_UPGRADE"); List> beforeAudit = bindingSnapshots(audit, "before"); List> afterAudit = bindingSnapshots(audit, "after"); assertEquals(3, beforeAudit.size()); assertEquals(3, afterAudit.size()); assertEquals(3, countProperty(beforeAudit, "ziduanBanbenId", "version-current")); assertEquals(3, countProperty(afterAudit, "ziduanBanbenId", "version-next")); assertEquals(3, countProperty(afterAudit, "fVersion", 4)); assertFalse(f.audit("ELN_TEMPLATE_BINDING_UPGRADE").getDataSnapshot() .toLowerCase().contains("ticket")); assertEquals(2, f.upgradeValidationCalls.get()); f.service.confirmUpgrade(f.authorization(), primary, upgradeConfirm(check, "version-next")); assertEquals(3, f.store.bindings.size()); } @Test public void rejectsIncompleteVersionsStaleSnapshotAndDependencyErrors() { Fixture f = new Fixture(); f.selectNextVersion(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); f.activeReference("reference-1", primary, "field-1", "version-current"); ElnTemplateVos.ChangeCheck check = f.service.checkUpgrade( f.authorization(), primary, "version-next"); ElnTemplateForms.UpgradeConfirm incomplete = upgradeConfirm(check, "version-next"); incomplete.getExpectedVersions().remove("reference-1"); expectDataException(() -> f.service.confirmUpgrade(f.authorization(), primary, incomplete)); ElnTemplateForms.UpgradeConfirm stale = upgradeConfirm(check, "version-next"); stale.getControls().get(0).setControlAlias("被客户端篡改"); expectDataException(() -> f.service.confirmUpgrade(f.authorization(), primary, stale)); f.validatorDiagnostics = Collections.singletonList(error("FORMULA_DEPENDENCY")); expectDataException(() -> f.service.checkUpgrade( f.authorization(), primary, "version-next")); } @Test public void casFailureRollsBackStateAndWritesIndependentFailureAudit() { Fixture f = new Fixture(); f.selectNextVersion(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); f.activeReference("reference-1", primary, "field-1", "version-current"); ElnTemplateVos.ChangeCheck check = f.service.checkUpgrade( f.authorization(), primary, "version-next"); f.store.failCasOnId = "reference-1"; expectDataException(() -> f.service.confirmUpgrade( f.authorization(), primary, upgradeConfirm(check, "version-next"))); assertEquals("version-current", f.store.binding(primary).getZiduanBanbenId()); assertEquals("version-current", f.store.binding("reference-1").getZiduanBanbenId()); assertTrue(f.hasAudit("ELN_TEMPLATE_BINDING_CONFIRM_FAILED")); assertEquals(Arrays.asList(TransactionDefinition.PROPAGATION_REQUIRES_NEW, TransactionDefinition.PROPAGATION_REQUIRES_NEW), f.transactions.completedPropagations()); } @Test public void failureAuditSurvivesCallingOuterTransactionRollback() { Fixture f = new Fixture(); f.selectNextVersion(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); ElnTemplateVos.ChangeCheck check = f.service.checkUpgrade( f.authorization(), primary, "version-next"); f.store.failCasOnId = primary; try { new TransactionTemplate(f.transactions).execute(status -> { try { f.service.confirmUpgrade(f.authorization(), primary, upgradeConfirm(check, "version-next")); } finally { status.setRollbackOnly(); } return null; }); fail("预期抛出 DataException"); } catch (DataException expected) { assertTrue(f.hasAudit("ELN_TEMPLATE_BINDING_CONFIRM_FAILED")); } assertEquals("version-current", f.store.binding(primary).getZiduanBanbenId()); assertEquals(Arrays.asList(TransactionDefinition.PROPAGATION_REQUIRED, TransactionDefinition.PROPAGATION_REQUIRES_NEW, TransactionDefinition.PROPAGATION_REQUIRES_NEW), f.transactions.completedPropagations()); } @Test public void removeMissingRequiresServerDocumentEvidenceAndIgnoresClientBoolean() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); ElnTemplateForms.RemoveMissingConfirm form = new ElnTemplateForms.RemoveMissingConfirm(); form.setOperationCode("remove-missing-1"); form.setExpectedVersion(f.store.binding(primary).getFVersion()); form.setClientReportedMissing(true); expectDataException(() -> f.service.confirmRemoveMissing(primary, form)); form.setReason("服务端体检确认控件缺失"); f.documentTags.add("eln.field.sample_code"); expectDataException(() -> f.service.confirmRemoveMissing(primary, form)); assertEquals("ACTIVE", f.store.binding(primary).getBangdingZhuangtai()); f.documentTags.clear(); f.service.confirmRemoveMissing(primary, form); assertEquals("REMOVED", f.store.binding(primary).getBangdingZhuangtai()); assertEquals(Collections.singletonList(primary), f.terminatedBindingIds); assertEquals("服务端体检确认控件缺失", f.store.binding(primary).getZhuangtaiYuanyin()); assertEquals("template-1.docx", f.lastEvidenceFileId); AuditEventDTO audit = f.audit("ELN_TEMPLATE_BINDING_REMOVE"); assertEquals("ELN_TEMPLATE_BINDING_REMOVE", audit.getActionCode()); assertEquals("服务端体检确认控件缺失", audit.getReason()); Map snapshot = f.auditSnapshot("ELN_TEMPLATE_BINDING_REMOVE"); assertEquals("服务端体检确认控件缺失", snapshot.get("reason")); assertEquals("SUCCESS", snapshot.get("result")); assertEquals(1, countProperty(Collections.singletonList( auditBinding(f, "ELN_TEMPLATE_BINDING_REMOVE", "before")), "bangdingZhuangtai", "ACTIVE")); assertEquals(1, countProperty(Collections.singletonList( auditBinding(f, "ELN_TEMPLATE_BINDING_REMOVE", "after")), "bangdingZhuangtai", "REMOVED")); } @Test public void removeMissingRetryWithSameNormalizedReasonIsIdempotent() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); ElnTemplateForms.RemoveMissingConfirm form = new ElnTemplateForms.RemoveMissingConfirm(); form.setOperationCode("remove-missing-retry"); form.setExpectedVersion(f.store.binding(primary).getFVersion()); form.setReason(" 服务端确认控件缺失 "); f.service.confirmRemoveMissing(primary, form); int persistedVersion = f.store.binding(primary).getFVersion(); int successAudits = f.countAudits("ELN_TEMPLATE_BINDING_REMOVE"); f.template.setBizStatus("PUBLISHED"); form.setReason("\t服务端确认控件缺失\n"); f.service.confirmRemoveMissing(primary, form); assertEquals(persistedVersion, (int) f.store.binding(primary).getFVersion()); assertEquals("服务端确认控件缺失", f.store.binding(primary).getZhuangtaiYuanyin()); assertEquals(successAudits, f.countAudits("ELN_TEMPLATE_BINDING_REMOVE")); } @Test public void removeMissingRetryWithDifferentReasonUsesConflictWithoutChangingRemoval() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); ElnTemplateForms.RemoveMissingConfirm form = new ElnTemplateForms.RemoveMissingConfirm(); form.setOperationCode("remove-missing-retry"); form.setExpectedVersion(f.store.binding(primary).getFVersion()); form.setReason("原因 A"); f.service.confirmRemoveMissing(primary, form); int persistedVersion = f.store.binding(primary).getFVersion(); int successAudits = f.countAudits("ELN_TEMPLATE_BINDING_REMOVE"); form.setReason(" 原因 B "); expectOperationStatus(409, () -> f.service.confirmRemoveMissing(primary, form)); assertEquals("REMOVED", f.store.binding(primary).getBangdingZhuangtai()); assertEquals(persistedVersion, (int) f.store.binding(primary).getFVersion()); assertEquals("原因 A", f.store.binding(primary).getZhuangtaiYuanyin()); assertEquals(successAudits, f.countAudits("ELN_TEMPLATE_BINDING_REMOVE")); assertEquals(1, f.countAudits("ELN_TEMPLATE_BINDING_CONFIRM_FAILED")); assertEquals("FAILED", f.auditSnapshot("ELN_TEMPLATE_BINDING_CONFIRM_FAILED").get("result")); } @Test public void rejectsRemovingPrimaryWithReferencesAndPromotesReferenceAfterDependenciesClear() { Fixture f = new Fixture(); String primary = f.activePrimary("primary-1", "field-1", "version-current"); f.activeReference("reference-1", primary, "field-1", "version-current"); f.activeReference("reference-2", primary, "field-1", "version-current"); expectDataException(() -> f.service.checkRemove(f.authorization(), primary)); ElnTemplateVos.ChangeCheck promote = f.service.checkPromoteReference( f.authorization(), "reference-1"); assertEquals(3, promote.getExpectedVersions().size()); f.service.confirmPromoteReference(f.authorization(), "reference-1", promoteConfirm(promote)); assertEquals("REMOVED", f.store.binding(primary).getBangdingZhuangtai()); ElnTemplateBindingEntity promoted = f.store.binding("reference-1"); assertEquals("PRIMARY", promoted.getKongjianJuese()); assertEquals("eln.field.sample_code", promoted.getKongjianBiaoqian()); assertEquals(null, promoted.getYinyongShiliId()); assertEquals("test-single-step", promoted.getStepId()); assertEquals("reference-1", f.store.binding("reference-2").getYinyongShiliId()); assertNotEquals(f.store.binding(primary).getCaozuoBianma(), promoted.getCaozuoBianma()); assertNotEquals(f.store.binding("reference-2").getCaozuoBianma(), promoted.getCaozuoBianma()); f.service.confirmPromoteReference(f.authorization(), "reference-1", promoteConfirm(promote)); } @Test public void promotesReferenceAfterRemovingPrimaryRegardlessOfBindingIdOrder() { Fixture f = new Fixture(); String primary = f.activePrimary("z-primary", "field-1", "version-current"); f.activeReference("a-reference", primary, "field-1", "version-current"); f.activeReference("b-reference", primary, "field-1", "version-current"); ElnTemplateVos.ChangeCheck promote = f.service.checkPromoteReference( f.authorization(), "a-reference"); f.service.confirmPromoteReference( f.authorization(), "a-reference", promoteConfirm(promote)); assertEquals("REMOVED", f.store.binding(primary).getBangdingZhuangtai()); assertEquals("PRIMARY", f.store.binding("a-reference").getKongjianJuese()); assertEquals("a-reference", f.store.binding("b-reference").getYinyongShiliId()); } @Test public void confirmsVersionCopyOnlyForCompletePendingSetAndRunsParticipantsOnce() { Fixture f = new Fixture(); f.template.setLaiyuanMubanId("source-template"); f.template.setShengbanCaozuoBianma("upgrade-copy-1"); f.pendingCopied("copy-1", "source-binding-1", "upgrade-copy-1:source-binding-1"); f.pendingCopiedReference("copy-2", "source-binding-2", "copy-1", "upgrade-copy-1:source-binding-2"); ElnTemplateForms.VersionCopyConfirm form = new ElnTemplateForms.VersionCopyConfirm(); form.setUpgradeOperationCode("upgrade-copy-1"); form.setExpectedVersions(f.expectedVersions("copy-1", "copy-2")); form.setControls(f.snapshots("copy-1", "copy-2")); ElnTemplateForms.VersionCopyConfirm incomplete = copyVersionForm(form); incomplete.getExpectedVersions().remove("copy-2"); expectDataException(() -> f.service.confirmVersionCopy(f.authorization(), incomplete)); f.service.confirmVersionCopy(f.authorization(), form); f.service.confirmVersionCopy(f.authorization(), form); assertEquals("ACTIVE", f.store.binding("copy-1").getBangdingZhuangtai()); assertEquals("ACTIVE", f.store.binding("copy-2").getBangdingZhuangtai()); assertEquals(1, f.participantCalls.size()); assertEquals("copy-1", f.participantCalls.get(0).get("source-binding-1")); assertEquals("copy-2", f.participantCalls.get(0).get("source-binding-2")); Map audit = f.auditSnapshot("ELN_TEMPLATE_BINDING_VERSION_COPY_CONFIRM"); List> beforeAudit = bindingSnapshots(audit, "before"); List> afterAudit = bindingSnapshots(audit, "after"); assertEquals(2, beforeAudit.size()); assertEquals(2, afterAudit.size()); assertEquals(2, countProperty(beforeAudit, "bangdingZhuangtai", "PENDING")); assertEquals(2, countProperty(afterAudit, "bangdingZhuangtai", "ACTIVE")); assertEquals(2, countProperty(afterAudit, "fVersion", 4)); assertFalse(f.audit("ELN_TEMPLATE_BINDING_VERSION_COPY_CONFIRM").getDataSnapshot() .toLowerCase().contains("ticket")); ElnTemplateForms.VersionCopyConfirm wrongVersion = copyVersionForm(form); wrongVersion.getExpectedVersions().put("copy-1", 99); expectDataException(() -> f.service.confirmVersionCopy(f.authorization(), wrongVersion)); ElnTemplateForms.VersionCopyConfirm wrongStructure = copyVersionForm(form); wrongStructure.getControls().get(0).setControlTag("eln.field.tampered"); expectDataException(() -> f.service.confirmVersionCopy(f.authorization(), wrongStructure)); } @Test public void draftVersionCopyInheritsPreReleasePrivateField() { Fixture f = new Fixture(); f.template.setLaiyuanMubanId("source-template"); f.template.setShengbanCaozuoBianma("upgrade-copy-1"); f.fields.get("field-1").setZuoyongFanwei("TEMPLATE"); f.fields.get("field-1").setMubanBianhao(f.template.getMubanBianhao()); f.pendingCopied("copy-1", "source-binding-1", "upgrade-copy-1:source-binding-1"); f.store.binding("copy-1").setZiduanBanbenId("version-pre"); ElnTemplateForms.VersionCopyConfirm form = new ElnTemplateForms.VersionCopyConfirm(); form.setUpgradeOperationCode("upgrade-copy-1"); form.setExpectedVersions(f.expectedVersions("copy-1")); form.setControls(f.snapshots("copy-1")); f.service.confirmVersionCopy(f.authorization(), form); assertEquals("ACTIVE", f.store.binding("copy-1").getBangdingZhuangtai()); assertEquals(1, f.participantCalls.size()); } @Test public void copiedBindingsRejectSingleActivationCancellationAndShrunkConfirmationSet() { Fixture f = new Fixture(); f.template.setLaiyuanMubanId("source-template"); f.template.setShengbanCaozuoBianma("upgrade-copy-1"); f.pendingCopied("copy-1", "source-binding-1", "upgrade-copy-1:source-binding-1"); f.pendingCopied("copy-2", "source-binding-2", "upgrade-copy-1:source-binding-2"); expectDataException(() -> f.service.activate(f.authorization(), "copy-1", "single-activate")); expectDataException(() -> f.service.cancel( f.authorization(), "copy-1", "single-cancel", "不允许单条取消")); f.store.binding("copy-2").setBangdingZhuangtai("CANCELLED"); ElnTemplateForms.VersionCopyConfirm shrunk = new ElnTemplateForms.VersionCopyConfirm(); shrunk.setUpgradeOperationCode("upgrade-copy-1"); shrunk.setExpectedVersions(f.expectedVersions("copy-1")); shrunk.setControls(f.snapshots("copy-1")); expectDataException(() -> f.service.confirmVersionCopy(f.authorization(), shrunk)); assertEquals("PENDING", f.store.binding("copy-1").getBangdingZhuangtai()); assertTrue(f.participantCalls.isEmpty()); } @Test public void versionCopyMixedStateUsesConflictException() { Fixture f = versionCopyFixture(); f.store.binding("copy-2").setBangdingZhuangtai("ACTIVE"); ElnTemplateForms.VersionCopyConfirm form = versionCopyForm(f); expectOperationStatus(409, () -> f.service.confirmVersionCopy(f.authorization(), form)); } @Test public void versionCopyPendingExpiryUsesConflictException() { Fixture f = versionCopyFixture(); f.store.binding("copy-1").setDaoqiShijian(new Date()); ElnTemplateForms.VersionCopyConfirm form = versionCopyForm(f); expectOperationStatus(409, () -> f.service.confirmVersionCopy(f.authorization(), form)); } @Test public void versionCopyRetryVersionDriftUsesConflictException() { Fixture f = versionCopyFixture(); ElnTemplateForms.VersionCopyConfirm form = versionCopyForm(f); f.service.confirmVersionCopy(f.authorization(), form); ElnTemplateForms.VersionCopyConfirm incomplete = copyVersionForm(form); incomplete.getExpectedVersions().remove("copy-2"); expectOperationStatus(409, () -> f.service.confirmVersionCopy(f.authorization(), incomplete)); ElnTemplateForms.VersionCopyConfirm stale = copyVersionForm(form); stale.getExpectedVersions().put("copy-1", 99); expectOperationStatus(409, () -> f.service.confirmVersionCopy(f.authorization(), stale)); } @Test public void copiedBindingSingleTransitionUsesConflictException() { Fixture f = versionCopyFixture(); expectOperationStatus(409, () -> f.service.activate(f.authorization(), "copy-1", "single-activate")); } private static ElnTemplateForms.ReserveBinding reserve(String fieldId, String versionId, String operationCode) { ElnTemplateForms.ReserveBinding form = new ElnTemplateForms.ReserveBinding(); form.setFieldId(fieldId); form.setFieldVersionId(versionId); form.setOperationCode(operationCode); return form; } private static ElnTemplateForms.ReserveBinding repeatReserve(String fieldId, String versionId, String operationCode, String groupCode, int minRows, int maxRows) { ElnTemplateForms.ReserveBinding form = reserve(fieldId, versionId, operationCode); form.setRepeatRequest(true); form.setRepeatGroupCode(groupCode); form.setRepeatPrototypeRowId("prototype"); form.setMinRows(minRows); form.setMaxRows(maxRows); return form; } private static ElnTemplateForms.ChangeCheckboxGroup checkboxGroupChange( String groupCode, String operationCode, int expectedVersion) { ElnTemplateForms.ChangeCheckboxGroup form = new ElnTemplateForms.ChangeCheckboxGroup(); form.setCheckboxGroupCode(groupCode); form.setOperationCode(operationCode); form.setExpectedVersion(expectedVersion); return form; } private static ElnTemplateVos.CheckboxGroup checkboxGroup( String code, Integer minSelected, Integer maxSelected, Integer version) { ElnTemplateVos.CheckboxGroup group = new ElnTemplateVos.CheckboxGroup(); group.setGroupCode(code); group.setMinSelected(minSelected); group.setMaxSelected(maxSelected); group.setVersion(version); return group; } private static ElnTemplateVos.Binding changeCheckboxGroup( ElnTemplateBindingService service, TemplateAnnotationAuthorization authorization, String bindingId, ElnTemplateForms.ChangeCheckboxGroup form) { return service.changeCheckboxGroup(authorization, bindingId, form); } private static Fixture versionCopyFixture() { Fixture f = new Fixture(); f.template.setLaiyuanMubanId("source-template"); f.template.setShengbanCaozuoBianma("upgrade-copy-1"); f.pendingCopied("copy-1", "source-binding-1", "upgrade-copy-1:source-binding-1"); f.pendingCopiedReference("copy-2", "source-binding-2", "copy-1", "upgrade-copy-1:source-binding-2"); return f; } private static ElnTemplateForms.VersionCopyConfirm versionCopyForm(Fixture f) { ElnTemplateForms.VersionCopyConfirm form = new ElnTemplateForms.VersionCopyConfirm(); form.setUpgradeOperationCode("upgrade-copy-1"); form.setExpectedVersions(f.expectedVersions("copy-1", "copy-2")); form.setControls(f.snapshots("copy-1", "copy-2")); return form; } private static ElnTemplateForms.PrivateField privateField( String operation, String name, String type) { ElnTemplateForms.PrivateField form = new ElnTemplateForms.PrivateField(); form.setOperationCode(operation); form.setZiduanMingcheng(name); form.setShujuLeixing(type); return form; } private static ElnTemplateForms.PrivateField privateFieldCopy( ElnTemplateForms.PrivateField source) { ElnTemplateForms.PrivateField copy = privateField(source.getOperationCode(), source.getZiduanMingcheng(), source.getShujuLeixing()); copy.setDanwei(source.getDanwei()); copy.setShifouBitian(source.getShifouBitian()); copy.setBeizhu(source.getBeizhu()); copy.setCheckboxGroupCode(source.getCheckboxGroupCode()); copy.setCheckboxGroupMinSelected(source.getCheckboxGroupMinSelected()); copy.setCheckboxGroupMaxSelected(source.getCheckboxGroupMaxSelected()); copy.setStepId(source.getStepId()); copy.setKongjianPeizhi(source.getKongjianPeizhi() == null ? null : new LinkedHashMap<>(source.getKongjianPeizhi())); return copy; } private static ElnTemplateForms.RepairConfirm repairConfirm(ElnTemplateVos.ChangeCheck check) { ElnTemplateForms.RepairConfirm form = new ElnTemplateForms.RepairConfirm(); copyCheck(check, form); return form; } private static ElnTemplateForms.UpgradeConfirm upgradeConfirm(ElnTemplateVos.ChangeCheck check, String targetVersionId) { ElnTemplateForms.UpgradeConfirm form = new ElnTemplateForms.UpgradeConfirm(); copyCheck(check, form); form.setTargetFieldVersionId(targetVersionId); return form; } private static ElnTemplateForms.RemoveConfirm removeConfirm(ElnTemplateVos.ChangeCheck check) { ElnTemplateForms.RemoveConfirm form = new ElnTemplateForms.RemoveConfirm(); copyCheck(check, form); return form; } private static ElnTemplateForms.PromoteConfirm promoteConfirm(ElnTemplateVos.ChangeCheck check) { ElnTemplateForms.PromoteConfirm form = new ElnTemplateForms.PromoteConfirm(); copyCheck(check, form); return form; } private static void copyCheck(ElnTemplateVos.ChangeCheck check, ElnTemplateForms.ChangeConfirm form) { form.setOperationCode(check.getOperationCode()); form.setExpectedVersions(new LinkedHashMap<>(check.getExpectedVersions())); form.setControls(copySnapshots(check.getControls())); List observedControlTags = new ArrayList<>(); for (ElnTemplateVos.ControlSnapshot control : check.getControls()) { observedControlTags.add(control.getControlTag()); } form.setObservedControlTags(observedControlTags); } private static List copySnapshots( Collection source) { List result = new ArrayList<>(); for (ElnTemplateVos.ControlSnapshot item : source) { ElnTemplateVos.ControlSnapshot copy = new ElnTemplateVos.ControlSnapshot(); copy.setBindingId(item.getBindingId()); copy.setControlTag(item.getControlTag()); copy.setControlAlias(item.getControlAlias()); copy.setControlType(item.getControlType()); copy.setControlRole(item.getControlRole()); copy.setFieldVersionId(item.getFieldVersionId()); copy.setPrimaryBindingId(item.getPrimaryBindingId()); copy.setRepeatGroupCode(item.getRepeatGroupCode()); copy.setRepeatPrototypeRowId(item.getRepeatPrototypeRowId()); copy.setStepId(item.getStepId()); copy.setControlConfig(item.getControlConfig() == null ? null : new LinkedHashMap<>(item.getControlConfig())); result.add(copy); } return result; } private static ElnTemplateForms.VersionCopyConfirm copyVersionForm( ElnTemplateForms.VersionCopyConfirm source) { ElnTemplateForms.VersionCopyConfirm copy = new ElnTemplateForms.VersionCopyConfirm(); copy.setUpgradeOperationCode(source.getUpgradeOperationCode()); copy.setExpectedVersions(new LinkedHashMap<>(source.getExpectedVersions())); copy.setControls(copySnapshots(source.getControls())); return copy; } private static ElnTemplateVos.Diagnostic error(String code) { ElnTemplateVos.Diagnostic diagnostic = new ElnTemplateVos.Diagnostic(); diagnostic.setSeverity("ERROR"); diagnostic.setCode(code); diagnostic.setMessage(code); return diagnostic; } private static void expectDataException(Runnable action) { try { action.run(); fail("预期抛出 DataException"); } catch (DataException expected) { assertFalse(expected.getMessage().trim().isEmpty()); } } private static void expectOperationStatus(int status, Runnable action) { try { action.run(); fail("预期抛出 ElnTemplateOperationException"); } catch (ElnTemplateOperationException expected) { assertEquals(status, expected.getStatus()); } } @SuppressWarnings("unchecked") private static Map controlConfig(Object target) { try { Method method = target.getClass().getMethod("getControlConfig"); return (Map) method.invoke(target); } catch (ReflectiveOperationException e) { throw new AssertionError("绑定与控件快照必须暴露 controlConfig", e); } } private static void setControlConfig(Object target, Map config) { try { Method method = target.getClass().getMethod("setControlConfig", Map.class); method.invoke(target, config); } catch (ReflectiveOperationException e) { throw new AssertionError("控件快照必须允许提交 controlConfig", e); } } @SuppressWarnings("unchecked") private static List> bindingSnapshots( Map audit, String key) { Object value = audit.get(key); if (!(value instanceof List)) { throw new AssertionError("审计 " + key + " 必须是绑定实体快照列表"); } return (List>) value; } private static int countProperty(List> snapshots, String property, Object expected) { int count = 0; for (Map snapshot : snapshots) { Object actual = null; for (Map.Entry item : snapshot.entrySet()) { if (property.equalsIgnoreCase(item.getKey())) { actual = item.getValue(); break; } } if (expected instanceof Number && actual instanceof Number ? ((Number) expected).longValue() == ((Number) actual).longValue() : Objects.equals(expected, actual)) { count++; } } return count; } @SuppressWarnings("unchecked") private static Map auditBinding( Fixture fixture, String action, String key) { Object value = fixture.auditSnapshot(action).get(key); if (!(value instanceof Map)) { throw new AssertionError("审计 " + key + " 必须是绑定实体快照"); } return (Map) value; } private static int numberProperty(Map snapshot, String property) { for (Map.Entry item : snapshot.entrySet()) { if (property.equalsIgnoreCase(item.getKey()) && item.getValue() instanceof Number) { return ((Number) item.getValue()).intValue(); } } throw new AssertionError("审计快照缺少数字属性: " + property); } private static ElnTemplateBindingServiceImpl testService( ElnTemplateBindingServiceImpl.BindingStore store, ElnFieldCatalogService fieldCatalogService, ElnTemplateRepeatGroupService repeatGroupService, List validators, List participants, List lifecycleParticipants, Supplier bindingIdSupplier, Supplier referenceIdSupplier, Consumer auditRecorder, ElnTemplateDocumentEvidence documentEvidence, Supplier userContextSupplier, ElnTemplateBindingConfirmationFacade facade) { return testService(store, fieldCatalogService, repeatGroupService, validators, participants, lifecycleParticipants, bindingIdSupplier, referenceIdSupplier, auditRecorder, documentEvidence, userContextSupplier, facade, null); } private static ElnTemplateBindingServiceImpl testService( ElnTemplateBindingServiceImpl.BindingStore store, ElnFieldCatalogService fieldCatalogService, ElnTemplateRepeatGroupService repeatGroupService, List validators, List participants, List lifecycleParticipants, Supplier bindingIdSupplier, Supplier referenceIdSupplier, Consumer auditRecorder, ElnTemplateDocumentEvidence documentEvidence, Supplier userContextSupplier, ElnTemplateBindingConfirmationFacade facade, ElnTemplateCheckboxGroupService checkboxGroupService) { try { for (Constructor constructor : ElnTemplateBindingServiceImpl.class.getDeclaredConstructors()) { if (constructor.getParameterCount() == 13 && constructor.getParameterTypes()[0] == ElnTemplateBindingServiceImpl.BindingStore.class && constructor.getParameterTypes()[12] == ElnTemplateCheckboxGroupService.class) { constructor.setAccessible(true); return (ElnTemplateBindingServiceImpl) constructor.newInstance( store, fieldCatalogService, repeatGroupService, validators, participants, lifecycleParticipants, bindingIdSupplier, referenceIdSupplier, auditRecorder, documentEvidence, userContextSupplier, facade, checkboxGroupService); } } throw new AssertionError("未找到测试构造器"); } catch (ReflectiveOperationException e) { throw new AssertionError("创建绑定服务测试实例失败", e); } } private static final class Fixture { private final MemoryStore store = new MemoryStore(); private final Map fields = new LinkedHashMap<>(); private final Map versions = new LinkedHashMap<>(); private final List audits = new ArrayList<>(); private final Set documentTags = new LinkedHashSet<>(); private final List> participantCalls = new ArrayList<>(); private final List terminatedBindingIds = new ArrayList<>(); private final SnapshotTransactionManager transactions = new SnapshotTransactionManager(store); private final AtomicInteger ids = new AtomicInteger(); private final AtomicInteger upgradeValidationCalls = new AtomicInteger(); private final AtomicInteger privateCreateCalls = new AtomicInteger(); private final Map repeatGroups = new LinkedHashMap<>(); private ElnTemplateVersionEntity template; private ElnFieldDefEntity createdPrivate; private ElnFieldForms.TemplateFieldCreate privateCommand; private List validatorDiagnostics = Collections.emptyList(); private List checkboxGroupDiagnostics = Collections.emptyList(); private String lastEvidenceFileId; private int evidenceReads; private final ElnTemplateBindingService service; private Fixture() { this(null); } private Fixture(ElnTemplateCheckboxGroupService checkboxGroupService) { template = template("template-1", "DRAFT", "enabled"); store.templates.put(template.getId(), template); addField("field-1", "sample_code", "version-current"); addVersion("version-current", "field-1", "PUBLISHED", "TEXT", "样品编号"); addVersion("version-old", "field-1", "PUBLISHED", "TEXT", "旧样品编号"); addVersion("version-draft", "field-1", "DRAFT", "TEXT", "草稿字段"); addVersion("version-pre", "field-1", "PRE_RELEASE", "TEXT", "预发布字段"); addVersion("version-next", "field-1", "PUBLISHED", "TEXTAREA", "样品编号新版"); addField("field-2", "remark", "version-2"); addVersion("version-2", "field-2", "PUBLISHED", "TEXT", "备注"); ElnTemplateBindingDependencyValidator validator = new ElnTemplateBindingDependencyValidator() { @Override public List validateUpgrade(String tenantId, String templateId, Set bindingIds, String targetFieldVersionId) { upgradeValidationCalls.incrementAndGet(); return validatorDiagnostics; } @Override public List validateRemoval(String tenantId, String templateId, Set bindingIds) { return validatorDiagnostics; } @Override public List validatePromotion(String tenantId, String templateId, String primaryBindingId, String referenceBindingId) { return validatorDiagnostics; } @Override public List validateCheckboxGroup(String tenantId, String templateId, String bindingId) { return checkboxGroupDiagnostics; } }; ElnTemplateVersionCopyParticipant participant = (tenantId, sourceTemplateId, targetTemplateId, bindingIdMap) -> participantCalls.add( new LinkedHashMap<>(bindingIdMap)); ElnTemplateBindingLifecycleParticipant lifecycleParticipant = (tenantId, bindingId, userId) -> terminatedBindingIds.add(bindingId); ElnFieldCatalogService fieldCatalog = privateFieldCatalog(); ElnTemplateRepeatGroupService repeatGroupService = new ElnTemplateRepeatGroupServiceImpl( new ElnTemplateRepeatGroupServiceImpl.Store() { @Override public ElnTemplateRepeatGroupEntity find(String tenantId, String templateId, String groupCode) { return repeatGroups.get(templateId + ":" + groupCode); } @Override public List list( String tenantId, String templateId) { List result = new ArrayList<>(); for (ElnTemplateRepeatGroupEntity group : repeatGroups.values()) { if (tenantId.equals(group.getFTenantId()) && templateId.equals(group.getMubanId())) { result.add(group); } } return result; } @Override public void insert(ElnTemplateRepeatGroupEntity entity) { repeatGroups.put(entity.getMubanId() + ":" + entity.getRepeatGroupCode(), entity); } }, () -> "repeat-group-" + ids.incrementAndGet()); ElnTemplateBindingConfirmationFacade facade = new ElnTemplateBindingConfirmationFacade(transactions); service = testService(store, fieldCatalog, repeatGroupService, Collections.singletonList(validator), Collections.singletonList(participant), Collections.singletonList(lifecycleParticipant), () -> "binding-new-" + ids.incrementAndGet(), () -> String.format("%032x", ids.incrementAndGet()), audits::add, (tenantId, fileId) -> { evidenceReads++; lastEvidenceFileId = fileId; List controls = new ArrayList<>(); for (String tag : documentTags) { controls.add(new jnpf.elnService.template.docx.DocxControlSnapshot( tag, null, null, "TEXT", "PRIMARY", "word/document.xml", "OK", Collections.emptyMap())); } return controls; }, () -> new ElnTemplateBindingServiceImpl.UserContext("tenant-1", "user-1"), facade, checkboxGroupService); } private TemplateAnnotationAuthorization authorization() { return new TemplateAnnotationAuthorization( "tenant-1", "user-1", "template-1", "template-1.docx"); } private void selectNextVersion() { fields.get("field-1").setDangqianZiduanBanbenId("version-next"); } private void addDropdownVersion(String id) { addVersion(id, "field-1", "PUBLISHED", "SELECT", "样品类型"); versions.get(id).setKongjianPeizhi("{\"placeholder\":\"请选择\"," + "\"options\":[{\"value\":\"A\",\"display\":\"选项 A\"}," + "{\"value\":\"B\",\"display\":\"选项 B\"}]}"); fields.get("field-1").setDangqianZiduanBanbenId(id); } private void addCheckboxFields() { addField("checkbox-1", "result_passed", "checkbox-version-1"); addVersion("checkbox-version-1", "checkbox-1", "PUBLISHED", "CHECKBOX", "合格"); addField("checkbox-2", "result_failed", "checkbox-version-2"); addVersion("checkbox-version-2", "checkbox-2", "PUBLISHED", "CHECKBOX", "不合格"); } private String pendingPrimary(String id, String fieldId, String versionId, String operation) { ElnTemplateBindingEntity binding = binding(id, fieldId, versionId, "PRIMARY", null, "PENDING"); binding.setStepId("test-single-step"); binding.setCaozuoBianma(operation); store.bindings.put(id, binding); return id; } private String activePrimary(String id, String fieldId, String versionId) { ElnTemplateBindingEntity binding = binding(id, fieldId, versionId, "PRIMARY", null, "ACTIVE"); binding.setStepId("test-single-step"); binding.setCaozuoBianma("seed-" + id); store.bindings.put(id, binding); return id; } private void activeReference(String id, String primaryId, String fieldId, String versionId) { ElnTemplateBindingEntity binding = binding(id, fieldId, versionId, "REFERENCE", primaryId, "ACTIVE"); binding.setKongjianBiaoqian("eln.ref." + binding.getZiduanBianma() + "." + id); binding.setCaozuoBianma("seed-" + id); store.bindings.put(id, binding); } private void pendingReference(String id, String primaryId, String fieldId, String versionId) { ElnTemplateBindingEntity binding = binding(id, fieldId, versionId, "REFERENCE", primaryId, "PENDING"); binding.setKongjianBiaoqian("eln.ref." + binding.getZiduanBianma() + "." + id); binding.setCaozuoBianma("seed-" + id); store.bindings.put(id, binding); } private void pendingCopied(String id, String sourceId, String operation) { ElnTemplateBindingEntity binding = binding(id, "field-1", "version-old", "PRIMARY", null, "PENDING"); binding.setLaiyuanBangdingId(sourceId); binding.setCaozuoBianma(operation); binding.setDaoqiShijian(null); binding.setKongjianBiaoqian("eln.field." + sourceId.replace("source-binding-", "copy_")); store.bindings.put(id, binding); } private void pendingCopiedReference(String id, String sourceId, String primaryId, String operation) { ElnTemplateBindingEntity binding = binding(id, "field-1", "version-old", "REFERENCE", primaryId, "PENDING"); binding.setLaiyuanBangdingId(sourceId); binding.setCaozuoBianma(operation); binding.setDaoqiShijian(null); binding.setKongjianBiaoqian("eln.ref.sample_code." + id); store.bindings.put(id, binding); } private ElnTemplateBindingEntity binding(String id, String fieldId, String versionId, String role, String primaryId, String status) { ElnFieldDefEntity field = fields.get(fieldId); ElnFieldVersionEntity version = versions.get(versionId); ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity(); binding.setId(id); binding.setFTenantId("tenant-1"); binding.setMubanId("template-1"); binding.setMubanBianhao("TMP-001"); binding.setZiduanDingyiId(fieldId); binding.setZiduanBianma(field.getZiduanBianma()); binding.setZiduanBanbenId(versionId); binding.setKongjianBiaoqian("eln.field." + field.getZiduanBianma()); binding.setKongjianBieming(version.getZiduanMingcheng()); binding.setKongjianLeixing(controlType(version.getShujuLeixing())); binding.setKongjianJuese(role); binding.setYinyongShiliId(primaryId); binding.setBangdingZhuangtai(status); binding.setFVersion(3); binding.setFDeleteMark(0); return binding; } private void addField(String id, String code, String currentVersion) { ElnFieldDefEntity field = new ElnFieldDefEntity(); field.setId(id); field.setFTenantId("tenant-1"); field.setZiduanBianma(code); field.setZuoyongFanwei("GLOBAL"); field.setDangqianZiduanBanbenId(currentVersion); field.setBizEnabled("enabled"); fields.put(id, field); store.fields.put(id, field); } private void addVersion(String id, String fieldId, String status, String dataType, String name) { ElnFieldVersionEntity version = new ElnFieldVersionEntity(); version.setId(id); version.setFTenantId("tenant-1"); version.setZiduanDingyiId(fieldId); version.setBizStatus(status); version.setShujuLeixing(dataType); version.setZiduanMingcheng(name); version.setKongjianPeizhi("{\"placeholder\":\"" + id + "\",\"options\":[\"A\",\"B\"]}"); versions.put(id, version); store.versions.put(id, version); } private ElnFieldCatalogService privateFieldCatalog() { return new UnsupportedFieldCatalogService() { @Override public ElnFieldVos.Detail createTemplateFieldAndPreRelease( ElnFieldForms.TemplateFieldCreate command) { privateCreateCalls.incrementAndGet(); privateCommand = command; createdPrivate = new ElnFieldDefEntity(); createdPrivate.setId("private-field"); createdPrivate.setFTenantId(command.getTenantId()); createdPrivate.setZiduanBianma("private_note"); createdPrivate.setZuoyongFanwei("TEMPLATE"); createdPrivate.setMubanBianhao(command.getTemplateCode()); createdPrivate.setBizEnabled("enabled"); createdPrivate.setBeizhu(command.getBeizhu()); createdPrivate.setDangqianZiduanBanbenId(null); fields.put(createdPrivate.getId(), createdPrivate); store.fields.put(createdPrivate.getId(), createdPrivate); addVersion("private-version", createdPrivate.getId(), "PRE_RELEASE", command.getShujuLeixing(), command.getZiduanMingcheng()); ElnFieldVersionEntity storedVersion = versions.get("private-version"); storedVersion.setDanwei(command.getDanwei()); storedVersion.setShifouBitian(command.getShifouBitian() == null ? 0 : command.getShifouBitian()); storedVersion.setPiciMoshi(command.getPiciMoshi()); storedVersion.setBangdingPeizhi(JsonUtil.getObjectToString(command.getBinding())); storedVersion.setKongjianPeizhi(command.getKongjianPeizhi() == null ? null : JsonUtil.getObjectToString(command.getKongjianPeizhi())); ElnFieldVos.Version voVersion = new ElnFieldVos.Version(); voVersion.setId("private-version"); voVersion.setBizStatus("PRE_RELEASE"); ElnFieldVos.Detail detail = new ElnFieldVos.Detail(); detail.setId(createdPrivate.getId()); detail.setZiduanBianma(createdPrivate.getZiduanBianma()); detail.setZuoyongFanwei("TEMPLATE"); detail.setMubanBianhao(command.getTemplateCode()); detail.setBizEnabled("enabled"); detail.setVersions(Collections.singletonList(voVersion)); return detail; } }; } private Map expectedVersions(String... ids) { Map result = new LinkedHashMap<>(); for (String id : ids) { result.put(id, store.binding(id).getFVersion()); } return result; } private List snapshots(String... ids) { List result = new ArrayList<>(); for (String id : ids) { ElnTemplateVos.ControlSnapshot snapshot = ElnTemplateBindingServiceImpl.snapshot(store.binding(id)); snapshot.setControlConfig(JsonUtil.getJsonToBean( versions.get(store.binding(id).getZiduanBanbenId()).getKongjianPeizhi(), Map.class)); result.add(snapshot); } return result; } private boolean hasAudit(String actionCode) { for (AuditEventDTO audit : audits) { if (actionCode.equals(audit.getActionCode())) { return true; } } return false; } private int countAudits(String actionCode) { int count = 0; for (AuditEventDTO audit : audits) { if (actionCode.equals(audit.getActionCode())) { count++; } } return count; } private AuditEventDTO audit(String actionCode) { for (AuditEventDTO audit : audits) { if (actionCode.equals(audit.getActionCode())) { return audit; } } throw new AssertionError("未找到审计事件: " + actionCode); } @SuppressWarnings("unchecked") private Map auditSnapshot(String actionCode) { return JsonUtil.getJsonToBean(audit(actionCode).getDataSnapshot(), Map.class); } } private static final class StubCheckboxGroupService implements ElnTemplateCheckboxGroupService { private final List groups; private StubCheckboxGroupService(List groups) { this.groups = groups; } @Override public List list(String tenantId, String templateId) { return groups; } @Override public ElnTemplateVos.CheckboxGroup ensureConfigured(String tenantId, String templateId, String groupCode, Integer minSelected, Integer maxSelected, String operatorId) { for (ElnTemplateVos.CheckboxGroup group : groups) { if (!groupCode.equals(group.getGroupCode())) continue; if (!Objects.equals(minSelected, group.getMinSelected()) || !Objects.equals(maxSelected, group.getMaxSelected())) { throw new DataException("选择组配置与已有配置不一致"); } return group; } ElnTemplateVos.CheckboxGroup created = checkboxGroup( groupCode, minSelected, maxSelected, 0); groups.add(created); return created; } @Override public ElnTemplateVos.CheckboxGroup change(String tenantId, String templateId, String groupCode, Integer minSelected, Integer maxSelected, Integer expectedVersion, String operatorId) { for (ElnTemplateVos.CheckboxGroup group : groups) { if (!groupCode.equals(group.getGroupCode())) continue; if (!Objects.equals(expectedVersion, group.getVersion())) { throw new DataException("选择组配置已被其他用户更新,请刷新后重试"); } group.setMinSelected(minSelected); group.setMaxSelected(maxSelected); group.setVersion(expectedVersion + 1); return group; } throw new DataException("选择组不存在"); } @Override public void removeUnused(String tenantId, String templateId, Set usedGroupCodes, String operatorId) { groups.removeIf(group -> !usedGroupCodes.contains(group.getGroupCode())); } @Override public void copyForVersion(String tenantId, String sourceTemplateId, String targetTemplateId) { } @Override public void afterBindingsActivated(String tenantId, String sourceTemplateId, String targetTemplateId, Map bindingIdMap) { } } private static ElnTemplateVersionEntity template(String id, String status, String enabled) { ElnTemplateVersionEntity template = new ElnTemplateVersionEntity(); template.setId(id); template.setFTenantId("tenant-1"); template.setMubanBianhao("TMP-001"); template.setMubanFile("[{\"fileId\":\"template-1.docx\"}]"); template.setBizStatus(status); template.setBizEnabled(enabled); template.setFDeleteMark(0); return template; } private static String controlType(String dataType) { if ("TEXTAREA".equals(dataType)) { return "MULTILINE"; } if ("CHECKBOX".equals(dataType) || "BOOLEAN".equals(dataType)) { return "CHECKBOX"; } return "TEXT"; } private static final class MemoryStore implements ElnTemplateBindingServiceImpl.BindingStore { private final Map templates = new LinkedHashMap<>(); private final Map fields = new LinkedHashMap<>(); private final Map versions = new LinkedHashMap<>(); private final Map bindings = new LinkedHashMap<>(); private final List lockedTemplateIds = new ArrayList<>(); private final List lockedBindingIds = new ArrayList<>(); private String failCasOnId; @Override public ElnTemplateVersionEntity findTemplate(String tenantId, String templateId) { ElnTemplateVersionEntity item = templates.get(templateId); return item != null && tenantId.equals(item.getFTenantId()) ? item : null; } @Override public ElnTemplateVersionEntity findTemplateForUpdate(String tenantId, String templateId) { lockedTemplateIds.add(templateId); return findTemplate(tenantId, templateId); } @Override public ElnFieldDefEntity findField(String tenantId, String fieldId) { ElnFieldDefEntity item = fields.get(fieldId); return item != null && tenantId.equals(item.getFTenantId()) ? item : null; } @Override public List listFields(String tenantId) { List result = new ArrayList<>(); for (ElnFieldDefEntity item : fields.values()) { if (tenantId.equals(item.getFTenantId())) { result.add(item); } } return result; } @Override public List listFieldVersions(String tenantId, String fieldId) { List result = new ArrayList<>(); for (ElnFieldVersionEntity item : versions.values()) { if (tenantId.equals(item.getFTenantId()) && fieldId.equals(item.getZiduanDingyiId())) { result.add(item); } } return result; } @Override public ElnFieldVersionEntity findFieldVersion(String tenantId, String versionId) { ElnFieldVersionEntity item = versions.get(versionId); return item != null && tenantId.equals(item.getFTenantId()) ? item : null; } @Override public ElnTemplateBindingEntity findBinding(String tenantId, String bindingId) { ElnTemplateBindingEntity item = bindings.get(bindingId); return item != null && tenantId.equals(item.getFTenantId()) ? copy(item) : null; } @Override public ElnTemplateBindingEntity findBindingForUpdate(String tenantId, String bindingId) { lockedBindingIds.add(bindingId); return findBinding(tenantId, bindingId); } @Override public ElnTemplateBindingEntity findByOperation(String tenantId, String templateId, String operationCode) { for (ElnTemplateBindingEntity item : bindings.values()) { if (tenantId.equals(item.getFTenantId()) && templateId.equals(item.getMubanId()) && operationCode.equals(item.getCaozuoBianma())) { return copy(item); } } return null; } @Override public List listBindings(String tenantId, String templateId) { List result = new ArrayList<>(); for (ElnTemplateBindingEntity item : bindings.values()) { if (tenantId.equals(item.getFTenantId()) && templateId.equals(item.getMubanId())) { result.add(copy(item)); } } return result; } @Override public void insert(ElnTemplateBindingEntity binding) { bindings.put(binding.getId(), copy(binding)); } @Override public boolean compareAndSet(ElnTemplateBindingEntity replacement, int expectedVersion, String expectedStatus) { ElnTemplateBindingEntity current = bindings.get(replacement.getId()); if (replacement.getId().equals(failCasOnId) || current == null || current.getFVersion() == null || current.getFVersion() != expectedVersion || !expectedStatus.equals(current.getBangdingZhuangtai())) { return false; } for (ElnTemplateBindingEntity item : bindings.values()) { if (!replacement.getId().equals(item.getId()) && replacement.getFTenantId().equals(item.getFTenantId()) && replacement.getMubanId().equals(item.getMubanId()) && replacement.getCaozuoBianma().equals(item.getCaozuoBianma())) { return false; } if (!replacement.getId().equals(item.getId()) && replacement.getFTenantId().equals(item.getFTenantId()) && replacement.getMubanId().equals(item.getMubanId()) && live(replacement) && live(item) && replacement.getKongjianBiaoqian().equals(item.getKongjianBiaoqian())) { return false; } if (!replacement.getId().equals(item.getId()) && replacement.getFTenantId().equals(item.getFTenantId()) && replacement.getMubanId().equals(item.getMubanId()) && live(replacement) && live(item) && "PRIMARY".equals(replacement.getKongjianJuese()) && "PRIMARY".equals(item.getKongjianJuese()) && replacement.getZiduanBianma().equals(item.getZiduanBianma())) { return false; } } ElnTemplateBindingEntity next = copy(replacement); next.setFVersion(expectedVersion + 1); bindings.put(next.getId(), next); return true; } private static boolean live(ElnTemplateBindingEntity binding) { return "PENDING".equals(binding.getBangdingZhuangtai()) || "ACTIVE".equals(binding.getBangdingZhuangtai()); } private ElnTemplateBindingEntity binding(String id) { return bindings.get(id); } private Map snapshot() { Map snapshot = new LinkedHashMap<>(); for (Map.Entry item : bindings.entrySet()) { snapshot.put(item.getKey(), copy(item.getValue())); } return snapshot; } private void restore(Map snapshot) { bindings.clear(); for (Map.Entry item : snapshot.entrySet()) { bindings.put(item.getKey(), copy(item.getValue())); } } private static ElnTemplateBindingEntity copy(ElnTemplateBindingEntity source) { ElnTemplateBindingEntity copy = new ElnTemplateBindingEntity(); copy.setId(source.getId()); copy.setMubanId(source.getMubanId()); copy.setMubanBianhao(source.getMubanBianhao()); copy.setZiduanDingyiId(source.getZiduanDingyiId()); copy.setZiduanBianma(source.getZiduanBianma()); copy.setZiduanBanbenId(source.getZiduanBanbenId()); copy.setKongjianBiaoqian(source.getKongjianBiaoqian()); copy.setKongjianBieming(source.getKongjianBieming()); copy.setKongjianLeixing(source.getKongjianLeixing()); copy.setKongjianJuese(source.getKongjianJuese()); copy.setYinyongShiliId(source.getYinyongShiliId()); copy.setRepeatGroupCode(source.getRepeatGroupCode()); copy.setRepeatPrototypeRowId(source.getRepeatPrototypeRowId()); copy.setCheckboxGroupCode(source.getCheckboxGroupCode()); copy.setStepId(source.getStepId()); copy.setBangdingZhuangtai(source.getBangdingZhuangtai()); copy.setCaozuoBianma(source.getCaozuoBianma()); copy.setLaiyuanBangdingId(source.getLaiyuanBangdingId()); copy.setDaoqiShijian(source.getDaoqiShijian()); copy.setZhuangtaiYuanyin(source.getZhuangtaiYuanyin()); copy.setFTenantId(source.getFTenantId()); copy.setFVersion(source.getFVersion()); copy.setFDeleteMark(source.getFDeleteMark()); return copy; } } private static final class SnapshotTransactionManager implements PlatformTransactionManager { private final MemoryStore store; private final List propagations = new ArrayList<>(); private final List> snapshots = new ArrayList<>(); private final List completed = new ArrayList<>(); private SnapshotTransactionManager(MemoryStore store) { this.store = store; } @Override public TransactionStatus getTransaction(TransactionDefinition definition) { propagations.add(definition.getPropagationBehavior()); snapshots.add(store.snapshot()); return new SimpleTransactionStatus(); } @Override public void commit(TransactionStatus status) { completed.add(Boolean.TRUE); } @Override public void rollback(TransactionStatus status) { store.restore(snapshots.get(completed.size())); completed.add(Boolean.FALSE); } private List completedPropagations() { return new ArrayList<>(propagations); } } private abstract static class UnsupportedFieldCatalogService implements ElnFieldCatalogService { @Override public void ensureTestSubmissionFields() { } @Override public ElnFieldVos.Detail create(ElnFieldForms.Create form) { throw unsupported(); } @Override public ElnFieldVos.Detail detail(String id) { throw unsupported(); } @Override public com.baomidou.mybatisplus.core.metadata.IPage page( ElnFieldForms.Query query) { throw unsupported(); } @Override public ElnFieldVos.Version createVersion(String fieldId, ElnFieldForms.VersionCreate form) { throw unsupported(); } @Override public ElnFieldVos.Version updateDraft(String versionId, ElnFieldForms.VersionUpdate form) { throw unsupported(); } @Override public void preRelease(String versionId) { throw unsupported(); } @Override public void publish(String versionId) { throw unsupported(); } @Override public void cancel(String versionId, String reason) { throw unsupported(); } @Override public void switchCurrent(String fieldId, String versionId, String reason) { throw unsupported(); } @Override public void promoteToGlobal(String fieldId, String reason) { throw unsupported(); } @Override public void enable(String fieldId, String reason) { throw unsupported(); } @Override public void disable(String fieldId, String reason) { throw unsupported(); } private UnsupportedOperationException unsupported() { return new UnsupportedOperationException(); } } }