From 26d64ab8c3c247116ec9b92e6569b42efeb3c1aa Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Sun, 29 Mar 2026 19:59:20 -0400 Subject: [PATCH 1/4] 866: FeatureFlag annotation --- .../codebloom/common/ff/FFAspect.java | 55 +++++++ .../common/ff/FeatureFlagManager.java | 80 ++++++++++ .../codebloom/common/ff/annotation/FF.java | 20 +++ .../codebloom/common/ff/FFAspectTest.java | 145 ++++++++++++++++++ .../common/ff/FeatureFlagManagerTest.java | 60 ++++++++ 5 files changed, 360 insertions(+) create mode 100644 src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java create mode 100644 src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java create mode 100644 src/main/java/org/patinanetwork/codebloom/common/ff/annotation/FF.java create mode 100644 src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java create mode 100644 src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java diff --git a/src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java b/src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java new file mode 100644 index 000000000..b731aa115 --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java @@ -0,0 +1,55 @@ +package org.patinanetwork.codebloom.common.ff; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.patinanetwork.codebloom.common.ff.annotation.FF; +import org.springframework.context.expression.MapAccessor; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ResponseStatusException; + +@Aspect +@Component +@Slf4j +public class FFAspect { + + private final FeatureFlagManager featureFlagManager; + private final ExpressionParser parser; + + public FFAspect(final FeatureFlagManager featureFlagManager) { + this.featureFlagManager = featureFlagManager; + this.parser = new SpelExpressionParser(); + } + + @Around("@annotation(ff)") + public Object gateMethodByFeatureFlags(final ProceedingJoinPoint joinPoint, final FF ff) throws Throwable { + String expression = ff.value(); + + featureFlagManager.validateExpressionFlagsExist(expression); + + var flags = featureFlagManager.getAllFlags(); + StandardEvaluationContext context = new StandardEvaluationContext(flags); + context.addPropertyAccessor(new MapAccessor()); + + Boolean isEnabled; + try { + isEnabled = parser.parseExpression(expression).getValue(context, Boolean.class); + } catch (Exception e) { + log.error("Invalid @FF expression: " + expression, e); + throw new IllegalArgumentException("Invalid @FF expression: " + expression, e); + } + + if (!Boolean.TRUE.equals(isEnabled)) { + throw new ResponseStatusException( + HttpStatus.FORBIDDEN, + "Endpoint is not available. Feature flag expression evaluated to false: " + expression); + } + + return joinPoint.proceed(); + } +} diff --git a/src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java b/src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java new file mode 100644 index 000000000..2d66b9cc5 --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java @@ -0,0 +1,80 @@ +package org.patinanetwork.codebloom.common.ff; + +import java.beans.Introspector; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; +import org.springframework.stereotype.Component; + +@Component +public class FeatureFlagManager { + private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"); + + private static final Set RESERVED_IDENTIFIERS = Set.of( + "true", + "false", + "null", + "and", + "or", + "not", + "eq", + "ne", + "lt", + "le", + "gt", + "ge", + "between", + "instanceof", + "matches"); + + private final FeatureFlagConfiguration ff; + private final Map cachedFlags; + + public FeatureFlagManager(final FeatureFlagConfiguration ff) { + this.ff = ff; + this.cachedFlags = initializeFlags(); + } + + private Map initializeFlags() { + Map flags = new HashMap<>(); + for (Method method : ff.getClass().getMethods()) { + if (method.getName().startsWith("is") + && method.getReturnType() == boolean.class + && method.getParameterCount() == 0) { + try { + String flagName = Introspector.decapitalize(method.getName().substring(2)); + flags.put(flagName, (Boolean) method.invoke(ff)); + } catch (Exception e) { + throw new IllegalStateException( + "Failed to resolve feature flag value from method: " + method.getName(), e); + } + } + } + return Map.copyOf(flags); + } + + public Map getAllFlags() { + return cachedFlags; + } + + public void validateExpressionFlagsExist(final String expression) { + Map flags = getAllFlags(); + Matcher matcher = IDENTIFIER_PATTERN.matcher(expression); + + while (matcher.find()) { + String token = matcher.group(); + + if (RESERVED_IDENTIFIERS.contains(token)) { + continue; + } + + if (!flags.containsKey(token)) { + throw new IllegalArgumentException("Unknown feature flag in @FF expression: " + token); + } + } + } +} diff --git a/src/main/java/org/patinanetwork/codebloom/common/ff/annotation/FF.java b/src/main/java/org/patinanetwork/codebloom/common/ff/annotation/FF.java new file mode 100644 index 000000000..a3faf44fd --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/common/ff/annotation/FF.java @@ -0,0 +1,20 @@ +package org.patinanetwork.codebloom.common.ff.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Feature flag gate + * + *

The value must be a SpEL boolean expression using feature flag names from FeatureFlagManager. Example: + * {@code "duels && userMetrics"} + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface FF { + String value(); +} diff --git a/src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java b/src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java new file mode 100644 index 000000000..1a7ea63ab --- /dev/null +++ b/src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java @@ -0,0 +1,145 @@ +package org.patinanetwork.codebloom.common.ff; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.patinanetwork.codebloom.common.ff.annotation.FF; +import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +@SpringBootTest(classes = FFAspectTest.TestConfig.class) +class FFAspectTest { + + @Autowired + private TestService testService; + + @BeforeEach + void resetCallCount() { + testService.resetCalls(); + } + + @Test + @DisplayName("Allows method execution when expression is true") + void allowsWhenTrue() { + String result = testService.methodWithEnabledFlag(); + + assertEquals("ok", result); + assertEquals(1, testService.getCalls()); + } + + @Test + @DisplayName("Throws forbidden when expression is false") + void forbiddenWhenFalse() { + ResponseStatusException exception = + assertThrows(ResponseStatusException.class, () -> testService.methodWithDisabledFlag()); + + assertEquals(HttpStatus.FORBIDDEN, exception.getStatusCode()); + assertEquals(0, testService.getCalls()); + } + + @Test + @DisplayName("Throws when unknown flag is used") + void throwsOnUnknownFlag() { + assertThrows(IllegalArgumentException.class, () -> testService.methodWithUnknownFlag()); + assertEquals(0, testService.getCalls()); + } + + @Test + @DisplayName("Throws when expression is invalid") + void throwsOnInvalidExpression() { + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> testService.methodWithInvalidExpression()); + + assertTrue(exception.getMessage().contains("Invalid @FF expression")); + assertEquals(0, testService.getCalls()); + } + + @Test + @DisplayName("Allows literal expressions") + void allowsLiterals() { + String result = testService.methodWithLiteralTrueExpression(); + + assertEquals("ok", result); + assertEquals(1, testService.getCalls()); + } + + public static class TestService { + private int calls; + + @FF("duels") + public String methodWithEnabledFlag() { + calls++; + return "ok"; + } + + @FF("duels && userMetrics") + public String methodWithDisabledFlag() { + calls++; + return "no"; + } + + @FF("duels && school") + public String methodWithUnknownFlag() { + calls++; + return "no"; + } + + @FF("duels &&") + public String methodWithInvalidExpression() { + calls++; + return "no"; + } + + @FF("true") + public String methodWithLiteralTrueExpression() { + calls++; + return "ok"; + } + + public int getCalls() { + return calls; + } + + public void resetCalls() { + calls = 0; + } + } + + @TestConfiguration + @EnableAspectJAutoProxy + static class TestConfig { + + @Bean + public FeatureFlagConfiguration featureFlagConfiguration() { + FeatureFlagConfiguration ff = new FeatureFlagConfiguration(); + ff.setDuels(true); + ff.setUserMetrics(false); + return ff; + } + + @Bean + public FeatureFlagManager featureFlagManager(final FeatureFlagConfiguration ff) { + return new FeatureFlagManager(ff); + } + + @Bean + public FFAspect ffAspect(final FeatureFlagManager featureFlagManager) { + return new FFAspect(featureFlagManager); + } + + @Bean + public TestService testService() { + return new TestService(); + } + } +} diff --git a/src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java new file mode 100644 index 000000000..a3d9419d9 --- /dev/null +++ b/src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java @@ -0,0 +1,60 @@ +package org.patinanetwork.codebloom.common.ff; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; + +class FeatureFlagManagerTest { + + private FeatureFlagManager featureFlagManager; + + @BeforeEach + void setUp() { + FeatureFlagConfiguration ff = new FeatureFlagConfiguration(); + ff.setDuels(true); + ff.setUserMetrics(false); + featureFlagManager = new FeatureFlagManager(ff); + } + + @Test + void validateExpressionFlagsExistAllowsKnownFlags() { + assertDoesNotThrow(() -> featureFlagManager.validateExpressionFlagsExist("duels && userMetrics")); + } + + @Test + void validateExpressionFlagsExistAllowsReservedIdentifiers() { + assertDoesNotThrow(() -> featureFlagManager.validateExpressionFlagsExist("duels && true && !false")); + } + + @Test + void validateExpressionFlagsExistThrowsWhenFlagUnknown() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> featureFlagManager.validateExpressionFlagsExist("duels && school")); + + assertTrue(exception.getMessage().contains("school")); + } + + @Test + void getAllFlagsReturnsValuesFromConfiguration() { + var flags = featureFlagManager.getAllFlags(); + + assertEquals(2, flags.size()); + assertEquals(Boolean.TRUE, flags.get("duels")); + assertEquals(Boolean.FALSE, flags.get("userMetrics")); + } + + @Test + void getAllFlagsReturnsImmutableView() { + var flags = featureFlagManager.getAllFlags(); + + assertThrows(UnsupportedOperationException.class, () -> flags.put("newFlag", true)); + assertFalse(flags.containsKey("newFlag")); + } +} From 16818d4c4ab673e430eefd4ea478a11d600c0395 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Tue, 31 Mar 2026 16:20:54 -0400 Subject: [PATCH 2/4] 866: Migrated controllers to use FF --- .../codebloom/api/duel/DuelController.java | 47 +--- .../codebloom/api/user/UserController.java | 12 +- .../codebloom/common/ff/FFAspect.java | 2 - .../common/ff/FeatureFlagManager.java | 39 ---- .../api/duel/DuelControllerTest.java | 210 +----------------- .../api/user/UserControllerTest.java | 27 +-- .../codebloom/common/ff/FFAspectTest.java | 7 +- .../common/ff/FeatureFlagManagerTest.java | 21 -- 8 files changed, 21 insertions(+), 344 deletions(-) diff --git a/src/main/java/org/patinanetwork/codebloom/api/duel/DuelController.java b/src/main/java/org/patinanetwork/codebloom/api/duel/DuelController.java index 3e27254df..5b934d6af 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/duel/DuelController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/duel/DuelController.java @@ -21,10 +21,10 @@ import org.patinanetwork.codebloom.common.dto.Empty; import org.patinanetwork.codebloom.common.dto.autogen.UnsafeGenericFailureResponse; import org.patinanetwork.codebloom.common.dto.lobby.DuelData; +import org.patinanetwork.codebloom.common.ff.annotation.FF; import org.patinanetwork.codebloom.common.security.AuthenticationObject; import org.patinanetwork.codebloom.common.security.annotation.Protected; import org.patinanetwork.codebloom.common.utils.sse.SseWrapper; -import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; import org.patinanetwork.codebloom.scheduled.pg.handler.LobbyNotifyHandler; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -48,19 +48,16 @@ public class DuelController { private final PartyManager partyManager; private final LobbyRepository lobbyRepository; private final LobbyNotifyHandler lobbyNotifyHandler; - private final FeatureFlagConfiguration ff; public DuelController( final DuelManager duelManager, final PartyManager partyManager, final LobbyRepository lobbyRepository, - final LobbyNotifyHandler lobbyNotifyHandler, - final FeatureFlagConfiguration ff) { + final LobbyNotifyHandler lobbyNotifyHandler) { this.duelManager = duelManager; this.partyManager = partyManager; this.lobbyRepository = lobbyRepository; this.lobbyNotifyHandler = lobbyNotifyHandler; - this.ff = ff; } @Operation(summary = "Join party", description = "Join a party by providing the lobby code.") @@ -91,13 +88,10 @@ public DuelController( @ApiResponse(responseCode = "200", description = "Party has been successfully joined!"), }) @PostMapping("/party/join") + @FF("duels") public ResponseEntity> joinParty( @Protected final AuthenticationObject authenticationObject, @RequestBody final JoinLobbyBody joinPartyBody) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - joinPartyBody.validate(); var user = authenticationObject.getUser(); @@ -136,11 +130,8 @@ public ResponseEntity> joinParty( @ApiResponse(responseCode = "200", description = "Duel successfully started!"), }) @PostMapping("/start") + @FF("duels") public ResponseEntity> startDuel(@Protected final AuthenticationObject authenticationObject) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - var user = authenticationObject.getUser(); try { @@ -172,11 +163,8 @@ public ResponseEntity> startDuel(@Protected final Authentica @ApiResponse(responseCode = "200", description = "Party left successfully"), }) @PostMapping("/party/leave") + @FF("duels") public ResponseEntity> leaveParty(@Protected final AuthenticationObject authenticationObject) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - User user = authenticationObject.getUser(); try { @@ -216,11 +204,8 @@ public ResponseEntity> leaveParty(@Protected final Authentic @ApiResponse(responseCode = "200", description = "Duel has been successfully ended!"), }) @PostMapping("/end") + @FF("duels") public ResponseEntity> endDuel(@Protected final AuthenticationObject authenticationObject) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - User user = authenticationObject.getUser(); var lobby = lobbyRepository @@ -261,12 +246,9 @@ public ResponseEntity> endDuel(@Protected final Authenticati @ApiResponse(responseCode = "200", description = "Party created successfully"), }) @PostMapping("/party/create") + @FF("duels") public ResponseEntity> createParty( @Protected final AuthenticationObject authenticationObject) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - User user = authenticationObject.getUser(); String joinCode; @@ -303,11 +285,8 @@ public ResponseEntity> createParty( content = @Content(schema = @Schema(implementation = UnsafeGenericFailureResponse.class))) }) @PostMapping(value = "/{lobbyCode}/sse") + @FF("duels") public SseWrapper> getDuelData(@PathVariable final String lobbyCode) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - var lobby = lobbyRepository .findActiveLobbyByJoinCode(lobbyCode) .or(() -> lobbyRepository.findAvailableLobbyByJoinCode(lobbyCode)) @@ -347,12 +326,9 @@ public SseWrapper> getDuelData(@PathVariable final String @ApiResponse(responseCode = "200", description = "Party or duel code was successfully found!"), }) @GetMapping("/current") + @FF("duels") public ResponseEntity> getPartyOrDuelCodeForUser( @Protected final AuthenticationObject authenticationObject) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - var user = authenticationObject.getUser(); Lobby lobby; @@ -393,12 +369,9 @@ public ResponseEntity> getPartyOrDuelCodeForUser( "The user's solved questions were processed (could still mean no new points were awarded)"), }) @PostMapping("/process") + @FF("duels") public ResponseEntity> processSolvedProblemsInDuel( @Protected final AuthenticationObject authenticationObject) { - if (!ff.isDuels()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is currently non-functional"); - } - var user = authenticationObject.getUser(); Lobby duel; diff --git a/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java b/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java index 1348a2716..4298cf8cc 100644 --- a/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java +++ b/src/main/java/org/patinanetwork/codebloom/api/user/UserController.java @@ -26,10 +26,10 @@ import org.patinanetwork.codebloom.common.dto.question.QuestionDto; import org.patinanetwork.codebloom.common.dto.user.UserDto; import org.patinanetwork.codebloom.common.dto.user.metrics.MetricsDto; +import org.patinanetwork.codebloom.common.ff.annotation.FF; import org.patinanetwork.codebloom.common.lag.FakeLag; import org.patinanetwork.codebloom.common.page.Page; import org.patinanetwork.codebloom.common.time.StandardizedOffsetDateTime; -import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -58,19 +58,16 @@ public class UserController { private final UserRepository userRepository; private final QuestionTopicService questionTopicService; private final UserMetricsRepository userMetricsRepository; - private final FeatureFlagConfiguration ff; public UserController( final QuestionRepository questionRepository, final UserRepository userRepository, final QuestionTopicService questionTopicService, - final UserMetricsRepository userMetricsRepository, - final FeatureFlagConfiguration ff) { + final UserMetricsRepository userMetricsRepository) { this.questionRepository = questionRepository; this.userRepository = userRepository; this.questionTopicService = questionTopicService; this.userMetricsRepository = userMetricsRepository; - this.ff = ff; } @Operation( @@ -227,6 +224,7 @@ public ResponseEntity>> getAllUsers( content = @Content(schema = @Schema(implementation = UnsafeGenericFailureResponse.class))), }) @GetMapping("{userId}/metrics") + @FF("userMetrics") public ResponseEntity>> getUserMetrics( final HttpServletRequest request, @PathVariable final String userId, @@ -244,10 +242,6 @@ public ResponseEntity>> getUserMetrics( FakeLag.sleep(500); - if (!ff.isUserMetrics()) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Endpoint is not available."); - } - if (startDate != null && endDate != null && startDate.isAfter(endDate)) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "startDate cannot be after endDate."); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java b/src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java index b731aa115..eebdca7ed 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java +++ b/src/main/java/org/patinanetwork/codebloom/common/ff/FFAspect.java @@ -30,8 +30,6 @@ public FFAspect(final FeatureFlagManager featureFlagManager) { public Object gateMethodByFeatureFlags(final ProceedingJoinPoint joinPoint, final FF ff) throws Throwable { String expression = ff.value(); - featureFlagManager.validateExpressionFlagsExist(expression); - var flags = featureFlagManager.getAllFlags(); StandardEvaluationContext context = new StandardEvaluationContext(flags); context.addPropertyAccessor(new MapAccessor()); diff --git a/src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java b/src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java index 2d66b9cc5..ef4cf950f 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java +++ b/src/main/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManager.java @@ -4,33 +4,11 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; import org.springframework.stereotype.Component; @Component public class FeatureFlagManager { - private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"); - - private static final Set RESERVED_IDENTIFIERS = Set.of( - "true", - "false", - "null", - "and", - "or", - "not", - "eq", - "ne", - "lt", - "le", - "gt", - "ge", - "between", - "instanceof", - "matches"); - private final FeatureFlagConfiguration ff; private final Map cachedFlags; @@ -60,21 +38,4 @@ private Map initializeFlags() { public Map getAllFlags() { return cachedFlags; } - - public void validateExpressionFlagsExist(final String expression) { - Map flags = getAllFlags(); - Matcher matcher = IDENTIFIER_PATTERN.matcher(expression); - - while (matcher.find()) { - String token = matcher.group(); - - if (RESERVED_IDENTIFIERS.contains(token)) { - continue; - } - - if (!flags.containsKey(token)) { - throw new IllegalArgumentException("Unknown feature flag in @FF expression: " + token); - } - } - } } diff --git a/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java index a43618c1f..ea5a4cb5d 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/duel/DuelControllerTest.java @@ -10,7 +10,6 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; 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; @@ -36,7 +35,6 @@ import org.patinanetwork.codebloom.common.time.StandardizedOffsetDateTime; import org.patinanetwork.codebloom.common.utils.duel.PartyCodeGenerator; import org.patinanetwork.codebloom.common.utils.sse.SseWrapper; -import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; import org.patinanetwork.codebloom.scheduled.pg.handler.LobbyNotifyHandler; import org.patinanetwork.codebloom.utilities.exception.ValidationException; import org.springframework.boot.test.context.SpringBootTest; @@ -53,10 +51,9 @@ public class DuelControllerTest { private PartyManager partyManager = mock(PartyManager.class); private LobbyRepository lobbyRepository = mock(LobbyRepository.class); private LobbyNotifyHandler lobbyNotifyHandler = mock(LobbyNotifyHandler.class); - private FeatureFlagConfiguration ff = mock(FeatureFlagConfiguration.class); public DuelControllerTest() { - this.duelController = new DuelController(duelManager, partyManager, lobbyRepository, lobbyNotifyHandler, ff); + this.duelController = new DuelController(duelManager, partyManager, lobbyRepository, lobbyNotifyHandler); this.faker = Faker.instance(); } @@ -82,8 +79,6 @@ private AuthenticationObject createAuthenticationObject(final User user) { @Test @DisplayName("Join lobby - invalid code length") void joinPartyIncorrectLengthCode() { - when(ff.isDuels()).thenReturn(true); - var joinPartyBody = JoinLobbyBody.builder().partyCode("ABC12").build(); User user = createRandomUser(); @@ -99,8 +94,6 @@ void joinPartyIncorrectLengthCode() { @Test @DisplayName("Join lobby - empty code") void joinLobbyEmptyCode() { - when(ff.isDuels()).thenReturn(true); - var joinPartyBody = JoinLobbyBody.builder().partyCode("").build(); User user = createRandomUser(); @@ -116,8 +109,6 @@ void joinLobbyEmptyCode() { @Test @DisplayName("Join lobby - null code") void joinLobbyNullCode() { - when(ff.isDuels()).thenReturn(true); - var joinPartyBody = JoinLobbyBody.builder().partyCode(null).build(); User user = createRandomUser(); @@ -130,34 +121,8 @@ void joinLobbyNullCode() { assertEquals("Lobby code may not be null or empty.", ex.getMessage()); } - @Test - @DisplayName("Join lobby - fails in production environment") - void joinLobbyFailsInProduction() { - when(ff.isDuels()).thenReturn(false); - - var joinPartyBody = JoinLobbyBody.builder().partyCode("ABC123").build(); - - User user = createRandomUser(); - AuthenticationObject authObj = createAuthenticationObject(user); - - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { - duelController.joinParty(authObj, joinPartyBody); - }); - - assertEquals(HttpStatus.FORBIDDEN.value(), exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - try { - verify(partyManager, times(0)).joinParty(any(), any()); - } catch (DuelException e) { - fail(e); - } - } - @Test void testJoinPartyPartyManagerFailed() { - when(ff.isDuels()).thenReturn(true); - var joinPartyBody = JoinLobbyBody.builder().partyCode("ABC123").build(); User user = createRandomUser(); @@ -185,8 +150,6 @@ void testJoinPartyPartyManagerFailed() { @Test void testJoinPartyHappyPath() { - when(ff.isDuels()).thenReturn(true); - var joinPartyBody = JoinLobbyBody.builder().partyCode("ABC123").build(); User user = createRandomUser(); @@ -212,30 +175,8 @@ void testJoinPartyHappyPath() { } } - @Test - void testLeavePartyFailureInProductionEnvironment() { - when(ff.isDuels()).thenReturn(false); - - User user = createRandomUser(); - AuthenticationObject authObj = createAuthenticationObject(user); - - org.springframework.web.server.ResponseStatusException exception = assertThrows( - org.springframework.web.server.ResponseStatusException.class, () -> duelController.leaveParty(authObj)); - - assertEquals(403, exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - try { - verify(partyManager, times(0)).leaveParty(any()); - } catch (DuelException e) { - fail(e); - } - } - @Test void testLeavePartyPartyManagerFailed() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); @@ -261,8 +202,6 @@ void testLeavePartyPartyManagerFailed() { @Test void testLeavePartyHappyPath() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); @@ -286,31 +225,8 @@ void testLeavePartyHappyPath() { } } - @Test - void testCreatePartyFailsInProductionEnvironment() { - when(ff.isDuels()).thenReturn(false); - - User user = createRandomUser(); - AuthenticationObject authObj = createAuthenticationObject(user); - - org.springframework.web.server.ResponseStatusException exception = assertThrows( - org.springframework.web.server.ResponseStatusException.class, - () -> duelController.createParty(authObj)); - - assertEquals(HttpStatus.FORBIDDEN.value(), exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - try { - verify(partyManager, times(0)).createParty(any()); - } catch (DuelException e) { - fail(e); - } - } - @Test void testCreatePartyPartyManagerFailed() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); @@ -340,8 +256,6 @@ void testCreatePartyPartyManagerFailed() { @Test void testCreatePartyHappyPath() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); @@ -367,28 +281,9 @@ void testCreatePartyHappyPath() { } } - @Test - @DisplayName("SSE endpoint - fails in production environment") - void getDuelDataFailsInProduction() { - when(ff.isDuels()).thenReturn(false); - - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { - duelController.getDuelData(null); - }); - - assertEquals(HttpStatus.FORBIDDEN.value(), exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - verify(lobbyRepository, times(0)).findActiveLobbyByLobbyPlayerPlayerId(any()); - verify(lobbyRepository, times(0)).findAvailableLobbyByLobbyPlayerPlayerId(any()); - verify(lobbyNotifyHandler, times(0)).register(any(), any()); - } - @Test @DisplayName("SSE endpoint - lobby does not exist") void getDuelDataLobbyDoesNotExist() throws DuelException { - when(ff.isDuels()).thenReturn(true); - when(lobbyRepository.findActiveLobbyByJoinCode(any())).thenReturn(Optional.empty()); when(lobbyRepository.findAvailableLobbyByJoinCode(any())).thenReturn(Optional.empty()); @@ -408,8 +303,6 @@ void getDuelDataLobbyDoesNotExist() throws DuelException { @Test @DisplayName("SSE endpoint - active lobby exists") void getDuelDataPlayerInActiveLobby() { - when(ff.isDuels()).thenReturn(true); - String lobbyId = randomUUID(); Lobby activeLobby = Lobby.builder() .id(lobbyId) @@ -434,8 +327,6 @@ void getDuelDataPlayerInActiveLobby() { @Test @DisplayName("SSE endpoint - available lobby exists") void getDuelDataAvailableLobbyExists() { - when(ff.isDuels()).thenReturn(true); - String lobbyId = randomUUID(); Lobby availableLobby = Lobby.builder() .id(lobbyId) @@ -459,33 +350,11 @@ void getDuelDataAvailableLobbyExists() { verify(lobbyNotifyHandler, times(1)).register(eq(lobbyId), any()); } - @Test - void testStartDuelIsInProd() { - User user = createRandomUser(); - AuthenticationObject authObj = createAuthenticationObject(user); - - when(ff.isDuels()).thenReturn(false); - - ResponseStatusException exception = - assertThrows(ResponseStatusException.class, () -> duelController.startDuel(authObj)); - - assertEquals(403, exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - try { - verify(duelManager, times(0)).startDuel(any(), eq(false)); - } catch (DuelException e) { - fail(e); - } - } - @Test void testStartDuelDuelManagerFailed() { User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); - when(ff.isDuels()).thenReturn(true); - try { doThrow(new DuelException(HttpStatus.INTERNAL_SERVER_ERROR, "This is an example duel exception.")) .when(duelManager) @@ -511,8 +380,6 @@ void testStartDuelHappyPath() { User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); - when(ff.isDuels()).thenReturn(true); - try { doNothing().when(duelManager).startDuel(eq(user.getId()), eq(user.isAdmin())); } catch (DuelException _) { @@ -526,34 +393,10 @@ void testStartDuelHappyPath() { assertTrue(apiResponder.isSuccess()); } - @Test - void testEndDuelIsInProd() { - User user = createRandomUser(); - AuthenticationObject authObj = createAuthenticationObject(user); - - when(ff.isDuels()).thenReturn(false); - - ResponseStatusException exception = - assertThrows(ResponseStatusException.class, () -> duelController.endDuel(authObj)); - - assertEquals(403, exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - verify(lobbyRepository, times(0)).findActiveLobbyByLobbyPlayerPlayerId(any()); - - try { - verify(duelManager, times(0)).endDuel(any(), eq(user.isAdmin())); - } catch (DuelException e) { - fail(e); - } - } - @Test void testEndDuelActivePartyForPlayerCannotBeFound() { User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); - - when(ff.isDuels()).thenReturn(true); when(lobbyRepository.findActiveLobbyByLobbyPlayerPlayerId(user.getId())).thenReturn(Optional.empty()); ResponseStatusException exception = @@ -584,7 +427,6 @@ void testEndDuelDuelManagerFailed() { .playerCount(3) .build(); - when(ff.isDuels()).thenReturn(true); when(lobbyRepository.findActiveLobbyByLobbyPlayerPlayerId(user.getId())).thenReturn(Optional.of(lobby)); try { @@ -609,31 +451,8 @@ void testEndDuelDuelManagerFailed() { } } - @Test - void testGetPartyOrDuelCodeForUserFailsInProduction() { - when(ff.isDuels()).thenReturn(false); - - User user = createRandomUser(); - AuthenticationObject authObj = createAuthenticationObject(user); - - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { - duelController.getPartyOrDuelCodeForUser(authObj); - }); - - assertEquals(HttpStatus.FORBIDDEN.value(), exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - try { - verify(duelManager, never()).getLobbyByUserId(eq(user.getId())); - } catch (DuelException e) { - fail(e); - } - } - @Test void testGetPartyOrDuelCodeForUserDuelManagerFailed() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); @@ -659,8 +478,6 @@ void testGetPartyOrDuelCodeForUserDuelManagerFailed() { @Test void testGetPartyOrDuelCodeForUserHappyPath() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); @@ -684,31 +501,8 @@ void testGetPartyOrDuelCodeForUserHappyPath() { } } - @Test - void testProcessSolvedProblemsInDuelFailsInProduction() { - when(ff.isDuels()).thenReturn(false); - - User user = createRandomUser(); - AuthenticationObject authObj = createAuthenticationObject(user); - - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { - duelController.processSolvedProblemsInDuel(authObj); - }); - - assertEquals(HttpStatus.FORBIDDEN.value(), exception.getStatusCode().value()); - assertEquals("Endpoint is currently non-functional", exception.getReason()); - - try { - verify(duelManager, never()).processSubmissions(eq(user), any()); - } catch (DuelException e) { - fail(e); - } - } - @Test void testProcessSolvedProblemsDuelManagerFailed() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); @@ -741,8 +535,6 @@ void testProcessSolvedProblemsDuelManagerFailed() { @Test void testProcessSolvedProblemsHappyPath() { - when(ff.isDuels()).thenReturn(true); - User user = createRandomUser(); AuthenticationObject authObj = createAuthenticationObject(user); diff --git a/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java b/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java index e3ffc3590..1500c5919 100644 --- a/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/api/user/UserControllerTest.java @@ -35,7 +35,6 @@ import org.patinanetwork.codebloom.common.dto.user.UserDto; import org.patinanetwork.codebloom.common.dto.user.metrics.MetricsDto; import org.patinanetwork.codebloom.common.page.Page; -import org.patinanetwork.codebloom.jda.properties.FeatureFlagConfiguration; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; @@ -48,12 +47,11 @@ public class UserControllerTest { private UserRepository userRepository = mock(UserRepository.class); private QuestionTopicService questionTopicService = mock(QuestionTopicService.class); private UserMetricsRepository userMetricsRepository = mock(UserMetricsRepository.class); - private FeatureFlagConfiguration ff = mock(FeatureFlagConfiguration.class); private HttpServletRequest request = mock(HttpServletRequest.class); public UserControllerTest() { this.userController = - new UserController(questionRepository, userRepository, questionTopicService, userMetricsRepository, ff); + new UserController(questionRepository, userRepository, questionTopicService, userMetricsRepository); this.faker = Faker.instance(); } @@ -374,21 +372,6 @@ private UserMetrics createRandomUserMetrics(final String userId) { .build(); } - @Test - @DisplayName("Get user metrics - feature flag disabled returns 403") - void getUserMetricsFeatureFlagDisabledReturnsForbidden() { - String userId = randomUUID(); - - when(ff.isUserMetrics()).thenReturn(false); - - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { - userController.getUserMetrics(request, userId, 1, 20, null, null); - }); - - assertEquals(HttpStatus.FORBIDDEN, exception.getStatusCode()); - assertEquals("Endpoint is not available.", exception.getReason()); - } - @Test @DisplayName("Get user metrics - startDate after endDate returns 400") void getUserMetricsInvalidDateRangeReturnsBadRequest() { @@ -396,8 +379,6 @@ void getUserMetricsInvalidDateRangeReturnsBadRequest() { OffsetDateTime startDate = OffsetDateTime.now(); OffsetDateTime endDate = startDate.minusDays(1); - when(ff.isUserMetrics()).thenReturn(true); - ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> { userController.getUserMetrics(request, userId, 1, 20, startDate, endDate); }); @@ -415,7 +396,6 @@ void getUserMetricsReturnsSuccessfullyWithDateRange() { List metricsList = List.of(createRandomUserMetrics(userId), createRandomUserMetrics(userId)); - when(ff.isUserMetrics()).thenReturn(true); when(userMetricsRepository.findUserMetrics(eq(userId), any())).thenReturn(metricsList); when(userMetricsRepository.countUserMetrics(eq(userId), any())).thenReturn(2); @@ -443,7 +423,6 @@ void getUserMetricsReturnsSuccessfullyWithDateRange() { void getUserMetricsDefaultsToLastSevenDays() { String userId = randomUUID(); - when(ff.isUserMetrics()).thenReturn(true); when(userMetricsRepository.findUserMetrics(eq(userId), any())).thenReturn(List.of()); when(userMetricsRepository.countUserMetrics(eq(userId), any())).thenReturn(0); @@ -460,7 +439,6 @@ void getUserMetricsDefaultsToLastSevenDays() { void getUserMetricsEmptyResults() { String userId = randomUUID(); - when(ff.isUserMetrics()).thenReturn(true); when(userMetricsRepository.findUserMetrics(eq(userId), any())).thenReturn(List.of()); when(userMetricsRepository.countUserMetrics(eq(userId), any())).thenReturn(0); @@ -479,7 +457,6 @@ void getUserMetricsEmptyResults() { void getUserMetricsPageSizeCapped() { String userId = randomUUID(); - when(ff.isUserMetrics()).thenReturn(true); when(userMetricsRepository.findUserMetrics(eq(userId), any())).thenReturn(List.of()); when(userMetricsRepository.countUserMetrics(eq(userId), any())).thenReturn(0); @@ -501,7 +478,6 @@ void getUserMetricsPaginationMultiplePages() { metricsList.add(createRandomUserMetrics(userId)); } - when(ff.isUserMetrics()).thenReturn(true); when(userMetricsRepository.findUserMetrics(eq(userId), any())).thenReturn(metricsList); when(userMetricsRepository.countUserMetrics(eq(userId), any())).thenReturn(45); @@ -522,7 +498,6 @@ void getUserMetricsDtoMapsFieldsCorrectly() { String userId = randomUUID(); UserMetrics metric = createRandomUserMetrics(userId); - when(ff.isUserMetrics()).thenReturn(true); when(userMetricsRepository.findUserMetrics(eq(userId), any())).thenReturn(List.of(metric)); when(userMetricsRepository.countUserMetrics(eq(userId), any())).thenReturn(1); diff --git a/src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java b/src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java index 1a7ea63ab..8a2a8b741 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/ff/FFAspectTest.java @@ -14,6 +14,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; @@ -50,7 +51,11 @@ void forbiddenWhenFalse() { @Test @DisplayName("Throws when unknown flag is used") void throwsOnUnknownFlag() { - assertThrows(IllegalArgumentException.class, () -> testService.methodWithUnknownFlag()); + IllegalArgumentException exception = + assertThrows(IllegalArgumentException.class, () -> testService.methodWithUnknownFlag()); + + assertTrue(exception.getMessage().contains("Invalid @FF expression")); + assertTrue(exception.getCause() instanceof SpelEvaluationException); assertEquals(0, testService.getCalls()); } diff --git a/src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java b/src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java index a3d9419d9..e0cd8b5cf 100644 --- a/src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java +++ b/src/test/java/org/patinanetwork/codebloom/common/ff/FeatureFlagManagerTest.java @@ -1,10 +1,8 @@ package org.patinanetwork.codebloom.common.ff; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,25 +20,6 @@ void setUp() { featureFlagManager = new FeatureFlagManager(ff); } - @Test - void validateExpressionFlagsExistAllowsKnownFlags() { - assertDoesNotThrow(() -> featureFlagManager.validateExpressionFlagsExist("duels && userMetrics")); - } - - @Test - void validateExpressionFlagsExistAllowsReservedIdentifiers() { - assertDoesNotThrow(() -> featureFlagManager.validateExpressionFlagsExist("duels && true && !false")); - } - - @Test - void validateExpressionFlagsExistThrowsWhenFlagUnknown() { - IllegalArgumentException exception = assertThrows( - IllegalArgumentException.class, - () -> featureFlagManager.validateExpressionFlagsExist("duels && school")); - - assertTrue(exception.getMessage().contains("school")); - } - @Test void getAllFlagsReturnsValuesFromConfiguration() { var flags = featureFlagManager.getAllFlags(); From f3fee5e4c2f075409c9933315a2cda5a8d2f73b3 Mon Sep 17 00:00:00 2001 From: Alfardil Alam Date: Tue, 31 Mar 2026 16:36:31 -0400 Subject: [PATCH 3/4] 866: Added javadoc --- .../codebloom/jda/properties/FeatureFlagConfiguration.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/patinanetwork/codebloom/jda/properties/FeatureFlagConfiguration.java b/src/main/java/org/patinanetwork/codebloom/jda/properties/FeatureFlagConfiguration.java index d0659943c..a2dc08c4f 100644 --- a/src/main/java/org/patinanetwork/codebloom/jda/properties/FeatureFlagConfiguration.java +++ b/src/main/java/org/patinanetwork/codebloom/jda/properties/FeatureFlagConfiguration.java @@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +/** Feature flags that we supported. Must be defined through application.yml */ @Getter @Setter @Component From df6b3583adb13c3ced43c81ab8eada2d79d8a900 Mon Sep 17 00:00:00 2001 From: Arshadul Monir Date: Tue, 31 Mar 2026 21:10:26 -0400 Subject: [PATCH 4/4] 866: TestProtector fix --- .../org/patinanetwork/codebloom/config/TestProtector.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java b/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java index 844695df6..ec9d2b28c 100644 --- a/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java +++ b/src/test/java/org/patinanetwork/codebloom/config/TestProtector.java @@ -1,6 +1,7 @@ package org.patinanetwork.codebloom.config; import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.Session; import org.patinanetwork.codebloom.common.db.models.user.User; import org.patinanetwork.codebloom.common.db.repos.session.SessionRepository; @@ -37,9 +38,10 @@ public Protector protector() { @Override public AuthenticationObject validateSession(final HttpServletRequest request) { User mockAdminUser = userRepository.getUserById("ed3bfe18-e42a-467f-b4fa-07e8da4d2555"); - Session mockAdminSession = sesssionRepository - .getSessionById("d99e10a2-6285-46f0-8150-ba4727b520f4") - .orElseThrow(); + Optional mockAdminSessionOp = + sesssionRepository.getSessionById("d99e10a2-6285-46f0-8150-ba4727b520f4"); + + Session mockAdminSession = mockAdminSessionOp.isPresent() ? mockAdminSessionOp.get() : null; return new AuthenticationObject(mockAdminUser, mockAdminSession); }