package jnpf.dmsService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jnpf.dmsEntity.watermark.DmsWatermarkConfigEntity; import jnpf.dmsEntity.watermark.DmsWatermarkPositionEntity; import jnpf.dmsEntity.watermark.form.DmsWatermarkConfigForm; import jnpf.dmsEntity.watermark.form.DmsWatermarkPositionForm; import jnpf.dmsEntity.watermark.form.DmsWatermarkQuery; import jnpf.dmsMapper.DmsWatermarkConfigMapper; import jnpf.dmsMapper.DmsWatermarkPositionMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.base.DictionaryDataApi; import jnpf.base.entity.DictionaryDataEntity; import jnpf.exception.DataException; import org.springframework.dao.DuplicateKeyException; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.apache.ibatis.session.Configuration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.lenient; @ExtendWith(MockitoExtension.class) class DmsWatermarkConfigServiceTest { @Mock private DmsWatermarkConfigMapper configMapper; @Mock private DmsWatermarkPositionMapper positionMapper; @Mock private DmsCurrentIdentity identity; @Mock private DictionaryDataApi dictionaryDataApi; private DmsWatermarkConfigService service; @BeforeEach void setUp() { initTable(DmsWatermarkConfigEntity.class); initTable(DmsWatermarkPositionEntity.class); service = new DmsWatermarkConfigService(configMapper, positionMapper, identity, dictionaryDataApi); DictionaryDataEntity entry = new DictionaryDataEntity(); entry.setDictionaryTypeId("879384732964547653"); entry.setEnCode("RECORD"); entry.setEnabledMark(1); lenient().when(dictionaryDataApi.getList("879384732964547653", "true")).thenReturn(List.of(entry)); } @Test void menuUserCanReadWatermarks() throws Exception { when(identity.tenantId()).thenReturn("tenant-1"); DmsWatermarkConfigEntity row = new DmsWatermarkConfigEntity(); row.setId(UUID.fromString("0199d0e5-8e70-7000-8000-000000000001")); row.setTenantId("tenant-1"); row.setName("下载水印"); Page page = new Page<>(1, 20); page.setRecords(Collections.singletonList(row)); page.setTotal(1); when(configMapper.selectPage(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(page); when(configMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(row); when(positionMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.emptyList()); DmsWatermarkQuery query = new DmsWatermarkQuery(); query.setCurrentPage(1); query.setPageSize(20); assertEquals(1, service.list(query).size()); assertEquals(row.getId(), service.detail(row.getId()).getId()); assertEquals("LEFT", service.detail(row.getId()).getHorizontalAlign()); assertEquals("TOP", service.detail(row.getId()).getVerticalAlign()); verify(identity, never()).requirePermissionAdministrator(); verify(configMapper, never()).insert(any(DmsWatermarkConfigEntity.class)); } @Test void menuUserCanManageWatermarks() throws Exception { when(identity.tenantId()).thenReturn("tenant-1"); when(identity.userId()).thenReturn("user-1"); when(configMapper.nextId()).thenAnswer(ignored -> UUID.randomUUID()); DmsWatermarkConfigEntity existing = new DmsWatermarkConfigEntity(); existing.setId(UUID.fromString("0199d0e5-8e70-7000-8000-000000000001")); existing.setTenantId("tenant-1"); existing.setRevision(1L); when(configMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(existing); when(configMapper.update(any(DmsWatermarkConfigEntity.class), any(LambdaUpdateWrapper.class))).thenReturn(1); when(positionMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.emptyList()); DmsWatermarkConfigForm form = validForm(); form.setRevision(1L); service.create(form); assertEquals(existing.getId(), service.update(existing.getId(), form).getId()); assertEquals(existing.getId(), service.setEnabled(existing.getId(), 1L, false).getId()); assertEquals(existing.getId(), service.delete(existing.getId(), 1L).getId()); verify(identity, never()).requirePermissionAdministrator(); } @Test void createsTiledTextWatermarkWithFullyVisibleOpacityAndFreeTextEnterprise() throws Exception { when(identity.tenantId()).thenReturn("tenant-1"); when(identity.userId()).thenReturn("user-1"); when(configMapper.nextId()).thenReturn( UUID.fromString("0199d0e5-8e70-7000-8000-000000000001"), UUID.fromString("0199d0e5-8e70-7000-8000-000000000002")); DmsWatermarkConfigForm form = validForm(); form.setOpacity(100); form.setLayoutMode("TILED"); form.setRepeatSpacingXMm(new BigDecimal("18")); form.setRepeatSpacingYMm(new BigDecimal("24")); service.create(form); verify(configMapper).insertBusinessType("tenant-1", UUID.fromString("0199d0e5-8e70-7000-8000-000000000001"), "RECORD"); ArgumentCaptor config = ArgumentCaptor.forClass(DmsWatermarkConfigEntity.class); verify(configMapper).insert(config.capture()); assertEquals(100, config.getValue().getOpacity()); assertEquals("LEFT", config.getValue().getHorizontalAlign()); assertEquals("TOP", config.getValue().getVerticalAlign()); assertEquals("ALL", config.getValue().getVersionScope()); assertEquals("广东丹霞生物制药有限公司", config.getValue().getEnterpriseId()); assertEquals("广东丹霞生物制药有限公司", config.getValue().getEnterpriseName()); assertEquals(Boolean.TRUE, config.getValue().getLoadDownload()); assertEquals(Boolean.TRUE, config.getValue().getLoadPrint()); ArgumentCaptor position = ArgumentCaptor.forClass(DmsWatermarkPositionEntity.class); verify(positionMapper).insert(position.capture()); assertEquals("A4", position.getValue().getPaperType()); assertEquals(new BigDecimal("210"), position.getValue().getPaperWidthMm()); assertEquals(new BigDecimal("297"), position.getValue().getPaperHeightMm()); assertEquals(new BigDecimal("65"), position.getValue().getPortraitXMm()); assertEquals(new BigDecimal("138"), position.getValue().getPortraitYMm()); assertEquals(new BigDecimal("108"), position.getValue().getLandscapeXMm()); assertEquals(new BigDecimal("95"), position.getValue().getLandscapeYMm()); } @Test void createsBarcodeWatermarkWithTextDisplayEnabled() throws Exception { when(identity.tenantId()).thenReturn("tenant-1"); when(identity.userId()).thenReturn("user-1"); when(configMapper.nextId()).thenReturn( UUID.fromString("0199d0e5-8e70-7000-8000-000000000001"), UUID.fromString("0199d0e5-8e70-7000-8000-000000000002")); DmsWatermarkConfigForm form = validForm(); form.setWatermarkType("BARCODE"); form.setBarcodeFormat("QR_CODE"); form.setShowBarcodeText(true); service.create(form); ArgumentCaptor config = ArgumentCaptor.forClass(DmsWatermarkConfigEntity.class); verify(configMapper).insert(config.capture()); assertEquals(Boolean.TRUE, config.getValue().getShowBarcodeText()); } @Test void customBarcodeAlwaysPersistsItsVisibleTextOption() throws Exception { when(identity.tenantId()).thenReturn("tenant-1"); when(identity.userId()).thenReturn("user-1"); when(configMapper.nextId()).thenAnswer(ignored -> UUID.randomUUID()); DmsWatermarkConfigForm form = validForm(); form.setWatermarkType("BARCODE"); form.setBarcodeFormat("QR_CODE"); form.setContentTemplate(jnpf.dmsEntity.watermark.DmsWatermarkParameters.CUSTOM_BARCODE_TEMPLATE); form.setShowBarcodeText(false); service.create(form); ArgumentCaptor config = ArgumentCaptor.forClass(DmsWatermarkConfigEntity.class); verify(configMapper).insert(config.capture()); assertEquals(Boolean.TRUE, config.getValue().getShowBarcodeText()); assertEquals(form.getContentTemplate(), config.getValue().getContentTemplate()); } @Test void customPlaceholderCannotBeMixedWithTemplatesOrUsedForTextWatermarks() { DmsWatermarkConfigForm form = validForm(); form.setContentTemplate(jnpf.dmsEntity.watermark.DmsWatermarkParameters.CUSTOM_BARCODE_TEMPLATE); assertThrows(DataException.class, () -> service.create(form)); form.setWatermarkType("BARCODE"); form.setBarcodeFormat("QR_CODE"); form.setContentTemplate("prefix" + form.getContentTemplate()); assertThrows(DataException.class, () -> service.create(form)); verify(configMapper, never()).insert(any(DmsWatermarkConfigEntity.class)); } @Test void rejectsConfigurationWithoutLoadSceneBeforePersistence() { DmsWatermarkConfigForm form = validForm(); form.setLoadScenes(Collections.emptyList()); DataException error = assertThrows(DataException.class, () -> service.create(form)); assertEquals("至少选择一个水印加载场景", error.getMessage()); verify(configMapper, never()).insert(any(DmsWatermarkConfigEntity.class)); } @Test void rejectsConfigurationWithoutBusinessType() { DmsWatermarkConfigForm form = validForm(); form.setWatermarkBusinessTypes(Collections.emptyList()); assertEquals("水印业务配置必须选择 1 到 50 项", assertThrows(DataException.class, () -> service.create(form)).getMessage()); verify(configMapper, never()).insert(any(DmsWatermarkConfigEntity.class)); } @Test void rejectsLandscapeCoordinateOutsideRotatedPage() { DmsWatermarkConfigForm form = validForm(); form.getPositions().get(0).setLandscapeYMm(new BigDecimal("211")); DataException error = assertThrows(DataException.class, () -> service.create(form)); assertEquals("横向纵坐标超出允许范围", error.getMessage()); verify(configMapper, never()).insert(any(DmsWatermarkConfigEntity.class)); } @Test void rejectsDuplicateNameWhenCreating() { when(identity.tenantId()).thenReturn("tenant-1"); when(configMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(1L); DataException error = assertThrows(DataException.class, () -> service.create(validForm())); assertEquals("已存在同名水印,请更换名称", error.getMessage()); verify(configMapper, never()).insert(any(DmsWatermarkConfigEntity.class)); } @Test void rejectsDuplicateNameWhenEditing() { UUID id = UUID.randomUUID(); DmsWatermarkConfigEntity existing = new DmsWatermarkConfigEntity(); existing.setId(id); existing.setRevision(1L); when(identity.tenantId()).thenReturn("tenant-1"); when(configMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(existing); when(configMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(1L); DmsWatermarkConfigForm form = validForm(); form.setRevision(1L); DataException error = assertThrows(DataException.class, () -> service.update(id, form)); assertEquals("已存在同名水印,请更换名称", error.getMessage()); ArgumentCaptor> query = ArgumentCaptor.forClass(LambdaQueryWrapper.class); verify(configMapper).selectCount(query.capture()); org.junit.jupiter.api.Assertions.assertTrue(query.getValue().getSqlSegment().contains("<>")); verify(configMapper, never()).update(any(DmsWatermarkConfigEntity.class), any(LambdaUpdateWrapper.class)); } @Test void reportsConcurrentDuplicateNameOnInsert() { when(identity.tenantId()).thenReturn("tenant-1"); when(identity.userId()).thenReturn("user-1"); when(configMapper.nextId()).thenReturn(UUID.randomUUID()); when(configMapper.insert(any(DmsWatermarkConfigEntity.class))).thenThrow( new DuplicateKeyException("uq_dms_watermark_configs_active_name")); DataException error = assertThrows(DataException.class, () -> service.create(validForm())); assertEquals("已存在同名水印,请更换名称", error.getMessage()); } @Test void reportsConcurrentDuplicateNameOnUpdate() { UUID id = UUID.randomUUID(); DmsWatermarkConfigEntity existing = new DmsWatermarkConfigEntity(); existing.setId(id); existing.setRevision(1L); when(identity.tenantId()).thenReturn("tenant-1"); when(identity.userId()).thenReturn("user-1"); when(configMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(existing); when(configMapper.update(any(DmsWatermarkConfigEntity.class), any(LambdaUpdateWrapper.class))).thenThrow( new DuplicateKeyException("uq_dms_watermark_configs_active_name")); DmsWatermarkConfigForm form = validForm(); form.setRevision(1L); DataException error = assertThrows(DataException.class, () -> service.update(id, form)); assertEquals("已存在同名水印,请更换名称", error.getMessage()); } @Test void persistsAndReturnsAlignmentOnCreateAndUpdate() throws Exception { when(identity.tenantId()).thenReturn("tenant-1"); when(identity.userId()).thenReturn("user-1"); when(configMapper.nextId()).thenAnswer(ignored -> UUID.randomUUID()); when(positionMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.emptyList()); DmsWatermarkConfigEntity existing = new DmsWatermarkConfigEntity(); existing.setId(UUID.randomUUID()); existing.setRevision(1L); when(configMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(existing); when(configMapper.update(any(DmsWatermarkConfigEntity.class), any(LambdaUpdateWrapper.class))).thenReturn(1); DmsWatermarkConfigForm form = validForm(); form.setHorizontalAlign("center"); form.setVerticalAlign("bottom"); form.setRevision(1L); var created = service.create(form); var updated = service.update(existing.getId(), form); assertEquals("CENTER", created.getHorizontalAlign()); assertEquals("BOTTOM", updated.getVerticalAlign()); ArgumentCaptor saved = ArgumentCaptor.forClass(DmsWatermarkConfigEntity.class); verify(configMapper).insert(saved.capture()); assertEquals("CENTER", saved.getValue().getHorizontalAlign()); assertEquals("BOTTOM", saved.getValue().getVerticalAlign()); verify(configMapper).update(saved.capture(), any(LambdaUpdateWrapper.class)); assertEquals("CENTER", saved.getValue().getHorizontalAlign()); assertEquals("BOTTOM", saved.getValue().getVerticalAlign()); } @Test void rejectsUnknownAlignmentBeforePersistence() { DmsWatermarkConfigForm form = validForm(); form.setHorizontalAlign("JUSTIFY"); assertEquals("水平对齐取值无效", assertThrows(DataException.class, () -> service.create(form)).getMessage()); form.setHorizontalAlign("LEFT"); form.setVerticalAlign("CENTER"); assertEquals("垂直对齐取值无效", assertThrows(DataException.class, () -> service.create(form)).getMessage()); verify(configMapper, never()).insert(any(DmsWatermarkConfigEntity.class)); } private DmsWatermarkConfigForm validForm() { DmsWatermarkPositionForm position = new DmsWatermarkPositionForm(); position.setPaperType("A4"); position.setPaperWidthMm(new BigDecimal("210")); position.setPaperHeightMm(new BigDecimal("297")); position.setPortraitXMm(new BigDecimal("65")); position.setPortraitYMm(new BigDecimal("138")); position.setLandscapeXMm(new BigDecimal("108")); position.setLandscapeYMm(new BigDecimal("95")); DmsWatermarkConfigForm form = new DmsWatermarkConfigForm(); form.setName("下载水印"); form.setWatermarkType("TEXT"); form.setVersionScope("ALL"); form.setLoadScenes(Arrays.asList("DOWNLOAD", "PRINT")); form.setWatermarkBusinessTypes(List.of("RECORD")); form.setEnabled(true); form.setEnterpriseName("广东丹霞生物制药有限公司"); form.setContentTemplate("下载人:{/CUN}"); form.setFontFamily("SimHei"); form.setFontColor("#666666"); form.setFontSize(24); form.setRotationAngle(-30); form.setOpacity(30); form.setLayoutMode("POSITION"); form.setMarkWidthMm(new BigDecimal("80")); form.setMarkHeightMm(new BigDecimal("20")); form.setPositions(Collections.singletonList(position)); return form; } private void initTable(Class type) { if (TableInfoHelper.getTableInfo(type) == null) { TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), ""), type); } } }