diff --git a/client/LICENSE b/LICENSE similarity index 93% rename from client/LICENSE rename to LICENSE index e83b0b46c..fb8a05ab1 100644 --- a/client/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2018 Split Software +Copyright © 2020 Split Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/client/CHANGES.txt b/client/CHANGES.txt index 36cc2ff53..4a07f1ce7 100644 --- a/client/CHANGES.txt +++ b/client/CHANGES.txt @@ -1,5 +1,8 @@ CHANGES +3.3.3 (Apr 7, 2020) + - Fix issue regarding special characters come from split/segments fetchers. + 3.3.2 (Jan 24, 2020) - Shade com.google.guava as well diff --git a/client/pom.xml b/client/pom.xml index e89303180..0349c631e 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 3.3.2 + 3.3.3 java-client jar diff --git a/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java b/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java index a04027c87..956a12efc 100644 --- a/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java +++ b/client/src/main/java/io/split/client/HttpSegmentChangeFetcher.java @@ -16,6 +16,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import static com.google.common.base.Preconditions.checkNotNull; @@ -73,7 +74,7 @@ public SegmentChange fetch(String segmentName, long since) { - String json = EntityUtils.toString(response.getEntity()); + String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); if (_log.isDebugEnabled()) { _log.debug("Received json: " + json); } diff --git a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java index e70085e54..2ca8819b2 100644 --- a/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java +++ b/client/src/main/java/io/split/client/HttpSplitChangeFetcher.java @@ -16,6 +16,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import static com.google.common.base.Preconditions.checkNotNull; @@ -68,7 +69,7 @@ public SplitChange fetch(long since) { } - String json = EntityUtils.toString(response.getEntity()); + String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); if (_log.isDebugEnabled()) { _log.debug("Received json: " + json); } diff --git a/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java b/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java index 23e957bad..4d8531764 100644 --- a/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/HttpSegmentChangeFetcherTest.java @@ -1,12 +1,19 @@ package io.split.client; +import io.split.client.dtos.SegmentChange; import io.split.engine.metrics.Metrics; +import org.apache.http.HttpEntity; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -48,4 +55,31 @@ public void testCustomURLAppendingPathNoBackslash() throws URISyntaxException { Assert.assertThat(fetcher.getTarget().toString(), Matchers.is(Matchers.equalTo("https://kubernetesturl.com/split/api/segmentChanges"))); } + @Test + public void testFetcherWithSpecialCharacters() throws URISyntaxException, IOException { + URI rootTarget = URI.create("https://api.split.io/api/segmentChanges"); + + CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class); + CloseableHttpResponse httpResponseMock = Mockito.mock(CloseableHttpResponse.class, Mockito.RETURNS_DEEP_STUBS); + StatusLine statusLineMock = Mockito.mock(StatusLine.class); + HttpEntity entityMock = Mockito.mock(HttpEntity.class); + + Mockito.when(statusLineMock.getStatusCode()).thenReturn(200); + Mockito.when(httpResponseMock.getStatusLine()).thenReturn(statusLineMock); + Mockito.when(entityMock.getContent()).thenReturn(getClass().getClassLoader().getResourceAsStream("segment-change-special-chatacters.json")); + Mockito.when(httpResponseMock.getEntity()).thenReturn(entityMock); + + Mockito.when(httpClientMock.execute((HttpUriRequest) Mockito.anyObject())).thenReturn(httpResponseMock); + + Metrics.NoopMetrics metrics = new Metrics.NoopMetrics(); + HttpSegmentChangeFetcher fetcher = HttpSegmentChangeFetcher.create(httpClientMock, rootTarget, metrics); + + SegmentChange change = fetcher.fetch("some_segment", 1234567); + + Assert.assertNotNull(change); + Assert.assertEquals(1, change.added.size()); + Assert.assertEquals("grüne_Straße", change.added.get(0)); + Assert.assertEquals(1, change.removed.size()); + Assert.assertEquals("other_user", change.removed.get(0)); + } } diff --git a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java index d797ff37d..2ee6de304 100644 --- a/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java +++ b/client/src/test/java/io/split/client/HttpSplitChangeFetcherTest.java @@ -1,14 +1,23 @@ package io.split.client; +import io.split.client.dtos.Split; +import io.split.client.dtos.SplitChange; import io.split.engine.metrics.Metrics; +import org.apache.http.HttpEntity; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.Map; public class HttpSplitChangeFetcherTest { @@ -48,4 +57,35 @@ public void testCustomURLAppendingPathNoBackslash() throws URISyntaxException { Assert.assertThat(fetcher.getTarget().toString(), Matchers.is(Matchers.equalTo("https://kubernetesturl.com/split/api/splitChanges"))); } + @Test + public void testFetcherWithSpecialCharacters() throws URISyntaxException, IOException { + URI rootTarget = URI.create("https://api.split.io"); + + CloseableHttpClient httpClientMock = Mockito.mock(CloseableHttpClient.class); + CloseableHttpResponse httpResponseMock = Mockito.mock(CloseableHttpResponse.class, Mockito.RETURNS_DEEP_STUBS); + StatusLine statusLineMock = Mockito.mock(StatusLine.class); + HttpEntity entityMock = Mockito.mock(HttpEntity.class); + + Mockito.when(statusLineMock.getStatusCode()).thenReturn(200); + Mockito.when(httpResponseMock.getStatusLine()).thenReturn(statusLineMock); + Mockito.when(entityMock.getContent()).thenReturn(getClass().getClassLoader().getResourceAsStream("split-change-special-characters.json")); + Mockito.when(httpResponseMock.getEntity()).thenReturn(entityMock); + + Mockito.when(httpClientMock.execute((HttpUriRequest) Mockito.anyObject())).thenReturn(httpResponseMock); + + Metrics.NoopMetrics metrics = new Metrics.NoopMetrics(); + HttpSplitChangeFetcher fetcher = HttpSplitChangeFetcher.create(httpClientMock, rootTarget, metrics); + + SplitChange change = fetcher.fetch(1234567); + + Assert.assertNotNull(change); + Assert.assertEquals(1, change.splits.size()); + Assert.assertNotNull(change.splits.get(0)); + + Split split = change.splits.get(0); + Map configs = split.configurations; + Assert.assertEquals(2, configs.size()); + Assert.assertEquals("{\"test\": \"blue\",\"grüne Straße\": 13}", configs.get("on")); + Assert.assertEquals("{\"test\": \"blue\",\"size\": 15}", configs.get("off")); + } } diff --git a/client/src/test/resources/segment-change-special-chatacters.json b/client/src/test/resources/segment-change-special-chatacters.json new file mode 100644 index 000000000..f8a2ccb06 --- /dev/null +++ b/client/src/test/resources/segment-change-special-chatacters.json @@ -0,0 +1,11 @@ +{ + "name": "employees", + "added": [ + "grüne_Straße" + ], + "removed": [ + "other_user" + ], + "since": -1, + "till": 1489542661161 +} diff --git a/client/src/test/resources/split-change-special-characters.json b/client/src/test/resources/split-change-special-characters.json new file mode 100644 index 000000000..276ab19ee --- /dev/null +++ b/client/src/test/resources/split-change-special-characters.json @@ -0,0 +1,55 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "DEMO_MURMUR2", + "trafficAllocation": 100, + "trafficAllocationSeed": 1314112417, + "seed": -2059033614, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "of", + "changeNumber": 1491244291288, + "algo": 2, + "configurations": { + "on": "{\"test\": \"blue\",\"grüne Straße\": 13}", + "off": "{\"test\": \"blue\",\"size\": 15}" + }, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": null + }, + "matcherType": "ALL_KEYS", + "negate": false, + "userDefinedSegmentMatcherData": null, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "of", + "size": 100 + } + ], + "label": "in segment all" + } + ] + } + ], + "since": 1491244291288, + "till": 1491244291288 +} diff --git a/pom.xml b/pom.xml index 228edcafe..1cb5c1638 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.split.client java-client-parent - 3.3.2 + 3.3.3 diff --git a/testing/pom.xml b/testing/pom.xml index d19608296..8782d2ca6 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -6,7 +6,7 @@ io.split.client java-client-parent - 3.3.2 + 3.3.3 java-client-testing