Skip to content

Commit

Permalink
PO-234: Feature toggle add note (#255)
Browse files Browse the repository at this point in the history
  • Loading branch information
sabahirfan authored Mar 14, 2024
1 parent aeaeea0 commit 3a908b6
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package uk.gov.hmcts.opal.controllers.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import uk.gov.hmcts.opal.launchdarkly.FeatureDisabledException;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(FeatureDisabledException.class)
public ResponseEntity<String> handleFeatureDisabledException(FeatureDisabledException ex) {
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(ex.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package uk.gov.hmcts.opal.launchdarkly;


public class FeatureDisabledException extends RuntimeException {

public FeatureDisabledException(String message) {
super(message);
}
}
15 changes: 15 additions & 0 deletions src/main/java/uk/gov/hmcts/opal/launchdarkly/FeatureToggle.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,19 @@
* @return boolean value
*/
boolean value() default true;

/**
* Indicates the default boolean value of feature toggle in case of failure to fetch the value from launchdarkly.
*
* @return boolean value
*/
boolean defaultValue() default true;

/**
* Indicates the default Exception to throw when the feature is not enabled.
*
* @return boolean value
*/
Class<? extends Throwable> throwException() default FeatureDisabledException.class;

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,30 @@ public class FeatureToggleAspect {
@Around("execution(* *(*)) && @annotation(featureToggle)")
public void checkFeatureEnabled(ProceedingJoinPoint joinPoint, FeatureToggle featureToggle) throws Throwable {

if (featureToggle.value() && featureToggleApi.isFeatureEnabled(featureToggle.feature())) {
if (featureToggle.value() && featureToggleApi.isFeatureEnabled(
featureToggle.feature(),
featureToggle.defaultValue()
)) {
joinPoint.proceed();
} else if (!featureToggle.value() && !featureToggleApi.isFeatureEnabled(featureToggle.feature())) {
} else if (!featureToggle.value() && !featureToggleApi.isFeatureEnabled(
featureToggle.feature(),
featureToggle.defaultValue()
)) {
joinPoint.proceed();
} else {
log.warn(
String message = String.format(
"Feature %s is not enabled for method %s",
featureToggle.feature(),
joinPoint.getSignature().getName()
);
log.warn(message);
// Check if an exception is specified in the annotation
if (featureToggle.throwException() != null) {
// Throw the specified exception
throw featureToggle.throwException()
.getConstructor(String.class)
.newInstance(message);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.stereotype.Service;
import uk.gov.hmcts.opal.dto.NoteDto;
import uk.gov.hmcts.opal.dto.search.NoteSearchDto;
import uk.gov.hmcts.opal.launchdarkly.FeatureToggle;
import uk.gov.hmcts.opal.service.DynamicConfigService;
import uk.gov.hmcts.opal.service.NoteServiceInterface;
import uk.gov.hmcts.opal.service.legacy.LegacyNoteService;
Expand All @@ -26,6 +27,7 @@ private NoteServiceInterface getCurrentModeService() {
}

@Override
@FeatureToggle(feature = "add-note", value = true)
public NoteDto saveNote(NoteDto noteDto) {
return getCurrentModeService().saveNote(noteDto);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package uk.gov.hmcts.opal.controllers.advice;

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ContextConfiguration;
import uk.gov.hmcts.opal.launchdarkly.FeatureDisabledException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@SpringBootTest
@ContextConfiguration(classes = GlobalExceptionHandler.class)
public class GlobalExceptionHandlerTest {

@Mock
private FeatureDisabledException exception;

@InjectMocks
private GlobalExceptionHandler globalExceptionHandler;

@Test
public void handleFeatureDisabledException_ReturnsMethodNotAllowed() {
// Arrange
String errorMessage = "Feature is disabled";
when(exception.getMessage()).thenReturn(errorMessage);

// Act
ResponseEntity<String> response = globalExceptionHandler.handleFeatureDisabledException(exception);

// Assert
assertEquals(HttpStatus.METHOD_NOT_ALLOWED, response.getStatusCode());
assertEquals(errorMessage, response.getBody());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package uk.gov.hmcts.opal.launchdarkly;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class FeatureDisabledExceptionTest {

@Test
public void testConstructor() {
// Arrange
String errorMessage = "Feature is disabled";

// Act
FeatureDisabledException exception = new FeatureDisabledException(errorMessage);

// Assert
assertEquals(errorMessage, exception.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
Expand All @@ -29,6 +32,7 @@
class FeatureToggleAspectTest {

private static final String NEW_FEATURE = "NEW_FEATURE";
private static final String EXCEPTION = "Feature NEW_FEATURE is not enabled for method myFeatureToggledMethod";
@Autowired
FeatureToggleAspect featureToggleAspect;

Expand Down Expand Up @@ -72,6 +76,24 @@ void shouldNotProceedToMethodInvocation_whenFeatureToggleIsDisabled(Boolean stat
verify(proceedingJoinPoint, never()).proceed();
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void shouldThrowException_whenFeatureToggleIsDisabled(Boolean state) {
when(featureToggle.value()).thenReturn(state);

when(featureToggle.throwException()).thenAnswer(invocation -> FeatureDisabledException.class);

givenToggle(NEW_FEATURE, !state);

FeatureDisabledException exception = assertThrows(
FeatureDisabledException.class,
() -> featureToggleAspect.checkFeatureEnabled(proceedingJoinPoint, featureToggle)
);

assertNotNull(exception);
assertEquals(EXCEPTION, exception.getMessage());
}

private void givenToggle(String feature, boolean state) {
when(ldClient.boolVariation(eq(feature), any(LDContext.class), anyBoolean()))
.thenReturn(state);
Expand Down

0 comments on commit 3a908b6

Please sign in to comment.