Repository: Unleash/unleash-client-java
Pull request #255 (2024-11-19 12:17:20)

UnleashContext.Builder Convenience
- made builder populate special properties by name

<!-- Thanks for creating a PR! To make it easier for reviewers and everyone else to understand what your changes relate to, please add some relevant content to the headings below. Feel free to ignore or delete sections that you don't think are relevant. Thank you! ❤️ -->

## About the changes
Making the io.getunleash.UnleashContext.Builder populate special properties for convenience. 
Sometimes users may specify a property by name and not by method. Currently this property will not be picked up.
This PR addresses this and picks up the special properties for the convenience of the user.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/70963730/badge)](https://coveralls.io/builds/70963730)

coverage: 86.515% (+0.2%) from 86.328%
when pulling **859b3248826e46f7b6d9cb5fc6ed8ff792f3b8aa on wayfair-contribs:unleashContext.Builder-Convenience**
into **71efb3cae9d4c27fb6b41a0ba9a0a9d900a081e7 on Unleash:main**.


Patch:
diff --git a/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java b/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java
index 912bdbe9..0faa50f8 100644
--- a/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java
+++ b/examples/cli-example/src/main/java/io/getunleash/example/AdvancedConstraints.java
@@ -8,27 +8,53 @@ import io.getunleash.repository.FeatureToggleResponse;
 import io.getunleash.util.UnleashConfig;
 
 public class AdvancedConstraints {
+
     public static void main(String[] args) throws InterruptedException {
-        UnleashConfig config = UnleashConfig.builder().appName("client-example.advanced")
-                .customHttpHeader("Authorization",
-                        "*:production.ZvzGdauVXYPyevrQVqnt8LSRHKuW")
-                .unleashAPI("http://localhost:1500/api").instanceId("example")
+        UnleashConfig config = UnleashConfig.builder()
+                .appName("client-example.advanced.java")
+                .customHttpHeader(
+                        "Authorization",
+                        getOrElse("UNLEASH_API_TOKEN",
+                                "default:default.a45fede67f99b17f67312c93e00f448340e7af4ace2b0de2650f5a99"))
+                .unleashAPI(getOrElse("UNLEASH_API_URL", "http://localhost:3063/api"))
+                .instanceId("java-example")
                 .synchronousFetchOnInitialisation(true)
-            .subscriber(new UnleashSubscriber() {
-                @Override
-                public void togglesFetched(FeatureToggleResponse toggleResponse) {
-                    System.out.println(toggleResponse);
-                    System.out.println(toggleResponse.getToggleCollection().getFeatures().size());
-                }
-            })
+                .sendMetricsInterval(30)
+                .subscriber(
+                        new UnleashSubscriber() {
+                            @Override
+                            public void togglesFetched(
+                                    FeatureToggleResponse toggleResponse) {
+                                System.out.println(toggleResponse);
+                                System.out.println(
+                                        toggleResponse
+                                                .getToggleCollection()
+                                                .getFeatures()
+                                                .size());
+                            }
+                        })
                 .build();
         Unleash unleash = new DefaultUnleash(config);
         while (true) {
             Thread.sleep(2000);
-            UnleashContext context = UnleashContext.builder().addProperty("semver", "1.5.2").build();
-            System.out.println(unleash.isEnabled("advanced.constraints", context)); // expect this to be true
-            UnleashContext smallerSemver = UnleashContext.builder().addProperty("semver", "1.1.0").build();
-            System.out.println(unleash.isEnabled("advanced.constraints", smallerSemver)); // expect this to be false
+            UnleashContext context = UnleashContext.builder()
+                    .addProperty("semver", "1.5.2")
+                    .build();
+            System.out.println(
+                    unleash.isEnabled("advanced.constraints", context)); // expect this to be true
+            UnleashContext smallerSemver = UnleashContext.builder()
+                    .addProperty("semver", "1.1.0")
+                    .build();
+            System.out.println(
+                    unleash.isEnabled("advanced.constraints", smallerSemver)); // expect this to be false
+        }
+    }
+
+    public static String getOrElse(String key, String defaultValue) {
+        String value = System.getenv(key);
+        if (value == null) {
+            return defaultValue;
         }
+        return value;
     }
 }
diff --git a/pom.xml b/pom.xml
index 03a89916..e3755ef0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>io.getunleash</groupId>
     <artifactId>unleash-client-java</artifactId>
-    <version>9.2.5-SNAPSHOT</version>
+    <version>9.2.6-SNAPSHOT</version>
 
     <properties>
         <version.slf4j>2.0.13</version.slf4j>
@@ -52,7 +52,7 @@
         <url>https://github.com/Unleash/unleash-client-java</url>
         <connection>scm:git:https://github.com/Unleash/unleash-client-java.git</connection>
         <developerConnection>scm:git:https://github.com/Unleash/unleash-client-java.git</developerConnection>
-      <tag>unleash-client-java-9.2.2</tag>
+      <tag>unleash-client-java-9.2.5</tag>
   </scm>
 
     <dependencies>
diff --git a/src/main/java/io/getunleash/UnleashContext.java b/src/main/java/io/getunleash/UnleashContext.java
index fa619cbf..a8c3f50d 100644
--- a/src/main/java/io/getunleash/UnleashContext.java
+++ b/src/main/java/io/getunleash/UnleashContext.java
@@ -129,7 +129,7 @@ public class UnleashContext {
             context.sessionId.ifPresent(val -> this.sessionId = val);
             context.remoteAddress.ifPresent(val -> this.remoteAddress = val);
             context.currentTime.ifPresent(val -> this.currentTime = val);
-            context.properties.forEach(this.properties::put);
+            this.properties.putAll(context.properties);
         }
 
         public Builder appName(String appName) {
@@ -173,7 +173,26 @@ public class UnleashContext {
         }
 
         public Builder addProperty(String name, String value) {
-            properties.put(name, value);
+            switch (name) {
+                case "environment":
+                    this.environment = value;
+                    break;
+                case "appName":
+                    this.appName = value;
+                    break;
+                case "userId":
+                    this.userId = value;
+                    break;
+                case "sessionId":
+                    this.sessionId = value;
+                    break;
+                case "remoteAddress":
+                    this.remoteAddress = value;
+                    break;
+                default:
+                    this.properties.put(name, value);
+            }
+
             return this;
         }
 
diff --git a/src/main/java/io/getunleash/util/ConstraintMerger.java b/src/main/java/io/getunleash/util/ConstraintMerger.java
index 28db759e..7440decb 100644
--- a/src/main/java/io/getunleash/util/ConstraintMerger.java
+++ b/src/main/java/io/getunleash/util/ConstraintMerger.java
@@ -17,7 +17,8 @@ public class ConstraintMerger {
                         Optional.ofNullable(strategy.getConstraints())
                                 .orElseGet(Collections::emptyList),
                         Optional.ofNullable(strategy.getSegments())
-                                .orElseGet(Collections::emptyList).stream()
+                                .orElseGet(Collections::emptyList)
+                                .stream()
                                 .map(repository::getSegment)
                                 .map(s -> s == null ? DENY_SEGMENT : s)
                                 .map(Segment::getConstraints)


Test patch:
diff --git a/src/test/java/io/getunleash/UnleashContextTest.java b/src/test/java/io/getunleash/UnleashContextTest.java
index fa6b9211..f5e84fe4 100644
--- a/src/test/java/io/getunleash/UnleashContextTest.java
+++ b/src/test/java/io/getunleash/UnleashContextTest.java
@@ -1,10 +1,12 @@
 package io.getunleash;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
 import io.getunleash.util.UnleashConfig;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+
 public class UnleashContextTest {
 
     @Test
@@ -39,7 +41,7 @@ public class UnleashContextTest {
         assertThat(context.getRemoteAddress()).hasValue("127.0.0.1");
         assertThat(context.getEnvironment()).hasValue("prod");
         assertThat(context.getAppName()).hasValue("myapp");
-        assertThat(context.getProperties().get("test")).isEqualTo("me");
+        assertThat(context.getProperties()).containsExactly(entry("test", "me"));
     }
 
     @Test
@@ -70,7 +72,7 @@ public class UnleashContextTest {
     }
 
     @Test
-    public void should_not_ovveride_static_context_fields() {
+    public void should_not_override_static_context_fields() {
         UnleashContext context =
                 UnleashContext.builder()
                         .userId("test@mail.com")
@@ -96,4 +98,36 @@ public class UnleashContextTest {
         assertThat(enhanced.getEnvironment()).hasValue("env");
         assertThat(enhanced.getAppName()).hasValue("myApp");
     }
+
+    @Nested
+    class BuilderTest {
+
+        @Test
+        void should_set_special_properties() {
+            final UnleashContext context = UnleashContext.builder()
+                .addProperty("userId", "test@mail.com")
+                .addProperty("sessionId", "123")
+                .addProperty("remoteAddress", "127.0.0.1")
+                .addProperty("environment", "env")
+                .addProperty("appName", "myApp")
+                .build();
+
+            assertThat(context.getUserId()).contains("test@mail.com");
+            assertThat(context.getSessionId()).contains("123");
+            assertThat(context.getRemoteAddress()).contains("127.0.0.1");
+            assertThat(context.getEnvironment()).contains("env");
+            assertThat(context.getAppName()).contains("myApp");
+        }
+
+        @Test
+        void should_set_non_special_properties() {
+            final UnleashContext context = UnleashContext.builder()
+                .addProperty("foo", "bar")
+                .build();
+
+            assertThat(context.getProperties()).containsExactly(entry("foo", "bar"));
+        }
+
+    }
+
 }
diff --git a/src/test/java/io/getunleash/util/IpAddressMatcherTest.java b/src/test/java/io/getunleash/util/IpAddressMatcherTest.java
index f9d48f37..8b2bf5cb 100644
--- a/src/test/java/io/getunleash/util/IpAddressMatcherTest.java
+++ b/src/test/java/io/getunleash/util/IpAddressMatcherTest.java
@@ -19,7 +19,9 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import org.junit.jupiter.api.Test;
 
-/** @author Luke Taylor */
+/**
+ * @author Luke Taylor
+ */
 class IpAddressMatcherTest {
     private final IpAddressMatcher v6matcher = new IpAddressMatcher("fe80::21f:5bff:fe33:bd68");
     private final IpAddressMatcher v4matcher = new IpAddressMatcher("192.168.1.104");