package jnpf.elnService.cross; import com.baomidou.mybatisplus.core.conditions.Wrapper; import jnpf.elnEntity.field.ElnFieldDefEntity; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateCrossReferenceEntity; import jnpf.elnEntity.template.ElnTemplateForms; import jnpf.elnEntity.template.ElnTemplateVersionEntity; import jnpf.elnEntity.template.ElnTemplateVos; import jnpf.elnMapper.ElnFieldDefMapper; import jnpf.elnMapper.ElnFieldVersionMapper; import jnpf.elnMapper.ElnTemplateBindingMapper; import jnpf.elnMapper.ElnTemplateCrossReferenceMapper; import jnpf.elnMapper.ElnTemplateMapper; import jnpf.elnService.cross.impl.ElnCrossTemplateScalarReferenceServiceImpl; import jnpf.elnService.template.ElnTemplateBindingService; import jnpf.elnService.template.TemplateAnnotationAuthorization; import jnpf.exception.DataException; import org.junit.Test; import org.mockito.ArgumentCaptor; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class ElnCrossTemplateScalarReferenceServiceImplTest { @Test public void exposesEnabledPublishedSourceTemplatesExceptCurrentTemplate() { Fixture fixture = new Fixture(); List templates = fixture.service.sourceTemplates(authorization()); assertEquals(1, templates.size()); assertEquals("source-template", templates.get(0).getSourceTemplateId()); assertEquals("SOURCE-TEMPLATE", templates.get(0).getSourceTemplateCode()); } @Test public void excludesEveryVersionWithTheCurrentTemplateCode() { Fixture fixture = new Fixture(); assertTrue(fixture.service.sourceTemplates(authorization()).stream() .noneMatch(item -> "TARGET-TEMPLATE".equals(item.getSourceTemplateCode()))); when(fixture.templateMapper.selectOne(any(Wrapper.class))) .thenReturn(fixture.sameCodeTemplate); assertTrue(fixture.service.candidates( authorization(), fixture.sameCodeTemplate.getId()).isEmpty()); } @Test public void exposesOnlyEligibleCandidatesFromSelectedSourceTemplate() { Fixture fixture = new Fixture(); List candidates = fixture.service.candidates(authorization(), "source-template"); assertEquals(1, candidates.size()); assertEquals("source-template", candidates.get(0).getSourceTemplateId()); assertEquals("SOURCE-TEMPLATE", candidates.get(0).getSourceTemplateCode()); assertEquals("source-field", candidates.get(0).getSourceFieldId()); assertEquals("source-version", candidates.get(0).getSourceFieldVersionId()); fixture.field.setBizEnabled("disabled"); assertTrue(fixture.service.candidates(authorization(), "source-template").isEmpty()); assertTrue(fixture.service.candidates(authorization(), "other-template").isEmpty()); } @Test public void reservePinsSourceIdentityAndReturnsStableReadOnlyMarker() { Fixture fixture = new Fixture(); ElnTemplateForms.ReserveCrossTemplateScalarBinding form = fixture.form(); ElnTemplateVos.Binding result = fixture.service.reserve(authorization(), form); assertEquals("CROSS_TEMPLATE", result.getValueSource()); assertEquals(Boolean.TRUE, result.getReadOnly()); ArgumentCaptor captor = ArgumentCaptor.forClass(ElnTemplateCrossReferenceEntity.class); verify(fixture.referenceMapper).insert(captor.capture()); ElnTemplateCrossReferenceEntity rule = captor.getValue(); assertEquals("target-binding", rule.getMubanBangdingId()); assertEquals("source-template", rule.getLaiyuanMubanId()); assertEquals("SOURCE-TEMPLATE", rule.getLaiyuanMubanBianhao()); assertEquals("source-field", rule.getLaiyuanZiduanDingyiId()); assertEquals("source-version", rule.getLaiyuanZiduanBanbenId()); assertEquals("tenant-1", rule.getFTenantId()); } @Test public void rejectsReusingOperationBindingForAnotherSource() { Fixture fixture = new Fixture(); ElnTemplateCrossReferenceEntity existing = new ElnTemplateCrossReferenceEntity(); existing.setLaiyuanMubanBianhao("OTHER-TEMPLATE"); existing.setLaiyuanZiduanBanbenId("source-version"); when(fixture.referenceMapper.selectOne(any(Wrapper.class))).thenReturn(existing); expectDataException(() -> fixture.service.reserve(authorization(), fixture.form()), "已用于其他跨模板来源"); verify(fixture.referenceMapper, never()).insert(any(ElnTemplateCrossReferenceEntity.class)); } @Test public void rejectsCheckboxGroupBeforeReservingCrossTemplateBinding() { Fixture fixture = new Fixture(); ElnTemplateForms.ReserveCrossTemplateScalarBinding form = fixture.form(); form.setCheckboxGroupCode("result_choice"); expectDataException(() -> fixture.service.reserve(authorization(), form), "跨模板只读字段不能加入选择组"); verify(fixture.bindingService, never()).reservePrimary(any(), any()); verify(fixture.referenceMapper, never()).insert(any(ElnTemplateCrossReferenceEntity.class)); } @Test public void contextDecorationRestoresReadOnlyCrossTemplateIdentity() { Fixture fixture = new Fixture(); ElnTemplateVos.Binding binding = new ElnTemplateVos.Binding(); binding.setId("target-binding"); binding.setStatus("PENDING"); ElnTemplateVos.AnnotationContext context = new ElnTemplateVos.AnnotationContext(); context.setBindings(Collections.singletonList(binding)); ElnTemplateCrossReferenceEntity rule = new ElnTemplateCrossReferenceEntity(); rule.setMubanBangdingId("target-binding"); when(fixture.referenceMapper.selectList(any(Wrapper.class))) .thenReturn(Collections.singletonList(rule)); fixture.service.decorate(authorization(), context); assertEquals("CROSS_TEMPLATE", binding.getValueSource()); assertEquals(Boolean.TRUE, binding.getReadOnly()); } private static void expectDataException(Action action, String fragment) { try { action.run(); fail("expected DataException containing " + fragment); } catch (DataException expected) { assertTrue(expected.getMessage().contains(fragment)); } } private static TemplateAnnotationAuthorization authorization() { try { Constructor constructor = TemplateAnnotationAuthorization.class.getDeclaredConstructor( String.class, String.class, String.class, String.class); constructor.setAccessible(true); return constructor.newInstance("tenant-1", "user-1", "target-template", "file-1"); } catch (ReflectiveOperationException e) { throw new AssertionError(e); } } private interface Action { void run(); } private static final class Fixture { private final ElnTemplateMapper templateMapper = mock(ElnTemplateMapper.class); private final ElnTemplateBindingMapper bindingMapper = mock(ElnTemplateBindingMapper.class); private final ElnFieldDefMapper fieldMapper = mock(ElnFieldDefMapper.class); private final ElnFieldVersionMapper versionMapper = mock(ElnFieldVersionMapper.class); private final ElnTemplateCrossReferenceMapper referenceMapper = mock(ElnTemplateCrossReferenceMapper.class); private final ElnTemplateBindingService bindingService = mock(ElnTemplateBindingService.class); private final ElnFieldDefEntity field = new ElnFieldDefEntity(); private final ElnTemplateVersionEntity sameCodeTemplate = new ElnTemplateVersionEntity(); private final ElnCrossTemplateScalarReferenceServiceImpl service; private Fixture() { ElnTemplateVersionEntity template = new ElnTemplateVersionEntity(); template.setId("source-template"); template.setFTenantId("tenant-1"); template.setMubanBianhao("SOURCE-TEMPLATE"); template.setBizStatus("PUBLISHED"); template.setBizEnabled("enabled"); template.setFDeleteMark(0); ElnTemplateVersionEntity targetTemplate = new ElnTemplateVersionEntity(); targetTemplate.setId("target-template"); targetTemplate.setFTenantId("tenant-1"); targetTemplate.setMubanBianhao("TARGET-TEMPLATE"); targetTemplate.setBizStatus("PUBLISHED"); targetTemplate.setBizEnabled("enabled"); targetTemplate.setFDeleteMark(0); sameCodeTemplate.setId("same-code-template"); sameCodeTemplate.setFTenantId("tenant-1"); sameCodeTemplate.setMubanBianhao("TARGET-TEMPLATE"); sameCodeTemplate.setBizStatus("PUBLISHED"); sameCodeTemplate.setBizEnabled("enabled"); sameCodeTemplate.setFDeleteMark(0); when(templateMapper.selectList(any(Wrapper.class))) .thenReturn(Arrays.asList(template, targetTemplate, sameCodeTemplate)); when(templateMapper.selectOne(any(Wrapper.class))).thenReturn(template); when(templateMapper.selectById("target-template")).thenReturn(targetTemplate); ElnTemplateBindingEntity sourceBinding = new ElnTemplateBindingEntity(); sourceBinding.setId("source-binding"); sourceBinding.setMubanId("source-template"); sourceBinding.setZiduanDingyiId("source-field"); sourceBinding.setZiduanBanbenId("source-version"); sourceBinding.setBangdingZhuangtai("ACTIVE"); sourceBinding.setKongjianJuese("PRIMARY"); sourceBinding.setFTenantId("tenant-1"); sourceBinding.setFDeleteMark(0); when(bindingMapper.selectList(any(Wrapper.class))) .thenReturn(Collections.singletonList(sourceBinding)); field.setId("source-field"); field.setFTenantId("tenant-1"); field.setZiduanBianma("source_text"); field.setZuoyongFanwei("GLOBAL"); field.setBizEnabled("enabled"); field.setFDeleteMark(0); when(fieldMapper.selectById("source-field")).thenReturn(field); ElnFieldVersionEntity version = new ElnFieldVersionEntity(); version.setId("source-version"); version.setFTenantId("tenant-1"); version.setZiduanDingyiId("source-field"); version.setZiduanMingcheng("来源文本"); version.setShujuLeixing("TEXT"); version.setBizStatus("PUBLISHED"); version.setFDeleteMark(0); when(versionMapper.selectById("source-version")).thenReturn(version); ElnTemplateVos.Binding target = new ElnTemplateVos.Binding(); target.setId("target-binding"); target.setStatus("PENDING"); when(bindingService.reservePrimary(any(), any())).thenReturn(target); when(referenceMapper.insert(any(ElnTemplateCrossReferenceEntity.class))).thenReturn(1); service = new ElnCrossTemplateScalarReferenceServiceImpl( templateMapper, bindingMapper, fieldMapper, versionMapper, referenceMapper, bindingService); } private ElnTemplateForms.ReserveCrossTemplateScalarBinding form() { ElnTemplateForms.ReserveCrossTemplateScalarBinding form = new ElnTemplateForms.ReserveCrossTemplateScalarBinding(); form.setOperationCode("reserve-op"); form.setSourceTemplateId("source-template"); form.setSourceTemplateCode("SOURCE-TEMPLATE"); form.setSourceFieldId("source-field"); form.setSourceFieldVersionId("source-version"); return form; } } }