├── .gitignore ├── .travis.yml ├── README.md ├── flips-core ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── flips │ │ │ ├── advice │ │ │ ├── FlipBeanAdvice.java │ │ │ └── FlipFeatureAdvice.java │ │ │ ├── annotation │ │ │ ├── FlipBean.java │ │ │ ├── FlipOff.java │ │ │ ├── FlipOnDateTime.java │ │ │ ├── FlipOnDaysOfWeek.java │ │ │ ├── FlipOnEnvironmentProperty.java │ │ │ ├── FlipOnOff.java │ │ │ ├── FlipOnProfiles.java │ │ │ └── FlipOnSpringExpression.java │ │ │ ├── condition │ │ │ ├── DateTimeFlipCondition.java │ │ │ ├── DayOfWeekFlipCondition.java │ │ │ ├── FlipCondition.java │ │ │ ├── FlipOffCondition.java │ │ │ ├── SpringEnvironmentPropertyFlipCondition.java │ │ │ ├── SpringExpressionFlipCondition.java │ │ │ └── SpringProfileFlipCondition.java │ │ │ ├── config │ │ │ └── FlipContextConfiguration.java │ │ │ ├── exception │ │ │ ├── FeatureNotEnabledException.java │ │ │ ├── FlipBeanFailedException.java │ │ │ └── SpringExpressionEvaluationFailureException.java │ │ │ ├── model │ │ │ ├── DefaultFlipConditionEvaluator.java │ │ │ ├── EmptyFlipConditionEvaluator.java │ │ │ ├── Feature.java │ │ │ ├── FeatureContext.java │ │ │ ├── FeatureExpressionContext.java │ │ │ ├── FlipAnnotationAttributes.java │ │ │ ├── FlipConditionEvaluator.java │ │ │ └── FlipConditionEvaluatorFactory.java │ │ │ ├── processor │ │ │ └── FlipAnnotationProcessor.java │ │ │ ├── store │ │ │ └── FlipAnnotationsStore.java │ │ │ └── utils │ │ │ ├── AnnotationUtils.java │ │ │ ├── DateTimeUtils.java │ │ │ ├── Utils.java │ │ │ └── ValidationUtils.java │ └── resources │ │ ├── logback.xml │ │ └── org │ │ └── flips │ │ └── application.properties │ └── test │ └── java │ └── org │ └── flips │ ├── advice │ ├── FlipBeanAdviceIntegrationTest.java │ ├── FlipBeanAdviceUnitTest.java │ ├── FlipFeatureAdviceIntegrationTest.java │ └── FlipFeatureAdviceUnitTest.java │ ├── condition │ ├── DateTimeFlipConditionUnitTest.java │ ├── DayOfWeekFlipConditionUnitTest.java │ ├── FlipOffConditionUnitTest.java │ ├── SpringEnvironmentPropertyFlipConditionUnitTest.java │ ├── SpringExpressionFlipConditionUnitTest.java │ └── SpringProfileFlipConditionUnitTest.java │ ├── fixture │ ├── TestClientFlipAnnotationsWithAnnotationsOnMethod.java │ ├── TestClientFlipAnnotationsWithSpringExpressionAnnotations.java │ ├── TestClientFlipBeanSpringComponentSource.java │ ├── TestClientFlipBeanSpringComponentTarget.java │ ├── TestClientFlipSpringComponent.java │ └── TestClientFlipSpringService.java │ ├── model │ ├── EmptyFlipConditionEvaluatorUnitTest.java │ ├── FeatureContextUnitTest.java │ ├── FlipAnnotationAttributesUnitTest.java │ ├── FlipConditionEvaluatorFactoryUnitTest.java │ └── FlipConditionEvaluatorUnitTest.java │ ├── processor │ └── FlipAnnotationProcessorUnitTest.java │ ├── store │ ├── FlipAnnotationsStoreIntegrationTest.java │ └── FlipAnnotationsStoreUnitTest.java │ └── utils │ ├── AnnotationUtilsUnitTest.java │ ├── UtilsUnitTest.java │ └── ValidationUtilsUnitTest.java ├── flips-web ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── org │ │ └── flips │ │ └── describe │ │ ├── config │ │ └── FlipWebContextConfiguration.java │ │ ├── controller │ │ └── FlipDescriptionController.java │ │ ├── controlleradvice │ │ └── FlipControllerAdvice.java │ │ └── model │ │ ├── FeatureNotEnabledErrorResponse.java │ │ └── FlipDescription.java │ └── test │ └── java │ └── org │ └── flips │ └── describe │ ├── controller │ ├── FlipDescriptionControllerIntegrationTest.java │ └── FlipDescriptionControllerUnitTest.java │ ├── controlleradvice │ ├── FlipControllerAdviceIntegrationTest.java │ └── FlipControllerAdviceUnitTest.java │ └── fixture │ ├── TestClientController.java │ ├── TestClientControllerOther.java │ └── TestClientFlipAnnotationsDescription.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | logs/** 2 | build/** 3 | .gradle/** 4 | .idea/ 5 | classes/** 6 | *.iml 7 | target/** -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | script: mvn test 3 | jdk: 4 | - oraclejdk8 5 | after_success: 6 | - mvn clean test jacoco:report coveralls:report -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flips 2 | [Flips](https://dzone.com/articles/flips-feature-flip-for-java) is an implementation of Feature Toggles pattern for Java. [Feature Toggles](https://martinfowler.com/articles/feature-toggles.html) are a powerful technique, allowing teams to modify system behavior without changing the code. 3 | 4 | [![Build Status](https://travis-ci.org/Feature-Flip/flips.svg?branch=master)](https://travis-ci.org/Feature-Flip/flips) 5 | [![Coverage Status](https://coveralls.io/repos/github/Feature-Flip/flips/badge.svg?branch=master)](https://coveralls.io/github/Feature-Flip/flips?branch=master) 6 | 7 | ## Getting Started 8 | Following feature toggle is going to switch bean with same signature method to go live on specific date time. 9 | Property allows to define different datetime specific to ENV and in ISO 8601 format. 10 | 11 | ```java 12 | @Component 13 | class MyBean { 14 | 15 | @FlipBean(with = AnotherBean.class) 16 | @FlipOnDateTime(cutoffDateTimeProperty="datetime-property") 17 | public List
getLatestArticles(){ 18 | // OLD logic of getting latest articles 19 | } 20 | } 21 | 22 | @Component 23 | class AnotherBean { 24 | 25 | public List
getLatestArticles(){ 26 | // NEW logic of getting latest articles 27 | } 28 | } 29 | 30 | ``` 31 | 32 | ## Why another library for feature toggle ? 33 | The idea behind Flips is to let the users implement toggles with minimum configuration and coding. This library is intended to work with Java8, Spring Core / Spring MVC / Spring Boot. 34 | 35 | ## Where do I get sample project(s) ? 36 | Sample projects can be found [here](https://github.com/SarthakMakhija/flips-samples). 37 | 38 | ## Maven Configuration 39 | 40 | Include flips-web if the project is a WEB project - 41 | ``` 42 | 43 | com.github.feature-flip 44 | flips-web 45 | 1.0.1 46 | 47 | ``` 48 | Otherwise flips-core, 49 | 50 | ``` 51 | 52 | com.github.feature-flip 53 | flips-core 54 | 1.0.1 55 | 56 | ``` 57 | 58 | ## Detail Description of all Annotations 59 | 60 | Flips provides various annotations to flip a feature. Let's have a detailed walk-through of all the annotations - 61 | 62 | **@FlipOnEnvironmentProperty** is used to flip a feature based on the value of an environment property. 63 | 64 | **Usage** 65 | 66 | ```java 67 | @Component 68 | class EmailSender{ 69 | 70 | @FlipOnEnvironmentProperty(property="feature.send.email", expectedValue="true") 71 | public void sendEmail(EmailMessage emailMessage){ 72 | } 73 | } 74 | ``` 75 | Feature ```sendEmail``` is enabled if the property **feature.send.email** is set to true. 76 | 77 | **@FlipOnProfiles** is used to flip a feature based on the envinronment in which the application is running. 78 | 79 | **Usage** 80 | 81 | ```java 82 | @Component 83 | class EmailSender{ 84 | 85 | @FlipOnProfiles(activeProfiles={"dev", "qa"}) 86 | public void sendEmail(EmailMessage emailMessage){ 87 | } 88 | } 89 | ``` 90 | Feature ```sendEmail``` is enabled if the current profile is either **dev or qa**. 91 | 92 | **@FlipOnDaysOfWeek** is used to flip a feature based on the day of the week. 93 | 94 | **Usage** 95 | 96 | ```java 97 | @Component 98 | class EmailSender{ 99 | 100 | @FlipOnDaysOfWeek(daysOfWeek={DayOfWeek.MONDAY,DayOfWeek.TUESDAY}) 101 | public void sendEmail(EmailMessage emailMessage){ 102 | } 103 | } 104 | ``` 105 | Feature ```sendEmail``` is enabled if current day is either **MONDAY or TUESDAY**. 106 | 107 | **@FlipOnDateTime** is used to flip a feature based on date and time. 108 | 109 | **Usage** 110 | 111 | ```java 112 | @Component 113 | class EmailSender{ 114 | 115 | @FlipOnDateTime(cutoffDateTimeProperty="default.date.enabled") 116 | public void sendEmail(EmailMessage emailMessage){ 117 | } 118 | } 119 | ``` 120 | Feature ```sendEmail``` is enabled if current datetime is equal to or greater than the value defined by the **default.date.enabled** property. 121 | 122 | **@FlipOnSpringExpression** is used to flip a feature based on the evaluation of spring expression. 123 | 124 | **Usage** 125 | 126 | ```java 127 | @Component 128 | class EmailSender{ 129 | 130 | @FlipOnSpringExpression(expression = "T(java.lang.Math).sqrt(4) * 100.0 < T(java.lang.Math).sqrt(4) * 10.0") 131 | public void sendEmail(EmailMessage emailMessage){ 132 | } 133 | } 134 | ``` 135 | Feature ```sendEmail``` is enabled if the expression evaluates to TRUE. 136 | 137 | **@FlipBean** is used to flip the invocation of a method with another method. It is most likely to be used in conjunction with @FlipOn... annotation. 138 | 139 | **Usage** 140 | 141 | ```java 142 | @Component 143 | class EmailSender{ 144 | 145 | @FlipBean(with=SendGridEmailSender.class) 146 | public void sendEmail(EmailMessage emailMessage){ 147 | } 148 | } 149 | ``` 150 | will flip the invocation of ```sendEmail``` method with the one (having exactly same signature) defined in **SendGridEmailSender**. 151 | 152 | **@FlipOff** is used to flip a feature off. 153 | 154 | **Usage** 155 | 156 | ```java 157 | @Component 158 | class EmailSender{ 159 | 160 | @FlipOff 161 | public void sendEmail(EmailMessage emailMessage){ 162 | } 163 | } 164 | ``` 165 | Feature ```sendEmail``` is always DISABLED. 166 | 167 | 168 | ## FAQs 169 | 170 | **1. Is there a way to combine these annotations ? Eg; I want a feature to be enabled only on PROD environment but after a given date.** 171 | 172 | **Yes**, these annotations can be combined. Currently, such combinations are treated as AND operations, meaning all the conditions MUST evaluate to TRUE for a feature to be enabled. 173 | 174 | **Usage** 175 | 176 | ```java 177 | @Component 178 | class EmailSender{ 179 | 180 | @FlipOnProfiles(activeProfiles = "PROD") 181 | @FlipOnDateTime(cutoffDateTimeProperty = "sendemail.feature.active.after") 182 | public void sendEmail(EmailMessage emailMessage){ 183 | } 184 | } 185 | ``` 186 | this will throw FeatureNotEnabledException is either of the conditions evaluate to FALSE 187 | 188 | **2. Is there a way to flip a bean based on conditions ? Eg; I want a feature to be ```flipped with``` only in DEV.** 189 | 190 | **Yes**, @FlipBean can be used with conditions. If used with conditions, flip bean will be activated if all the conditions evaluate to TRUE 191 | 192 | **Usage** 193 | 194 | ```java 195 | @Component 196 | class EmailSender{ 197 | 198 | @FlipBean(with=SendGridEmailSender.class) 199 | @FlipOnProfiles(activeProfiles = "DEV") 200 | public void sendEmail(EmailMessage emailMessage){ 201 | } 202 | } 203 | ``` 204 | this will flip the implementation of sendEmail with the same method defined in ```SendGridEmailSender```if active profile is DEV. 205 | 206 | **3. What date format is accepted in FlipOnDateTime ?** 207 | 208 | **ISO-8601**. 209 | 210 | **Usage** 211 | 212 | ```java 213 | @Component 214 | class EmailSender{ 215 | 216 | @FlipOnDateTime(cutoffDateTimeProperty = "sendemail.feature.active.after") 217 | public void sendEmail(EmailMessage emailMessage){ 218 | } 219 | } 220 | ``` 221 | Assuming, today is 20th Sep 2018, one could set **sendemail.feature.active.after** to a value equal to before 20th Sep 2018. sendemail.feature.active.after=2018-09-16T00:00:00Z 222 | 223 | **4. What happens on invoking a disabled feature ?** 224 | 225 | **FeatureNotEnabledException** is thrown if a disabled feature is invoked. In case of a WEB application, one could use 226 | flips-web dependency which also provides ```ControllerAdvice``` meant to handle this exception. It returns a default response and a status code of 501. 227 | 228 | **5. Is it possible for the client of this library to override the response returned by ```ControllerAdvice``` ?** 229 | 230 | **Yes**, this is doable. You can register your ```ControllerAdvice``` with an exception handler meant for handling **FeatureNotEnabledException**. 231 | Please refer [Sample Project](https://github.com/SarthakMakhija/flips-samples/tree/master/flips-sample-spring-boot/src/main/java/com/finder/article/advice). 232 | 233 | **6. What should be the signature of target method while using @FlipBean** 234 | 235 | The target method should have exactly the same signature as the method which is annotated with @FlipBean annotation. 236 | Please refer "getArticleStatisticsByYear" method [Sample Project](https://github.com/SarthakMakhija/flips-samples/blob/master/flips-sample-spring-boot/src/main/java/com/finder/article/controller/ArticleController.java). 237 | 238 | **7. How do I load Spring Configuration related to Flips ?** 239 | 240 | In order to bring all Flips related annotations in effect, FlipConfiguration needs to be imported. 241 | 242 | **Usage** 243 | 244 | ```java 245 | @SpringBootApplication 246 | @Import(FlipWebContextConfiguration.class) 247 | class ApplicationConfig{ 248 | public static void main(String[] args) { 249 | SpringApplication.run(ApplicationConfig.class, args); 250 | } 251 | } 252 | ``` 253 | you will need to import FlipWebContextConfiguration as mentioned above. 254 | Please refer [Sample Project](https://github.com/SarthakMakhija/flips-samples/blob/master/flips-sample-spring-boot/src/main/java/com/finder/article/ApplicationConfig.java). 255 | 256 | **8. Is there a way to create custom annotation(s) to flip a feature ?** 257 | 258 | **Yes**. You can create a custom annotation to fit your use case. Create a custom annotation at METHOD level which has a meta-annotation of type @FlipOnOff. 259 | 260 | ```java 261 | @Target({ElementType.METHOD}) 262 | @Retention(RetentionPolicy.RUNTIME) 263 | @FlipOnOff(value = MyCustomCondition.class) 264 | public @interface MyCustomAnnotation { 265 | } 266 | ``` 267 | As a part of this annotation, specify the condition which will evaluate the result of this annotation. 268 | 269 | ```java 270 | @Component 271 | public class MyCustomCondition implements FlipCondition { 272 | 273 | @Override 274 | public boolean evaluateCondition(FeatureContext featureContext, 275 | FlipAnnotationAttributes flipAnnotationAttributes) { 276 | return false; 277 | } 278 | } 279 | ``` 280 | This ```Condition``` class needs to implement FlipCondition and **MUST be a Spring Component**. This is it !! 281 | 282 | **9. How do I get to see all the methods which are annotated with Flip annotations along with their status ?** 283 | 284 | This functionality is available in flips-web as a REST endpoint. You can hit **/describe/features** to see the features and their status. 285 | 286 | ## Want to contribute? 287 | 1. Fork it 288 | 2. Create your feature branch (git checkout -b my-new-feature) 289 | 3. Commit your changes (git commit -am 'Add some feature') 290 | 3. Push to the branch (git push origin my-new-feature) 291 | 4. Create new Pull Request 292 | 293 | ## Share feedback 294 | Please use Github [issues](https://github.com/Feature-Flip/flips/issues) to share feedback, feature suggestions and report issues. 295 | 296 | ## Credits 297 | 1. A big Thank you to [Sunit Parekh](https://github.com/sunitparekh/) for providing his guidance 298 | 2. [Prateek Shah](https://github.com/shahprateek1991) 299 | 3. [Yashica](https://github.com/yashicag) 300 | -------------------------------------------------------------------------------- /flips-core/.gitignore: -------------------------------------------------------------------------------- 1 | logs/** 2 | build/** 3 | .gradle/** 4 | .idea/ 5 | classes/** 6 | *.iml 7 | target/** -------------------------------------------------------------------------------- /flips-core/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | flips 5 | com.github.feature-flip 6 | 1.1 7 | 8 | 9 | 4.0.0 10 | flips-core 11 | jar 12 | 13 | flips-core 14 | https://github.com/Feature-Flip/flips 15 | Flips Core framework, provides all the flip annotations, conditions and different advices 16 | 17 | 18 | UTF-8 19 | 20 | 21 | 22 | 23 | junit 24 | junit 25 | test 26 | 27 | 28 | org.mockito 29 | mockito-all 30 | test 31 | 32 | 33 | org.powermock 34 | powermock-api-mockito 35 | test 36 | 37 | 38 | org.powermock 39 | powermock-module-junit4 40 | test 41 | 42 | 43 | org.springframework 44 | spring-test 45 | test 46 | 47 | 48 | org.springframework 49 | spring-core 50 | 51 | 52 | org.springframework 53 | spring-context 54 | 55 | 56 | org.springframework 57 | spring-context-support 58 | ${version.spring} 59 | 60 | 61 | org.springframework 62 | spring-aop 63 | ${version.spring} 64 | 65 | 66 | org.aspectj 67 | aspectjrt 68 | ${version.aspectj} 69 | 70 | 71 | org.aspectj 72 | aspectjweaver 73 | ${version.aspectj} 74 | 75 | 76 | org.slf4j 77 | slf4j-api 78 | 79 | 80 | org.slf4j 81 | jcl-over-slf4j 82 | 83 | 84 | ch.qos.logback 85 | logback-classic 86 | 87 | 88 | 89 | 90 | 91 | 92 | src/main/resources 93 | 94 | logback.xml 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/advice/FlipBeanAdvice.java: -------------------------------------------------------------------------------- 1 | package org.flips.advice; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.annotation.Around; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.aspectj.lang.annotation.Pointcut; 7 | import org.aspectj.lang.reflect.MethodSignature; 8 | import org.flips.annotation.FlipBean; 9 | import org.flips.exception.FeatureNotEnabledException; 10 | import org.flips.exception.FlipBeanFailedException; 11 | import org.flips.store.FlipAnnotationsStore; 12 | import org.flips.utils.AnnotationUtils; 13 | import org.flips.utils.Utils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.context.ApplicationContext; 18 | import org.springframework.context.annotation.Lazy; 19 | import org.springframework.stereotype.Component; 20 | import org.springframework.util.ClassUtils; 21 | 22 | import java.lang.reflect.InvocationTargetException; 23 | import java.lang.reflect.Method; 24 | 25 | @Component 26 | @Aspect 27 | public class FlipBeanAdvice { 28 | 29 | private ApplicationContext applicationContext; 30 | 31 | private FlipAnnotationsStore flipAnnotationsStore; 32 | 33 | private static final Logger logger = LoggerFactory.getLogger(FlipBeanAdvice.class); 34 | 35 | @Autowired 36 | public FlipBeanAdvice(ApplicationContext applicationContext, @Lazy FlipAnnotationsStore flipAnnotationsStore) { 37 | this.applicationContext = applicationContext; 38 | this.flipAnnotationsStore = flipAnnotationsStore; 39 | } 40 | 41 | @Pointcut("@annotation(org.flips.annotation.FlipBean)") 42 | private void flipBeanPointcut(){} 43 | 44 | @Around("flipBeanPointcut()") 45 | public Object inspectFlips(ProceedingJoinPoint joinPoint) throws Throwable { 46 | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 47 | Method method = signature.getMethod(); 48 | FlipBean annotation = AnnotationUtils.getAnnotation(method, FlipBean.class); 49 | Class tobeFlippedWith = annotation.with(); 50 | 51 | if ( shouldFlipBean(method, tobeFlippedWith) ) { 52 | Method targetMethod = getMethodOnTargetBean(method, tobeFlippedWith); 53 | logger.info("Flipping {} of {} with {} of {}", method.getName(), method.getDeclaringClass().getName(), targetMethod.getName(), targetMethod.getDeclaringClass().getName()); 54 | return invokeMethod(joinPoint, tobeFlippedWith, targetMethod); 55 | } 56 | 57 | return joinPoint.proceed(); 58 | } 59 | 60 | private boolean shouldFlipBean(Method method, Class tobeFlippedWith) { 61 | return flipAnnotationsStore.isFeatureEnabled(method) && 62 | (tobeFlippedWith != method.getDeclaringClass()); 63 | } 64 | 65 | private Method getMethodOnTargetBean(Method method, Class tobeFlippedWith) { 66 | try{ 67 | return ClassUtils.getMethod(tobeFlippedWith, method.getName(), method.getParameterTypes()); 68 | } 69 | catch (IllegalStateException e){ 70 | throw new FlipBeanFailedException("Could not invoke " + method.getName() + " with parameters " + method.getParameterTypes() + " on class " + tobeFlippedWith, e); 71 | } 72 | } 73 | 74 | private Object invokeMethod(ProceedingJoinPoint joinPoint, Class tobeFlippedWith, Method targetMethod) throws Throwable { 75 | try{ 76 | return Utils.invokeMethod(targetMethod, applicationContext.getBean(tobeFlippedWith), joinPoint.getArgs()); 77 | } 78 | catch (InvocationTargetException ex){ 79 | if ( ex.getCause() instanceof FeatureNotEnabledException ){ 80 | throw ex.getCause(); 81 | }else{ 82 | throw new FlipBeanFailedException(ex); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/advice/FlipFeatureAdvice.java: -------------------------------------------------------------------------------- 1 | package org.flips.advice; 2 | 3 | import org.aspectj.lang.JoinPoint; 4 | import org.aspectj.lang.annotation.Aspect; 5 | import org.aspectj.lang.annotation.Before; 6 | import org.aspectj.lang.annotation.Pointcut; 7 | import org.aspectj.lang.reflect.MethodSignature; 8 | import org.flips.exception.FeatureNotEnabledException; 9 | import org.flips.store.FlipAnnotationsStore; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.annotation.Lazy; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | @Component 17 | @Aspect 18 | public class FlipFeatureAdvice { 19 | 20 | private FlipAnnotationsStore flipAnnotationsStore; 21 | 22 | @Autowired 23 | public FlipFeatureAdvice(@Lazy FlipAnnotationsStore flipAnnotationsStore) { 24 | this.flipAnnotationsStore = flipAnnotationsStore; 25 | } 26 | 27 | @Pointcut("execution(@(@org.flips.annotation.FlipOnOff *) * *(..)) && !@annotation(org.flips.annotation.FlipBean)") 28 | private void featureToInspectPointcut(){} 29 | 30 | @Before("featureToInspectPointcut()") 31 | public void inspectFlips(JoinPoint joinPoint) throws Throwable { 32 | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 33 | Method method = signature.getMethod(); 34 | 35 | this.ensureFeatureIsEnabled(method); 36 | } 37 | 38 | private void ensureFeatureIsEnabled(Method method) { 39 | boolean featureEnabled = flipAnnotationsStore.isFeatureEnabled(method); 40 | if ( !featureEnabled ) 41 | throw new FeatureNotEnabledException("Feature not enabled, identified by method " + method, method); 42 | } 43 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipBean.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface FlipBean { 11 | 12 | Class with(); 13 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipOff.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import org.flips.condition.FlipOffCondition; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.METHOD}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @FlipOnOff(value = FlipOffCondition.class) 13 | public @interface FlipOff { 14 | } 15 | -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipOnDateTime.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import org.flips.condition.DateTimeFlipCondition; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.METHOD}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @FlipOnOff(value = DateTimeFlipCondition.class) 13 | public @interface FlipOnDateTime { 14 | 15 | String cutoffDateTimeProperty(); 16 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipOnDaysOfWeek.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import org.flips.condition.DayOfWeekFlipCondition; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | import java.time.DayOfWeek; 10 | 11 | @Target({ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @FlipOnOff(value = DayOfWeekFlipCondition.class) 14 | public @interface FlipOnDaysOfWeek { 15 | 16 | DayOfWeek[] daysOfWeek(); 17 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipOnEnvironmentProperty.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import org.flips.condition.SpringEnvironmentPropertyFlipCondition; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.METHOD}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @FlipOnOff(value = SpringEnvironmentPropertyFlipCondition.class) 13 | public @interface FlipOnEnvironmentProperty { 14 | 15 | String property(); 16 | String expectedValue() default "true"; 17 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipOnOff.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import org.flips.condition.FlipCondition; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.ANNOTATION_TYPE) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | public @interface FlipOnOff { 10 | 11 | Class value(); 12 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipOnProfiles.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import org.flips.condition.SpringProfileFlipCondition; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.METHOD}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @FlipOnOff(value = SpringProfileFlipCondition.class) 13 | public @interface FlipOnProfiles { 14 | 15 | String[] activeProfiles(); 16 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/annotation/FlipOnSpringExpression.java: -------------------------------------------------------------------------------- 1 | package org.flips.annotation; 2 | 3 | import org.flips.condition.SpringExpressionFlipCondition; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.METHOD}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @FlipOnOff(value = SpringExpressionFlipCondition.class) 13 | public @interface FlipOnSpringExpression { 14 | 15 | String expression(); 16 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/condition/DateTimeFlipCondition.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.flips.utils.DateTimeUtils; 6 | import org.flips.utils.ValidationUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.time.OffsetDateTime; 12 | import java.time.ZonedDateTime; 13 | import java.time.format.DateTimeParseException; 14 | 15 | @Component 16 | public class DateTimeFlipCondition implements FlipCondition { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(DateTimeFlipCondition.class); 19 | 20 | @Override 21 | public boolean evaluateCondition(FeatureContext featureContext, 22 | FlipAnnotationAttributes flipAnnotationAttributes) { 23 | 24 | String dateTimeProperty = flipAnnotationAttributes.getAttributeValue("cutoffDateTimeProperty", ""); 25 | ValidationUtils.requireNonEmpty(dateTimeProperty, "cutoffDateTimeProperty element can not be NULL or EMPTY when using @FlipOnDateTime"); 26 | 27 | String dateTime = featureContext.getPropertyValueOrDefault(dateTimeProperty, String.class, ""); 28 | ValidationUtils.requireNonEmpty(dateTime, dateTimeProperty + " containing datetime can not be NULL or EMPTY when using @FlipOnDateTime"); 29 | 30 | return isCurrentDateTimeAfterOrEqualCutoffDateTime(getCutoffDateTime(dateTime), DateTimeUtils.getCurrentTime()); 31 | } 32 | 33 | private boolean isCurrentDateTimeAfterOrEqualCutoffDateTime(ZonedDateTime cutoffDateTime, ZonedDateTime currentUtcTime) { 34 | logger.info("DateTimeFlipCondition: cutoffDateTime {}, currentUtcTime {}", cutoffDateTime, currentUtcTime); 35 | return currentUtcTime.isEqual(cutoffDateTime) || currentUtcTime.isAfter(cutoffDateTime); 36 | } 37 | 38 | private ZonedDateTime getCutoffDateTime(String datetime){ 39 | logger.info("DateTimeFlipCondition: parsing {}", datetime); 40 | try{ 41 | return OffsetDateTime.parse(datetime).atZoneSameInstant(DateTimeUtils.UTC); 42 | } 43 | catch (DateTimeParseException e){ 44 | logger.error("Could not parse " + datetime + ", expected format yyyy-MM-ddTHH:mm:ssZ"); 45 | throw e; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/condition/DayOfWeekFlipCondition.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.flips.utils.DateTimeUtils; 6 | import org.flips.utils.Utils; 7 | import org.flips.utils.ValidationUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.time.DayOfWeek; 13 | import java.util.Arrays; 14 | 15 | @Component 16 | public class DayOfWeekFlipCondition implements FlipCondition { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(DayOfWeekFlipCondition.class); 19 | 20 | @Override 21 | public boolean evaluateCondition(FeatureContext featureContext, FlipAnnotationAttributes flipAnnotationAttributes) { 22 | DayOfWeek[] enabledOnDaysOfWeek = (DayOfWeek[])flipAnnotationAttributes.getAttributeValue("daysOfWeek", Utils.emptyArray(DayOfWeek.class)); 23 | DayOfWeek currentDay = DateTimeUtils.getDayOfWeek(); 24 | 25 | ValidationUtils.requireNonEmpty(enabledOnDaysOfWeek, "daysOfWeek element can not be NULL or EMPTY when using @FlipOnDaysOfWeek"); 26 | 27 | logger.info("DayOfWeekFlipCondition: Enabled on days {}, current day {}", enabledOnDaysOfWeek, currentDay); 28 | return Arrays.asList(enabledOnDaysOfWeek).contains(currentDay); 29 | } 30 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/condition/FlipCondition.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | 6 | public interface FlipCondition { 7 | 8 | boolean evaluateCondition(FeatureContext featureContext, 9 | FlipAnnotationAttributes flipAnnotationAttributes); 10 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/condition/FlipOffCondition.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class FlipOffCondition implements FlipCondition { 9 | 10 | @Override 11 | public boolean evaluateCondition(FeatureContext featureContext, FlipAnnotationAttributes flipAnnotationAttributes) { 12 | return false; 13 | } 14 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/condition/SpringEnvironmentPropertyFlipCondition.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.flips.utils.ValidationUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class SpringEnvironmentPropertyFlipCondition implements FlipCondition { 12 | 13 | private static final Logger logger = LoggerFactory.getLogger(SpringEnvironmentPropertyFlipCondition.class); 14 | 15 | @Override 16 | public boolean evaluateCondition(FeatureContext featureContext, 17 | FlipAnnotationAttributes flipAnnotationAttributes) { 18 | 19 | String property = flipAnnotationAttributes.getAttributeValue("property", ""); 20 | String expectedValue = flipAnnotationAttributes.getAttributeValue("expectedValue", ""); 21 | 22 | ValidationUtils.requireNonEmpty(property, "property element can not be NULL or EMPTY when using @FlipOnEnvironmentProperty"); 23 | ValidationUtils.requireNonEmpty(expectedValue, "expectedValue element can not be NULL or EMPTY when using @FlipOnEnvironmentProperty"); 24 | 25 | String propertyValue = featureContext.getPropertyValueOrDefault(property, String.class, ""); 26 | logger.info("SpringEnvironmentPropertyFlipCondition: property {}, expectedValue {}, actualValue {}", property, expectedValue, propertyValue); 27 | 28 | return propertyValue.equalsIgnoreCase(expectedValue); 29 | } 30 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/condition/SpringExpressionFlipCondition.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.exception.SpringExpressionEvaluationFailureException; 4 | import org.flips.model.FeatureContext; 5 | import org.flips.model.FlipAnnotationAttributes; 6 | import org.flips.utils.ValidationUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.expression.EvaluationContext; 10 | import org.springframework.expression.Expression; 11 | import org.springframework.expression.ExpressionParser; 12 | import org.springframework.expression.spel.SpelEvaluationException; 13 | import org.springframework.stereotype.Component; 14 | 15 | @Component 16 | public class SpringExpressionFlipCondition implements FlipCondition { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(SpringExpressionFlipCondition.class); 19 | 20 | @Override 21 | public boolean evaluateCondition(FeatureContext featureContext, 22 | FlipAnnotationAttributes flipAnnotationAttributes) { 23 | 24 | String expression = flipAnnotationAttributes.getAttributeValue("expression", ""); 25 | ValidationUtils.requireNonEmpty(expression, "expression element can not be NULL or EMPTY when using @FlipOnSpringExpression"); 26 | 27 | return evaluateExpression(featureContext, expression); 28 | } 29 | 30 | private boolean evaluateExpression(FeatureContext featureContext, String expression){ 31 | ExpressionParser parser = featureContext.getExpressionParser(); 32 | EvaluationContext context = featureContext.getEvaluationContext(); 33 | Expression parsedExpression = parser.parseExpression(expression); 34 | Object value = null; 35 | try{ 36 | value = parsedExpression.getValue(context); 37 | logger.info("SpringExpressionFlipCondition: Evaluated expression {} to {}", expression, value); 38 | }catch (SpelEvaluationException e){ 39 | throw new SpringExpressionEvaluationFailureException("Evaluation failed for " + expression + ", returned value " + value, e); 40 | } 41 | if ( value != null && !Boolean.class.isInstance(value) ) 42 | throw new SpringExpressionEvaluationFailureException("Can not convert " + value + " to boolean from expression " + expression); 43 | 44 | return value == null ? false : (boolean)value; 45 | } 46 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/condition/SpringProfileFlipCondition.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.flips.utils.Utils; 6 | import org.flips.utils.ValidationUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.util.CollectionUtils; 11 | 12 | import static java.util.Arrays.asList; 13 | 14 | @Component 15 | public class SpringProfileFlipCondition implements FlipCondition { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(SpringProfileFlipCondition.class); 18 | 19 | @Override 20 | public boolean evaluateCondition(FeatureContext featureContext, FlipAnnotationAttributes flipAnnotationAttributes) { 21 | String[] expectedProfiles = flipAnnotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY); 22 | String[] activeProfiles = featureContext.getActiveProfilesOrEmpty(); 23 | 24 | ValidationUtils.requireNonEmpty(expectedProfiles, "activeProfiles element can not be NULL or EMPTY when using @FlipOnProfiles"); 25 | return isAnyActiveProfileContainedInExpectedProfile(expectedProfiles, activeProfiles); 26 | } 27 | 28 | private boolean isAnyActiveProfileContainedInExpectedProfile(String[] expectedProfiles, String[] activeProfiles) { 29 | logger.info("SpringProfileFlipCondition: Expected profile(s) {}, active profile(s) {}", expectedProfiles, activeProfiles); 30 | return CollectionUtils.containsAny(asList(activeProfiles), asList(expectedProfiles)); 31 | } 32 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/config/FlipContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.flips.config; 2 | 3 | import org.springframework.context.annotation.*; 4 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 5 | 6 | @Configuration 7 | @EnableAspectJAutoProxy 8 | @ComponentScan(basePackages = "org.flips") 9 | @PropertySource("classpath:org/flips/application.properties") 10 | public class FlipContextConfiguration { 11 | 12 | @Bean 13 | public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { 14 | return new PropertySourcesPlaceholderConfigurer(); 15 | } 16 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/exception/FeatureNotEnabledException.java: -------------------------------------------------------------------------------- 1 | package org.flips.exception; 2 | 3 | import org.flips.model.Feature; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | public class FeatureNotEnabledException extends RuntimeException { 8 | private Feature feature; 9 | 10 | public FeatureNotEnabledException(String message, Method method) { 11 | super(message); 12 | this.feature = new Feature(method.getName(), method.getDeclaringClass().getName()); 13 | } 14 | 15 | public Feature getFeature(){ 16 | return feature; 17 | } 18 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/exception/FlipBeanFailedException.java: -------------------------------------------------------------------------------- 1 | package org.flips.exception; 2 | 3 | public class FlipBeanFailedException extends RuntimeException{ 4 | 5 | public FlipBeanFailedException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | 9 | public FlipBeanFailedException(Throwable cause) { 10 | super(cause); 11 | } 12 | 13 | public FlipBeanFailedException(String message) { 14 | super(message); 15 | } 16 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/exception/SpringExpressionEvaluationFailureException.java: -------------------------------------------------------------------------------- 1 | package org.flips.exception; 2 | 3 | public class SpringExpressionEvaluationFailureException extends RuntimeException { 4 | 5 | public SpringExpressionEvaluationFailureException(String message, Throwable cause) { 6 | super(message, cause); 7 | } 8 | 9 | public SpringExpressionEvaluationFailureException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/DefaultFlipConditionEvaluator.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.flips.annotation.FlipOnOff; 4 | import org.flips.condition.FlipCondition; 5 | import org.flips.utils.AnnotationUtils; 6 | import org.flips.utils.ValidationUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.context.ApplicationContext; 10 | 11 | import java.lang.annotation.Annotation; 12 | import java.util.LinkedHashMap; 13 | import java.util.Map; 14 | 15 | class DefaultFlipConditionEvaluator extends FlipConditionEvaluator { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(DefaultFlipConditionEvaluator.class); 18 | 19 | private Map, FlipAnnotationAttributes> flipConditions = new LinkedHashMap<>(); 20 | 21 | protected DefaultFlipConditionEvaluator(ApplicationContext applicationContext, FeatureContext featureContext, Annotation[] annotations) { 22 | super(applicationContext, featureContext); 23 | 24 | ValidationUtils.requireNonNull(annotations, "annotations[] can not be null while constructing DefaultFlipConditionEvaluator"); 25 | buildFlipConditions(annotations); 26 | } 27 | 28 | private void buildFlipConditions(Annotation[] annotations) { 29 | for ( Annotation annotation : annotations ){ 30 | if ( AnnotationUtils.isMetaAnnotationDefined(annotation, FlipOnOff.class) ) { 31 | Class condition = AnnotationUtils.getAnnotationOfType(annotation, FlipOnOff.class).value(); 32 | FlipAnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation); 33 | 34 | flipConditions.put(condition, annotationAttributes); 35 | } 36 | } 37 | logger.debug("Built DefaultFlipConditionEvaluator {}", flipConditions); 38 | } 39 | 40 | private boolean evaluateFlipCondition(ApplicationContext applicationContext, 41 | FeatureContext featureContext, 42 | Class conditionClazz, 43 | FlipAnnotationAttributes flipAnnotationAttributes) { 44 | 45 | return applicationContext.getBean(conditionClazz).evaluateCondition(featureContext, flipAnnotationAttributes); 46 | } 47 | 48 | @Override 49 | public boolean evaluate() { 50 | return flipConditions 51 | .entrySet() 52 | .stream() 53 | .map(entry -> evaluateFlipCondition(applicationContext, featureContext, entry.getKey(), entry.getValue())) 54 | .filter(result -> result == Boolean.FALSE) 55 | .findFirst().orElse(true); 56 | } 57 | 58 | @Override 59 | public boolean isEmpty() { 60 | return flipConditions.isEmpty(); 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | final StringBuilder sb = new StringBuilder("DefaultFlipConditionEvaluator{"); 66 | sb.append("flipConditions=").append(flipConditions); 67 | sb.append('}'); 68 | return sb.toString(); 69 | } 70 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/EmptyFlipConditionEvaluator.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | 5 | class EmptyFlipConditionEvaluator extends FlipConditionEvaluator { 6 | 7 | protected EmptyFlipConditionEvaluator(ApplicationContext applicationContext, FeatureContext featureContext) { 8 | super(applicationContext, featureContext); 9 | } 10 | 11 | @Override 12 | public boolean evaluate() { 13 | return true; 14 | } 15 | 16 | @Override 17 | public boolean isEmpty() { 18 | return true; 19 | } 20 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/Feature.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | public class Feature { 4 | 5 | private String featureName; 6 | private String className; 7 | 8 | public Feature(String featureName, String className) { 9 | this.featureName = featureName; 10 | this.className = className; 11 | } 12 | 13 | public String getFeatureName(){ 14 | return featureName; 15 | } 16 | 17 | public String getClassName(){ 18 | return className; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/FeatureContext.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.flips.utils.Utils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.core.env.Environment; 8 | import org.springframework.expression.EvaluationContext; 9 | import org.springframework.expression.ExpressionParser; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | public class FeatureContext { 14 | 15 | private Environment environment; 16 | private FeatureExpressionContext featureExpressionContext; 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(FeatureContext.class); 19 | 20 | @Autowired 21 | public FeatureContext(Environment environment, FeatureExpressionContext featureExpressionContext) { 22 | this.environment = environment; 23 | this.featureExpressionContext = featureExpressionContext; 24 | } 25 | 26 | public T getPropertyValueOrDefault(String property, Class t, T defaultValue) { 27 | logger.debug("Getting String property {}", property); 28 | return environment.getProperty(property, t, defaultValue); 29 | } 30 | 31 | public String[] getActiveProfilesOrEmpty(){ 32 | String[] activeProfiles = environment.getActiveProfiles(); 33 | logger.debug("Getting active profiles {}", activeProfiles); 34 | 35 | return Utils.isEmpty(activeProfiles) ? Utils.EMPTY_STRING_ARRAY : activeProfiles; 36 | } 37 | 38 | public ExpressionParser getExpressionParser(){ 39 | return featureExpressionContext.getExpressionParser(); 40 | } 41 | 42 | public EvaluationContext getEvaluationContext(){ 43 | return featureExpressionContext.getEvaluationContext(); 44 | } 45 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/FeatureExpressionContext.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.springframework.beans.factory.BeanFactory; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.expression.BeanExpressionContextAccessor; 6 | import org.springframework.context.expression.BeanFactoryResolver; 7 | import org.springframework.expression.EvaluationContext; 8 | import org.springframework.expression.ExpressionParser; 9 | import org.springframework.expression.spel.standard.SpelExpressionParser; 10 | import org.springframework.expression.spel.support.StandardEvaluationContext; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class FeatureExpressionContext { 15 | 16 | private BeanFactory beanFactory; 17 | 18 | @Autowired 19 | public FeatureExpressionContext(BeanFactory beanFactory) { 20 | this.beanFactory = beanFactory; 21 | } 22 | 23 | public ExpressionParser getExpressionParser(){ 24 | return new SpelExpressionParser(); 25 | } 26 | 27 | public EvaluationContext getEvaluationContext(){ 28 | StandardEvaluationContext context = new StandardEvaluationContext(); 29 | context.setBeanResolver(new BeanFactoryResolver(beanFactory)); 30 | context.addPropertyAccessor(new BeanExpressionContextAccessor()); 31 | 32 | return context; 33 | } 34 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/FlipAnnotationAttributes.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.flips.utils.ValidationUtils; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | 9 | public class FlipAnnotationAttributes { 10 | 11 | private Map attributes = new HashMap<>(); 12 | 13 | private FlipAnnotationAttributes(Map attributes) { 14 | this.attributes = attributes; 15 | } 16 | 17 | private Optional getAttributeValue(String attributeName) { 18 | ValidationUtils.requireNonEmpty(attributeName, "attributeName can not be NULL or EMPTY"); 19 | return Optional.ofNullable(attributes.get(attributeName)); 20 | } 21 | 22 | public T getAttributeValue(String attributeName, T defaultValue){ 23 | return (T) getAttributeValue(attributeName).orElse(defaultValue); 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | 31 | FlipAnnotationAttributes that = (FlipAnnotationAttributes) o; 32 | return attributes.equals(that.attributes); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | final StringBuilder sb = new StringBuilder("FlipAnnotationAttributes{"); 38 | sb.append("attributes=").append(attributes); 39 | sb.append('}'); 40 | return sb.toString(); 41 | } 42 | 43 | public static class Builder{ 44 | 45 | private Map attributes = new HashMap<>(); 46 | 47 | public Builder addAll(Map newAttributes){ 48 | ValidationUtils.requireNonNull(newAttributes, "attributes to be added in FlipAnnotationAttributes can not be null"); 49 | attributes.putAll(new HashMap<>(newAttributes)); 50 | return this; 51 | } 52 | 53 | public FlipAnnotationAttributes build(){ 54 | return new FlipAnnotationAttributes(attributes); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/FlipConditionEvaluator.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | 5 | public abstract class FlipConditionEvaluator { 6 | 7 | protected final ApplicationContext applicationContext; 8 | protected final FeatureContext featureContext; 9 | 10 | protected FlipConditionEvaluator(ApplicationContext applicationContext, FeatureContext featureContext){ 11 | this.applicationContext = applicationContext; 12 | this.featureContext = featureContext; 13 | } 14 | 15 | public abstract boolean evaluate(); 16 | public abstract boolean isEmpty(); 17 | 18 | @Override 19 | public String toString() { 20 | final StringBuilder sb = new StringBuilder("FlipConditionEvaluator{"); 21 | sb.append(this.getClass().getName()); 22 | sb.append('}'); 23 | return sb.toString(); 24 | } 25 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/model/FlipConditionEvaluatorFactory.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.lang.annotation.Annotation; 11 | import java.util.Arrays; 12 | 13 | @Component 14 | public class FlipConditionEvaluatorFactory { 15 | 16 | private static final Logger logger = LoggerFactory.getLogger(FlipConditionEvaluatorFactory.class); 17 | private static FlipConditionEvaluator emptyFlipConditionEvaluator; 18 | 19 | private FeatureContext featureContext; 20 | private ApplicationContext applicationContext; 21 | 22 | @Autowired 23 | public FlipConditionEvaluatorFactory(ApplicationContext applicationContext, FeatureContext featureContext) { 24 | this.applicationContext = applicationContext; 25 | this.featureContext = featureContext; 26 | } 27 | 28 | @PostConstruct 29 | protected void buildEmptyFlipConditionEvaluator(){ 30 | emptyFlipConditionEvaluator = new EmptyFlipConditionEvaluator(applicationContext, featureContext); 31 | } 32 | 33 | public FlipConditionEvaluator buildFlipConditionEvaluator(Annotation[] annotations){ 34 | logger.debug("Using FlipConditionEvaluatorFactory to build condition Evaluator for {}", Arrays.toString(annotations)); 35 | if ( annotations.length == 0 ) return emptyFlipConditionEvaluator; 36 | return new DefaultFlipConditionEvaluator(applicationContext, featureContext, annotations); 37 | } 38 | 39 | public FlipConditionEvaluator getEmptyFlipConditionEvaluator(){ 40 | return emptyFlipConditionEvaluator; 41 | } 42 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/processor/FlipAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | package org.flips.processor; 2 | 3 | import org.flips.model.FlipConditionEvaluator; 4 | import org.flips.model.FlipConditionEvaluatorFactory; 5 | import org.flips.utils.AnnotationUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.lang.annotation.Annotation; 12 | import java.lang.reflect.Method; 13 | 14 | @Component 15 | public class FlipAnnotationProcessor { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(FlipAnnotationProcessor.class); 18 | 19 | private FlipConditionEvaluatorFactory flipConditionEvaluatorFactory; 20 | 21 | @Autowired 22 | public FlipAnnotationProcessor(FlipConditionEvaluatorFactory flipConditionEvaluatorFactory) { 23 | this.flipConditionEvaluatorFactory = flipConditionEvaluatorFactory; 24 | } 25 | 26 | public FlipConditionEvaluator getFlipConditionEvaluator(Method method){ 27 | return getFlipConditionEvaluatorOnMethod(method); 28 | } 29 | 30 | private FlipConditionEvaluator getFlipConditionEvaluatorOnMethod(Method method) { 31 | logger.debug("Getting feature condition evaluator at Method level {}", method.getName()); 32 | 33 | Annotation[] annotations = AnnotationUtils.getAnnotations(method); 34 | return buildFlipConditionEvaluator(annotations); 35 | } 36 | 37 | private FlipConditionEvaluator buildFlipConditionEvaluator(Annotation[] annotations) { 38 | return flipConditionEvaluatorFactory.buildFlipConditionEvaluator(annotations); 39 | } 40 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/store/FlipAnnotationsStore.java: -------------------------------------------------------------------------------- 1 | package org.flips.store; 2 | 3 | import org.flips.model.FlipConditionEvaluator; 4 | import org.flips.model.FlipConditionEvaluatorFactory; 5 | import org.flips.processor.FlipAnnotationProcessor; 6 | import org.flips.utils.Utils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.aop.framework.AopProxyUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.stereotype.Component; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.lang.reflect.Method; 17 | import java.util.*; 18 | 19 | import static java.util.stream.Collectors.toList; 20 | import static java.util.stream.Collectors.toMap; 21 | 22 | @Component 23 | public class FlipAnnotationsStore { 24 | 25 | private static final Logger logger = LoggerFactory.getLogger(FlipAnnotationsStore.class); 26 | 27 | private final Map store = new HashMap<>(); 28 | private final List excludedPackages; 29 | 30 | 31 | private ApplicationContext applicationContext; 32 | private FlipAnnotationProcessor flipAnnotationProcessor; 33 | private FlipConditionEvaluatorFactory flipConditionEvaluatorFactory; 34 | 35 | @Autowired 36 | public FlipAnnotationsStore(ApplicationContext applicationContext, 37 | FlipAnnotationProcessor flipAnnotationProcessor, 38 | FlipConditionEvaluatorFactory flipConditionEvaluatorFactory, 39 | @Value("${exclude.package.scan}") String excludePackagesToScan) { 40 | 41 | this.applicationContext = applicationContext; 42 | this.flipAnnotationProcessor = flipAnnotationProcessor; 43 | this.flipConditionEvaluatorFactory = flipConditionEvaluatorFactory; 44 | this.excludedPackages = Arrays.asList(excludePackagesToScan.split(",")); 45 | } 46 | 47 | @PostConstruct 48 | protected void buildFlipAnnotationsStore(){ 49 | String[] flipComponents = getFlipComponents(); 50 | if ( flipComponents.length != 0 ) { 51 | Map flipConditionEvaluatorMap = 52 | Arrays.stream(flipComponents) 53 | .flatMap(beanDefinition -> getAllMethodsWithConditionEvaluator(AopProxyUtils.ultimateTargetClass(applicationContext.getBean(beanDefinition))).stream()) 54 | .collect(toMap(MethodConditionEvaluator::getMethod, MethodConditionEvaluator::getFlipConditionEvaluator)); 55 | 56 | store.putAll(flipConditionEvaluatorMap); 57 | } 58 | logger.debug("Completed building FlipAnnotationsStore {}", store); 59 | } 60 | 61 | public boolean isFeatureEnabled(Method method) { 62 | return store 63 | .getOrDefault(method, flipConditionEvaluatorFactory.getEmptyFlipConditionEvaluator()) 64 | .evaluate(); 65 | } 66 | 67 | public int getTotalMethodsCached(){ 68 | return store.size(); 69 | } 70 | 71 | public Set allMethodsCached() { 72 | return new HashSet<>(store.keySet()); 73 | } 74 | 75 | private List getAllMethodsWithConditionEvaluator(Class clazz){ 76 | if ( !isPackageExcludedFromScan(clazz.getPackage().getName()) ) { 77 | logger.debug("Scanning class {} for flip annotations", clazz.getName()); 78 | return Arrays.stream (clazz.getDeclaredMethods()) 79 | .map (Utils::getAccessibleMethod) 80 | .filter (accessibleMethod -> accessibleMethod != null) 81 | .map (accessibleMethod -> new MethodConditionEvaluator(accessibleMethod, flipAnnotationProcessor.getFlipConditionEvaluator(accessibleMethod))) 82 | .filter (methodConditionEvaluator -> !methodConditionEvaluator.getFlipConditionEvaluator().isEmpty()) 83 | .collect (toList()); 84 | } 85 | return Collections.emptyList(); 86 | } 87 | 88 | private boolean isPackageExcludedFromScan(String packageName){ 89 | return excludedPackages 90 | .stream() 91 | .filter(excludedPackage -> packageName.startsWith(excludedPackage)) 92 | .map (pkg -> true) 93 | .findFirst() 94 | .orElse(false); 95 | } 96 | 97 | private String[] getFlipComponents() { 98 | return applicationContext.getBeanDefinitionNames(); 99 | } 100 | 101 | static class MethodConditionEvaluator { 102 | private Method method; 103 | private FlipConditionEvaluator flipConditionEvaluator; 104 | 105 | public MethodConditionEvaluator(Method method, FlipConditionEvaluator flipConditionEvaluator) { 106 | this.method = method; 107 | this.flipConditionEvaluator = flipConditionEvaluator; 108 | } 109 | 110 | public Method getMethod() { 111 | return method; 112 | } 113 | 114 | public FlipConditionEvaluator getFlipConditionEvaluator() { 115 | return flipConditionEvaluator; 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/utils/AnnotationUtils.java: -------------------------------------------------------------------------------- 1 | package org.flips.utils; 2 | 3 | import org.flips.model.FlipAnnotationAttributes; 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.lang.reflect.Method; 7 | 8 | public final class AnnotationUtils { 9 | 10 | private AnnotationUtils() { 11 | throw new AssertionError("No AnnotationUtils instances for you!"); 12 | } 13 | 14 | public static T getAnnotationOfType(Annotation annotation, Class cls) { 15 | return org.springframework.core.annotation.AnnotationUtils.getAnnotation(annotation, cls); 16 | } 17 | 18 | public static T findAnnotationByTypeIfAny(Annotation[] annotations, Class cls){ 19 | for ( Annotation a : annotations ){ 20 | T annotation = getAnnotationOfType(a, cls); 21 | if ( annotation != null ) return annotation; 22 | } 23 | return null; 24 | } 25 | 26 | public static boolean isMetaAnnotationDefined(Annotation annotation, Class annotationType) { 27 | return org.springframework.core.annotation.AnnotationUtils.isAnnotationMetaPresent(annotation.getClass(), annotationType); 28 | } 29 | 30 | public static Annotation[] getAnnotations(Method method){ 31 | return org.springframework.core.annotation.AnnotationUtils.getAnnotations(method); 32 | } 33 | 34 | public static Annotation[] getAnnotations(Class clazz){ 35 | return org.springframework.core.annotation.AnnotationUtils.getAnnotations(clazz); 36 | } 37 | 38 | public static T getAnnotation(Method method, Class annotationType){ 39 | return method.getAnnotation(annotationType); 40 | } 41 | 42 | public static FlipAnnotationAttributes getAnnotationAttributes(Annotation annotation) { 43 | return new FlipAnnotationAttributes.Builder().addAll(org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes(annotation)).build(); 44 | } 45 | 46 | public static T findAnnotation(Class clazz, Class annotationType) { 47 | return org.springframework.core.annotation.AnnotationUtils.findAnnotation(clazz, annotationType); 48 | } 49 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/utils/DateTimeUtils.java: -------------------------------------------------------------------------------- 1 | package org.flips.utils; 2 | 3 | import java.time.DayOfWeek; 4 | import java.time.ZoneId; 5 | import java.time.ZonedDateTime; 6 | 7 | public final class DateTimeUtils { 8 | 9 | public static final ZoneId UTC = ZoneId.of("UTC"); 10 | 11 | private DateTimeUtils() { 12 | throw new AssertionError("No DateTimeUtils instances for you!"); 13 | } 14 | 15 | public static ZonedDateTime getCurrentTime(){ 16 | return ZonedDateTime.now(UTC); 17 | } 18 | 19 | public static DayOfWeek getDayOfWeek(){ 20 | return getCurrentTime().getDayOfWeek(); 21 | } 22 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package org.flips.utils; 2 | 3 | import org.springframework.util.ObjectUtils; 4 | import org.springframework.util.StringUtils; 5 | 6 | import java.lang.reflect.Array; 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Modifier; 10 | 11 | public final class Utils { 12 | 13 | public static final String[] EMPTY_STRING_ARRAY = new String[0]; 14 | 15 | private Utils() { 16 | throw new AssertionError("No Utils instances for you!"); 17 | } 18 | 19 | public static boolean isEmpty(Object[] array){ 20 | return ObjectUtils.isEmpty(array); 21 | } 22 | 23 | public static boolean isEmpty(String str){ 24 | return StringUtils.isEmpty(str); 25 | } 26 | 27 | public static Object emptyArray(Class clazz){ 28 | return Array.newInstance(clazz, 0); 29 | } 30 | 31 | public static Method getAccessibleMethod(Method method) { 32 | if ( Modifier.isPublic(method.getModifiers()) ) return method; 33 | else return null; 34 | } 35 | 36 | public static Object invokeMethod(Method method, Object obj, Object... args) throws InvocationTargetException, IllegalAccessException { 37 | return method.invoke(obj, args); 38 | } 39 | } -------------------------------------------------------------------------------- /flips-core/src/main/java/org/flips/utils/ValidationUtils.java: -------------------------------------------------------------------------------- 1 | package org.flips.utils; 2 | 3 | 4 | public final class ValidationUtils { 5 | 6 | private ValidationUtils() { 7 | throw new AssertionError("No ValidationUtils instances for you!"); 8 | } 9 | 10 | public static final String requireNonEmpty(String str, String message) { 11 | boolean isEmpty = Utils.isEmpty(str); 12 | if ( isEmpty ) 13 | throw new IllegalArgumentException(message); 14 | return str; 15 | } 16 | 17 | public static final T[] requireNonEmpty(T[] t, String message) { 18 | if ( Utils.isEmpty(t) ) 19 | throw new IllegalArgumentException(message); 20 | return t; 21 | } 22 | 23 | public static final Object requireNonNull(Object o, String message) { 24 | if ( o == null ) 25 | throw new IllegalArgumentException(message); 26 | return o; 27 | } 28 | } -------------------------------------------------------------------------------- /flips-core/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /flips-core/src/main/resources/org/flips/application.properties: -------------------------------------------------------------------------------- 1 | exclude.package.scan=org.springframework -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/advice/FlipBeanAdviceIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.advice; 2 | 3 | import org.flips.config.FlipContextConfiguration; 4 | import org.flips.exception.FeatureNotEnabledException; 5 | import org.flips.exception.FlipBeanFailedException; 6 | import org.flips.fixture.TestClientFlipBeanSpringComponentSource; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.TestPropertySource; 13 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @ContextConfiguration(classes = FlipContextConfiguration.class) 19 | @ActiveProfiles("dev") 20 | @TestPropertySource(properties = {"flip.bean=1"}) 21 | public class FlipBeanAdviceIntegrationTest { 22 | 23 | @Autowired 24 | private TestClientFlipBeanSpringComponentSource testClientFlipBeanSpringComponentSource; 25 | 26 | @Test 27 | public void shouldInvokeMethodTargetClassGivenFlipBeanAnnotationIsProvided(){ 28 | String output = testClientFlipBeanSpringComponentSource.map("flip bean operation"); 29 | assertEquals("flip bean operation:TARGET", output); 30 | } 31 | 32 | @Test(expected = FeatureNotEnabledException.class) 33 | public void shouldThrowFlipBeanFailedExceptionGivenMethodOnTargetIsFlippedOff(){ 34 | testClientFlipBeanSpringComponentSource.currentDate(); 35 | } 36 | 37 | @Test(expected = FlipBeanFailedException.class) 38 | public void shouldThrowFlipBeanFailedExceptionGivenMethodWithTheSameNameAndParameterTypesIsNotPresentInTarget(){ 39 | testClientFlipBeanSpringComponentSource.nextDate(); 40 | } 41 | 42 | @Test(expected = FlipBeanFailedException.class) 43 | public void shouldThrowFlipBeanFailedExceptionGivenMethodWithTheSameNameAndParameterTypesIsNotAccessibleInTarget(){ 44 | testClientFlipBeanSpringComponentSource.previousDate(); 45 | } 46 | 47 | @Test 48 | public void shouldInvokeMethodTargetClassGivenFlipBeanAnnotationWithConditionEvaluatingToTrueIsProvided(){ 49 | String output = testClientFlipBeanSpringComponentSource.changeCase("InPuT"); 50 | assertEquals("input", output); 51 | } 52 | 53 | @Test 54 | public void shouldInvokeMethodTargetClassGivenFlipBeanAnnotationWithMultipleConditionsEvaluatingToTrueIsProvided(){ 55 | String output = testClientFlipBeanSpringComponentSource.html(); 56 | assertEquals("", output); 57 | } 58 | 59 | @Test 60 | public void shouldNotInvokeMethodTargetClassGivenFlipBeanAnnotationWithConditionEvaluatingToFalseIsProvided(){ 61 | String output = testClientFlipBeanSpringComponentSource.identity("identity"); 62 | assertEquals("identity", output); 63 | } 64 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/advice/FlipBeanAdviceUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.advice; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.reflect.MethodSignature; 5 | import org.flips.annotation.FlipBean; 6 | import org.flips.exception.FeatureNotEnabledException; 7 | import org.flips.exception.FlipBeanFailedException; 8 | import org.flips.fixture.TestClientFlipBeanSpringComponentSource; 9 | import org.flips.fixture.TestClientFlipBeanSpringComponentTarget; 10 | import org.flips.store.FlipAnnotationsStore; 11 | import org.flips.utils.AnnotationUtils; 12 | import org.flips.utils.Utils; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.mockito.InjectMocks; 16 | import org.mockito.Mock; 17 | import org.powermock.api.mockito.PowerMockito; 18 | import org.powermock.core.classloader.annotations.PrepareForTest; 19 | import org.powermock.modules.junit4.PowerMockRunner; 20 | import org.springframework.context.ApplicationContext; 21 | 22 | import java.lang.reflect.InvocationTargetException; 23 | import java.lang.reflect.Method; 24 | 25 | import static org.mockito.Matchers.any; 26 | import static org.mockito.Mockito.*; 27 | 28 | @RunWith(PowerMockRunner.class) 29 | @PrepareForTest({AnnotationUtils.class, Utils.class}) 30 | public class FlipBeanAdviceUnitTest { 31 | 32 | @InjectMocks 33 | private FlipBeanAdvice flipBeanAdvice; 34 | 35 | @Mock 36 | private ApplicationContext applicationContext; 37 | 38 | @Mock 39 | private FlipAnnotationsStore flipAnnotationsStore; 40 | 41 | @Test 42 | public void shouldNotFlipBeanAsTargetClassToBeFlippedWithIsSameAsSourceClass() throws Throwable { 43 | ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); 44 | MethodSignature signature = mock(MethodSignature.class); 45 | Method method = TestClientFlipBeanSpringComponentSource.class.getMethod("noFlip", String.class); 46 | FlipBean flipBean = mock(FlipBean.class); 47 | Class tobeFlippedWith = TestClientFlipBeanSpringComponentSource.class; 48 | 49 | PowerMockito.mockStatic(AnnotationUtils.class); 50 | 51 | when(joinPoint.getSignature()).thenReturn(signature); 52 | when(signature.getMethod()).thenReturn(method); 53 | when(AnnotationUtils.getAnnotation(method, FlipBean.class)).thenReturn(flipBean); 54 | when(flipBean.with()).thenReturn(tobeFlippedWith); 55 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(true); 56 | 57 | flipBeanAdvice.inspectFlips(joinPoint); 58 | 59 | verify(joinPoint).getSignature(); 60 | verify(signature).getMethod(); 61 | PowerMockito.verifyStatic(); 62 | AnnotationUtils.getAnnotation(method, FlipBean.class); 63 | 64 | verify(flipBean).with(); 65 | verify(joinPoint).proceed(); 66 | verify(applicationContext, never()).getBean(any(Class.class)); 67 | verify(flipAnnotationsStore).isFeatureEnabled(method); 68 | } 69 | 70 | @Test 71 | public void shouldNotFlipBeanAsConditionToFlipBeanEvaluateToFalse() throws Throwable { 72 | ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); 73 | MethodSignature signature = mock(MethodSignature.class); 74 | Method method = TestClientFlipBeanSpringComponentSource.class.getMethod("noFlip", String.class); 75 | FlipBean flipBean = mock(FlipBean.class); 76 | Class tobeFlippedWith = TestClientFlipBeanSpringComponentSource.class; 77 | 78 | PowerMockito.mockStatic(AnnotationUtils.class); 79 | 80 | when(joinPoint.getSignature()).thenReturn(signature); 81 | when(signature.getMethod()).thenReturn(method); 82 | when(AnnotationUtils.getAnnotation(method, FlipBean.class)).thenReturn(flipBean); 83 | when(flipBean.with()).thenReturn(tobeFlippedWith); 84 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(false); 85 | 86 | flipBeanAdvice.inspectFlips(joinPoint); 87 | 88 | verify(joinPoint).getSignature(); 89 | verify(signature).getMethod(); 90 | PowerMockito.verifyStatic(); 91 | AnnotationUtils.getAnnotation(method, FlipBean.class); 92 | 93 | verify(flipBean).with(); 94 | verify(joinPoint).proceed(); 95 | verify(applicationContext, never()).getBean(any(Class.class)); 96 | verify(flipAnnotationsStore).isFeatureEnabled(method); 97 | } 98 | 99 | @Test 100 | public void shouldFlipBeanAsTargetClassToBeFlippedWithIsNotSameAsSourceClass() throws Throwable { 101 | ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); 102 | MethodSignature signature = mock(MethodSignature.class); 103 | Method method = TestClientFlipBeanSpringComponentSource.class.getMethod("map", String.class); 104 | FlipBean flipBean = mock(FlipBean.class); 105 | Class tobeFlippedWith = TestClientFlipBeanSpringComponentTarget.class; 106 | 107 | PowerMockito.mockStatic(AnnotationUtils.class); 108 | 109 | when(joinPoint.getSignature()).thenReturn(signature); 110 | when(signature.getMethod()).thenReturn(method); 111 | when(joinPoint.getArgs()).thenReturn(new Object[]{"Input"}); 112 | when(AnnotationUtils.getAnnotation(method, FlipBean.class)).thenReturn(flipBean); 113 | when(flipBean.with()).thenReturn(tobeFlippedWith); 114 | when(applicationContext.getBean(tobeFlippedWith)).thenReturn(mock(tobeFlippedWith)); 115 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(true); 116 | 117 | flipBeanAdvice.inspectFlips(joinPoint); 118 | 119 | verify(joinPoint).getSignature(); 120 | verify(signature).getMethod(); 121 | PowerMockito.verifyStatic(); 122 | AnnotationUtils.getAnnotation(method, FlipBean.class); 123 | 124 | verify(flipBean).with(); 125 | verify(applicationContext).getBean(tobeFlippedWith); 126 | verify(joinPoint).getArgs(); 127 | verify(joinPoint, never()).proceed(); 128 | verify(flipAnnotationsStore).isFeatureEnabled(method); 129 | } 130 | 131 | @Test(expected = FlipBeanFailedException.class) 132 | public void shouldNotFlipBeanGivenTargetClassDoesNotContainTheMethodToBeInvoked() throws Throwable { 133 | ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); 134 | MethodSignature signature = mock(MethodSignature.class); 135 | Method method = TestClientFlipBeanSpringComponentSource.class.getMethod("nextDate"); 136 | FlipBean flipBean = mock(FlipBean.class); 137 | Class tobeFlippedWith = TestClientFlipBeanSpringComponentTarget.class; 138 | 139 | PowerMockito.mockStatic(AnnotationUtils.class); 140 | 141 | when(joinPoint.getSignature()).thenReturn(signature); 142 | when(signature.getMethod()).thenReturn(method); 143 | when(joinPoint.getArgs()).thenReturn(new Object[]{"Input"}); 144 | when(AnnotationUtils.getAnnotation(method, FlipBean.class)).thenReturn(flipBean); 145 | when(flipBean.with()).thenReturn(tobeFlippedWith); 146 | when(applicationContext.getBean(tobeFlippedWith)).thenReturn(mock(tobeFlippedWith)); 147 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(true); 148 | 149 | flipBeanAdvice.inspectFlips(joinPoint); 150 | } 151 | 152 | @Test(expected = FlipBeanFailedException.class) 153 | public void shouldNotFlipBeanGivenInvocationTargetExceptionIsThrownWhileInvokingMethodOnTargetBean() throws Throwable { 154 | ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); 155 | MethodSignature signature = mock(MethodSignature.class); 156 | Method method = TestClientFlipBeanSpringComponentSource.class.getMethod("currentDate"); 157 | FlipBean flipBean = mock(FlipBean.class); 158 | Class tobeFlippedWith = TestClientFlipBeanSpringComponentTarget.class; 159 | Object bean = mock(tobeFlippedWith); 160 | 161 | PowerMockito.mockStatic(AnnotationUtils.class); 162 | PowerMockito.mockStatic(Utils.class); 163 | 164 | when(joinPoint.getSignature()).thenReturn(signature); 165 | when(signature.getMethod()).thenReturn(method); 166 | when(joinPoint.getArgs()).thenReturn(new Object[]{"Input"}); 167 | when(AnnotationUtils.getAnnotation(method, FlipBean.class)).thenReturn(flipBean); 168 | when(flipBean.with()).thenReturn(tobeFlippedWith); 169 | when(applicationContext.getBean(tobeFlippedWith)).thenReturn(bean); 170 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(true); 171 | PowerMockito.doThrow(new InvocationTargetException(new RuntimeException("test"))).when(Utils.class, "invokeMethod", any(Method.class), any(), any(Object[].class)); 172 | 173 | flipBeanAdvice.inspectFlips(joinPoint); 174 | } 175 | 176 | @Test(expected = FeatureNotEnabledException.class) 177 | public void shouldNotFlipBeanGivenFeatureNotEnabledExceptionIsThrownWhileInvokingMethodOnTargetBean() throws Throwable { 178 | ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); 179 | MethodSignature signature = mock(MethodSignature.class); 180 | Method method = TestClientFlipBeanSpringComponentSource.class.getMethod("currentDate"); 181 | FlipBean flipBean = mock(FlipBean.class); 182 | Class tobeFlippedWith = TestClientFlipBeanSpringComponentTarget.class; 183 | Object bean = mock(tobeFlippedWith); 184 | 185 | PowerMockito.mockStatic(AnnotationUtils.class); 186 | PowerMockito.mockStatic(Utils.class); 187 | 188 | when(joinPoint.getSignature()).thenReturn(signature); 189 | when(signature.getMethod()).thenReturn(method); 190 | when(joinPoint.getArgs()).thenReturn(new Object[]{"Input"}); 191 | when(AnnotationUtils.getAnnotation(method, FlipBean.class)).thenReturn(flipBean); 192 | when(flipBean.with()).thenReturn(tobeFlippedWith); 193 | when(applicationContext.getBean(tobeFlippedWith)).thenReturn(bean); 194 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(true); 195 | PowerMockito.doThrow(new InvocationTargetException(new FeatureNotEnabledException("feature not enabled", method))).when(Utils.class, "invokeMethod", any(Method.class), any(), any(Object[].class)); 196 | 197 | flipBeanAdvice.inspectFlips(joinPoint); 198 | } 199 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/advice/FlipFeatureAdviceIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.advice; 2 | 3 | import org.flips.config.FlipContextConfiguration; 4 | import org.flips.exception.FeatureNotEnabledException; 5 | import org.flips.fixture.TestClientFlipSpringService; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @ContextConfiguration(classes = FlipContextConfiguration.class) 16 | public class FlipFeatureAdviceIntegrationTest { 17 | 18 | @Autowired 19 | private TestClientFlipSpringService testClientFlipSpringService; 20 | 21 | @Test 22 | public void shouldInvokeTheEnabledFeatureSuccessfully(){ 23 | boolean result = testClientFlipSpringService.enabledMethod(); 24 | assertEquals(true, result); 25 | } 26 | 27 | @Test(expected = FeatureNotEnabledException.class) 28 | public void shouldThrowFeatureNotEnabledExceptionGivenFeatureIsDisabled(){ 29 | testClientFlipSpringService.disabledMethod(); 30 | } 31 | 32 | @Test(expected = FeatureNotEnabledException.class) 33 | public void shouldThrowFeatureNotEnabledExceptionGivenFeatureIsDisabledUsingSpringExpression(){ 34 | testClientFlipSpringService.disabledMethodUsingSpringExpression(); 35 | } 36 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/advice/FlipFeatureAdviceUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.advice; 2 | 3 | import org.aspectj.lang.JoinPoint; 4 | import org.aspectj.lang.reflect.MethodSignature; 5 | import org.flips.exception.FeatureNotEnabledException; 6 | import org.flips.store.FlipAnnotationsStore; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.InjectMocks; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | import org.powermock.api.mockito.PowerMockito; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | import static org.mockito.Mockito.*; 17 | 18 | @RunWith(MockitoJUnitRunner.class) 19 | public class FlipFeatureAdviceUnitTest { 20 | 21 | @InjectMocks 22 | private FlipFeatureAdvice flipFeatureAdvice; 23 | 24 | @Mock 25 | private FlipAnnotationsStore flipAnnotationsStore; 26 | 27 | @Test 28 | public void shouldExecuteSuccessfullyGivenFeatureIsEnabled() throws Throwable { 29 | JoinPoint joinPoint = mock(JoinPoint.class); 30 | MethodSignature signature = mock(MethodSignature.class); 31 | Method method = PowerMockito.mock(Method.class); 32 | 33 | when(joinPoint.getSignature()).thenReturn(signature); 34 | when(signature.getMethod()).thenReturn(method); 35 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(true); 36 | 37 | flipFeatureAdvice.inspectFlips(joinPoint); 38 | 39 | verify(joinPoint).getSignature(); 40 | verify(signature).getMethod(); 41 | verify(flipAnnotationsStore).isFeatureEnabled(method); 42 | } 43 | 44 | @Test(expected = FeatureNotEnabledException.class) 45 | public void shouldThrowFeatureNotEnabledExceptionGivenFeatureIsDisabled() throws Throwable { 46 | JoinPoint joinPoint = mock(JoinPoint.class); 47 | MethodSignature signature = mock(MethodSignature.class); 48 | Method method = this.getClass().getMethod("dummyMethod"); 49 | 50 | when(joinPoint.getSignature()).thenReturn(signature); 51 | when(signature.getMethod()).thenReturn(method); 52 | when(flipAnnotationsStore.isFeatureEnabled(method)).thenReturn(false); 53 | 54 | flipFeatureAdvice.inspectFlips(joinPoint); 55 | 56 | verify(joinPoint).getSignature(); 57 | verify(signature).getMethod(); 58 | verify(flipAnnotationsStore).isFeatureEnabled(method); 59 | } 60 | 61 | public void dummyMethod(){} 62 | 63 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/condition/DateTimeFlipConditionUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.flips.utils.DateTimeUtils; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.powermock.api.mockito.PowerMockito; 9 | import org.powermock.core.classloader.annotations.PrepareForTest; 10 | import org.powermock.modules.junit4.PowerMockRunner; 11 | 12 | import java.time.OffsetDateTime; 13 | import java.time.ZonedDateTime; 14 | import java.time.format.DateTimeParseException; 15 | 16 | import static org.flips.utils.DateTimeUtils.UTC; 17 | import static org.junit.Assert.assertEquals; 18 | import static org.mockito.Mockito.*; 19 | 20 | @RunWith(PowerMockRunner.class) 21 | @PrepareForTest(DateTimeUtils.class) 22 | public class DateTimeFlipConditionUnitTest { 23 | 24 | @Test 25 | public void shouldReturnTrueOnMatchingConditionGivenCurrentDateIsGreaterThanCutoffDate(){ 26 | FeatureContext featureContext = mock(FeatureContext.class); 27 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 28 | String cutoffDateTimeProperty = "feature.dev.enabled.date"; 29 | 30 | PowerMockito.mockStatic(DateTimeUtils.class); 31 | 32 | when(flipAnnotationAttributes.getAttributeValue("cutoffDateTimeProperty", "")).thenReturn(cutoffDateTimeProperty); 33 | when(featureContext.getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, "")).thenReturn("2015-02-05T02:05:17+00:00"); 34 | PowerMockito.when(DateTimeUtils.getCurrentTime()).thenReturn(ZonedDateTime.now(UTC)); 35 | 36 | DateTimeFlipCondition condition = new DateTimeFlipCondition(); 37 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 38 | 39 | assertEquals(true, result); 40 | verify(flipAnnotationAttributes).getAttributeValue("cutoffDateTimeProperty", ""); 41 | verify(featureContext).getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, ""); 42 | 43 | PowerMockito.verifyStatic(); 44 | DateTimeUtils.getCurrentTime(); 45 | } 46 | 47 | @Test 48 | public void shouldReturnTrueOnMatchingConditionGivenCurrentDateIsEqualToCutoffDate(){ 49 | FeatureContext featureContext = mock(FeatureContext.class); 50 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 51 | String cutoffDateTimeProperty = "feature.dev.enabled.date"; 52 | 53 | PowerMockito.mockStatic(DateTimeUtils.class); 54 | 55 | when(flipAnnotationAttributes.getAttributeValue("cutoffDateTimeProperty", "")).thenReturn(cutoffDateTimeProperty); 56 | when(featureContext.getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, "")).thenReturn("2015-02-05T02:05:17+00:00"); 57 | PowerMockito.when(DateTimeUtils.getCurrentTime()).thenReturn(OffsetDateTime.parse("2015-02-05T02:05:17+00:00").atZoneSameInstant(DateTimeUtils.UTC)); 58 | 59 | DateTimeFlipCondition condition = new DateTimeFlipCondition(); 60 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 61 | 62 | assertEquals(true, result); 63 | verify(flipAnnotationAttributes).getAttributeValue("cutoffDateTimeProperty", ""); 64 | verify(featureContext).getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, ""); 65 | 66 | PowerMockito.verifyStatic(); 67 | DateTimeUtils.getCurrentTime(); 68 | } 69 | 70 | @Test 71 | public void shouldReturnFalseOnMatchingConditionGivenCurrentDateIsLesserThanCutoffDate(){ 72 | FeatureContext featureContext = mock(FeatureContext.class); 73 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 74 | String cutoffDateTimeProperty = "feature.dev.disabled.date"; 75 | 76 | PowerMockito.mockStatic(DateTimeUtils.class); 77 | 78 | when(flipAnnotationAttributes.getAttributeValue("cutoffDateTimeProperty", "")).thenReturn(cutoffDateTimeProperty); 79 | when(featureContext.getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, "")).thenReturn(ZonedDateTime.now().plusDays(2).toInstant().toString()); 80 | PowerMockito.when(DateTimeUtils.getCurrentTime()).thenReturn(ZonedDateTime.now(UTC)); 81 | 82 | DateTimeFlipCondition condition = new DateTimeFlipCondition(); 83 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 84 | 85 | assertEquals(false, result); 86 | verify(flipAnnotationAttributes).getAttributeValue("cutoffDateTimeProperty", ""); 87 | verify(featureContext).getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, ""); 88 | 89 | PowerMockito.verifyStatic(); 90 | DateTimeUtils.getCurrentTime(); 91 | } 92 | 93 | @Test(expected = DateTimeParseException.class) 94 | public void shouldThrowRuntimeExceptionGivenDateTimeIsProvidedInUnsupportedFormat(){ 95 | FeatureContext featureContext = mock(FeatureContext.class); 96 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 97 | String cutoffDateTimeProperty = "feature.dev.enabled.date"; 98 | 99 | PowerMockito.mockStatic(DateTimeUtils.class); 100 | when(flipAnnotationAttributes.getAttributeValue("cutoffDateTimeProperty", "")).thenReturn(cutoffDateTimeProperty); 101 | when(featureContext.getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, "")).thenReturn("2014-01-02"); 102 | PowerMockito.when(DateTimeUtils.getCurrentTime()).thenReturn(ZonedDateTime.now(UTC)); 103 | 104 | DateTimeFlipCondition condition = new DateTimeFlipCondition(); 105 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 106 | } 107 | 108 | @Test(expected = IllegalArgumentException.class) 109 | public void shouldThrowIllegalArgumentExceptionGivenNoPropertyContainingDateTimeIsProvided(){ 110 | FeatureContext featureContext = mock(FeatureContext.class); 111 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 112 | String cutoffDateTimeProperty = ""; 113 | 114 | when(flipAnnotationAttributes.getAttributeValue("cutoffDateTimeProperty", "")).thenReturn(cutoffDateTimeProperty); 115 | 116 | DateTimeFlipCondition condition = new DateTimeFlipCondition(); 117 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 118 | } 119 | 120 | @Test(expected = IllegalArgumentException.class) 121 | public void shouldThrowIllegalArgumentExceptionGivenPropertyContainingDateTimeIsBlank(){ 122 | FeatureContext featureContext = mock(FeatureContext.class); 123 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 124 | String cutoffDateTimeProperty = "feature.dev.enabled.date"; 125 | 126 | when(flipAnnotationAttributes.getAttributeValue("cutoffDateTimeProperty", "")).thenReturn(cutoffDateTimeProperty); 127 | when(featureContext.getPropertyValueOrDefault(cutoffDateTimeProperty, String.class, "")).thenReturn(""); 128 | 129 | DateTimeFlipCondition condition = new DateTimeFlipCondition(); 130 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 131 | } 132 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/condition/DayOfWeekFlipConditionUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.flips.utils.DateTimeUtils; 6 | import org.flips.utils.Utils; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.powermock.api.mockito.PowerMockito; 10 | import org.powermock.core.classloader.annotations.PrepareForTest; 11 | import org.powermock.modules.junit4.PowerMockRunner; 12 | 13 | import java.time.DayOfWeek; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | import static org.mockito.Mockito.*; 17 | 18 | @RunWith(PowerMockRunner.class) 19 | @PrepareForTest(DateTimeUtils.class) 20 | public class DayOfWeekFlipConditionUnitTest { 21 | 22 | @Test 23 | public void shouldReturnTrueOnMatchingConditionGivenCurrentDayOfWeekIsAmongstSpecifiedDaysOfWeek(){ 24 | FeatureContext featureContext = mock(FeatureContext.class); 25 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 26 | DayOfWeek[] empty = (DayOfWeek[])Utils.emptyArray(DayOfWeek.class); 27 | 28 | PowerMockito.mockStatic(DateTimeUtils.class); 29 | 30 | when(flipAnnotationAttributes.getAttributeValue("daysOfWeek", empty)).thenReturn(new DayOfWeek[]{DayOfWeek.MONDAY, DayOfWeek.TUESDAY}); 31 | PowerMockito.when(DateTimeUtils.getDayOfWeek()).thenReturn(DayOfWeek.MONDAY); 32 | 33 | DayOfWeekFlipCondition condition = new DayOfWeekFlipCondition(); 34 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 35 | 36 | assertEquals(true, result); 37 | verify(flipAnnotationAttributes).getAttributeValue("daysOfWeek", empty); 38 | 39 | PowerMockito.verifyStatic(); 40 | DateTimeUtils.getDayOfWeek(); 41 | } 42 | 43 | @Test 44 | public void shouldReturnFalseOnMatchingConditionGivenCurrentDayOfWeekIsNotAmongstSpecifiedDaysOfWeek(){ 45 | FeatureContext featureContext = mock(FeatureContext.class); 46 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 47 | DayOfWeek[] empty = (DayOfWeek[])Utils.emptyArray(DayOfWeek.class); 48 | 49 | PowerMockito.mockStatic(DateTimeUtils.class); 50 | 51 | when(flipAnnotationAttributes.getAttributeValue("daysOfWeek", empty)).thenReturn(new DayOfWeek[]{DayOfWeek.MONDAY, DayOfWeek.TUESDAY}); 52 | PowerMockito.when(DateTimeUtils.getDayOfWeek()).thenReturn(DayOfWeek.FRIDAY); 53 | 54 | DayOfWeekFlipCondition condition = new DayOfWeekFlipCondition(); 55 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 56 | 57 | assertEquals(false, result); 58 | verify(flipAnnotationAttributes).getAttributeValue("daysOfWeek", empty); 59 | 60 | PowerMockito.verifyStatic(); 61 | DateTimeUtils.getDayOfWeek(); 62 | } 63 | 64 | @Test(expected = IllegalArgumentException.class) 65 | public void shouldThrowIllegalArgumentExceptionGivenEmptyWeekDays(){ 66 | FeatureContext featureContext = mock(FeatureContext.class); 67 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 68 | DayOfWeek[] empty = (DayOfWeek[])Utils.emptyArray(DayOfWeek.class); 69 | 70 | PowerMockito.mockStatic(DateTimeUtils.class); 71 | 72 | when(flipAnnotationAttributes.getAttributeValue("daysOfWeek", empty)).thenReturn(new DayOfWeek[]{}); 73 | PowerMockito.when(DateTimeUtils.getDayOfWeek()).thenReturn(DayOfWeek.FRIDAY); 74 | 75 | DayOfWeekFlipCondition condition = new DayOfWeekFlipCondition(); 76 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 77 | } 78 | 79 | @Test(expected = IllegalArgumentException.class) 80 | public void shouldThrowIllegalArgumentExceptionGivenNullWeekDays(){ 81 | FeatureContext featureContext = mock(FeatureContext.class); 82 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 83 | DayOfWeek[] empty = (DayOfWeek[])Utils.emptyArray(DayOfWeek.class); 84 | 85 | PowerMockito.mockStatic(DateTimeUtils.class); 86 | 87 | when(flipAnnotationAttributes.getAttributeValue("daysOfWeek", empty)).thenReturn(null); 88 | PowerMockito.when(DateTimeUtils.getDayOfWeek()).thenReturn(DayOfWeek.FRIDAY); 89 | 90 | DayOfWeekFlipCondition condition = new DayOfWeekFlipCondition(); 91 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 92 | } 93 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/condition/FlipOffConditionUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | import static org.mockito.Mockito.*; 9 | 10 | public class FlipOffConditionUnitTest { 11 | 12 | @Test 13 | public void shouldReturnFalseGivenFeatureIsFlippedOff(){ 14 | FeatureContext featureContext = mock(FeatureContext.class); 15 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 16 | 17 | FlipOffCondition condition = new FlipOffCondition(); 18 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 19 | 20 | assertEquals(false, result); 21 | } 22 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/condition/SpringEnvironmentPropertyFlipConditionUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | import static org.mockito.Mockito.*; 9 | 10 | public class SpringEnvironmentPropertyFlipConditionUnitTest { 11 | 12 | @Test 13 | public void shouldReturnTrueOnMatchingConditionGivenExpectedValueOfPropertyMatchesPropertyValue(){ 14 | FeatureContext featureContext = mock(FeatureContext.class); 15 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 16 | 17 | when(flipAnnotationAttributes.getAttributeValue("property", "")).thenReturn("feature.email.compose"); 18 | when(flipAnnotationAttributes.getAttributeValue("expectedValue", "")).thenReturn("Y"); 19 | when(featureContext.getPropertyValueOrDefault("feature.email.compose", String.class, "")).thenReturn("Y"); 20 | 21 | SpringEnvironmentPropertyFlipCondition condition = new SpringEnvironmentPropertyFlipCondition(); 22 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 23 | 24 | assertEquals(true, result); 25 | verify(flipAnnotationAttributes).getAttributeValue("property", ""); 26 | verify(flipAnnotationAttributes).getAttributeValue("expectedValue", ""); 27 | verify(featureContext).getPropertyValueOrDefault("feature.email.compose", String.class, ""); 28 | } 29 | 30 | @Test 31 | public void shouldReturnFalseOnMatchingConditionGivenExpectedValueOfPropertyDoesNotMatchPropertyValue(){ 32 | FeatureContext featureContext = mock(FeatureContext.class); 33 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 34 | 35 | when(flipAnnotationAttributes.getAttributeValue("property", "")).thenReturn("feature.email.compose"); 36 | when(flipAnnotationAttributes.getAttributeValue("expectedValue", "")).thenReturn("N"); 37 | when(featureContext.getPropertyValueOrDefault("feature.email.compose", String.class, "")).thenReturn("Y"); 38 | 39 | SpringEnvironmentPropertyFlipCondition condition = new SpringEnvironmentPropertyFlipCondition(); 40 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 41 | 42 | assertEquals(false, result); 43 | verify(flipAnnotationAttributes).getAttributeValue("property", ""); 44 | verify(flipAnnotationAttributes).getAttributeValue("expectedValue", ""); 45 | verify(featureContext).getPropertyValueOrDefault("feature.email.compose", String.class, ""); 46 | } 47 | 48 | @Test(expected = IllegalArgumentException.class) 49 | public void shouldThrowIllegalArgumentExceptionGivenEnvironmentPropertyIsBlank(){ 50 | FeatureContext featureContext = mock(FeatureContext.class); 51 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 52 | 53 | when(flipAnnotationAttributes.getAttributeValue("property", "")).thenReturn(""); 54 | when(flipAnnotationAttributes.getAttributeValue("expectedValue", "")).thenReturn("N"); 55 | 56 | SpringEnvironmentPropertyFlipCondition condition = new SpringEnvironmentPropertyFlipCondition(); 57 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 58 | } 59 | 60 | @Test(expected = IllegalArgumentException.class) 61 | public void shouldThrowIllegalArgumentExceptionGivenExpectedValueOfPropertyIsBlank(){ 62 | FeatureContext featureContext = mock(FeatureContext.class); 63 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 64 | 65 | when(flipAnnotationAttributes.getAttributeValue("property", "")).thenReturn("feature.email.compose"); 66 | when(flipAnnotationAttributes.getAttributeValue("expectedValue", "")).thenReturn(""); 67 | 68 | SpringEnvironmentPropertyFlipCondition condition = new SpringEnvironmentPropertyFlipCondition(); 69 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 70 | } 71 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/condition/SpringExpressionFlipConditionUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.exception.SpringExpressionEvaluationFailureException; 4 | import org.flips.model.FeatureContext; 5 | import org.flips.model.FlipAnnotationAttributes; 6 | import org.junit.Test; 7 | import org.springframework.expression.Expression; 8 | import org.springframework.expression.ExpressionParser; 9 | import org.springframework.expression.spel.SpelEvaluationException; 10 | import org.springframework.expression.spel.support.StandardEvaluationContext; 11 | 12 | import static org.junit.Assert.*; 13 | import static org.mockito.Mockito.*; 14 | 15 | public class SpringExpressionFlipConditionUnitTest { 16 | 17 | @Test 18 | public void shouldReturnTrueOnEvaluatingSpringExpressionGivenValidSpringExpression(){ 19 | FeatureContext featureContext = mock(FeatureContext.class); 20 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 21 | 22 | String inputExpression = "@bean.property.equals('test')"; 23 | ExpressionParser expressionParser = mock(ExpressionParser.class); 24 | StandardEvaluationContext context = mock(StandardEvaluationContext.class); 25 | Expression expression = mock(Expression.class); 26 | 27 | when(flipAnnotationAttributes.getAttributeValue("expression", "")).thenReturn(inputExpression); 28 | when(featureContext.getExpressionParser()).thenReturn(expressionParser); 29 | when(featureContext.getEvaluationContext()).thenReturn(context); 30 | when(expressionParser.parseExpression(inputExpression)).thenReturn(expression); 31 | when(expression.getValue(context)).thenReturn(true); 32 | 33 | SpringExpressionFlipCondition condition = new SpringExpressionFlipCondition(); 34 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 35 | 36 | assertEquals(true, result); 37 | verify(flipAnnotationAttributes).getAttributeValue("expression", ""); 38 | verify(featureContext).getExpressionParser(); 39 | verify(featureContext).getEvaluationContext(); 40 | verify(expressionParser).parseExpression(inputExpression); 41 | verify(expression).getValue(context); 42 | } 43 | 44 | @Test 45 | public void shouldReturnFalseOnEvaluatingSpringExpressionGivenValidSpringExpression(){ 46 | FeatureContext featureContext = mock(FeatureContext.class); 47 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 48 | 49 | String inputExpression = "@bean.property.equals('test')"; 50 | ExpressionParser expressionParser = mock(ExpressionParser.class); 51 | StandardEvaluationContext context = mock(StandardEvaluationContext.class); 52 | Expression expression = mock(Expression.class); 53 | 54 | when(flipAnnotationAttributes.getAttributeValue("expression", "")).thenReturn(inputExpression); 55 | when(featureContext.getExpressionParser()).thenReturn(expressionParser); 56 | when(featureContext.getEvaluationContext()).thenReturn(context); 57 | when(expressionParser.parseExpression(inputExpression)).thenReturn(expression); 58 | when(expression.getValue(context)).thenReturn(false); 59 | 60 | SpringExpressionFlipCondition condition = new SpringExpressionFlipCondition(); 61 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 62 | 63 | assertEquals(false, result); 64 | verify(flipAnnotationAttributes).getAttributeValue("expression", ""); 65 | verify(featureContext).getExpressionParser(); 66 | verify(featureContext).getEvaluationContext(); 67 | verify(expressionParser).parseExpression(inputExpression); 68 | verify(expression).getValue(context); 69 | } 70 | 71 | @Test 72 | public void shouldReturnFalseOnEvaluatingSpringExpressionGivenSpringExpressionEvaluatesToNull(){ 73 | FeatureContext featureContext = mock(FeatureContext.class); 74 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 75 | 76 | String inputExpression = "@bean.property.equals('test')"; 77 | ExpressionParser expressionParser = mock(ExpressionParser.class); 78 | StandardEvaluationContext context = mock(StandardEvaluationContext.class); 79 | Expression expression = mock(Expression.class); 80 | 81 | when(flipAnnotationAttributes.getAttributeValue("expression", "")).thenReturn(inputExpression); 82 | when(featureContext.getExpressionParser()).thenReturn(expressionParser); 83 | when(featureContext.getEvaluationContext()).thenReturn(context); 84 | when(expressionParser.parseExpression(inputExpression)).thenReturn(expression); 85 | when(expression.getValue(context)).thenReturn(null); 86 | 87 | SpringExpressionFlipCondition condition = new SpringExpressionFlipCondition(); 88 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 89 | 90 | assertEquals(false, result); 91 | verify(flipAnnotationAttributes).getAttributeValue("expression", ""); 92 | verify(featureContext).getExpressionParser(); 93 | verify(featureContext).getEvaluationContext(); 94 | verify(expressionParser).parseExpression(inputExpression); 95 | verify(expression).getValue(context); 96 | } 97 | 98 | @Test(expected = IllegalArgumentException.class) 99 | public void shouldThrowIllegalArgumentExceptionGivenExpressionIsEmpty(){ 100 | FeatureContext featureContext = mock(FeatureContext.class); 101 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 102 | 103 | String inputExpression = ""; 104 | when(flipAnnotationAttributes.getAttributeValue("expression", "")).thenReturn(inputExpression); 105 | 106 | SpringExpressionFlipCondition condition = new SpringExpressionFlipCondition(); 107 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 108 | } 109 | 110 | @Test(expected = SpringExpressionEvaluationFailureException.class) 111 | public void shouldThrowSpringExpressionEvaluationFailureExceptionGivenExpressionCannotBeParsed(){ 112 | FeatureContext featureContext = mock(FeatureContext.class); 113 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 114 | 115 | String inputExpression = "@unknownbean.property.equals('test')"; 116 | ExpressionParser expressionParser = mock(ExpressionParser.class); 117 | StandardEvaluationContext context = mock(StandardEvaluationContext.class); 118 | Expression expression = mock(Expression.class); 119 | 120 | when(flipAnnotationAttributes.getAttributeValue("expression", "")).thenReturn(inputExpression); 121 | when(featureContext.getExpressionParser()).thenReturn(expressionParser); 122 | when(featureContext.getEvaluationContext()).thenReturn(context); 123 | when(expressionParser.parseExpression(inputExpression)).thenReturn(expression); 124 | doThrow(SpelEvaluationException.class).when(expression).getValue(context); 125 | 126 | SpringExpressionFlipCondition condition = new SpringExpressionFlipCondition(); 127 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 128 | 129 | assertEquals(false, result); 130 | verify(flipAnnotationAttributes).getAttributeValue("expression", ""); 131 | verify(featureContext).getExpressionParser(); 132 | verify(featureContext).getEvaluationContext(); 133 | verify(expressionParser).parseExpression(inputExpression); 134 | verify(expression).getValue(context); 135 | } 136 | 137 | @Test(expected = SpringExpressionEvaluationFailureException.class) 138 | public void shouldThrowSpringExpressionEvaluationFailureExceptionGivenExpressionEvaluationIsNotBoolean(){ 139 | FeatureContext featureContext = mock(FeatureContext.class); 140 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 141 | 142 | String inputExpression = "@unknownbean.property.equals('test')"; 143 | ExpressionParser expressionParser = mock(ExpressionParser.class); 144 | StandardEvaluationContext context = mock(StandardEvaluationContext.class); 145 | Expression expression = mock(Expression.class); 146 | 147 | when(flipAnnotationAttributes.getAttributeValue("expression", "")).thenReturn(inputExpression); 148 | when(featureContext.getExpressionParser()).thenReturn(expressionParser); 149 | when(featureContext.getEvaluationContext()).thenReturn(context); 150 | when(expressionParser.parseExpression(inputExpression)).thenReturn(expression); 151 | when(expression.getValue(context)).thenReturn("test_value"); 152 | 153 | SpringExpressionFlipCondition condition = new SpringExpressionFlipCondition(); 154 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 155 | 156 | assertEquals(false, result); 157 | verify(flipAnnotationAttributes).getAttributeValue("expression", ""); 158 | verify(featureContext).getExpressionParser(); 159 | verify(featureContext).getEvaluationContext(); 160 | verify(expressionParser).parseExpression(inputExpression); 161 | verify(expression).getValue(context); 162 | } 163 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/condition/SpringProfileFlipConditionUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.condition; 2 | 3 | import org.flips.model.FeatureContext; 4 | import org.flips.model.FlipAnnotationAttributes; 5 | import org.flips.utils.Utils; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.mockito.Mockito.*; 10 | 11 | public class SpringProfileFlipConditionUnitTest { 12 | 13 | @Test 14 | public void shouldReturnTrueOnMatchingConditionGivenAnyActiveProfileIsPresentInExpectedProfiles(){ 15 | FeatureContext featureContext = mock(FeatureContext.class); 16 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 17 | 18 | String[] expectedActiveProfiles = {"dev", "qa"}; 19 | String[] activeProfiles = {"dev"}; 20 | 21 | when(flipAnnotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY)).thenReturn(expectedActiveProfiles); 22 | when(featureContext.getActiveProfilesOrEmpty()).thenReturn(activeProfiles); 23 | 24 | SpringProfileFlipCondition condition = new SpringProfileFlipCondition(); 25 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 26 | 27 | assertEquals(true, result); 28 | verify(flipAnnotationAttributes).getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY); 29 | verify(featureContext).getActiveProfilesOrEmpty(); 30 | } 31 | 32 | @Test 33 | public void shouldReturnFalseOnMatchingConditionGivenNoActiveProfileIsPresentInExpectedProfiles(){ 34 | FeatureContext featureContext = mock(FeatureContext.class); 35 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 36 | 37 | String[] expectedActiveProfiles = {"dev", "qa"}; 38 | String[] activeProfiles = {"uat"}; 39 | 40 | when(flipAnnotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY)).thenReturn(expectedActiveProfiles); 41 | when(featureContext.getActiveProfilesOrEmpty()).thenReturn(activeProfiles); 42 | 43 | SpringProfileFlipCondition condition = new SpringProfileFlipCondition(); 44 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 45 | 46 | assertEquals(false, result); 47 | verify(flipAnnotationAttributes).getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY); 48 | verify(featureContext).getActiveProfilesOrEmpty(); 49 | } 50 | 51 | @Test 52 | public void shouldReturnFalseOnMatchingConditionGivenActiveProfilesIsEmpty(){ 53 | FeatureContext featureContext = mock(FeatureContext.class); 54 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 55 | 56 | String[] expectedActiveProfiles = {"dev", "qa"}; 57 | String[] activeProfiles = {}; 58 | 59 | when(flipAnnotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY)).thenReturn(expectedActiveProfiles); 60 | when(featureContext.getActiveProfilesOrEmpty()).thenReturn(activeProfiles); 61 | 62 | SpringProfileFlipCondition condition = new SpringProfileFlipCondition(); 63 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 64 | 65 | assertEquals(false, result); 66 | verify(flipAnnotationAttributes).getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY); 67 | verify(featureContext).getActiveProfilesOrEmpty(); 68 | } 69 | 70 | @Test 71 | public void shouldReturnTrueOnMatchingConditionGivenAnyActiveProfileIsPresentInExpectedProfilesWithActiveProfilesMoreThanExpectedProfiles(){ 72 | FeatureContext featureContext = mock(FeatureContext.class); 73 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 74 | 75 | String[] expectedActiveProfiles = {"dev", "qa"}; 76 | String[] activeProfiles = {"uat", "dev", "qa"}; 77 | 78 | when(flipAnnotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY)).thenReturn(expectedActiveProfiles); 79 | when(featureContext.getActiveProfilesOrEmpty()).thenReturn(activeProfiles); 80 | 81 | SpringProfileFlipCondition condition = new SpringProfileFlipCondition(); 82 | boolean result = condition.evaluateCondition(featureContext, flipAnnotationAttributes); 83 | 84 | assertEquals(true, result); 85 | verify(flipAnnotationAttributes).getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY); 86 | verify(featureContext).getActiveProfilesOrEmpty(); 87 | } 88 | 89 | @Test(expected = IllegalArgumentException.class) 90 | public void shouldThrowIllegalArgumentExceptionGivenExpectedProfilesIsNull(){ 91 | FeatureContext featureContext = mock(FeatureContext.class); 92 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 93 | 94 | String[] expectedActiveProfiles = null; 95 | String[] activeProfiles = {"uat"}; 96 | 97 | when(flipAnnotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY)).thenReturn(expectedActiveProfiles); 98 | when(featureContext.getActiveProfilesOrEmpty()).thenReturn(activeProfiles); 99 | 100 | SpringProfileFlipCondition condition = new SpringProfileFlipCondition(); 101 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 102 | } 103 | 104 | @Test(expected = IllegalArgumentException.class) 105 | public void shouldThrowIllegalArgumentExceptionGivenExpectedProfilesIsEmpty(){ 106 | FeatureContext featureContext = mock(FeatureContext.class); 107 | FlipAnnotationAttributes flipAnnotationAttributes = mock(FlipAnnotationAttributes.class); 108 | 109 | String[] expectedActiveProfiles = {}; 110 | String[] activeProfiles = {"uat"}; 111 | 112 | when(flipAnnotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY)).thenReturn(expectedActiveProfiles); 113 | when(featureContext.getActiveProfilesOrEmpty()).thenReturn(activeProfiles); 114 | 115 | SpringProfileFlipCondition condition = new SpringProfileFlipCondition(); 116 | condition.evaluateCondition(featureContext, flipAnnotationAttributes); 117 | } 118 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/fixture/TestClientFlipAnnotationsWithAnnotationsOnMethod.java: -------------------------------------------------------------------------------- 1 | package org.flips.fixture; 2 | 3 | import org.flips.annotation.*; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.time.DayOfWeek; 7 | 8 | @Component 9 | public class TestClientFlipAnnotationsWithAnnotationsOnMethod { 10 | 11 | @FlipOff 12 | public void disabledFeature(){ 13 | 14 | } 15 | 16 | @FlipOnDateTime(cutoffDateTimeProperty = "past.feature.date") 17 | public void featureWithCurrentDtGtProvidedDt(){ 18 | } 19 | 20 | @FlipOnDateTime(cutoffDateTimeProperty = "future.feature.date") 21 | public void featureWithCurrentDtLtProvidedDt(){ 22 | } 23 | 24 | @FlipOnDateTime(cutoffDateTimeProperty = "past.feature.date") 25 | @FlipOnEnvironmentProperty(property = "feature.disabled") 26 | public void featureWithCurrentDtGtProvidedDtWithDisabledSpringProperty(){ 27 | } 28 | 29 | @FlipOnDateTime(cutoffDateTimeProperty = "past.feature.date") 30 | @FlipOnEnvironmentProperty(property = "feature.enabled") 31 | public void featureWithCurrentDtLtProvidedDtWithEnabledSpringProperty(){ 32 | } 33 | 34 | @FlipOnProfiles(activeProfiles = {"dev", "qa"}) 35 | public void springProfilesFeature(){ 36 | } 37 | 38 | public void noFeatureConditionsAppliedEnabledByDefault(){ 39 | } 40 | 41 | @FlipOff 42 | public void featureWithSameMethodNameInDifferentClass(){ 43 | } 44 | 45 | @FlipOnDateTime(cutoffDateTimeProperty = "past.feature.date") 46 | @FlipOnEnvironmentProperty(property = "feature.enabled") 47 | @FlipOff 48 | public void featureWithFlipOffAndConditionBasedAnnotations(){ 49 | } 50 | 51 | @FlipOnDaysOfWeek(daysOfWeek = {DayOfWeek.MONDAY, 52 | DayOfWeek.TUESDAY, 53 | DayOfWeek.WEDNESDAY, 54 | DayOfWeek.THURSDAY, 55 | DayOfWeek.FRIDAY, 56 | DayOfWeek.SATURDAY, 57 | DayOfWeek.SUNDAY}) 58 | public void featureWithDayOfWeekConditionBasedAnnotation(){ 59 | } 60 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/fixture/TestClientFlipAnnotationsWithSpringExpressionAnnotations.java: -------------------------------------------------------------------------------- 1 | package org.flips.fixture; 2 | 3 | import org.flips.annotation.FlipOnSpringExpression; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class TestClientFlipAnnotationsWithSpringExpressionAnnotations { 8 | 9 | @FlipOnSpringExpression(expression = "@environment.getProperty('should.golive') == true") 10 | public void featureWithSpringExpressionBasedOnEnvironmentProperty(){ 11 | } 12 | 13 | @FlipOnSpringExpression(expression = "@testClientFlipSpringComponent.shouldGoLive() == true") 14 | public void featureWithSpringExpressionBasedOnBeanReference(){ 15 | } 16 | 17 | @FlipOnSpringExpression(expression = "T(java.lang.Math).sqrt(4) * 100.0 < T(java.lang.Math).sqrt(4) * 10.0") 18 | public void featureWithSpringExpressionBasedOnNonBeanReference(){ 19 | } 20 | 21 | @FlipOnSpringExpression(expression = "@testClientFlipSpringComponent.deploymentEnvironment().contains('DEV')") 22 | public void featureWithSpringExpressionBasedOnBeanReferenceWithMethodInvocationOnPropertyValue(){ 23 | } 24 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/fixture/TestClientFlipBeanSpringComponentSource.java: -------------------------------------------------------------------------------- 1 | package org.flips.fixture; 2 | 3 | import org.flips.annotation.FlipBean; 4 | import org.flips.annotation.FlipOff; 5 | import org.flips.annotation.FlipOnEnvironmentProperty; 6 | import org.flips.annotation.FlipOnProfiles; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.time.LocalDate; 10 | 11 | @Component 12 | public class TestClientFlipBeanSpringComponentSource { 13 | 14 | @FlipBean(with = TestClientFlipBeanSpringComponentSource.class) 15 | public String noFlip(String str){ 16 | return str + ":" + "SOURCE"; 17 | } 18 | 19 | @FlipBean(with = TestClientFlipBeanSpringComponentTarget.class) 20 | public String map(String str){ 21 | return str + ":" + "SOURCE"; 22 | } 23 | 24 | @FlipBean(with = TestClientFlipBeanSpringComponentTarget.class) 25 | @FlipOnProfiles(activeProfiles = "dev") 26 | public String changeCase(String str){ 27 | return str.toUpperCase(); 28 | } 29 | 30 | @FlipBean(with = TestClientFlipBeanSpringComponentTarget.class) 31 | @FlipOff 32 | public String identity(String str){ 33 | return str; 34 | } 35 | 36 | @FlipBean(with = TestClientFlipBeanSpringComponentTarget.class) 37 | @FlipOnProfiles(activeProfiles = "dev") 38 | @FlipOnEnvironmentProperty(property = "flip.bean", expectedValue = "1") 39 | public String html(){ 40 | return ""; 41 | } 42 | 43 | @FlipBean(with = TestClientFlipBeanSpringComponentTarget.class) 44 | public LocalDate currentDate(){ 45 | return LocalDate.now(); 46 | } 47 | 48 | @FlipBean(with = TestClientFlipBeanSpringComponentTarget.class) 49 | public LocalDate nextDate(){ 50 | return currentDate().plusDays(1); 51 | } 52 | 53 | @FlipBean(with = TestClientFlipBeanSpringComponentTarget.class) 54 | public LocalDate previousDate(){ 55 | return currentDate().minusDays(1); 56 | } 57 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/fixture/TestClientFlipBeanSpringComponentTarget.java: -------------------------------------------------------------------------------- 1 | package org.flips.fixture; 2 | 3 | import org.flips.annotation.FlipOff; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.time.LocalDate; 7 | 8 | @Component 9 | public class TestClientFlipBeanSpringComponentTarget { 10 | 11 | public String map(String str){ 12 | return str + ":" + "TARGET"; 13 | } 14 | 15 | @FlipOff 16 | public LocalDate currentDate(){ 17 | return LocalDate.now(); 18 | } 19 | 20 | private LocalDate previousDate(){ 21 | return currentDate().minusDays(1); 22 | } 23 | 24 | public String changeCase(String str){ 25 | return str.toLowerCase(); 26 | } 27 | 28 | public String identity(String str){ 29 | return str + ":" + "identity"; 30 | } 31 | 32 | public String html(){ 33 | return "".toUpperCase(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/fixture/TestClientFlipSpringComponent.java: -------------------------------------------------------------------------------- 1 | package org.flips.fixture; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class TestClientFlipSpringComponent { 7 | 8 | public boolean shouldGoLive(){ 9 | return true; 10 | } 11 | 12 | public String deploymentEnvironment(){ 13 | return "Development".toUpperCase(); 14 | } 15 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/fixture/TestClientFlipSpringService.java: -------------------------------------------------------------------------------- 1 | package org.flips.fixture; 2 | 3 | import org.flips.annotation.FlipOff; 4 | import org.flips.annotation.FlipOnSpringExpression; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class TestClientFlipSpringService { 9 | 10 | public boolean enabledMethod(){ 11 | return true; 12 | } 13 | 14 | @FlipOff 15 | public void disabledMethod(){ 16 | } 17 | 18 | @FlipOnSpringExpression(expression = "T(java.lang.Math).sqrt(4) * 100.0 < T(java.lang.Math).sqrt(4) * 10.0") 19 | public void disabledMethodUsingSpringExpression(){ 20 | } 21 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/model/EmptyFlipConditionEvaluatorUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.junit.Test; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | import static org.mockito.Mockito.mock; 8 | 9 | public class EmptyFlipConditionEvaluatorUnitTest { 10 | 11 | @Test 12 | public void shouldReturnIsEmptyTrueGivenEmptyFlipConditionEvaluator(){ 13 | ApplicationContext applicationContext = mock(ApplicationContext.class); 14 | FeatureContext featureContext = mock(FeatureContext.class); 15 | 16 | EmptyFlipConditionEvaluator conditionEvaluator = new EmptyFlipConditionEvaluator(applicationContext, featureContext); 17 | assertEquals(true, conditionEvaluator.isEmpty()); 18 | } 19 | 20 | @Test 21 | public void shouldEvaluateToTrueGivenEmptyFlipConditionEvaluator(){ 22 | ApplicationContext applicationContext = mock(ApplicationContext.class); 23 | FeatureContext featureContext = mock(FeatureContext.class); 24 | 25 | EmptyFlipConditionEvaluator conditionEvaluator = new EmptyFlipConditionEvaluator(applicationContext, featureContext); 26 | assertEquals(true, conditionEvaluator.evaluate()); 27 | } 28 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/model/FeatureContextUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.flips.utils.Utils; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | import org.springframework.core.env.Environment; 10 | import org.springframework.expression.EvaluationContext; 11 | import org.springframework.expression.ExpressionParser; 12 | import org.springframework.expression.spel.support.StandardEvaluationContext; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.mockito.Mockito.*; 16 | 17 | @RunWith(MockitoJUnitRunner.class) 18 | public class FeatureContextUnitTest { 19 | 20 | @InjectMocks 21 | private FeatureContext featureContext; 22 | 23 | @Mock 24 | private Environment environment; 25 | 26 | @Mock 27 | private FeatureExpressionContext featureExpressionContext; 28 | 29 | @Test 30 | public void shouldReturnNonEmptyPropertyValueGivenPropertyExistsInEnvironment(){ 31 | String property = "feature.enabled"; 32 | 33 | when(environment.getProperty(property, String.class, "")).thenReturn("true"); 34 | String propertyValue = featureContext.getPropertyValueOrDefault(property, String.class, ""); 35 | 36 | assertEquals("true", propertyValue); 37 | verify(environment).getProperty(property, String.class, ""); 38 | } 39 | 40 | @Test 41 | public void shouldReturnEmptyPropertyValueGivenPropertyDoesNotExistInEnvironment(){ 42 | String property = "feature.enabled"; 43 | 44 | when(environment.getProperty(property, String.class, "")).thenReturn(""); 45 | String propertyValue = featureContext.getPropertyValueOrDefault(property, String.class, ""); 46 | 47 | assertEquals("", propertyValue); 48 | verify(environment).getProperty(property, String.class, ""); 49 | } 50 | 51 | @Test 52 | public void shouldReturnEmptyProfilesGivenNoActiveProfilesIsSetInEnvironment(){ 53 | when(environment.getActiveProfiles()).thenReturn(null); 54 | String[] activeProfiles = featureContext.getActiveProfilesOrEmpty(); 55 | 56 | assertEquals(Utils.EMPTY_STRING_ARRAY, activeProfiles); 57 | verify(environment).getActiveProfiles(); 58 | } 59 | 60 | @Test 61 | public void shouldReturnActiveProfilesGivenNonEmptyActiveProfilesIsSetInEnvironment(){ 62 | when(environment.getActiveProfiles()).thenReturn(new String[]{"dev"}); 63 | String[] activeProfiles = featureContext.getActiveProfilesOrEmpty(); 64 | 65 | assertEquals(new String[]{"dev"}, activeProfiles); 66 | verify(environment).getActiveProfiles(); 67 | } 68 | 69 | @Test 70 | public void shouldReturnExpressionParser(){ 71 | ExpressionParser expressionParser = mock(ExpressionParser.class); 72 | when(featureExpressionContext.getExpressionParser()).thenReturn(expressionParser); 73 | 74 | ExpressionParser parser = featureContext.getExpressionParser(); 75 | 76 | assertEquals(expressionParser, parser); 77 | verify(featureExpressionContext).getExpressionParser(); 78 | } 79 | 80 | @Test 81 | public void shouldReturnEvaluationContext(){ 82 | EvaluationContext evaluationContext = mock(StandardEvaluationContext.class); 83 | when(featureExpressionContext.getEvaluationContext()).thenReturn(evaluationContext); 84 | 85 | EvaluationContext context = featureContext.getEvaluationContext(); 86 | 87 | assertEquals(evaluationContext, context); 88 | verify(featureExpressionContext).getEvaluationContext(); 89 | } 90 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/model/FlipAnnotationAttributesUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.flips.utils.Utils; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | public class FlipAnnotationAttributesUnitTest { 12 | 13 | @Test 14 | public void shouldReturnPropertyValueAsStringGivenAnnotationAttributesContainsProperty(){ 15 | Map attributes = new HashMap(){{ 16 | put("cutoffDateTimeProperty", "past.feature.date"); 17 | }}; 18 | FlipAnnotationAttributes annotationAttributes = new FlipAnnotationAttributes.Builder().addAll(attributes).build(); 19 | 20 | String propertyValue = annotationAttributes.getAttributeValue("cutoffDateTimeProperty", ""); 21 | assertEquals("past.feature.date", propertyValue); 22 | } 23 | 24 | @Test 25 | public void shouldReturnPropertyValueAsBlankStringGivenAnnotationAttributesDoesNotContainProperty(){ 26 | Map attributes = new HashMap<>(); 27 | FlipAnnotationAttributes annotationAttributes = new FlipAnnotationAttributes.Builder().addAll(attributes).build(); 28 | 29 | String propertyValue = annotationAttributes.getAttributeValue("cutoffDateTimeProperty", ""); 30 | assertEquals("", propertyValue); 31 | } 32 | 33 | @Test 34 | public void shouldReturnPropertyValueAsStringArrayGivenAnnotationAttributesContainsProperty(){ 35 | Map attributes = new HashMap(){{ 36 | put("activeProfiles", new String[]{"dev"}); 37 | }}; 38 | FlipAnnotationAttributes annotationAttributes = new FlipAnnotationAttributes.Builder().addAll(attributes).build(); 39 | 40 | String[] propertyValue = annotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY); 41 | assertEquals(new String[]{"dev"}, propertyValue); 42 | } 43 | 44 | @Test 45 | public void shouldReturnPropertyValueAsEmptyArrayGivenAnnotationAttributesDoesNotContainProperty(){ 46 | Map attributes = new HashMap<>(); 47 | FlipAnnotationAttributes annotationAttributes = new FlipAnnotationAttributes.Builder().addAll(attributes).build(); 48 | 49 | String[] propertyValue = annotationAttributes.getAttributeValue("activeProfiles", Utils.EMPTY_STRING_ARRAY); 50 | assertEquals(Utils.EMPTY_STRING_ARRAY, propertyValue); 51 | } 52 | 53 | @Test(expected = IllegalArgumentException.class) 54 | public void shouldThrowIllegalArgumentExceptionGivenPropertyNameIsNull(){ 55 | Map attributes = new HashMap<>(); 56 | FlipAnnotationAttributes annotationAttributes = new FlipAnnotationAttributes.Builder().addAll(attributes).build(); 57 | 58 | annotationAttributes.getAttributeValue(null, Utils.EMPTY_STRING_ARRAY); 59 | } 60 | 61 | @Test(expected = IllegalArgumentException.class) 62 | public void shouldThrowIllegalArgumentExceptionGivenAttributesToAddAreNull(){ 63 | Map attributes = null; 64 | new FlipAnnotationAttributes.Builder().addAll(attributes).build(); 65 | } 66 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/model/FlipConditionEvaluatorFactoryUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.flips.annotation.FlipOnOff; 4 | import org.flips.utils.AnnotationUtils; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.powermock.api.mockito.PowerMockito; 11 | import org.powermock.core.classloader.annotations.PrepareForTest; 12 | import org.powermock.modules.junit4.PowerMockRunner; 13 | import org.springframework.context.ApplicationContext; 14 | 15 | import java.lang.annotation.Annotation; 16 | 17 | import static org.junit.Assert.assertTrue; 18 | import static org.mockito.Mockito.when; 19 | 20 | @RunWith(PowerMockRunner.class) 21 | @PrepareForTest(AnnotationUtils.class) 22 | public class FlipConditionEvaluatorFactoryUnitTest { 23 | 24 | @InjectMocks 25 | private FlipConditionEvaluatorFactory flipConditionEvaluatorFactory; 26 | 27 | @Mock 28 | private ApplicationContext applicationContext; 29 | 30 | @Mock 31 | private FeatureContext featureContext; 32 | 33 | @Before 34 | public void setUp(){ 35 | flipConditionEvaluatorFactory.buildEmptyFlipConditionEvaluator(); 36 | } 37 | 38 | @Test 39 | public void shouldReturnEmptyFlipConditionEvaluatorGivenNoAnnotationsProvided(){ 40 | FlipConditionEvaluatorFactory flipConditionEvaluatorFactory = new FlipConditionEvaluatorFactory(applicationContext, featureContext); 41 | 42 | FlipConditionEvaluator flipConditionEvaluator = flipConditionEvaluatorFactory.buildFlipConditionEvaluator(new Annotation[0]); 43 | assertTrue(flipConditionEvaluator instanceof EmptyFlipConditionEvaluator); 44 | } 45 | 46 | @Test 47 | public void shouldReturnDefaultFlipConditionEvaluatorGivenAnnotationsOfTypeFlipOnOffIsProvided(){ 48 | Annotation[] annotations = new Annotation[1]; 49 | 50 | PowerMockito.mockStatic(AnnotationUtils.class); 51 | when(AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class)).thenReturn(false); 52 | 53 | FlipConditionEvaluator flipConditionEvaluator = flipConditionEvaluatorFactory.buildFlipConditionEvaluator(annotations); 54 | 55 | assertTrue(flipConditionEvaluator instanceof DefaultFlipConditionEvaluator); 56 | 57 | PowerMockito.verifyStatic(); 58 | AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class); 59 | } 60 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/model/FlipConditionEvaluatorUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.model; 2 | 3 | import org.flips.annotation.FlipOnEnvironmentProperty; 4 | import org.flips.annotation.FlipOnOff; 5 | import org.flips.annotation.FlipOnProfiles; 6 | import org.flips.condition.FlipCondition; 7 | import org.flips.condition.SpringEnvironmentPropertyFlipCondition; 8 | import org.flips.condition.SpringProfileFlipCondition; 9 | import org.flips.utils.AnnotationUtils; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.powermock.api.mockito.PowerMockito; 13 | import org.powermock.core.classloader.annotations.PrepareForTest; 14 | import org.powermock.modules.junit4.PowerMockRunner; 15 | import org.springframework.context.ApplicationContext; 16 | 17 | import java.lang.annotation.Annotation; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.mockito.Matchers.any; 21 | import static org.mockito.Mockito.*; 22 | 23 | @RunWith(PowerMockRunner.class) 24 | @PrepareForTest(AnnotationUtils.class) 25 | public class FlipConditionEvaluatorUnitTest { 26 | 27 | @Test(expected = IllegalArgumentException.class) 28 | public void shouldThrowIllegalArgumentExceptionGivenNullAnnotationsWhileInstantiatingDefaultFlipConditionEvaluator(){ 29 | ApplicationContext applicationContext = mock(ApplicationContext.class); 30 | FeatureContext featureContext = mock(FeatureContext.class); 31 | 32 | new DefaultFlipConditionEvaluator(applicationContext, featureContext, null); 33 | } 34 | 35 | @Test 36 | public void shouldEvaluateFlipConditionToTrueGivenEmptyAnnotationArray(){ 37 | ApplicationContext applicationContext = mock(ApplicationContext.class); 38 | FeatureContext featureContext = mock(FeatureContext.class); 39 | Annotation[] annotations = new Annotation[0]; 40 | 41 | DefaultFlipConditionEvaluator conditionEvaluator = new DefaultFlipConditionEvaluator(applicationContext, featureContext, annotations); 42 | boolean result = conditionEvaluator.evaluate(); 43 | 44 | assertEquals(true, result); 45 | verify(applicationContext, never()).getBean(any(Class.class)); 46 | } 47 | 48 | @Test 49 | public void shouldEvaluateFlipConditionToTrueGivenNoFlipConditionIsDefined(){ 50 | ApplicationContext applicationContext = mock(ApplicationContext.class); 51 | FeatureContext featureContext = mock(FeatureContext.class); 52 | Annotation[] annotations = new Annotation[1]; 53 | 54 | PowerMockito.mockStatic(AnnotationUtils.class); 55 | when(AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class)).thenReturn(false); 56 | 57 | DefaultFlipConditionEvaluator conditionEvaluator = new DefaultFlipConditionEvaluator(applicationContext, featureContext, annotations); 58 | boolean result = conditionEvaluator.evaluate(); 59 | 60 | assertEquals(true, result); 61 | verify(applicationContext, never()).getBean(any(Class.class)); 62 | 63 | PowerMockito.verifyStatic(); 64 | AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class); 65 | } 66 | 67 | @Test 68 | public void shouldEvaluateFlipConditionToTrueGivenFlipConditionIsDefinedWithConditionEvaluatingToTrue() { 69 | ApplicationContext applicationContext = mock(ApplicationContext.class); 70 | FeatureContext featureContext = mock(FeatureContext.class); 71 | Annotation[] annotations = new Annotation[1]; 72 | Class conditionClass = SpringProfileFlipCondition.class; 73 | SpringProfileFlipCondition condition = mock(SpringProfileFlipCondition.class); 74 | 75 | FlipOnOff annotationFlipOnOff = mock(FlipOnOff.class); 76 | FlipAnnotationAttributes annotationAttributes = mock(FlipAnnotationAttributes.class); 77 | 78 | PowerMockito.mockStatic(AnnotationUtils.class); 79 | when(AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class)).thenReturn(true); 80 | when(AnnotationUtils.getAnnotationOfType(annotations[0], FlipOnOff.class)).thenReturn(annotationFlipOnOff); 81 | when(annotationFlipOnOff.value()).thenReturn(conditionClass); 82 | when(AnnotationUtils.getAnnotationAttributes(annotations[0])).thenReturn(annotationAttributes); 83 | when(applicationContext.getBean(conditionClass)).thenReturn(condition); 84 | when(condition.evaluateCondition(featureContext, annotationAttributes)).thenReturn(true); 85 | 86 | DefaultFlipConditionEvaluator conditionEvaluator = new DefaultFlipConditionEvaluator(applicationContext, featureContext, annotations); 87 | boolean result = conditionEvaluator.evaluate(); 88 | 89 | assertEquals(true, result); 90 | verify(applicationContext).getBean(conditionClass); 91 | verify(condition).evaluateCondition(featureContext, annotationAttributes); 92 | verify(annotationFlipOnOff).value(); 93 | 94 | PowerMockito.verifyStatic(); 95 | AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class); 96 | AnnotationUtils.getAnnotationOfType(annotations[0], FlipOnOff.class); 97 | AnnotationUtils.getAnnotationAttributes(annotations[0]); 98 | } 99 | 100 | @Test 101 | public void shouldEvaluateFlipConditionToFalseGivenFlipConditionIsDefinedWithConditionEvaluatingToFalse() { 102 | ApplicationContext applicationContext = mock(ApplicationContext.class); 103 | FeatureContext featureContext = mock(FeatureContext.class); 104 | Annotation[] annotations = new Annotation[1]; 105 | Class conditionClass = SpringProfileFlipCondition.class; 106 | 107 | FlipOnOff annotationFlipOnOff = mock(FlipOnOff.class); 108 | SpringProfileFlipCondition condition = mock(SpringProfileFlipCondition.class); 109 | FlipAnnotationAttributes annotationAttributes = mock(FlipAnnotationAttributes.class); 110 | 111 | PowerMockito.mockStatic(AnnotationUtils.class); 112 | when(AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class)).thenReturn(true); 113 | when(AnnotationUtils.getAnnotationOfType(annotations[0], FlipOnOff.class)).thenReturn(annotationFlipOnOff); 114 | when(annotationFlipOnOff.value()).thenReturn(conditionClass); 115 | when(AnnotationUtils.getAnnotationAttributes(annotations[0])).thenReturn(annotationAttributes); 116 | when(applicationContext.getBean(conditionClass)).thenReturn(condition); 117 | when(condition.evaluateCondition(featureContext, annotationAttributes)).thenReturn(false); 118 | 119 | DefaultFlipConditionEvaluator conditionEvaluator = new DefaultFlipConditionEvaluator(applicationContext, featureContext, annotations); 120 | boolean result = conditionEvaluator.evaluate(); 121 | 122 | assertEquals(false, result); 123 | verify(applicationContext).getBean(conditionClass); 124 | verify(condition).evaluateCondition(featureContext, annotationAttributes); 125 | verify(annotationFlipOnOff).value(); 126 | 127 | PowerMockito.verifyStatic(); 128 | AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class); 129 | AnnotationUtils.getAnnotationOfType(annotations[0], FlipOnOff.class); 130 | AnnotationUtils.getAnnotationAttributes(annotations[0]); 131 | } 132 | 133 | @Test 134 | public void shouldEvaluateFlipConditionToFalseGivenMultipleFlipConditionsAreDefinedWithOneConditionEvaluatingToFalse() { 135 | ApplicationContext applicationContext = mock(ApplicationContext.class); 136 | FeatureContext featureContext = mock(FeatureContext.class); 137 | Annotation[] annotations = new Annotation[]{mock(FlipOnProfiles.class), mock(FlipOnEnvironmentProperty.class)}; 138 | FlipOnOff flipCondition = mock(FlipOnOff.class); 139 | FlipOnOff flipConditionOther = mock(FlipOnOff.class); 140 | 141 | Class conditionClass = SpringProfileFlipCondition.class; 142 | Class conditionClassOther = SpringEnvironmentPropertyFlipCondition.class; 143 | 144 | FlipCondition conditionInstance = mock(SpringProfileFlipCondition.class); 145 | FlipCondition conditionInstanceOther = mock(SpringEnvironmentPropertyFlipCondition.class); 146 | 147 | FlipAnnotationAttributes annotationAttributes = mock(FlipAnnotationAttributes.class); 148 | FlipAnnotationAttributes annotationAttributesOther = mock(FlipAnnotationAttributes.class); 149 | 150 | PowerMockito.mockStatic(AnnotationUtils.class); 151 | when(AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class)).thenReturn(true); 152 | when(AnnotationUtils.isMetaAnnotationDefined(annotations[1], FlipOnOff.class)).thenReturn(true); 153 | 154 | 155 | when(AnnotationUtils.getAnnotationOfType(annotations[0], FlipOnOff.class)).thenReturn(flipCondition); 156 | when(AnnotationUtils.getAnnotationOfType(annotations[1], FlipOnOff.class)).thenReturn(flipConditionOther); 157 | 158 | when(flipCondition.value()).thenReturn(conditionClass); 159 | when(flipConditionOther.value()).thenReturn(conditionClassOther); 160 | 161 | when(AnnotationUtils.getAnnotationAttributes(annotations[0])).thenReturn(annotationAttributes); 162 | when(AnnotationUtils.getAnnotationAttributes(annotations[1])).thenReturn(annotationAttributesOther); 163 | 164 | when(applicationContext.getBean(conditionClass)).thenReturn(conditionInstance); 165 | when(applicationContext.getBean(conditionClassOther)).thenReturn(conditionInstanceOther); 166 | 167 | when(conditionInstance.evaluateCondition(featureContext, annotationAttributes)).thenReturn(false); 168 | when(conditionInstanceOther.evaluateCondition(featureContext, annotationAttributesOther)).thenReturn(true); 169 | 170 | DefaultFlipConditionEvaluator conditionEvaluator = new DefaultFlipConditionEvaluator(applicationContext, featureContext, annotations); 171 | boolean result = conditionEvaluator.evaluate(); 172 | 173 | assertEquals(false, result); 174 | verify(applicationContext).getBean(conditionClass); 175 | verify(applicationContext, never()).getBean(conditionClassOther); 176 | verify(conditionInstance).evaluateCondition(featureContext, annotationAttributes); 177 | verify(conditionInstanceOther, never()).evaluateCondition(featureContext, annotationAttributesOther); 178 | verify(flipCondition).value(); 179 | verify(flipConditionOther).value(); 180 | 181 | 182 | PowerMockito.verifyStatic(); 183 | AnnotationUtils.isMetaAnnotationDefined(annotations[0], FlipOnOff.class); 184 | AnnotationUtils.isMetaAnnotationDefined(annotations[1], FlipOnOff.class); 185 | AnnotationUtils.getAnnotationOfType(annotations[0], FlipOnOff.class); 186 | AnnotationUtils.getAnnotationOfType(annotations[1], FlipOnOff.class); 187 | AnnotationUtils.getAnnotationAttributes(annotations[0]); 188 | AnnotationUtils.getAnnotationAttributes(annotations[1]); 189 | } 190 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/processor/FlipAnnotationProcessorUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.processor; 2 | 3 | import org.flips.annotation.FlipOff; 4 | import org.flips.model.FeatureContext; 5 | import org.flips.model.FlipConditionEvaluator; 6 | import org.flips.model.FlipConditionEvaluatorFactory; 7 | import org.flips.utils.AnnotationUtils; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.InjectMocks; 11 | import org.mockito.Mock; 12 | import org.powermock.api.mockito.PowerMockito; 13 | import org.powermock.core.classloader.annotations.PrepareForTest; 14 | import org.powermock.modules.junit4.PowerMockRunner; 15 | import org.springframework.context.ApplicationContext; 16 | 17 | import java.lang.annotation.Annotation; 18 | import java.lang.reflect.Method; 19 | 20 | import static org.junit.Assert.assertNotNull; 21 | import static org.mockito.Mockito.*; 22 | 23 | @RunWith(PowerMockRunner.class) 24 | @PrepareForTest({AnnotationUtils.class}) 25 | public class FlipAnnotationProcessorUnitTest { 26 | 27 | @InjectMocks 28 | private FlipAnnotationProcessor flipAnnotationProcessor; 29 | 30 | @Mock 31 | private ApplicationContext applicationContext; 32 | 33 | @Mock 34 | private FeatureContext featureContext; 35 | 36 | @Mock 37 | private FlipConditionEvaluatorFactory flipConditionEvaluatorFactory; 38 | 39 | @Test 40 | public void shouldReturnFlipConditionEvaluatorGivenMethodWithFlipAnnotations() { 41 | Method method = PowerMockito.mock(Method.class); 42 | FlipOff flipOff = mock(FlipOff.class); 43 | Annotation[] annotations = new Annotation[]{flipOff}; 44 | FlipConditionEvaluator flipConditionEvaluator = mock(FlipConditionEvaluator.class); 45 | 46 | PowerMockito.mockStatic(AnnotationUtils.class); 47 | when(AnnotationUtils.getAnnotations(method)).thenReturn(annotations); 48 | when(flipConditionEvaluatorFactory.buildFlipConditionEvaluator(annotations)).thenReturn(flipConditionEvaluator); 49 | when(flipConditionEvaluator.isEmpty()).thenReturn(false); 50 | 51 | FlipConditionEvaluator conditionEvaluator = flipAnnotationProcessor.getFlipConditionEvaluator(method); 52 | 53 | assertNotNull("FlipConditionEvaluator was null after invoking flipAnnotationProcessor.getFlipConditionEvaluator", conditionEvaluator); 54 | verify(flipConditionEvaluatorFactory).buildFlipConditionEvaluator(annotations); 55 | 56 | PowerMockito.verifyStatic(); 57 | AnnotationUtils.getAnnotations(method); 58 | } 59 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/store/FlipAnnotationsStoreIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.store; 2 | 3 | import org.flips.config.FlipContextConfiguration; 4 | import org.flips.fixture.TestClientFlipAnnotationsWithAnnotationsOnMethod; 5 | import org.flips.fixture.TestClientFlipAnnotationsWithSpringExpressionAnnotations; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.TestPropertySource; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @ContextConfiguration(classes = FlipContextConfiguration.class) 20 | @TestPropertySource(properties = {"feature.disabled=false", "feature.enabled=true", "past.feature.date=2014-12-30T14:00:00Z", "future.feature.date=3030-12-30T14:00:00Z"}) 21 | @ActiveProfiles("dev") 22 | public class FlipAnnotationsStoreIntegrationTest { 23 | 24 | @Autowired 25 | private FlipAnnotationsStore flipAnnotationsStore; 26 | 27 | @Test 28 | public void shouldReturnFeatureDisabledGivenFeatureIsSetAsDisabled() throws Exception { 29 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("disabledFeature"); 30 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 31 | 32 | assertEquals(false, result); 33 | } 34 | 35 | @Test 36 | public void shouldReturnFeatureEnabledGivenFeatureIsSetWithPastCutoffDateTimeFlipCondition() throws Exception { 37 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("featureWithCurrentDtGtProvidedDt"); 38 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 39 | 40 | assertEquals(true, result); 41 | } 42 | 43 | @Test 44 | public void shouldReturnFeatureDisabledGivenFeatureIsSetWithFutureCutoffDateTimeFlipCondition() throws Exception { 45 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("featureWithCurrentDtLtProvidedDt"); 46 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 47 | 48 | assertEquals(false, result); 49 | } 50 | 51 | @Test 52 | public void shouldReturnFeatureDisabledGivenFeatureIsSetWithPastCutoffDateTimeFlipConditionAndFeaturePropertyIsDisabled() throws Exception { 53 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("featureWithCurrentDtGtProvidedDtWithDisabledSpringProperty"); 54 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 55 | 56 | assertEquals(false, result); 57 | } 58 | 59 | @Test 60 | public void shouldReturnFeatureEnabledGivenFeatureIsSetWithPastCutoffDateTimeFlipConditionAndFeaturePropertyIsEnabled() throws Exception { 61 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("featureWithCurrentDtLtProvidedDtWithEnabledSpringProperty"); 62 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 63 | 64 | assertEquals(true, result); 65 | } 66 | 67 | @Test 68 | public void shouldReturnFeatureEnabledGivenActiveProfileIsOneOfExpectedProfiles() throws Exception { 69 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("springProfilesFeature"); 70 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 71 | 72 | assertEquals(true, result); 73 | } 74 | 75 | @Test 76 | public void shouldReturnFeatureEnabledGivenNoFlipConditionIsPresent() throws Exception { 77 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("noFeatureConditionsAppliedEnabledByDefault"); 78 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 79 | 80 | assertEquals(true, result); 81 | } 82 | 83 | @Test 84 | public void shouldReturnFeatureDisabledGivenSpringExpressionWithEnvironmentPropertyEvaluatesToFalse() throws Exception{ 85 | Method method = TestClientFlipAnnotationsWithSpringExpressionAnnotations.class.getMethod("featureWithSpringExpressionBasedOnEnvironmentProperty"); 86 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 87 | 88 | assertEquals(false, result); 89 | } 90 | 91 | @Test 92 | public void shouldReturnFeatureEnabledGivenSpringExpressionWithBeanReferenceEvaluatesToTrue() throws Exception{ 93 | Method method = TestClientFlipAnnotationsWithSpringExpressionAnnotations.class.getMethod("featureWithSpringExpressionBasedOnBeanReference"); 94 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 95 | 96 | assertEquals(true, result); 97 | } 98 | 99 | @Test 100 | public void shouldReturnFeatureEnabledGivenSpringExpressionWithBeanReferenceWithMethodInvocationOnPropertyValueEvaluatesToTrue() throws Exception{ 101 | Method method = TestClientFlipAnnotationsWithSpringExpressionAnnotations.class.getMethod("featureWithSpringExpressionBasedOnBeanReferenceWithMethodInvocationOnPropertyValue"); 102 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 103 | 104 | assertEquals(true, result); 105 | } 106 | 107 | @Test 108 | public void shouldReturnFeatureDisabledGivenSpringExpressionWithNonBeanReferenceEvaluatesToFalse() throws Exception{ 109 | Method method = TestClientFlipAnnotationsWithSpringExpressionAnnotations.class.getMethod("featureWithSpringExpressionBasedOnNonBeanReference"); 110 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 111 | 112 | assertEquals(false, result); 113 | } 114 | 115 | @Test 116 | public void shouldReturnFeatureWithSameNameInDifferentClassDisabledGivenFeatureIsSetToDisabled() throws Exception { 117 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("featureWithSameMethodNameInDifferentClass"); 118 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 119 | 120 | assertEquals(false, result); 121 | } 122 | 123 | @Test 124 | public void shouldReturnFeatureDisabledGivenFeatureIsDisabledByEvaluatingAllOfTheConditions() throws Exception { 125 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("featureWithFlipOffAndConditionBasedAnnotations"); 126 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 127 | 128 | assertEquals(false, result); 129 | } 130 | 131 | @Test 132 | public void shouldReturnFeatureEnabledGivenFeatureWithDayOfWeekEvaluatesToTrue() throws Exception{ 133 | Method method = TestClientFlipAnnotationsWithAnnotationsOnMethod.class.getMethod("featureWithDayOfWeekConditionBasedAnnotation"); 134 | boolean result = flipAnnotationsStore.isFeatureEnabled(method); 135 | 136 | assertEquals(true, result); 137 | } 138 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/store/FlipAnnotationsStoreUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.store; 2 | 3 | import org.flips.model.FlipConditionEvaluator; 4 | import org.flips.model.FlipConditionEvaluatorFactory; 5 | import org.flips.processor.FlipAnnotationProcessor; 6 | import org.flips.utils.Utils; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.test.util.ReflectionTestUtils; 14 | 15 | import java.lang.reflect.Method; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.mockito.Matchers.any; 21 | import static org.mockito.Mockito.*; 22 | 23 | @RunWith(MockitoJUnitRunner.class) 24 | public class FlipAnnotationsStoreUnitTest { 25 | 26 | public static class FlipAnnotationTestClient { 27 | public void method1(){ 28 | } 29 | private void method2(){ 30 | } 31 | } 32 | 33 | private FlipAnnotationsStore flipAnnotationsStore; 34 | 35 | @Mock 36 | private ApplicationContext applicationContext; 37 | 38 | @Mock 39 | private FlipAnnotationProcessor flipAnnotationProcessor; 40 | 41 | @Mock 42 | private FlipConditionEvaluatorFactory flipConditionEvaluatorFactory; 43 | 44 | @Before 45 | public void setUp(){ 46 | flipAnnotationsStore = new FlipAnnotationsStore(applicationContext, 47 | flipAnnotationProcessor, 48 | flipConditionEvaluatorFactory, 49 | "org.springframework" 50 | ); 51 | } 52 | 53 | @Test 54 | public void shouldNotStoreFlipAnnotationsGivenNoBeansWereAnnotatedWithFlipsAnnotation(){ 55 | when(applicationContext.getBeanDefinitionNames()).thenReturn(Utils.EMPTY_STRING_ARRAY); 56 | 57 | flipAnnotationsStore.buildFlipAnnotationsStore(); 58 | 59 | assertEquals(0, flipAnnotationsStore.getTotalMethodsCached()); 60 | assertEquals(0, flipAnnotationsStore.allMethodsCached().size()); 61 | verify(applicationContext).getBeanDefinitionNames(); 62 | verify(flipAnnotationProcessor, never()).getFlipConditionEvaluator(any(Method.class)); 63 | } 64 | 65 | @Test 66 | public void shouldNotStoreFlipAnnotationsGivenEmptyFlipConditionEvaluator() throws Exception { 67 | String[] beanDefinitionNames = {"beanA"}; 68 | Method method = FlipAnnotationTestClient.class.getMethod("method1"); 69 | FlipConditionEvaluator flipConditionEvaluator = mock(FlipConditionEvaluator.class); 70 | 71 | when(applicationContext.getBeanDefinitionNames()).thenReturn(beanDefinitionNames); 72 | when(applicationContext.getBean("beanA")).thenReturn(new FlipAnnotationTestClient()); 73 | when(flipAnnotationProcessor.getFlipConditionEvaluator(method)).thenReturn(flipConditionEvaluator); 74 | when(flipConditionEvaluator.isEmpty()).thenReturn(true); 75 | 76 | flipAnnotationsStore.buildFlipAnnotationsStore(); 77 | 78 | assertEquals(0, flipAnnotationsStore.getTotalMethodsCached()); 79 | assertEquals(0, flipAnnotationsStore.allMethodsCached().size()); 80 | verify(applicationContext).getBeanDefinitionNames(); 81 | verify(applicationContext).getBean("beanA"); 82 | verify(flipAnnotationProcessor).getFlipConditionEvaluator(method); 83 | verify(flipConditionEvaluator).isEmpty(); 84 | } 85 | 86 | @Test 87 | public void shouldStoreFlipAnnotationsGivenBeansHaveMethodsWithFlipsAnnotationsAndMethodsWereAccessibleWithNonEmptyFlipConditionEvaluator() throws Exception { 88 | String[] beanDefinitionNames = {"beanA"}; 89 | Method method = FlipAnnotationTestClient.class.getMethod("method1"); 90 | FlipConditionEvaluator flipConditionEvaluator = mock(FlipConditionEvaluator.class); 91 | 92 | when(applicationContext.getBeanDefinitionNames()).thenReturn(beanDefinitionNames); 93 | when(applicationContext.getBean("beanA")).thenReturn(new FlipAnnotationTestClient()); 94 | when(flipAnnotationProcessor.getFlipConditionEvaluator(method)).thenReturn(flipConditionEvaluator); 95 | when(flipConditionEvaluator.isEmpty()).thenReturn(false); 96 | 97 | flipAnnotationsStore.buildFlipAnnotationsStore(); 98 | 99 | assertEquals(1, flipAnnotationsStore.getTotalMethodsCached()); 100 | assertEquals(1, flipAnnotationsStore.allMethodsCached().size()); 101 | verify(applicationContext).getBeanDefinitionNames(); 102 | verify(applicationContext).getBean("beanA"); 103 | verify(flipAnnotationProcessor).getFlipConditionEvaluator(method); 104 | verify(flipConditionEvaluator).isEmpty(); 105 | } 106 | 107 | @Test 108 | public void shouldReturnFeatureEnabledGivenFlipConditionEvaluatorReturnsTrue() throws Exception{ 109 | Method method = FlipAnnotationTestClient.class.getMethod("method1"); 110 | FlipConditionEvaluator flipConditionEvaluator = mock(FlipConditionEvaluator.class); 111 | Map store = new HashMap(){{ 112 | put(method, flipConditionEvaluator); 113 | }}; 114 | 115 | ReflectionTestUtils.setField(flipAnnotationsStore, "store", store); 116 | 117 | when(flipConditionEvaluator.evaluate()).thenReturn(true); 118 | boolean featureEnabled = flipAnnotationsStore.isFeatureEnabled(method); 119 | 120 | assertEquals(true, featureEnabled); 121 | verify(flipConditionEvaluator).evaluate(); 122 | } 123 | 124 | @Test 125 | public void shouldReturnFeatureDisabledGivenFlipConditionEvaluatorReturnsFalse() throws Exception{ 126 | Method method = FlipAnnotationTestClient.class.getMethod("method1"); 127 | FlipConditionEvaluator flipConditionEvaluator = mock(FlipConditionEvaluator.class); 128 | Map store = new HashMap(){{ 129 | put(method, flipConditionEvaluator); 130 | }}; 131 | 132 | ReflectionTestUtils.setField(flipAnnotationsStore, "store", store); 133 | 134 | when(flipConditionEvaluator.evaluate()).thenReturn(false); 135 | boolean featureEnabled = flipAnnotationsStore.isFeatureEnabled(method); 136 | 137 | assertEquals(false, featureEnabled); 138 | verify(flipConditionEvaluator).evaluate(); 139 | } 140 | 141 | @Test 142 | public void shouldReturnFeatureEnabledGivenMethodIsNotCached() throws Exception{ 143 | Method method = FlipAnnotationTestClient.class.getMethod("method1"); 144 | FlipConditionEvaluator flipConditionEvaluator = mock(FlipConditionEvaluator.class); 145 | Map store = new HashMap(){{ 146 | put(null, flipConditionEvaluator); 147 | }}; 148 | ReflectionTestUtils.setField(flipAnnotationsStore, "store", store); 149 | 150 | when(flipConditionEvaluatorFactory.getEmptyFlipConditionEvaluator()).thenReturn(flipConditionEvaluator); 151 | when(flipConditionEvaluator.evaluate()).thenReturn(true); 152 | boolean featureEnabled = flipAnnotationsStore.isFeatureEnabled(method); 153 | 154 | assertEquals(true, featureEnabled); 155 | verify(flipConditionEvaluator).evaluate(); 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/utils/AnnotationUtilsUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.utils; 2 | 3 | import org.flips.annotation.FlipBean; 4 | import org.flips.annotation.FlipOff; 5 | import org.flips.annotation.FlipOnOff; 6 | import org.flips.annotation.FlipOnProfiles; 7 | import org.flips.fixture.TestClientFlipAnnotationsWithAnnotationsOnMethod; 8 | import org.flips.fixture.TestClientFlipBeanSpringComponentSource; 9 | import org.flips.model.FlipAnnotationAttributes; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.powermock.api.mockito.PowerMockito; 13 | import org.powermock.core.classloader.annotations.PrepareForTest; 14 | import org.powermock.modules.junit4.PowerMockRunner; 15 | 16 | import java.lang.annotation.Annotation; 17 | import java.lang.annotation.Inherited; 18 | import java.lang.reflect.Constructor; 19 | import java.lang.reflect.Method; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | import static org.junit.Assert.*; 24 | import static org.mockito.Mockito.*; 25 | 26 | @RunWith(PowerMockRunner.class) 27 | @PrepareForTest({org.springframework.core.annotation.AnnotationUtils.class}) 28 | public class AnnotationUtilsUnitTest { 29 | 30 | @Test(expected = AssertionError.class) 31 | public void shouldThrowAssertionErrorGivenAnAttemptIsMadeToInstantiateAnnotationUtils() throws Exception { 32 | Constructor constructor = AnnotationUtils.class.getDeclaredConstructors()[0]; 33 | constructor.setAccessible(true); 34 | constructor.newInstance(); 35 | } 36 | 37 | @Test 38 | public void shouldReturnFlipConditionAnnotationGivenAnnotationAndClassOfAnnotation(){ 39 | FlipOff flipOff = mock(FlipOff.class); 40 | FlipOff flipOffAnnotation = mock(FlipOff.class); 41 | 42 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 43 | when(org.springframework.core.annotation.AnnotationUtils.getAnnotation(flipOff, FlipOff.class)).thenReturn(flipOffAnnotation); 44 | 45 | FlipOff found = AnnotationUtils.getAnnotationOfType(flipOff, FlipOff.class); 46 | assertNotNull(found); 47 | 48 | PowerMockito.verifyStatic(); 49 | org.springframework.core.annotation.AnnotationUtils.getAnnotation(flipOff, FlipOff.class); 50 | } 51 | 52 | @Test 53 | public void shouldReturnAnnotationByTypeGivenArrayOfAnnotations(){ 54 | FlipOff flipOff = mock(FlipOff.class); 55 | FlipOff flipOffAnnotation = mock(FlipOff.class); 56 | Annotation[] annotations = {flipOff}; 57 | 58 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 59 | when(org.springframework.core.annotation.AnnotationUtils.getAnnotation(annotations[0], FlipOff.class)).thenReturn(flipOffAnnotation); 60 | 61 | FlipOff found = AnnotationUtils.findAnnotationByTypeIfAny(annotations, FlipOff.class); 62 | assertNotNull(found); 63 | 64 | PowerMockito.verifyStatic(); 65 | org.springframework.core.annotation.AnnotationUtils.getAnnotation(annotations[0], FlipOff.class); 66 | } 67 | 68 | @Test 69 | public void shouldReturnNullGivenAnnotationTypeIsNotPresentInArrayOfAnnotations(){ 70 | FlipOff flipOff = mock(FlipOff.class); 71 | Annotation[] annotations = {flipOff}; 72 | 73 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 74 | when(org.springframework.core.annotation.AnnotationUtils.getAnnotation(annotations[0], FlipOff.class)).thenReturn(null); 75 | 76 | FlipOff found = AnnotationUtils.findAnnotationByTypeIfAny(annotations, FlipOff.class); 77 | assertNull(found); 78 | 79 | PowerMockito.verifyStatic(); 80 | org.springframework.core.annotation.AnnotationUtils.getAnnotation(annotations[0], FlipOff.class); 81 | } 82 | 83 | @Test 84 | public void shouldReturnTrueAsFlipConditionPresentGivenAnAnnotationWithMetaAnnotationAsFlipOnOff(){ 85 | FlipOnProfiles flipOnProfiles = mock(FlipOnProfiles.class); 86 | Annotation annotation = flipOnProfiles; 87 | 88 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 89 | when(org.springframework.core.annotation.AnnotationUtils.isAnnotationMetaPresent(annotation.getClass(), FlipOnOff.class)).thenReturn(true); 90 | 91 | boolean isFlipConditionDefined = AnnotationUtils.isMetaAnnotationDefined(annotation, FlipOnOff.class); 92 | assertEquals(true, isFlipConditionDefined); 93 | 94 | PowerMockito.verifyStatic(); 95 | org.springframework.core.annotation.AnnotationUtils.isAnnotationMetaPresent(annotation.getClass(), FlipOnOff.class); 96 | } 97 | 98 | @Test 99 | public void shouldReturnFalseAsFlipConditionPresentGivenAnAnnotationWithoutMetaAnnotationAsFlipOnOff(){ 100 | Inherited inherited = mock(Inherited.class); 101 | Annotation annotation = inherited; 102 | 103 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 104 | 105 | boolean isFlipConditionDefined = AnnotationUtils.isMetaAnnotationDefined(annotation, FlipOnOff.class); 106 | assertEquals(false, isFlipConditionDefined); 107 | 108 | PowerMockito.verifyStatic(); 109 | org.springframework.core.annotation.AnnotationUtils.isAnnotationMetaPresent(annotation.getClass(), FlipOnOff.class); 110 | } 111 | 112 | @Test 113 | public void shouldReturnAnnotationsOnMethodGivenMethod(){ 114 | Method method = PowerMockito.mock(Method.class); 115 | Annotation[] annotations = {mock(FlipOff.class)}; 116 | 117 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 118 | when(org.springframework.core.annotation.AnnotationUtils.getAnnotations(method)).thenReturn(annotations); 119 | 120 | Annotation[] annotationsOnMethod = AnnotationUtils.getAnnotations(method); 121 | assertEquals(annotations, annotationsOnMethod); 122 | 123 | PowerMockito.verifyStatic(); 124 | org.springframework.core.annotation.AnnotationUtils.getAnnotations(method); 125 | } 126 | 127 | @Test 128 | public void shouldReturnAnnotationsOnClassGivenClass(){ 129 | Annotation[] annotations = {mock(FlipOff.class)}; 130 | Class clazz = TestClientFlipAnnotationsWithAnnotationsOnMethod.class; 131 | 132 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 133 | when(org.springframework.core.annotation.AnnotationUtils.getAnnotations(any(Class.class))).thenReturn(annotations); 134 | 135 | Annotation[] annotationsOnMethod = AnnotationUtils.getAnnotations(clazz); 136 | assertEquals(annotations, annotationsOnMethod); 137 | 138 | PowerMockito.verifyStatic(); 139 | org.springframework.core.annotation.AnnotationUtils.getAnnotations(clazz); 140 | } 141 | 142 | @Test 143 | public void shouldReturnFlipAnnotationAttributesGivenFlipAnnotation(){ 144 | FlipOnProfiles flipOnProfiles = mock(FlipOnProfiles.class); 145 | Annotation annotation = flipOnProfiles; 146 | 147 | Map annotationAttributes = new HashMap() {{ 148 | put("activeProfiles", new String[]{"dev"}); 149 | }}; 150 | 151 | FlipAnnotationAttributes expectedAnnotationAttributes = new FlipAnnotationAttributes.Builder().addAll(annotationAttributes).build(); 152 | 153 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 154 | when(org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes(annotation)).thenReturn(annotationAttributes); 155 | 156 | FlipAnnotationAttributes flipAnnotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation); 157 | assertEquals(expectedAnnotationAttributes, flipAnnotationAttributes); 158 | 159 | PowerMockito.verifyStatic(); 160 | org.springframework.core.annotation.AnnotationUtils.getAnnotationAttributes(annotation); 161 | } 162 | 163 | @Test 164 | public void shouldReturnAnnotationOfTypeOnClassGivenClass(){ 165 | Class clazz = TestClientFlipBeanSpringComponentSource.class; 166 | FlipBean annotation = mock(FlipBean.class); 167 | 168 | PowerMockito.mockStatic(org.springframework.core.annotation.AnnotationUtils.class); 169 | when(org.springframework.core.annotation.AnnotationUtils.findAnnotation(clazz, FlipBean.class)).thenReturn(annotation); 170 | 171 | FlipBean flipBean = AnnotationUtils.findAnnotation(clazz, FlipBean.class); 172 | assertEquals(annotation, flipBean); 173 | 174 | PowerMockito.verifyStatic(); 175 | org.springframework.core.annotation.AnnotationUtils.findAnnotation(clazz, FlipBean.class); 176 | } 177 | 178 | @Test 179 | public void shouldReturnSingleAnnotationOnMethodGivenMethodAndAnnotationType() throws Exception { 180 | Class clazz = TestClientFlipBeanSpringComponentSource.class; 181 | Method method = clazz.getMethod("noFlip", String.class); 182 | 183 | FlipBean annotation = AnnotationUtils.getAnnotation(method, FlipBean.class); 184 | assertNotNull(annotation); 185 | } 186 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/utils/UtilsUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | public class UtilsUnitTest { 12 | 13 | @Test(expected = InvocationTargetException.class) 14 | public void shouldThrowAssertionErrorGivenAnAttemptIsMadeToInstantiateUtils() throws Exception { 15 | Constructor constructor = Utils.class.getDeclaredConstructors()[0]; 16 | constructor.setAccessible(true); 17 | constructor.newInstance(); 18 | } 19 | 20 | @Test 21 | public void shouldReturnTrueGivenAnEmptyArray(){ 22 | boolean isEmpty = Utils.isEmpty(new Object[]{}); 23 | assertEquals(true, isEmpty); 24 | } 25 | 26 | @Test 27 | public void shouldReturnFalseGivenANonEmptyArray(){ 28 | boolean isEmpty = Utils.isEmpty(new Object[]{"sample"}); 29 | assertEquals(false, isEmpty); 30 | } 31 | 32 | @Test 33 | public void shouldReturnTrueGivenEmptyString(){ 34 | boolean isEmpty = Utils.isEmpty(""); 35 | assertEquals(true, isEmpty); 36 | } 37 | 38 | @Test 39 | public void shouldReturnTrueGivenNonEmptyString(){ 40 | boolean isEmpty = Utils.isEmpty("Sample"); 41 | assertEquals(false, isEmpty); 42 | } 43 | 44 | @Test 45 | public void shouldReturnEmptyArrayGivenClassType(){ 46 | Object emptyArray = Utils.emptyArray(String.class); 47 | assertTrue(emptyArray instanceof String[]); 48 | assertEquals(0, ((String[])emptyArray).length); 49 | } 50 | 51 | @Test 52 | public void shouldReturnAccessibleMethodGivenAPublicMethod() throws Exception { 53 | Method method = Utils.getAccessibleMethod(TestUtils.class.getMethod("greeting")); 54 | assertNotNull(method); 55 | } 56 | 57 | @Test 58 | public void shouldInvokeTargetMethod() throws Exception { 59 | Method method = Utils.getAccessibleMethod(TestUtils.class.getMethod("greeting")); 60 | TestUtils testUtils = new TestUtils(); 61 | 62 | Object result = Utils.invokeMethod(method, testUtils); 63 | assertEquals("Hello", result); 64 | } 65 | } 66 | 67 | class TestUtils{ 68 | public String greeting(){ 69 | return "Hello"; 70 | } 71 | } -------------------------------------------------------------------------------- /flips-core/src/test/java/org/flips/utils/ValidationUtilsUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Objects; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | public class ValidationUtilsUnitTest { 14 | 15 | @Test(expected = InvocationTargetException.class) 16 | public void shouldThrowInvocationTargetExceptionGivenAnAttemptIsMadeToInstantiateValidationUtils() throws Exception { 17 | Constructor constructor = ValidationUtils.class.getDeclaredConstructors()[0]; 18 | constructor.setAccessible(true); 19 | constructor.newInstance(); 20 | } 21 | 22 | @Test(expected = IllegalArgumentException.class) 23 | public void shouldThrowIllegalArgumentExceptionGivenNullValue(){ 24 | ValidationUtils.requireNonEmpty((String)null, "Expected argument can not be null"); 25 | } 26 | 27 | @Test(expected = IllegalArgumentException.class) 28 | public void shouldThrowIllegalArgumentExceptionGivenEmptyValue(){ 29 | ValidationUtils.requireNonEmpty("", "Expected argument can not be null"); 30 | } 31 | 32 | @Test 33 | public void shouldReturnTheValueAsIsGivenNonEmptyValue(){ 34 | String result = ValidationUtils.requireNonEmpty("test", "Expected argument can not be null"); 35 | assertEquals(result, "test"); 36 | } 37 | 38 | @Test(expected = IllegalArgumentException.class) 39 | public void shouldThrowIllegalArgumentExceptionGivenNullArray(){ 40 | ValidationUtils.requireNonEmpty((String[])null, "Expected argument can not be null"); 41 | } 42 | 43 | @Test(expected = IllegalArgumentException.class) 44 | public void shouldThrowIllegalArgumentExceptionGivenEmptyArray(){ 45 | ValidationUtils.requireNonEmpty(new String[]{}, "Expected argument can not be null"); 46 | } 47 | 48 | @Test 49 | public void shouldReturnTheValueAsIsGivenNonEmptyArray(){ 50 | String[] result = ValidationUtils.requireNonEmpty(new String[]{"non-empty"}, "Expected argument can not be null"); 51 | assertEquals(result, new String[]{"non-empty"}); 52 | } 53 | 54 | @Test(expected = IllegalArgumentException.class) 55 | public void shouldThrowIllegalArgumentExceptionGivenNullObject(){ 56 | ValidationUtils.requireNonNull(null, "Expected argument can not be null"); 57 | } 58 | 59 | @Test 60 | public void shouldReturnTheObjectAsIsGivenNonEmptyObject(){ 61 | Map annotationAttributes = new HashMap<>(); 62 | Object result = ValidationUtils.requireNonNull(annotationAttributes, "Expected argument can not be null"); 63 | 64 | assertEquals(annotationAttributes, result); 65 | } 66 | } -------------------------------------------------------------------------------- /flips-web/.gitignore: -------------------------------------------------------------------------------- 1 | logs/** 2 | build/** 3 | .gradle/** 4 | .idea/ 5 | classes/** 6 | *.iml 7 | target/** -------------------------------------------------------------------------------- /flips-web/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | flips 5 | com.github.feature-flip 6 | 1.1 7 | 8 | 9 | 4.0.0 10 | flips-web 11 | jar 12 | 13 | flips-web 14 | https://github.com/Feature-Flip/flips 15 | Flips Web framework, provides a Controller Advice and a Flip Description Controller 16 | 17 | 18 | UTF-8 19 | 20 | 21 | 22 | 23 | junit 24 | junit 25 | test 26 | 27 | 28 | org.mockito 29 | mockito-all 30 | test 31 | 32 | 33 | org.powermock 34 | powermock-api-mockito 35 | test 36 | 37 | 38 | org.powermock 39 | powermock-module-junit4 40 | test 41 | 42 | 43 | org.springframework 44 | spring-test 45 | test 46 | 47 | 48 | org.hamcrest 49 | hamcrest-all 50 | test 51 | 52 | 53 | com.jayway.jsonpath 54 | json-path-assert 55 | test 56 | 57 | 58 | org.springframework 59 | spring-core 60 | 61 | 62 | org.springframework 63 | spring-context 64 | 65 | 66 | org.springframework 67 | spring-web 68 | 69 | 70 | org.springframework 71 | spring-webmvc 72 | 73 | 74 | org.slf4j 75 | slf4j-api 76 | 77 | 78 | org.slf4j 79 | jcl-over-slf4j 80 | 81 | 82 | ch.qos.logback 83 | logback-classic 84 | 85 | 86 | javax.servlet 87 | javax.servlet-api 88 | 3.1.0 89 | 90 | 91 | com.fasterxml.jackson.core 92 | jackson-databind 93 | 94 | 95 | com.github.feature-flip 96 | flips-core 97 | 1.0-SNAPSHOT 98 | 99 | 100 | -------------------------------------------------------------------------------- /flips-web/src/main/java/org/flips/describe/config/FlipWebContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.config; 2 | 3 | import org.flips.config.FlipContextConfiguration; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 7 | 8 | @Configuration 9 | @Import(FlipContextConfiguration.class) 10 | @EnableWebMvc 11 | public class FlipWebContextConfiguration { 12 | } -------------------------------------------------------------------------------- /flips-web/src/main/java/org/flips/describe/controller/FlipDescriptionController.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.controller; 2 | 3 | import org.flips.describe.model.FlipDescription; 4 | import org.flips.store.FlipAnnotationsStore; 5 | import org.flips.utils.Utils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Lazy; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.Comparator; 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.function.Predicate; 15 | 16 | import static java.util.stream.Collectors.toList; 17 | 18 | @RestController 19 | @RequestMapping(value = "/describe") 20 | public class FlipDescriptionController { 21 | 22 | private FlipAnnotationsStore flipAnnotationsStore; 23 | 24 | @Autowired 25 | public FlipDescriptionController(@Lazy FlipAnnotationsStore flipAnnotationsStore) { 26 | this.flipAnnotationsStore = flipAnnotationsStore; 27 | } 28 | 29 | @RequestMapping(value = "/features", method = RequestMethod.GET) 30 | @ResponseBody 31 | public List describeFeatures(){ 32 | return getAllFlipDescription(null); 33 | } 34 | 35 | @RequestMapping(value = "/features/{featureName}", method = RequestMethod.GET) 36 | @ResponseBody 37 | public List describeFeaturesWithFilter(@PathVariable("featureName") String featureName){ 38 | return getAllFlipDescription(featureName); 39 | } 40 | 41 | private List getAllFlipDescription(String featureName) { 42 | return getAllMethodsCached() 43 | .stream() 44 | .filter (getFeatureFilter(featureName)) 45 | .map (this::buildFlipDescription) 46 | .sorted (Comparator.comparing(FlipDescription::getMethodName)) 47 | .collect(toList()); 48 | } 49 | 50 | private Set getAllMethodsCached() { 51 | return flipAnnotationsStore.allMethodsCached(); 52 | } 53 | 54 | private Predicate getFeatureFilter(String featureName){ 55 | if ( Utils.isEmpty(featureName) ) return (Method method) -> true; 56 | return (Method method) -> method.getName().equals(featureName); 57 | } 58 | 59 | private FlipDescription buildFlipDescription(Method method) { 60 | return new FlipDescription(method.getName(), 61 | method.getDeclaringClass().getName(), 62 | flipAnnotationsStore.isFeatureEnabled(method) 63 | ); 64 | } 65 | } -------------------------------------------------------------------------------- /flips-web/src/main/java/org/flips/describe/controlleradvice/FlipControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.controlleradvice; 2 | 3 | import org.flips.describe.model.FeatureNotEnabledErrorResponse; 4 | import org.flips.exception.FeatureNotEnabledException; 5 | import org.flips.exception.FlipBeanFailedException; 6 | import org.springframework.core.Ordered; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.web.bind.annotation.ControllerAdvice; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | import org.springframework.web.bind.annotation.ResponseStatus; 13 | 14 | @ControllerAdvice 15 | @Order(Ordered.LOWEST_PRECEDENCE) 16 | public class FlipControllerAdvice { 17 | 18 | @ExceptionHandler(FeatureNotEnabledException.class) 19 | @ResponseStatus (HttpStatus.NOT_IMPLEMENTED) 20 | @ResponseBody 21 | public FeatureNotEnabledErrorResponse handleFeatureNotEnabledException(FeatureNotEnabledException ex) { 22 | return new FeatureNotEnabledErrorResponse(ex); 23 | } 24 | 25 | @ExceptionHandler(FlipBeanFailedException.class) 26 | @ResponseStatus (HttpStatus.INTERNAL_SERVER_ERROR) 27 | @ResponseBody 28 | public String handleFlipBeanFailedException(FlipBeanFailedException ex) { 29 | return ex.getMessage(); 30 | } 31 | } -------------------------------------------------------------------------------- /flips-web/src/main/java/org/flips/describe/model/FeatureNotEnabledErrorResponse.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.model; 2 | 3 | import org.flips.exception.FeatureNotEnabledException; 4 | import org.flips.model.Feature; 5 | 6 | public class FeatureNotEnabledErrorResponse { 7 | 8 | private String errorMessage; 9 | private Feature feature; 10 | 11 | public FeatureNotEnabledErrorResponse(FeatureNotEnabledException ex) { 12 | this.errorMessage = ex.getMessage(); 13 | this.feature = ex.getFeature(); 14 | } 15 | 16 | public String getErrorMessage(){ 17 | return errorMessage; 18 | } 19 | 20 | public String getFeatureName(){ 21 | return feature.getFeatureName(); 22 | } 23 | 24 | public String getClassName(){ 25 | return feature.getClassName(); 26 | } 27 | } -------------------------------------------------------------------------------- /flips-web/src/main/java/org/flips/describe/model/FlipDescription.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import org.flips.model.Feature; 5 | 6 | import java.io.Serializable; 7 | 8 | public class FlipDescription implements Serializable { 9 | 10 | private Feature feature; 11 | 12 | @JsonProperty("enabled") 13 | private boolean enabled; 14 | 15 | public FlipDescription(String methodName, String className, boolean enabled) { 16 | this.feature = new Feature(methodName, className); 17 | this.enabled = enabled; 18 | } 19 | 20 | @JsonProperty("feature") 21 | public String getMethodName(){ 22 | return feature.getFeatureName(); 23 | } 24 | 25 | @JsonProperty("class") 26 | public String getClassName(){ 27 | return feature.getClassName(); 28 | } 29 | } -------------------------------------------------------------------------------- /flips-web/src/test/java/org/flips/describe/controller/FlipDescriptionControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.controller; 2 | 3 | import org.flips.describe.config.FlipWebContextConfiguration; 4 | import org.hamcrest.Matchers; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.TestPropertySource; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | import org.springframework.test.context.web.WebAppConfiguration; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 15 | import org.springframework.web.context.WebApplicationContext; 16 | 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 19 | 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @ContextConfiguration(classes = FlipWebContextConfiguration.class) 22 | @TestPropertySource(properties = {"default.date.enabled=2015-10-20T16:12:12Z"}) 23 | @WebAppConfiguration 24 | public class FlipDescriptionControllerIntegrationTest { 25 | 26 | @Autowired 27 | private WebApplicationContext webApplicationContext; 28 | 29 | private MockMvc mvc; 30 | 31 | @Before 32 | public void setUp(){ 33 | mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); 34 | } 35 | 36 | @Test 37 | public void shouldReturnAllFeatureDescriptionGivenFeaturesWereCached() throws Exception { 38 | mvc.perform(get("/describe/features")) 39 | .andExpect(jsonPath("$", Matchers.hasSize(5))) 40 | .andExpect(jsonPath("$[0].feature", Matchers.equalTo("feature1"))) 41 | .andExpect(jsonPath("$[0].class", Matchers.equalTo("org.flips.describe.fixture.TestClientFlipAnnotationsDescription"))) 42 | .andExpect(jsonPath("$[0].enabled", Matchers.equalTo(true))) 43 | .andExpect(jsonPath("$[1].feature", Matchers.equalTo("feature2"))) 44 | .andExpect(jsonPath("$[1].class", Matchers.equalTo("org.flips.describe.fixture.TestClientFlipAnnotationsDescription"))) 45 | .andExpect(jsonPath("$[1].enabled", Matchers.equalTo(false))) 46 | .andExpect(jsonPath("$[2].feature", Matchers.equalTo("feature3"))) 47 | .andExpect(jsonPath("$[2].class", Matchers.equalTo("org.flips.describe.fixture.TestClientFlipAnnotationsDescription"))) 48 | .andExpect(jsonPath("$[2].enabled", Matchers.equalTo(true))); 49 | } 50 | } -------------------------------------------------------------------------------- /flips-web/src/test/java/org/flips/describe/controller/FlipDescriptionControllerUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.controller; 2 | 3 | import org.flips.annotation.FlipOff; 4 | import org.flips.store.FlipAnnotationsStore; 5 | import org.hamcrest.Matchers; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.InjectMocks; 10 | import org.mockito.Mock; 11 | import org.mockito.runners.MockitoJUnitRunner; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 14 | 15 | import java.lang.reflect.Method; 16 | import java.util.HashSet; 17 | import java.util.Set; 18 | 19 | import static org.mockito.Mockito.*; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 23 | 24 | @RunWith(MockitoJUnitRunner.class) 25 | public class FlipDescriptionControllerUnitTest { 26 | 27 | private MockMvc mvc; 28 | 29 | @InjectMocks 30 | private FlipDescriptionController flipDescriptionController; 31 | 32 | @Mock 33 | private FlipAnnotationsStore flipAnnotationsStore; 34 | 35 | @Before 36 | public void setUp(){ 37 | mvc = MockMvcBuilders.standaloneSetup(flipDescriptionController).build(); 38 | } 39 | 40 | @Test 41 | public void shouldReturnAllFeatureDescriptionGivenFeaturesWereCached() throws Exception { 42 | Set methods = new HashSet(){{ 43 | add(TestClientFlipAnnotations.class.getMethod("disabledFeature")); 44 | add(TestClientFlipAnnotations.class.getMethod("enabledFeature")); 45 | }} ; 46 | when(flipAnnotationsStore.allMethodsCached()).thenReturn(methods); 47 | when(flipAnnotationsStore.isFeatureEnabled(TestClientFlipAnnotations.class.getMethod("disabledFeature"))).thenReturn(false); 48 | when(flipAnnotationsStore.isFeatureEnabled(TestClientFlipAnnotations.class.getMethod("enabledFeature"))).thenReturn(true); 49 | 50 | mvc.perform (get("/describe/features")) 51 | .andExpect(status().isOk()) 52 | .andExpect(jsonPath("$[0].feature", Matchers.equalTo("disabledFeature"))) 53 | .andExpect(jsonPath("$[0].class", Matchers.equalTo("org.flips.describe.controller.TestClientFlipAnnotations"))) 54 | .andExpect(jsonPath("$[0].enabled", Matchers.equalTo(false))) 55 | .andExpect(jsonPath("$[1].feature", Matchers.equalTo("enabledFeature"))) 56 | .andExpect(jsonPath("$[1].class", Matchers.equalTo("org.flips.describe.controller.TestClientFlipAnnotations"))) 57 | .andExpect(jsonPath("$[1].enabled", Matchers.equalTo(true))); 58 | 59 | verify(flipAnnotationsStore).allMethodsCached(); 60 | verify(flipAnnotationsStore).isFeatureEnabled(TestClientFlipAnnotations.class.getMethod("disabledFeature")); 61 | verify(flipAnnotationsStore).isFeatureEnabled(TestClientFlipAnnotations.class.getMethod("enabledFeature")); 62 | } 63 | 64 | @Test 65 | public void shouldReturnFeatureDescriptionFilteredByNameGivenFeaturesWereCached() throws Exception { 66 | Set methods = new HashSet(){{ 67 | add(TestClientFlipAnnotations.class.getMethod("disabledFeature")); 68 | add(TestClientFlipAnnotations.class.getMethod("enabledFeature")); 69 | }} ; 70 | when(flipAnnotationsStore.allMethodsCached()).thenReturn(methods); 71 | when(flipAnnotationsStore.isFeatureEnabled(TestClientFlipAnnotations.class.getMethod("disabledFeature"))).thenReturn(false); 72 | 73 | mvc.perform (get("/describe/features/disabledFeature")) 74 | .andExpect(status().isOk()) 75 | .andExpect(jsonPath("$", Matchers.hasSize(1))) 76 | .andExpect(jsonPath("$[0].feature", Matchers.equalTo("disabledFeature"))) 77 | .andExpect(jsonPath("$[0].class", Matchers.equalTo("org.flips.describe.controller.TestClientFlipAnnotations"))) 78 | .andExpect(jsonPath("$[0].enabled", Matchers.equalTo(false))); 79 | 80 | verify(flipAnnotationsStore).allMethodsCached(); 81 | verify(flipAnnotationsStore).isFeatureEnabled(TestClientFlipAnnotations.class.getMethod("disabledFeature")); 82 | verify(flipAnnotationsStore, never()).isFeatureEnabled(TestClientFlipAnnotations.class.getMethod("enabledFeature")); 83 | } 84 | 85 | @Test 86 | public void shouldReturnEmptyFeatureDescriptionFilteredByNameGivenNoFeaturesMatchTheGivenCriteria() throws Exception { 87 | Set methods = new HashSet(){{ 88 | add(TestClientFlipAnnotations.class.getMethod("disabledFeature")); 89 | add(TestClientFlipAnnotations.class.getMethod("enabledFeature")); 90 | }} ; 91 | when(flipAnnotationsStore.allMethodsCached()).thenReturn(methods); 92 | 93 | mvc.perform (get("/describe/features/noFeature")) 94 | .andExpect(status().isOk()) 95 | .andExpect(jsonPath("$", Matchers.hasSize(0))); 96 | 97 | verify(flipAnnotationsStore).allMethodsCached(); 98 | verify(flipAnnotationsStore, never()).isFeatureEnabled(any(Method.class)); 99 | verify(flipAnnotationsStore, never()).isFeatureEnabled(any(Method.class)); 100 | } 101 | 102 | @Test 103 | public void shouldReturnEmptyFeatureDescriptionGivenNoFeaturesWereCached() throws Exception { 104 | Set methods = new HashSet<>(); 105 | when(flipAnnotationsStore.allMethodsCached()).thenReturn(methods); 106 | 107 | mvc.perform (get("/describe/features")) 108 | .andExpect(status().isOk()) 109 | .andExpect(jsonPath("$", Matchers.hasSize(0))); 110 | 111 | verify(flipAnnotationsStore).allMethodsCached(); 112 | verify(flipAnnotationsStore, never()).isFeatureEnabled(any(Method.class)); 113 | verify(flipAnnotationsStore, never()).isFeatureEnabled(any(Method.class)); 114 | } 115 | } 116 | 117 | class TestClientFlipAnnotations { 118 | @FlipOff 119 | public void disabledFeature(){ 120 | } 121 | 122 | public void enabledFeature(){ 123 | } 124 | } -------------------------------------------------------------------------------- /flips-web/src/test/java/org/flips/describe/controlleradvice/FlipControllerAdviceIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.controlleradvice; 2 | 3 | import org.flips.describe.config.FlipWebContextConfiguration; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import org.springframework.test.context.web.WebAppConfiguration; 12 | import org.springframework.test.web.servlet.MockMvc; 13 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 14 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 15 | import org.springframework.web.context.WebApplicationContext; 16 | 17 | import static org.hamcrest.core.IsEqual.equalTo; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 21 | 22 | @RunWith(SpringJUnit4ClassRunner.class) 23 | @ContextConfiguration(classes = FlipWebContextConfiguration.class) 24 | @WebAppConfiguration 25 | public class FlipControllerAdviceIntegrationTest { 26 | 27 | @Autowired 28 | private WebApplicationContext webApplicationContext; 29 | 30 | private MockMvc mockMvc; 31 | 32 | @Before 33 | public void setUp() { 34 | mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); 35 | } 36 | 37 | @Test 38 | public void shouldReturnNotImplementedGivenFeatureIsDisabled() throws Exception { 39 | mockMvc.perform(get("/test/featureDisabled/")) 40 | .andExpect(status().is(HttpStatus.NOT_IMPLEMENTED.value())) 41 | .andExpect(jsonPath("$.errorMessage" ,equalTo("Feature not enabled, identified by method public void org.flips.describe.fixture.TestClientController.featureDisabled()"))) 42 | .andExpect(jsonPath("$.featureName" ,equalTo("featureDisabled"))) 43 | .andExpect(jsonPath("$.className" ,equalTo("org.flips.describe.fixture.TestClientController"))); 44 | } 45 | 46 | @Test 47 | public void shouldReturnStatusOkGivenFeatureIsEnabled() throws Exception { 48 | mockMvc.perform(get("/test/featureEnabled/" )) 49 | .andExpect(status().is(200)); 50 | } 51 | 52 | @Test 53 | public void shouldInvokeAlternateBeanGivenFlipBeanAnnotationIsPresent() throws Exception { 54 | mockMvc.perform(get("/test/className/")) 55 | .andExpect(status().is(HttpStatus.OK.value())) 56 | .andExpect(MockMvcResultMatchers.content().string("org.flips.describe.fixture.TestClientController")); 57 | } 58 | 59 | @Test 60 | public void shouldReturnNotImplementedGivenFeatureIsDisabledInAlternateBean() throws Exception { 61 | mockMvc.perform(get("/test/methodName/")) 62 | .andExpect(status().is(HttpStatus.NOT_IMPLEMENTED.value())) 63 | .andExpect(jsonPath("$.errorMessage" ,equalTo("Feature not enabled, identified by method public void org.flips.describe.fixture.TestClientController.methodName()"))) 64 | .andExpect(jsonPath("$.featureName" ,equalTo("methodName"))) 65 | .andExpect(jsonPath("$.className" ,equalTo("org.flips.describe.fixture.TestClientController"))); 66 | } 67 | } -------------------------------------------------------------------------------- /flips-web/src/test/java/org/flips/describe/controlleradvice/FlipControllerAdviceUnitTest.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.controlleradvice; 2 | 3 | import org.flips.describe.model.FeatureNotEnabledErrorResponse; 4 | import org.flips.exception.FeatureNotEnabledException; 5 | import org.flips.exception.FlipBeanFailedException; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.runners.MockitoJUnitRunner; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | 15 | @RunWith(MockitoJUnitRunner.class) 16 | public class FlipControllerAdviceUnitTest { 17 | 18 | @InjectMocks 19 | private FlipControllerAdvice flipControllerAdvice; 20 | 21 | @Test 22 | public void shouldHandleFeatureNotEnabledExceptionGivenFeatureIsDisabled() throws NoSuchMethodException { 23 | Method method = this.getClass().getMethod("testClientMethod"); 24 | FeatureNotEnabledErrorResponse response = flipControllerAdvice.handleFeatureNotEnabledException(new FeatureNotEnabledException("feature disabled",method)); 25 | 26 | assertEquals("feature disabled" , response.getErrorMessage()); 27 | assertEquals("testClientMethod" , response.getFeatureName()); 28 | assertEquals("org.flips.describe.controlleradvice.FlipControllerAdviceUnitTest", response.getClassName()); 29 | } 30 | 31 | @Test 32 | public void shouldHandleFlipBeanFailedExceptionGivenFipWithBeanFailed() throws NoSuchMethodException { 33 | String response = flipControllerAdvice.handleFlipBeanFailedException(new FlipBeanFailedException("test")); 34 | assertEquals("test", response); 35 | } 36 | 37 | public void testClientMethod(){} 38 | } -------------------------------------------------------------------------------- /flips-web/src/test/java/org/flips/describe/fixture/TestClientController.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.fixture; 2 | 3 | 4 | import org.flips.annotation.FlipOff; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | @RequestMapping(value = "/test") 13 | public class TestClientController { 14 | 15 | @FlipOff 16 | @RequestMapping(value = "/featureDisabled", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 17 | public void featureDisabled() { 18 | } 19 | 20 | @RequestMapping(value = "/featureEnabled", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 21 | public void featureEnabled() { 22 | } 23 | 24 | @RequestMapping(value = "/className", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 25 | @ResponseBody 26 | public String className() { 27 | return TestClientController.class.getName(); 28 | } 29 | 30 | @RequestMapping(value = "/methodName", method = RequestMethod.GET) 31 | @FlipOff 32 | public void methodName() { 33 | } 34 | } -------------------------------------------------------------------------------- /flips-web/src/test/java/org/flips/describe/fixture/TestClientControllerOther.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.fixture; 2 | 3 | import org.flips.annotation.FlipBean; 4 | import org.springframework.http.MediaType; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping(value = "/test-other") 12 | public class TestClientControllerOther { 13 | 14 | @FlipBean(with = TestClientController.class) 15 | @RequestMapping(value = "/className", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 16 | @ResponseBody 17 | public String className() { 18 | return TestClientControllerOther.class.getName(); 19 | } 20 | 21 | @FlipBean(with = TestClientController.class) 22 | @RequestMapping(value = "/methodName", method = RequestMethod.GET) 23 | public void methodName() { 24 | } 25 | } -------------------------------------------------------------------------------- /flips-web/src/test/java/org/flips/describe/fixture/TestClientFlipAnnotationsDescription.java: -------------------------------------------------------------------------------- 1 | package org.flips.describe.fixture; 2 | 3 | 4 | import org.flips.annotation.FlipOff; 5 | import org.flips.annotation.FlipOnDateTime; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class TestClientFlipAnnotationsDescription { 10 | 11 | @FlipOnDateTime(cutoffDateTimeProperty = "default.date.enabled") 12 | public void feature1(){ 13 | } 14 | 15 | @FlipOff 16 | public void feature2(){ 17 | } 18 | 19 | @FlipOnDateTime(cutoffDateTimeProperty = "default.date.enabled") 20 | public void feature3(){ 21 | } 22 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | com.github.feature-flip 6 | flips 7 | 1.1 8 | pom 9 | 10 | Feature Flips for Java 11 | https://github.com/Feature-Flip/flips 12 | 13 | Flips is a Feature Toggle library for Java. Features can be flipped ON / OFF based on various conditions, 14 | or a combination of different conditions. 15 | Flips provide various annotations FlipOnDateTime, FlipOnProfiles etc. to flip ON / OFF a feature. 16 | 17 | 18 | 19 | scm:git:git@github.com:Feature-Flip/flips.git 20 | scm:git:git@github.com:Feature-Flip/flips.git 21 | git@github.com:Feature-Flip/flips.git 22 | HEAD 23 | 24 | 25 | 26 | 27 | Apache 2 28 | http://www.apache.org/licenses/LICENSE-2.0.html 29 | repo 30 | 31 | 32 | 33 | 34 | 35 | Sarthak Makhija 36 | sarthak.makhija@gmail.com 37 | https://github.com/SarthakMakhija/ 38 | 39 | 40 | 41 | 42 | https://github.com/Feature-Flip/flips/issues 43 | GitHub Issues 44 | 45 | 46 | 47 | 1.8 48 | 1.8 49 | 50 | UTF-8 51 | 52 | 4.12 53 | 4.3.18.RELEASE 54 | 1.10.19 55 | 1.6.4 56 | 1.6.4 57 | 1.3 58 | 0.7.6.201602180812 59 | 1.8.9 60 | 1.7.25 61 | 1.2.3 62 | 2.8.11.1 63 | 2.3.0 64 | 65 | 66 | 67 | flips-core 68 | flips-web 69 | 70 | 71 | 72 | 73 | 74 | junit 75 | junit 76 | ${version.junit} 77 | 78 | 79 | org.mockito 80 | mockito-all 81 | ${version.mockito} 82 | 83 | 84 | org.powermock 85 | powermock-api-mockito 86 | ${version.powermockito.api} 87 | 88 | 89 | org.powermock 90 | powermock-module-junit4 91 | ${version.powermockito.junit4} 92 | 93 | 94 | org.hamcrest 95 | hamcrest-all 96 | ${version.hamcrest.all} 97 | 98 | 99 | com.jayway.jsonpath 100 | json-path-assert 101 | ${version.jsonpath} 102 | 103 | 104 | org.springframework 105 | spring-test 106 | ${version.spring} 107 | 108 | 109 | org.springframework 110 | spring-core 111 | ${version.spring} 112 | 113 | 114 | org.springframework 115 | spring-web 116 | ${version.spring} 117 | 118 | 119 | org.springframework 120 | spring-webmvc 121 | ${version.spring} 122 | 123 | 124 | org.springframework 125 | spring-context 126 | ${version.spring} 127 | 128 | 129 | commons-logging 130 | commons-logging 131 | 132 | 133 | 134 | 135 | org.slf4j 136 | slf4j-api 137 | ${version.sl4j} 138 | 139 | 140 | org.slf4j 141 | jcl-over-slf4j 142 | ${version.sl4j} 143 | 144 | 145 | ch.qos.logback 146 | logback-classic 147 | ${version.logback} 148 | 149 | 150 | com.fasterxml.jackson.core 151 | jackson-databind 152 | ${version.jackson.library} 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | org.jacoco 161 | jacoco-maven-plugin 162 | ${version.jacoco} 163 | 164 | 165 | prepare-agent 166 | 167 | prepare-agent 168 | 169 | 170 | 171 | report 172 | prepare-package 173 | 174 | report 175 | 176 | 177 | 178 | post-unit-test 179 | test 180 | 181 | report 182 | 183 | 184 | target/jacoco.exec 185 | target/jacoco 186 | 187 | 188 | 189 | 190 | 191 | 192 | org.eluder.coveralls 193 | coveralls-maven-plugin 194 | 4.3.0 195 | 196 | qnn8YRs2yuOxzJkYvUku9YNQWvfLkhGMe 197 | 198 | 199 | 200 | 201 | org.apache.maven.plugins 202 | maven-release-plugin 203 | 2.5.3 204 | 205 | true 206 | false 207 | release 208 | deploy 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | release 217 | 218 | 219 | 220 | org.apache.maven.plugins 221 | maven-gpg-plugin 222 | 1.6 223 | 224 | 225 | sign-artifacts 226 | verify 227 | 228 | sign 229 | 230 | 231 | 232 | 233 | 234 | org.apache.maven.plugins 235 | maven-source-plugin 236 | 2.2.1 237 | 238 | 239 | attach-sources 240 | 241 | jar-no-fork 242 | 243 | 244 | 245 | 246 | 247 | org.apache.maven.plugins 248 | maven-javadoc-plugin 249 | 2.9.1 250 | 251 | 252 | attach-javadocs 253 | 254 | jar 255 | 256 | 257 | -Xdoclint:none 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | ossrh 270 | https://oss.sonatype.org/content/repositories/snapshots 271 | 272 | 273 | ossrh 274 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 275 | 276 | 277 | 278 | --------------------------------------------------------------------------------