diff --git a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUser.java b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUser.java index 955417a4..24206a66 100755 --- a/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUser.java +++ b/src/main/java/com/devcycle/sdk/server/common/model/DevCycleUser.java @@ -139,15 +139,17 @@ static void setCustomValue(Map customData, String key, Value val * * @param ctx A context to load a targeting key and user data from * @return An initialized DevCycleUser with data from the context - * @throws TargetingKeyMissingError if the targeting key or user_id attribute is not set + * @throws TargetingKeyMissingError if none of the targeting key, user_id, or userId attributes are set or valid */ public static DevCycleUser fromEvaluationContext(EvaluationContext ctx) { String userId = ""; if (ctx != null && ctx.getTargetingKey() != null && !ctx.getTargetingKey().isEmpty()) { userId = ctx.getTargetingKey(); - } else if (ctx != null && ctx.getValue("user_id") != null) { + } else if (ctx != null && ctx.getValue("user_id") != null && ctx.getValue("user_id").isString()) { userId = ctx.getValue("user_id").asString(); + } else if (ctx != null && ctx.getValue("userId") != null && ctx.getValue("userId").isString()) { + userId = ctx.getValue("userId").asString(); } if (userId == null || userId.isEmpty()) { @@ -160,7 +162,7 @@ public static DevCycleUser fromEvaluationContext(EvaluationContext ctx) { Map privateCustomData = new LinkedHashMap<>(); for (String key : ctx.keySet()) { - if (key.equals("user_id") || key.equals("targetingKey")) { + if (key.equals("user_id") || key.equals("targetingKey") || key.equals("userId")) { continue; } diff --git a/src/test/java/com/devcycle/sdk/server/common/model/DevCycleUserTest.java b/src/test/java/com/devcycle/sdk/server/common/model/DevCycleUserTest.java index 99a4d550..1aa68ee1 100644 --- a/src/test/java/com/devcycle/sdk/server/common/model/DevCycleUserTest.java +++ b/src/test/java/com/devcycle/sdk/server/common/model/DevCycleUserTest.java @@ -70,6 +70,113 @@ public void testCreateUserOnlyUserId() { Assert.assertEquals(user.getUserId(), "user-4567"); } + @Test + public void testFromEvaluationContextWithUserId() { + Map apiAttrs = new LinkedHashMap(); + apiAttrs.put("userId", new Value("test-userId-123")); + + // ensure fallback to userId when target key and user_id are null + EvaluationContext ctx = new MutableContext(null, apiAttrs); + DevCycleUser user = DevCycleUser.fromEvaluationContext(ctx); + Assert.assertEquals(user.getUserId(), "test-userId-123"); + + // ensure fallback to userId when target key and user_id are empty + ctx = new MutableContext("", apiAttrs); + user = DevCycleUser.fromEvaluationContext(ctx); + Assert.assertEquals(user.getUserId(), "test-userId-123"); + } + + @Test + public void testFromEvaluationContextUserIdPriorityOrder() { + Map apiAttrs = new LinkedHashMap(); + apiAttrs.put("user_id", new Value("user_id_value")); + apiAttrs.put("userId", new Value("userId_value")); + + // Test priority: targetingKey > user_id > userId + // When all three are present, targetingKey should win + EvaluationContext ctx = new MutableContext("targetingKey_value", apiAttrs); + DevCycleUser user = DevCycleUser.fromEvaluationContext(ctx); + Assert.assertEquals(user.getUserId(), "targetingKey_value"); + + // When targetingKey is null, user_id should win over userId + ctx = new MutableContext(null, apiAttrs); + user = DevCycleUser.fromEvaluationContext(ctx); + Assert.assertEquals(user.getUserId(), "user_id_value"); + + // When targetingKey is empty, user_id should win over userId + ctx = new MutableContext("", apiAttrs); + user = DevCycleUser.fromEvaluationContext(ctx); + Assert.assertEquals(user.getUserId(), "user_id_value"); + + // When only userId is present, it should be used + Map userIdOnlyAttrs = new LinkedHashMap(); + userIdOnlyAttrs.put("userId", new Value("userId_only_value")); + ctx = new MutableContext(null, userIdOnlyAttrs); + user = DevCycleUser.fromEvaluationContext(ctx); + Assert.assertEquals(user.getUserId(), "userId_only_value"); + } + + @Test + public void testFromEvaluationContextUserIdExcludedFromCustomData() { + Map apiAttrs = new LinkedHashMap(); + apiAttrs.put("userId", new Value("test-userId-123")); + apiAttrs.put("customField", new Value("customValue")); + + // When userId is used as the user ID, it should be excluded from custom data + EvaluationContext ctx = new MutableContext(null, apiAttrs); + DevCycleUser user = DevCycleUser.fromEvaluationContext(ctx); + + Assert.assertEquals(user.getUserId(), "test-userId-123"); + Assert.assertNotNull(user.getCustomData()); + Assert.assertEquals(user.getCustomData().size(), 1); + Assert.assertEquals(user.getCustomData().get("customField"), "customValue"); + Assert.assertFalse(user.getCustomData().containsKey("userId")); + } + + @Test + public void testFromEvaluationContextAllUserIdFieldsExcludedFromCustomData() { + Map apiAttrs = new LinkedHashMap(); + apiAttrs.put("user_id", new Value("user_id_value")); + apiAttrs.put("userId", new Value("userId_value")); + apiAttrs.put("customField", new Value("customValue")); + + // All user ID fields should be excluded from custom data regardless of which is used + EvaluationContext ctx = new MutableContext(null, apiAttrs); + DevCycleUser user = DevCycleUser.fromEvaluationContext(ctx); + + Assert.assertEquals(user.getUserId(), "user_id_value"); + Assert.assertNotNull(user.getCustomData()); + Assert.assertEquals(user.getCustomData().size(), 1); + Assert.assertEquals(user.getCustomData().get("customField"), "customValue"); + Assert.assertFalse(user.getCustomData().containsKey("userId")); + Assert.assertFalse(user.getCustomData().containsKey("user_id")); + Assert.assertFalse(user.getCustomData().containsKey("targetingKey")); + } + + @Test + public void testFromEvaluationContextInvalidUserIdTypes() { + Map apiAttrs = new LinkedHashMap(); + + // Test with non-string userId value - should be ignored + apiAttrs.put("userId", new Value(123)); + EvaluationContext ctx = new MutableContext(null, apiAttrs); + + try { + DevCycleUser.fromEvaluationContext(ctx); + Assert.fail("Expected TargetingKeyMissingError"); + } catch (TargetingKeyMissingError e) { + // expected + } + + // Test with non-string user_id value but valid userId string - should use userId + apiAttrs.put("user_id", new Value(456)); + apiAttrs.put("userId", new Value("valid-userId")); + ctx = new MutableContext(null, apiAttrs); + + DevCycleUser user = DevCycleUser.fromEvaluationContext(ctx); + Assert.assertEquals(user.getUserId(), "valid-userId"); + } + @Test public void testCreateUserWithAttributes() { Map apiAttrs = new LinkedHashMap();