package jnpf.dmsService; import jnpf.dmsEntity.permission.entity.DmsAccessExclusionEntity; import jnpf.dmsEntity.permission.entity.DmsPermissionResourceEntity; import jnpf.dmsEntity.permission.enums.DmsResourceType; import jnpf.dmsEntity.permission.enums.DmsSourceType; import jnpf.dmsEntity.permission.form.AccessExclusionBatchForm; import jnpf.dmsEntity.permission.form.AccessExclusionItemForm; import jnpf.dmsEntity.permission.form.AccessExclusionRevokeForm; import jnpf.dmsEntity.permission.vo.DmsAccessExclusionVO; import jnpf.dmsEntity.permission.vo.DmsCursorPageVO; import jnpf.dmsIntegration.audit.DmsAuditAdapter; import jnpf.dmsMapper.DmsAccessExclusionMapper; import jnpf.dmsMapper.DmsPermissionResourceMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsCursorCodec; import jnpf.dmsPermission.DmsPermissionError; import jnpf.dmsPermission.DmsPermissionException; import jnpf.dmsPermission.DmsPermissionProperties; import jnpf.dmsPermission.DmsPrincipalValidator; import jnpf.dmsPermission.DmsUserDisplayNameResolver; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class DmsAccessExclusionServiceTest { private static final String TENANT_ID = "tenant-1"; private static final String ADMIN_ID = "admin-1"; private static final String USER_ID = "user-1"; private static final String SOURCE_ID = "exclude-command-1"; @Test void createsWholeExclusionSetWithOneBatchInsert() { Fixture fixture = new Fixture(); UUID documentId = UUID.randomUUID(); AccessExclusionBatchForm form = form(item(DmsResourceType.DOCUMENT, documentId, "reason")); DmsAccessExclusionEntity created = exclusion(DmsResourceType.DOCUMENT, documentId, "reason"); when(fixture.exclusionMapper.selectBySource(TENANT_ID, "MANUAL", SOURCE_ID)) .thenReturn(Collections.emptyList(), Collections.singletonList(created)); when(fixture.resourceMapper.selectByTenantAndIds(TENANT_ID, Collections.singletonList(documentId))) .thenReturn(Collections.singletonList(resource(DmsResourceType.DOCUMENT, documentId))); when(fixture.exclusionMapper.insertBatch(any())).thenReturn(1); fixture.service.create(form); ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); verify(fixture.exclusionMapper).insertBatch(captor.capture()); assertEquals(1, captor.getValue().size()); DmsAccessExclusionEntity inserted = captor.getValue().get(0); assertEquals("READ", inserted.getActionCode()); assertEquals(USER_ID, inserted.getUserId()); assertEquals(ADMIN_ID, inserted.getExcludedById()); verify(fixture.principalValidator).validateUsers(anyCollection()); verify(fixture.auditAdapter).dataChange("CREATE", "创建 DMS 用户读取排除", "dms_access_exclusions", created.getId().toString(), "reason", null); } @Test void returnsExistingSetForExactSourceRetry() { Fixture fixture = new Fixture(); UUID documentId = UUID.randomUUID(); AccessExclusionBatchForm form = form(item(DmsResourceType.DOCUMENT, documentId, "reason")); when(fixture.exclusionMapper.selectBySource(TENANT_ID, "MANUAL", SOURCE_ID)) .thenReturn(Collections.singletonList(exclusion(DmsResourceType.DOCUMENT, documentId, "reason"))); fixture.service.create(form); verify(fixture.exclusionMapper, never()).insertBatch(any()); verify(fixture.resourceMapper, never()).selectByTenantAndIds(any(), any()); verify(fixture.principalValidator, never()).validateUsers(anyCollection()); } @Test void rejectsDifferentSetUsingSameSourceWithoutWriting() { Fixture fixture = new Fixture(); UUID documentId = UUID.randomUUID(); AccessExclusionBatchForm form = form(item(DmsResourceType.DOCUMENT, documentId, "new reason")); when(fixture.exclusionMapper.selectBySource(TENANT_ID, "MANUAL", SOURCE_ID)) .thenReturn(Collections.singletonList(exclusion(DmsResourceType.DOCUMENT, documentId, "old reason"))); DmsPermissionException error = assertThrows(DmsPermissionException.class, () -> fixture.service.create(form)); assertEquals(DmsPermissionError.SOURCE_CONFLICT, error.getError()); verify(fixture.exclusionMapper, never()).insertBatch(any()); } @Test void rejectsFolderExclusion() { Fixture fixture = new Fixture(); DmsPermissionException error = assertThrows(DmsPermissionException.class, () -> fixture.service.create(form(item(DmsResourceType.FOLDER, UUID.randomUUID(), "reason")))); assertEquals(DmsPermissionError.INVALID_REQUEST, error.getError()); verify(fixture.exclusionMapper, never()).lockSource(any(), any(), any()); } @Test void revokesWholeSetInOneUpdate() { Fixture fixture = new Fixture(); DmsAccessExclusionEntity first = exclusion(DmsResourceType.DOCUMENT, UUID.randomUUID(), "reason"); DmsAccessExclusionEntity second = exclusion(DmsResourceType.FILE_VERSION, UUID.randomUUID(), "reason"); List ids = Arrays.asList(first.getId(), second.getId()); ids.sort(java.util.Comparator.comparing(UUID::toString)); when(fixture.exclusionMapper.selectByTenantAndIdsForUpdate(TENANT_ID, ids)) .thenReturn(Arrays.asList(first, second)); when(fixture.exclusionMapper.revokeBatch(TENANT_ID, ids, "USER", ADMIN_ID, "revoke")) .thenReturn(2); DmsAccessExclusionEntity revokedFirst = exclusion( DmsResourceType.valueOf(first.getResourceType()), first.getResourceId(), "reason"); revokedFirst.setId(first.getId()); revokedFirst.setRevokedAt(new Date()); DmsAccessExclusionEntity revokedSecond = exclusion( DmsResourceType.valueOf(second.getResourceType()), second.getResourceId(), "reason"); revokedSecond.setId(second.getId()); revokedSecond.setRevokedAt(new Date()); when(fixture.exclusionMapper.selectByTenantAndIds(TENANT_ID, ids)) .thenReturn(Arrays.asList(revokedFirst, revokedSecond)); AccessExclusionRevokeForm form = new AccessExclusionRevokeForm(); form.setExclusionIds(ids); form.setReason("revoke"); fixture.service.revoke(form); verify(fixture.exclusionMapper).revokeBatch(TENANT_ID, ids, "USER", ADMIN_ID, "revoke"); } @Test void rejectsSecondRevocationWithoutUpdating() { Fixture fixture = new Fixture(); DmsAccessExclusionEntity existing = exclusion(DmsResourceType.DOCUMENT, UUID.randomUUID(), "reason"); existing.setRevokedAt(new Date()); List ids = Collections.singletonList(existing.getId()); when(fixture.exclusionMapper.selectByTenantAndIdsForUpdate(TENANT_ID, ids)) .thenReturn(Collections.singletonList(existing)); AccessExclusionRevokeForm form = new AccessExclusionRevokeForm(); form.setExclusionIds(ids); form.setReason("revoke again"); DmsPermissionException error = assertThrows(DmsPermissionException.class, () -> fixture.service.revoke(form)); assertEquals(DmsPermissionError.EXCLUSION_ALREADY_REVOKED, error.getError()); verify(fixture.exclusionMapper, never()).revokeBatch(any(), any(), any(), any(), any()); } @Test void queriesExclusionsWithTenantScopedKeysetPage() { Fixture fixture = new Fixture(); UUID resourceId = UUID.randomUUID(); DmsAccessExclusionEntity first = exclusion(DmsResourceType.DOCUMENT, resourceId, "first"); DmsAccessExclusionEntity second = exclusion(DmsResourceType.DOCUMENT, resourceId, "second"); when(fixture.exclusionMapper.selectPage(TENANT_ID, resourceId, USER_ID, null, null, 2)) .thenReturn(Arrays.asList(first, second)); when(fixture.userDisplayNameResolver.resolve(anyCollection())) .thenReturn(Collections.singletonMap(USER_ID, "运营用户(yunying1)")); DmsCursorPageVO result = fixture.service.page( resourceId, " " + USER_ID + " ", null, 1); assertEquals(1, result.getList().size()); assertEquals(first.getId(), result.getList().get(0).getId()); assertEquals("运营用户(yunying1)", result.getList().get(0).getUserName()); assertTrue(result.isHasMore()); assertFalse(result.getNextCursor() == null || result.getNextCursor().isEmpty()); verify(fixture.currentIdentity).requirePermissionAdministrator(); verify(fixture.userDisplayNameResolver).resolve(anyCollection()); } private AccessExclusionBatchForm form(AccessExclusionItemForm item) { AccessExclusionBatchForm result = new AccessExclusionBatchForm(); result.setSourceType(DmsSourceType.MANUAL); result.setSourceId(SOURCE_ID); result.setItems(Collections.singletonList(item)); return result; } private AccessExclusionItemForm item(DmsResourceType type, UUID id, String reason) { AccessExclusionItemForm result = new AccessExclusionItemForm(); result.setResourceType(type); result.setResourceId(id); result.setUserId(USER_ID); result.setExcludeReason(reason); return result; } private DmsPermissionResourceEntity resource(DmsResourceType type, UUID id) { DmsPermissionResourceEntity result = new DmsPermissionResourceEntity(); result.setId(id); result.setResourceType(type.name()); return result; } private DmsAccessExclusionEntity exclusion(DmsResourceType type, UUID resourceId, String reason) { DmsAccessExclusionEntity result = new DmsAccessExclusionEntity(); result.setId(UUID.randomUUID()); result.setTenantId(TENANT_ID); result.setResourceType(type.name()); result.setResourceId(resourceId); result.setUserId(USER_ID); result.setActionCode("READ"); result.setSourceType("MANUAL"); result.setSourceId(SOURCE_ID); result.setValidFrom(new Date(System.currentTimeMillis() - 60_000)); result.setExcludeReason(reason); result.setExcludedByType("USER"); result.setExcludedById(ADMIN_ID); result.setRevision(0L); result.setCreatedAt(new Date()); result.setUpdatedAt(new Date()); return result; } private static class Fixture { private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class); private final DmsPermissionProperties properties = new DmsPermissionProperties(); private final DmsPrincipalValidator principalValidator = mock(DmsPrincipalValidator.class); private final DmsPermissionResourceMapper resourceMapper = mock(DmsPermissionResourceMapper.class); private final DmsAccessExclusionMapper exclusionMapper = mock(DmsAccessExclusionMapper.class); private final DmsCursorCodec cursorCodec = new DmsCursorCodec(); private final DmsAuditAdapter auditAdapter = mock(DmsAuditAdapter.class); private final DmsUserDisplayNameResolver userDisplayNameResolver = mock(DmsUserDisplayNameResolver.class); private final DmsAccessExclusionService service = new DmsAccessExclusionService(currentIdentity, properties, principalValidator, resourceMapper, exclusionMapper, cursorCodec, auditAdapter, userDisplayNameResolver); private Fixture() { when(currentIdentity.tenantId()).thenReturn(TENANT_ID); when(currentIdentity.userId()).thenReturn(ADMIN_ID); when(userDisplayNameResolver.resolve(anyCollection())).thenReturn(Collections.emptyMap()); } } }