package jnpf.elnService.formula; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.formula.ElnFormulaDefEntity; import jnpf.elnEntity.formula.ElnFormulaParameterSlotEntity; import jnpf.elnEntity.formula.ElnFormulaVersionEntity; import jnpf.elnEntity.formula.ElnTemplateFormulaBindingEntity; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateVos; import jnpf.elnService.formula.impl.FormulaFieldBindingDependencyValidator; import jnpf.elnService.formula.impl.FormulaTemplatePublishValidator; import jnpf.elnService.template.docx.DocxControlSnapshot; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class FormulaTemplateExtensionsTest { @Test public void fieldDependencyValidatorMarksProductionConstructorForSpringInjection() { Constructor production = null; for (Constructor constructor : FormulaFieldBindingDependencyValidator.class.getConstructors()) { if (constructor.getAnnotation(Autowired.class) != null) production = constructor; } assertNotNull("多 public 构造器时生产构造器必须显式 @Autowired", production); } @Test public void publishValidatorMarksProductionConstructorForSpringInjection() { Constructor production = null; for (Constructor constructor : FormulaTemplatePublishValidator.class.getConstructors()) { if (constructor.getAnnotation(Autowired.class) != null) production = constructor; } assertNotNull("多 public 构造器时生产构造器必须显式 @Autowired", production); } @Test public void fieldDependencyValidatorRejectsIncompatibleUpgradeRemovalAndPromotion() { ExtensionStore store = new ExtensionStore(); FormulaFieldBindingDependencyValidator validator = new FormulaFieldBindingDependencyValidator(store); assertCode(validator.validateUpgrade("tenant", "template", Collections.singleton("input"), "target-text"), "FORMULA_FIELD_TYPE_MISMATCH"); assertCode(validator.validateRemoval("tenant", "template", Collections.singleton("result")), "FORMULA_FIELD_IN_USE"); assertCode(validator.validatePromotion("tenant", "template", "result", "result-ref"), "FORMULA_FIELD_IN_USE"); assertCode(validator.validateCheckboxGroup("tenant", "template", "result"), "CHECKBOX_GROUP_FORMULA_RESULT_READ_ONLY"); } @Test public void publishValidatorRejectsPendingAndResultLockMismatch() { ExtensionStore store = new ExtensionStore(); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Collections.singletonList( new DocxControlSnapshot("eln.field.result", null, null, "TEXT", "PRIMARY", "word/document.xml", "OK", Collections.emptyMap())), new FormulaDependencyGraph()); store.formulaBinding.setBangdingZhuangtai("PENDING"); assertCode(validator.validate("tenant", "template", attachment("file.docx")), "PENDING_FORMULA_BINDING"); store.formulaBinding.setBangdingZhuangtai("ACTIVE"); assertCode(validator.validate("tenant", "template", attachment("file.docx")), "FORMULA_RESULT_LOCK_MISMATCH"); } @Test public void publishValidatorDoesNotTreatPendingResultLocksAsOrphans() { ExtensionStore store = new ExtensionStore(); store.formulaBinding.setBangdingZhuangtai("PENDING"); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Arrays.asList( locked("eln.field.result"), locked("eln.field.result-ref")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertCode(diagnostics, "PENDING_FORMULA_BINDING"); assertNoCode(diagnostics, "FORMULA_RESULT_LOCK_MISMATCH"); } @Test public void publishValidatorIgnoresLockedControlsOutsideFieldBindings() { ExtensionStore store = new ExtensionStore(); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Arrays.asList( locked("eln.field.result"), locked("eln.field.result-ref"), locked("document.protected.control")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertNoCode(diagnostics, "FORMULA_RESULT_LOCK_MISMATCH"); } @Test public void publishValidatorRequiresLocksOnlyForActiveReferences() { ExtensionStore store = new ExtensionStore(); store.fields.get("result-ref").setBangdingZhuangtai("REMOVED"); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Collections.singletonList( locked("eln.field.result")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertNoCode(diagnostics, "FORMULA_RESULT_LOCK_MISMATCH"); } @Test public void publishValidatorRejectsParameterAndResultBindingsFromAnotherTemplate() { ExtensionStore store = new ExtensionStore(); store.fields.get("input").setMubanId("other-template"); store.fields.get("result").setMubanId("other-template"); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Collections.singletonList( locked("eln.field.result-ref")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertEquals(2, diagnostics.stream().filter(item -> "FORMULA_BINDING_TEMPLATE_MISMATCH".equals(item.getCode())).count()); } @Test public void publishValidatorRejectsCrossTemplateBindingsWhenFormulaVersionIsMissing() { ExtensionStore store = new ExtensionStore(); store.formulaBinding.setGongshiBanbenId("missing-version"); store.fields.get("input").setMubanId("other-template"); store.fields.get("result").setMubanId("other-template"); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Collections.singletonList( locked("eln.field.result-ref")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertEquals(2, diagnostics.stream().filter(item -> "FORMULA_BINDING_TEMPLATE_MISMATCH".equals(item.getCode())).count()); } @Test public void publishValidatorRejectsCrossTemplateBindingInExtraParameterMapping() { ExtensionStore store = new ExtensionStore(); store.addField("extra", "extra-version", "DECIMAL", "PRIMARY", null); store.fields.get("extra").setMubanId("other-template"); Map mapping = new LinkedHashMap<>(); mapping.put("amount", "input"); mapping.put("extra", "extra"); store.formulaBinding.setCanshuYingshe(mapping); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Arrays.asList( locked("eln.field.result"), locked("eln.field.result-ref")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertEquals(1, diagnostics.stream().filter(item -> "FORMULA_BINDING_TEMPLATE_MISMATCH".equals(item.getCode())).count()); } @Test public void publishValidatorRejectsVersionOwnedByAnotherFormulaDefinition() { ExtensionStore store = new ExtensionStore(); store.formulaVersion.setGongshiDingyiId("other-definition"); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Arrays.asList( locked("eln.field.result"), locked("eln.field.result-ref")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertEquals(1, diagnostics.stream().filter(item -> "FORMULA_DEFINITION_VERSION_MISMATCH".equals(item.getCode())).count()); } @Test public void publishValidatorRejectsDefinitionWithUnexpectedIdentity() { ExtensionStore store = new ExtensionStore(); store.definition.setId("other-definition"); FormulaTemplatePublishValidator validator = new FormulaTemplatePublishValidator( store, (tenantId, fileId) -> Arrays.asList( locked("eln.field.result"), locked("eln.field.result-ref")), new FormulaDependencyGraph()); List diagnostics = validator.validate( "tenant", "template", attachment("file.docx")); assertEquals(1, diagnostics.stream().filter(item -> "FORMULA_DEFINITION_VERSION_MISMATCH".equals(item.getCode())).count()); } private static void assertCode(List diagnostics, String code) { assertTrue("缺少诊断: " + code, diagnostics.stream().anyMatch(item -> code.equals(item.getCode()))); } private static void assertNoCode(List diagnostics, String code) { assertTrue("不应存在诊断: " + code, diagnostics.stream().noneMatch(item -> code.equals(item.getCode()))); } private static DocxControlSnapshot locked(String tag) { return new DocxControlSnapshot(tag, null, "sdtContentLocked", "TEXT", "PRIMARY", "word/document.xml", "OK", Collections.emptyMap()); } private static String attachment(String fileId) { return "[{\"fileId\":\"" + fileId + "\",\"fileExtension\":\"docx\"}]"; } private static final class ExtensionStore implements FormulaFieldBindingDependencyValidator.Store, FormulaTemplatePublishValidator.Store { private final ElnTemplateFormulaBindingEntity formulaBinding = new ElnTemplateFormulaBindingEntity(); private final Map fields = new LinkedHashMap<>(); private final Map fieldVersions = new LinkedHashMap<>(); private final ElnFormulaVersionEntity formulaVersion = new ElnFormulaVersionEntity(); private final ElnFormulaDefEntity definition = new ElnFormulaDefEntity(); private final ElnFormulaParameterSlotEntity slot = new ElnFormulaParameterSlotEntity(); private ExtensionStore() { formulaBinding.setId("formula-binding"); formulaBinding.setFTenantId("tenant"); formulaBinding.setMubanId("template"); formulaBinding.setGongshiDingyiId("definition"); formulaBinding.setGongshiBanbenId("version"); formulaBinding.setCanshuYingshe(Collections.singletonMap("amount", "input")); formulaBinding.setJieguoBangdingId("result"); formulaBinding.setBangdingZhuangtai("ACTIVE"); definition.setId("definition"); definition.setBizEnabled("enabled"); formulaVersion.setId("version"); formulaVersion.setGongshiDingyiId("definition"); formulaVersion.setBizStatus("PUBLISHED"); formulaVersion.setJieguoLeixing("DECIMAL"); slot.setCanshuBianma("amount"); slot.setShujuLeixing("DECIMAL"); addField("input", "input-version", "DECIMAL", "PRIMARY", null); addField("result", "result-version", "DECIMAL", "PRIMARY", null); addField("result-ref", "result-version", "DECIMAL", "REFERENCE", "result"); ElnFieldVersionEntity target = new ElnFieldVersionEntity(); target.setId("target-text"); target.setShujuLeixing("TEXT"); fieldVersions.put(target.getId(), target); } private void addField(String id, String versionId, String type, String role, String primary) { ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity(); binding.setId(id); binding.setMubanId("template"); binding.setZiduanBanbenId(versionId); binding.setKongjianBiaoqian("eln.field." + id); binding.setKongjianJuese(role); binding.setYinyongShiliId(primary); binding.setBangdingZhuangtai("ACTIVE"); fields.put(id, binding); if (!fieldVersions.containsKey(versionId)) { ElnFieldVersionEntity version = new ElnFieldVersionEntity(); version.setId(versionId); version.setShujuLeixing(type); fieldVersions.put(versionId, version); } } @Override public List listFormulaBindings( String tenantId, String templateId) { return Collections.singletonList(formulaBinding); } @Override public ElnTemplateBindingEntity findFieldBinding(String tenantId, String id) { return fields.get(id); } @Override public List listFieldBindings( String tenantId, String templateId) { List result = new java.util.ArrayList<>(); for (ElnTemplateBindingEntity field : fields.values()) if (templateId.equals(field.getMubanId())) result.add(field); return result; } @Override public ElnFieldVersionEntity findFieldVersion(String tenantId, String id) { return fieldVersions.get(id); } @Override public ElnFormulaVersionEntity findFormulaVersion(String tenantId, String id) { return formulaVersion.getId().equals(id) ? formulaVersion : null; } @Override public ElnFormulaDefEntity findFormulaDefinition(String tenantId, String id) { return definition; } @Override public List listSlots(String tenantId, String versionId) { return Collections.singletonList(slot); } } }