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