Skip to content

Commit ed42e7f

Browse files
Add springboot smoke tests for the open feature SDK (#9961)
Add springboot smoke tests for the open feature SDK
1 parent 9885025 commit ed42e7f

29 files changed

+6930
-2
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@
116116
**/DataStreams* @DataDog/data-streams-monitoring
117117

118118
# @DataDog/feature-flagging-and-experimentation-sdk
119-
/products/feature-flagging/ @DataDog/feature-flagging-and-experimentation-sdk
119+
/dd-smoke-tests/openfeature/ @DataDog/feature-flagging-and-experimentation-sdk
120+
/products/feature-flagging/ @DataDog/feature-flagging-and-experimentation-sdk
120121

121122
# @DataDog/profiling-java
122123
/dd-java-agent/agent-profiling/ @DataDog/profiling-java
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import org.springframework.boot.gradle.tasks.bundling.BootJar
2+
3+
plugins {
4+
id 'java'
5+
id 'org.springframework.boot' version '2.7.15'
6+
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
7+
}
8+
9+
apply from: "$rootDir/gradle/java.gradle"
10+
apply from: "$rootDir/gradle/spring-boot-plugin.gradle"
11+
description = 'Open Feature provider Smoke Tests.'
12+
13+
testJvmConstraints {
14+
minJavaVersion = JavaVersion.VERSION_11
15+
}
16+
17+
tasks.named("compileJava", JavaCompile) {
18+
configureCompiler(it, 11, JavaVersion.VERSION_11)
19+
}
20+
21+
dependencies {
22+
implementation project(':products:feature-flagging:api')
23+
implementation 'org.springframework.boot:spring-boot-starter-web'
24+
25+
testImplementation project(':dd-smoke-tests')
26+
testImplementation project(':products:feature-flagging:lib')
27+
}
28+
29+
tasks.withType(Test).configureEach {
30+
dependsOn "bootJar"
31+
def bootJarTask = tasks.named('bootJar', BootJar)
32+
jvmArgumentProviders.add(new CommandLineArgumentProvider() {
33+
@Override
34+
Iterable<String> asArguments() {
35+
return bootJarTask.map { ["-Ddatadog.smoketest.springboot.shadowJar.path=${it.archiveFile.get()}"] }.get()
36+
}
37+
})
38+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package datadog.smoketest.springboot;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
public class SpringbootApplication {
8+
9+
public static void main(final String[] args) {
10+
SpringApplication.run(SpringbootApplication.class, args);
11+
}
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package datadog.smoketest.springboot.openfeature;
2+
3+
import datadog.trace.api.openfeature.Provider;
4+
import dev.openfeature.sdk.Client;
5+
import dev.openfeature.sdk.OpenFeatureAPI;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
@Configuration
10+
public class OpenFeatureConfiguration {
11+
12+
@Bean
13+
public Client openFeatureClient() {
14+
OpenFeatureAPI api = OpenFeatureAPI.getInstance();
15+
api.setProviderAndWait(new Provider());
16+
return api.getClient();
17+
}
18+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package datadog.smoketest.springboot.openfeature;
2+
3+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
4+
5+
import dev.openfeature.sdk.Client;
6+
import dev.openfeature.sdk.EvaluationContext;
7+
import dev.openfeature.sdk.FlagEvaluationDetails;
8+
import dev.openfeature.sdk.MutableContext;
9+
import dev.openfeature.sdk.Structure;
10+
import dev.openfeature.sdk.Value;
11+
import java.util.HashMap;
12+
import java.util.List;
13+
import java.util.Map;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.web.bind.annotation.PostMapping;
18+
import org.springframework.web.bind.annotation.RequestBody;
19+
import org.springframework.web.bind.annotation.RequestMapping;
20+
import org.springframework.web.bind.annotation.RestController;
21+
22+
@RestController
23+
@RequestMapping("/openfeature")
24+
public class OpenFeatureController {
25+
26+
private static final Logger LOGGER = LoggerFactory.getLogger(OpenFeatureController.class);
27+
28+
private final Client client;
29+
30+
public OpenFeatureController(final Client client) {
31+
this.client = client;
32+
}
33+
34+
@PostMapping(
35+
value = "/evaluate",
36+
consumes = APPLICATION_JSON_VALUE,
37+
produces = APPLICATION_JSON_VALUE)
38+
public ResponseEntity<?> evaluate(@RequestBody final EvaluateRequest request) {
39+
try {
40+
final EvaluationContext context = context(request);
41+
FlagEvaluationDetails<?> details;
42+
switch (request.getVariationType()) {
43+
case "BOOLEAN":
44+
details =
45+
client.getBooleanDetails(
46+
request.getFlag(), (Boolean) request.getDefaultValue(), context);
47+
break;
48+
case "STRING":
49+
details =
50+
client.getStringDetails(
51+
request.getFlag(), (String) request.getDefaultValue(), context);
52+
break;
53+
case "INTEGER":
54+
final Number integerEval = (Number) request.getDefaultValue();
55+
details = client.getIntegerDetails(request.getFlag(), integerEval.intValue(), context);
56+
break;
57+
case "NUMERIC":
58+
final Number doubleEval = (Number) request.getDefaultValue();
59+
details = client.getDoubleDetails(request.getFlag(), doubleEval.doubleValue(), context);
60+
break;
61+
case "JSON":
62+
details =
63+
client.getObjectDetails(
64+
request.getFlag(), Value.objectToValue(request.getDefaultValue()), context);
65+
break;
66+
default:
67+
throw new IllegalArgumentException(
68+
"Unsupported variation type: " + request.getVariationType());
69+
}
70+
71+
final Object value = details.getValue();
72+
final Map<String, Object> result = new HashMap<>();
73+
result.put("flagKey", details.getFlagKey());
74+
result.put("variant", details.getVariant());
75+
result.put("reason", details.getReason());
76+
result.put("value", value instanceof Value ? context.convertValue((Value) value) : value);
77+
result.put("errorCode", details.getErrorCode());
78+
result.put("errorMessage", details.getErrorMessage());
79+
result.put("flagMetadata", details.getFlagMetadata().asUnmodifiableMap());
80+
return ResponseEntity.ok(result);
81+
} catch (Throwable e) {
82+
LOGGER.error("Error on resolution", e);
83+
return ResponseEntity.internalServerError().body(e.getMessage());
84+
}
85+
}
86+
87+
private static EvaluationContext context(final EvaluateRequest request) {
88+
final MutableContext context = new MutableContext();
89+
context.setTargetingKey(request.getTargetingKey());
90+
if (request.attributes != null) {
91+
request.attributes.forEach(
92+
(key, value) -> {
93+
if (value instanceof Boolean) {
94+
context.add(key, (Boolean) value);
95+
} else if (value instanceof Integer) {
96+
context.add(key, (Integer) value);
97+
} else if (value instanceof Double) {
98+
context.add(key, (Double) value);
99+
} else if (value instanceof String) {
100+
context.add(key, (String) value);
101+
} else if (value instanceof Map) {
102+
context.add(key, Value.objectToValue(value).asStructure());
103+
} else if (value instanceof List) {
104+
context.add(key, Value.objectToValue(value).asList());
105+
} else {
106+
context.add(key, (Structure) null);
107+
}
108+
});
109+
}
110+
return context;
111+
}
112+
113+
public static class EvaluateRequest {
114+
private String flag;
115+
private String variationType;
116+
private Object defaultValue;
117+
private String targetingKey;
118+
private Map<String, Object> attributes;
119+
120+
public Map<String, Object> getAttributes() {
121+
return attributes;
122+
}
123+
124+
public void setAttributes(Map<String, Object> attributes) {
125+
this.attributes = attributes;
126+
}
127+
128+
public Object getDefaultValue() {
129+
return defaultValue;
130+
}
131+
132+
public void setDefaultValue(Object defaultValue) {
133+
this.defaultValue = defaultValue;
134+
}
135+
136+
public String getFlag() {
137+
return flag;
138+
}
139+
140+
public void setFlag(String flag) {
141+
this.flag = flag;
142+
}
143+
144+
public String getTargetingKey() {
145+
return targetingKey;
146+
}
147+
148+
public void setTargetingKey(String targetingKey) {
149+
this.targetingKey = targetingKey;
150+
}
151+
152+
public String getVariationType() {
153+
return variationType;
154+
}
155+
156+
public void setVariationType(String variationType) {
157+
this.variationType = variationType;
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)