Skip to content

Commit e71cedb

Browse files
authored
Merge pull request #53 from isa-group/hotfix/feature-evaluation-crashing
fix: empty strings as default value for expression and serverExpression
2 parents aaba802 + 5eee4bd commit e71cedb

5 files changed

Lines changed: 101 additions & 9 deletions

File tree

src/main/java/io/github/isagroup/models/FeatureStatus.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ public void setLimit(Object limit) {
3838
}
3939

4040
public static Optional<Boolean> computeFeatureEvaluation(String expression, PlanContextManager planContextManager) {
41-
ExpressionParser expressionParser = new SpelExpressionParser();
42-
EvaluationContext evaluationContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
41+
42+
if (expression == null) {
43+
throw new IllegalArgumentException(
44+
"expression was null. A expression must be provided to compute its evaluation");
45+
}
4346

4447
if (expression.trim().isEmpty()) {
4548
return Optional.of(false);
4649
}
4750

51+
ExpressionParser expressionParser = new SpelExpressionParser();
52+
EvaluationContext evaluationContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
53+
4854
return Optional.ofNullable(expressionParser.parseExpression(expression).getValue(evaluationContext,
4955
planContextManager,
5056
Boolean.class));

src/main/java/io/github/isagroup/services/parsing/FeatureParser.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,23 @@ private static void loadBasicAttributes(Feature feature, String featureName, Map
249249
+ "; Current defaultValue: " + (String) map.get("defaultValue"));
250250
}
251251

252-
try {
253-
feature.setExpression((String) map.get("expression"));
254-
feature.setServerExpression((String) map.get("serverExpression"));
255-
} catch (NoSuchElementException e) {
256-
throw new PricingParsingException("The feature " + featureName
257-
+ " does not have either an evaluation expression or serverExpression.");
252+
if (map.get("expression") != null && !(map.get("expression") instanceof String)) {
253+
throw new PricingParsingException("'expression' must be a String");
254+
}
255+
String expression = "";
256+
if (map.get("expression") != null) {
257+
expression = (String) map.get("expression");
258+
}
259+
feature.setExpression(expression);
260+
261+
if (map.get("serverExpression") != null && !(map.get("serverExpression") instanceof String)) {
262+
throw new PricingParsingException("'expression' must be a String");
263+
}
264+
String serverExpression = "";
265+
if (map.get("serverExpression") != null) {
266+
serverExpression = (String) map.get("serverExpression");
258267
}
268+
feature.setServerExpression(serverExpression);
259269

260270
String featureTag = (String) map.get("tag");
261271

src/test/java/io/github/isagroup/PricingEvaluatorUtilTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import java.util.Map;
55

66
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.api.DisplayName;
78
import org.junit.jupiter.api.Test;
9+
import org.springframework.expression.spel.SpelEvaluationException;
810

911
import io.github.isagroup.services.jwt.PricingJwtUtils;
1012

@@ -127,4 +129,46 @@ void tokenUserContextTest() {
127129
"UserContext havePetsDashboard value is not the same after token codification");
128130

129131
}
132+
133+
private class NoExpressionsInFeatures extends PricingContext {
134+
135+
@Override
136+
public String getConfigFilePath() {
137+
return "pricing/feature-inoperative-no-expression.yaml";
138+
}
139+
140+
@Override
141+
public String getJwtSecret() {
142+
return "Testing";
143+
}
144+
145+
@Override
146+
public Map<String, Object> getUserContext() {
147+
Map<String, Object> map = new HashMap<>();
148+
map.put("pets", 1);
149+
return map;
150+
}
151+
152+
@Override
153+
public String getUserPlan() {
154+
return "BASIC";
155+
}
156+
157+
}
158+
159+
@Test
160+
@DisplayName("Given a feature with no 'expression' should be disabled in the pricing token")
161+
void givenAFeatureWithNoExpressionShouldNotThrowWhenComputingUserToken() {
162+
163+
PricingContext context = new NoExpressionsInFeatures();
164+
PricingEvaluatorUtil pricingEvaluatorUtil = new PricingEvaluatorUtil(context);
165+
PricingJwtUtils pricingJwtUtils = new PricingJwtUtils(context);
166+
assertDoesNotThrow(() -> pricingEvaluatorUtil.generateUserToken());
167+
168+
Map<String, Map<String, Object>> featureStatuses = pricingJwtUtils
169+
.getFeaturesFromJwtToken(pricingEvaluatorUtil.generateUserToken());
170+
assertFalse((boolean) featureStatuses.get("support").get("eval"));
171+
172+
}
173+
130174
}

src/test/java/io/github/isagroup/parsing/positive/FeatureParserTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ void givenGuaranteeFeatureShouldParse() {
4848
guaranteeFeature.setDefaultValue("99.9%");
4949
guaranteeFeature.setValueType(ValueType.TEXT);
5050
guaranteeFeature.setDocURL("https://example.org");
51+
guaranteeFeature.setExpression("");
52+
guaranteeFeature.setServerExpression("");
5153

5254
PricingManager pricingManager = YamlUtils
5355
.retrieveManagerFromYaml(POSITIVE_CASES + "type/guarantee-feature.yml");
@@ -65,7 +67,7 @@ void givenAFeatureExpressionShouldBeOptional() {
6567
.retrieveManagerFromYaml(POSITIVE_CASES + "expression/expression-is-null.yml");
6668

6769
assertNotNull(pricingManager.getFeatures().get(featName));
68-
assertNull(pricingManager.getFeatures().get(featName).getExpression());
70+
assertEquals("", pricingManager.getFeatures().get(featName).getExpression());
6971

7072
}
7173
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
saasName: Inoperative support
2+
syntaxVersion: "2.1"
3+
createdAt: "2024-01-15"
4+
currency: EUR
5+
features:
6+
pets:
7+
valueType: BOOLEAN
8+
defaultValue: true
9+
expression: userContext['pets'] < planContext['usageLimits']['maxPets']
10+
serverExpression: userContext['pets'] <= planContext['usageLimits']['maxPets']
11+
type: DOMAIN
12+
support:
13+
valueType: BOOLEAN
14+
defaultValue: false
15+
type: SUPPORT
16+
usageLimits:
17+
maxPets:
18+
valueType: NUMERIC
19+
defaultValue: 2
20+
unit: pet
21+
type: NON_RENEWABLE
22+
linkedFeatures:
23+
- pets
24+
plans:
25+
BASIC:
26+
description: Basic plan
27+
price: 0.0
28+
unit: user/month
29+
features: null
30+
usageLimits: null

0 commit comments

Comments
 (0)