From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master

---
 jnpf-dms/jnpf-dms-biz/src/test/java/jnpf/dmsService/DmsPermissionDecisionServiceTest.java |  188 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 188 insertions(+), 0 deletions(-)

diff --git a/jnpf-dms/jnpf-dms-biz/src/test/java/jnpf/dmsService/DmsPermissionDecisionServiceTest.java b/jnpf-dms/jnpf-dms-biz/src/test/java/jnpf/dmsService/DmsPermissionDecisionServiceTest.java
new file mode 100644
index 0000000..85cc609
--- /dev/null
+++ b/jnpf-dms/jnpf-dms-biz/src/test/java/jnpf/dmsService/DmsPermissionDecisionServiceTest.java
@@ -0,0 +1,188 @@
+package jnpf.dmsService;
+
+import jnpf.base.UserInfo;
+import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity;
+import jnpf.dmsEntity.permission.enums.DmsPermissionAction;
+import jnpf.dmsEntity.permission.enums.DmsResourceType;
+import jnpf.dmsEntity.permission.form.PermissionDecisionForm;
+import jnpf.dmsEntity.permission.model.DmsResourcePath;
+import jnpf.dmsEntity.permission.model.DmsSubjectSet;
+import jnpf.dmsEntity.permission.vo.DmsPermissionDecisionVO;
+import jnpf.dmsIntegration.audit.DmsAuditAdapter;
+import jnpf.dmsMapper.DmsFilePermissionMapper;
+import jnpf.dmsPermission.DmsAccessExclusionResolver;
+import jnpf.dmsPermission.DmsCurrentIdentity;
+import jnpf.dmsPermission.DmsPermissionGroupMembershipResolver;
+import jnpf.dmsPermission.DmsPermissionMatcher;
+import jnpf.dmsPermission.DmsPermissionProperties;
+import jnpf.dmsPermission.DmsResourceResolver;
+import jnpf.dmsPermission.DmsSubjectResolver;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class DmsPermissionDecisionServiceTest {
+    private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class);
+    private final DmsSubjectResolver subjectResolver = mock(DmsSubjectResolver.class);
+    private final DmsResourceResolver resourceResolver = mock(DmsResourceResolver.class);
+    private final DmsFilePermissionMapper permissionMapper = mock(DmsFilePermissionMapper.class);
+    private final DmsPermissionGroupMembershipResolver groupResolver =
+            mock(DmsPermissionGroupMembershipResolver.class);
+    private final DmsPermissionMatcher matcher = mock(DmsPermissionMatcher.class);
+    private final DmsPermissionProperties properties = new DmsPermissionProperties();
+    private final DmsAuditAdapter auditAdapter = mock(DmsAuditAdapter.class);
+    private final DmsAccessExclusionResolver exclusionResolver = mock(DmsAccessExclusionResolver.class);
+    private final DmsPermissionDecisionService service = new DmsPermissionDecisionService(
+            currentIdentity, subjectResolver, resourceResolver, permissionMapper, groupResolver,
+            matcher, properties, auditAdapter, exclusionResolver);
+    private final UserInfo user = new UserInfo();
+
+    @BeforeEach
+    void setUp() {
+        user.setTenantId("tenant-1");
+        user.setUserId("user-1");
+        when(currentIdentity.requireUser()).thenReturn(user);
+        when(currentIdentity.tenantId(user)).thenReturn("tenant-1");
+        when(subjectResolver.resolve(user)).thenReturn(mock(DmsSubjectSet.class));
+    }
+
+    @Test
+    void exclusionWinsWithoutLoadingAllowCandidates() {
+        UUID documentId = UUID.randomUUID();
+        PermissionDecisionForm request = request(DmsResourceType.DOCUMENT, documentId);
+        DmsResourcePath path = document(documentId);
+        when(resourceResolver.resolveAll("tenant-1", Collections.singletonList(request)))
+                .thenReturn(Collections.singletonMap(documentId, path));
+        when(exclusionResolver.resolve(eq("tenant-1"), eq("user-1"), any()))
+                .thenReturn(new DmsAccessExclusionResolver.Snapshot(
+                        new HashSet<>(Collections.singletonList(documentId)), Collections.emptySet()));
+
+        DmsPermissionDecisionVO result = service.evaluate(request);
+
+        assertFalse(result.isAllowed());
+        assertEquals("USER_READ_EXCLUDED", result.getReasonCode());
+        verify(permissionMapper, never()).selectCandidates(any(), any(), anyList());
+        verify(subjectResolver, never()).resolve(user);
+    }
+
+    @Test
+    void fileVersionExclusionOnlyDeniesSpecifiedVersionInBatch() {
+        UUID documentId = UUID.randomUUID();
+        UUID excludedVersionId = UUID.randomUUID();
+        UUID visibleVersionId = UUID.randomUUID();
+        PermissionDecisionForm excludedRequest = request(DmsResourceType.FILE_VERSION, excludedVersionId);
+        PermissionDecisionForm visibleRequest = request(DmsResourceType.FILE_VERSION, visibleVersionId);
+        DmsResourcePath excludedPath = version(documentId, excludedVersionId);
+        DmsResourcePath visiblePath = version(documentId, visibleVersionId);
+        Map<UUID, DmsResourcePath> paths = new HashMap<>();
+        paths.put(excludedVersionId, excludedPath);
+        paths.put(visibleVersionId, visiblePath);
+        when(resourceResolver.resolveAll("tenant-1", Arrays.asList(excludedRequest, visibleRequest)))
+                .thenReturn(paths);
+        when(exclusionResolver.resolve(eq("tenant-1"), eq("user-1"), any()))
+                .thenReturn(new DmsAccessExclusionResolver.Snapshot(Collections.emptySet(),
+                        new HashSet<>(Collections.singletonList(excludedVersionId))));
+        when(resourceResolver.candidateResourceIds(visiblePath))
+                .thenReturn(Arrays.asList(visibleVersionId, documentId));
+        DmsFilePermissionEntity allow = new DmsFilePermissionEntity();
+        allow.setId(UUID.randomUUID());
+        when(permissionMapper.selectCandidates("tenant-1", Collections.singletonList("READ"),
+                Arrays.asList(visibleVersionId, documentId))).thenReturn(Collections.singletonList(allow));
+        allow.setActionCode("READ");
+        when(matcher.matches(eq(allow), eq(visiblePath), any(DmsSubjectSet.class))).thenReturn(true);
+
+        List<DmsPermissionDecisionVO> result = service.evaluate(Arrays.asList(excludedRequest, visibleRequest));
+
+        assertFalse(result.get(0).isAllowed());
+        assertEquals("USER_READ_EXCLUDED", result.get(0).getReasonCode());
+        assertTrue(result.get(1).isAllowed());
+        assertEquals(allow.getId(), result.get(1).getMatchedPermissionId());
+        verify(permissionMapper).selectCandidates("tenant-1", Collections.singletonList("READ"),
+                Arrays.asList(visibleVersionId, documentId));
+        verify(groupResolver).resolve(anySet(), any(DmsSubjectSet.class));
+    }
+
+    @Test
+    void twoHundredMixedDecisionsUseOneBulkLoadPerDependency() {
+        List<PermissionDecisionForm> requests = new java.util.ArrayList<>();
+        Map<UUID, DmsResourcePath> paths = new HashMap<>();
+        for (int index = 0; index < properties.getMaxDecisionBatchSize(); index++) {
+            UUID resourceId = UUID.randomUUID();
+            DmsPermissionAction action = index % 2 == 0
+                    ? DmsPermissionAction.READ : DmsPermissionAction.UPDATE;
+            PermissionDecisionForm request = request(DmsResourceType.DOCUMENT, resourceId, action);
+            DmsResourcePath path = document(resourceId);
+            requests.add(request);
+            paths.put(resourceId, path);
+            when(resourceResolver.candidateResourceIds(path)).thenReturn(Collections.singletonList(resourceId));
+        }
+        when(resourceResolver.resolveAll("tenant-1", requests)).thenReturn(paths);
+        when(exclusionResolver.resolve(eq("tenant-1"), eq("user-1"), any()))
+                .thenReturn(DmsAccessExclusionResolver.Snapshot.empty());
+        when(permissionMapper.selectCandidates(eq("tenant-1"), anyList(), anyList()))
+                .thenReturn(Collections.emptyList());
+
+        List<DmsPermissionDecisionVO> result = service.evaluate(requests);
+
+        assertEquals(properties.getMaxDecisionBatchSize(), result.size());
+        assertTrue(result.stream().noneMatch(DmsPermissionDecisionVO::isAllowed));
+        org.mockito.ArgumentCaptor<List<String>> actions = org.mockito.ArgumentCaptor.forClass(List.class);
+        org.mockito.ArgumentCaptor<List<UUID>> resourceIds = org.mockito.ArgumentCaptor.forClass(List.class);
+        verify(resourceResolver, times(1)).resolveAll("tenant-1", requests);
+        verify(subjectResolver, times(1)).resolve(user);
+        verify(exclusionResolver, times(1)).resolve(eq("tenant-1"), eq("user-1"), any());
+        verify(permissionMapper, times(1)).selectCandidates(eq("tenant-1"), actions.capture(),
+                resourceIds.capture());
+        verify(groupResolver, times(1)).resolve(anySet(), any(DmsSubjectSet.class));
+        assertEquals(Arrays.asList("READ", "UPDATE"), actions.getValue());
+        assertEquals(properties.getMaxDecisionBatchSize(), new HashSet<>(resourceIds.getValue()).size());
+    }
+
+    private PermissionDecisionForm request(DmsResourceType type, UUID id) {
+        return request(type, id, DmsPermissionAction.READ);
+    }
+
+    private PermissionDecisionForm request(DmsResourceType type, UUID id, DmsPermissionAction action) {
+        PermissionDecisionForm result = new PermissionDecisionForm();
+        result.setResourceType(type);
+        result.setResourceId(id);
+        result.setActionCode(action);
+        return result;
+    }
+
+    private DmsResourcePath document(UUID documentId) {
+        DmsResourcePath path = new DmsResourcePath();
+        path.setActualResourceType("DOCUMENT");
+        path.setActualResourceId(documentId);
+        path.setDocumentId(documentId);
+        return path;
+    }
+
+    private DmsResourcePath version(UUID documentId, UUID versionId) {
+        DmsResourcePath path = new DmsResourcePath();
+        path.setActualResourceType("FILE_VERSION");
+        path.setActualResourceId(versionId);
+        path.setDocumentId(documentId);
+        return path;
+    }
+}

--
Gitblit v1.8.0