diff --git a/core/pom.xml b/core/pom.xml index 0f04020..8663f9a 100755 --- a/core/pom.xml +++ b/core/pom.xml @@ -104,5 +104,12 @@ commons-csv ${commons-csv.version} + + + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file diff --git a/core/src/main/java/de/bstly/we/businesslogic/PermissionManager.java b/core/src/main/java/de/bstly/we/businesslogic/PermissionManager.java index c346fd3..ce79041 100755 --- a/core/src/main/java/de/bstly/we/businesslogic/PermissionManager.java +++ b/core/src/main/java/de/bstly/we/businesslogic/PermissionManager.java @@ -416,11 +416,7 @@ public class PermissionManager implements UserDataProvider { List existingPermissions = get(target, name); for (Permission existingPermission : existingPermissions) { - if (additional && (starts == null && (existingPermission.getStarts() == null - || existingPermission.getStarts().isBefore(Instant.now())) - || starts != null && (starts.isBefore(existingPermission.getExpires()) - || existingPermission.getStarts() != null - && starts.isAfter(existingPermission.getStarts())))) { + if (additional) { if (permission == null) { permission = existingPermission; } else if (existingPermission.getExpires().isAfter(permission.getExpires())) { @@ -437,12 +433,22 @@ public class PermissionManager implements UserDataProvider { permission.setStarts(permissionStarts); permission.setExpires(permissionsExpires); } else { + // For expired permissions, calculate the appropriate future expiry date if (permission.getExpires() == null || permission.getExpires().isBefore(Instant.now())) { - permission.setExpires(Instant.now()); + Instant baseExpiry = permission.getExpires() != null ? permission.getExpires() : Instant.now(); + Instant newExpiry = baseExpiry; + + // Keep adding lifetime periods until we get a date in the future + while (newExpiry.isBefore(Instant.now()) || newExpiry.equals(Instant.now())) { + newExpiry = InstantHelper.plus(newExpiry, permissionMapping.getLifetime(), + permissionMapping.getLifetimeUnit()); + } + permission.setExpires(newExpiry); + } else { + // For valid permissions, extend from current expiry + permission.setExpires(InstantHelper.plus(permission.getExpires(), permissionMapping.getLifetime(), + permissionMapping.getLifetimeUnit())); } - - permission.setExpires(InstantHelper.plus(permission.getExpires(), permissionMapping.getLifetime(), - permissionMapping.getLifetimeUnit())); } if (permission.getStarts() != null && permission.getStarts().isBefore(Instant.now())) { diff --git a/core/src/test/java/de/bstly/we/businesslogic/PermissionManagerTest.java b/core/src/test/java/de/bstly/we/businesslogic/PermissionManagerTest.java new file mode 100644 index 0000000..dafcb47 --- /dev/null +++ b/core/src/test/java/de/bstly/we/businesslogic/PermissionManagerTest.java @@ -0,0 +1,295 @@ +package de.bstly.we.businesslogic; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Sort; + +import com.google.gson.JsonArray; + +import de.bstly.we.model.Permission; +import de.bstly.we.model.PermissionMapping; +import de.bstly.we.model.QPermission; +import de.bstly.we.repository.PermissionRepository; + +@ExtendWith(MockitoExtension.class) +class PermissionManagerTest { + + @Mock + private PermissionRepository permissionRepository; + + @Mock + private PermissionMappingManager permissionMappingManager; + + @InjectMocks + private PermissionManager permissionManager; + + private static final Long TARGET_USER_ID = 1L; + private static final Integer TEST_ITEM_ID = 1; + private static final String ROLE_MEMBER = "ROLE_MEMBER"; + + @Test + void testGetForItem_NewAddonPermission_CreatesNewWhenNoExisting() { + // Given + Instant currentTime = Instant.parse("2025-03-15T10:00:00Z"); + PermissionMapping addonMapping = createAddonPermissionMapping(ROLE_MEMBER, 1L, ChronoUnit.YEARS, true); + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(addonMapping)); + + // No existing permissions + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList()); + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(1, result.size()); + Permission permission = result.get(0); + assertEquals(TARGET_USER_ID, permission.getTarget()); + assertEquals(ROLE_MEMBER, permission.getName()); + assertTrue(permission.isAddon()); // Should be addon since no existing permission found + + // Should create new permission with 1 year from current time: March 15, 2025 + 1 year = March 15, 2026 + Instant expectedExpiry = Instant.parse("2026-03-15T10:00:00Z"); + assertEquals(expectedExpiry, permission.getExpires()); + } + + @Test + void testGetForItem_AddonExtendsValidPermission_Simple() { + // Given - existing valid permission that expires on June 15, 2025 + Instant currentTime = Instant.parse("2025-03-15T10:00:00Z"); + Instant existingExpiry = Instant.parse("2025-06-15T10:00:00Z"); // 3 months in the future + Permission existingPermission = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, existingExpiry); + + PermissionMapping addonMapping = createAddonPermissionMapping(ROLE_MEMBER, 1L, ChronoUnit.YEARS, true); + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(addonMapping)); + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList(existingPermission)); + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(1, result.size()); + Permission extendedPermission = result.get(0); + assertEquals(existingPermission, extendedPermission); // Should be the same object (extended) + + // Should extend by exactly 1 year from current expiry: June 15, 2025 + 1 year = June 15, 2026 + Instant expectedExpiry = Instant.parse("2026-06-15T10:00:00Z"); + assertEquals(expectedExpiry, extendedPermission.getExpires()); + assertEquals(TARGET_USER_ID, extendedPermission.getTarget()); + assertEquals(ROLE_MEMBER, extendedPermission.getName()); + } + + @Test + void testGetForItem_AddonExtendsExpiredPermission_SimpleCase() { + // Given - existing permission that expired on February 15, 2025 (1 month ago from March 15) + Instant currentTime = Instant.parse("2025-03-15T10:00:00Z"); + Instant expiredDate = Instant.parse("2025-02-15T10:00:00Z"); // Expired exactly 1 month ago + Permission expiredPermission = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, expiredDate); + + PermissionMapping addonMapping = createAddonPermissionMapping(ROLE_MEMBER, 1L, ChronoUnit.YEARS, true); + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(addonMapping)); + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList(expiredPermission)); + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(1, result.size()); + Permission extendedPermission = result.get(0); + assertEquals(expiredPermission, extendedPermission); // Should be the same object (extended) + + // Should extend by exactly 1 year from original expiry: Feb 15, 2025 + 1 year = Feb 15, 2026 + Instant expectedExpiry = Instant.parse("2026-02-15T10:00:00Z"); + assertEquals(expectedExpiry, extendedPermission.getExpires()); + assertTrue(extendedPermission.getExpires().isAfter(currentTime)); // Should be in the future + } + + @Test + void testGetForItem_AddonExtendsLongExpiredPermission_CatchesUp() { + // Given - permission that expired 2 years ago (Dec 31, 2023, applied in Nov 2025) + Instant currentTime = Instant.parse("2025-02-09T10:00:00Z"); + Instant longExpiredDate = Instant.parse("2023-12-31T23:59:59Z"); + Permission longExpiredPermission = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, longExpiredDate); + + PermissionMapping addonMapping = createAddonPermissionMapping(ROLE_MEMBER, 1L, ChronoUnit.YEARS, true); + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(addonMapping)); + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList(longExpiredPermission)); + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(1, result.size()); + Permission extendedPermission = result.get(0); + assertEquals(longExpiredPermission, extendedPermission); // Should be the same object (extended) + + // Should "catch up" to future: 2023-12-31 -> 2024-12-31 -> 2025-12-31 (first future date) + Instant expectedExpiry = Instant.parse("2025-12-31T23:59:59Z"); + assertEquals(expectedExpiry, extendedPermission.getExpires()); + assertTrue(extendedPermission.getExpires().isAfter(currentTime)); // Should be in the future + } + + + @Test + void testGetForItem_AddonExtendsNonExpiredPermission_CatchesUp() { + // Given - permission that expired 2 years ago (Dec 31, 2023, applied in Nov 2025) + Instant currentTime = Instant.parse("2025-02-09T10:00:00Z"); + Instant expiredDate = Instant.parse("2025-12-31T23:59:59Z"); + Permission existingPermission = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, expiredDate); + + PermissionMapping addonMapping = createAddonPermissionMapping(ROLE_MEMBER, 1L, ChronoUnit.YEARS, true); + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(addonMapping)); + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList(existingPermission)); + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(1, result.size()); + Permission extendedPermission = result.get(0); + assertEquals(existingPermission, extendedPermission); // Should be the same object (extended) + + // Should "catch up" to future: 2025-12-31 -> 2026-12-31 (first future date) + Instant expectedExpiry = Instant.parse("2026-12-31T23:59:59Z"); + assertEquals(expectedExpiry, extendedPermission.getExpires()); + assertTrue(extendedPermission.getExpires().isAfter(currentTime)); // Should be in the future + } + + @Test + void testGetForItem_AddonSelectsLatestExpiringPermission() { + // Given - multiple existing permissions with different expiry dates + Instant currentTime = Instant.parse("2025-03-15T10:00:00Z"); + Permission permission1 = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, Instant.parse("2025-06-23T10:00:00Z")); // 100 days + Permission permission2 = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, Instant.parse("2025-10-01T10:00:00Z")); // 200 days (latest) + Permission permission3 = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, Instant.parse("2025-05-04T10:00:00Z")); // 50 days + + PermissionMapping addonMapping = createAddonPermissionMapping(ROLE_MEMBER, 6L, ChronoUnit.MONTHS, true); + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(addonMapping)); + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList(permission1, permission2, permission3)); + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(1, result.size()); + Permission selectedPermission = result.get(0); + assertEquals(permission2, selectedPermission); // Should select the one with latest expiry (Oct 1, 2025) + + // Should extend by 6 months from latest expiry: Oct 1, 2025 + 6 months = April 1, 2026 + Instant expectedExpiry = Instant.parse("2026-04-01T10:00:00Z"); + assertEquals(expectedExpiry, selectedPermission.getExpires()); + assertEquals(TARGET_USER_ID, selectedPermission.getTarget()); + assertEquals(ROLE_MEMBER, selectedPermission.getName()); + } + + @Test + void testGetForItem_NonAddonCreatesNewPermission() { + // Given + Instant currentTime = Instant.parse("2025-03-15T10:00:00Z"); + PermissionMapping nonAddonMapping = createAddonPermissionMapping(ROLE_MEMBER, 1L, ChronoUnit.YEARS, false); // Not addon + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(nonAddonMapping)); + + // Existing permission exists but should be ignored since this is not an addon + Permission existingPermission = createPermission(TARGET_USER_ID, ROLE_MEMBER, false, null, Instant.parse("2025-06-23T10:00:00Z")); + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList(existingPermission)); + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(1, result.size()); + Permission newPermission = result.get(0); + assertNotEquals(existingPermission, newPermission); // Should be a new permission object + assertEquals(TARGET_USER_ID, newPermission.getTarget()); + assertEquals(ROLE_MEMBER, newPermission.getName()); + assertFalse(newPermission.isAddon()); // Should not be addon + + // Should create new permission with 1 year from current time: March 15, 2025 + 1 year = March 15, 2026 + Instant expectedExpiry = Instant.parse("2026-03-15T10:00:00Z"); + assertEquals(expectedExpiry, newPermission.getExpires()); + } + + @Test + void testGetForItem_MultiplePermissionNames() { + // Given - mapping with multiple permission names + Instant currentTime = Instant.parse("2025-03-15T10:00:00Z"); + Set permissionNames = new HashSet<>(Arrays.asList("ROLE_MEMBER", "ROLE_SPECIAL")); + PermissionMapping multiMapping = createPermissionMapping(permissionNames, 1L, ChronoUnit.YEARS, true); + when(permissionMappingManager.getAllByItem(TEST_ITEM_ID)).thenReturn(Arrays.asList(multiMapping)); + + when(permissionRepository.findAll(any(com.querydsl.core.types.Predicate.class), any(Sort.class))) + .thenReturn(Arrays.asList()); // No existing permissions + + // When + List result = permissionManager.getForItem(TARGET_USER_ID, TEST_ITEM_ID, new JsonArray(), + currentTime, null); + + // Then + assertEquals(2, result.size()); + + // Should have both permission names + Set resultNames = new HashSet<>(); + for (Permission p : result) { + resultNames.add(p.getName()); + assertEquals(TARGET_USER_ID, p.getTarget()); + assertTrue(p.isAddon()); + } + assertEquals(permissionNames, resultNames); + } + + // Helper methods + private Permission createPermission(Long target, String name, boolean addon, Instant starts, Instant expires) { + Permission permission = new Permission(); + permission.setTarget(target); + permission.setName(name); + permission.setAddon(addon); + permission.setStarts(starts); + permission.setExpires(expires); + return permission; + } + + private PermissionMapping createAddonPermissionMapping(String permissionName, Long lifetime, ChronoUnit unit, boolean isAddon) { + Set names = new HashSet<>(); + names.add(permissionName); + return createPermissionMapping(names, lifetime, unit, isAddon); + } + + private PermissionMapping createPermissionMapping(Set names, Long lifetime, ChronoUnit unit, boolean isAddon) { + PermissionMapping mapping = new PermissionMapping(); + mapping.setItem(TEST_ITEM_ID); + mapping.setNames(names); + mapping.setLifetime(lifetime); + mapping.setLifetimeUnit(unit); + mapping.setAddon(isAddon); + mapping.setLifetimeRound(false); + return mapping; + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index f9bc5c7..6103304 100755 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 2.2 1.14.1 3.6.3 - 3.5.0 + 3.6.0