diff --git a/client/pom.xml b/client/pom.xml index b8d94bba..6bd936d6 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 4.12.1 + 4.13.0-rc1 java-client jar diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java index 2f29c171..ec4e49cf 100644 --- a/client/src/main/java/io/split/client/SplitClientConfig.java +++ b/client/src/main/java/io/split/client/SplitClientConfig.java @@ -6,6 +6,7 @@ import io.split.integrations.IntegrationsConfig; import io.split.storages.enums.OperationMode; import io.split.storages.enums.StorageMode; +import io.split.service.HttpAuthScheme; import org.apache.hc.core5.http.HttpHost; import pluggable.CustomStorageWrapper; @@ -91,6 +92,7 @@ public class SplitClientConfig { private final HashSet _flagSetsFilter; private final int _invalidSets; private final CustomHeaderDecorator _customHeaderDecorator; + private final HttpAuthScheme _authScheme; public static Builder builder() { @@ -148,7 +150,8 @@ private SplitClientConfig(String endpoint, ThreadFactory threadFactory, HashSet flagSetsFilter, int invalidSets, - CustomHeaderDecorator customHeaderDecorator) { + CustomHeaderDecorator customHeaderDecorator, + HttpAuthScheme authScheme) { _endpoint = endpoint; _eventsEndpoint = eventsEndpoint; _featuresRefreshRate = pollForFeatureChangesEveryNSeconds; @@ -201,6 +204,7 @@ private SplitClientConfig(String endpoint, _flagSetsFilter = flagSetsFilter; _invalidSets = invalidSets; _customHeaderDecorator = customHeaderDecorator; + _authScheme = authScheme; Properties props = new Properties(); try { @@ -408,6 +412,9 @@ public int getInvalidSets() { public CustomHeaderDecorator customHeaderDecorator() { return _customHeaderDecorator; } + public HttpAuthScheme authScheme() { + return _authScheme; + } public static final class Builder { @@ -466,6 +473,7 @@ public static final class Builder { private HashSet _flagSetsFilter = new HashSet<>(); private int _invalidSetsCount = 0; private CustomHeaderDecorator _customHeaderDecorator = null; + private HttpAuthScheme _authScheme = null; public Builder() { } @@ -960,6 +968,17 @@ public Builder customHeaderDecorator(CustomHeaderDecorator customHeaderDecorator return this; } + /** + * Authentication Scheme + * + * @param authScheme + * @return this builder + */ + public Builder authScheme(HttpAuthScheme authScheme) { + _authScheme = authScheme; + return this; + } + /** * Thread Factory * @@ -1120,7 +1139,8 @@ public SplitClientConfig build() { _threadFactory, _flagSetsFilter, _invalidSetsCount, - _customHeaderDecorator); + _customHeaderDecorator, + _authScheme); } } } \ No newline at end of file diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java index a783b144..68eeba67 100644 --- a/client/src/main/java/io/split/client/SplitFactoryImpl.java +++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java @@ -57,8 +57,10 @@ import io.split.engine.segments.SegmentChangeFetcher; import io.split.engine.segments.SegmentSynchronizationTaskImp; import io.split.integrations.IntegrationsConfig; +import io.split.service.HttpAuthScheme; import io.split.service.SplitHttpClient; import io.split.service.SplitHttpClientImpl; +import io.split.service.SplitHttpClientKerberosImpl; import io.split.storages.SegmentCache; import io.split.storages.SegmentCacheConsumer; import io.split.storages.SegmentCacheProducer; @@ -525,6 +527,13 @@ private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClient httpClientbuilder = setupProxy(httpClientbuilder, config); } + if (config.authScheme() == HttpAuthScheme.KERBEROS) { + return SplitHttpClientKerberosImpl.create( + requestDecorator, + apiToken, + sdkMetadata); + + } return SplitHttpClientImpl.create(httpClientbuilder.build(), requestDecorator, apiToken, diff --git a/client/src/main/java/io/split/service/HttpAuthScheme.java b/client/src/main/java/io/split/service/HttpAuthScheme.java new file mode 100644 index 00000000..1753f736 --- /dev/null +++ b/client/src/main/java/io/split/service/HttpAuthScheme.java @@ -0,0 +1,5 @@ +package io.split.service; + +public enum HttpAuthScheme { + KERBEROS +} diff --git a/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java b/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java new file mode 100644 index 00000000..82dd9a35 --- /dev/null +++ b/client/src/main/java/io/split/service/SplitHttpClientKerberosImpl.java @@ -0,0 +1,211 @@ +package io.split.service; + +import io.split.client.RequestDecorator; +import io.split.client.dtos.SplitHttpResponse; +import io.split.client.utils.SDKMetadata; +import io.split.engine.common.FetchOptions; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.message.BasicHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class SplitHttpClientKerberosImpl implements SplitHttpClient { + + private static final Logger _log = LoggerFactory.getLogger(SplitHttpClientKerberosImpl.class); + private static final String HEADER_CACHE_CONTROL_NAME = "Cache-Control"; + private static final String HEADER_CACHE_CONTROL_VALUE = "no-cache"; + private static final String HEADER_API_KEY = "Authorization"; + private static final String HEADER_CLIENT_KEY = "SplitSDKClientKey"; + private static final String HEADER_CLIENT_MACHINE_NAME = "SplitSDKMachineName"; + private static final String HEADER_CLIENT_MACHINE_IP = "SplitSDKMachineIP"; + private static final String HEADER_CLIENT_VERSION = "SplitSDKVersion"; + + private final RequestDecorator _requestDecorator; + private final String _apikey; + private final SDKMetadata _metadata; + + public static SplitHttpClientKerberosImpl create(RequestDecorator requestDecorator, + String apikey, + SDKMetadata metadata) { + return new SplitHttpClientKerberosImpl(requestDecorator, apikey, metadata); + } + + SplitHttpClientKerberosImpl(RequestDecorator requestDecorator, + String apikey, + SDKMetadata metadata) { + _requestDecorator = requestDecorator; + _apikey = apikey; + _metadata = metadata; + } + + public synchronized SplitHttpResponse get(URI uri, FetchOptions options, Map> additionalHeaders) { + HttpURLConnection getHttpURLConnection = null; + try { + getHttpURLConnection = (HttpURLConnection) uri.toURL().openConnection(); + return doGet(getHttpURLConnection, options, additionalHeaders); + } catch (Exception e) { + throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e); + } finally { + try { + if (getHttpURLConnection != null) { + getHttpURLConnection.disconnect(); + } + } catch (Exception e) { + _log.error(String.format("Could not close HTTP URL Connection: %s", e), e); + } + } + } + public SplitHttpResponse doGet(HttpURLConnection getHttpURLConnection, FetchOptions options, Map> additionalHeaders) { + try { + getHttpURLConnection.setRequestMethod("GET"); + setBasicHeaders(getHttpURLConnection); + setAdditionalAndDecoratedHeaders(getHttpURLConnection, additionalHeaders); + + if (options.cacheControlHeadersEnabled()) { + getHttpURLConnection.setRequestProperty(HEADER_CACHE_CONTROL_NAME, HEADER_CACHE_CONTROL_VALUE); + } + + _log.debug(String.format("Request Headers: %s", getHttpURLConnection.getRequestProperties())); + + int responseCode = getHttpURLConnection.getResponseCode(); + + if (_log.isDebugEnabled()) { + _log.debug(String.format("[%s] %s. Status code: %s", + getHttpURLConnection.getRequestMethod(), + getHttpURLConnection.getURL().toString(), + responseCode)); + } + + String statusMessage = ""; + if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) { + _log.warn(String.format("Response status was: %s. Reason: %s", responseCode, + getHttpURLConnection.getResponseMessage())); + statusMessage = getHttpURLConnection.getResponseMessage(); + } + + InputStreamReader inputStreamReader = new InputStreamReader(getHttpURLConnection.getInputStream()); + BufferedReader br = new BufferedReader(inputStreamReader); + String strCurrentLine; + StringBuilder bld = new StringBuilder(); + while ((strCurrentLine = br.readLine()) != null) { + bld.append(strCurrentLine); + } + String responseBody = bld.toString(); + inputStreamReader.close(); + return new SplitHttpResponse(responseCode, + statusMessage, + responseBody, + getResponseHeaders(getHttpURLConnection)); + } catch (Exception e) { + throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e); + } + } + + public synchronized SplitHttpResponse post(URI uri, HttpEntity entity, Map> additionalHeaders) throws IOException { + HttpURLConnection postHttpURLConnection = null; + try { + postHttpURLConnection = (HttpURLConnection) uri.toURL().openConnection(); + return doPost(postHttpURLConnection, entity, additionalHeaders); + } catch (Exception e) { + throw new IllegalStateException(String.format("Problem in http post operation: %s", e), e); + } finally { + try { + if (postHttpURLConnection != null) { + postHttpURLConnection.disconnect(); + } + } catch (Exception e) { + _log.error(String.format("Could not close URL Connection: %s", e), e); + } + } + } + + public SplitHttpResponse doPost(HttpURLConnection postHttpURLConnection, + HttpEntity entity, + Map> additionalHeaders) { + try { + postHttpURLConnection.setRequestMethod("POST"); + setBasicHeaders(postHttpURLConnection); + setAdditionalAndDecoratedHeaders(postHttpURLConnection, additionalHeaders); + + postHttpURLConnection.setRequestProperty("Accept-Encoding", "gzip"); + postHttpURLConnection.setRequestProperty("Content-Type", "application/json"); + _log.debug(String.format("Request Headers: %s", postHttpURLConnection.getRequestProperties())); + + postHttpURLConnection.setDoOutput(true); + String postBody = EntityUtils.toString(entity); + OutputStream os = postHttpURLConnection.getOutputStream(); + os.write(postBody.getBytes(StandardCharsets.UTF_8)); + os.flush(); + os.close(); + _log.debug(String.format("Posting: %s", postBody)); + + int responseCode = postHttpURLConnection.getResponseCode(); + String statusMessage = ""; + if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) { + statusMessage = postHttpURLConnection.getResponseMessage(); + _log.warn(String.format("Response status was: %s. Reason: %s", responseCode, + statusMessage)); + } + return new SplitHttpResponse(responseCode, statusMessage, "", getResponseHeaders(postHttpURLConnection)); + } catch (Exception e) { + throw new IllegalStateException(String.format("Problem in http post operation: %s", e), e); + } + } + + private void setBasicHeaders(HttpURLConnection urlConnection) { + urlConnection.setRequestProperty(HEADER_API_KEY, "Bearer " + _apikey); + urlConnection.setRequestProperty(HEADER_CLIENT_VERSION, _metadata.getSdkVersion()); + urlConnection.setRequestProperty(HEADER_CLIENT_MACHINE_IP, _metadata.getMachineIp()); + urlConnection.setRequestProperty(HEADER_CLIENT_MACHINE_NAME, _metadata.getMachineName()); + urlConnection.setRequestProperty(HEADER_CLIENT_KEY, _apikey.length() > 4 + ? _apikey.substring(_apikey.length() - 4) + : _apikey); + } + + private void setAdditionalAndDecoratedHeaders(HttpURLConnection urlConnection, Map> additionalHeaders) { + if (additionalHeaders != null) { + for (Map.Entry> entry : additionalHeaders.entrySet()) { + for (String value : entry.getValue()) { + urlConnection.setRequestProperty(entry.getKey(), value); + } + } + } + HttpRequest request = new HttpGet(""); + _requestDecorator.decorateHeaders(request); + for (Header header : request.getHeaders()) { + urlConnection.setRequestProperty(header.getName(), header.getValue()); + } + } + + private Header[] getResponseHeaders(HttpURLConnection urlConnection) { + List responseHeaders = new ArrayList<>(); + Map> map = urlConnection.getHeaderFields(); + for (Map.Entry> entry : map.entrySet()) { + if (entry.getKey() != null) { + BasicHeader responseHeader = new BasicHeader(entry.getKey(), entry.getValue()); + responseHeaders.add(responseHeader); + } + } + return responseHeaders.toArray(new Header[0]); + } + @Override + public void close() throws IOException { + // Added for compatibility with HttpSplitClient, no action needed as URLConnection objects are closed. + } +} diff --git a/client/src/test/java/io/split/client/SplitClientConfigTest.java b/client/src/test/java/io/split/client/SplitClientConfigTest.java index 1b640071..86b18541 100644 --- a/client/src/test/java/io/split/client/SplitClientConfigTest.java +++ b/client/src/test/java/io/split/client/SplitClientConfigTest.java @@ -6,6 +6,7 @@ import io.split.client.impressions.ImpressionsManager; import io.split.client.dtos.RequestContext; import io.split.integrations.IntegrationsConfig; +import io.split.service.HttpAuthScheme; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -254,4 +255,16 @@ public Map> getHeaderOverrides(RequestContext context) { Assert.assertNull(config2.customHeaderDecorator()); } + + @Test + public void checkExpectedAuthScheme() { + SplitClientConfig cfg = SplitClientConfig.builder() + .authScheme(HttpAuthScheme.KERBEROS) + .build(); + Assert.assertEquals(HttpAuthScheme.KERBEROS, cfg.authScheme()); + + cfg = SplitClientConfig.builder() + .build(); + Assert.assertEquals(null, cfg.authScheme()); + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/client/SplitFactoryImplTest.java b/client/src/test/java/io/split/client/SplitFactoryImplTest.java index 2d548f9e..57441ced 100644 --- a/client/src/test/java/io/split/client/SplitFactoryImplTest.java +++ b/client/src/test/java/io/split/client/SplitFactoryImplTest.java @@ -2,7 +2,10 @@ import io.split.client.impressions.ImpressionsManager; import io.split.client.utils.FileTypeEnum; +import io.split.client.utils.SDKMetadata; import io.split.integrations.IntegrationsConfig; +import io.split.service.HttpAuthScheme; +import io.split.service.SplitHttpClientKerberosImpl; import io.split.storages.enums.OperationMode; import io.split.storages.pluggable.domain.UserStorageWrapper; import io.split.telemetry.storage.TelemetryStorage; @@ -22,6 +25,8 @@ import java.lang.reflect.Modifier; import java.net.URISyntaxException; +import static io.split.client.SplitClientConfig.splitSdkVersion; + public class SplitFactoryImplTest extends TestCase { public static final String API_KEY ="29013ionasdasd09u"; public static final String ENDPOINT = "https://sdk.split-stage.io"; @@ -344,4 +349,19 @@ public void testLocalhosJsonInputStreamNullAndFileTypeNull() throws URISyntaxExc Object splitChangeFetcher = method.invoke(splitFactory, splitClientConfig); Assert.assertTrue(splitChangeFetcher instanceof LegacyLocalhostSplitChangeFetcher); } + + @Test + public void testFactoryKerberosInstance() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { + SplitClientConfig splitClientConfig = SplitClientConfig.builder() + .setBlockUntilReadyTimeout(10000) + .authScheme(HttpAuthScheme.KERBEROS) + .build(); + SplitFactoryImpl splitFactory = new SplitFactoryImpl("asdf", splitClientConfig); + + Method method = SplitFactoryImpl.class.getDeclaredMethod("buildSplitHttpClient", String.class, + SplitClientConfig.class, SDKMetadata.class, RequestDecorator.class); + method.setAccessible(true); + Object SplitHttpClient = method.invoke(splitFactory, "asdf", splitClientConfig, new SDKMetadata(splitSdkVersion, "", ""), new RequestDecorator(null)); + Assert.assertTrue(SplitHttpClient instanceof SplitHttpClientKerberosImpl); + } } \ No newline at end of file diff --git a/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java b/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java new file mode 100644 index 00000000..4f814c31 --- /dev/null +++ b/client/src/test/java/io/split/service/HttpSplitClientKerberosTest.java @@ -0,0 +1,214 @@ +package io.split.service; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import io.split.TestHelper; +import io.split.client.RequestDecorator; +import io.split.client.dtos.*; +import io.split.client.impressions.Impression; +import io.split.client.utils.Json; +import io.split.client.utils.SDKMetadata; +import io.split.client.utils.Utils; +import io.split.engine.common.FetchOptions; + +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.core5.http.*; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; + +public class HttpSplitClientKerberosTest { + + @Test + public void testGetWithSpecialCharacters() throws URISyntaxException, IOException { + Map> responseHeaders = new HashMap>(); + responseHeaders.put((HttpHeaders.VIA), Arrays.asList("HTTP/1.1 s_proxy_rio1")); + + HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class); + when(mockHttpURLConnection.getHeaderFields()).thenReturn(responseHeaders); + + RequestDecorator decorator = new RequestDecorator(null); + + Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + ByteArrayInputStream stubInputStream = new ByteArrayInputStream(Files.asCharSource( + new File("src/test/resources/split-change-special-characters.json"), Charsets.UTF_8).read().getBytes(Charsets.UTF_8)); + when(mockHttpURLConnection.getInputStream()).thenReturn(stubInputStream); + + SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata()); + + Map> additionalHeaders = Collections.singletonMap("AdditionalHeader", + Collections.singletonList("add")); + + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doGet(mockHttpURLConnection, + new FetchOptions.Builder().cacheControlHeaders(true).build(), additionalHeaders); + SplitChange change = Json.fromJson(splitHttpResponse.body(), SplitChange.class); + + Header[] headers = splitHttpResponse.responseHeaders(); + assertThat(headers[0].getName(), is(equalTo("Via"))); + assertThat(headers[0].getValue(), is(equalTo("[HTTP/1.1 s_proxy_rio1]"))); + 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")); + Assert.assertEquals(2, split.sets.size()); + } + + @Test + public void testGetParameters() throws URISyntaxException, MalformedURLException { + URI uri = new URI("https://api.split.io/splitChanges?since=1234567"); + FetchOptions options = new FetchOptions.Builder().cacheControlHeaders(true).build(); + SplitHttpClientKerberosImpl splitHtpClientKerberos = Mockito.mock(SplitHttpClientKerberosImpl.class); + when(splitHtpClientKerberos.get(uri, options, null)).thenCallRealMethod(); + + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.get(uri, options, null); + + ArgumentCaptor connectionCaptor = ArgumentCaptor.forClass(HttpURLConnection.class); + ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(FetchOptions.class); + ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(HashMap.class); + verify(splitHtpClientKerberos).doGet(connectionCaptor.capture(), optionsCaptor.capture(), headersCaptor.capture()); + + assertThat(connectionCaptor.getValue().getRequestMethod(), is(equalTo("GET"))); + assertThat(connectionCaptor.getValue().getURL().toString(), is(equalTo(new URL("https://api.split.io/splitChanges?since=1234567").toString()))); + + assertThat(optionsCaptor.getValue().cacheControlHeadersEnabled(), is(equalTo(true))); + } + + @Test + public void testGetError() throws URISyntaxException, IOException { + HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class); + RequestDecorator decorator = new RequestDecorator(null); + + Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR); + ByteArrayInputStream stubInputStream = new ByteArrayInputStream(Files.asCharSource( + new File("src/test/resources/split-change-special-characters.json"), Charsets.UTF_8).read().getBytes(Charsets.UTF_8)); + when(mockHttpURLConnection.getInputStream()).thenReturn(stubInputStream); + + SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata()); + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doGet(mockHttpURLConnection, + new FetchOptions.Builder().cacheControlHeaders(true).build(), null); + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, (long) splitHttpResponse.statusCode()); + } + + @Test(expected = IllegalStateException.class) + public void testException() throws URISyntaxException, InvocationTargetException, NoSuchMethodException, + IllegalAccessException, IOException { + HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class); + RequestDecorator decorator = null; + + Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR); + ByteArrayInputStream stubInputStream = new ByteArrayInputStream(Files.asCharSource( + new File("src/test/resources/split-change-special-characters.json"), Charsets.UTF_8).read().getBytes(Charsets.UTF_8)); + when(mockHttpURLConnection.getInputStream()).thenReturn(stubInputStream); + + SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata()); + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doGet(mockHttpURLConnection, + new FetchOptions.Builder().cacheControlHeaders(true).build(), null); + } + + @Test + public void testPost() throws URISyntaxException, IOException, ParseException { + HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class); + RequestDecorator decorator = new RequestDecorator(null); + Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + + SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata()); + + // Send impressions + List toSend = Arrays.asList(new TestImpressions("t1", Arrays.asList( + KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null)), + KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null)), + KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null)))), + new TestImpressions("t2", Arrays.asList( + KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null)), + KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null)), + KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null))))); + + Map> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode", + Collections.singletonList("OPTIMIZED")); + when(mockHttpURLConnection.getHeaderFields()).thenReturn(additionalHeaders); + + ByteArrayOutputStream mockOs = Mockito.mock( ByteArrayOutputStream.class); + when(mockHttpURLConnection.getOutputStream()).thenReturn(mockOs); + + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doPost(mockHttpURLConnection, Utils.toJsonEntity(toSend), + additionalHeaders); + + // Capture outgoing request and validate it + ArgumentCaptor captor = ArgumentCaptor.forClass(byte[].class); + verify(mockOs).write(captor.capture()); + String postBody = EntityUtils.toString(Utils.toJsonEntity(toSend)); + assertThat(captor.getValue(), is(equalTo(postBody.getBytes(StandardCharsets.UTF_8)))); + + Header[] headers = splitHttpResponse.responseHeaders(); + assertThat(headers[0].getName(), is(equalTo("SplitSDKImpressionsMode"))); + assertThat(headers[0].getValue(), is(equalTo("[OPTIMIZED]"))); + + Assert.assertEquals(200, (long) splitHttpResponse.statusCode()); + } + + @Test + public void testPotParameters() throws URISyntaxException, IOException { + URI uri = new URI("https://kubernetesturl.com/split/api/testImpressions/bulk"); + SplitHttpClientKerberosImpl splitHtpClientKerberos = Mockito.mock(SplitHttpClientKerberosImpl.class); + when(splitHtpClientKerberos.post(uri, null, null)).thenCallRealMethod(); + + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.post(uri, null, null); + + ArgumentCaptor connectionCaptor = ArgumentCaptor.forClass(HttpURLConnection.class); + ArgumentCaptor entityCaptor = ArgumentCaptor.forClass(HttpEntity.class); + ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(HashMap.class); + verify(splitHtpClientKerberos).doPost(connectionCaptor.capture(), entityCaptor.capture(), headersCaptor.capture()); + + assertThat(connectionCaptor.getValue().getURL().toString(), is(equalTo(new URL("https://kubernetesturl.com/split/api/testImpressions/bulk").toString()))); + } + + @Test + public void testPosttError() throws URISyntaxException, IOException { + HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class); + RequestDecorator decorator = new RequestDecorator(null); + ByteArrayOutputStream mockOs = Mockito.mock( ByteArrayOutputStream.class); + Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR); + when(mockHttpURLConnection.getOutputStream()).thenReturn(mockOs); + + SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata()); + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doPost(mockHttpURLConnection, + Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null); + + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, (long) splitHttpResponse.statusCode()); + } + + @Test(expected = IllegalStateException.class) + public void testPosttException() throws URISyntaxException, IOException { + HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class); + RequestDecorator decorator = null; + Mockito.when(mockHttpURLConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + + SplitHttpClientKerberosImpl splitHtpClientKerberos = SplitHttpClientKerberosImpl.create(decorator, "qwerty", metadata()); + SplitHttpResponse splitHttpResponse = splitHtpClientKerberos.doPost(mockHttpURLConnection, + Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null); + } + + private SDKMetadata metadata() { + return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP"); + } + +} diff --git a/pluggable-storage/pom.xml b/pluggable-storage/pom.xml index f643564f..39128cb9 100644 --- a/pluggable-storage/pom.xml +++ b/pluggable-storage/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.12.1 + 4.13.0-rc1 2.1.0 diff --git a/pom.xml b/pom.xml index 3dc7d33f..bf2f4cb0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 io.split.client java-client-parent - 4.12.1 + 4.13.0-rc1 diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml index a8ce195f..e0a3c838 100644 --- a/redis-wrapper/pom.xml +++ b/redis-wrapper/pom.xml @@ -6,7 +6,7 @@ java-client-parent io.split.client - 4.12.1 + 4.13.0-rc1 redis-wrapper 3.1.0 diff --git a/testing/pom.xml b/testing/pom.xml index 2240c94d..556d98af 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -5,7 +5,7 @@ io.split.client java-client-parent - 4.12.1 + 4.13.0-rc1 java-client-testing jar