package jnpf.dmsService; import jnpf.base.UserInfo; import jnpf.dmsEntity.permission.model.DmsSubjectSet; import jnpf.dmsEntity.permission.enums.DmsPermissionAction; import jnpf.dmsEntity.permission.enums.DmsResourceType; import jnpf.dmsEntity.permission.vo.DmsCursorPageVO; import jnpf.dmsEntity.permission.vo.DmsFileCenterItemVO; import jnpf.dmsEntity.permission.vo.DmsFolderTreeNodeVO; import jnpf.dmsMapper.DmsFileCenterMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsPermission.DmsFileCenterCursorCodec; import jnpf.dmsPermission.DmsFileCenterQuery; import jnpf.dmsPermission.DmsPermissionException; import jnpf.dmsPermission.DmsPermissionGroupMembershipResolver; import jnpf.dmsPermission.DmsPermissionProperties; import jnpf.dmsPermission.DmsSubjectResolver; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import java.util.Arrays; import java.util.Collections; 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.assertNotNull; 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.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; class DmsFileCenterServiceTest { @Test void folderTreeReturnsOnlyOnePageWithPermissionFilteredExpansionState() { Fixture fixture = new Fixture(false); when(fixture.subjectResolver.resolve(fixture.user)).thenReturn(new DmsSubjectSet()); DmsFileCenterItemVO first = folder("A", fixture.firstId); first.setParentId(fixture.folderId); first.setHasChildren(true); DmsFileCenterItemVO second = folder("B", fixture.secondId); second.setHasChildren(false); when(fixture.mapper.selectFolders(any())).thenReturn(Arrays.asList(first, second)); DmsCursorPageVO result = fixture.service.folderTree(fixture.folderId, null, 1); assertEquals(1, result.getList().size()); assertEquals(first.getId(), result.getList().get(0).getId()); assertEquals(first.getParentId(), result.getList().get(0).getParentId()); assertEquals(first.getName(), result.getList().get(0).getName()); assertTrue(result.getList().get(0).isHasChildren()); assertTrue(result.isHasMore()); DmsFileCenterCursorCodec.Cursor decoded = fixture.cursorCodec.decode(result.getNextCursor(), "FOLDER", "tenant-1", "user-1", fixture.folderId, fixture.cursorCodec.fingerprint(null)); assertEquals(first.getId(), decoded.getId()); assertEquals(first.getSortCode(), decoded.getSortCode()); ArgumentCaptor captor = ArgumentCaptor.forClass(DmsFileCenterQuery.class); verify(fixture.mapper).selectFolders(captor.capture()); assertTrue(captor.getValue().isIncludeFolderChildren()); assertFalse(captor.getValue().isAdministrator()); assertEquals(fixture.folderId, captor.getValue().getParentId()); assertEquals("tenant-1", captor.getValue().getTenantId()); assertEquals("user-1", captor.getValue().getUserId()); } @Test void folderTreeSupportsRootLeafAndEmptyResults() { Fixture fixture = new Fixture(true); DmsFileCenterItemVO leaf = folder("A", fixture.firstId); leaf.setHasChildren(false); when(fixture.mapper.selectFolders(any())).thenReturn(Collections.singletonList(leaf), Collections.emptyList()); DmsCursorPageVO root = fixture.service.folderTree(null, null, 20); assertFalse(root.getList().get(0).isHasChildren()); assertFalse(root.isHasMore()); assertTrue(fixture.service.folderTree(fixture.firstId, null, 20).getList().isEmpty()); } @Test void folderTreeRejectsInvalidPageSizeAndCursorFromAnotherParentOrUser() { Fixture fixture = new Fixture(true); String cursor = fixture.cursorCodec.encode("FOLDER", "tenant-1", "user-1", fixture.folderId, fixture.cursorCodec.fingerprint(null), new DmsFileCenterCursorCodec.Cursor( "A", null, null, 1024L, fixture.firstId)); assertThrows(DmsPermissionException.class, () -> fixture.service.folderTree(null, null, 0)); assertThrows(DmsPermissionException.class, () -> fixture.service.folderTree(null, null, 101)); assertThrows(DmsPermissionException.class, () -> fixture.service.folderTree(null, cursor, 20)); fixture.user.setUserId("user-2"); assertThrows(DmsPermissionException.class, () -> fixture.service.folderTree(fixture.folderId, cursor, 20)); verify(fixture.mapper, never()).selectFolders(any()); } @Test void folderTreeRequiresAuthenticatedUser() { Fixture fixture = new Fixture(false); when(fixture.currentIdentity.requireUser()).thenThrow(new IllegalStateException("not logged in")); assertThrows(IllegalStateException.class, () -> fixture.service.folderTree(null, null, 20)); verify(fixture.mapper, never()).selectFolders(any()); } @Test void resolvesOrdinaryUserSubjectsBeforeFolderQueryAndReturnsKeysetCursor() { Fixture fixture = new Fixture(false); DmsSubjectSet subjects = new DmsSubjectSet(); subjects.getDirectOrgIds().add("org-direct"); subjects.getAncestorOrgIds().add("org-parent"); subjects.getPositionIds().add("position-1"); subjects.getRoleIds().add("role-1"); subjects.getPermissionGroupIds().add("group-1"); when(fixture.subjectResolver.resolve(fixture.user)).thenReturn(subjects); DmsFileCenterItemVO first = folder("A", fixture.firstId); DmsFileCenterItemVO second = folder("B", fixture.secondId); DmsFileCenterItemVO overflow = folder("C", fixture.thirdId); when(fixture.mapper.selectFolders(any())).thenReturn(Arrays.asList(first, second, overflow)); DmsCursorPageVO result = fixture.service.rootFolders(null, 2); assertEquals(Arrays.asList(first, second), result.getList()); assertTrue(result.isHasMore()); assertNotNull(result.getNextCursor()); verify(fixture.groupResolver).resolveAll(subjects, 1000); ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(DmsFileCenterQuery.class); verify(fixture.mapper).selectFolders(queryCaptor.capture()); DmsFileCenterQuery query = queryCaptor.getValue(); assertFalse(query.isAdministrator()); assertEquals(3, query.getLimit()); assertEquals(Collections.singletonList("org-direct"), query.getDirectOrgIds()); assertEquals(Collections.singletonList("org-parent"), query.getAncestorOrgIds()); assertEquals(Collections.singletonList("position-1"), query.getPositionIds()); assertEquals(Collections.singletonList("role-1"), query.getRoleIds()); assertEquals(Collections.singletonList("group-1"), query.getPermissionGroupIds()); } @Test void administratorSkipsSubjectAndPermissionGroupResolution() { Fixture fixture = new Fixture(true); when(fixture.mapper.selectCurrentFiles(any())).thenReturn(Collections.emptyList()); DmsCursorPageVO result = fixture.service.currentDocuments( fixture.folderId, null, null, " report "); assertFalse(result.isHasMore()); assertEquals(Collections.emptyList(), result.getList()); verify(fixture.subjectResolver, never()).resolve(any()); verify(fixture.groupResolver, never()).resolveAll(any(DmsSubjectSet.class), any(Integer.class)); ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(DmsFileCenterQuery.class); verify(fixture.mapper).selectCurrentFiles(queryCaptor.capture()); verify(fixture.mapper, never()).selectCurrentDocuments(any()); assertTrue(queryCaptor.getValue().isAdministrator()); assertEquals("report", queryCaptor.getValue().getKeyword()); assertEquals(21, queryCaptor.getValue().getLimit()); } @Test void fileNameExistenceChecksExactNameWithoutUsingVersion() { Fixture fixture = new Fixture(true); when(fixture.mapper.fileNameExists("tenant-1", fixture.folderId, "report.pdf")) .thenReturn(true); assertTrue(fixture.service.fileNameExists(fixture.folderId, " report.pdf ", "V1")); assertTrue(fixture.service.fileNameExists(fixture.folderId, " report.pdf ", "V2")); verify(fixture.mapper, times(2)).fileNameExists("tenant-1", fixture.folderId, "report.pdf"); } @Test void fileNameExistenceRejectsMissingInputs() { Fixture fixture = new Fixture(true); assertThrows(DmsPermissionException.class, () -> fixture.service.fileNameExists(null, "report.pdf", "V1")); assertThrows(DmsPermissionException.class, () -> fixture.service.fileNameExists(fixture.folderId, " ", "V1")); verify(fixture.mapper, never()).fileNameExists(any(), any(), any()); } @Test void fileNameExistenceRequiresFolderCreatePermission() { Fixture fixture = new Fixture(false); fixture.service.fileNameExists(fixture.folderId, "report.pdf", "V1"); verify(fixture.decisionService).requireAllowed( DmsResourceType.FOLDER, fixture.folderId, DmsPermissionAction.CREATE); verify(fixture.mapper).fileNameExists("tenant-1", fixture.folderId, "report.pdf"); } @Test void folderBrowsingWithoutKeywordKeepsDocumentContainerQuery() { Fixture fixture = new Fixture(true); when(fixture.mapper.selectHistoricalVersions(any())).thenReturn(Collections.emptyList()); fixture.service.historicalVersions(fixture.folderId, null, 20, " "); verify(fixture.mapper).selectHistoricalVersions(any()); verify(fixture.mapper, never()).selectHistoricalFiles(any()); } @Test void historicalSearchUsesActualFileVersionQuery() { Fixture fixture = new Fixture(true); when(fixture.mapper.selectHistoricalFiles(any())).thenReturn(Collections.emptyList()); fixture.service.historicalVersions(fixture.folderId, null, 20, " report "); ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(DmsFileCenterQuery.class); verify(fixture.mapper).selectHistoricalFiles(queryCaptor.capture()); verify(fixture.mapper, never()).selectHistoricalVersions(any()); assertEquals("report", queryCaptor.getValue().getKeyword()); } @Test void rejectsUnknownSharedCenterBeforeCallingMapper() { Fixture fixture = new Fixture(true); assertThrows(DmsPermissionException.class, () -> fixture.service.sharedDocuments("ARCHIVE", null, 20)); verify(fixture.mapper, never()).selectSharedDocuments(any()); verify(fixture.mapper, never()).selectSharedHistoricalVersions(any()); } @Test void currentDocumentDirectoryUsesCurrentVersionQuery() { Fixture fixture = new Fixture(true); when(fixture.mapper.selectCurrentDocumentVersions(any())).thenReturn(Collections.emptyList()); fixture.service.documentVersions(fixture.firstId, "CURRENT", null, 20, " report "); ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(DmsFileCenterQuery.class); verify(fixture.mapper).selectCurrentDocumentVersions(queryCaptor.capture()); verify(fixture.mapper, never()).selectDocumentVersions(any()); assertEquals(fixture.firstId, queryCaptor.getValue().getDocumentId()); assertEquals("report", queryCaptor.getValue().getKeyword()); } @Test void historicalDocumentDirectoryUsesTerminalVersionQuery() { Fixture fixture = new Fixture(true); when(fixture.mapper.selectDocumentVersions(any())).thenReturn(Collections.emptyList()); fixture.service.documentVersions(fixture.firstId, "HISTORICAL", null, 20, null); verify(fixture.mapper).selectDocumentVersions(any()); verify(fixture.mapper, never()).selectCurrentDocumentVersions(any()); } private static DmsFileCenterItemVO folder(String name, UUID id) { DmsFileCenterItemVO value = new DmsFileCenterItemVO(); value.setId(id); value.setName(name); value.setResourceType("Folder"); value.setSortCode((long) name.charAt(0) * 1024); return value; } private static class Fixture { private final UUID folderId = UUID.fromString("018f0000-0000-7000-8000-000000000201"); private final UUID firstId = UUID.fromString("018f0000-0000-7000-8000-000000000202"); private final UUID secondId = UUID.fromString("018f0000-0000-7000-8000-000000000203"); private final UUID thirdId = UUID.fromString("018f0000-0000-7000-8000-000000000204"); private final UserInfo user = new UserInfo(); private final DmsCurrentIdentity currentIdentity = mock(DmsCurrentIdentity.class); private final DmsSubjectResolver subjectResolver = mock(DmsSubjectResolver.class); private final DmsPermissionGroupMembershipResolver groupResolver = mock(DmsPermissionGroupMembershipResolver.class); private final DmsPermissionProperties properties = properties(); private final DmsFileCenterCursorCodec cursorCodec = new DmsFileCenterCursorCodec(properties); private final DmsFileCenterMapper mapper = mock(DmsFileCenterMapper.class); private final DmsPermissionDecisionService decisionService = mock(DmsPermissionDecisionService.class); private final DmsFileCenterService service = new DmsFileCenterService( currentIdentity, subjectResolver, groupResolver, properties, cursorCodec, mapper, decisionService); private Fixture(boolean administrator) { user.setUserId("user-1"); user.setTenantId("tenant-1"); when(currentIdentity.requireUser()).thenReturn(user); when(currentIdentity.tenantId(user)).thenReturn("tenant-1"); when(currentIdentity.isPermissionAdministrator()).thenReturn(administrator); } private static DmsPermissionProperties properties() { DmsPermissionProperties value = new DmsPermissionProperties(); value.setDefaultPageSize(20); value.setMaxPageSize(100); value.setMaxPermissionGroupScan(1000); value.setCursorSigningSecret("0123456789abcdef0123456789abcdef"); return value; } } }