package jnpf.dmsPermission; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.entity.DmsFilePermissionEntity; import jnpf.dmsEntity.permission.entity.DmsPermissionUsageEntity; 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.dmsMapper.DmsFilePermissionMapper; import jnpf.dmsMapper.DmsPermissionUsageMapper; import jnpf.dmsService.DmsPermissionDecisionService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Date; 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.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class DmsPermissionGuardTest { private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class); private final DmsPermissionDecisionService decisionService = mock(DmsPermissionDecisionService.class); private final DmsFilePermissionMapper permissionMapper = mock(DmsFilePermissionMapper.class); private final DmsPermissionUsageMapper usageMapper = mock(DmsPermissionUsageMapper.class); private final DmsResourceResolver resourceResolver = mock(DmsResourceResolver.class); private final DmsSubjectResolver subjectResolver = mock(DmsSubjectResolver.class); private final DmsPermissionGroupMembershipResolver groupResolver = mock(DmsPermissionGroupMembershipResolver.class); private final DmsPermissionMatcher matcher = mock(DmsPermissionMatcher.class); private final DmsAccessExclusionResolver exclusionResolver = mock(DmsAccessExclusionResolver.class); private final DmsPermissionGuard guard = new DmsPermissionGuard(currentIdentity, decisionService, permissionMapper, usageMapper, resourceResolver, subjectResolver, groupResolver, matcher, exclusionResolver); private final UUID permissionId = UUID.randomUUID(); private final UUID resourceId = UUID.randomUUID(); 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)); when(resourceResolver.resolve(any(), any(), any())).thenReturn(mock(DmsResourcePath.class)); when(exclusionResolver.isExcluded(any(), any(), any())).thenReturn(false); when(matcher.matches(any(), any(), any())).thenReturn(true); DmsPermissionDecisionVO decision = new DmsPermissionDecisionVO(); decision.setAllowed(true); decision.setMatchedPermissionId(permissionId); when(decisionService.evaluate(any(PermissionDecisionForm.class))).thenReturn(decision); } @Test void rejectsPermissionRevokedWhileWaitingForLock() { DmsFilePermissionEntity permission = activePermission(); permission.setRevokedAt(new Date()); when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission); DmsPermissionOperation operation = mockOperation(); DmsPermissionException error = assertThrows(DmsPermissionException.class, () -> guard.execute(context(), operation)); assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError()); verify(operation, never()).execute(); } @Test void rejectsPermissionExhaustedWhileWaitingForLock() { DmsFilePermissionEntity permission = activePermission(); permission.setMaxUseCount(1L); permission.setUsedCount(1L); when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission); DmsPermissionOperation operation = mockOperation(); DmsPermissionException error = assertThrows(DmsPermissionException.class, () -> guard.execute(context(), operation)); assertEquals(DmsPermissionError.PERMISSION_EXHAUSTED, error.getError()); verify(operation, never()).execute(); } @Test void recordsLimitedUseAfterBusinessOperationSucceeds() { DmsFilePermissionEntity permission = activePermission(); permission.setMaxUseCount(2L); when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission); DmsPermissionOperation operation = mockOperation(); when(operation.execute()).thenReturn("created"); DmsPermissionExecutionContext context = context(); String result = guard.execute(context, operation); assertEquals("created", result); org.mockito.ArgumentCaptor captor = org.mockito.ArgumentCaptor.forClass(DmsPermissionUsageEntity.class); verify(usageMapper).insert(captor.capture()); DmsPermissionUsageEntity usage = captor.getValue(); assertEquals(permissionId, usage.getPermissionId()); assertEquals(resourceId, usage.getActualResourceId()); assertEquals(context.getOperationId(), usage.getOperationId()); assertEquals("READ", usage.getActionCode()); } @Test void replaysPriorUseWithoutExecutingBusinessOperationAgain() { DmsPermissionExecutionContext context = context(); DmsPermissionUsageEntity prior = new DmsPermissionUsageEntity(); prior.setPermissionId(permissionId); prior.setUserId("user-1"); prior.setActualResourceType("DOCUMENT"); prior.setActualResourceId(resourceId); prior.setActionCode("READ"); prior.setOperationId(context.getOperationId()); when(usageMapper.selectByOperation("tenant-1", "user-1", context.getOperationId())).thenReturn(prior); when(permissionMapper.selectByTenantAndId("tenant-1", permissionId)).thenReturn(activePermission()); DmsPermissionOperation operation = mockOperation(); when(operation.replay()).thenReturn("replayed"); String result = guard.execute(context, operation); assertEquals("replayed", result); verify(operation, never()).execute(); verify(operation).replay(); verify(usageMapper, never()).insert(any(DmsPermissionUsageEntity.class)); } @Test void rejectsReadExcludedLimitedPermissionWithoutUsingPermission() { DmsFilePermissionEntity permission = activePermission(); permission.setMaxUseCount(2L); when(permissionMapper.selectByTenantAndIdForUpdate("tenant-1", permissionId)).thenReturn(permission); when(exclusionResolver.isExcluded(any(), any(), any())).thenReturn(true); DmsPermissionOperation operation = mockOperation(); DmsPermissionException error = assertThrows(DmsPermissionException.class, () -> guard.execute(context(), operation)); assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError()); verify(operation, never()).execute(); verify(usageMapper, never()).insert(any(DmsPermissionUsageEntity.class)); } @Test void rejectsIdempotentReplayAfterReadExclusionIsAdded() { DmsPermissionExecutionContext context = context(); DmsPermissionUsageEntity prior = new DmsPermissionUsageEntity(); prior.setPermissionId(permissionId); prior.setUserId("user-1"); prior.setActualResourceType("DOCUMENT"); prior.setActualResourceId(resourceId); prior.setActionCode("READ"); prior.setOperationId(context.getOperationId()); when(usageMapper.selectByOperation("tenant-1", "user-1", context.getOperationId())).thenReturn(prior); when(exclusionResolver.isExcluded(any(), any(), any())).thenReturn(true); DmsPermissionOperation operation = mockOperation(); DmsPermissionException error = assertThrows(DmsPermissionException.class, () -> guard.execute(context, operation)); assertEquals(DmsPermissionError.PERMISSION_DENIED, error.getError()); verify(operation, never()).replay(); verify(permissionMapper, never()).selectByTenantAndId(any(), any()); } @SuppressWarnings("unchecked") private DmsPermissionOperation mockOperation() { return mock(DmsPermissionOperation.class); } private DmsPermissionExecutionContext context() { DmsPermissionExecutionContext context = new DmsPermissionExecutionContext(); context.setResourceType(DmsResourceType.DOCUMENT); context.setResourceId(resourceId); context.setActionCode(DmsPermissionAction.READ); context.setOperationId(UUID.randomUUID()); return context; } private DmsFilePermissionEntity activePermission() { DmsFilePermissionEntity permission = new DmsFilePermissionEntity(); permission.setId(permissionId); permission.setTenantId("tenant-1"); permission.setActionCode("READ"); permission.setValidFrom(new Date(System.currentTimeMillis() - 60_000)); permission.setUsedCount(0L); return permission; } }