Skip to content

Commit 504bdf5

Browse files
chrfwowtoddbaert
authored andcommitted
Add tests to verify unwrapping of nested contexts
Signed-off-by: christian.lutnik <christian.lutnik@dynatrace.com>
1 parent f261776 commit 504bdf5

File tree

2 files changed

+91
-13
lines changed

2 files changed

+91
-13
lines changed

src/main/java/dev/openfeature/sdk/LayeredEvaluationContext.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,19 +235,37 @@ public Map<String, Value> asUnmodifiableMap() {
235235

236236
@Override
237237
public Map<String, Object> asObjectMap() {
238-
// Build the object map directly from the resolved attribute map,
239-
// so this stays consistent with equals/hashCode and asMap().
240-
Map<String, Value> resolved = getResolvedMap();
241-
if (resolved.isEmpty()) {
238+
if (keySet != null && keySet.isEmpty()) {
242239
return new HashMap<>(0);
243240
}
244241

245-
HashMap<String, Object> map = HashMapUtils.forEntries(resolved.size());
246-
for (Map.Entry<String, Value> entry : resolved.entrySet()) {
247-
Value value = entry.getValue();
248-
// Value is responsible for exposing the underlying Java representation.
249-
map.put(entry.getKey(), value == null ? null : value.asObject());
242+
HashMap<String, Object> map;
243+
if (keySet != null) {
244+
// use helper to size the map based on expected entries
245+
map = HashMapUtils.forEntries(keySet.size());
246+
} else {
247+
map = new HashMap<>();
248+
}
249+
250+
if (apiContext != null) {
251+
map.putAll(apiContext.asObjectMap());
252+
}
253+
if (transactionContext != null) {
254+
map.putAll(transactionContext.asObjectMap());
250255
}
256+
if (clientContext != null) {
257+
map.putAll(clientContext.asObjectMap());
258+
}
259+
if (invocationContext != null) {
260+
map.putAll(invocationContext.asObjectMap());
261+
}
262+
if (hookContexts != null) {
263+
for (int i = 0; i < hookContexts.size(); i++) {
264+
EvaluationContext hookContext = hookContexts.get(i);
265+
map.putAll(hookContext.asObjectMap());
266+
}
267+
}
268+
251269
return map;
252270
}
253271

src/test/java/dev/openfeature/sdk/LayeredEvaluationContextTest.java

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package dev.openfeature.sdk;
22

3-
import static org.junit.jupiter.api.Assertions.*;
3+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertFalse;
6+
import static org.junit.jupiter.api.Assertions.assertNotNull;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
49

510
import java.util.Map;
611
import java.util.Set;
@@ -151,7 +156,7 @@ void keySetIsGeneratedCorrectly() {
151156
"api",
152157
"override",
153158
"targetingKey" // expected, even though not explicitly set
154-
);
159+
);
155160

156161
assertEquals(expectedKeys, layeredContext.keySet());
157162
// cached key set
@@ -192,7 +197,7 @@ void mapIsGeneratedCorrectly() {
192197
"api", new Value("api"),
193198
"override", new Value("hook"),
194199
"targetingKey", new Value("hook-level") // expected, even though not explicitly set
195-
);
200+
);
196201

197202
assertEquals(expectedKeys, layeredContext.asMap());
198203
assertEquals(expectedKeys, layeredContext.asUnmodifiableMap());
@@ -239,7 +244,7 @@ void mapIsGeneratedCorrectly() {
239244
"api", "api",
240245
"override", "hook",
241246
"targetingKey", "hook-level" // expected, even though not explicitly set in map
242-
);
247+
);
243248

244249
assertEquals(expectedKeys, layeredContext.asObjectMap());
245250
}
@@ -257,6 +262,61 @@ void creatingMapWithCachedNonEmptyKeySetWorks() {
257262
assertNotNull(layeredContext.keySet());
258263
assertEquals(apiContext.asObjectMap(), layeredContext.asObjectMap());
259264
}
265+
266+
@Test
267+
void nestedContextsAreUnwrappedCorrectly() {
268+
var innerApiContext = new ImmutableContext(Map.of("inner", new Value("api")));
269+
var outerApiContext = new ImmutableContext(Map.of("outer", new Value(innerApiContext)));
270+
271+
var innerClientContext = new ImmutableContext(Map.of("inner", new Value("client")));
272+
var outerClientContext = new ImmutableContext(Map.of("outer", new Value(innerClientContext)));
273+
var layeredContext = new LayeredEvaluationContext(outerApiContext, null, outerClientContext, null);
274+
275+
var objectMap = layeredContext.asObjectMap();
276+
277+
assertEquals(Map.of("outer", Map.of("inner", "client")), objectMap);
278+
}
279+
280+
@Test
281+
void nestedHookContextsAreUnwrappedCorrectly() {
282+
var innerApiContext = new ImmutableContext(Map.of("inner", new Value("api")));
283+
var outerApiContext = new ImmutableContext(Map.of("outer", new Value(innerApiContext)));
284+
285+
var innerClientContext = new ImmutableContext(Map.of("inner", new Value("client")));
286+
var outerClientContext = new ImmutableContext(Map.of("outer", new Value(innerClientContext)));
287+
var layeredContext = new LayeredEvaluationContext(outerApiContext, null, outerClientContext, null);
288+
289+
var innerHookContext = new ImmutableContext(Map.of("inner", new Value("hook")));
290+
var outerHookContext = new ImmutableContext(Map.of("outer", new Value(innerHookContext)));
291+
292+
layeredContext.putHookContext(outerHookContext);
293+
294+
var objectMap = layeredContext.asObjectMap();
295+
296+
assertEquals(Map.of("outer", Map.of("inner", "hook")), objectMap);
297+
}
298+
299+
@Test
300+
void objectMapIsMutable() {
301+
LayeredEvaluationContext layeredContext =
302+
new LayeredEvaluationContext(apiContext, transactionContext, clientContext, invocationContext);
303+
304+
var objectMap = layeredContext.asObjectMap();
305+
assertDoesNotThrow(() -> objectMap.put("a", "b"));
306+
assertEquals("b", objectMap.get("a"));
307+
}
308+
309+
@Test
310+
void mutatingObjectMapHasNoSideEffects() {
311+
LayeredEvaluationContext layeredContext =
312+
new LayeredEvaluationContext(apiContext, transactionContext, clientContext, invocationContext);
313+
314+
var objectMap1 = layeredContext.asObjectMap();
315+
objectMap1.put("a", "b");
316+
317+
var objectMap2 = layeredContext.asObjectMap();
318+
assertNull(objectMap2.get("a"));
319+
}
260320
}
261321

262322
@Nested

0 commit comments

Comments
 (0)